How to Rerun Failed Tests: Commands for Every Framework and CI
One red test should not cost a full suite run. Here are the exact commands to rerun only what failed.

One red check out of hundreds can hold up a whole release. Code is now checked automatically after every change, so knowing how to rerun failed tests, and only those, cuts that wait short.
The default move is to press rerun and sit through the full suite again, including every test that already passed. Worse, a test that passes on the second try can hide a real bug.
This guide gives you the exact command for 10 frameworks and 4 CI platforms. It also shows you how to spot flaky tests before a rerun buries them.
What does it mean to rerun failed tests?
Rerunning failed tests means running only the tests that failed in your last run instead of the whole suite. The test runner reads a saved list of failures, such as Playwright's .last-run.json or pytest's .pytest_cache, and runs just those tests.
Many test runners keep a record of the last run. The flag that reads that record is what lets you skip the tests that already passed.
A rerun is not the same as a retry, even though the two words often get used as if they were. The difference changes what a green build is telling you.
Retry vs rerun failed tests: what is the difference?
| Retry | Rerun failed tests | Full rerun | |
|---|---|---|---|
| When it happens | Automatically, inside the same run | Manually, in a new run | Manually, in a new run |
| What runs | The failed test, again | Only the tests that failed last time | Every test |
| Good for | Short timing or network blips | Checking a fix quickly | Final check before merge or release |
| Playwright example | retries: 2 | --last-failed | npx playwright test |
| pytest example | --reruns 2 (plugin) | --lf | pytest |
| Main risk | Hides flaky tests | Misses tests your fix broke | Slowest feedback |
In practice, you need all three. Retries absorb one-off blips in CI, reruns give you quick feedback after a fix, and a full run before merge catches anything the fix broke elsewhere.
Whichever you pick, read the error first. A Playwright test failure that repeats on every attempt is a bug in the code or the test, not bad luck.
With the terms clear, here is the short answer for every framework in one place.
How to rerun failed tests: quick commands for every framework
The workflow is the same in almost every runner:
- Run the full suite once so the runner can record every result.
- Let the runner save the list of failures to disk.
- Read the error, trace, or log to find the cause.
- Rerun only the failures with one flag, such as --last-failed or --lf.
- Run the full suite before you merge.
Steps 1, 2, and 5 are the same everywhere. Step 4 is the only part that changes between frameworks, and step 3 is the one people skip.

The table below maps step 4 to each framework. It also shows how each one retries automatically and where it keeps the failure list, because that file is what breaks first in CI.
| Framework | Rerun only failed tests | Automatic retries | Where failures are stored |
|---|---|---|---|
| Playwright | npx playwright test --last-failed | --retries=2 or retries in config | test-results/.last-run.json |
| Jest | npx jest --onlyFailures | jest.retryTimes(2) | Kept by Jest between runs |
| Vitest | Press f in watch mode | --retry=2 | The running watch session |
| Cypress | Cypress Cloud only | retries: { runMode: 2 } | Cypress Cloud |
| pytest | pytest --lf | pytest --reruns 2 (plugin) | .pytest_cache |
| TestNG | Run testng-failed.xml | IRetryAnalyzer | testng-failed.xml in the output folder |
| Maven Surefire | No separate command | -Dsurefire.rerunFailingTestsCount=2 | Inside the same build |
| Gradle | No separate command | org.gradle.test-retry plugin | Inside the same build |
| RSpec | rspec --only-failures | Not built in | File set by example_status_persistence_file_path |
| .NET (Microsoft.Testing.Platform) | No separate command | --retry-failed-tests 3 | Inside the same run |
The table gives you the command. The sections below cover the setup details that make each one work, starting with JavaScript.
How to rerun failed tests in JavaScript and TypeScript frameworks
JavaScript runners split into two groups. Playwright and Jest can rerun failures from the last run on their own, while Vitest and Cypress lean on watch mode or a paid cloud service.
Rerun failed tests in Playwright
Playwright added --last-failed in version 1.44, according to the Playwright release notes. After every run, it writes the failures to .last-run.json inside the output folder, which is test-results by default.
Take a login suite with 14 tests across Chromium and Firefox. The first run passes 11 tests and fails 3.

