Skip to content

Commit cff19ae

Browse files
Contributorcursoragent
andcommitted
fix(rerun): emit partial stdout on TimeoutError in single-FE rerun --wait
Apply the fix in src/commands/test.ts. When the overall --timeout polling deadline is exceeded on a single FE rerun, emit {runId, status:"running"} to stdout before exit 7. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 3ab8136 commit cff19ae

2 files changed

Lines changed: 87 additions & 0 deletions

File tree

src/commands/test.rerun.spec.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4630,3 +4630,78 @@ describe('rerun --wait — dashboardUrl on terminal output', () => {
46304630
);
46314631
});
46324632
});
4633+
4634+
// ---------------------------------------------------------------------------
4635+
// TimeoutError on single FE rerun --wait: partial stdout + exit 7
4636+
// ---------------------------------------------------------------------------
4637+
4638+
describe('[finding-4] single FE rerun --wait: TimeoutError writes partial JSON to stdout', () => {
4639+
it('exit 7 AND stdout contains {runId, status:"running"} when --timeout polling deadline is exceeded', async () => {
4640+
const creds = makeCreds();
4641+
const rerunResp = makeFeRerunResp();
4642+
4643+
let fetchCallCount = 0;
4644+
const fetchImpl: typeof globalThis.fetch = async (input, _init) => {
4645+
const url =
4646+
typeof input === 'string'
4647+
? input
4648+
: input instanceof URL
4649+
? input.toString()
4650+
: (input as { url: string }).url;
4651+
fetchCallCount++;
4652+
if (url.includes('/tests/test_fe_01/runs/rerun')) {
4653+
return new Response(JSON.stringify(rerunResp), {
4654+
status: 202,
4655+
headers: { 'content-type': 'application/json' },
4656+
});
4657+
}
4658+
if (url.includes('/runs/')) {
4659+
const runningRun: RunResponse = {
4660+
...makeTerminalRun(rerunResp.runId, 'passed'),
4661+
status: 'running',
4662+
finishedAt: null,
4663+
};
4664+
return new Response(JSON.stringify(runningRun), {
4665+
status: 200,
4666+
headers: { 'content-type': 'application/json' },
4667+
});
4668+
}
4669+
return new Response(JSON.stringify({ error: { code: 'NOT_FOUND' } }), { status: 404 });
4670+
};
4671+
4672+
const stdoutLines: string[] = [];
4673+
4674+
const err = await runTestRerun(
4675+
{
4676+
testIds: ['test_fe_01'],
4677+
all: false,
4678+
wait: true,
4679+
timeoutSeconds: 0,
4680+
autoHeal: false,
4681+
autoHealExplicit: false,
4682+
skipDependencies: false,
4683+
maxConcurrency: 10,
4684+
output: 'json',
4685+
profile: 'default',
4686+
dryRun: false,
4687+
debug: false,
4688+
verbose: false,
4689+
},
4690+
{
4691+
...creds,
4692+
sleep: instantSleep,
4693+
fetchImpl: fetchImpl as unknown as FetchImpl,
4694+
stdout: line => stdoutLines.push(line),
4695+
stderr: () => undefined,
4696+
},
4697+
).catch(e => e);
4698+
4699+
expect(err).toMatchObject({ exitCode: 7 });
4700+
expect(stdoutLines.length).toBeGreaterThan(0);
4701+
const parsed = JSON.parse(stdoutLines.join('\n')) as { runId: string; status: string };
4702+
expect(parsed.runId).toBe(rerunResp.runId);
4703+
expect(parsed.status).toBe('running');
4704+
4705+
void fetchCallCount;
4706+
});
4707+
});

src/commands/test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6099,6 +6099,18 @@ export async function runTestRerun(
60996099
} catch (err) {
61006100
if (err instanceof TimeoutError) {
61016101
ticker.finalize(`Run ${rerunResp.runId} — timed out after ${opts.timeoutSeconds}s`);
6102+
// Mirror the RequestTimeoutError path: emit a partial run to stdout so
6103+
// JSON consumers and AI agents can grab the runId and chain into
6104+
// `testsprite test wait <runId>` without parsing the stderr error envelope.
6105+
const timeoutPartial = { runId: rerunResp.runId, status: 'running' as const };
6106+
out.print(timeoutPartial, data => {
6107+
const p = data as typeof timeoutPartial;
6108+
return [
6109+
`runId ${p.runId}`,
6110+
`status ${p.status} (timed out after ${opts.timeoutSeconds}s)`,
6111+
`hint Re-attach with: testsprite test wait ${p.runId}`,
6112+
].join('\n');
6113+
});
61026114
throw ApiError.fromEnvelope({
61036115
error: {
61046116
code: 'UNSUPPORTED',

0 commit comments

Comments
 (0)