How to Automate Tests with Jenkins
Jenkins pipelines are defined in a Jenkinsfile, a text file committed to your repository alongside the code it builds and tests. Using a declarative syntax, the Jenkinsfile specifies which agents run the pipeline, what stages execute in what order, how test results are reported, and what happens when stages fail. This "pipeline as code" approach means your CI configuration is version-controlled, reviewable, and reproducible.
Create a Jenkinsfile
Add a file named Jenkinsfile (no extension) to your repository root. The declarative syntax starts with a pipeline block containing agent, stages, and post-build sections:
pipeline {
agent any
environment {
NODE_ENV = 'test'
}
stages {
stage('Install') {
steps {
sh 'npm ci'
}
}
stage('Lint') {
steps {
sh 'npx eslint . --max-warnings=0'
}
}
stage('Unit Tests') {
steps {
sh 'npx jest --ci --reporters=default --reporters=jest-junit'
}
post {
always {
junit 'junit.xml'
}
}
}
}
post {
failure {
echo 'Pipeline failed. Check test results.'
}
}
}
The agent any directive tells Jenkins to run the pipeline on any available agent. The environment block sets environment variables for all stages. Each stage block defines a named step in the pipeline, and the post block after Unit Tests publishes JUnit XML results to Jenkins's built-in test reporting UI.
In Jenkins, create a new Pipeline job, set the pipeline definition to "Pipeline script from SCM," point it at your repository, and Jenkins will automatically find and execute the Jenkinsfile on every build trigger.
Configure Stages for Test Types
A complete testing pipeline has multiple stages that progressively increase in depth and execution time. Structure them as quality gates: each stage must pass before the next one runs.
pipeline {
agent any
stages {
stage('Install') {
steps {
sh 'npm ci'
}
}
stage('Static Analysis') {
steps {
sh 'npx eslint . --max-warnings=0'
sh 'npx tsc --noEmit'
}
}
stage('Unit Tests') {
steps {
sh 'npx jest --ci --coverage --reporters=default --reporters=jest-junit'
}
post {
always {
junit 'junit.xml'
publishHTML(target: [
reportDir: 'coverage/lcov-report',
reportFiles: 'index.html',
reportName: 'Coverage Report'
])
}
}
}
stage('Integration Tests') {
steps {
sh 'npx jest --ci --config jest.integration.config.js --reporters=default --reporters=jest-junit'
}
post {
always {
junit 'junit-integration.xml'
}
}
}
stage('E2E Tests') {
steps {
sh 'npm run build'
sh 'npm start &'
sh 'npx wait-on http://localhost:3000 --timeout 30000'
sh 'npx playwright test'
}
post {
always {
publishHTML(target: [
reportDir: 'playwright-report',
reportFiles: 'index.html',
reportName: 'Playwright Report'
])
}
}
}
}
}
Separate Jest configurations for unit and integration tests let you run different test directories with different settings (timeouts, environment variables, setup files). Integration tests might need longer timeouts and database connection strings. E2E tests need the application running, so the stage builds it, starts it as a background process, waits for it to be healthy, then runs Playwright tests.
Add Parallel Execution
Jenkins supports parallel stages within a parallel block. Use this when stages are independent and can run simultaneously:
stage('Test') {
parallel {
stage('Unit Tests') {
agent { docker { image 'node:20' } }
steps {
sh 'npm ci'
sh 'npx jest --ci --shard=1/2'
}
post {
always { junit 'junit.xml' }
}
}
stage('Unit Tests Shard 2') {
agent { docker { image 'node:20' } }
steps {
sh 'npm ci'
sh 'npx jest --ci --shard=2/2'
}
post {
always { junit 'junit.xml' }
}
}
stage('Security Scan') {
agent { docker { image 'node:20' } }
steps {
sh 'npm ci'
sh 'npm audit --audit-level=high'
}
}
}
}
Each parallel stage runs on its own agent (Docker container in this example), executing independently. Jenkins waits for all parallel stages to complete before moving to the next sequential stage. If any parallel stage fails, the overall pipeline fails, but all parallel stages still complete so you see the full results.
For E2E test parallelism, shard Playwright tests across parallel stages the same way:
stage('E2E Tests') {
parallel {
stage('E2E Shard 1') {
agent { docker { image 'mcr.microsoft.com/playwright:v1.47.0-noble' } }
steps {
sh 'npm ci'
sh 'npx playwright test --shard=1/3'
}
}
stage('E2E Shard 2') {
agent { docker { image 'mcr.microsoft.com/playwright:v1.47.0-noble' } }
steps {
sh 'npm ci'
sh 'npx playwright test --shard=2/3'
}
}
stage('E2E Shard 3') {
agent { docker { image 'mcr.microsoft.com/playwright:v1.47.0-noble' } }
steps {
sh 'npm ci'
sh 'npx playwright test --shard=3/3'
}
}
}
}
Set Up Docker Agents
Docker agents run each pipeline stage in a fresh container, ensuring consistent environments regardless of what is installed on the Jenkins server. The Docker Pipeline plugin lets you specify container images per stage:
pipeline {
agent none // No global agent, each stage specifies its own
stages {
stage('Backend Tests') {
agent {
docker {
image 'python:3.12'
args '-v $HOME/.cache/pip:/root/.cache/pip'
}
}
steps {
sh 'pip install -r requirements.txt'
sh 'pytest --junitxml=results.xml'
}
post {
always { junit 'results.xml' }
}
}
stage('Frontend Tests') {
agent {
docker {
image 'node:20'
args '-v $HOME/.npm:/root/.npm'
}
}
steps {
sh 'npm ci'
sh 'npx jest --ci'
}
}
stage('E2E Tests') {
agent {
docker {
image 'mcr.microsoft.com/playwright:v1.47.0-noble'
}
}
steps {
sh 'npm ci'
sh 'npx playwright test'
}
}
}
}
The args parameter mounts cache volumes into the container so pip and npm caches persist between builds. Microsoft's official Playwright Docker image includes all browser binaries and system dependencies, eliminating the need to install them during the build.
For integration tests that need databases, combine Docker agents with Testcontainers or use Docker Compose to start service dependencies. Mount the host's Docker socket (-v /var/run/docker.sock:/var/run/docker.sock) into the build container so Testcontainers can create sibling containers.
Configure Test Reporting
Jenkins has built-in support for JUnit XML test reports. The junit step parses XML report files and displays results in the Jenkins UI: pass/fail counts, individual test details, failure messages, duration trends, and flaky test detection.
Configure your test frameworks to output JUnit XML:
- Jest: Install jest-junit (
npm install --save-dev jest-junit) and add--reporters=jest-junitto the test command. SetJEST_JUNIT_OUTPUT_DIR=./reportsto control the output location. - pytest: Use the built-in
--junitxml=reports/results.xmlflag. - Playwright: Add the junit reporter to playwright.config.ts:
reporter: [['junit', { outputFile: 'results.xml' }]] - JUnit 5 / Maven: Maven Surefire automatically generates JUnit XML reports in target/surefire-reports/.
The HTML Publisher plugin adds rich HTML report hosting. Upload Playwright's HTML report, Jest's coverage report, or Allure reports as publishable artifacts that team members can browse directly from the Jenkins build page.
For teams managing Jenkins at scale, the Test Results Analyzer plugin provides cross-build test trends: which tests are failing most frequently, which have become flaky, and which consistently take the longest to run. These insights guide test suite maintenance and pipeline optimization.
Build Triggers
Jenkins supports multiple triggers for starting pipeline builds. The most common for continuous testing:
Webhook triggers: GitHub, GitLab, and Bitbucket can send webhooks to Jenkins when code is pushed or a pull request is opened. Install the GitHub Branch Source plugin (or equivalent for your platform) to automatically create pipeline jobs for each branch and PR. This is the standard approach for continuous testing because it reacts to every code change instantly.
Poll SCM: Jenkins checks the repository for changes on a schedule (for example, every minute). This is a fallback when webhooks are not available, but it adds up to a minute of delay and generates unnecessary load on both Jenkins and the repository server.
Scheduled builds: Use cron syntax to run pipelines on a schedule, for example nightly builds that run slower test suites, weekly security scans, or monthly performance benchmark runs that are too expensive for every commit.
Pipeline Best Practices
Keep Jenkinsfiles in the repository. Never configure pipeline logic through the Jenkins web UI. Pipeline-as-code in the repository is version-controlled, reviewable, and portable between Jenkins instances.
Use shared libraries for common logic. If multiple repositories have similar pipeline structures, extract shared steps into a Jenkins shared library. This centralizes pipeline logic and makes it easier to update across all projects.
Limit agent scope. Use agent none at the pipeline level and specify agents per stage. This prevents a single agent from being blocked for the entire pipeline duration and allows different stages to use different environments (Docker images, node labels).
Clean up workspaces. Jenkins workspaces grow over time with build artifacts, test reports, and cached files. Use cleanWs() in the post-build section to clean up, or configure workspace cleanup policies at the Jenkins level.
Set timeouts. Prevent hung builds from consuming agents indefinitely by setting stage-level timeouts:
stage('E2E Tests') {
options {
timeout(time: 15, unit: 'MINUTES')
}
steps {
sh 'npx playwright test'
}
}
A production Jenkins pipeline uses a declarative Jenkinsfile with sequential quality gates (lint, unit tests, integration tests, E2E tests), parallel execution for independent stages, Docker agents for reproducible environments, and JUnit XML reporting for test result visibility. Trigger builds via webhooks for instant feedback on every code change.