This command reads .last-run.json and skips the 11 tests that already passed:
npx playwright test --last-failed
The rerun report lists only the 3 failed tests, and all 3 fail again because nothing was fixed yet. The run still took 14.9s against 18.7s for the full suite, because the failing tests were the slowest ones (6.1s to 8.4s each).

A rerun without a fix only confirms the failure. For failures that come and go, set retries in the config so Playwright retries inside the same run. When a test fails first and then passes on a retry, Playwright marks it as "flaky," as described in the Playwright retries docs.
import { defineConfig } from '@playwright/test';
export default defineConfig({
retries: process.env.CI ? 2 : 0,
});
Two more flags are worth knowing:
-
--fail-on-flaky-tests (v1.45+) sets the exit code to 1 if any test was flaky, so retries cannot quietly turn a red build green.
-
--last-failed-file lets you choose where the last-run file is saved and read. It first shipped in the 1.61.0 package, so run npx playwright --version before you rely on it.
Tip: When you work out how to rerun failed tests in Playwright locally, add --trace on to the command. Every rerun test then records a full trace, ready to open in the Trace Viewer.
The Playwright CLI has more filters, such as --grep and --only-changed, for narrowing a local run even further.
If an AI coding agent writes or fixes your tests, the open-source TestDino Playwright skill gives it guides for flaky tests, CI setup, and trace-based debugging.
Rerun failed tests in Jest
Jest's --onlyFailures flag, or -f for short, runs the tests that failed in the previous execution, as the Jest CLI docs describe. In watch mode, pressing f does the same thing.
npx jest --onlyFailures
For retries, jest.retryTimes() works with the default jest-circus runner. It has to sit at the top level of a test file or inside a describe block.
jest.retryTimes(2, { logErrorsBeforeRetry: true });
The logErrorsBeforeRetry option prints the first error before the retry, so a pass on the second attempt still leaves a trail. Retry options like this one exist in most JavaScript testing frameworks, and the logged error is what lets you find the flaky test later.
Rerun failed tests in Vitest
Vitest has no CLI flag that reruns only the last failures. In watch mode, press h to see the shortcuts, and f reruns only the failed tests.
For automatic retries, use the --retry flag or the retry option from the Vitest retry docs:
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
retry: 2,
},
});
Rerun failed tests in Cypress
Cypress added test retries in version 5.0. Both modes default to 0, so you turn them on per mode, as the Cypress test retries docs explain:
const { defineConfig } = require('cypress');
module.exports = defineConfig({
retries: {
runMode: 2,
openMode: 0,
},
});
The open-source runner has no command to rerun only failed specs. Cypress Cloud offers "Re-run optimization" on Business and Enterprise plans, which kicks in when you use your CI provider's rerun failed jobs button.
If rerunning only failures matters to your team, weigh that gap in any Playwright vs Cypress decision. Python teams have an easier time, because pytest has more rerun flags than any other runner in this guide.
Rerun failed tests in Python with pytest
pytest saves the result of every run in a .pytest_cache folder at the project root. The pytest cache docs list 3 flags that read it.
That folder holds local state only, so keep it out of Git. Add .pytest_cache/ to .gitignore, next to Playwright's output folders if the repo has both.

