---
title: "Playwright's New --last-failed-file Flag: Re-Run Only Failed Tests in GitHub Actions"
description: "Playwright v1.61 adds --last-failed-file, a flag that makes caching --last-failed in CI simpler and more reliable. Learn how to set it up in sharded GitHub Actions workflows."
canonical: "https://currents.dev/posts/playwright-re-run-only-failed-tests"
updated: "2026-06-22"
---

# Playwright's New --last-failed-file Flag: Re-Run Only Failed Tests in GitHub Actions

Published 2026-06-22 by Walter Galvão.

Canonical URL: https://currents.dev/posts/playwright-re-run-only-failed-tests

Playwright v1.61 adds --last-failed-file, a flag that makes caching --last-failed in CI simpler and more reliable. Learn how to set it up in sharded GitHub Actions workflows.

---


Playwright v1.61 shipped a small flag that makes caching [`--last-failed`](https://playwright.dev/docs/test-cli#all-options) in CI significantly cleaner: [`--last-failed-file`](https://playwright.dev/docs/test-cli#all-options). If you use sharded Playwright tests in GitHub Actions and have ever wanted to wire up "Re-run failed jobs" without reaching into Playwright's internals, this is for you.

## When This Is the Right Tool

Playwright's [`retries`](https://playwright.dev/docs/test-retries) config already handles the common case: flaky tests that need a couple of attempts to pass. If a test is occasionally unreliable, retries are the better fit — they handle it automatically without human intervention.

`--last-failed` solves a different problem. Think about a release that gets blocked because a CI environment variable wasn't set correctly, or a feature flag wasn't enabled in the environment under test. The tests that failed aren't broken — the environment was. Once you've fixed the config, you want to re-run only the affected tests immediately, without touching test code or waiting for a full shard replay across your entire suite.

That's the scenario where "Re-run failed jobs" with `--last-failed` is the fastest path to unblocking a release.

## Why --last-failed Was Awkward in CI

Playwright has had [`--last-failed`](https://playwright.dev/docs/running-tests#run-last-failed-tests) for a while. Locally, it works great. Run your tests, some fail, re-run with `--last-failed`, and only the failures execute. Simple.

In CI, wiring this up required some internal knowledge. The `.last-run.json` file that powers `--last-failed` lived inside the first project's [`outputDir`](https://playwright.dev/docs/api/class-testconfig#test-config-output-dir). Since that path is defined in your Playwright config, your CI workflow had to hardcode it — leaking project internals into your pipeline just to cache a file.

Without that setup, clicking "Re-run failed jobs" in GitHub Actions meant Playwright re-ran every test assigned to that shard. Not just the ones that failed. All of them. Minutes wasted, CI budget burned on work that already passed.

## Improving It at the Source

At Currents, we've been building tooling around `--last-failed` in CI for a while. Our [GitHub Action](https://github.com/currents-dev/playwright-last-failed) worked around the hardcoded file path by reaching into Playwright's internal output directory structure. It worked, but it was fragile. Every time Playwright changed internals, we had to adapt.

We wanted to clean this up on the Playwright side, so we opened a [PR](https://github.com/microsoft/playwright/pull/40806), which the Playwright team helped us get landed in time for the v1.61 release.

## The New Flag

The result is a new flag in Playwright v1.61: `--last-failed-file <path>`. You can also set it via the `PLAYWRIGHT_LAST_RUN_OUTPUT_FILE` environment variable.

It does exactly what the name suggests: tells Playwright where to write and read the `.last-run.json` file. You can place it anywhere. Outside `outputDir`, outside `test-results/`, in a dedicated cache directory. The file no longer gets wiped on test start, and you don't need to know which project's output directory Playwright chose internally.

One flag. But it removes the need to know Playwright's internal directory layout, making the caching setup straightforward.

## Using It with GitHub Actions Retries

The idea is straightforward: cache the `.last-run.json` file per shard, so that when you retry a failed job, Playwright can read it and skip the tests that already passed.

### Cache Key Design

The cache key needs three properties:

1. It must be scoped to the current workflow run (`run_id`), so stale caches from previous runs don't interfere.
2. It must include the shard index, so each shard caches its own file.
3. It must include `run_attempt`, because GitHub Actions caches are immutable. Once a key is written, it can't be overwritten. Each retry needs to write a new entry.

The pattern:

```yaml
key: last-run-${{ github.run_id }}-${{ matrix.shard }}-${{ github.run_attempt }}
restore-keys: |
  last-run-${{ github.run_id }}-${{ matrix.shard }}
```

The `restore-keys` prefix (without `run_attempt`) lets a retry find and restore the cache from the previous attempt. On a fresh run, nothing matches, so all tests execute normally.

### Workflow Steps

You need to split cache restore and save into separate steps. The combined `actions/cache` action only saves on job success — but the whole point is to cache the failure results so the *retry* can skip them. Use `actions/cache/restore` and `actions/cache/save` with `if: always()` on the save:

```yaml
- name: Restore last-run file
  uses: actions/cache/restore@v5
  with:
    path: .last-run.json
    key: last-run-${{ github.run_id }}-${{ matrix.shard }}-${{ github.run_attempt }}
    restore-keys: |
      last-run-${{ github.run_id }}-${{ matrix.shard }}-

- name: Run Playwright tests
  run: |
    EXTRA_FLAGS=""
    if [ -f .last-run.json ]; then
      EXTRA_FLAGS="--last-failed"
    fi
    npx playwright test --shard=${{ matrix.shard }}/${{ strategy.job-total }} $EXTRA_FLAGS --last-failed-file .last-run.json

- name: Save last-run file
  if: always()
  uses: actions/cache/save@v5
  with:
    path: .last-run.json
    key: last-run-${{ github.run_id }}-${{ matrix.shard }}-${{ github.run_attempt }}
```

The key points:

- **`actions/cache/save` with `if: always()`** is mandatory. If you use the combined `actions/cache` action instead, it skips the save when the job fails — exactly the case where you need it most.
- **`--last-failed-file .last-run.json`** is always passed so Playwright writes to a known location, not inside `test-results/` where it would be wiped.
- **`--last-failed`** is only added when the file already exists, meaning this is a retry run.
- On first run, no cache exists, so all tests execute and the file is written and saved (even if tests fail).
- On retry, the cache restores the file, and only the failures re-run.

`run_attempt` is in the save key so each attempt writes a new cache entry (GitHub cache keys are immutable). The `restore-keys` prefix without `run_attempt` lets each retry find the previous attempt's entry.

## Gotchas and Limitations

A few things to watch out for:

**The file is shard-scoped.** The `.last-run.json` records only the tests that ran in that specific shard. No cross-shard contamination. This is exactly what you want.

**`outputDir` gets wiped.** This is why `--last-failed-file` exists. If you point it somewhere inside `test-results/`, it will get deleted before tests start. Put it in the project root or a dedicated cache directory.

**GitHub cache immutability.** GitHub Actions caches are write-once per key. The `run_attempt` strategy above handles this. Alternatively, you can use `actions/cache/save` and `actions/cache/restore` as separate steps for more control.

**Test file changes between attempts.** If someone pushes a commit that restructures test files between the initial run and the retry, `--last-failed` might not match tests correctly. In practice this rarely happens because retries are immediate.

**Use "Re-run failed jobs", not "Re-run all jobs".** If you click "Re-run all jobs", every shard restarts — including the ones that passed. Those shards have no `.last-run.json` from a failed run, so they'll execute their full test suite again rather than skipping anything. You'd be re-running tests that already passed, which defeats the purpose. A more advanced setup can detect whether the cached file represents a failed or passed run and skip the `--last-failed` flag accordingly, but that's outside the scope of this example.

**Cache must save on failure.** Don't use the combined `actions/cache` action — it skips the save when the job fails. Use `actions/cache/restore` to restore and `actions/cache/save` with `if: always()` to save. Without this, a failed shard will never write its cache entry and the retry will re-run every test.

## Skip the Caching Setup

The native approach works. But the cache key design, the conditional logic, and the edge cases add up. If you'd rather not manage it yourself, our [GitHub Action](https://github.com/currents-dev/playwright-last-failed) handles shard-aware caching automatically:

```yaml
- name: Playwright Last Failed
  id: last-failed
  uses: currents-dev/playwright-last-failed@v2
  with:
    pw-output-dir: test-results
    matrix-index: ${{ matrix.shard }}
    matrix-total: ${{ strategy.job-total }}

- name: Run Playwright
  run: npx playwright test ${{ steps.last-failed.outputs.extra-pw-flags }}
```

It takes care of the cache keys, shard indexing, and conditional flags. Full setup docs are [here](https://docs.currents.dev/getting-started/ci-setup/github-actions/re-run-failed-only-tests/re-run-failed-only-tests-sharded).

## Wrapping Up

A small flag, but it meaningfully reduces the friction of setting up `--last-failed` in CI. If you're sharding Playwright in GitHub Actions, it's the cheapest improvement you'll make this quarter. No new infrastructure, no third-party dependencies (unless you want them), just a flag and a cache step.


---

## About the author

**Walter Galvão**, Software Engineer. Walter Galvão is a software engineer at Currents. He writes about Playwright in practice: debugging flaky tests, structuring suites for CI, and the engineering workflows that keep large test suites reliable.

LinkedIn: https://www.linkedin.com/in/waltergalvao/
