Skip to content

Commit d9ce26d

Browse files
committed
fix(flaky): cap --runs at 10 per maintainer scope (#115)
Rescope the flaky detector's --runs bound from 1-100 to 1-10 as requested in the #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.
1 parent fbae730 commit d9ce26d

5 files changed

Lines changed: 68 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ All notable changes to `@testsprite/testsprite-cli` are documented here. The for
66

77
### Added
88

9-
- **`testsprite test flaky <test-id>`** — repeat-run flaky-test detector. Replays a test N times (`--runs <n>`, default 5), aggregates the outcomes, and reports a stability verdict (`stable` / `flaky` / `failing`) plus the `runId` and `failureKind` of every attempt that did not pass. 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 a merge on flakiness (`testsprite test flaky <id> --runs 5 || exit 1`). Flags: `--runs <n>` (1–100), `--until-fail` (stop at the first non-passing attempt), `--timeout <s>` (per-attempt), and `--output json` for a machine-readable stability report. Frontend replays are free verbatim script replays; a one-line advisory is printed for backend tests, whose closure reruns may cost credits.
9+
- **`testsprite test flaky <test-id>`** — repeat-run flaky-test detector. Replays a test N times (`--runs <n>`, default 5), aggregates the outcomes, and reports a stability verdict (`stable` / `flaky` / `failing`) plus the `runId` and `failureKind` of every attempt that did not pass. 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 a merge on flakiness (`testsprite test flaky <id> --runs 5 || exit 1`). Flags: `--runs <n>` (1–10), `--until-fail` (stop at the first non-passing attempt), `--timeout <s>` (per-attempt), and `--output json` for a machine-readable stability report. Frontend replays are free verbatim script replays; a one-line advisory is printed for backend tests, whose closure reruns may cost credits.
1010

1111
## [0.1.2] - 2026-06-19
1212

DOCUMENTATION.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -389,15 +389,15 @@ Detect a **flaky** test by replaying it several times and reporting how often it
389389
testsprite test flaky test_xxxxxxxx --runs 10
390390

391391
# Fast "is it flaky at all?" — stop at the first non-passing attempt
392-
testsprite test flaky test_xxxxxxxx --runs 20 --until-fail
392+
testsprite test flaky test_xxxxxxxx --runs 10 --until-fail
393393

394394
# Machine-readable stability report for CI
395395
testsprite test flaky test_xxxxxxxx --runs 10 --output json
396396
```
397397

398398
Flags:
399399

400-
- `--runs <n>` — number of replays (1–100, default 5).
400+
- `--runs <n>` — number of replays (1–10, default 5).
401401
- `--until-fail` — stop at the first attempt that does not pass.
402402
- `--timeout <s>` — per-attempt polling deadline (same semantics as `test wait`).
403403

src/commands/test.flaky.spec.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ describe('runFlaky', () => {
345345
expect((err as ApiError).code).toBe('NOT_FOUND');
346346
});
347347

348-
it('rejects --runs outside 1..100 with a validation error (exit 5)', async () => {
348+
it('rejects --runs below the range (0) with a validation error (exit 5)', async () => {
349349
const { fetchImpl } = makeFlakyFetch({ statuses: [] });
350350
const { deps } = makeDeps(fetchImpl);
351351
const err = await runFlaky(
@@ -365,4 +365,25 @@ describe('runFlaky', () => {
365365
expect(err).toBeInstanceOf(ApiError);
366366
expect((err as ApiError).exitCode).toBe(5);
367367
});
368+
369+
it('rejects --runs above the cap (11) with a validation error (exit 5)', async () => {
370+
const { fetchImpl } = makeFlakyFetch({ statuses: [] });
371+
const { deps } = makeDeps(fetchImpl);
372+
const err = await runFlaky(
373+
{
374+
profile: 'default',
375+
output: 'text',
376+
dryRun: false,
377+
debug: false,
378+
verbose: false,
379+
testId: 'test_x',
380+
runs: 11,
381+
untilFail: false,
382+
timeoutSeconds: 600,
383+
},
384+
deps,
385+
).catch((e: unknown) => e);
386+
expect(err).toBeInstanceOf(ApiError);
387+
expect((err as ApiError).exitCode).toBe(5);
388+
});
368389
});

src/commands/test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7567,8 +7567,8 @@ export function createTestCommand(deps: TestDeps = {}): Command {
75677567
// `test flaky` — repeat-run flaky-test detector
75687568
// ---------------------------------------------------------------------------
75697569

7570-
/** Upper bound on `--runs` so a typo can't spawn thousands of replays. */
7571-
const MAX_FLAKY_RUNS = 100;
7570+
/** Upper bound on `--runs` so a repeat-runner can't amplify free FE replays. */
7571+
const MAX_FLAKY_RUNS = 10;
75727572
/** Default replay count when `--runs` is omitted. */
75737573
const DEFAULT_FLAKY_RUNS = 5;
75747574

test/__snapshots__/help.snapshot.test.ts.snap

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,15 @@ Commands:
259259
11 rate limited — honor Retry-After
260260
261261
On failure/blocked/cancelled, run: testsprite test artifact get <run-id>
262+
flaky [options] <test-id> Repeatedly replay a test to measure stability and surface flakiness.
263+
Replays run with auto-heal OFF (strict verbatim) so healed drift cannot mask nondeterministic pass/fail.
264+
265+
Exit codes:
266+
0 stable (every attempt passed)
267+
1 flaky or failing (at least one attempt did not pass)
268+
3 auth error
269+
4 test not found (no replayable run — trigger \`testsprite test run <id>\` first)
270+
5 validation error
262271
code Inspect and edit generated test code
263272
plan Manage FE test plan-steps (FE-only)
264273
failure Export the latest-failure agent bundle
@@ -334,6 +343,38 @@ Global options (--dry-run, --output, --profile, --endpoint-url, --verbose, --deb
334343
"
335344
`;
336345
346+
exports[`--help snapshots > test flaky 1`] = `
347+
"Usage: testsprite test flaky [options] <test-id>
348+
349+
Repeatedly replay a test to measure stability and surface flakiness.
350+
Replays run with auto-heal OFF (strict verbatim) so healed drift cannot mask nondeterministic pass/fail.
351+
352+
Exit codes:
353+
0 stable (every attempt passed)
354+
1 flaky or failing (at least one attempt did not pass)
355+
3 auth error
356+
4 test not found (no replayable run — trigger \`testsprite test run <id>\` first)
357+
5 validation error
358+
359+
Options:
360+
--runs <n> number of replays to run (1-10, default 5)
361+
--until-fail stop at the first non-passing attempt (fast "is it flaky at
362+
all?" check) (default: false)
363+
--timeout <s> per-attempt max seconds to wait (1-3600, default 600)
364+
-h, --help display help for command
365+
366+
Notes:
367+
• Frontend replays are free verbatim script replays (no credit); backend replays
368+
re-run the dependency closure and may cost credits — a one-line advisory is printed.
369+
• Replays use auto-heal OFF so a flaky test is not silently "healed" into a pass;
370+
this measures replay stability of the saved script against the configured URL.
371+
• \`--output json\` emits a machine-readable stability report for CI gating.
372+
373+
Global options (--dry-run, --output, --profile, --endpoint-url, --verbose, --debug):
374+
testsprite --help
375+
"
376+
`;
377+
337378
exports[`--help snapshots > test get 1`] = `
338379
"Usage: testsprite test get [options] <test-id>
339380

0 commit comments

Comments
 (0)