Skip to content

Commit 4b2e45b

Browse files
committed
fix(dry-run): include failed run-scoped step sample
1 parent b9e9601 commit 4b2e45b

3 files changed

Lines changed: 87 additions & 23 deletions

File tree

src/commands/test.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2667,6 +2667,40 @@ describe('runSteps', () => {
26672667
expect(block.match(/error: /g)).toHaveLength(1);
26682668
});
26692669

2670+
it('--run-id dry-run sample maps the failed step error and failure contributor flag', async () => {
2671+
const out: string[] = [];
2672+
const page = await runSteps(
2673+
{
2674+
profile: 'default',
2675+
output: 'json',
2676+
debug: false,
2677+
dryRun: true,
2678+
testId: 'test_fe',
2679+
runId: 'run_dry',
2680+
},
2681+
{
2682+
env: {} as NodeJS.ProcessEnv,
2683+
credentialsPath: join(tmpdir(), 'testsprite-no-creds'),
2684+
stdout: line => out.push(line),
2685+
stderr: () => undefined,
2686+
},
2687+
);
2688+
2689+
const failing = page.items.find(step => step.stepIndex === 3);
2690+
expect(failing).toMatchObject({
2691+
status: 'failed',
2692+
error: expect.any(String),
2693+
stepType: 'assertion',
2694+
outcomeContributesToFailure: true,
2695+
});
2696+
expect(page.items.find(step => step.stepIndex === 1)?.outcomeContributesToFailure).toBe(false);
2697+
2698+
const printed = JSON.parse(out[0]!) as { items: Array<{ stepIndex: number; error?: string }> };
2699+
expect(printed.items.some(step => step.stepIndex === 3 && typeof step.error === 'string')).toBe(
2700+
true,
2701+
);
2702+
});
2703+
26702704
it('--run-id: rejects a runId that belongs to a different test (exit 4)', async () => {
26712705
const { credentialsPath } = makeCreds();
26722706
// The run-scoped endpoint returns a run whose testId differs from the

src/lib/dry-run/samples.test.ts

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -407,20 +407,41 @@ describe('findSample', () => {
407407
expect(e).toBeUndefined();
408408
});
409409

410-
// defect-2 fix: getRun sample must return the passed shape (first-match-wins
411-
// in findSample). Prior to fix, a duplicate failed-shape entry appeared
412-
// before the passed-shape entry; `test wait --dry-run` always resolved to
413-
// status: "failed", giving agents the wrong happy-path canned response.
414-
it('GET /runs/{runId} resolves to the passed-shape getRun (not the failed shape)', () => {
410+
it('GET /runs/{runId} sample includes a failed run-scoped step', () => {
415411
const e = findSample('GET', 'https://api.testsprite.com/api/cli/v1/runs/run_xyz');
416412
expect(e?.operationId).toBe('getRun');
417413
const body = e?.body() as {
418414
status: string;
419415
runId: string;
420-
stepSummary: { failedCount: number };
416+
failedStepIndex: number | null;
417+
failureKind: string | null;
418+
error: string | null;
419+
stepSummary: { total: number; completed: number; passedCount: number; failedCount: number };
420+
steps: Array<{
421+
stepIndex: string;
422+
type: string;
423+
status: string | null;
424+
error: string | null;
425+
}>;
421426
};
422-
expect(body.status).toBe('passed');
423-
expect(body.stepSummary.failedCount).toBe(0);
427+
expect(body.status).toBe('failed');
428+
expect(body.failedStepIndex).toBe(3);
429+
expect(body.failureKind).toBe('assertion');
430+
expect(body.error).toEqual(expect.any(String));
431+
expect(body.stepSummary).toMatchObject({
432+
total: 3,
433+
completed: 3,
434+
passedCount: 2,
435+
failedCount: 1,
436+
});
437+
438+
const failingStep = body.steps.find(step => step.stepIndex === '0003');
439+
expect(failingStep).toMatchObject({
440+
type: 'assertion',
441+
status: 'failed',
442+
error: expect.any(String),
443+
});
444+
expect(failingStep?.error).not.toBe('');
424445
});
425446

426447
it('getTest sample carries priority field (G1a)', () => {

src/lib/dry-run/samples.ts

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -691,37 +691,35 @@ const ENTRIES: DryRunSampleEntry[] = [
691691
},
692692
} satisfies BatchRerunResponse),
693693
// M3.3 piece-3 — GET /runs/{runId} (live status / long-poll).
694-
// A terminal `passed` row is the most useful dry-run shape: agents see
695-
// what a completed run looks like, and `--wait` terminates immediately.
696-
// fix(2026-05-21): a duplicate failed-shape entry that appeared before
697-
// this entry was removed; findSample first-match-wins was always
698-
// returning status: "failed" for `test wait --dry-run`.
694+
// Use a terminal failed row with a concrete failed step so
695+
// `test steps --run-id <id> --dry-run` demonstrates the run-scoped
696+
// error + failedStepIndex mapping without needing live credentials.
699697
entry('getRun', 'GET', '/runs/{runId}', {
700698
runId: SAMPLE_RUN_ID,
701-
testId: SAMPLE_TEST_ID_PASSED,
699+
testId: SAMPLE_TEST_ID_FAILED,
702700
projectId: SAMPLE_PROJECT_ID,
703701
userId: SAMPLE_USER_ID,
704-
status: 'passed',
702+
status: 'failed',
705703
source: 'cli',
706704
createdAt: '2026-05-15T19:32:00.000Z',
707705
startedAt: '2026-05-15T19:32:05.000Z',
708706
finishedAt: '2026-05-15T19:34:00.000Z',
709707
codeVersion: 'v1',
710708
targetUrl: SAMPLE_TARGET_URL,
711709
createdFrom: null,
712-
failedStepIndex: null,
713-
failureKind: null,
714-
error: null,
710+
failedStepIndex: 3,
711+
failureKind: 'assertion',
712+
error: 'Expected billing status badge to be visible, but it was not found.',
715713
videoUrl: null,
716714
stepSummary: {
717-
total: 8,
718-
completed: 8,
719-
passedCount: 8,
720-
failedCount: 0,
715+
total: 3,
716+
completed: 3,
717+
passedCount: 2,
718+
failedCount: 1,
721719
},
722720
// Representative per-run steps so `test steps --run-id <id> --dry-run`
723721
// demonstrates real output instead of an empty list (the generic
724-
// `/runs/{runId}` sample is also used by `test wait`, which ignores steps).
722+
// `/runs/{runId}` sample is also safe for wait flows, which ignore steps).
725723
steps: [
726724
{
727725
stepIndex: '0001',
@@ -745,6 +743,17 @@ const ENTRIES: DryRunSampleEntry[] = [
745743
htmlSnapshotUrl: null,
746744
createdAt: '2026-05-15T19:32:20.000Z',
747745
},
746+
{
747+
stepIndex: '0003',
748+
type: 'assertion',
749+
action: 'assert_visible',
750+
status: 'failed',
751+
description: 'Billing status badge is visible',
752+
error: 'Expected billing status badge to be visible, but it was not found.',
753+
screenshotUrl: null,
754+
htmlSnapshotUrl: null,
755+
createdAt: '2026-05-15T19:32:30.000Z',
756+
},
748757
],
749758
} satisfies RunResponse),
750759
];

0 commit comments

Comments
 (0)