cheat sheet

Cypress

Day-to-day Cypress CLI commands and config patterns — interactive runs, headless CI, intercepts, sessions, component testing, and Playwright comparison.

#cypress#cli#testing#e2eupdated 05-31-2026

Cypress — CLI and configuration

What it is

Cypress is an end-to-end testing framework that runs inside the browser. The test code, assertions, and DOM-interaction primitives execute in the same JS context as the page under test — making the live-debugging UI the best in the space. For new E2E suites, Playwright is the modern default; Cypress retains a niche through its visual debugger and existing investment.

Install

bash
npm install -D cypress

Output: cypress binary on PATH; on first run, a ~200 MB Electron binary downloads into ~/.cache/Cypress/<version>/.

bash
# Scaffold config + folders on first run
npx cypress open

Output:

text
It looks like this is your first time using Cypress: 14.0.0
✔ Verified Cypress! /Users/alice/.cache/Cypress/14.0.0
Opening Cypress...

This generates cypress.config.js, cypress/e2e/, cypress/support/, cypress/fixtures/.

Add scripts:

json
{
  "scripts": {
    "cy:open": "cypress open",
    "cy:run": "cypress run",
    "e2e": "start-server-and-test dev http://localhost:3000 'cypress run'"
  }
}

Day-to-day commands

CommandWhat it does
cypress openOpens the Launchpad UI — interactive runner with live test execution.
cypress runHeadless run, all browsers; default browser is Electron.
cypress run --browser chromeUse Chrome (or firefox, edge, electron).
cypress run --spec "cypress/e2e/login.cy.js"Single spec file.
cypress run --headedHeadless run, but shows the browser (useful in CI debugging).
cypress run --recordUpload results to Cypress Cloud (requires record key).
cypress run --parallelCypress Cloud parallelism across machines. Requires --record.
cypress infoPrint resolved config, browser detection, env.
cypress verifyVerify the binary works without running tests.
cypress cache list / clear / pathManage the binary cache (~/.cache/Cypress).
cypress install (--force)Reinstall the binary; useful when cache is corrupt.

Common scenarios

Minimal E2E spec

javascript
// cypress/e2e/login.cy.js
describe("login", () => {
  beforeEach(() => cy.visit("/login"));

  it("logs in with valid creds", () => {
    cy.get('[data-cy="email"]').type("alice@example.com");
    cy.get('[data-cy="password"]').type("hunter2");
    cy.get('[data-cy="submit"]').click();
    cy.url().should("include", "/dashboard");
  });

  it("shows an error on bad creds", () => {
    cy.get('[data-cy="email"]').type("alice@example.com");
    cy.get('[data-cy="password"]').type("wrong");
    cy.get('[data-cy="submit"]').click();
    cy.contains("Invalid credentials").should("be.visible");
  });
});
bash
npx cypress run --spec "cypress/e2e/login.cy.js"

Output:

text
Running:  login.cy.js
  login
    ✓ logs in with valid creds (842ms)
    ✓ shows an error on bad creds (461ms)

Tests:        2
Passing:      2
Duration:     1 second

Network mocking with cy.intercept

javascript
it("renders users from the API", () => {
  cy.intercept("GET", "/api/users", { fixture: "users.json" }).as("getUsers");
  cy.visit("/users");
  cy.wait("@getUsers");
  cy.get('[data-cy="row"]').should("have.length", 3);
});

it("shows an error toast on 500", () => {
  cy.intercept("POST", "/api/save", { statusCode: 500, body: { error: "boom" } });
  cy.visit("/edit");
  cy.get('[data-cy="save"]').click();
  cy.contains("Save failed").should("be.visible");
});

Always define intercepts before the request fires — cy.visit triggers the page load and any in-flight requests.

cy.session for auth reuse

javascript
// cypress/support/commands.js
Cypress.Commands.add("login", (email, password) => {
  cy.session([email, password], () => {
    cy.visit("/login");
    cy.get('[data-cy="email"]').type(email);
    cy.get('[data-cy="password"]').type(password);
    cy.get('[data-cy="submit"]').click();
    cy.url().should("include", "/dashboard");
  });
});

Use across specs — the auth state (cookies, localStorage, sessionStorage) is cached per id and restored without re-logging in.

Component testing

javascript
// cypress.config.js
import { defineConfig } from "cypress";
export default defineConfig({
  component: {
    devServer: { framework: "react", bundler: "vite" },
  },
});
javascript
// src/Counter.cy.jsx
import { Counter } from "./Counter";

describe("<Counter />", () => {
  it("increments", () => {
    cy.mount(<Counter />);
    cy.get('[data-cy="inc"]').click().click();
    cy.get('[data-cy="count"]').should("have.text", "2");
  });
});
bash
npx cypress run --component

Output:

text
  Running:  src/Counter.cy.jsx

  <Counter />
    ✓ increments (52ms)

  1 passing (160ms)

