Running Cypress in CI/CD Pipelines
Cypress ships with a headless mode specifically designed for continuous integration: instead of opening the interactive Test Runner, cypress run executes every spec in a headless browser (Electron by default, or Chrome/Firefox/Edge if installed) and exits with a non-zero status code on failure, which is exactly the signal a CI pipeline needs to fail a build. Wiring Cypress into CI turns end-to-end coverage into a gate: a pull request that breaks checkout flow or login never reaches main because the pipeline stops it, rather than relying on a human to notice during manual testing.
Cricket analogy: Just as a DRS (Decision Review System) check automatically overturns an incorrect on-field call before the game moves on, a CI-gated Cypress run automatically blocks a broken pull request before it reaches production, removing reliance on a human umpire catching every mistake.
Choosing a CI Provider and Cypress Modes
Cypress is provider-agnostic — it runs the same way on GitHub Actions, CircleCI, GitLab CI, Jenkins, or Bitbucket Pipelines because the only real requirement is a Node.js environment and a browser binary. GitHub Actions users typically use the official cypress-io/github-action, which handles installing dependencies, caching the Cypress binary cache (a separate download from node_modules), and starting the app server before running specs. CircleCI offers a similar convenience through the cypress-io/cypress orb. The key performance lever in any provider is caching: Cypress's binary is large (100+ MB) and re-downloading it on every run adds minutes to each build, so caching ~/.cache/Cypress alongside node_modules is essential for fast pipelines.
Cricket analogy: Choosing a CI provider is like choosing which ground to host a Test match — Lord's, the MCG, or Eden Gardens all follow the same ICC playing conditions, but each venue has its own pitch preparation quirks a touring team must adapt to.
name: e2e-tests
on: [pull_request]
jobs:
cypress-run:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Cache Cypress binary and node_modules
uses: actions/cache@v4
with:
path: |
~/.cache/Cypress
node_modules
key: ${{ runner.os }}-cypress-${{ hashFiles('**/package-lock.json') }}
- name: Cypress run
uses: cypress-io/github-action@v6
with:
build: npm run build
start: npm start
wait-on: 'http://localhost:3000'
browser: chrome
record: true
env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}Headless Execution, Recording, and Artifacts
By default, cypress run captures a video of the entire spec run and a screenshot at the moment of any failure, saving both to cypress/videos and cypress/screenshots. In CI, these artifacts should be uploaded as build artifacts (via actions/upload-artifact on GitHub Actions, for example) so a failing run can be diagnosed without reproducing it locally. Passing the --record flag alongside a CYPRESS_RECORD_KEY environment variable sends results to Cypress Cloud, which layers a searchable dashboard, DOM snapshots at each command, and network stubs on top of the raw video — dramatically cutting the time needed to root-cause an intermittent CI failure.
Cricket analogy: This is like Hawk-Eye ball-tracking data being saved after every delivery in a Test match — even if nobody reviews it live, having the trajectory recorded means a controversial LBW decision can be re-examined later without replaying the over.
Cypress Cloud's free tier includes a limited number of recorded test results per month. Teams that only need artifacts for debugging (not the dashboard, parallelization, or flaky-test analytics) can skip --record entirely and rely on uploading the local video/screenshot artifacts through their CI provider's native artifact storage.
Environment Variables and Secrets
Cypress automatically picks up any environment variable prefixed with CYPRESS_ and exposes it inside tests via Cypress.env() — for example, CYPRESS_API_URL becomes accessible as Cypress.env('API_URL'). This is the standard mechanism for pointing the same test suite at different environments (staging vs. production-like preview URLs) without editing cypress.config.js per run, and for injecting the record key or third-party API tokens needed during a test. In CI, these values should always come from the provider's encrypted secret store (GitHub Actions secrets, CircleCI contexts, GitLab CI/CD variables) rather than being committed to the repository or hardcoded in the pipeline YAML, since CI logs and config files are far more exposed than a local .env file.
Cricket analogy: This is like a team's coded signal system between the dressing room and the pitch — a specific towel signal means something different depending on match situation, just as the same CYPRESS_API_URL variable resolves to a different endpoint depending on which environment triggered the run.
Never echo CYPRESS_RECORD_KEY or other secret-backed environment variables to CI logs (avoid env | grep CYPRESS in a debug step) — CI logs are frequently retained for months and may be visible to a wider audience than the secret store itself.
cypress runis the headless CI entry point and exits non-zero on any test failure, making it a natural pipeline gate.- Cypress works identically across GitHub Actions, CircleCI, GitLab CI, and Jenkins; only Node.js and a browser binary are required.
- Caching
~/.cache/Cypressandnode_modulesis the single biggest lever for keeping CI run times fast. - Videos and screenshots are captured automatically on failure; upload them as CI artifacts for post-mortem debugging.
- The
--recordflag plusCYPRESS_RECORD_KEYsends results to Cypress Cloud for a searchable dashboard and DOM-level debugging. - Any
CYPRESS_-prefixed environment variable is exposed to tests viaCypress.env(), enabling per-environment configuration. - Secrets must always come from the CI provider's encrypted store, never hardcoded in YAML or committed to the repo.
Practice what you learned
1. Which command is the standard entry point for running Cypress tests in a CI pipeline?
2. What environment variable prefix does Cypress automatically expose to `Cypress.env()`?
3. What is the primary purpose of caching `~/.cache/Cypress` in a CI pipeline?
4. What does passing `--record` with a valid `CYPRESS_RECORD_KEY` do?
5. Where should CI secrets like `CYPRESS_RECORD_KEY` be stored?
Was this page helpful?
You May Also Like
Parallelization and Cypress Cloud
Understand how Cypress Cloud distributes specs across parallel CI machines using load balancing, and how to interpret parallelization results.
Retries and Flake Reduction
Learn how Cypress's built-in retry-ability and test-level retries work, and practical techniques for reducing flaky end-to-end tests.
Debugging Cypress Tests
Learn the practical toolkit for debugging failing or flaky Cypress tests — from time-travel debugging and the Command Log to browser DevTools and cy.debug().