Skip to content

Commit e8179bb

Browse files
committed
fix(steps): surface run-scoped step errors
1 parent 7b7d80d commit e8179bb

2 files changed

Lines changed: 84 additions & 3 deletions

File tree

src/commands/test.test.ts

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2522,11 +2522,81 @@ describe('runSteps', () => {
25222522
// timestamps come from RunStepDto.createdAt
25232523
expect(first.capturedAt).toBe('2026-06-01T10:00:05.000Z');
25242524
expect(first.updatedAt).toBe('2026-06-01T10:00:05.000Z');
2525+
expect(first.stepType).toBe('action');
2526+
expect(first.error).toBeNull();
25252527
// outcomeContributesToFailure: null when run passed (failedStepIndex is null)
25262528
expect(first.outcomeContributesToFailure).toBeNull();
25272529
// JSON output should be parseable with 2 items
2528-
const printed = JSON.parse(out[0]!) as { items: unknown[] };
2530+
const printed = JSON.parse(out[0]!) as {
2531+
items: Array<{ stepType?: string; error?: string | null }>;
2532+
};
25292533
expect(printed.items).toHaveLength(2);
2534+
expect(printed.items[0]!.stepType).toBe('action');
2535+
expect(printed.items[0]!.error).toBeNull();
2536+
});
2537+
2538+
it('--run-id preserves per-step error text in JSON output', async () => {
2539+
const { credentialsPath } = makeCreds();
2540+
const runWithStepError = {
2541+
...RUN_WITH_STEPS,
2542+
status: 'failed' as const,
2543+
failedStepIndex: 2,
2544+
stepSummary: { total: 2, completed: 2, passedCount: 1, failedCount: 1 },
2545+
steps: RUN_WITH_STEPS.steps.map(step =>
2546+
step.stepIndex === '0002'
2547+
? {
2548+
...step,
2549+
status: 'failed' as const,
2550+
error: 'AssertionError: expected heading to be visible',
2551+
}
2552+
: step,
2553+
),
2554+
};
2555+
const fetchImpl = makeFetch(() => ({ body: runWithStepError }));
2556+
const out: string[] = [];
2557+
2558+
await runSteps(
2559+
{ profile: 'default', output: 'json', debug: false, testId: 'test_fe', runId: 'run_scoped' },
2560+
{ credentialsPath, fetchImpl, stdout: line => out.push(line) },
2561+
);
2562+
2563+
const printed = JSON.parse(out[0]!) as {
2564+
items: Array<{ stepIndex: number; stepType?: string; error?: string | null }>;
2565+
};
2566+
const failedStep = printed.items.find(step => step.stepIndex === 2)!;
2567+
expect(failedStep.stepType).toBe('assertion');
2568+
expect(failedStep.error).toBe('AssertionError: expected heading to be visible');
2569+
});
2570+
2571+
it('--run-id prints per-step error text under failed rows in text mode', async () => {
2572+
const { credentialsPath } = makeCreds();
2573+
const runWithStepError = {
2574+
...RUN_WITH_STEPS,
2575+
status: 'failed' as const,
2576+
failedStepIndex: 2,
2577+
stepSummary: { total: 2, completed: 2, passedCount: 1, failedCount: 1 },
2578+
steps: RUN_WITH_STEPS.steps.map(step =>
2579+
step.stepIndex === '0002'
2580+
? {
2581+
...step,
2582+
status: 'failed' as const,
2583+
error: 'AssertionError: expected heading to be visible',
2584+
}
2585+
: step,
2586+
),
2587+
};
2588+
const fetchImpl = makeFetch(() => ({ body: runWithStepError }));
2589+
const out: string[] = [];
2590+
2591+
await runSteps(
2592+
{ profile: 'default', output: 'text', debug: false, testId: 'test_fe', runId: 'run_scoped' },
2593+
{ credentialsPath, fetchImpl, stdout: line => out.push(line) },
2594+
);
2595+
2596+
const block = out.join('\n');
2597+
expect(block).toContain('assert heading');
2598+
expect(block).toContain('failed');
2599+
expect(block).toContain(' error: AssertionError: expected heading to be visible');
25302600
});
25312601

25322602
it('--run-id: outcomeContributesToFailure=true on the failedStepIndex step', async () => {

src/commands/test.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,9 +159,16 @@ export interface CliTestCode {
159159
export interface CliTestStep {
160160
testId: string;
161161
stepIndex: number;
162+
/**
163+
* Run-scoped steps expose the backend's original plan-step type. Optional so
164+
* the cumulative /tests/{id}/steps response remains backward-compatible.
165+
*/
166+
stepType?: 'action' | 'assertion';
162167
action: string;
163168
description: string;
164169
status: 'passed' | 'failed' | null;
170+
/** Per-step failure text already returned by GET /runs/{id}?includeSteps=true. */
171+
error?: string | null;
165172
screenshotUrl: string | null;
166173
htmlSnapshotUrl: string | null;
167174
runIdIfAvailable: string | null;
@@ -3612,9 +3619,11 @@ function mapRunStepToCliTestStep(step: RunStepDto, run: RunResponse): CliTestSte
36123619
return {
36133620
testId: run.testId,
36143621
stepIndex: numericIndex,
3622+
stepType: step.type,
36153623
action: step.action,
36163624
description: step.description ?? '',
36173625
status: step.status,
3626+
error: step.error,
36183627
screenshotUrl: step.screenshotUrl,
36193628
htmlSnapshotUrl: step.htmlSnapshotUrl,
36203629
runIdIfAvailable: run.runId,
@@ -8232,16 +8241,18 @@ function renderStepsText(page: Page<CliTestStep>): string {
82328241
' ' +
82338242
'UPDATED';
82348243

8235-
const rows = page.items.map(s => {
8244+
const rows = page.items.flatMap(s => {
82368245
const marker = s.outcomeContributesToFailure === true ? '* ' : ' ';
8237-
return [
8246+
const row = [
82388247
marker,
82398248
pad(String(s.stepIndex), indexWidth),
82408249
pad(s.action, actionWidth),
82418250
pad(s.status ?? '—', statusWidth),
82428251
pad(descOf(s), descWidth),
82438252
s.updatedAt,
82448253
].join(' ');
8254+
const error = s.error?.trim();
8255+
return error ? [row, ` error: ${error}`] : [row];
82458256
});
82468257

82478258
const lines: string[] = [header, ...rows, ''];

0 commit comments

Comments
 (0)