Skip to content

feat(cli): add 'test flaky' repeat-run flaky-test detector#132

Merged
zeshi-du merged 4 commits into
TestSprite:mainfrom
lxcario:feat/test-flaky-command
Jul 5, 2026
Merged

feat(cli): add 'test flaky' repeat-run flaky-test detector#132
zeshi-du merged 4 commits into
TestSprite:mainfrom
lxcario:feat/test-flaky-command

Conversation

@lxcario

@lxcario lxcario commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Closes #115

What

Adds a new command, testsprite test flaky , that replays a single test N times, aggregates the outcomes, and reports a stability verdict: stable / flaky / failing.

Why

A test that passes once can still be nondeterministic. There is currently no first-class way to surface that from the CLI. flaky lets a human (or CI) quantify stability before trusting -- or gating a merge on -- a result.

Behavior

  • --runs (default 5, range 1-10): number of replays.
  • --until-fail: stop at the first non-passing attempt.
  • --timeout : per-attempt timeout.
  • --output json: machine-readable stability report (verdict + each attempt's runId and failureKind).
  • Replays run with auto-heal OFF (strict verbatim) so a healed drift can't mask a nondeterministic pass/fail.
  • Exit code is 0 only when every attempt passed, so CI can gate: testsprite test flaky --runs 5 || exit 1.
  • Frontend replays are free verbatim script replays; a one-line advisory is printed for backend tests, whose closure reruns may cost credits.

Tests

19 tests across src/lib/flaky.test.ts (aggregation/verdict logic, exit codes, until-fail, runs bounds) and src/commands/test.flaky.spec.ts (command wiring, flag parsing, JSON output). All pass. typecheck + prettier clean. CHANGELOG and DOCUMENTATION updated; help snapshot refreshed for the new subcommand.

Summary by CodeRabbit

  • New Features
    • Added testsprite test flaky <test-id> to replay a test multiple times and classify stability as stable/flaky/failing using strict verbatim replays (auto-heal disabled).
    • Supports --runs (capped), --timeout, --until-fail, --output json, and --dry-run.
    • Implements clear exit codes (0 stable, 1 non-stable, 4 not replayable yet, 5 validation error) and includes per-attempt failure details (e.g., runId, failureKind).
  • Documentation
    • Added CLI reference with flags, JSON output shape, and exit-code mapping.
  • Tests
    • Added unit coverage for flaky verdict aggregation/rendering, command behavior (including NOT_FOUND/non-replayable handling), and help snapshot for test flaky --help.

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b9a9e29c-9a0a-47ed-90e5-ca8171326437

📥 Commits

Reviewing files that changed from the base of the PR and between ab46f14 and 49bf82d.

📒 Files selected for processing (1)
  • CHANGELOG.md
💤 Files with no reviewable changes (1)
  • CHANGELOG.md

Walkthrough

This PR adds a testsprite test flaky <test-id> command that reruns tests, aggregates pass/fail outcomes into stable/flaky/failing verdicts, and emits text or JSON reports. It also adds the supporting library, CLI wiring, tests, and docs updates.

Changes

Flaky Test Detection Feature

Layer / File(s) Summary
Flaky report domain and rendering
src/lib/flaky.ts, src/lib/flaky.test.ts
Defines flaky-test outcome and report types, computes summaries and exit codes, renders text output, and verifies verdict, ratio, and formatting behavior in unit tests.
CLI subcommand and replay loop
src/commands/test.ts
Registers test flaky <test-id>, validates replay options, runs repeated reruns and polling, builds the report, and applies the flaky exit-code behavior.
Command tests and documentation
src/commands/test.flaky.spec.ts, src/commands/test.test.ts, test/help.snapshot.test.ts, CHANGELOG.md, DOCUMENTATION.md
Adds command surface and behavior tests, updates top-level and help snapshots, and documents the command, flags, JSON output, and exit codes.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant testCommand
  participant runFlaky
  participant TestSprite API

  User->>testCommand: testsprite test flaky <test-id>
  testCommand->>runFlaky: invoke(opts)
  loop each attempt
    runFlaky->>TestSprite API: POST /tests/{id}/runs/rerun
    TestSprite API-->>runFlaky: runId / error
    runFlaky->>TestSprite API: poll run status
    TestSprite API-->>runFlaky: terminal outcome
  end
  runFlaky->>runFlaky: summarizeFlaky(attempts)
  runFlaky-->>testCommand: report or CLIError
  testCommand-->>User: text/JSON output
Loading

Related issues: #115

Suggested reviewers: ruili-testsprite, zeshi-du

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the new repeat-run flaky-test detector and matches the main change.
Linked Issues check ✅ Passed The PR implements the flaky subcommand, repeat-run scoring, JSON/text reporting, exit codes, backend advisory, tests, and docs required by #115.
Out of Scope Changes check ✅ Passed The changes stay focused on the flaky-test command, its library, tests, help, and docs with no clear unrelated additions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/commands/test.flaky.spec.ts (2)

236-255: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Missing exit-code assertion for FAILING verdict test.

Unlike the sibling FLAKY test (lines 231-233), which asserts exitCode === 1, this test only checks the message contains 'failing'. Per the documented contract, exit 1 applies to both flaky and failing verdicts — worth asserting here too for parity and to guard the exit-code contract.

As per path instructions, "Exit-code mapping: error paths must map to the documented exit code ... don't introduce ad-hoc codes."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/test.flaky.spec.ts` around lines 236 - 255, The FAILING verdict
test is missing an assertion that the thrown CLIError uses exit code 1, unlike
the nearby FLAKY test. Update the test in test.flaky.spec.ts within the
runFlaky/CLIError assertion block to verify the error’s exit code is 1 in
addition to checking the message contains failing, keeping parity with the
documented exit-code contract for failing verdicts.

Source: Path instructions


327-346: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Test doesn't verify the exit code 4 it claims to test.

The test title says "throws exit 4 when the test has no replayable run," but only asserts err instanceof ApiError and .code === 'NOT_FOUND' — it never checks .exitCode. Given the documented exit-code contract for this command (exit 4 for no replayable run), this test should assert that mapping explicitly, similar to how the validation tests check .exitCode for exit 5.

As per path instructions, "Exit-code mapping: error paths must map to the documented exit code (see DOCUMENTATION.md / the exit-code table); don't introduce ad-hoc codes." A test named after this specific exit code should verify it.

✅ Suggested fix
     expect(err).toBeInstanceOf(ApiError);
     expect((err as ApiError).code).toBe('NOT_FOUND');
+    expect((err as ApiError).exitCode).toBe(4);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/test.flaky.spec.ts` around lines 327 - 346, The flaky test for
no replayable run only checks the ApiError code and does not verify the
documented exit-code mapping. Update the test in test.flaky.spec.ts around
runFlaky to assert the returned error’s exitCode is 4 in addition to checking
ApiError and NOT_FOUND, matching the existing exit-code assertions used in the
validation tests and the command’s documented contract.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/commands/test.flaky.spec.ts`:
- Around line 236-255: The FAILING verdict test is missing an assertion that the
thrown CLIError uses exit code 1, unlike the nearby FLAKY test. Update the test
in test.flaky.spec.ts within the runFlaky/CLIError assertion block to verify the
error’s exit code is 1 in addition to checking the message contains failing,
keeping parity with the documented exit-code contract for failing verdicts.
- Around line 327-346: The flaky test for no replayable run only checks the
ApiError code and does not verify the documented exit-code mapping. Update the
test in test.flaky.spec.ts around runFlaky to assert the returned error’s
exitCode is 4 in addition to checking ApiError and NOT_FOUND, matching the
existing exit-code assertions used in the validation tests and the command’s
documented contract.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c6bb73ac-e0ca-4831-bceb-e706bb13605e

📥 Commits

Reviewing files that changed from the base of the PR and between d9ce26d and 6734c92.

⛔ Files ignored due to path filters (1)
  • test/__snapshots__/help.snapshot.test.ts.snap is excluded by !**/*.snap, !**/*.snap
📒 Files selected for processing (8)
  • CHANGELOG.md
  • DOCUMENTATION.md
  • src/commands/test.flaky.spec.ts
  • src/commands/test.test.ts
  • src/commands/test.ts
  • src/lib/flaky.test.ts
  • src/lib/flaky.ts
  • test/help.snapshot.test.ts
✅ Files skipped from review due to trivial changes (3)
  • test/help.snapshot.test.ts
  • DOCUMENTATION.md
  • CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/commands/test.test.ts
  • src/lib/flaky.test.ts
  • src/commands/test.ts
  • src/lib/flaky.ts

@lxcario

lxcario commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

Rescoped per the #115 triage: --runs is now capped at 10 (validated 1-10) - the constant, the --runs help text, validation, docs (DOCUMENTATION.md / CHANGELOG.md) and the bound tests are all updated. Auto-heal stays OFF and the backend-credit advisory is kept. Also rebased onto current main (resolved a CHANGELOG conflict and refreshed the help snapshot for the new --request-timeout global option); all 8 checks are green now. Ready for the continued review.

@zeshi-du

zeshi-du commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

This is approved — the rework matches the issue #115 decision exactly (--runs capped at 1–10, replays strict-verbatim with auto-heal OFF, backend-credit advisory), and --until-fail + the stability verdict shape are well designed. It just went CONFLICTING under today's merge wave (#96/#33 touched test.ts and the help snapshot). Rebase onto main, re-run npm test to refresh the snapshot, and it merges — no other changes requested.

lxcario added 3 commits July 6, 2026 00:22
Rescope the flaky detector's --runs bound from 1-100 to 1-10 as requested in the TestSprite#115 triage: uncapped FE replays amplify free executions. Updates the MAX_FLAKY_RUNS constant (which drives the validation, error message, and --runs help text), docs, changelog, and the runs-bound tests. Regenerates the help snapshot, which also adds the previously-missing 'test flaky' entry.
Rebasing onto current main (which added the global --request-timeout option, TestSprite#17) changes the 'Global options' line rendered in the test flaky --help output. Regenerate the snapshot so the help snapshot test stays green on CI.
@lxcario lxcario force-pushed the feat/test-flaky-command branch from 6734c92 to ab46f14 Compare July 5, 2026 21:33

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Around line 9-14: Resolve the merge conflict in CHANGELOG.md by removing the
conflict markers and keeping only the flaky-test release note. Preserve the
`testsprite test flaky` entry with the `--runs 1–10` limit, and drop the
unrelated JUnit report bullet so the changelog matches the current CLI contract.
Use the existing `testsprite test flaky <test-id>` entry as the anchor when
editing.

In `@src/commands/test.ts`:
- Around line 8239-8251: The `test flaky` dry-run branch exits before
`makeClient()` and therefore skips the stderr dry-run banner, unlike other
dry-run flows. Update the `opts.dryRun` path in `test` command handling to
explicitly emit the same dry-run banner used by `runDiff` and `runTestRunAll`
before returning, keeping the dry-run output on stderr and preserving
consistency across CLI commands.
- Around line 8287-8308: The rerun error handling in the `triggerRerun` catch
block currently treats all non-NOT_FOUND failures as retryable attempts, which
hides auth/permission failures and the intended exit-3 path. Update the `catch
(err)` logic in `test.ts` to detect non-retryable API errors (especially
authorization/permission cases) and rethrow them instead of pushing to
`attempts` or updating the ticker, while keeping only transient errors as
recorded attempts. Also ensure the `--dry-run` path still emits the usual
dry-run banner before rerun handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c9e2ab50-9482-4b4f-a787-f2a7aee6bc4e

📥 Commits

Reviewing files that changed from the base of the PR and between 6734c92 and ab46f14.

⛔ Files ignored due to path filters (1)
  • test/__snapshots__/help.snapshot.test.ts.snap is excluded by !**/*.snap, !**/*.snap
📒 Files selected for processing (8)
  • CHANGELOG.md
  • DOCUMENTATION.md
  • src/commands/test.flaky.spec.ts
  • src/commands/test.test.ts
  • src/commands/test.ts
  • src/lib/flaky.test.ts
  • src/lib/flaky.ts
  • test/help.snapshot.test.ts
✅ Files skipped from review due to trivial changes (1)
  • src/commands/test.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • test/help.snapshot.test.ts
  • DOCUMENTATION.md
  • src/lib/flaky.ts
  • src/lib/flaky.test.ts

Comment thread CHANGELOG.md Outdated
Comment thread src/commands/test.ts
Comment on lines +8239 to +8251
if (opts.dryRun) {
out.print({
dryRun: true,
command: 'test flaky',
testId: opts.testId,
runs: opts.runs,
untilFail: opts.untilFail,
method: 'POST',
path: `/api/cli/v1/tests/${opts.testId}/runs/rerun`,
note: `Would replay the test up to ${opts.runs}x with auto-heal OFF and report a stability score.`,
});
return undefined;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Dry-run path omits the dry-run banner.

This branch returns before makeClient(), so the stderr dry-run banner never fires. Every other early-return dry-run path emits it explicitly (e.g. runDiff Line 3729, runTestRunAll Line 5437 — "this path returns before makeClient() fires the banner, so emit it here"). Without it, test flaky --dry-run is inconsistent with the rest of the CLI.

🛠️ Proposed fix
   if (opts.dryRun) {
+    emitDryRunBanner(stderrFn);
     out.print({
       dryRun: true,
       command: 'test flaky',

As per path instructions: "human chatter, hints, and advisories go to stderr."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (opts.dryRun) {
out.print({
dryRun: true,
command: 'test flaky',
testId: opts.testId,
runs: opts.runs,
untilFail: opts.untilFail,
method: 'POST',
path: `/api/cli/v1/tests/${opts.testId}/runs/rerun`,
note: `Would replay the test up to ${opts.runs}x with auto-heal OFF and report a stability score.`,
});
return undefined;
}
if (opts.dryRun) {
emitDryRunBanner(stderrFn);
out.print({
dryRun: true,
command: 'test flaky',
testId: opts.testId,
runs: opts.runs,
untilFail: opts.untilFail,
method: 'POST',
path: `/api/cli/v1/tests/${opts.testId}/runs/rerun`,
note: `Would replay the test up to ${opts.runs}x with auto-heal OFF and report a stability score.`,
});
return undefined;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/test.ts` around lines 8239 - 8251, The `test flaky` dry-run
branch exits before `makeClient()` and therefore skips the stderr dry-run
banner, unlike other dry-run flows. Update the `opts.dryRun` path in `test`
command handling to explicitly emit the same dry-run banner used by `runDiff`
and `runTestRunAll` before returning, keeping the dry-run output on stderr and
preserving consistency across CLI commands.

Source: Path instructions

Comment thread src/commands/test.ts
Comment on lines +8287 to +8308
} catch (err) {
// A missing replayable run is fatal for the whole command (mirror rerun):
// there is nothing to repeat, so point the user at a fresh `test run`.
if (err instanceof ApiError && err.code === 'NOT_FOUND') {
throw ApiError.fromEnvelope({
error: {
code: 'NOT_FOUND',
message: `Test ${opts.testId} has no replayable run (unknown/cross-tenant id, or it has never completed a clean run).`,
nextAction: `Trigger a fresh run first: testsprite test run ${opts.testId}`,
requestId: err.requestId ?? 'local',
details: { testId: opts.testId, reason: 'no_replayable_run' },
},
});
}
// Any other trigger error is recorded as an errored attempt so a single
// transient blip doesn't abort a long stability probe.
const code = err instanceof ApiError ? err.code : 'ERROR';
attempts.push({ attempt: i, runId: null, outcome: 'error', failureKind: code });
ticker.update(`Attempt ${i}/${opts.runs} — error (${code})`);
if (opts.untilFail) break;
continue;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the flaky command implementation and nearby help text.
rg -n "runFlaky|flaky|emitDryRunBanner|AUTH_REQUIRED|AUTH_INVALID|NOT_FOUND|untilFail|runs" src/commands/test.ts

# Show the relevant slices around the flaky command and dry-run handling.
sed -n '8120,8365p' src/commands/test.ts

Repository: TestSprite/testsprite-cli

Length of output: 22872


Propagate non-retryable rerun errors instead of counting them as attempts
triggerRerun should rethrow auth/permission failures instead of recording them as error attempts; otherwise the command returns exit 1 and masks the documented exit 3 path. --dry-run also skips the usual dry-run banner here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/test.ts` around lines 8287 - 8308, The rerun error handling in
the `triggerRerun` catch block currently treats all non-NOT_FOUND failures as
retryable attempts, which hides auth/permission failures and the intended exit-3
path. Update the `catch (err)` logic in `test.ts` to detect non-retryable API
errors (especially authorization/permission cases) and rethrow them instead of
pushing to `attempts` or updating the ticker, while keeping only transient
errors as recorded attempts. Also ensure the `--dry-run` path still emits the
usual dry-run banner before rerun handling.

Source: Path instructions

@lxcario

lxcario commented Jul 5, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto latest main and resolved the CHANGELOG.md conflict from the merge wave — kept the existing JUnit report entry and the test flaky entry with the --runs 1–10 cap (dropped the stale 1–100 duplicate; no conflict markers left). All 9 checks are green again (Lint & Format, Typecheck, Build 20/22, Unit 20/22, E2E, Coverage, CodeRabbit).

Ready for merge whenever you are — thanks for the review! 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Hackathon] Add testsprite test flaky <id> — repeat-run flaky-test detector with a CI stability gate

2 participants