Headless CI

yaml
# .github/workflows/e2e.yml
- uses: actions/cache@v4
  with:
    path: ~/.cache/Cypress
    key: cypress-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
- run: npm ci
- run: npx start-server-and-test dev http://localhost:3000 'cypress run --browser chrome'
- uses: actions/upload-artifact@v4
  if: failure()
  with:
    name: cypress-failures
    path: |
      cypress/screenshots
      cypress/videos

Caching ~/.cache/Cypress saves ~200 MB of download per job.

Parallel runs (Cypress Cloud)

bash
export CYPRESS_RECORD_KEY=abc123
npx cypress run --record --parallel --group "e2e"

Output:

text
   Recorded Run: https://cloud.cypress.io/projects/abc/runs/42
   Tests:        24
   Passing:      24
   Duration:     1 minute 12 seconds

Cypress Cloud is the only first-class way to parallelise across machines. Third-party cypress-parallel does file-level splitting without Cloud.

Custom command

javascript
// cypress/support/commands.js
Cypress.Commands.add("dataCy", (sel) => cy.get(`[data-cy="${sel}"]`));

// Usage anywhere:
cy.dataCy("submit").click();

Repeated test selectors are the biggest source of brittle Cypress code; custom commands centralise them.

Useful flags

FlagPurpose
--browser <name>chrome, firefox, edge, electron.
--spec <pattern>Glob or comma-separated list of spec files.
--config baseUrl=https://staging.example.comOverride config values at the CLI.
--env name=valueSet Cypress env (accessible via Cypress.env("name")).
--reporter junitReporter (default spec). Combine with reporter options.
--record --key <key>Upload to Cypress Cloud.
--parallelCloud parallelism — splits specs across machines.
--group <name>Group label for Cloud runs (e.g. "smoke" vs "regression").
--quiet (-q)Suppress Cypress's own banner output.
--no-exitDon't exit after run (useful for --headed debugging).

Configuration

cypress.config.js

javascript
import { defineConfig } from "cypress";

export default defineConfig({
  e2e: {
    baseUrl: "http://localhost:3000",
    specPattern: "cypress/e2e/**/*.cy.{js,ts,jsx,tsx}",
    supportFile: "cypress/support/e2e.js",
    setupNodeEvents(on, config) {
      // Plugin registration
      // on('task', { ... });
      return config;
    },
    retries: { runMode: 2, openMode: 0 },
    video: false,
    screenshotOnRunFailure: true,
    defaultCommandTimeout: 5000,
    requestTimeout: 10000,
    viewportWidth: 1280,
    viewportHeight: 720,
  },
  component: {
    devServer: { framework: "react", bundler: "vite" },
    specPattern: "src/**/*.cy.{js,ts,jsx,tsx}",
  },
});

Per-environment via env vars

bash
CYPRESS_baseUrl=https://staging.example.com npx cypress run
CYPRESS_RECORD_KEY=$KEY npx cypress run --record

Output:

text
  (Run Starting)
  Cypress:    14.0.0
  Browser:    Electron 124 (headless)
  baseUrl:    https://staging.example.com

  Tests:      24 passing

Any config key can be overridden with CYPRESS_<key>=value.

Test isolation (v12+)

javascript
e2e: {
  testIsolation: true,   // default in v12+
}

Each test starts with cleared cookies, localStorage, sessionStorage. Combined with cy.session(), this gives clean isolation without the per-test login cost.

TypeScript

json
// tsconfig.json (root)
{
  "compilerOptions": {
    "types": ["cypress", "node"]
  },
  "include": ["cypress/**/*.ts"]
}

Custom commands need type augmentation:

typescript
// cypress/support/commands.ts
declare global {
  namespace Cypress {
    interface Chainable {
      login(email: string, password: string): Chainable<void>;
      dataCy(sel: string): Chainable<JQuery<HTMLElement>>;
    }
  }
}

Common pitfalls

  1. await cy.get(...) — Cypress chainables aren't promises. await resolves them to undefined. Use .then(...) for synchronous interop.
  2. Defining intercept after request firedcy.visit() triggers requests; declare cy.intercept BEFORE.
  3. cy.wait(3000) is an antipattern — use aliased intercept waits (cy.wait('@alias')) or expand defaultCommandTimeout for slow renders.
  4. Cross-origin — same-origin restriction relaxed in v12 with cy.origin(). Auth redirects to a different domain need explicit cy.origin(url, () => { ... }) wrapping.
  5. Binary cache desyncCypress binary verification failed after a version bump. npx cypress install --force.
  6. Cypress Cloud is paid — parallel mode requires it (or a third-party orchestrator). Easy to assume it's free.
  7. Component tests don't use cy.intercept — there's no Node-side proxy. Stub at the import level instead.
  8. Tests pass but you can't see why — use cy.pause() mid-test in cypress open. Time-travel + DOM snapshots are the killer feature.

See also