Playwright GitHub Actions setup guide 2026: Complete walkthrough
By TurnSignal · October 2, 2026 · 5 min read
In short
- Start from the workflow Playwright generates: checkout, setup-node, npm ci, npx playwright install --with-deps, npx playwright test, upload the report.
- Shard with a matrix for large suites, use the blob reporter and merge-reports for one combined HTML report, and give each shard a unique artifact name.
- Enable retries in CI only, keep traces on failure, and use failOnFlakyTests if a pass on retry should still fail the build.
- GitHub keeps artifacts for 90 days by default; Playwright's template sets retention-days to 30. Post one PR comment and update it in place with find-comment.
The complete workflow file
This workflow runs Playwright tests on every push to main and every pull request, split across four shards, and uploads each shard's results. It follows the workflow Playwright itself generates with npm init playwright@latest, plus a sharding matrix.
name: Playwright Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shardIndex: [1, 2, 3, 4]
shardTotal: [4]
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: lts/*
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
- name: Upload blob report
uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: blob-report-${{ matrix.shardIndex }}
path: blob-report
retention-days: 1
This runs 4 shards in parallel. Change shardIndex and shardTotal together to match your suite size. Each shard uploads a blob report, which the merge job further down turns into one HTML report.
Step by step explanation
Triggers
The workflow triggers on pushes to main and on pull requests targeting main. Add more branches if you release from them.
Job configuration
The timeout-minutes setting kills the job if it runs longer, so a hung test does not burn CI minutes for the default six hours. ubuntu-latest is the runner Playwright's own template uses.
Sharding matrix
Setting fail-fast to false means a failure in one shard does not cancel the others, so you see every failure in one run instead of only the first. The matrix creates one job per shard index.
Setup steps
npm ci installs exactly what your lock file says. npx playwright install --with-deps downloads the browser binaries and the operating system libraries they need on Linux. The cache: 'npm' option on setup-node caches the npm download cache, keyed on your lock file.
Running tests
Each shard runs a subset of the suite. By default Playwright shards by test file. With fullyParallel: true it shards by individual test, which balances the shards much better when your files have very different numbers of tests. Playwright does not balance shards by duration, so one slow file can make one shard the long pole.
Uploading artifacts
The !cancelled() condition uploads results even when tests fail, and skips the upload when someone cancels the run. Each shard needs a unique artifact name, which is why the shard index is part of it. GitHub keeps artifacts for 90 days by default unless you set retention-days.
Merging shard reports into one
Without merging, every shard has its own partial report and you have to open four of them. Set the blob reporter in CI, then add a job that downloads every blob report and merges them.
merge-reports:
if: ${{ !cancelled() }}
needs: [test]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: lts/*
- run: npm ci
- uses: actions/download-artifact@v5
with:
path: all-blob-reports
pattern: blob-report-*
merge-multiple: true
- run: npx playwright merge-reports --reporter html ./all-blob-reports
- uses: actions/upload-artifact@v4
with:
name: html-report--attempt-${{ github.run_attempt }}
path: playwright-report
retention-days: 14
The blob report names include the shard number, so they do not clash when merged. The needs line waits for every shard; !cancelled() still merges when some shards failed, which is exactly when you want the report.
Adding retries and traces
Retries let a flaky test pass on a second attempt instead of failing the build. Configure them in playwright.config.ts, not in the workflow file.
import { defineConfig } from '@playwright/test';
export default defineConfig({
retries: process.env.CI ? 2 : 0,
reporter: process.env.CI ? 'blob' : 'html',
use: {
trace: 'retain-on-failure',
video: 'retain-on-failure',
screenshot: 'only-on-failure',
},
});
This enables 2 retries in CI only, so flaky tests still fail loudly on your laptop. A test that fails and then passes on retry is reported as flaky, not passed. Setting trace to retain-on-failure keeps traces only for tests that failed.
Setting up PR comments
A PR comment shows the result without opening the Actions tab. On its own, create-or-update-comment posts a new comment on every run, so long-lived PRs fill up with them. Look up the earlier comment with find-comment first and replace it.
comment:
if: github.event_name == 'pull_request'
needs: [test]
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- name: Find previous comment
uses: peter-evans/find-comment@v3
id: fc
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: 'github-actions[bot]'
body-includes: Playwright Test Results
- name: Post or update results
uses: peter-evans/create-or-update-comment@v5
with:
comment-id: ${{ steps.fc.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
edit-mode: replace
body: |
## Playwright Test Results
Status: ${{ needs.test.result }}
[View the run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
The permissions block gives the job write access to PR comments. When find-comment returns no ID, a new comment is created; otherwise the existing one is replaced. Pull requests from forks get a read-only token, so this job cannot comment on them.
Common problems and fixes
Tests fail with browser not found
The workflow is missing npx playwright install. Installing the npm package does not download browsers; that step does.
Tests fail with missing system libraries
On Linux, browsers need system libraries that a bare runner or container may not have. Use npx playwright install --with-deps instead of plain npx playwright install.
Sharded artifacts overwrite each other
Each shard needs a unique artifact name. Add the matrix shard index to the artifact name, as in the workflow above.
Retries hide real failures
A flaky test passes on retry and the build goes green. Set failOnFlakyTests: !!process.env.CI in playwright.config.ts if a flaky result should fail the CI build.
Artifacts are too large
Videos and traces add up quickly across a big suite. Keep them only on failure with retain-on-failure, and lower retention-days for artifacts nobody reads after a few days.
Playwright configuration for CI
A playwright.config.ts that matches the workflow above.
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: process.env.CI ? [['blob'], ['github']] : 'html',
use: {
trace: 'retain-on-failure',
video: 'retain-on-failure',
screenshot: 'only-on-failure',
baseURL: process.env.BASE_URL || 'http://localhost:3000',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
});
The forbidOnly option fails the build if someone commits test.only(). Playwright's CI guide recommends 1 worker in CI for stability and reproducibility, and sharding for speed. The github reporter adds failure annotations to the workflow run.
Next steps
This setup runs Playwright tests in GitHub Actions with sharding, merged reports, retries, and a single PR comment. From here, consider tracking flaky tests and test history across runs, since one run's report cannot tell you whether a failure is new.
Questions people ask
How many shards should I use?
Start with the number that brings each shard under about 10 to 15 minutes, then adjust from real timings. Every shard repeats checkout, install and browser download, so a short suite gains little from sharding.
Should I cache Playwright browsers?
Playwright's CI guide does not recommend it: restoring the cache takes about as long as downloading the browsers, and on Linux the system dependencies are not cacheable. Run npx playwright install --with-deps in each job.
Should I use pull_request or pull_request_target?
Use pull_request for most projects. pull_request_target runs with your repository's secrets and a write token, which is a security risk if the job checks out and runs code from a fork.
Can I run Playwright on self-hosted runners?
Yes. Install the browsers and their dependencies on the runner with npx playwright install --with-deps, then use runs-on: self-hosted. If the machine is powerful, Playwright's CI guide says you can raise workers above 1.
Sources
- Playwright CI documentation
- Playwright: setting up CI with GitHub Actions
- Playwright sharding and merging reports
- GitHub Actions workflow syntax
- actions/upload-artifact documentation
- peter-evans/create-or-update-comment
Playwright details were checked against Playwright 1.63 and its documentation on October 2, 2026.
Try TurnSignal on your next run
Add one reporter line next to your existing reporters. Free to start, no credit card, no repository access.