Cypress E2E Testing
Cypress is a popular end-to-end testing tool built around developer ergonomics: it runs in the browser alongside your app, automatically waits for elements, and offers time-travel debugging where you can step back through each command and inspect the DOM at that moment. The result is a tight, visual feedback loop that many developers find friendlier than out-of-process runners.
Like all E2E tooling, Cypress belongs at the top of the pyramid — a small, stable suite for critical user flows, with most logic verified by faster unit and integration tests. Understanding its command-queue model is the key to writing tests that aren't flaky.
TL;DR
- Great for E2E and component testing of web apps.
- Keep tests behavior-focused (user-visible outcomes).
- Reduce flakiness with stable selectors and deterministic data.
- Use network stubbing carefully — faster, but less realistic.
Quick Example
Commands read like a user's actions and auto-wait at each step:
Setup
The Command-Queue Model
Cypress feels different because of how it runs:
- It runs alongside the browser and controls it directly.
- Commands are queued and retried automatically until they pass or time out.
This is what makes tests read simply without explicit waits — but you need the model in mind: commands are asynchronous and chained, not imperative line-by-line code.
Selectors & Custom Commands
- Prefer
data-testid(or similar) for stability; avoid brittle CSS selectors that break on restyling. - Custom commands keep tests readable —
cy.login()for fast auth,cy.createProject()to seed via API — but don't hide so much that tests become opaque.
Network Stubbing
Stubbing (cy.intercept) helps when the backend is unstable, you want to test edge cases deterministically, or you want faster feedback. But don't stub everything — keep some real-backend tests to catch integration issues that stubs would hide.
Component Testing
Cypress can also test components in isolation, in a real browser — faster than full E2E while keeping real rendering. This complements unit tests rather than replacing them.
Best Practices
- Test behavior, asserting user-visible outcomes, not implementation.
- Stable selectors + deterministic data to kill flakiness.
- Set up state via API (
cy.request) instead of clicking through the UI. - Reset DB/test data between tests; never depend on test order.
- Keep the suite small and run headless in CI with screenshots/videos on failure.
Common Mistakes
Order-dependent, shared-state tests
Stubbing everything
FAQ
Cypress or Playwright?
Cypress runs in-browser with a polished interactive runner and time-travel debugging that's great for developer experience and debugging single-browser web apps. Playwright runs out-of-process, supports Chromium/Firefox/WebKit with true parallelism, and has first-class tracing — stronger for cross-browser coverage and large CI suites. Choose Cypress for DX and in-browser debugging, Playwright for breadth and scale.
Why do my Cypress commands behave asynchronously?
Because Cypress queues commands and runs them asynchronously with automatic retries — cy.get(...) doesn't return an element synchronously; it enqueues work. That's why you chain with .then() and assertions rather than assigning results to variables. Fighting the queue (mixing in synchronous logic that expects immediate values) is a common source of confusion and bugs.
How do I keep Cypress tests from being flaky?
Use stable data-testid selectors, rely on Cypress's built-in retry/auto-wait (avoid cy.wait(ms) with fixed times — wait on a request or assertion instead), seed and reset data so tests are independent, and don't over-stub. Most flakiness comes from timing assumptions, shared state, or test-order dependencies — remove those.
Should I stub the network or hit a real backend?
Both, deliberately. Stub (cy.intercept) for speed, deterministic edge cases, and when the backend is unreliable. But keep a layer of tests against a real backend so genuine integration problems — serialization, auth, schema mismatches — surface. Stubbing everything gives fast but false confidence.
Related Topics
- Testing — The hub
- Playwright — The main alternative
- Integration Testing — The layer below E2E
- Unit Testing — Where most logic belongs
- API Testing — Testing the backend directly