Cypress CI/CD Integration: GitHub Actions, Parallelization, and Reporting

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/videos

The 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 chrome

Cypress 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:3000

Parallelization with Cypress Cloud

The simplest way to parallelize Cypress tests is Cypress Cloud (formerly Dashboard). It distributes test files across machines automatically.

Setup:

  1. Sign up at cloud.cypress.io
  2. Get your project ID and record key
  3. 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: 14

Set 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-junit

This 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=5

Cypress 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: true

Continuous 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:

  1. Use cypress-io/github-action — handles caching and browser installation
  2. Use wait-on — ensure the app is ready before tests start
  3. Parallelize with matrix strategy — 4× machines = 4× throughput
  4. Set retries: 2 in CI — absorb single-run flakiness
  5. Upload screenshots and videos on failure for fast debugging
  6. Run component tests separately — faster, cheaper, no server needed
  7. Use Cypress Cloud if you want automatic load balancing and flakiness tracking

Read more