# npx playwright test --last-failed: re-run only the tests that failed

> How --last-failed picks tests from test-results/.last-run.json, why it runs the whole suite in a fresh CI job, and how to use it safely with shards.

Published 2026-10-31, updated 2026-10-31. Canonical: https://turnsignal.ai/blog/playwright-last-failed

## In short

- `npx playwright test --last-failed` runs only the tests that failed in the previous run. Playwright reads them from `test-results/.last-run.json`, which every run writes at the end.
- In Playwright 1.63, if that file is missing (a fresh CI job, a cleaned workspace) `--last-failed` filters nothing and **the whole suite runs**. No warning is printed.
- If the previous run had no failures, `--last-failed` selects no tests and the run stops with "No tests found". Add `--pass-with-no-tests` when that should count as success.
- Tests that pass on the second try are flaky. A green re-run hides them unless you track them.

## What --last-failed does

Run your suite, then run only what failed:

```bash
npx playwright test
npx playwright test --last-failed
```

Playwright's CLI describes the flag as "Only re-run the failures". It combines with other options: `--last-failed --headed`, `--last-failed --debug` and `--last-failed --project=chromium` all work. It is the quickest way to iterate on a few broken tests after a long run.

## Where Playwright keeps the last run

At the end of every run (not with `--list`), Playwright writes `.last-run.json` into the output folder of the first selected project. With default settings that is `test-results/.last-run.json`. The file is small:

```json
{
  "status": "failed",
  "failedTests": [
    "a1b2c3d4e5f6…-9f8e7d6c5b4a…"
  ]
}
```

The entries are test IDs. Playwright's reporter API says a test ID is computed from the test file name, the test title and the project name. Renaming a test or moving it to another file therefore changes its ID, and `--last-failed` will not find it.

### Which tests count as failed

- Tests whose outcome is not OK: failed, timed out, or passed when they were expected to fail.
- Tests that **never ran** because the run stopped early, for example after `maxFailures` was reached. They are in the list too, so the next `--last-failed` run picks them up.
- Not flaky tests. A test that failed and then passed on retry counts as OK, so it is not re-run.

Two options move the file: `--last-failed-file <file>` on the command line, or the `PLAYWRIGHT_LAST_RUN_OUTPUT_FILE` environment variable. The same setting controls where the next run writes it.

## Why it runs everything in CI

In Playwright 1.63, `--last-failed` reads the file before the run starts. If the file does not exist or cannot be read, Playwright applies no filter at all, so every test runs, and nothing in the output says so. That is what happens in most CI setups:

- **"Re-run failed jobs" on GitHub Actions** starts the job on a fresh runner. `test-results/` from the failed attempt is gone.
- **A new pipeline** in any CI system checks out a clean workspace.
- **A separate job** that tries to re-run failures runs on a different machine than the job that failed.

So `--last-failed` in CI works in two ways only. Either in the **same job**, right after the full run, or by **carrying the file over** from the failed run as an artifact or cache and pointing `--last-failed-file` at it. The first is simpler:

```yaml
# GitHub Actions step: full run, then one more pass over what failed
- name: Run Playwright tests
  run: npx playwright test || npx playwright test --last-failed
```

In a shell, `a || b` runs `b` only when `a` fails, and the step takes the exit code of the last command that ran. So the step passes when every failure passes on the second pass. Note what that means: you have just turned real failures into a green step, which is exactly what `retries` already does inside one run, with traces and the flaky label. Most teams are better served by `retries: 2` in CI. Use the second pass only when a whole test run, not a single test, is what fails intermittently.

## When the last run had no failures

If `failedTests` is empty, `--last-failed` keeps no tests. Playwright 1.63 then stops with **"Error: No tests found"**, just as when a filter matches nothing. In the `a || b` pattern above this cannot happen, because `b` only runs after a failure. In other scripts, add `--pass-with-no-tests` so an empty selection is a success. The [guide to "No tests found"](https://turnsignal.ai/blog/playwright-no-tests-found) lists the other causes of that error.

## With shards

The failed-test filter is applied after Playwright has split the suite into shards. `npx playwright test --shard=2/4 --last-failed` keeps the shard's usual set of tests and then keeps only those that failed. Run it on the same shard (with the same shard's `.last-run.json`) for the result to make sense. A shard with no failures selects nothing, and with `--shard` set Playwright does not treat an empty selection as an error.

## Related flags

- `--only-changed [ref]` runs test files affected by changes between `HEAD` and a git ref (all uncommitted changes by default). Playwright's CI docs suggest it as a quick first pass on pull requests, followed by the full suite, because it is a heuristic and can miss tests.
- `--repeat-each=N` runs every selected test N times. With `--last-failed --repeat-each=10` you can check whether a failure reproduces or was a one-off.
- `--fail-on-flaky-tests` makes the run fail if any test was flaky, for teams that want retries to be information only.

## Passing on the second try is still flaky

A test that fails in the full run and passes in the `--last-failed` pass did not get fixed; it is flaky. If the next run is green, nobody remembers that it failed. Over weeks, the number of tests that only pass on a second chance grows quietly. The [guide to flaky Playwright tests](https://turnsignal.ai/blog/playwright-flaky-tests) covers finding the cause, and [four questions](https://turnsignal.ai/blog/playwright-rerun-failed-jobs-flaky-or-real) help tell a flaky failure from a real one before you press re-run.

[TurnSignal](https://turnsignal.ai/) keeps every attempt of every test across runs, so a test that failed and then passed is recorded as flaky with the run and attempt behind it, and the next failure of that test on a pull request is labelled **known flaky**. Failures that are new on a branch are labelled **new**, so you know which ones to look at before re-running anything. See the [docs](https://turnsignal.ai/docs).

## FAQ

**Does --last-failed work in the VS Code extension or UI mode?**

The flag belongs to the `npx playwright test` command line and its `.last-run.json` file. UI mode and the VS Code extension keep their own test list, where you pick the tests to run.

**Can I see which tests --last-failed will run before running them?**

Yes: `npx playwright test --last-failed --list` prints the selected tests without running them. A `--list` run does not overwrite `.last-run.json`.

**Why did --last-failed run my whole suite?**

Because `test-results/.last-run.json` did not exist or could not be read, usually in a fresh CI job or after the folder was deleted. Playwright then applies no filter.

**Is --last-failed the same as retries?**

No. `retries` re-runs a failed test immediately within the same run and reports it as flaky if it then passes. `--last-failed` is a new run that only selects last time's failures, and it knows nothing about the earlier attempt.

## Sources

- [Playwright docs: Command line (--last-failed, --last-failed-file, --pass-with-no-tests)](https://playwright.dev/docs/test-cli)
- [Playwright 1.63 source: .last-run.json (lastRun.ts)](https://github.com/microsoft/playwright/blob/v1.63.0/packages/playwright/src/runner/lastRun.ts)
- [Playwright API: TestCase.id](https://playwright.dev/docs/api/class-testcase#test-case-id)
- [Playwright docs: Retries](https://playwright.dev/docs/test-retries)
