Running Cypress in CI/CD Pipelines — GitHub Actions, GitLab CI and Jenkins

A Cypress test suite that only runs on your laptop is a manual verification step. A suite wired into your CI/CD pipeline — triggered on every push, running against every pull request, blocking merges on failure — is an automated quality gate. Running Cypress in CI requires headless execution, browser installation, dependency management, and artifact collection. This lesson covers the complete setup for the three most popular CI platforms: GitHub Actions, GitLab CI, and Jenkins.

Cypress in GitHub Actions, GitLab CI and Jenkins

Each platform requires slightly different configuration, but the core pipeline structure is identical: install dependencies, start the application (if needed), run Cypress headlessly, and upload results.

// ── GITHUB ACTIONS — Complete workflow ──

const GITHUB_ACTIONS = `
# .github/workflows/cypress.yml
name: Cypress Tests

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  cypress-run:
    runs-on: ubuntu-22.04

    steps:
      # Step 1: Check out the code
      - name: Checkout
        uses: actions/checkout@v4

      # Step 2: Use the official Cypress GitHub Action
      # It handles: Node install, npm ci, browser install, caching
      - name: Cypress run
        uses: cypress-io/github-action@v6
        with:
          browser: chrome
          headed: false
          # If your app needs to be started first:
          # start: npm start
          # wait-on: 'http://localhost:3000'
          # wait-on-timeout: 120

      # Step 3: Upload screenshots and videos on failure
      - name: Upload artifacts
        uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: cypress-screenshots-videos
          path: |
            cypress/screenshots
            cypress/videos
          retention-days: 7
`;


// ── GITLAB CI — .gitlab-ci.yml ──

const GITLAB_CI = `
# .gitlab-ci.yml
cypress:
  image: cypress/browsers:latest
  stage: test
  script:
    - npm ci
    - npx cypress run --browser chrome
  artifacts:
    when: on_failure
    paths:
      - cypress/screenshots/
      - cypress/videos/
    expire_in: 7 days
  cache:
    key: cypress-\${CI_COMMIT_REF_SLUG}
    paths:
      - node_modules/
      - ~/.cache/Cypress/
`;


// ── JENKINS — Jenkinsfile ──

const JENKINS = `
// Jenkinsfile
pipeline {
    agent {
        docker {
            image 'cypress/browsers:latest'
        }
    }
    stages {
        stage('Install') {
            steps { sh 'npm ci' }
        }
        stage('Test') {
            steps { sh 'npx cypress run --browser chrome' }
        }
    }
    post {
        failure {
            archiveArtifacts artifacts: 'cypress/screenshots/**,cypress/videos/**'
        }
    }
}
`;


// ── Key CI configuration decisions ──

const CI_DECISIONS = [
    {
        decision: 'Browser selection',
        recommendation: 'Chrome for speed; Firefox for cross-browser; Electron as fallback',
        command: 'npx cypress run --browser chrome',
    },
    {
        decision: 'Dependency caching',
        recommendation: 'Cache node_modules/ and ~/.cache/Cypress/ to skip reinstallation',
        command: 'GitHub Action handles this; GitLab/Jenkins: configure cache paths',
    },
    {
        decision: 'Artifact collection',
        recommendation: 'Upload screenshots and videos on failure; use if: failure() or when: on_failure',
        command: 'Configured in the artifact/post step',
    },
    {
        decision: 'Application startup',
        recommendation: 'If testing your own app: start it in CI and wait for it to be ready',
        command: 'start: npm start, wait-on: http://localhost:3000',
    },
    {
        decision: 'Timeout configuration',
        recommendation: 'Increase defaultCommandTimeout to 10s in CI (slower than local)',
        command: 'CYPRESS_DEFAULT_COMMAND_TIMEOUT=10000 npx cypress run',
    },
];

console.log('CI/CD Platform Configurations');
console.log('GitHub Actions: cypress-io/github-action@v6 (recommended — handles everything)');
console.log('GitLab CI: cypress/browsers Docker image + npm ci + npx cypress run');
console.log('Jenkins: Dockerfile with cypress/browsers + pipeline stages');
Note: The official cypress-io/github-action@v6 is the recommended way to run Cypress in GitHub Actions. It handles Node.js installation, npm ci, Cypress binary caching, browser installation, and headless execution in a single step. It eliminates 15-20 lines of manual configuration and is maintained by the Cypress team. For GitLab and Jenkins, use the official cypress/browsers Docker image which includes Chrome, Firefox, Edge, and all system dependencies pre-installed.
Tip: Set CYPRESS_DEFAULT_COMMAND_TIMEOUT=10000 as an environment variable in your CI configuration. CI runners are typically slower than developer laptops due to shared CPU, containerisation overhead, and network latency. Increasing the default timeout from 4 seconds to 10 seconds prevents timeout-related flakiness in CI without changing your local development experience. You can also set CYPRESS_VIDEO=false to skip video recording and speed up the pipeline when you only need screenshots on failure.
Warning: If your Cypress tests need to test your own application (not a public site like saucedemo.com), you must start the application inside the CI pipeline before running tests. The start and wait-on options in the GitHub Action handle this automatically. Without them, Cypress tries to visit your app at localhost:3000 and fails with a connection refused error. Always verify the app is healthy before starting tests.

Common Mistakes

Mistake 1 — Not caching Cypress binary and node_modules

❌ Wrong: Every CI run downloads 500MB+ of npm packages and the Cypress binary from scratch — adding 3-5 minutes to every pipeline.

✅ Correct: Configuring dependency caching so npm ci uses the cache and the Cypress binary is restored from a previous run. The GitHub Action handles this automatically; for other CI platforms, cache ~/.cache/Cypress/ and node_modules/.

Mistake 2 — Not uploading failure artifacts

❌ Wrong: The pipeline fails, screenshots and videos are generated, but they are lost when the CI container is destroyed.

✅ Correct: Configuring artifact upload with if: failure() (GitHub) or when: on_failure (GitLab) so diagnostic evidence is preserved for post-failure analysis.

🧠 Test Yourself

What is the primary advantage of using cypress-io/github-action@v6 instead of manually configuring each CI step?