Skip to content
Latchkey

How to Balance Test Shards by Timing in GitHub Actions

Splitting tests by recorded duration, not file count, keeps every shard finishing together so the slowest one is no longer the bottleneck.

Record per-test timings, then partition the suite so each shard holds roughly equal total time. Runners that support a timings file (Playwright, Knapsack-style splitters) do this automatically.

Steps

  • Persist per-test durations from a prior run as an artifact.
  • Feed the timings to a splitter so each shard holds equal time.
  • Fall back to even file splits when no timing data exists yet.

Workflow

.github/workflows/ci.yml
jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        shard: [1, 2, 3, 4]
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npx playwright test --shard=${{ matrix.shard }}/4
      - uses: actions/upload-artifact@v4
        with:
          name: timings-${{ matrix.shard }}
          path: test-results/timings.json

Gotchas

  • Timing data drifts as tests change; refresh it periodically or shards re-skew.
  • Suffix the artifact name with the shard, or v4 rejects the duplicate upload.

Related guides

Run this faster and cheaper on Latchkey managed runners. Start free →