Cypress CI/CD Integration: GitHub Actions, Parallelization, and Reporting
Getting Cypress tests to run reliably in CI is more than installing a package. Browser tests need headless browsers, system dependencies, shared state management, and enough parallelism to keep pipelines fast. This guide covers the complete setup: GitHub Actions workflow, browser installation, parallelization, artifacts, and reporting.
Basic GitHub Actions Setup
The fastest way to get Cypress running in GitHub Actions is using the official Cypress GitHub Action:
# .github/workflows/cypress.yml
name: Cypress Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Cypress tests
uses: cypress-io/github-action@v6
with:
start: npm start
wait-on: 'http://localhost:3000'
wait-on-timeout: 60
browser: chrome
- name: Upload screenshots
if: failure()
uses: actions/upload-artifact@v4
with:
name: cypress-screenshots
path: cypress/screenshots
- name: Upload videos
if: always()
uses: actions/upload-artifact@v4
with:
name: cypress-videos
path: cypress/videosThe cypress-io/github-action handles Node.js setup, dependency installation, browser installation, and caching automatically.
What wait-on Does
wait-on polls a URL before running tests. This prevents the test runner from starting while your app is still booting. If your app takes longer to start, increase wait-on-timeout (default: 60 seconds).
Installing Browsers Manually
If you prefer not to use the GitHub Action, install browsers manually:
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Install Cypress binary
run: npx cypress install
- name: Start app and run tests
run: npm start &
npx wait-on http://localhost:3000
npx cypress run --browser chromeCypress caches the browser binary at ~/.cache/Cypress. Cache it between runs:
- name: Cache Cypress binary
uses: actions/cache@v4
with:
path: ~/.cache/Cypress
key: cypress-${{ runner.os }}-${{ hashFiles('package-lock.json') }}Running Against a Docker-Composed Backend
For apps with a real database or backend service:
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: testdb
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- name: Setup database
run: npm run db:migrate
env:
DATABASE_URL: postgres://test:test@localhost:5432/testdb
- name: Run Cypress tests
uses: cypress-io/github-action@v6
with:
start: npm start
wait-on: 'http://localhost:3000'
env:
DATABASE_URL: postgres://test:test@localhost:5432/testdb
CYPRESS_BASE_URL: http://localhost:3000Parallelization with Cypress Cloud
The simplest way to parallelize Cypress tests is Cypress Cloud (formerly Dashboard). It distributes test files across machines automatically.
Setup:
- Sign up at cloud.cypress.io
- Get your project ID and record key
- Add the project ID to
cypress.config.ts:
export default defineConfig({
projectId: 'abc123',
e2e: {
// ...
},
});GitHub Actions with parallelization:
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
machines: [1, 2, 3, 4]
steps:
- uses: actions/checkout@v4
- name: Run Cypress in parallel
uses: cypress-io/github-action@v6
with:
start: npm start
wait-on: 'http://localhost:3000'
record: true
parallel: true
group: 'E2E Tests'
ci-build-id: ${{ github.run_id }}-${{ github.run_attempt }}
env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}Cypress Cloud assigns spec files to machines as they become available, balancing load based on historical run times.
Parallelization Without Cypress Cloud
If you prefer not to use Cypress Cloud, shard tests by directory:
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
spec:
- 'cypress/e2e/auth/**'
- 'cypress/e2e/checkout/**'
- 'cypress/e2e/dashboard/**'
- 'cypress/e2e/settings/**'
steps:
- uses: actions/checkout@v4
- name: Run Cypress (${{ matrix.spec }})
uses: cypress-io/github-action@v6
with:
start: npm start
wait-on: 'http://localhost:3000'
spec: ${{ matrix.spec }}This splits work equally across jobs without requiring any external service.
Cypress Configuration for CI
Tune cypress.config.ts for CI environments:
import { defineConfig } from 'cypress';
export default defineConfig({
e2e: {
baseUrl: process.env.CYPRESS_BASE_URL || 'http://localhost:3000',
// Retry flaky tests in CI
retries: {
runMode: 2, // retries in headless mode (CI)
openMode: 0, // no retries in interactive mode
},
// Increase timeout for slow CI environments
defaultCommandTimeout: 10000,
pageLoadTimeout: 30000,
// Always capture video in CI
video: true,
screenshotOnRunFailure: true,
// Limit concurrent tests per machine
experimentalRunAllSpecs: false,
},
});Test Artifacts
Cypress generates three artifact types on failure:
- Screenshots — automatically captured on
cy.screenshot()or test failure - Videos — full recording of the test run per spec file
- HAR files — network traffic log (with plugins)
Upload them in CI:
- name: Upload test artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: cypress-artifacts-${{ matrix.machines }}
path: |
cypress/screenshots
cypress/videos
retention-days: 14Set retention-days to something reasonable — videos accumulate fast and storage costs add up.
JUnit Reporting for Test Summary
Generate JUnit XML to get test results in the GitHub Actions summary view:
npm install --save-dev cypress-multi-reporters mocha-junit-reporter// cypress.config.ts
export default defineConfig({
reporter: 'cypress-multi-reporters',
reporterOptions: {
configFile: 'reporter-config.json',
},
});// reporter-config.json
{
"reporterEnabled": "spec, mocha-junit-reporter",
"mochaJunitReporterReporterOptions": {
"mochaFile": "test-results/results-[hash].xml"
}
}- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results
path: test-results/
- name: Report test results
uses: dorny/test-reporter@v1
if: always()
with:
name: Cypress Tests
path: test-results/*.xml
reporter: java-junitThis adds a test summary table to every GitHub Actions run.
Handling Flaky Tests
Flaky tests are the primary reason CI pipelines lose trust. Cypress's retries config handles most cases, but for persistent flakiness:
Tag and quarantine:
it.skip('flaky test — JIRA-456', () => {
// investigate before re-enabling
});Identify flakeys locally:
npx cypress run --spec "cypress/e2e/flaky.cy.ts" --<span class="hljs-built_in">env CYPRESS_RETRIES=5Cypress Cloud flakiness detection: Cypress Cloud automatically flags tests that pass on retry as flaky, giving you a flakiness rate per spec and per test.
Environment Variables
Expose secrets to Cypress via environment variables:
- name: Run Cypress
uses: cypress-io/github-action@v6
with:
start: npm start
wait-on: 'http://localhost:3000'
env:
CYPRESS_API_KEY: ${{ secrets.API_KEY }}
CYPRESS_AUTH_TOKEN: ${{ secrets.AUTH_TOKEN }}In Cypress tests, access them with Cypress.env():
const apiKey = Cypress.env('API_KEY');Note: environment variables prefixed with CYPRESS_ are automatically available via Cypress.env() (without the prefix).
Component Tests in CI
Run component tests separately from E2E tests:
jobs:
component-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run component tests
uses: cypress-io/github-action@v6
with:
component: true
browser: chrome
e2e-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run E2E tests
uses: cypress-io/github-action@v6
with:
start: npm start
wait-on: 'http://localhost:3000'Component tests don't need a running server, so they're fast and cheap to run on every push.
Pre-Merge vs. Scheduled Runs
A common pattern: run fast tests on every PR, run the full suite nightly.
# Fast tests on every PR
on:
pull_request:
branches: [main]
jobs:
smoke:
steps:
- uses: cypress-io/github-action@v6
with:
spec: 'cypress/e2e/smoke/**'
---
# Full suite nightly
on:
schedule:
- cron: '0 2 * * *' # 2am UTC daily
jobs:
full-suite:
strategy:
matrix:
machines: [1, 2, 3, 4, 5]
steps:
- uses: cypress-io/github-action@v6
with:
record: true
parallel: trueContinuous Monitoring Beyond CI
CI runs tests on deploys. But production can break between deploys due to third-party outages, database issues, or CDN failures. For 24/7 monitoring that runs your tests every 5 minutes and pages you when something breaks, HelpMeTest handles scheduling, alerting, and test history — no infrastructure required.
Summary
A production-ready Cypress CI setup:
- Use
cypress-io/github-action— handles caching and browser installation - Use
wait-on— ensure the app is ready before tests start - Parallelize with matrix strategy — 4× machines = 4× throughput
- Set
retries: 2in CI — absorb single-run flakiness - Upload screenshots and videos on failure for fast debugging
- Run component tests separately — faster, cheaper, no server needed
- Use Cypress Cloud if you want automatic load balancing and flakiness tracking