Most Playwright tutorials reach for GitHub Actions. But if you work in an enterprise environment, there's a good chance your team runs Jenkins — self-hosted, behind a VPN, integrated with Jira, SonarQube, and an artifact registry that predates GitHub Actions by a decade.
Setting up Playwright on Jenkins has a few gotchas that don't exist on hosted CI: browser dependencies, display servers, Content Security Policy blocking your HTML report, and credential injection patterns that differ from GitHub Secrets. This guide covers all of them.
What This Guide Covers
- Jenkins setup for Playwright (plugins + Docker)
- Declarative Jenkinsfile from scratch
- Docker agent with official Playwright image
- Parallel sharding across multiple containers
- HTML report publishing in Jenkins UI
- Secure credentials with Jenkins Credentials Manager
- Scheduled nightly regression runs
- Slack failure notifications via webhook
1. Jenkins vs GitHub Actions for Playwright
Before diving in, it's worth understanding why you'd choose Jenkins over a hosted solution in 2026.
Choose Jenkins when:
- Self-hosted infrastructure — tests must run inside a private network, access internal staging environments, or use on-premise hardware (mobile device farms, dedicated GPU machines)
- Enterprise integrations — Jira test results, SonarQube quality gates, Nexus artifact publishing, LDAP authentication — Jenkins has plugins for all of these out of the box
- Fine-grained access control — per-project credentials, role-based access, audit logs that satisfy compliance requirements (SOC 2, ISO 27001)
- Existing investment — your team already runs Jenkins for deployment pipelines. Adding a test stage is much simpler than maintaining a second CI platform
Choose GitHub Actions when:
- Zero infrastructure management and a free tier are sufficient
- Your team is fully cloud-native with no on-premise requirements
- You want native GitHub PR integration without webhook configuration
Many enterprise teams run both: Jenkins as the primary CI (build, test, deploy) and GitHub Actions for lightweight PR checks. This guide focuses on making Jenkins the authoritative test runner. For the GitHub Actions alternative, see our GitHub Actions CI/CD guide.
2. Jenkins Setup
Required plugins
Go to Manage Jenkins → Plugins → Available plugins and install:
- Pipeline — Declarative Pipeline syntax support (usually pre-installed)
- Docker Pipeline — run pipeline stages inside Docker containers
- HTML Publisher — publish Playwright HTML reports as a job artifact link
- Slack Notification — send build status alerts to Slack channels
- Credentials Binding — inject secrets from Jenkins Credentials Manager into pipeline stages
- Git — clone repositories (usually pre-installed)
Jenkins version: This guide targets Jenkins LTS 2.440+ with Pipeline Plugin 2.7+. The Declarative Pipeline syntax used here requires Pipeline Plugin 2.5 or later. Check your version at Manage Jenkins → System Information.
Docker on the Jenkins agent
The Playwright Docker agent approach eliminates browser installation headaches entirely. Make sure Docker is installed on your Jenkins agent nodes:
# Install Docker on the agent (Ubuntu) sudo apt-get update sudo apt-get install -y docker.io # Add the jenkins user to the docker group sudo usermod -aG docker jenkins # Verify docker run hello-world
Restart the Jenkins agent after adding the user to the docker group. Without this, the pipeline will fail with permission denied while trying to connect to the Docker daemon socket.
Create a Pipeline job
- Click New Item in Jenkins
- Enter a name (e.g.,
playwright-e2e) and select Pipeline - Under Pipeline, set Definition to Pipeline script from SCM
- Set SCM to Git and enter your repository URL
- Set Script Path to
Jenkinsfile - Save
Jenkins will now read your Jenkinsfile from the repository root on every build trigger. This is the recommended approach — your pipeline definition lives in version control alongside your tests.
3. Basic Jenkinsfile
Start with the simplest working pipeline. This installs dependencies, runs all tests, and archives the report — all inside the official Playwright Docker container.
pipeline { agent { docker { // Official Playwright image — browsers pre-installed image 'mcr.microsoft.com/playwright:v1.47.0-noble' // Required: Playwright needs HOME set when running as root in Docker args '-e HOME=/root' } } environment { // Playwright needs this in headless Linux environments CI = 'true' } stages { stage('Install') { steps { sh 'npm ci' } } stage('Test') { steps { sh 'npx playwright test' } } } post { always { // Archive test results even on failure archiveArtifacts artifacts: 'playwright-report/**', allowEmptyArchive: true archiveArtifacts artifacts: 'test-results/**', allowEmptyArchive: true } } }
Image version pinning: Always pin the Playwright Docker image version (e.g., v1.47.0-noble) to match the @playwright/test version in your package.json. Mismatched versions cause "browser revision mismatch" errors. Update both together when upgrading Playwright.
Why the Docker agent?
Without Docker, you'd need to install Node.js, browsers, and all system-level browser dependencies (libgbm, libasound2, fonts, etc.) on every Jenkins agent. The official Playwright Docker image bundles all of this. A fresh Jenkins agent with Docker takes 2 minutes to run Playwright tests. Without Docker, it takes 20+ minutes of provisioning — or a fragile agent configuration that breaks on OS updates.
4. Publishing the HTML Report
Archiving artifacts gives you a downloadable zip, but Jenkins can also serve the Playwright HTML report directly as a clickable link on the job page. This requires the HTML Publisher plugin.
post { always { archiveArtifacts artifacts: 'playwright-report/**,test-results/**', allowEmptyArchive: true publishHTML(target: [ reportName: 'Playwright Report', reportDir: 'playwright-report', reportFiles: 'index.html', keepAll: true, alwaysLinkToLastBuild: true, allowMissing: true ]) } }
After the build, a Playwright Report link appears in the Jenkins job sidebar. Click it to view the interactive HTML report without downloading anything.
Fixing the Content Security Policy (CSP) issue
Jenkins ships with a strict Content Security Policy that blocks the JavaScript in Playwright's HTML report. You'll see a blank white page with CSP errors in the browser console. Fix it by running this in the Jenkins Script Console (Manage Jenkins → Script Console):
System.setProperty( 'hudson.model.DirectoryBrowserSupport.CSP', "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;" )
This relaxes CSP enough for Playwright's report to render correctly while still blocking external resource loading. To make this persistent across Jenkins restarts, add it to your Jenkins JVM arguments in jenkins.xml or JAVA_OPTS:
JAVA_OPTS="-Dhudson.model.DirectoryBrowserSupport.CSP=\ \"default-src 'self'; script-src 'self' 'unsafe-inline'; \ style-src 'self' 'unsafe-inline'; img-src 'self' data:;\""
Security note: The permissive CSP setting default-src *; script-src * 'unsafe-inline' that many StackOverflow answers recommend is too broad for production Jenkins. Use the more restrictive version above, which only allows inline scripts and styles from the same origin — sufficient for Playwright's self-contained report.
5. Secure Credentials
Test pipelines often need secrets: staging URLs, login credentials, API keys. Never hardcode these in your Jenkinsfile. Use Jenkins Credentials Manager.
Adding credentials in Jenkins
- Go to Manage Jenkins → Credentials → System → Global credentials
- Click Add Credentials
- For a URL or API key: kind = Secret text, ID =
staging-base-url - For login pairs: kind = Username with password, ID =
test-user-creds
Using credentials in Jenkinsfile
pipeline { agent { docker { image 'mcr.microsoft.com/playwright:v1.47.0-noble'; args '-e HOME=/root' } } environment { CI = 'true' // Inject secret text credentials as environment variables BASE_URL = credentials('staging-base-url') API_KEY = credentials('test-api-key') } stages { stage('Install') { steps { sh 'npm ci' } } stage('Test') { steps { // BASE_URL and API_KEY are available as env vars // Jenkins automatically masks them in log output sh 'npx playwright test' } } } post { always { publishHTML(target: [reportName: 'Playwright Report', reportDir: 'playwright-report', reportFiles: 'index.html', keepAll: true, alwaysLinkToLastBuild: true, allowMissing: true]) } } }
Jenkins automatically masks credential values in build logs — any log line containing the secret value is replaced with ****. Your playwright.config.ts reads these the same way it would in any CI environment:
export default defineConfig({ use: { baseURL: process.env.BASE_URL || 'http://localhost:3000', }, });
For username/password credentials, Jenkins injects them as CREDENTIAL_ID_USR and CREDENTIAL_ID_PSW — two separate environment variables split from the credential pair.
6. Parallel Execution with Sharding
A 200-test Playwright suite can take 15–25 minutes on a single container. Split it across 4 parallel containers and you're under 7 minutes. Jenkins' parallel() directive makes this straightforward.
pipeline { // No top-level agent — each parallel branch declares its own agent none environment { CI = 'true' BASE_URL = credentials('staging-base-url') PLAYWRIGHT_IMAGE = 'mcr.microsoft.com/playwright:v1.47.0-noble' } stages { stage('Test (Parallel Shards)') { parallel { stage('Shard 1/4') { agent { docker { image "${PLAYWRIGHT_IMAGE}"; args '-e HOME=/root' } } steps { sh 'npm ci' sh 'npx playwright test --shard=1/4' } post { always { stash name: 'shard-1-results', includes: 'blob-report/**', allowEmpty: true } } } stage('Shard 2/4') { agent { docker { image "${PLAYWRIGHT_IMAGE}"; args '-e HOME=/root' } } steps { sh 'npm ci' sh 'npx playwright test --shard=2/4' } post { always { stash name: 'shard-2-results', includes: 'blob-report/**', allowEmpty: true } } } stage('Shard 3/4') { agent { docker { image "${PLAYWRIGHT_IMAGE}"; args '-e HOME=/root' } } steps { sh 'npm ci' sh 'npx playwright test --shard=3/4' } post { always { stash name: 'shard-3-results', includes: 'blob-report/**', allowEmpty: true } } } stage('Shard 4/4') { agent { docker { image "${PLAYWRIGHT_IMAGE}"; args '-e HOME=/root' } } steps { sh 'npm ci' sh 'npx playwright test --shard=4/4' } post { always { stash name: 'shard-4-results', includes: 'blob-report/**', allowEmpty: true } } } } } stage('Merge Reports') { // Merge all shard blob reports into a single HTML report agent { docker { image "${PLAYWRIGHT_IMAGE}"; args '-e HOME=/root' } } steps { sh 'npm ci' unstash 'shard-1-results' unstash 'shard-2-results' unstash 'shard-3-results' unstash 'shard-4-results' sh 'npx playwright merge-reports --reporter html ./blob-report' } post { always { publishHTML(target: [ reportName: 'Playwright Report', reportDir: 'playwright-report', reportFiles: 'index.html', keepAll: true, alwaysLinkToLastBuild: true, allowMissing: true ]) } } } } }
Each shard runs independently in its own Docker container, then stash/unstash collects the partial blob reports into a final Merge Reports stage that produces a single unified HTML report. This is the same pattern Playwright recommends for distributed CI runs.
Blob reporter config: In playwright.config.ts, set reporter: [['blob']] when running with --shard, and reporter: [['html']] for local runs. Or check the CI env variable: reporter: process.env.CI ? [['blob']] : [['html']]. The merge step then converts blob reports to the final HTML.
7. playwright.config.ts for Jenkins
A few config settings matter specifically for Jenkins runs:
import { defineConfig, devices } from '@playwright/test'; const isCI = !!process.env.CI; export default defineConfig({ testDir: './tests', fullyParallel: true, forbidOnly: isCI, // 2 retries on CI catches intermittent flakiness retries: isCI ? 2 : 0, // Blob reporter for shard merging in CI; HTML for local reporter: isCI ? [['blob'], ['junit', { outputFile: 'results/junit.xml' }]] : [['html', { open: 'on-failure' }]], use: { baseURL: process.env.BASE_URL || 'http://localhost:3000', // Capture traces on first retry — essential for Jenkins debugging trace: 'on-first-retry', screenshot: 'only-on-failure', video: 'retain-on-failure', // Headless in CI — no display server available headless: true, // Longer timeouts for slower CI environments actionTimeout: isCI ? 30_000 : 10_000, navigationTimeout: isCI ? 60_000 : 30_000, }, projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] }, }, ], });
The JUnit XML reporter is optional but valuable if your Jenkins setup integrates with test result trend graphs — Jenkins' built-in JUnit plugin can parse this XML and display pass/fail trends across builds:
post { always { // Parse JUnit XML for trend graphs and per-test status junit allowEmptyResults: true, testResults: 'results/junit.xml' } }
8. Scheduled Nightly Runs
The most common Jenkins pattern for Playwright: run smoke tests on every commit trigger, and run the full regression suite overnight when CI resources are idle.
pipeline { agent { docker { image 'mcr.microsoft.com/playwright:v1.47.0-noble'; args '-e HOME=/root' } } triggers { // Poll SCM every 5 minutes for changes (or use webhook) pollSCM('H/5 * * * *') // Full regression at 2 AM Monday–Friday cron('0 2 * * 1-5') } environment { CI = 'true' BASE_URL = credentials('staging-base-url') // Detect whether this is a nightly run NIGHTLY = env.BUILD_CAUSE?.contains('TimerTrigger') ? 'true' : 'false' } stages { stage('Install') { steps { sh 'npm ci' } } stage('Smoke Tests') { when { environment name: 'NIGHTLY', value: 'false' } steps { sh 'npx playwright test --grep @smoke' } } stage('Full Regression') { when { environment name: 'NIGHTLY', value: 'true' } steps { sh 'npx playwright test' } } } post { always { publishHTML(target: [reportName: 'Playwright Report', reportDir: 'playwright-report', reportFiles: 'index.html', keepAll: true, alwaysLinkToLastBuild: true, allowMissing: true]) } } }
Webhooks over polling: pollSCM works but wastes Jenkins resources checking for changes constantly. Set up a Git webhook (GitHub, GitLab, Bitbucket all support this) to push to Jenkins on each commit instead. This gives you faster feedback and eliminates unnecessary polling jobs.
9. Slack Failure Notifications
A pipeline that fails silently gets ignored. Wire Slack notifications so your team is alerted the moment tests fail — with a direct link to the failing build and report.
Configure the Slack plugin
- Install the Slack Notification plugin
- Go to Manage Jenkins → System → Slack
- Add your Slack workspace domain and add the Bot User OAuth Token as a Jenkins credential (kind: Secret text, ID:
slack-bot-token) - Set the default channel (e.g.,
#playwright-ci)
Jenkinsfile Slack integration
post { always { publishHTML(target: [ reportName: 'Playwright Report', reportDir: 'playwright-report', reportFiles: 'index.html', keepAll: true, alwaysLinkToLastBuild: true, allowMissing: true ]) junit allowEmptyResults: true, testResults: 'results/junit.xml' } failure { slackSend( channel: '#playwright-ci', color: 'danger', tokenCredentialId: 'slack-bot-token', message: [ "*Playwright Tests Failed* :red_circle:", "Job: `${env.JOB_NAME}` | Build: `#${env.BUILD_NUMBER}`", "Branch: `${env.GIT_BRANCH}`", "<${env.BUILD_URL}|View Build> | <${env.BUILD_URL}Playwright_20Report/|View Report>" ].join('\n') ) } fixed { // Notify when a previously failing build recovers slackSend( channel: '#playwright-ci', color: 'good', tokenCredentialId: 'slack-bot-token', message: "*Playwright Tests Fixed* :white_check_mark: — `${env.JOB_NAME}` build `#${env.BUILD_NUMBER}` is now passing." ) } }
The fixed post condition fires only when a build succeeds after previous failures — useful for "all clear" signals without sending a message on every successful run.
10. Complete Production Jenkinsfile
Here's everything combined: Docker agent, parallel 4-shard execution, report merging, HTML publishing, JUnit results, and Slack notifications. This is the config used in real enterprise Playwright deployments:
pipeline { agent none environment { CI = 'true' BASE_URL = credentials('staging-base-url') PLAYWRIGHT_IMAGE = 'mcr.microsoft.com/playwright:v1.47.0-noble' } triggers { pollSCM('H/5 * * * *') cron('0 2 * * 1-5') } options { timeout(time: 60, unit: 'MINUTES') buildDiscarder(logRotator(numToKeepStr: '30')) disableConcurrentBuilds() } stages { stage('Test') { parallel { stage('Shard 1/4') { agent { docker { image "${PLAYWRIGHT_IMAGE}"; args '-e HOME=/root' } } steps { sh 'npm ci && npx playwright test --shard=1/4' } post { always { stash name: 's1', includes: 'blob-report/**', allowEmpty: true } } } stage('Shard 2/4') { agent { docker { image "${PLAYWRIGHT_IMAGE}"; args '-e HOME=/root' } } steps { sh 'npm ci && npx playwright test --shard=2/4' } post { always { stash name: 's2', includes: 'blob-report/**', allowEmpty: true } } } stage('Shard 3/4') { agent { docker { image "${PLAYWRIGHT_IMAGE}"; args '-e HOME=/root' } } steps { sh 'npm ci && npx playwright test --shard=3/4' } post { always { stash name: 's3', includes: 'blob-report/**', allowEmpty: true } } } stage('Shard 4/4') { agent { docker { image "${PLAYWRIGHT_IMAGE}"; args '-e HOME=/root' } } steps { sh 'npm ci && npx playwright test --shard=4/4' } post { always { stash name: 's4', includes: 'blob-report/**', allowEmpty: true } } } } } stage('Merge & Report') { agent { docker { image "${PLAYWRIGHT_IMAGE}"; args '-e HOME=/root' } } steps { sh 'npm ci' unstash 's1'; unstash 's2'; unstash 's3'; unstash 's4' sh 'npx playwright merge-reports --reporter html,junit ./blob-report' } post { always { publishHTML(target: [ reportName: 'Playwright Report', reportDir: 'playwright-report', reportFiles: 'index.html', keepAll: true, alwaysLinkToLastBuild: true, allowMissing: true ]) junit allowEmptyResults: true, testResults: 'results/junit.xml' } failure { slackSend( channel: '#playwright-ci', color: 'danger', tokenCredentialId: 'slack-bot-token', message: "*Playwright Tests Failed* :red_circle:\nJob: `${env.JOB_NAME}` | Build: `#${env.BUILD_NUMBER}`\n<${env.BUILD_URL}|View Build>" ) } fixed { slackSend( channel: '#playwright-ci', color: 'good', tokenCredentialId: 'slack-bot-token', message: "*Playwright Tests Fixed* :white_check_mark: — `${env.JOB_NAME}` build `#${env.BUILD_NUMBER}` passing again." ) } } } } }
11. Jenkins + Playwright Best Practices
Installing browsers with npx playwright install --with-deps on a bare Jenkins agent downloads 400–600MB and installs 20+ system packages on every run. The official Playwright Docker image does this once at build time. Use it.
Add options { timeout(time: 60, unit: 'MINUTES') } to every pipeline. Without this, a hung browser process or a test waiting forever can lock up an agent indefinitely. 60 minutes is generous for most suites — a healthy 200-test Playwright suite with 4-shard parallelism completes in under 10 minutes.
If a developer pushes twice in quick succession, two full regression runs launching simultaneously double your agent load. disableConcurrentBuilds() queues the second run until the first finishes. For smoke tests this is overkill — but for nightly full regression, it's essential.
Using mcr.microsoft.com/playwright:latest means tomorrow's build may use a different Playwright version than today's. Pin the exact version tag and update it alongside your @playwright/test package version bump. Treat it as a dependency upgrade, not a config change.
A commit-triggered smoke job (2–3 min) gives developers instant feedback. A nightly regression job (full suite) gives the team comprehensive coverage without slowing down the commit cycle. Use Jenkins' job triggering to kick off the regression job from the smoke job on the main branch if smoke passes.
FAQ
How do I run Playwright tests in Jenkins?
Create a Jenkinsfile with a Docker agent using the official Playwright image (mcr.microsoft.com/playwright). Add stages for npm ci and npx playwright test. In the post block, publish the HTML report and archive artifacts. Configure a Jenkins Pipeline job to read the Jenkinsfile from your repository's SCM.
Should I use Jenkins or GitHub Actions for Playwright CI?
Jenkins when you need self-hosted infrastructure, enterprise integrations (Jira, SonarQube, Nexus), or strict access control. GitHub Actions when you want zero infrastructure overhead and native GitHub PR integration. Many teams run both — Jenkins for deployment pipelines, GitHub Actions for lightweight PR checks.
How do I run Playwright tests in parallel in Jenkins?
Use the parallel() directive in a Declarative Pipeline with separate stages for each shard. Pass --shard=1/4, --shard=2/4, etc. to Playwright. Use stash/unstash to collect blob reports from each container, then merge them with npx playwright merge-reports in a final stage.
Why is the Playwright HTML report blank in Jenkins?
Jenkins' Content Security Policy blocks the report's JavaScript. Run this in the Jenkins Script Console: System.setProperty('hudson.model.DirectoryBrowserSupport.CSP', "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;"). For persistence across restarts, add it to JAVA_OPTS in your Jenkins startup configuration.
How do I store Playwright test credentials securely in Jenkins?
Use Jenkins Credentials Manager (Manage Jenkins → Credentials). Add secrets as "Secret text" or "Username with password". Inject them into your pipeline with credentials('my-credential-id') in the environment block. Jenkins automatically masks them in build logs.
Can I run Playwright without Docker in Jenkins?
Yes, but you'll need to install Node.js, browsers, and all browser system dependencies on each Jenkins agent. Run npx playwright install --with-deps chromium as a stage step. The downside: it adds 60–120 seconds per build, requires root access on the agent, and breaks if system package versions change. The Docker approach is strongly preferred for production pipelines.
Playwright + Claude AI Course
Master Playwright CI/CD — Jenkins, GitHub Actions, and AI Test Generation
This guide covers the Jenkins pipeline. The full course covers the complete framework: TypeScript, Page Object Model, API testing, CI/CD with Jenkins and GitHub Actions, and Claude AI generating tests 3–5× faster than writing by hand. Everything built on a real e-commerce project you can add to your portfolio.
- Complete CI/CD pipelines for Jenkins and GitHub Actions
- Claude AI generates tests 3–5× faster than writing by hand
- Web + API + Device testing in one framework
- Real e-commerce project — portfolio-ready from day one