How to Use Jenkins with Cypress for End-to-End Testing

How to Use Jenkins with Cypress for End-to-End Testing

Unit tests give you confidence that individual functions work, but they’ve never once caught the kind of bug that shows up when a real user clicks through an actual checkout flow and hits a broken redirect. That’s the gap Cypress fills, and wiring it into Jenkins was one of the more satisfying integrations I’ve set up — watching a full browser-based test suite run automatically on every pull request, complete with screenshots and videos of any failures, genuinely changed how confident my team felt about shipping.

This article walks through setting up Jenkins to run Cypress end-to-end tests, from a basic setup to advanced patterns like parallel test splitting, Docker-based execution, and integration with reporting tools.

Why Cypress for End-to-End Testing

Cypress is a JavaScript-based end-to-end testing framework that runs directly in the browser, giving it much better visibility into your application than older tools like Selenium (which communicate with the browser over a remote protocol). Cypress can:

Running Cypress in Jenkins means every code change gets validated against real user workflows automatically, rather than relying solely on manual QA passes before releases.

Jenkins Architecture Context

Cypress tests need a running instance of your application to test against — meaning this pipeline stage typically comes after deployment to a test environment (or after starting your app locally within the same pipeline, for simpler setups). Jenkins agents running Cypress need Node.js and either a full browser install or Cypress’s official Docker images, which bundle all required browser dependencies.

Prerequisites

Step 1: Basic Project Setup

Assuming Cypress is already part of your package.json:

{
  "devDependencies": {
    "cypress": "^13.6.0"
  },
  "scripts": {
    "cypress:run": "cypress run"
  }
}

Step 2: Basic Jenkinsfile Running Cypress Directly

pipeline {
    agent any

    tools {
        nodejs 'NodeJS-20'
    }

    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }

        stage('Install Dependencies') {
            steps {
                sh 'npm ci'
            }
        }

        stage('Start Application') {
            steps {
                sh 'npm run start &'
                sh 'npx wait-on http://localhost:3000'
            }
        }

        stage('Run Cypress Tests') {
            steps {
                sh 'npx cypress run --reporter junit --reporter-options "mochaFile=results/cypress-[hash].xml"'
            }
        }
    }

    post {
        always {
            junit 'results/*.xml'
            archiveArtifacts artifacts: 'cypress/screenshots/**/*.png', allowEmptyArchive: true
            archiveArtifacts artifacts: 'cypress/videos/**/*.mp4', allowEmptyArchive: true
        }
        failure {
            mail to: 'qa-team@example.com',
                 subject: "Cypress Tests Failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
                 body: "Check archived screenshots and videos at ${env.BUILD_URL}"
        }
    }
}

The wait-on package here is important — it prevents Cypress from starting before your application is actually ready to accept requests, which is one of the most common causes of flaky CI test failures.

Step 3: Running Cypress via Docker (Recommended for Consistency)

Rather than installing browsers directly on Jenkins agents (which can lead to version drift and “works on my machine” issues), I strongly recommend using Cypress’s official Docker images:

pipeline {
    agent {
        docker {
            image 'cypress/included:13.6.0'
            args '-u root'
        }
    }

    stages {
        stage('Install Dependencies') {
            steps {
                sh 'npm ci'
            }
        }

        stage('Run Cypress Tests') {
            steps {
                sh 'cypress run --reporter junit --reporter-options "mochaFile=results/cypress-[hash].xml"'
            }
        }
    }

    post {
        always {
            junit 'results/*.xml'
            archiveArtifacts artifacts: 'cypress/screenshots/**/*.png', allowEmptyArchive: true
            archiveArtifacts artifacts: 'cypress/videos/**/*.mp4', allowEmptyArchive: true
        }
    }
}

The cypress/included image bundles Cypress itself plus all browser dependencies, meaning you don’t need to manage Chrome/Firefox installs on your Jenkins agents at all.

Step 4: Parallel Test Execution with Cypress

For large test suites, splitting tests across multiple parallel Jenkins stages dramatically cuts total run time. Combine this with Jenkins Pipeline’s parallel directive:

