Cypress
In-browser test execution, automatic retry-ability and time-travel debugging — what Cypress's architecture buys, and the constraints that come with it.
2 min read · updated 19 September 2026
Cypress runs inside the browser, in the same run loop as your application. That single architectural decision explains everything people love and everything they find limiting about it.
#What the architecture buys
Retry-ability everywhere. Every assertion automatically retries until it passes or times out. There is no equivalent of a stale element and no need to think about waiting.
// No awaits, no waits. The queue handles ordering; assertions retry.
it('adds an item to the cart', () => {
cy.visit('/products/field-notes');
cy.findByRole('button', { name: 'Add to cart' }).click();
cy.findByRole('status').should('have.text', '1 item in cart');
cy.findByRole('link', { name: 'Checkout' }).should('be.enabled');
});Time-travel debugging. The Cypress runner keeps a DOM snapshot for every command. Hover over any step afterwards and the application appears exactly as it was at that moment. For diagnosing a failure this is genuinely excellent and remains a real differentiator.
Direct access to the application. Because the test shares a process with the app, you can reach in:
// Seed state through the app's own store rather than clicking through a form.
cy.window().its('store').invoke('dispatch', { type: 'cart/add', payload: item });
// Stub a network call at the browser level and wait on it by name.
cy.intercept('POST', '/api/orders', { statusCode: 402, body: { error: 'declined' } })
.as('placeOrder');
cy.findByRole('button', { name: /^Pay/ }).click();
cy.wait('@placeOrder');
cy.findByRole('alert').should('contain.text', 'declined');cy.intercept plus cy.wait('@alias') is the cleanest network-driven
waiting model of the three major tools.
#What the architecture costs
No multi-tab or multi-origin freedom. Cypress lives in the page. A
second tab, a native dialog, or an OAuth redirect to another origin all
require workarounds — cy.origin() handles the last of these, and it is
still more friction than the alternatives.
No real cross-browser parity. Chromium-family browsers and Firefox are supported; WebKit support is experimental. If Safari matters, Playwright or Selenium with a device cloud is the answer.
Parallelisation is a product. Splitting a suite across machines with load balancing is a Cypress Cloud feature. You can shard by hand, but the polished version is paid — which is the main reason large suites migrate.
The command queue surprises people. cy.get() does not return an
element; it enqueues a command. This is fine until you try to mix it with
ordinary JavaScript:
// Does not work — text is a Chainable, not a string.
const text = cy.get('h1').invoke('text');
if (text === 'Hello') { /* never true */ }
// Works — stay inside the chain.
cy.get('h1').invoke('text').then((text) => {
expect(text).to.equal('Hello');
});#Component testing
Cypress component testing mounts a single component in a real browser with your real bundler config. Unlike jsdom-based Testing Library setups, layout, CSS and real event dispatch all work.
// cypress/component/PriceTag.cy.tsx
import { PriceTag } from '../../src/PriceTag';
it('shows a strikethrough original price when discounted', () => {
cy.mount(<PriceTag cents={1_200} wasCents={1_500} />);
cy.findByText('£12.00').should('be.visible');
cy.findByText('£15.00').should('have.css', 'text-decoration-line', 'line-through');
});That last assertion — a computed CSS property — is one no jsdom test can make, and it is the strongest argument for browser-based component testing.
#Configuration worth having
// cypress.config.ts
export default defineConfig({
e2e: {
baseUrl: 'http://127.0.0.1:3000',
retries: { runMode: 1, openMode: 0 }, // one retry in CI, none locally
video: false, // large, and the snapshots are better
screenshotOnRunFailure: true,
experimentalRunAllSpecs: true
},
// A per-spec browser restart is the default and it is slow; keep specs
// few and large rather than many and small.
numTestsKeptInMemory: 20
});Install @testing-library/cypress and prefer findByRole over cy.get
with a CSS selector — the argument in
DOM testing applies identically here.
#Choosing between the three
| Cypress | Playwright | Selenium | |
|---|---|---|---|
| Debugging experience | best | very good (trace) | basic |
| Cross-browser | Chromium, Firefox | all three engines | all, plus devices |
| Parallel across machines | paid | free | Grid |
| Multi-tab / multi-origin | limited | full | full |
| Language support | JS/TS only | JS/TS, Python, .NET, Java | everything |
| Component testing | real browser | real browser | no |
Cypress is a strong choice for a front-end team working in JavaScript on a Chromium-first product, where the developer experience is worth more than the scale ceiling. Beyond a few hundred specs, or where Safari matters, the constraints start to bite.
Common questions
- Can Cypress test multiple browser tabs?
- Not directly. Cypress runs inside the browser alongside your application, which means it cannot drive a second tab or a separate window. The usual workaround is to assert the target URL and then visit it in the same tab, which covers most real cases.
- Is Cypress slower than Playwright?
- Generally yes at scale, mainly because parallelisation across machines is a paid Cloud feature and the per-spec startup cost is higher. For a small to medium suite on one machine the difference is not usually what decides it.
- Why does Cypress not need awaits?
- Commands are enqueued rather than executed immediately, and the queue runs asynchronously. That makes the syntax clean and means you cannot mix Cypress commands with ordinary async code the way you might expect — a common source of confusion for people arriving from Playwright.
Runnable samples for this page
last test results ↗- JavaScript (Cypress)
cypress-samples/cypress/e2e/tools/cypress
Working tests, not fragments — they run in CI on every push to 8exgh/endtoendtester-samples.
Was this page useful?
Related topics
- PlaywrightThe default browser automation tool in 2026 — auto-waiting locators, tracing, sharding and fixtures — with the configuration that matters and the mistakes that still cause flakiness.
- Selenium WebDriverThe W3C standard for browser automation — where it still wins, the waiting model that decides whether a suite is stable, and Grid for scale.
- Testing ReactComponents, hooks, context, server components and async state — what to test in a React application, what to leave alone, and how to avoid act warnings.
- End-to-End TestingWhat belongs in an end-to-end suite and what does not, how many journeys are enough, and the practices that keep a browser suite from becoming the thing everyone ignores.
- Flaky TestsWhy tests fail intermittently, the six root causes and how to fix each one, how to detect flakiness deliberately, and what to do with a test you cannot fix today.
- End-to-End Code CoverageInstrument the application, run Playwright or Cypress against it, and merge the result with the unit run — the measurement that shows which code only your slowest tests protect.
- Screen RecordingsVideo of a test run — when it is worth the storage, how to configure it so it costs nothing on green runs, and why a trace is usually better.
- Traces and Debugging CI FailuresA trace records every action, request, console message and DOM snapshot of a run — the single artefact that turns an unreproducible CI failure into a five-minute diagnosis.