Continuous Integration
The pattern that works for me in every CI provider: install deps, install Playwright with --with-deps (that flag is the one everyone forgets), run tests with workers: 1 for stability, upload the report with if: !cancelled() so you actually get the artifact when tests fail. The rest is boilerplate.
The three steps that actually matter
Every CI setup for Playwright boils down to the same three things. The --with-deps flag on step 2 is what trips people up most — it installs the OS-level browser dependencies (libglib, libnss, etc.) that the browser binary needs to actually launch. Without it you get 'Failed to launch browser' errors that look like a Playwright bug.
Workers on CI — use 1, not the default
By default Playwright uses all available CPU cores as workers. On a shared CI runner those cores are often virtual and shared with other jobs — running many workers in parallel leads to flaky tests from resource contention. I set workers: 1 on CI. If you need speed, use sharding across multiple machines rather than workers on one.
GitHub Actions — the config I actually use
The if: ${{ !cancelled() }} on the artifact upload is critical. When a test fails, the job is marked as failed — and by default any subsequent steps are skipped. Without this condition, you never get the HTML report when you need it most (when tests fail).
On deployment trigger: I use github.event.deployment_status.state == 'success' when testing against a preview URL. Vercel and similar platforms fire the deployment_status event and put the URL in deployment_status.target_url — I pass that as PLAYWRIGHT_TEST_BASE_URL.
Faster feedback on PRs — --only-changed
--only-changed runs only the test files affected by the current changeset. Playwright analyzes the dependency graph to figure out which test files import or depend on the changed source files. On a large project this can cut CI time from 15 minutes to 2 minutes for a small PR.
Important: this is a heuristic, not a guarantee. It can miss tests if the dependency analysis doesn't catch all relationships. I always follow it with a full test run after the PR merges to main.
When the browser won't launch on CI
Error: Failed to launch browser on CI is almost always a missing system dependency. First check: did you run --with-deps? If yes, try DEBUG=pw:browser to see exactly what the browser binary says when it fails to start.
Don't cache browser binaries between CI runs. The time to restore from cache is similar to re-downloading, and on Linux the OS dependencies aren't cacheable anyway. Just always reinstall.
Other CI providers
For all other providers the approach is the same — the only difference is the YAML syntax. Most use the official Playwright Docker image (mcr.microsoft.com/playwright:v1.x-noble) to skip the browser installation step entirely. The image already has all browsers and system dependencies installed.