Playwright Test Sharding on GitHub Actions and GitLab CI
End-to-end tests are slow by nature — they launch browsers, navigate real pages, wait for network. A 400-test Playwright suite can take 20-30 minutes on a single runner. Sharding splits those tests across multiple workers and brings that down to 5-8 minutes. Here's how to configure it on GitHub Actions and GitLab CI.
How Playwright Sharding Works
Playwright uses the same --shard=N/M syntax as Jest:
# Run the first of 4 shards (25% of test files)
npx playwright <span class="hljs-built_in">test --shard=1/4
<span class="hljs-comment"># Run the second shard
npx playwright <span class="hljs-built_in">test --shard=2/4Playwright distributes test files across shards based on their last recorded duration (from the .last-run.json cache). Slow test files go first to minimize the chance of one shard being a bottleneck.
Blob Reporter: The Key to Merged Reports
With multiple shards, you need to merge results into one report. Playwright's blob reporter outputs a compact binary format designed for this:
// playwright.config.ts
export default defineConfig({
reporter: process.env.CI
? [['blob', { outputDir: 'blob-report' }]]
: [['html']],
});After all shards finish, npx playwright merge-reports combines the blobs into a full HTML report.
GitHub Actions Setup
name: Playwright Tests
on: [push, pull_request]
jobs:
test:
name: Playwright (shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shardIndex: [1, 2, 3, 4]
shardTotal: [4]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium
- name: Run Playwright tests
run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
env:
BASE_URL: https://staging.example.com
- name: Upload blob report
uses: actions/upload-artifact@v4
if: always()
with:
name: blob-report-${{ matrix.shardIndex }}
path: blob-report/
retention-days: 1
merge-reports:
needs: test
if: always()
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: Download blob reports
uses: actions/download-artifact@v4
with:
pattern: blob-report-*
merge-multiple: true
path: all-blob-reports/
- name: Merge reports
run: npx playwright merge-reports --reporter html ./all-blob-reports
- name: Upload HTML report
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report/
retention-days: 7The merge-multiple: true flag on download-artifact pulls all matching artifacts into a single flat directory. merge-reports reads them all and generates the combined HTML.
Running Multiple Browsers in Parallel
Combine sharding with browser matrix to cover multiple environments without multiplying total time:
strategy:
fail-fast: false
matrix:
browser: [chromium, firefox, webkit]
shardIndex: [1, 2]
shardTotal: [2]
steps:
- name: Run tests (${{ matrix.browser }}, shard ${{ matrix.shardIndex }}/2)
run: npx playwright test --project=${{ matrix.browser }} --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
- name: Upload blob report
uses: actions/upload-artifact@v4
if: always()
with:
name: blob-report-${{ matrix.browser }}-${{ matrix.shardIndex }}
path: blob-report/This creates 6 parallel jobs (3 browsers × 2 shards), then merges all 6 blob reports.
GitLab CI Setup
GitLab uses parallel and CI_NODE_INDEX / CI_NODE_TOTAL variables:
playwright-tests:
image: mcr.microsoft.com/playwright:v1.44.0-jammy
parallel: 4
script:
- npm ci
- npx playwright test --shard=$CI_NODE_INDEX/$CI_NODE_TOTAL
artifacts:
when: always
paths:
- blob-report/
expire_in: 1 day
merge-playwright-reports:
image: node:20-slim
needs: [playwright-tests]
when: always
script:
- npm ci
- |
# Blob reports from all parallel jobs are in blob-report/
npx playwright merge-reports --reporter html ./blob-report
artifacts:
when: always
paths:
- playwright-report/
expire_in: 1 weekGitLab's parallel: 4 automatically sets CI_NODE_INDEX (1-4) and CI_NODE_TOTAL (4) for each job. The artifacts from parallel jobs are merged by GitLab before the merge-playwright-reports job runs.
Playwright Cache in CI
Playwright browsers take 1-2 minutes to download. Cache them:
# GitHub Actions
- name: Cache Playwright browsers
uses: actions/cache@v3
id: playwright-cache
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
- name: Install Playwright browsers
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: npx playwright install --with-deps chromiumFor GitLab:
cache:
key: playwright-$CI_COMMIT_REF_SLUG
paths:
- ~/.cache/ms-playwright/
- node_modules/Shard Balance Troubleshooting
Shards finish at different times when test duration is uneven. Check balance:
# Locally, list how many tests each shard gets
<span class="hljs-keyword">for i <span class="hljs-keyword">in 1 2 3 4; <span class="hljs-keyword">do
<span class="hljs-built_in">echo <span class="hljs-string">"Shard $i: <span class="hljs-subst">$(npx playwright test --shard=$i/4 --list 2>/dev/null | wc -l) tests"
<span class="hljs-keyword">doneIf one shard has significantly more slow tests:
- Look for single test files with very long durations
- Split those files into smaller ones
- Playwright will rebalance after the next run updates its timing cache
Disabling Parallelism Within a Shard
By default, Playwright runs tests within a shard in parallel (one per worker). For tests that need serial execution:
// tests/serial-tests.spec.ts
import { test } from '@playwright/test';
test.describe.configure({ mode: 'serial' });
test('step 1', async ({ page }) => { /* ... */ });
test('step 2 depends on step 1', async ({ page }) => { /* ... */ });Or globally limit workers per shard:
export default defineConfig({
workers: process.env.CI ? 2 : undefined, // 2 workers per shard in CI
});Expected Timings
| Shard count | 200-test suite | 500-test suite |
|---|---|---|
| 1 | ~15 min | ~35 min |
| 2 | ~8 min | ~18 min |
| 4 | ~4 min | ~10 min |
| 8 | ~3 min | ~6 min |
These include browser startup time per shard (~30-60s). Beyond 8 shards, startup overhead reduces gains.
Summary
Playwright sharding splits test files across multiple CI runners using --shard=N/M. Use the blob reporter on each shard, then merge-reports in a downstream job to combine results into a single HTML report. GitHub Actions uses a matrix strategy; GitLab uses parallel. Cache browsers to avoid re-downloading on every run. The typical 4-shard setup reduces a 30-minute suite to 8-10 minutes.