Cypress is an open-source JavaScript testing framework built for the modern web. It lets developers write fast, reliable end-to-end, integration, and component tests that run directly inside the browser alongside the application under test. Since its launch, Cypress has become one of the most popular alternatives to Selenium for front-end teams.
Definition: What Is Cypress?#
Cypress is a Node.js based testing tool that runs your tests in the same run loop as your application. Instead of driving the browser from the outside through a separate protocol, Cypress executes inside the browser, which gives it direct access to the DOM (document object model), the network layer, and your application code.
This architecture is the single biggest difference between Cypress and tools like Selenium or Playwright, and it shapes most of its strengths and limitations.
Key Characteristics#
- Runs in the browser: Tests execute in the same context as your app, not over a remote driver
- All-in-one: Test runner, assertion library, and mocking come bundled, no extra setup
- Automatic waiting: Commands retry until elements are ready, so you rarely write manual waits
- Time-travel debugging: The Test Runner snapshots each step so you can inspect exactly what happened
- JavaScript and TypeScript first: Tests are written in the same language as most front-end code
Core Features of Cypress#
1. Automatic Waiting and Retries#
Cypress automatically waits for elements to exist and become actionable before it acts on them. You do not need explicit sleeps:
// Cypress retries this command until the button exists,// is visible, and is enabled, or the timeout is reachedcy.get("[data-cy=submit]").click();
// Cypress retries this command until the button exists,// is visible, and is enabled, or the timeout is reachedcy.get("[data-cy=submit]").click();
This removes the most common cause of flaky tests: race conditions between the test and the app.
2. Time-Travel Debugging#
The Cypress Test Runner takes a snapshot of your application at every command. You can hover over each step in the command log and see the exact state of the page at that moment, which makes failures far easier to diagnose.
3. Network Stubbing and Spying#
Cypress can intercept, stub, and assert on network requests, so you can test edge cases without a live backend:
// Stub the API responsecy.intercept("GET", "/api/users", {statusCode: 200,body: [{ id: 1, name: "Test User" }],}).as("getUsers");cy.visit("/users");cy.wait("@getUsers");
// Stub the API responsecy.intercept("GET", "/api/users", {statusCode: 200,body: [{ id: 1, name: "Test User" }],}).as("getUsers");cy.visit("/users");cy.wait("@getUsers");
4. Screenshots and Videos#
When a test fails in headless mode, Cypress automatically captures a screenshot, and it can record a full video of the run. This is useful for debugging failures in CI (continuous integration).
// Manual screenshotcy.screenshot("after-login");
// Manual screenshotcy.screenshot("after-login");
5. Component Testing#
Beyond end-to-end tests, Cypress can mount and test individual UI components in isolation for frameworks like React, Vue, and Angular, which sits between unit tests and full browser tests.
When to Use Cypress#
Cypress is a strong fit for:
1. End-to-End Testing of Web Apps#
Testing complete user journeys through a single web application.
Example use case: Verifying a signup flow from the form through to the welcome screen. See our end-to-end testing guide for the broader approach.
2. Front-End Teams Working in JavaScript#
Teams that already write JavaScript or TypeScript get a low learning curve, since tests use the same language and tooling as the app.
3. Fast Feedback During Development#
The interactive Test Runner reloads tests as you save, giving near-instant feedback while you build features.
4. Component Testing#
Validating individual components in a real browser without spinning up the whole app.
Cypress vs Alternatives#
Cypress is often weighed against Selenium and Playwright. Each makes a different architectural trade-off.
| Feature | Cypress | Selenium | Playwright |
|---|---|---|---|
| Architecture | Runs inside browser | Drives browser via WebDriver | Drives browser via protocol |
| Languages | JavaScript, TypeScript | Many (Java, Python, C#, etc.) | JavaScript, Python, Java, .NET |
| Auto-wait | Built-in | Manual waits required | Built-in |
| Multi-tab / multi-origin | Limited | Supported | Supported |
| Browser support | Chromium family, Firefox, WebKit (experimental) | All major browsers | Chromium, Firefox, WebKit |
| Setup | Single dependency | Requires driver management | Single installer |
For a deeper head-to-head, read Cypress vs Selenium and What Is Playwright?.
Choose Cypress when you have a JavaScript front end, want fast developer feedback, and test a single web app.
Choose Selenium when you need multiple language bindings, broad browser coverage, or legacy support.
Choose Playwright when you need multi-tab or multi-origin flows, cross-browser parity, or non-JavaScript language support.
Limitations to Know#
Cypress is excellent within its design, but the in-browser architecture comes with constraints:
- One browser tab at a time: Multi-tab scenarios are not natively supported
- JavaScript and TypeScript only: No bindings for other languages
- Cross-origin is restricted: Visiting multiple domains in one test needs
cy.originand has limits - No native Safari: WebKit support is experimental
If any of these are central to your tests, Playwright or Selenium may fit better.
Getting Started with Cypress#
Installation#
npm install cypress --save-dev
npm install cypress --save-dev
Open the Test Runner#
npx cypress open
npx cypress open
This scaffolds the cypress/ folder, adds example specs, and opens the interactive runner where you can pick a browser.
Your First Test#
describe("homepage", () => {it("loads and shows the title", () => {cy.visit("https://example.cypress.io");cy.contains("type").click();cy.url().should("include", "/commands/actions");});});
describe("homepage", () => {it("loads and shows the title", () => {cy.visit("https://example.cypress.io");cy.contains("type").click();cy.url().should("include", "/commands/actions");});});
Running Tests#
# Open the interactive Test Runnernpx cypress open# Run all tests headlessly (for CI)npx cypress run# Run a single spec filenpx cypress run --spec "cypress/e2e/login.cy.js"
# Open the interactive Test Runnernpx cypress open# Run all tests headlessly (for CI)npx cypress run# Run a single spec filenpx cypress run --spec "cypress/e2e/login.cy.js"
Best Practices#
- Use dedicated test selectors. Add
data-cyattributes to key elements so tests do not break when styling or text changes:
<button data-cy="submit" type="submit">Sign in</button>
<button data-cy="submit" type="submit">Sign in</button>
cy.get("[data-cy=submit]").click();
cy.get("[data-cy=submit]").click();
- Lean on automatic retries instead of fixed waits:
// Good: retries until the element is readycy.get("[data-cy=welcome]").should("be.visible");// Avoid: brittle and slowcy.wait(2000);
// Good: retries until the element is readycy.get("[data-cy=welcome]").should("be.visible");// Avoid: brittle and slowcy.wait(2000);
- Stub the network for unstable dependencies so tests stay fast and deterministic with
cy.intercept. - Keep tests independent. Each test should set up its own state rather than depending on a previous test.
Conclusion#
Cypress pairs an in-browser architecture with automatic waiting and time-travel debugging to give front-end teams a fast, friendly testing experience. It shines for JavaScript single-app end-to-end testing, with the trade-off of being scoped to one browser tab and the JavaScript ecosystem.
Whichever framework you pick, someone still has to write and maintain every test as the UI changes. ObserveOne's Autopilot takes a different path: it generates Playwright tests from a URL and self-heals selectors when the app shifts, so your coverage survives redesigns instead of breaking on every refactor.