diff --git a/test/qa-fix-loop-fixture.test.ts b/test/qa-fix-loop-fixture.test.ts index 15956eee7..54267fb7b 100644 --- a/test/qa-fix-loop-fixture.test.ts +++ b/test/qa-fix-loop-fixture.test.ts @@ -21,7 +21,9 @@ const scenario = ${JSON.stringify(scenario)}; const attempts = [], records = [], servers = []; const late = scenario === 'late-timeout'; const workMs = 1000, drainMs = 80; -let finalized = false, outerMs; +const realNow = Date.now; +let finalized = false, outerMs, frozenAt; +if (late) Date.now = () => frozenAt ?? realNow(); const collector = { addTest: entry => { records.push({ ...entry, afterFinalization: finalized }); if (scenario === 'recording') throw new Error('QA recorder failure'); @@ -46,10 +48,10 @@ mock.module(path.join(root, 'test/helpers/e2e-helpers.ts'), () => ({ testConcurrentIfSelected: (id, body, budget) => { if (id !== 'qa-fix-loop') return; outerMs = budget; - // Keep the actual native outer deadline, scaling only its 5s recording - // margin to80ms. The pre-fix equal work/outer deadline stays equal. - const nativeBudget = late ? (budget >= workMs + 5000 ? budget - 5000 + 80 : budget) : 5000; - test(id, body, nativeBudget); + // The late attempt's clock stands still through its real setup, so the + // mocked work budget reaches the capture whole; the deadline is the + // registered outer budget. + test(id, late ? () => { frozenAt = realNow(); return body(); } : body, late ? budget : 5000); }, copyDirSync: (source, target) => fs.cpSync(source, target, { recursive: true }), setupBrowseShims: cwd => fs.mkdirSync(path.join(cwd, 'browse/bin'), { recursive: true }), @@ -62,6 +64,7 @@ mock.module(path.join(root, 'test/helpers/e2e-helpers.ts'), () => ({ mock.module(path.join(root, 'test/helpers/session-runner.ts'), () => ({ SESSION_DRAIN_GRACE_MS: late ? drainMs : 5000, runSkillTest: async options => { + frozenAt = undefined; const cwd = options.workingDirectory; const git = (...args) => execFileSync('git', args, { cwd, encoding: 'utf8', timeout: 5000 }); const initial = fs.readFileSync(path.join(cwd, 'index.html'), 'utf8'); @@ -102,6 +105,7 @@ afterAll(async () => { // behind child exit; this reproduces the observed late recording race. if (late) await new Promise(resolve => setTimeout(resolve, 200)); fs.writeFileSync(${JSON.stringify(factsPath)}, JSON.stringify({ attempts, records, servers, outerMs })); + Date.now = realNow; }); await import(path.join(root, 'test/skill-e2e-qa-workflow.test.ts')); `); @@ -152,8 +156,7 @@ await import(path.join(root, 'test/skill-e2e-qa-workflow.test.ts')); } if (scenario === 'late-timeout') { expect(outerMs).toBe(1000 + 80 + 5000); - expect(attempts[0].captureBudgetMs).toBeGreaterThan(0); - expect(attempts[0].captureBudgetMs).toBeLessThan(1000); + expect(attempts[0].captureBudgetMs).toBe(1000); expect(records[0].exit_reason).toBe('timeout'); expect(child.stderr).not.toContain('Unhandled error between tests'); }