Skip to content

Re-run only failed tests

When a CI run fails on a handful of flaky tests, re-running the whole suite wastes minutes. Nijam remembers which tests failed, so a retry can run only those. Each attempt is clubbed into one run on the dashboard, with a status rolled up across attempts: a test that failed on the first attempt and passed on the retry shows the run as recovered, not as two unrelated runs.

  1. Your test job runs and reports to Nijam as usual.
  2. If it fails, a retry step runs nijam-... fetch-failed. That asks Nijam which tests failed in the previous attempt (keyed by each test’s stable id) and writes a filter file for your test runner.
  3. You run the test runner with that filter, so only the previous failures execute.
  4. The retry reports as a new attempt of the same run. The dashboard groups the attempts and rolls up the result.

The CLI ships with each reporter. It writes the framework’s native filter to --output, and (with --export-env) the variables that club the retry under the original run.

nijam-pw is installed with @nijam/pw-reporter. It prints file:line tokens, which Playwright runs directly.

Terminal window
npx nijam-pw fetch-failed --output failed.txt --export-env "$GITHUB_ENV"
# failed.txt -> e2e/login.spec.ts:42 e2e/cart.spec.ts:17
npx playwright test $(cat failed.txt)

The recipes below run the suite, and on failure fetch the previous failures and re-run only those in the same job. Because --export-env writes a fresh NIJAM_RUN_ATTEMPT, the retry is a distinct attempt clubbed under the original run. The Playwright command is shown; swap it for the pytest or Vitest line from above.

continue-on-error lets the first step fail without ending the job, so the retry decides the final status. Re-running and passing turns the job (and the clubbed Nijam run) green.

.github/workflows/e2e.yml
jobs:
test:
runs-on: ubuntu-latest
env:
NIJAM_API_KEY: ${{ secrets.NIJAM_API_KEY }}
NIJAM_PROJECT_ID: <your-project-id>
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22 }
- run: npm ci
- run: npx playwright install --with-deps
- name: Run tests
id: tests
continue-on-error: true
run: npx playwright test
# fetch-failed and the re-run are SEPARATE steps on purpose: --export-env writes
# NIJAM_RUN_ATTEMPT / NIJAM_RUN_GROUP / NIJAM_RERUN to $GITHUB_ENV, which GitHub only
# applies to the NEXT step. In one step the re-run wouldn't see them and would merge
# into attempt 1 instead of clubbing as attempt 2.
- name: Fetch the failed tests
if: steps.tests.outcome == 'failure'
run: npx nijam-pw fetch-failed --output failed.txt --export-env "$GITHUB_ENV"
- name: Re-run only the failed tests
if: steps.tests.outcome == 'failure'
run: |
if [ -s failed.txt ]; then
npx playwright test $(cat failed.txt)
else
echo "No failures recorded; re-running the full suite."
npx playwright test
fi

When you split a suite across CI shards (a matrix of parallel jobs), the failures land in separate jobs, and Nijam finalizes the run only once every shard has reported. So the retry can’t be an inline step, it has to be a post-matrix job that runs after all shards finish, fetches the failures aggregated across every shard (the fetch-failed endpoint groups by test id, not by shard), and re-runs them in a single unsharded job, clubbed as attempt 2.

Four rules make it work:

  1. Turn off fail-fast on the shard matrix. If a failing shard cancels the others, the run never finalizes, and fetch-failed (which only returns finalized runs) comes back empty.
  2. Retry in a separate job that waits on all shards (needs / requires / a later stage).
  3. Re-run unsharded, fetch-failed already returns the whole failing set, no need to re-split a handful of tests.
  4. Keep fetch-failed and the re-run in separate steps (or source a file) so the exported NIJAM_RUN_ATTEMPT reaches the re-run; otherwise it merges into attempt 1 instead of clubbing.

The Playwright command is shown; swap it for the pytest or Vitest line from the sections above.

fail-fast: false lets every shard report; the retry-failed job needs the matrix (so it runs after the run is finalized) and only on failure().

.github/workflows/e2e.yml
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false # every shard must report so the run finalizes
matrix:
shard: [1, 2, 3, 4]
env:
NIJAM_API_KEY: ${{ secrets.NIJAM_API_KEY }}
NIJAM_PROJECT_ID: <your-project-id>
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22 }
- run: npm ci && npx playwright install --with-deps
- run: npx playwright test --shard=${{ matrix.shard }}/4
retry-failed:
needs: test # runs after every shard finished (run is finalized)
if: failure()
runs-on: ubuntu-latest
env:
NIJAM_API_KEY: ${{ secrets.NIJAM_API_KEY }}
NIJAM_PROJECT_ID: <your-project-id>
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22 }
- run: npm ci && npx playwright install --with-deps
- name: Fetch failures (aggregated across shards)
run: npx nijam-pw fetch-failed --output failed.txt --export-env "$GITHUB_ENV"
- name: Re-run just those, unsharded
run: if [ -s failed.txt ]; then npx playwright test $(cat failed.txt); else echo "nothing to re-run"; fi

GITHUB_RUN_ID is the same in the matrix and the retry job, so fetch-failed correlates them automatically.

fetch-failed --export-env writes three variables the retry’s reporter reads:

VariablePurpose
NIJAM_RUN_GROUPThe original CI run id, so the retry clubs under the first attempt.
NIJAM_RUN_ATTEMPTThe next attempt number, so the retry is its own attempt (not a merge).
NIJAM_RERUNMarks the run a partial re-run, tagged “failed only” in the dashboard.

On GitHub Actions and GitLab, a re-run keeps the same run id, so attempts club even without these. On providers that mint a new run id per attempt (for example CircleCI’s CIRCLE_BUILD_NUM), or when your retry runs in a separate workflow, pass the original id explicitly:

Terminal window
npx nijam-pw fetch-failed --ci-run-id "$ORIGINAL_RUN_ID" --output failed.txt --export-env nijam.env