45Cypress (full module)
What you will master here
- Cypress architecture — runs INSIDE the browser
- Command queue + automatic retries
- Selecting elements;
cy.contains,cy.get - Network:
cy.interceptfor stub + spy - Fixtures, custom commands, cy.session
- Limitations: one tab, single origin, no real multi-page
- Cypress 13+:
cy.origin, component testing, parallelism
45.1 Architecture
Cypress runs inside the browser alongside your app. A Node.js helper handles things the browser can't (filesystem, network proxy). This in-process design = very fast, automatic command queueing, snapshot-able UI state.
Cypress test code ──runs in same browser as── Your app
│
└─ Node helper (file IO, plugins)
Consequences (good + bad):
- + Fast: no protocol round trips
- + Time-travel debugger built into the runner UI
- + Automatic retries on each command in the chain
- − One browser tab, one origin per test (cy.origin opens new origin)
- − No support for true multi-tab popups
- − Until v10 no WebKit (now experimental)
45.2 First test
// cypress/e2e/login.cy.js
describe('login', () => {
beforeEach(() => cy.visit('/login'));
it('happy path', () => {
cy.get('[data-cy=email]').type('alice@test.com');
cy.get('[data-cy=password]').type('p4ssw0rd');
cy.contains('button', 'Sign in').click();
cy.url().should('include', '/dashboard');
cy.contains('Welcome, Alice').should('be.visible');
});
});
45.3 Command chains + retry-ability
Every Cypress command returns a chain. Assertions retry until they pass or timeout (default 4 s). Same flake-resistance as Playwright web-first assertions.
cy.get('.notification') // re-finds until ≤4s
.should('be.visible') // retries assertion
.and('contain.text', 'Saved'); // chained assertion
// Aliases (@alias) for reuse
cy.intercept('GET', '/api/users').as('users');
cy.visit('/');
cy.wait('@users').its('response.statusCode').should('eq', 200);
45.4 Network interception
// Stub
cy.intercept('GET', '/api/orders', { fixture: 'orders.json' });
// Stub with inline body + delay
cy.intercept('POST', '/api/checkout', {
statusCode: 500,
body: { message: 'down' },
delay: 1000,
});
// Spy (pass-through, observe only)
cy.intercept('POST', '/api/track').as('track');
cy.contains('Click').click();
cy.wait('@track').its('request.body').should('deep.include', { event: 'cta' });
// Modify request before sending
cy.intercept('POST', '/api/x', (req) => {
req.headers['x-test'] = '1';
req.continue();
});
45.5 Fixtures, custom commands, sessions
// cypress/fixtures/user.json
{ "email": "alice@test.com", "password": "p4ssw0rd" }
// In test
cy.fixture('user').then(u => cy.get('[data-cy=email]').type(u.email));
// Custom commands (cypress/support/commands.js)
Cypress.Commands.add('signIn', (email, pw) => {
cy.session([email], () => {
cy.visit('/login');
cy.get('[data-cy=email]').type(email);
cy.get('[data-cy=password]').type(pw);
cy.contains('button','Sign in').click();
cy.url().should('include', '/dashboard');
});
});
// In test
cy.signIn('alice@test.com', 'p4ssw0rd');
45.6 Multi-origin with cy.origin
// Modern Cypress (12+) — visit a different origin in the same test
cy.visit('/');
cy.contains('Sign in with Google').click();
cy.origin('https://accounts.google.com', () => {
cy.get('input[type=email]').type(Cypress.env('GOOGLE_EMAIL'));
cy.contains('Next').click();
});
cy.url().should('include', '/dashboard');
45.7 Cypress vs Playwright — interview comparison
| Concern | Cypress | Playwright |
|---|---|---|
| Architecture | In-browser | Out-of-process (WebSocket+CDP) |
| Multi-tab | No (workarounds) | Native |
| Multi-origin | cy.origin | Native, any origin freely |
| Browser engines | Chromium, Firefox, WebKit (experimental) | Chromium, Firefox, WebKit (all first-class) |
| Parallelism | Paid Cypress Cloud | Free, workers + sharding |
| Language | JS/TS only | JS/TS, Python, Java, .NET |
| iframes | Workarounds (cypress-iframe) | frameLocator (built-in) |
| Time-travel debugger | Built-in runner | Trace Viewer |
| API testing | cy.request | request fixture |
| Component testing | Native (CT mode) | Separate Playwright Component Testing |
Module 45 — Cypress Q&A
Why is Cypress fast?
It runs in the same process as the app — no remote protocol round trips. Each command operates directly on the DOM. Trade-off: one tab, one origin per test (until cy.origin).
What's the command queue?
Cypress chains commands; each enqueues then runs sequentially with automatic retry on assertions. You don't await — the runner handles ordering. Side effect: commands don't return promises;
.then() hands off the result.cy.intercept vs cy.request?
cy.intercept stubs/spies XHR/fetch made BY the app (e.g. while clicking through). cy.request makes a direct HTTP call FROM the test (useful for API testing or setup).
Why is Cypress limited to one tab?
Its in-browser architecture pins it to one execution context. Popups and target=_blank links don't work natively. Workarounds: open via cy.visit, or use cy.window().its('open') to stub window.open.
What is cy.session?
Cache an authenticated session across tests. The login steps run once per session id; subsequent tests reuse cookies/localStorage. Massive speedup for suites that all need auth.
How does Cypress handle iframes?
Not natively. Workarounds:
cy.get('iframe').its('0.contentDocument.body').then(cy.wrap).find('...'), or the cypress-iframe plugin. This is one of Cypress's main pain points vs Playwright.How do you run Cypress in parallel?
Use Cypress Cloud (paid) or Sorry Cypress (open-source self-hosted) for sharded parallel runs. Cypress itself doesn't shard for free.
How do you set up data-driven tests?
Fixture file + loop:
cy.fixture('users').then(users => users.forEach(u => it(`logs in ${u.email}`, () => { ... }))). Or use it.each-style external libs.What's the biggest reason teams choose Playwright over Cypress now?
Multi-tab and multi-origin scenarios, free parallelism, all three browser engines as first-class, lower total cost. Cypress still wins on time-travel debugger feel and component testing maturity.