Sharded runs
Re-run only failed tests when using native Playwright sharding
Native Playwright sharding runs tests in parallel across a fixed set of jobs. On retry, each job should run only the tests that failed in the previous execution.
How sharding changes --last-failed
Each shard is an independent Playwright process with its own .last-run.json. A shard's file lists only the tests that ran in that shard, so:
Every shard needs its own cached copy of
.last-run.json, keyed by shard index.Shard 2 re-runs shard 2's failures. Shards never see each other's results, so there is nothing to merge.
Shard assignment must stay stable between attempts. Playwright distributes tests deterministically, so keep the shard total and the test files unchanged between the original run and the retry.
What the setup has to do
Four things have to be true for a sharded rerun to work:
.last-run.jsonsurvives between attempts. CI machines are ephemeral, the file must be cached (or fetched from Currents) and restored into the retried job.The save key is unique per shard and per attempt. It needs the shard index, because each shard has a different file, and the attempt number, because on GitHub Actions an existing cache key cannot be overwritten, each retry has to write a new entry.
The restore key is a prefix that omits the attempt. This is the step that is easy to miss: a key containing the attempt number never hits on a retry, because that attempt has not written a cache yet. Restore has to fall back to a stable
run_id+ shard prefix so it picks up the most recent previous attempt.--last-failedis passed conditionally. The first attempt has no.last-run.json, passing--last-failedwhen the file is missing is not what you want on a clean run. Add the flag only when a previous attempt's file was actually restored.
On GitHub Actions, points 2 and 3 come out as a save key with run_attempt and a restore prefix without it:
key: last-run-${{ github.run_id }}-${{ matrix.shard }}-${{ github.run_attempt }}
restore-keys: |
last-run-${{ github.run_id }}-${{ matrix.shard }}-Scoping the key to run_id also keeps a previous workflow's results from leaking into a fresh run.
The file also has to be saved even when the job fails, which is the case that matters here. Use actions/cache/save with if: always() - the combined actions/cache action skips its save step when the job fails.
Re-run the failed jobs only
Use the CI provider's Re-run failed jobs (GitHub Actions) or Retry failed jobs (GitLab CI):
Shards that passed are not restarted.
Shards that failed re-run with only their own failed tests.
Re-run all jobs would restart every shard, including the ones that passed, which defeats the purpose. This is the opposite of Orchestrated runs, where Rerun All Jobs is the correct choice because Currents redistributes the failed tests across all available machines.
Choosing an approach
playwright-last-failed action
GitHub Actions. Handles the cache, the per-shard/per-attempt key, and the conditional flags, and outputs the flags to pass to Playwright.
currents cache with --preset last-run
GitLab CI and other providers. Stores .last-run.json in Currents-managed storage and writes the shard and --last-failed flags to an env file.
Native CI cache
No extra tooling. You own the cache keys and the conditional flag logic. See the caveats above.
Playwright also accepts --last-failed-file <path> (or PLAYWRIGHT_LAST_RUN_OUTPUT_FILE) to write the last-run file to an explicit path instead of the default <outputDir>/.last-run.json. This is useful with a native cache, where caching a stable path is simpler than depending on the internal outputDir layout. See Re-run Only Failed Tests for details.
Step-by-step guides
See also the overview: Re-run Only Failed Tests.
Last updated
Was this helpful?