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 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
npm install -D cypress
Output: cypress binary on PATH; on first run, a ~200 MB Electron binary downloads into ~/.cache/Cypress/<version>/.
# Scaffold config + folders on first run
npx cypress open
Output:
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:
{
"scripts": {
"cy:open": "cypress open",
"cy:run": "cypress run",
"e2e": "start-server-and-test dev http://localhost:3000 'cypress run'"
}
}
Day-to-day commands
| Command | What it does |
|---|---|
cypress open | Opens the Launchpad UI — interactive runner with live test execution. |
cypress run | Headless run, all browsers; default browser is Electron. |
cypress run --browser chrome | Use Chrome (or firefox, edge, electron). |
cypress run --spec "cypress/e2e/login.cy.js" | Single spec file. |
cypress run --headed | Headless run, but shows the browser (useful in CI debugging). |
cypress run --record | Upload results to Cypress Cloud (requires record key). |
cypress run --parallel | Cypress Cloud parallelism across machines. Requires --record. |
cypress info | Print resolved config, browser detection, env. |
cypress verify | Verify the binary works without running tests. |
cypress cache list / clear / path | Manage the binary cache (~/.cache/Cypress). |
cypress install (--force) | Reinstall the binary; useful when cache is corrupt. |
Common scenarios
Minimal E2E spec
// 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");
});
});
npx cypress run --spec "cypress/e2e/login.cy.js"
Output:
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
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
// 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
// cypress.config.js
import { defineConfig } from "cypress";
export default defineConfig({
component: {
devServer: { framework: "react", bundler: "vite" },
},
});
// 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");
});
});
npx cypress run --component
Output:
Running: src/Counter.cy.jsx
<Counter />
✓ increments (52ms)
1 passing (160ms)
Headless CI
# .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)
export CYPRESS_RECORD_KEY=abc123
npx cypress run --record --parallel --group "e2e"
Output:
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
// 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
| Flag | Purpose |
|---|---|
--browser <name> | chrome, firefox, edge, electron. |
--spec <pattern> | Glob or comma-separated list of spec files. |
--config baseUrl=https://staging.example.com | Override config values at the CLI. |
--env name=value | Set Cypress env (accessible via Cypress.env("name")). |
--reporter junit | Reporter (default spec). Combine with reporter options. |
--record --key <key> | Upload to Cypress Cloud. |
--parallel | Cloud 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-exit | Don't exit after run (useful for --headed debugging). |
Configuration
cypress.config.js
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
CYPRESS_baseUrl=https://staging.example.com npx cypress run
CYPRESS_RECORD_KEY=$KEY npx cypress run --record
Output:
(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+)
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
// tsconfig.json (root)
{
"compilerOptions": {
"types": ["cypress", "node"]
},
"include": ["cypress/**/*.ts"]
}
Custom commands need type augmentation:
// 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
await cy.get(...)— Cypress chainables aren't promises.awaitresolves them toundefined. Use.then(...)for synchronous interop.- Defining intercept after request fired —
cy.visit()triggers requests; declarecy.interceptBEFORE. cy.wait(3000)is an antipattern — use aliased intercept waits (cy.wait('@alias')) or expanddefaultCommandTimeoutfor slow renders.- Cross-origin — same-origin restriction relaxed in v12 with
cy.origin(). Auth redirects to a different domain need explicitcy.origin(url, () => { ... })wrapping. - Binary cache desync —
Cypress binary verification failedafter a version bump.npx cypress install --force. - Cypress Cloud is paid — parallel mode requires it (or a third-party orchestrator). Easy to assume it's free.
- Component tests don't use
cy.intercept— there's no Node-side proxy. Stub at the import level instead. - Tests pass but you can't see why — use
cy.pause()mid-test incypress open. Time-travel + DOM snapshots are the killer feature.
See also
- Packages: npm-cypress — full package reference, ecosystem, Playwright comparison
- JavaScript: playwright — the modern alternative
- JavaScript: vitest — unit-test companion