Cypress is an open-source JavaScript test framework for end-to-end and component testing of web applications, in which test code runs inside the same browser as the application under test rather than controlling it from outside. In web testing it is widely used by frontend teams to simulate user journeys such as signing in, filling forms and completing purchases, and to verify the interface automatically on every code change.
Its distinctive architecture gives it automatic waiting, time-travel debugging and direct network control, which make tests easier to write and debug. The same design also brings limits, notably around multiple tabs, multiple origins and browser coverage, that separate it from neighbors such as Playwright and Selenium.
How Cypress runs tests inside the browser
Cypress starts a Node.js server process and launches a browser in which the application loads in an iframe while the test runner occupies the surrounding page. Test commands execute in the browser’s JavaScript context, so they can access the DOM, the window object and application code directly. The Node.js process handles tasks the browser cannot, such as file system access, database seeding and network proxying.
Commands such as cy.get(), cy.click() and cy.contains() do not run immediately. They are enqueued and executed in order, and each query is retried until its assertion passes or a timeout expires, by default 4 seconds. This built-in retry-ability removes most manual sleeps and is the main reason Cypress tests are less prone to timing failures than naive scripts.
| Element | Example | Role |
|---|---|---|
| Queries | cy.get, cy.contains, cy.find | Locate elements, retried automatically |
| Actions | cy.click, cy.type, cy.select | Simulate user interaction |
| Assertions | should, expect | Verify state, retried with the preceding query |
| Network control | cy.intercept | Observe, stub or delay HTTP requests |
| Node tasks | cy.task | Run server-side code such as database resets |
| Custom commands | Cypress.Commands.add | Reuse steps such as logging in |
End-to-end and component testing modes
Cypress offers two testing types. End-to-end testing loads the full application from a URL and exercises it as a user would. Component testing mounts a single UI component in isolation using the project’s bundler, which is faster and lets developers test states that are hard to reach through the full interface.
| Aspect | End-to-end testing | Component testing |
|---|---|---|
| Scope | Whole application with backend | One component with mocked props and data |
| Speed | Seconds per test | Typically well under a second per test |
| Setup | Running application and test data | Framework adapter and bundler configuration |
| Best for | Critical user journeys | Visual states, edge cases, design system parts |
A balanced suite follows the testing pyramid: many fast unit tests, a solid layer of component tests and a smaller set of end-to-end tests covering the journeys that generate revenue or risk. Running hundreds of end-to-end tests for every detail usually produces slow, fragile pipelines.
Cypress compared with Playwright and Selenium
Cypress, Playwright and Selenium automate browsers in fundamentally different ways. Selenium drives browsers from outside through the WebDriver protocol, Playwright controls them through low-level automation protocols from a separate process, and Cypress runs inside the page. These architectures explain most differences in capability.
| Property | Cypress | Playwright | Selenium |
|---|---|---|---|
| Architecture | Runs in the browser | Controls browser from outside | WebDriver protocol |
| Languages | JavaScript and TypeScript | JavaScript, Python, Java, .NET | Most major languages |
| Browsers | Chromium family, Firefox, WebKit experimental | Chromium, Firefox, WebKit | All major browsers |
| Multiple tabs | Not supported | Supported | Supported |
| Automatic waiting | Yes, retry-based | Yes, auto-wait on actions | Explicit waits required |
| Debugging | Interactive runner with snapshots | Trace viewer | Depends on tooling |
Cypress is often the easiest start for frontend developers because of its interactive runner. Playwright tends to win when tests need multiple tabs, several origins or parallel runs without a paid service. Selenium remains relevant for broad language and browser grid coverage.
Writing reliable Cypress tests
Reliable tests depend on stable selectors, controlled data and independent tests. Selecting elements by CSS classes or text that designers change frequently is the most common source of breakage; dedicated attributes such as data-cy or accessible roles and labels are far more stable.
- Reset state before each test with
cy.taskor API calls instead of depending on the previous test. - Log in programmatically through an API request and
cy.sessionrather than through the login form in every test. - Stub unstable third-party requests with
cy.interceptwhile keeping at least one test against the real backend. - Avoid fixed waits such as
cy.wait(3000); wait for an aliased request or an assertion instead. - Keep tests short and focused on one behavior so a failure points to a clear cause.
Limitations and common mistakes
The in-browser architecture brings real constraints. Tests cannot open or switch between tabs, and visiting several origins in one test requires the cy.origin command. Native browser dialogs, file downloads and hover-only menus need workarounds, because Cypress dispatches synthetic events by default rather than operating-system input.
| Problem | Typical cause | Remedy |
|---|---|---|
| Flaky tests | Shared state or uncontrolled network timing | Isolate data and alias requests |
| Slow suite | Too many UI logins and long journeys | Use cy.session and component tests |
| Broken selectors | Styling classes used as locators | Use data attributes or roles |
| False passes | Assertions on stubbed data only | Keep a contract or real-backend test |
| Async confusion | Treating commands as promises | Chain commands and use then callbacks |
A common conceptual mistake is assigning a command result to a variable as if it were synchronous. Cypress commands return chainable objects, not values, so data must be passed through .then() or aliases.
Explained without jargon
Cypress is a tool that acts like a tireless tester. It opens a website in a browser and clicks buttons. It types into forms and checks that the right things appear on the screen.
Developers write down the steps once, for example: log in, add a product to the cart, pay. Cypress then repeats those steps after every change to the website. If something breaks, it shows a recording of each step. That makes it easy to see what went wrong.
A typical use case
A software company sells a subscription web app, and a release once broke the upgrade-plan button for users with expired cards without anyone noticing for two days. The team decides to protect its critical journeys with automated tests.
They write 25 Cypress end-to-end tests covering sign-up, login, plan upgrade, billing updates and cancellation, plus about 150 component tests for form fields and pricing tables. Test users are created through an API task before each test, login uses cy.session, and the payment provider is stubbed with cy.intercept except in one nightly test that uses its sandbox environment.
The suite runs in the continuous integration pipeline in about eight minutes on every pull request. Within the first quarter it blocks three releases that would have broken billing flows, and the team no longer relies on manual checks before each deployment.