Skip to content
End To End Tester

Playwright Sharding

Splitting a browser suite across machines and merging the reports back — the mechanics, the prerequisites, and how to choose a shard count that is actually faster.

2 min read · updated 19 September 2026

Sharding splits a test suite across machines. Four shards of a twenty-minute suite is five minutes, plus overhead.

It is also the fastest way to discover that your suite was never safe to run concurrently — so get parallel execution right first.

#The mechanics

bash
npx playwright test --shard=1/4
npx playwright test --shard=2/4
npx playwright test --shard=3/4
npx playwright test --shard=4/4

Playwright partitions by test file, deterministically, so every shard runs a disjoint subset and the union is the whole suite. Shards need no knowledge of each other.

Workers and shards multiply:

typescript
// playwright.config.ts
export default defineConfig({
  fullyParallel: true,
  workers: process.env.CI ? 4 : undefined     // 4 workers × 4 shards = 16 concurrent
});

#In GitHub Actions

yaml
jobs:
  e2e:
    strategy:
      fail-fast: false
      matrix:
        shard: [1, 2, 3, 4]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22, cache: npm }
      - run: npm ci
      - run: npx playwright install --with-deps chromium
      - run: npx playwright test --shard=${{ matrix.shard }}/4

      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: blob-report-${{ matrix.shard }}
          path: blob-report/
          retention-days: 7

  merge:
    needs: e2e
    if: always()
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22, cache: npm }
      - run: npm ci
      - uses: actions/download-artifact@v4
        with: { path: all-blobs, pattern: blob-report-*, merge-multiple: true }
      - run: npx playwright merge-reports --reporter=html,github ./all-blobs
      - uses: actions/upload-artifact@v4
        with: { name: playwright-report, path: playwright-report/ }
typescript
// The blob reporter is what makes merging possible.
reporter: process.env.CI ? [['blob'], ['github']] : [['html']]

Without the merge step you get four partial reports, four flaky counts and four sets of trace links. With it, one report for the whole run — which is what anyone diagnosing a failure actually needs.

#The prerequisites

Sharding multiplies concurrency, so everything that was marginally unsafe becomes reliably broken.

Unique data per shard and per worker.

typescript
const email = `buyer-${process.env.SHARD_INDEX ?? 0}-${test.info().workerIndex}-${Date.now()}@example.test`;

Ephemeral ports. listen(0), dynamicPort(). A hardcoded port is fine on one machine and fatal on four.

No shared accounts. Sixteen concurrent tests signing in as [email protected] and mutating its settings will fail in ways that look random.

A backend that can take it. Sixteen concurrent browsers is sixteen concurrent users. Connection pools, rate limits and worker counts all have to allow for it — and if they do not, you have found a genuine capacity problem. See load testing.

Per-shard infrastructure. Each shard needs its own application instance and database, or a scoped slice of a shared one.

yaml
- run: |
    docker compose -p shard-${{ matrix.shard }} up -d --wait
  env:
    APP_PORT: ${{ 3000 + matrix.shard }}

#Choosing a shard count

Each shard pays a fixed setup cost — checkout, npm ci, browser install, application start. Call it two minutes.

suite 20 min, setup 2 min

 1 shard   20 + 2 = 22 min
 2 shards  10 + 2 = 12 min
 4 shards   5 + 2 =  7 min
 8 shards 2.5 + 2 =  4.5 min
16 shards 1.2 + 2 =  3.2 min   ← setup now dominates
32 shards 0.6 + 2 =  2.6 min   ← 32 machines to save 36 seconds

Aim for a few minutes of test time per shard. Beyond that you are paying for machines to sit installing browsers.

Caching the browser download is the cheapest way to move that curve:

yaml
- uses: actions/cache@v4
  with:
    path: ~/.cache/ms-playwright
    key: playwright-${{ runner.os }}-${{ hashFiles('package-lock.json') }}

#Uneven shards

Playwright distributes by file, so a suite of one enormous file and twenty small ones shards badly — one machine runs for twelve minutes while three finish in two.

Symptoms: wildly different shard durations in the run summary.

Fixes:

  • Split the large file. Usually the right answer, and better for readability anyway.
  • --shard with durations. Recent Playwright versions can use the previous run's timings; pass --last-failed-style metadata or supply a .last-run.json.
  • fullyParallel: true, so tests within a file also run concurrently — which softens the imbalance considerably.

#Retries with sharding

typescript
retries: process.env.CI ? 1 : 0

A retry runs on the same shard, so a shard with several flaky tests takes disproportionately longer. That is another reason to treat the flaky count as a defect rather than a cost of doing business — and the merged report is where you can see it for the run as a whole.

#Other runners

The same pattern, different flags:

bash
pytest --splits 4 --group 1        # pytest-split
./gradlew test -Dkotest.shard=1/4  # JUnit 5 via a custom filter, or Gradle test distribution
dotnet test --filter "Shard=1"     # usually a custom trait, assigned by a script
npx jest --shard=1/4               # Jest has native sharding too

Playwright's is the most complete because of the blob reporter and merge-reports; elsewhere you generally merge JUnit XML and lose some fidelity.

Common questions

What is the difference between workers and shards in Playwright?
Workers are parallel processes on one machine; shards are subsets of the suite run on different machines. They multiply — four shards of four workers is sixteen tests running at once.
How many shards should I use?
Enough that each shard runs for a few minutes. Below roughly two minutes per shard the per-machine setup — checkout, install, browser download — dominates, and adding shards makes the wall clock worse rather than better.
How do I get one report from several shards?
Use the blob reporter on each shard, upload the blobs as artefacts, then run playwright merge-reports in a job that depends on all of them. Merging is what makes the flaky count and the trace links work across the whole run.

Runnable samples for this page

last test results ↗

Working tests, not fragments — they run in CI on every push to 8exgh/endtoendtester-samples.

Was this page useful?