pipeline {
    agent none
    stages {
        stage('E2E Tests') {
            parallel {
                stage('Auth Flow Tests') {
                    agent { docker { image 'cypress/included:13.6.0' } }
                    steps {
                        sh 'npm ci'
                        sh 'cypress run --spec "cypress/e2e/auth/**/*.cy.js"'
                    }
                }
                stage('Checkout Flow Tests') {
                    agent { docker { image 'cypress/included:13.6.0' } }
                    steps {
                        sh 'npm ci'
                        sh 'cypress run --spec "cypress/e2e/checkout/**/*.cy.js"'
                    }
                }
                stage('Dashboard Tests') {
                    agent { docker { image 'cypress/included:13.6.0' } }
                    steps {
                        sh 'npm ci'
                        sh 'cypress run --spec "cypress/e2e/dashboard/**/*.cy.js"'
                    }
                }
            }
        }
    }
}

Alternatively, if you have a Cypress Cloud account, cypress run --record --parallel automatically load-balances test files across whatever number of Jenkins agents/containers you spin up, without you having to manually partition specs by folder.

Step 5: Cypress with Docker Compose for Full-Stack Testing

For applications with multiple dependent services (frontend, backend API, database), Docker Compose is often cleaner than trying to manage everything in raw shell steps:

# docker-compose.ci.yml
version: '3.8'
services:
  app:
    build: .
    ports:
      - "3000:3000"
    depends_on:
      - db
  db:
    image: postgres:15
    environment:
      POSTGRES_PASSWORD: testpass
  cypress:
    image: cypress/included:13.6.0
    depends_on:
      - app
    environment:
      CYPRESS_baseUrl: http://app:3000
    volumes:
      - ./cypress:/e2e/cypress
      - ./cypress.config.js:/e2e/cypress.config.js
    working_dir: /e2e
stage('Run Full-Stack E2E Tests') {
    steps {
        sh 'docker-compose -f docker-compose.ci.yml up --abort-on-container-exit --exit-code-from cypress'
    }
}

post {
    always {
        sh 'docker-compose -f docker-compose.ci.yml down -v'
    }
}

Integrating with Git and GitHub

Post Cypress results back to pull requests using GitHub status checks, and consider posting a summary comment with pass/fail counts and a link to archived screenshots/videos for failed tests:

post {
    failure {
        githubNotify status: 'FAILURE', context: 'cypress-e2e', description: 'E2E tests failed'
    }
    success {
        githubNotify status: 'SUCCESS', context: 'cypress-e2e', description: 'All E2E tests passed'
    }
}

Integrating with Kubernetes

For teams deploying preview environments per pull request in Kubernetes, trigger Cypress only after confirming the preview environment is healthy:

stage('Wait for Preview Environment') {
    steps {
        sh 'kubectl rollout status deployment/preview-${env.CHANGE_ID} --timeout=120s'
    }
}

stage('Run Cypress Against Preview') {
    steps {
        sh "CYPRESS_baseUrl=https://pr-${env.CHANGE_ID}.preview.myapp.com cypress run"
    }
}

This pattern — spin up an ephemeral preview environment per PR, then run Cypress against it — gives reviewers real confidence that a change works end-to-end before merging, not just that unit tests pass.

Monitoring and Reporting

Beyond JUnit XML for Jenkins’ built-in test reporting, consider integrating:

Troubleshooting Common Issues

Best Practices

FAQs

Do I need a Cypress Cloud subscription to use Cypress in Jenkins? No, cypress run works completely standalone and free; Cypress Cloud is an optional paid add-on for recording, dashboards, and automatic parallelization load-balancing.

Can Cypress test against a locally started app within the same Jenkins job, or does it need a separate deployed environment? Both work — for quick smoke tests you can start your app locally in the same pipeline job; for closer-to-production validation, testing against a real deployed staging or preview environment is generally more reliable.

How do I handle authentication in Cypress tests without repeating login steps in every test? Use Cypress’s cy.session() API to cache authenticated sessions across tests, dramatically speeding up suites that require login.

Is Cypress better than Selenium for CI/CD pipelines? Cypress generally offers faster, less flaky tests for modern JavaScript-heavy applications due to its architecture running inside the browser, though Selenium still has broader cross-browser and multi-language support in some enterprise contexts.

Summary

Integrating Cypress into Jenkins gives your pipeline real, browser-level confidence that critical user flows actually work — not just that isolated functions return the right values. Whether you run it directly on agents, inside Docker containers for consistency, or split across parallel stages for speed, the core pattern stays the same: get your application into a testable state, run Cypress, and archive results (JUnit XML, screenshots, videos) so failures are easy to diagnose without needing to reproduce them locally.

References

Exit mobile version