CI/CD and Scaling
By Syncfusion®, Inc. · Last updated
Why tests run on every push, a GitHub Actions workflow for Playwright, CI settings in the config (forbidOnly, retries, workers, reporters), environment variables and secrets, workers and parallel tests, sharding across machines and merging blob reports, running in Docker, other CI systems, and choosing which tests run when.
CI/CD and Scaling
- Tests help most when they run every time someone changes the code, not only when someone remembers.
- CI (continuous integration) is a server that runs your tests on every push and pull request. CD (continuous delivery) ships the app when they pass.
- In this lesson, you will set up Playwright for CI, and make a big suite fast with workers and sharding.
Simple lesson: CI runs your tests for you, on every change, on a clean machine.
A Real-World Example
- A developer added
test.onlyto focus on one test and forgot to remove it. Locally, only one test runs, and it passes. - On CI, that would hide every other test. The config's
forbidOnly: !!process.env.CIstops it. - The command sets
CI=true, like a CI server does. Run it.
- Remove
CI=truefrom the command and run again. Locally the focus is allowed, so only the login test runs.
What You Will Learn
- What a CI pipeline does, step by step.
- A GitHub Actions workflow for Playwright.
- CI settings in the config, environment variables and secrets.
- Workers and parallel tests.
- Sharding across machines and merging reports.
- Docker, other CI systems, and which tests to run when.
A CI Pipeline
The Steps
Every Playwright pipeline does the same five things on a fresh machine:
- Get the code.
- Install Node.js and the project's packages with
npm ci. - Install the browsers with
npx playwright install --with-deps. - Run
npx playwright test. - Upload the report and traces, even when tests fail.
Animation: a push starts the pipeline. Checkout, install packages, install browsers, run tests and upload report light up one after another, then a green check appears on the pull request
git pushor pull request
✓ All checks have passed
A push starts the pipeline. Each step runs on a clean machine, and the result shows on the pull request.
A GitHub Actions Workflow
npm init playwright@latestoffers to create this file:.github/workflows/playwright.yml.
name: Playwright Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npx playwright test
env:
BASE_URL: ${{ vars.BASE_URL }}
SHOP_PASSWORD: ${{ secrets.SHOP_PASSWORD }}
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
retention-days: 30
if: ${{ !cancelled() }}uploads the report even when tests fail. That is exactly when you need it.--with-depsalso installs the system libraries browsers need on Linux.- The Playwright docs advise not caching browsers. Downloading them is about as fast as restoring a cache.
Simple lesson: checkout, install, install browsers, test, upload. Upload even when tests fail.
The Config on CI
process.env.CI
- Most CI systems set the
CIenvironment variable. The config reads it to behave differently there. - The config made by
npm init playwrightalready does this:
- Open
playwright.config.tsin the Explorer. On CI:forbidOnlyis on,retriesis 2 andworkersis 1. - With
CI=true, the test fails once and passes on retry, so it is flaky. RemoveCI=trueand it simply fails.
Environment Variables and Secrets
- Point tests at different servers with a variable:
baseURL: process.env.BASE_URL ?? 'http://localhost:3000'. - Passwords and tokens go in the CI system's secrets, never in the repository. The workflow passes them in with
env:.
- Remove the
BASE_URL=...part and run. The test trieslocalhost:3000, which the lab cannot reach.
Never print secrets in tests or logs. CI systems hide known secrets in logs, but a changed or encoded copy can still leak.
Simple lesson: process.env.CI switches CI settings. URLs and secrets come from environment variables.
Running Tests in Parallel
Workers
- A worker is a separate process with its own browser. Tests in different workers run at the same time.
- By default, Playwright uses half your CPU cores.
--workers=4orworkers: 4sets it.workers: '50%'uses half the cores. - Files run in parallel. Tests inside one file run one after another, unless you turn on
fullyParallel: trueor usetest.describe.configure({ mode: 'parallel' }).
- The first line says how many workers ran. Try
--workers=1and--workers=6.
Why CI Often Uses One Worker
- CI machines are often small. Too many workers fight for CPU and make tests slower and flakier.
- The default config uses
workers: process.env.CI ? 1 : undefined. On a big CI machine, raise it. - To go faster than one machine allows, use sharding.
Sharding
Splitting the Suite Across Machines
--shard=1/3runs the first third of the tests. Three CI machines run1/3,2/3and3/3at the same time.- With
fullyParallel: true, Playwright splits by test. Without it, it splits by file.
- Change it to
--shard=2/3and--shard=3/3. Each shard runs different tests, and together they run all six.
Merging Shard Reports
- Each shard writes only part of the results. To get one HTML report, each shard uses the blob reporter, and a last job merges them:
jobs:
test:
strategy:
fail-fast: false
matrix:
shardIndex: [1, 2, 3, 4]
shardTotal: [4]
runs-on: ubuntu-latest
steps:
# ... checkout, npm ci, install browsers ...
- run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: blob-report-${{ matrix.shardIndex }}
path: blob-report
merge-reports:
if: ${{ !cancelled() }}
needs: [test]
runs-on: ubuntu-latest
steps:
# ... checkout, npm ci ...
- uses: actions/download-artifact@v4
with:
path: all-blob-reports
pattern: blob-report-*
merge-multiple: true
- run: npx playwright merge-reports --reporter html ./all-blob-reports
- In the config, use
reporter: process.env.CI ? 'blob' : 'html'.
Shards finish only as fast as the slowest one. Keep test files similar in size, or turn on fullyParallel so Playwright can balance single tests.
Simple lesson: workers split tests on one machine. Shards split them across machines. Blob reports merge back into one.
Docker and Other CI Systems
The Official Docker Image
- Microsoft publishes Docker images with Node.js, the browsers and their system libraries already installed.
- The image version must match your
@playwright/testversion, for examplemcr.microsoft.com/playwright:v1.64.0-noble. - Running in Docker gives everyone the same fonts and system, which keeps screenshot baselines stable.
docker run --rm --ipc=host -v "$PWD":/work -w /work \
mcr.microsoft.com/playwright:v1.64.0-noble \
/bin/bash -c "npm ci && npx playwright test"
--ipc=hostis recommended for Chromium, so it does not run out of shared memory.
When you update @playwright/test, update the Docker image tag in the same pull request. A mismatch is a common cause of "Executable doesn't exist" errors on CI.
Other CI Systems
- Azure Pipelines, GitLab CI, Jenkins and CircleCI follow the same steps: install, install browsers, test, publish the report.
- The Playwright docs have a ready-made file for each one.
- Use the
junitreporter where the CI system shows JUnit results in its own test tab.
Choosing What Runs When
Fast Feedback First
Not every test must run on every push. Many teams split them:
- On every pull request: a fast smoke set, tagged
@smoke, with--grep @smoke. - Before merging, or every night: the full suite on all browsers.
- On every pull request: a fast smoke set, tagged
--last-failedreruns only what failed last time.--only-changedruns tests affected by changed files, compared with git.maxFailures: 10stops a broken run early, so the team gets feedback faster.
Simple lesson: smoke tests on every change, the full suite before merging or overnight.
Try It in Your Project
- These steps take the lesson from the lab to your own app.
- Add the GitHub Actions workflow and make it upload the report even on failure.
- Run
@smoketests on pull requests and the full suite before merging. - When the suite passes 10 minutes, add sharding.
Simple lesson: use one idea from this lesson on your own app this week.
TL;DR
- CI runs tests on every change: checkout,
npm ci,npx playwright install --with-deps, test, upload the report. process.env.CIturns onforbidOnly, retries and fewer workers.- URLs and secrets come from environment variables, never from the code.
- Workers run tests in parallel on one machine.
fullyParallelsplits single tests. --shard=x/ysplits across machines. The blob reporter andmerge-reportsmake one report.- Docker images give everyone the same browsers. Run smoke tests often and the full suite before merging.
Common mistakes
| Mistake | What happens | Fix |
|---|---|---|
| Uploading the report only when tests pass | No report when you need it | if: ${{ !cancelled() }} |
Forgetting --with-deps on Linux | Browsers fail to start with missing libraries | npx playwright install --with-deps |
| A Docker image version that does not match the package | Browsers are missing or the wrong version | Use the same version as @playwright/test |
| Many workers on a small CI machine | Slow, flaky runs | One or two workers, then shard |
| Sharding with the html reporter | Each shard has a partial report | Use blob and merge-reports |
Check Yourself
Exercises
What to Remember
- CI runs tests on every change, on a clean machine. Upload the report even when tests fail.
process.env.CIswitches onforbidOnly, retries and fewer workers.- Secrets and URLs come from environment variables.
- Workers and
fullyParallelfor one machine.--shardandmerge-reportsfor many. - Docker gives everyone the same browsers. Smoke tests first, the full suite later.
Next: Playwright Library and Advanced Browser Control