Q35 of 42 · Playwright

How would you set up sharding across multiple CI runners without Playwright Cloud?

PlaywrightSeniorplaywrightshardingciparallelsenior

Short answer

Short answer: Use the built-in `--shard=N/M` flag. Each CI runner passes a unique shard index and the total. Playwright partitions tests deterministically by file and test count. For duration-balanced shards, post-process JUnit timings and feed them to a custom shard mapping. No cloud service required.

Detail

Playwright has built-in sharding without any external service:

npx playwright test --shard=1/4   # runner 1 of 4
npx playwright test --shard=2/4   # runner 2 of 4
# ... etc

Each runner picks up roughly 1/4 of the tests. Playwright's default partition is by file then test count — usually balanced for similar-cost specs.

GitHub Actions matrix:

strategy:
  fail-fast: false
  matrix:
    shard: [1, 2, 3, 4, 5, 6, 7, 8]
steps:
  - run: npx playwright test --shard=${{ matrix.shard }}/8

Eight parallel runners; each tackles 1/8 of the suite.

Combine with projects for cross-browser × shard:

matrix:
  browser: [chromium, firefox, webkit]
  shard: [1, 2, 3]
steps:
  - run: npx playwright test --project=${{ matrix.browser }} --shard=${{ matrix.shard }}/3

For duration-balanced sharding (when default partition produces uneven shards), post-process JUnit timings and assign tests:

  1. After each successful CI run, parse JUnit XML for per-test durations.
  2. Store timings.json somewhere accessible (S3, gh-pages, the repo).
  3. Before the next run, read it and bin-pack tests across shards (greedy: assign each test to the currently-shortest shard).
  4. Pass the resulting test list via --grep or a generated config.

This gets you cloud-quality balancing without paying for it. A 30-minute suite that defaults to 6-minute / 4-minute / 12-minute / 8-minute shards becomes 4 × 7.5-minute shards.

Reports across shards:

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

merge-reports:
  needs: [test]
  runs-on: ubuntu-latest
  steps:
    - uses: actions/download-artifact@v4
      with: { pattern: blob-report-* }
    - run: npx playwright merge-reports --reporter=html ./all-blob-reports

merge-reports combines per-shard blob reports into a single HTML report — the developer experience matches a non-sharded run.

The senior signal: knowing the built-in is sufficient for most teams, naming the duration-balanced extension when needed, and the merge-reports step for unified output.

// WHAT INTERVIEWERS LOOK FOR

Built-in `--shard=N/M` knowledge, matrix integration, duration-balanced extension, and `merge-reports` for unified output.

// COMMON PITFALL

Hand-rolling spec splitting with shell scripts when Playwright already has `--shard` built in.