pytest --lf, --ff, and --sw
pytest --lf # run only the tests that failed last time
pytest --ff # run failed tests first, then the rest
pytest --sw # stop at the first failure, resume from it next time
Each one fits a different moment:
-
--lf (or --last-failed) is the classic rerun. Use it right after a fix.
-
--ff (or --failed-first) gives you fast feedback on the fix and still runs the full suite.
-
--sw (or --stepwise) helps when many tests fail. You fix them one at a time, and each run picks up where the last one stopped.
What happens when nothing failed
By default, --lf runs the whole suite when the cache has no failures. In CI, that turns a quick rerun into a full-length run without warning.
pytest --lf --lfnf none
With --lfnf none (short for --last-failed-no-failures), pytest prints a message and exits successfully instead.
Tip: A fresh CI runner starts with no .pytest_cache, so --lf has nothing to read and falls back to the full suite. Cache that folder between jobs so the rerun job can find it.
pytest rerun failed tests automatically with pytest-rerunfailures
For retries inside the same run, install the pytest-rerunfailures plugin.
pip install pytest-rerunfailures
pytest --reruns 2 --reruns-delay 1
You can also limit reruns to one test, or to one kind of error:
import pytest
@pytest.mark.flaky(reruns=2, reruns_delay=1)
def test_payment_confirmation():
...
pytest --reruns 2 --only-rerun ConnectionError
Rerun attempts show as R in the output. The plugin does not work with --pdb, --looponfail, or the separate flaky plugin.
Both options work the same way when pytest drives a browser, as in a Python Playwright suite. Java runners take a different path, because most of them retry inside the same build instead of starting a new run.
Rerun failed test cases in Java, Ruby, and .NET
Outside JavaScript and Python, how to rerun failed tests depends on the runner. TestNG and RSpec can rerun failures from a previous run, while Maven Surefire, Gradle, and .NET retry within the build that failed.
Rerun failed test cases in TestNG
Every time tests fail in a suite, TestNG writes a testng-failed.xml file to its output folder, according to the TestNG docs. The file lists the failed methods plus the methods they depend on, so you can run it like any other suite.
java org.testng.TestNG -d test-outputs test-outputs/testng-failed.xml
Under Maven, Surefire writes TestNG output to target/surefire-reports by default. Point the suite property at the generated file:
mvn test -Dsurefire.suiteXmlFiles=target/surefire-reports/testng-failed.xml
For automatic retries, implement IRetryAnalyzer and attach it to the tests that need it.
import org.testng.IRetryAnalyzer;
import org.testng.ITestResult;
public class RetryFailed implements IRetryAnalyzer {
private static final int MAX_RETRIES = 2;
private int attempts = 0;
@Override
public boolean retry(ITestResult result) {
return attempts++ < MAX_RETRIES;
}
}
@Test(retryAnalyzer = RetryFailed.class)
public void paymentIsConfirmed() {
// test body
}
Maven Surefire and JUnit
Maven Surefire reruns failing tests within the same build through one property. It supports JUnit 4.12+, JUnit 5, and TestNG, per the Surefire rerun docs.
mvn test -Dsurefire.rerunFailingTestsCount=2
A rerun only turns a build green when the test passes on one of its attempts. Here, testFail has a real bug, so it fails on Run 1, 2, and 3, and Surefire still reports a build failure.

To make it permanent, add it to the plugin config:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<rerunFailingTestsCount>2</rerunFailingTestsCount>
</configuration>
</plugin>
A test that fails and then passes counts as a "flake." The build still passes, and the console prints Flakes: 1. Set failOnFlakeCount if you want flakes to fail the build, and use failsafe.rerunFailingTestsCount for integration tests run by Failsafe.
Gradle test-retry plugin
Gradle's own --rerun-tasks flag reruns every task, not the failed tests. For test-level retries, use the official test-retry plugin.
plugins {
id("org.gradle.test-retry") version "1.6.6"
}
tasks.test {
retry {
maxRetries.set(2)
maxFailures.set(20)
failOnPassedAfterRetry.set(false)
}
}
The maxFailures setting matters. If more than 20 tests fail, the plugin skips retries, because that many failures points to a broken build, not a flaky test.
RSpec
RSpec has supported --only-failures since rspec-core 3.3. It needs a file to store results, set in your spec helper, as the RSpec docs show.
RSpec.configure do |config|
config.example_status_persistence_file_path = "spec/examples.txt"
end
rspec --only-failures
rspec --next-failure
The --next-failure flag runs failures one at a time and stops at the first one, which is handy for fixing a long list.
.NET with Microsoft.Testing.Platform
.NET projects on Microsoft.Testing.Platform can add the Retry extension. It does not work with the older VSTest runner.
dotnet add package Microsoft.Testing.Extensions.Retry
dotnet run --project Contoso.MyTests -- --retry-failed-tests 3
All of these work well on a laptop. CI is where they tend to break, because the file that stores the failures disappears with the runner.
How to rerun only failed tests in CI
CI platforms have their own rerun buttons, but they work on jobs, not tests. If one test fails in a job with 400 tests, rerunning the job runs all 400 again. So in CI, how to rerun failed tests depends on whether the failure file survives from one attempt to the next.
To rerun only failed tests in CI, you need 2 things: the failure file from the previous attempt, and a runner flag that reads it. The platforms below differ in how much of that they handle for you.

