Cypress is a front-end testing tool for anything that runs in a browser. You write tests in JavaScript or TypeScript, and Cypress runs them inside the same run loop as your application — giving you native access to every object, while it automatically waits, retries, and snapshots the DOM at each step. It covers two testing types from one install:
- End-to-End (E2E) testing — drive a real, deployed application through a real browser (
cy.visit('/login'), type, click, assert). - Component testing — mount a single React, Vue, Angular, or Svelte component in isolation and test it like a unit, but in a real browser.
This guide is everything you need to install Cypress, write and run tests, configure projects, stub the network, and ship reliable tests in CI. It is built directly from the Cypress source code at commit 1f8ff3b.
Note
Cypress installs as a single npm package (
cypress) that downloads a self-contained binary (the Test Runner, bundled with its own Electron browser). You do not need to install a separate browser driver, Selenium, or WebDriver server.
1. Quick Start
Get a working test in under five minutes.
# 1. Initialize a project (skip if you already have package.json)
npm init -y
# 2. Install Cypress as a dev dependency
npm install --save-dev cypress
# 3. Open the Cypress App (interactive Test Runner)
npx cypress open
It looks like this is your first time using Cypress: 14.x.x
✔ Verified Cypress! C:\Users\you\AppData\Local\Cypress\Cache\14.x.x\Cypress
Opening Cypress...
The Cypress App launches. Choose E2E Testing, let Cypress scaffold the config files, pick a browser, and click Create new spec. Cypress writes a starter spec and runs it live in the browser.
A first end-to-end test looks like this:
// cypress/e2e/spec.cy.js
describe('My First Test', () => {
it('visits the app and checks the title', () => {
cy.visit('https://example.cypress.io')
cy.contains('type').click()
cy.url().should('include', '/commands/actions')
cy.get('.action-email')
.type('[email removed]')
.should('have.value', '[email removed]')
})
})
Tip
Use
cy.visit()to load a page,cy.get()/cy.contains()to query elements, action commands like.click()and.type()to interact, and.should()to assert. Cypress automatically waits for elements to exist and for assertions to pass — you almost never need manual waits or sleeps.
2. Installation
2.1 Requirements
| Requirement | Detail |
|---|---|
| Node.js | >= 22.19.0 (declared in the package engines field) |
| Operating systems | macOS 11+ (Intel/Apple Silicon), Linux (Debian/Ubuntu, Fedora, etc.), Windows 10/11 |
| Package manager | npm, Yarn (Classic or Berry/PnP), or pnpm |
| Disk | ~1 GB free for the downloaded binary and its cache |
Important
Cypress ships its own browser (Electron) and bundles the entire Test Runner. The
cypressnpm package is small; the heavy binary is downloaded on first install and cached globally per version, not per project.
2.2 Install the package
npm
npm install --save-dev cypress
Yarn
yarn add --dev cypress
pnpm
pnpm add --save-dev cypress
Installing the package triggers a postinstall step that downloads and unzips the Cypress binary into the global cache.
Installing Cypress (version: 14.x.x)
✔ Downloaded Cypress
✔ Unzipped Cypress
✔ Finished Installation C:\Users\you\AppData\Local\Cypress\Cache\14.x.x
2.3 Verify the installation
npx cypress verify
✔ Verified Cypress! C:\Users\you\AppData\Local\Cypress\Cache\14.x.x\Cypress
Print full environment information (binary version, cache location, browsers detected):
npx cypress info
2.4 Controlling the binary download
The download is driven by environment variables read at install time:
| Variable | Effect |
|---|---|
CYPRESS_INSTALL_BINARY |
Set to a version, a URL, a local .zip path, or 0 to skip the download entirely |
CYPRESS_DOWNLOAD_MIRROR |
Point the download at a private mirror |
CYPRESS_CACHE_FOLDER |
Override the global binary cache location |
CYPRESS_DOWNLOAD_USE_CA |
Use the system / npm CA bundle for the download request |
HTTP_PROXY / HTTPS_PROXY / NO_PROXY |
Standard proxy variables — honored for the binary download |
Caution
If you install with
CYPRESS_INSTALL_BINARY=0(common in Docker base images), you must provide the binary another way before running tests, orcypress runwill fail to verify. Runnpx cypress installonce the binary is available.
2.5 Managing the binary cache
The cypress cache command manages downloaded binaries:
npx cypress cache path # print the cache folder
npx cypress cache list # list cached versions (add --size for disk usage)
npx cypress cache clear # remove ALL cached binaries
npx cypress cache prune # remove all cached versions EXCEPT the currently installed one
Tip
Run
npx cypress cache pruneperiodically on CI build agents and developer machines to reclaim disk from old Cypress versions.
3. Setting Up a Project
3.1 Open the Cypress App
npx cypress open
On first launch Cypress walks you through choosing a testing type (E2E or Component) and scaffolds the required files:
cypress.config.js(or.ts/.mjs) — your project configurationcypress/support/e2e.jsand/orcypress/support/component.js— support files loaded before every speccypress/fixtures/— static test datacypress/e2e/— where your E2E specs live
3.2 The configuration file
Cypress is configured with a cypress.config.js file at your project root using defineConfig:
// cypress.config.js
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}',
setupNodeEvents(on, config) {
// register tasks, plugins, and event listeners here
},
},
})
// cypress.config.ts
import { defineConfig } from 'cypress'
export default defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
},
})
Note
The default config filename is
cypress.config.js. You can change it with--config-fileon the CLI. TypeScript (cypress.config.ts) and ESM (cypress.config.mjs) config files are supported out of the box.
3.3 Project folder layout
| Folder / file | Default | Purpose |
|---|---|---|
cypress/e2e/ |
from specPattern |
E2E spec files (*.cy.js) |
cypress/fixtures/ |
cypress/fixtures (fixturesFolder) |
Static JSON / data loaded by cy.fixture() |
cypress/support/e2e.js |
cypress/support/e2e.{js,jsx,ts,tsx} (supportFile) |
Runs before every E2E spec — custom commands, global hooks |
cypress/support/component.js |
cypress/support/component.{js,jsx,ts,tsx} |
Runs before every component spec |
cypress/downloads/ |
cypress/downloads (downloadsFolder) |
Files downloaded during tests |
cypress/screenshots/ |
cypress/screenshots (screenshotsFolder) |
Saved screenshots |
cypress/videos/ |
cypress/videos (videosFolder) |
Recorded run videos (when video: true) |
4. Writing Tests
Cypress uses the Mocha describe/it BDD structure and Chai assertions, with jQuery-style DOM querying. You don't import these — they are globals inside specs.
4.1 Test structure
describe('Login page', () => {
beforeEach(() => {
cy.visit('/login')
})
it('shows an error for bad credentials', () => {
cy.get('[data-cy="username"]').type('wrong')
cy.get('[data-cy="password"]').type('nope')
cy.get('[data-cy="submit"]').click()
cy.get('.error').should('contain', 'Invalid credentials')
})
})
| Hook | When it runs |
|---|---|
before() |
Once, before all tests in the block |
beforeEach() |
Before every test |
afterEach() |
After every test |
after() |
Once, after all tests in the block |
4.2 The command chain and subjects
Cypress commands are asynchronous and chainable. Each command yields a subject to the next:
cy.get('ul.todo-list') // yields the <ul> element
.find('li') // yields the <li> children
.first() // yields the first <li>
.should('contain', 'Buy milk') // asserts on it
Important
Commands do not run when the line executes — they are enqueued and run in order after the test function returns. This is why you cannot assign a command's return value to a variable. Use
.then()to work with yielded values, or aliases (.as()) to reuse them.
// WRONG — value is a Chainable, not the element
const button = cy.get('button')
// RIGHT — work with the subject inside .then()
cy.get('button').then(($button) => {
// $button is the jQuery-wrapped element
})
4.3 Automatic waiting and retry-ability
Cypress automatically retries queries (like cy.get) and assertions until they pass or a timeout is reached. The default command timeout is 4000 ms (defaultCommandTimeout). This means you rarely need cy.wait(ms):
// Cypress retries cy.get + the assertion until the element appears
// with the right text, or 4s elapses — no manual wait needed
cy.get('.async-result').should('have.text', 'Loaded')
Tip
If you find yourself adding
cy.wait(2000), replace it with an assertion on the thing you're actually waiting for, or wait on a network request alias (see §8). Hard-coded waits make tests slow and flaky.
5. Command Reference
Every command below is a method on the cy global (or, for the listed event/utility methods, on Cypress). Commands marked query are automatically retried; action commands interact with the DOM and require a connected, visible, actionable element.
5.1 Querying the DOM (retried automatically)
| Command | Yields | Description |
|---|---|---|
cy.get(selector) |
element(s) | Get DOM elements by CSS selector or alias (@name) |
cy.contains(text) |
element | Get the element containing the given text |
cy.find(selector) |
element(s) | Find descendants of the current subject |
cy.within(fn) |
— | Scope all commands inside fn to the current element |
cy.root() |
element | Get the root element (document or within subject) |
cy.focused() |
element | Get the element that currently has focus |
cy.shadow() |
element(s) | Traverse into the shadow DOM of the subject |
cy.get('[data-cy="email"]')
cy.contains('Submit').click()
cy.get('form').within(() => {
cy.get('input[name="email"]').type('[email removed]') // scoped to the form
})
Tip
Prefer dedicated
data-*test attributes (e.g.[data-cy="submit"]) over CSS classes or text that change with styling and copy. This is the single most effective way to reduce selector flakiness.
5.2 Traversal (retried automatically)
children, closest, eq, filter, first, last, next, nextAll, nextUntil, not, parent, parents, parentsUntil, prev, prevAll, prevUntil, siblings.
cy.get('li').first() // first matching element
cy.get('li').eq(2) // the third element (0-indexed)
cy.get('.active').parent() // the parent element
cy.get('tr').filter('.selected') // narrow the set
cy.get('#start').next().siblings() // walk the DOM
5.3 Actions (interact with elements)
| Command | Description |
|---|---|
.click() |
Click an element ({ force: true } to bypass actionability) |
.dblclick() |
Double-click |
.rightclick() |
Right-click (context menu) |
.type(text) |
Type into an input; supports key sequences like {enter}, {backspace}, {selectall} |
.clear() |
Clear an input or textarea |
.check() / .uncheck() |
Check/uncheck checkboxes and radios |
.select(value) |
Select an <option> from a <select> |
.selectFile(path) |
Attach a file to a file input (or drag-drop) |
.scrollIntoView() |
Scroll an element into the viewport |
cy.scrollTo(position) |
Scroll the window/element to a position |
.trigger(event) |
Dispatch a raw DOM event |
.focus() / .blur() |
Focus / blur an element |
.submit() |
Submit a form |
.press(key) |
Dispatch a real keyboard key press (e.g. Cypress.Keyboard.Keys.TAB) |
cy.get('input[name="q"]').type('cypress{enter}')
cy.get('#terms').check()
cy.get('select#country').select('Canada')
cy.get('input[type=file]').selectFile('cypress/fixtures/avatar.png')
cy.get('button.submit').click({ force: true }) // bypass actionability checks
Warning
Action commands enforce actionability: the element must exist, be visible, not be disabled, not be covered, and not be animating. If an action fails with "element is not visible" or "covered by another element", fix the app state or use
{ force: true }deliberately —forceskips these safety checks.
5.4 Navigation
| Command | Description |
|---|---|
cy.visit(url) |
Visit a URL (relative URLs resolve against baseUrl) |
cy.reload() |
Reload the page (cy.reload(true) forces from server, not cache) |
cy.go(direction) |
Navigate 'back' / 'forward', or by number |
cy.visit('/dashboard') // resolves against baseUrl
cy.go('back')
cy.reload()
5.5 Location & window
cy.url(), cy.hash(), cy.location(), cy.window(), cy.document(), cy.title(), cy.viewport(width, height).
cy.url().should('include', '/checkout')
cy.title().should('eq', 'My App')
cy.viewport(375, 667) // simulate an iPhone-sized viewport
cy.window().its('myAppStore').should('exist')
5.6 Assertions
Assert with .should() and .and() using Chai/Chai-jQuery/Sinon-Chai chainers. You can also drop into an explicit callback with expect.
cy.get('.todo').should('have.length', 3)
cy.get('a.link')
.should('have.attr', 'href')
.and('include', 'cypress.io')
// callback form for complex assertions
cy.get('.list li').should(($li) => {
expect($li).to.have.length(3)
expect($li.eq(0)).to.contain('first')
})
Common chainers: have.length, have.text, contain, have.value, have.attr, have.class, be.visible, exist / not.exist, be.checked, be.disabled, have.css, match.
Note
.should('not.exist')is the correct way to assert an element is gone..should('not.be.visible')asserts it exists but is hidden. They are different — pick deliberately.
5.7 Connectors & subject management
| Command | Description |
|---|---|
.then(fn) |
Yield the subject into a callback (work with real values) |
.each(fn) |
Iterate over an array-like subject |
.spread(fn) |
Spread an array subject into callback arguments |
.its(path) |
Get a property of the subject (retried) |
.invoke(fn) |
Call a function on the subject (retried) |
.wrap(value) |
Wrap any value so it can be chained / asserted |
.as(alias) |
Alias the subject for later reuse via @alias |
cy.wrap({ name: 'Jane' }).its('name').should('eq', 'Jane')
cy.get('.price').invoke('text').then((txt) => Number(txt.replace('$', '')))
cy.get('button').as('saveBtn')
cy.get('@saveBtn').click()
5.8 Network, requests & tasks
| Command | Description |
|---|---|
cy.intercept(...) |
Spy on or stub network requests (see §8) |
cy.request(...) |
Make an HTTP request outside the browser (no stubbing applies) |
cy.wait('@alias') |
Wait for an intercepted request to complete |
cy.exec(cmd) |
Run a system command (Node side) |
cy.task(name, arg) |
Invoke a Node task registered in setupNodeEvents |
cy.request('POST', '/api/seed', { users: 3 }).its('status').should('eq', 200)
cy.exec('npm run db:reset')
cy.task('queryDb', 'SELECT * FROM users') // task defined in setupNodeEvents
Important
cy.request()runs from Cypress's Node process, not the browser — it is not affected bycy.intercept()and does not carry the browser's cookies automatically. Use it to seed data and hit APIs directly; usecy.visit+cy.interceptto test the app's own requests.
5.9 Files, fixtures & storage
cy.fixture(path), cy.readFile(path), cy.writeFile(path, contents), cy.getCookie/cy.getCookies/cy.getAllCookies, cy.setCookie, cy.clearCookie/cy.clearCookies/cy.clearAllCookies, cy.getAllLocalStorage/cy.clearLocalStorage/cy.clearAllLocalStorage, cy.getAllSessionStorage/cy.clearAllSessionStorage.
cy.fixture('user.json').then((user) => {
cy.get('#name').type(user.name)
})
cy.setCookie('token', 'abc123')
cy.clearAllLocalStorage()
Caution
When setting a cookie with
sameSite: 'no_restriction'(sent asSameSite=None), you must also setsecure: true, or Cypress throws an error. Cookie name prefixes__Secure-and__Host-likewise enforce their browser rules.
5.10 Stubs, spies & clock
| Command | Description |
|---|---|
cy.stub() |
Create a Sinon stub (replace a function) |
cy.spy() |
Create a Sinon spy (observe a function) |
cy.clock() |
Take control of Date, setTimeout, setInterval, etc. |
cy.tick(ms) |
Advance the controlled clock |
cy.clock()
cy.visit('/')
cy.tick(60_000) // fast-forward 60s of timers
cy.window().then((win) => {
const onClick = cy.stub().as('onClick')
win.addEventListener('click', onClick)
})
5.11 Sessions & multi-origin
| Command | Description |
|---|---|
cy.session(id, setup, options?) |
Cache and restore browser session state (cookies, localStorage) across tests |
cy.origin(url, fn) |
Run commands against a different origin (cross-domain) |
// Log in once, reuse the session across every test
beforeEach(() => {
cy.session('user', () => {
cy.visit('/login')
cy.get('#user').type('jane')
cy.get('#pass').type('secret{enter}')
cy.url().should('include', '/dashboard')
})
})
// Interact with a third-party login on another domain
cy.origin('https://auth.example.com', () => {
cy.get('#email').type('[email removed]')
cy.get('#continue').click()
})
Tip
cy.session()is the recommended way to handle authentication. It runs thesetuponce, caches the resulting session, and restores it for subsequent tests — dramatically faster than logging in through the UI in every test.
5.12 Debugging & misc
cy.debug(), cy.pause(), cy.log(message), cy.screenshot(), Cypress.env(), cy.wait(ms | '@alias').
cy.pause() // pause execution; step through in the App
cy.get('.thing').debug() // log the subject to devtools
cy.log('about to submit')
cy.screenshot('after-login')
6. Configuration
Set these in cypress.config.js. Values listed are the defaults read from Cypress's source.
6.1 Timeouts
| Option | Default | Purpose |
|---|---|---|
defaultCommandTimeout |
4000 |
ms to retry most commands/assertions |
requestTimeout |
5000 |
ms to wait for cy.request / intercept request stage |
responseTimeout |
30000 |
ms to wait for a response |
pageLoadTimeout |
60000 |
ms to wait for cy.visit / page transitions |
execTimeout |
60000 |
ms for cy.exec |
taskTimeout |
60000 |
ms for cy.task |
6.2 Viewport & behavior
| Option | Default (E2E) | Purpose |
|---|---|---|
viewportWidth |
1000 |
Default browser viewport width (px) |
viewportHeight |
660 |
Default browser viewport height (px) |
baseUrl |
null |
Prefix for relative cy.visit / cy.request URLs |
scrollBehavior |
'top' |
How elements are scrolled before actions |
waitForAnimations |
true |
Wait for animations to finish before acting |
animationDistanceThreshold |
5 |
px movement that still counts as "animating" |
keystrokeDelay |
0 |
ms between simulated keystrokes |
includeShadowDom |
false |
Traverse into shadow roots by default |
6.3 Retries, isolation & test data
| Option | Default | Purpose |
|---|---|---|
retries |
{ runMode: 0, openMode: 0 } |
Automatic test retries per mode |
testIsolation |
true |
Reset browser state before each test (E2E) |
numTestsKeptInMemory |
50 |
Snapshots/command data kept in memory |
watchForFileChanges |
true |
Re-run specs on file change in open mode |
6.4 Artifacts
| Option | Default | Purpose |
|---|---|---|
video |
false |
Record a video of the run |
videoCompression |
false |
Compress recorded video (CRF value or false) |
videosFolder |
cypress/videos |
Output folder for videos |
screenshotOnRunFailure |
true |
Auto-screenshot on test failure in run mode |
screenshotsFolder |
cypress/screenshots |
Output folder for screenshots |
trashAssetsBeforeRuns |
true |
Clear old screenshots/videos before each run |
6.5 Network & security
| Option | Default | Purpose |
|---|---|---|
chromeWebSecurity |
true |
Chrome same-origin web security (set false for cross-origin in Chromium) |
blockHosts |
null |
Glob list of hosts to block (e.g. analytics) |
modifyObstructiveCode |
true |
Strip frame-busting code from responses |
userAgent |
null |
Override the browser User-Agent |
redirectionLimit |
20 |
Max redirects to follow |
clientCertificates |
[] |
Per-URL client certificate config |
6.6 Spec & support patterns
| Option | Default | Purpose |
|---|---|---|
specPattern |
cypress/e2e/**/*.cy.{js,jsx,ts,tsx} (E2E) |
Where specs are found |
excludeSpecPattern |
*.hot-update.js (E2E) |
Specs to ignore |
supportFile |
cypress/support/e2e.{js,jsx,ts,tsx} |
File run before every spec |
fixturesFolder |
cypress/fixtures |
Fixture data folder |
downloadsFolder |
cypress/downloads |
Download target folder |
reporter |
'spec' |
Mocha reporter for cypress run |
projectId |
null |
Cypress Cloud project ID (for recording) |
6.7 Per-test and per-suite overrides
Override config inline with Cypress.config() or a config object:
describe('mobile', { viewportWidth: 375, viewportHeight: 667 }, () => {
it('renders the mobile menu', { defaultCommandTimeout: 8000 }, () => {
// ...
})
})
6.8 Environment variables
Pass data into tests via env. Read it with Cypress.env('KEY').
// cypress.config.js
module.exports = defineConfig({ env: { apiUrl: 'http://localhost:4000' } })
cy.request(`${Cypress.env('apiUrl')}/users`)
Set or override env values from outside:
# via CLI flag
npx cypress run --env apiUrl=https://staging.example.com,grepTags=@smoke
# via OS environment (CYPRESS_ prefix)
CYPRESS_apiUrl=https://staging.example.com npx cypress run
Note
OS variables prefixed with
CYPRESS_(e.g.CYPRESS_BASE_URL,CYPRESS_apiUrl) are automatically picked up and stripped of the prefix. This is the standard way to inject secrets and per-environment values in CI.
6.9 Experimental options
Cypress exposes opt-in experiments (all default false unless noted):
experimentalCspAllowList, experimentalInteractiveRunEvents, experimentalRunAllSpecs, experimentalMemoryManagement, experimentalModifyObstructiveThirdPartyCode, experimentalOriginDependencies, experimentalSourceRewriting, experimentalSingleTabRunMode, experimentalWebKitSupport, experimentalFastVisibility, experimentalStudio, injectDocumentDomain.
Warning
Experimental options can change behavior or be removed between releases. Enable them deliberately and pin your Cypress version when you rely on one.
7. Running Tests from the CLI
Cypress has two run modes: the interactive App (cypress open) and headless/CI runs (cypress run).
7.1 cypress open — interactive
npx cypress open
| Flag | Purpose |
|---|---|
-e, --env <env> |
Set environment variables |
-c, --config <config> |
Override config values inline |
-C, --config-file <path> |
Use a non-default config file |
-b, --browser <name|path> |
Pre-select a browser |
--e2e / --component |
Launch straight into a testing type |
-P, --project <path> |
Path to a project root |
--global |
Run the App in global mode |
-p, --port <port> |
Port to run Cypress on |
-d, --detached |
Run detached from the terminal |
7.2 cypress run — headless / CI
npx cypress run
====================================================================================================
(Run Starting)
┌────────────────────────────────────────────────────────────────────────────────────────────┐
│ Cypress: 14.x.x │
│ Browser: Electron 130 (headless) │
│ Specs: 3 found (login.cy.js, todo.cy.js, search.cy.js) │
└────────────────────────────────────────────────────────────────────────────────────────────┘
✓ All specs passed! 00:42 12 12 - - -
Key flags:
| Flag | Purpose |
|---|---|
-s, --spec <glob> |
Run only matching specs |
-b, --browser <name|path> |
Choose browser (chrome, firefox, edge, electron) |
--headed / --headless |
Show or hide the browser window |
--e2e / --component |
Choose testing type |
-c, --config / -C, --config-file |
Override config / use a different config file |
-e, --env <env> |
Set environment variables |
-r, --reporter <name> |
Mocha reporter (e.g. junit, mocha-junit-reporter) |
-o, --reporter-options <opts> |
Reporter options |
-q, --quiet |
Suppress Cypress output (reporter only) |
--no-exit |
Keep the browser open after the run finishes |
--pass-with-no-tests |
Exit 0 even if no specs are found |
-p, --port <port> |
Run on a specific port |
-P, --project <path> |
Project root path |
Cloud-recording / parallelization flags:
| Flag | Purpose |
|---|---|
--record [bool] |
Record results to Cypress Cloud |
-k, --key <record-key> |
Cloud record key |
--parallel |
Run specs in parallel across machines (requires --record) |
--group <name> |
Group runs in the Cloud dashboard |
--ci-build-id <id> |
Identify a CI build for grouping/parallel |
-t, --tag <tag> |
Tag a recorded run |
--auto-cancel-after-failures <n|false> |
Auto-cancel a recorded run after N failures |
# Run a single spec in Chrome, headed
npx cypress run --spec "cypress/e2e/login.cy.js" --browser chrome --headed
# Record to Cypress Cloud in parallel
npx cypress run --record --key <record-key> --parallel --group "ci"
Note
cypress runalways runs headless by default and exits with a non-zero code if any test fails — exactly what you want in CI. Add--headedonly for debugging.
7.3 Supported browsers
Cypress detects locally installed browsers and bundles its own. Out of the box it supports Electron (bundled), Chrome, Chromium, Chrome Beta/Canary, Edge, and Firefox. WebKit (Safari's engine) is available behind experimentalWebKitSupport.
npx cypress run --browser chrome
npx cypress run --browser firefox
npx cypress run --browser /path/to/custom/chrome # by absolute path
8. Network Stubbing with cy.intercept
cy.intercept() is Cypress's full-featured network layer — spy on requests, stub responses, and assert on traffic.
8.1 Spying (observe, don't change)
cy.intercept('GET', '/api/users').as('getUsers')
cy.visit('/users')
cy.wait('@getUsers').its('response.statusCode').should('eq', 200)
8.2 Stubbing (replace the response)
// Static stub from a fixture
cy.intercept('GET', '/api/users', { fixture: 'users.json' }).as('getUsers')
// Inline stub body + status
cy.intercept('POST', '/api/login', {
statusCode: 401,
body: { error: 'Invalid credentials' },
}).as('login')
cy.visit('/login')
cy.get('#submit').click()
cy.wait('@login')
cy.get('.error').should('contain', 'Invalid credentials')
8.3 Dynamic handling
cy.intercept('GET', '/api/cart', (req) => {
req.headers['x-test'] = 'true' // modify the outgoing request
req.reply((res) => {
res.body.items.push({ id: 99 }) // modify the response
})
})
8.4 Route matchers
The first argument can be a method + URL, a URL/glob, or a RouteMatcher object:
cy.intercept({ method: 'POST', url: '**/api/**', headers: { 'content-type': 'application/json' } }, handler)
cy.intercept('/assets/*.png', { statusCode: 404 }) // block images
Tip
Always wait on the alias (
cy.wait('@alias')) before asserting on app state that depends on a request. This synchronizes your test with the network instead of guessing withcy.wait(ms).
9. Component Testing
Component testing mounts a single UI component in a real browser without a full app. Cypress ships first-party adapters as separate packages.
| Framework | Package | Dev servers |
|---|---|---|
| React | @cypress/react |
Vite, Webpack |
| Vue | @cypress/vue |
Vite, Webpack |
| Angular | @cypress/angular (+ @cypress/angular-zoneless) |
Angular CLI/Webpack |
| Svelte | @cypress/svelte |
Vite, Webpack |
Configure the testing type and dev server in cypress.config.js:
const { defineConfig } = require('cypress')
module.exports = defineConfig({
component: {
devServer: {
framework: 'react',
bundler: 'vite',
},
},
})
Write a component spec with cy.mount():
// Button.cy.jsx
import Button from './Button'
describe('<Button />', () => {
it('calls onClick', () => {
const onClick = cy.stub().as('onClick')
cy.mount(<Button label="Save" onClick={onClick} />)
cy.get('button').should('have.text', 'Save').click()
cy.get('@onClick').should('have.been.calledOnce')
})
})
Note
Register
cy.mountonce incypress/support/component.jsby re-exporting the framework's mount function (the scaffolder does this for you when you choose Component Testing in the App).
The component-testing defaults differ from E2E: viewportWidth/viewportHeight default to 500×500, slowTestThreshold to 250 ms, and the support/spec patterns point at component.* files.
10. Reusable Test Code
10.1 Custom commands
Add your own cy.* commands in the support file:
// cypress/support/commands.js
Cypress.Commands.add('login', (user, pass) => {
cy.session([user, pass], () => {
cy.request('POST', '/api/login', { user, pass })
.its('body.token')
.then((token) => window.localStorage.setItem('token', token))
})
})
// usage in any spec
cy.login('jane', 'secret')
10.2 Node tasks
Run Node-side code (DB queries, file ops) via cy.task. Register in setupNodeEvents:
// cypress.config.js
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
on('task', {
resetDb() {
// runs in Node, not the browser
return null
},
})
},
},
})
cy.task('resetDb')
10.3 Fixtures
Store static data under cypress/fixtures/ and load it with cy.fixture():
cy.fixture('users.json').as('users')
cy.intercept('GET', '/api/users', { fixture: 'users.json' })
11. Continuous Integration
Cypress runs in any CI provider. The essentials:
# Typical CI command
npx cypress run --browser chrome
A minimal GitHub Actions workflow:
name: e2e
on: push
jobs:
cypress:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- run: npx cypress run --browser chrome
CI checklist:
- [ ] Node
>= 22.19.0available on the runner - [ ]
npm ciinstallscypressand triggers the binary download - [ ] Cache the Cypress binary directory (
~/.cache/Cypresson Linux) between builds - [ ] Start your app (or set
baseUrlto a deployed URL) beforecypress run - [ ] On Linux without a display, Cypress auto-launches a virtual framebuffer (Xvfb)
Tip
Cypress detects CI automatically via the
CIenvironment variable. To record and parallelize, sign up for Cypress Cloud, set aprojectIdin your config, and runcypress run --record --key <key> --parallel.
12. Troubleshooting
Error: Impossible options: both headless and headed are true
Cause: You passed both --headed and --headless to cypress run.
Solution: Pass only one. cypress run is headless by default; add --headed only to see the browser.
Prevention: Don't combine the two flags in CI scripts.
Error: Cannot start. Invalid mode:
Cause: Cypress was launched with an unrecognized run mode (usually a malformed CLI invocation or wrapper script).
Solution: Use cypress open or cypress run exactly; check for typos in custom npm scripts.
Error: Server#open called without config.baseUrl / "Cypress could not verify that this server is running"
Cause: You set or used a baseUrl that points at a server that isn't up yet (community issue #27990).
Solution: Start your application before running Cypress, or wait for the port (e.g. with start-server-and-test). Confirm baseUrl matches the actual host/port.
Prevention: Use start-server-and-test (or your CI's wait step) so Cypress only starts once the app responds.
Error: read ECONNRESET during a fresh npm install
Cause: Network/proxy interruption while downloading the Cypress binary (community issue #14175).
Solution: Retry the install; configure HTTP_PROXY/HTTPS_PROXY; or set CYPRESS_DOWNLOAD_MIRROR to a reachable mirror. Behind a corporate proxy, set CYPRESS_DOWNLOAD_USE_CA=1 to use your CA bundle.
Prevention: Cache the binary in CI and pin the Cypress version.
open hangs in an unconfigured monorepo with --e2e
Cause: Running cypress open --e2e in a workspace that has no Cypress config yet (community issue #29544).
Solution: Run cypress open without --e2e first so the App can scaffold config interactively, then re-run.
Element is "not visible" / "covered by another element"
Cause: Cypress's actionability checks failed — the element is hidden, off-screen, disabled, or under an overlay.
Solution: Assert the app reaches the right state first (.should('be.visible')), close the obstructing overlay, or use { force: true } when you intentionally want to bypass the check.
Prevention: Wait on the meaningful state, not a fixed delay.
Tests pass in headed but fail in headless Electron
Cause: Timing/rendering differences, or features that behave differently headless (community issue #31068, around video + headless Electron).
Solution: Reproduce with --browser electron --headless, add assertions instead of waits, and try --browser chrome to isolate whether it's Electron-specific.
cy.session() or cross-origin restore hangs
Cause: Sub-resource requests through the proxy timing out, especially with WebKit (community issue #33926).
Solution: Ensure all origins the test touches are reachable; use cy.origin() for third-party domains; avoid relying on slow third-party scripts during session setup.
Important
When a command fails, open the Command Log in the App and click the failing step. Cypress shows a DOM snapshot from the moment of failure plus the console output (
consoleProps) — this is usually faster than re-reading code.
13. Frequently Asked Questions
Q: Why can't I store a command's return value in a variable?
Commands are asynchronous and enqueued. Use .then((subject) => { ... }) to access yielded values, or alias with .as('name') and retrieve via cy.get('@name').
Q: How do I wait for an API call instead of using cy.wait(2000)?
Alias the request with cy.intercept(...).as('x') then cy.wait('@x'). Cypress waits exactly as long as the request takes.
Q: Does Cypress support multiple tabs or windows?
No. Cypress runs in a single browser tab by design. Test the behavior behind a target="_blank" link by removing the target or asserting on the href, and use cy.origin() for cross-domain flows.
Q: How do I test login without going through the UI every time?
Use cy.session() to run login once and restore the cached session for each test. Combine with cy.request() to authenticate via API for speed.
Q: What's the difference between cy.request() and cy.intercept()?
cy.request() makes an HTTP call from Node, bypassing the browser and any stubs — great for seeding data. cy.intercept() observes/stubs the requests your application makes from the browser.
Q: How do I run only one test or spec?
Use it.only(...) / describe.only(...) in code, or cypress run --spec "path/to/file.cy.js" from the CLI.
Q: How do I run Cypress in TypeScript?
Name your specs *.cy.ts and your config cypress.config.ts. Cypress transpiles TypeScript automatically; no extra setup is needed for basic use.
Q: Where is the downloaded Cypress binary stored?
In a global per-version cache. Print the path with npx cypress cache path, list versions with npx cypress cache list --size.
Q: How do I skip the binary download in Docker?
Set CYPRESS_INSTALL_BINARY=0 during npm install, then provide the binary (e.g. a prebuilt cypress/included image) or run npx cypress install later.
Q: Can I retry flaky tests automatically?
Yes. Set retries in config, e.g. retries: { runMode: 2, openMode: 0 } to retry failing tests up to twice in cypress run.
Q: How do I block analytics or third-party hosts?
Use the blockHosts config option with a glob list, e.g. blockHosts: ['*.google-analytics.com'].
Q: My component test viewport is tiny — why?
Component testing defaults to a 500×500 viewport (different from E2E's 1000×660). Set viewportWidth/viewportHeight in the component config block or per-test.
Q: How do I see why an element wasn't found?
Cypress retries until defaultCommandTimeout. Increase it for a specific command (cy.get(sel, { timeout: 10000 })) or globally, and check the Command Log snapshot for what the DOM looked like.
Q: Is there a way to grep/filter tests by tag?
Yes — the bundled @cypress/grep plugin lets you filter by title or tag (e.g. --env grep=@smoke).
Q: How do I take a screenshot or video manually?
cy.screenshot('name') captures on demand. Set video: true in config to record full runs to videosFolder.
14. Quick Reference
Most-used commands
cy.visit('/path') // load a page
cy.get('[data-cy="x"]') // query an element
cy.contains('text') // query by text
cy.click() / cy.type('...') / cy.clear()// interact
cy.should('be.visible') // assert (auto-retried)
cy.intercept('GET', '/api', ...).as('a')// stub/spy network
cy.wait('@a') // wait on a request
cy.session(id, setupFn) // cache login
cy.fixture('data.json') // load test data
cy.task('name', arg) // run Node code
CLI cheat sheet
npx cypress open # interactive App
npx cypress run # headless CI run
npx cypress run --spec "**/login.cy.js" --browser chrome
npx cypress run --record --key KEY --parallel
npx cypress verify # verify binary
npx cypress info # environment info
npx cypress cache list --size # cached versions
npx cypress install --force # reinstall binary
Key config defaults
| Option | Default |
|---|---|
defaultCommandTimeout |
4000 ms |
pageLoadTimeout |
60000 ms |
viewportWidth × viewportHeight |
1000 × 660 (E2E) |
retries |
{ runMode: 0, openMode: 0 } |
testIsolation |
true |
video |
false |
screenshotOnRunFailure |
true |