Cypress (full)

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.intercept for 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):

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

ConcernCypressPlaywright
ArchitectureIn-browserOut-of-process (WebSocket+CDP)
Multi-tabNo (workarounds)Native
Multi-origincy.originNative, any origin freely
Browser enginesChromium, Firefox, WebKit (experimental)Chromium, Firefox, WebKit (all first-class)
ParallelismPaid Cypress CloudFree, workers + sharding
LanguageJS/TS onlyJS/TS, Python, Java, .NET
iframesWorkarounds (cypress-iframe)frameLocator (built-in)
Time-travel debuggerBuilt-in runnerTrace Viewer
API testingcy.requestrequest fixture
Component testingNative (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.