Rerun failed tests in GitHub Actions
GitHub offers a "Re-run failed jobs" button, and the same thing from the CLI:
gh run rerun <run-id> --failed
According to the GitHub Actions docs, a rerun uses the same commit and ref as the original run. You can rerun a run up to 50 times within 30 days of the first attempt.
That reruns every test in the failed jobs. For a test-level rerun with Playwright, save .last-run.json to the cache after each attempt and restore it on the next one:
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npx playwright install --with-deps
- name: Restore last-run file
uses: actions/cache/restore@v4
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: |
if [ -f .last-run.json ]; then
npx playwright test --last-failed --last-failed-file .last-run.json
else
npx playwright test --shard=${{ matrix.shard }}/3 --last-failed-file .last-run.json
fi
- name: Save last-run file
if: always()
uses: actions/cache/save@v4
with:
path: .last-run.json
key: last-run-${{ github.run_id }}-${{ matrix.shard }}-${{ github.run_attempt }}
Four details make this setup work:
-
if: always() on the save step. Without it, the file is never saved on the attempt that fails, which is the only attempt you need it from.
-
run_attempt in the key. A cache key cannot be overwritten, so each attempt needs its own key. The restore-keys prefix then finds the latest one.
-
No --shard on the rerun. Each shard's file lists only that shard's failures. Splitting that short list again would drop some failures from the job that owns them.
-
--last-failed-file needs the 1.61.0 package or newer. On older versions, cache test-results/.last-run.json instead.
The Playwright in GitHub Actions setup covers the rest of the workflow, and Playwright sharding explains how to pick a shard count.
GitLab CI
GitLab's retry keyword retries a whole job up to 2 times, per the GitLab CI docs. Use when to limit it to infrastructure problems:
e2e:
script:
- npx playwright test
retry:
max: 2
when:
- runner_system_failure
- stuck_or_timeout_failure
This leaves test failures to the test runner's own retries, which show up in the report. A full Playwright GitLab CI pipeline uses the same split.
CircleCI
CircleCI has a built-in "Rerun failed tests" button. It needs 3 things, per the CircleCI docs: the job uses circleci tests run, uploads results with store_test_results, and produces JUnit XML with file or classname attributes.
steps:
- checkout
- run:
name: Run Playwright tests
command: |
mkdir -p test-results
circleci tests glob "tests/**/*.spec.ts" | circleci tests run --command="xargs npx playwright test --reporter=junit" --verbose --split-by=timings
environment:
PLAYWRIGHT_JUNIT_OUTPUT_NAME: test-results/results.xml
- store_test_results:
path: test-results
The xargs part matters. CircleCI passes the file names on standard input, and xargs turns them into arguments that Playwright can read.
Note: CircleCI reruns at the file or classname level. If 1 test fails in a spec file with 30 tests, all 30 run again. It also does not support Windows executors.
Azure Pipelines
Azure Pipelines lets you retry failed stages from the run page. For .NET tests on the VSTest runner, the VSTest task can rerun failures inside the same job:
- task: VSTest@2
inputs:
testAssemblyVer2: '**\*Tests.dll'
rerunFailedTests: true
rerunMaxAttempts: 3
The VSTest task docs list the limits, including no reruns for data-driven xUnit and NUnit tests. For browser suites, Playwright's own retries do the same job, and Playwright in Azure covers that setup.
Reruns get you back to green quickly. The catch is that green after a rerun does not always mean the code is fine.
When a rerun hides a real bug
A test that fails and then passes with no code change is flaky. Rerunning it turns the build green, but the cause is still there, waiting to fail again on another run.
Researchers at the University of Illinois studied 201 commits that fixed flaky tests in 51 open-source projects and classified the root cause of 161. In An Empirical Analysis of Flaky Tests (FSE 2014), async waits caused 45%, concurrency 20%, and test order dependency 12%.

Most of those causes come down to timing or shared state. A rerun changes the timing, so the test passes, and nobody looks at why it failed.
The same paper cites Google data: its test system averaged 1.6M test failures a day, and 73K of them (4.56%) came from flaky tests. Google reran each failing test 10 times on the same code and labeled it flaky if any rerun passed.
4 signs a rerun is hiding a problem
-
The same test passes on retry week after week. Look at it in your Playwright flaky tests report rather than just the latest run.
-
The error message changes between attempts. One timeout and one assertion failure in the same test usually point to a race condition.
-
The test passes alone but fails in the full suite. That is test order dependency, which a rerun of only that test will never show.
-
Your retry count keeps climbing. More retries per build means more flaky tests, even while the pass rate looks steady.
5 rules for reruns you can trust
- Keep retries low. Use 1 or 2 in CI and 0 locally, so flaky behavior shows up while you work.
- Fail on flakes once the suite is stable. Playwright has --fail-on-flaky-tests, Surefire has failOnFlakeCount, and the Gradle plugin has failOnPassedAfterRetry.
- Reset test data before a rerun. Leftover records from the first attempt can make the rerun fail for a new reason, or pass for the wrong one.
- Finish with a full run before merge. A rerun only checks the tests that failed, not the ones your fix might have broken.
- Track flakiness over time. Flaky test detection tools and a regular test failure analysis show which tests keep needing a second try.
To put a number on what reruns cost your team, TestDino's free tools include a Flaky Cost Calculator and a CI Budget Calculator.
Following these rules by hand across branches and shards takes steady effort. That is the gap TestDino fills for Playwright teams.
Rerun failed Playwright tests with TestDino
TestDino stores the result of every test in every run, so it already knows which tests failed and which were flaky. There are no cache files to save, and no conditional flags to maintain in your workflow.
Every attempt shows up on the Test Runs page. In this project, attempt #1 passed all 12 tests, while attempts #2 to #4 each failed the same 3 of 14, and AI Insights flagged those 3 as unstable.

Rerun from the CLI
Install the TestDino reporter, set TESTDINO_TOKEN as an environment variable, and point the rerun at an earlier run:
npm install -D @testdino/playwright
npx tdpw test --rerun failed --from-run <runId>
The --rerun flag accepts failed, flaky, or failed-and-flaky. You can narrow the list with --test-ids or remove tests with --exclude-ids. It needs @testdino/playwright 2.7.0+ and Playwright 1.56+.
Note: The --rerun flag cannot be combined with --shard, --grep, --grep-invert, --last-failed, or --test-list. TestDino picks the exact tests itself, so those filters would conflict with its list.
Rerun from the dashboard
Each test run page has a Re-run button with 4 scopes: Failed (the default, including timeouts), Flaky, Both, or Custom. You then choose what to run against:
-
This commit reruns the original code. If the test now passes, it is flaky. If it fails again, it is a real failure.
-
Latest on your branch reruns the current branch tip, which tells you whether a later fix worked.
The button needs the TestDino GitHub App and a workflow_dispatch trigger with a few inputs, which the TestDino rerun docs walk through.
Before you rerun anything, TestDino's AI failure analysis labels each failure as an Actual Bug, UI Change, Unstable Test, or Miscellaneous. That makes root cause analysis the first step instead of an afterthought, and the full test history shows whether a test has failed like this before.
Conclusion
Knowing how to rerun failed tests comes down to one habit: read the failure, rerun only what failed, then run everything before you merge.
The commands are short. Playwright has --last-failed, pytest has --lf, Jest has --onlyFailures, RSpec has --only-failures, and TestNG writes testng-failed.xml. Maven, Gradle, and .NET retry inside the build instead.
The hard part is CI, where the failure file has to survive between attempts, and the green result has to mean something. Cache the file, keep retries low, and treat every pass on retry as a flaky test to fix. Those same steps are also the fastest way to reduce Playwright CI runtime without cutting coverage.
FAQs

Pratik Patel
Co-founder

