11import { chunk } from "@std/collections/chunk" ;
2- import { deadline } from "@std/async/deadline" ;
32import type { ScenarioDefinition , StepOptions } from "@probitas/core" ;
43import type {
54 Reporter ,
@@ -11,7 +10,7 @@ import { ScenarioRunner } from "./scenario_runner.ts";
1110import { toScenarioMetadata } from "./metadata.ts" ;
1211import { timeit } from "./utils/timeit.ts" ;
1312import { mergeSignals } from "./utils/signal.ts" ;
14- import { ScenarioTimeoutError } from "./errors.ts" ;
13+ import { ScenarioTimeoutError , StepTimeoutError } from "./errors.ts" ;
1514
1615/**
1716 * Top-level test runner that orchestrates execution of multiple scenarios.
@@ -116,64 +115,40 @@ export class Runner {
116115 timeout : number ,
117116 signal ?: AbortSignal ,
118117 ) : Promise < ScenarioResult > {
119- try {
120- const timeoutSignal = mergeSignals (
121- signal ,
122- AbortSignal . timeout ( timeout ) ,
123- ) ;
124- const result = await timeit ( ( ) =>
125- deadline (
126- scenarioRunner . run ( scenario , { signal : timeoutSignal } ) ,
127- timeout ,
128- { signal : timeoutSignal } ,
129- )
130- ) ;
118+ const timeoutSignal = mergeSignals (
119+ signal ,
120+ AbortSignal . timeout ( timeout ) ,
121+ ) ;
122+
123+ const result = await timeit ( ( ) =>
124+ scenarioRunner . run ( scenario , { signal : timeoutSignal } )
125+ ) ;
126+
127+ // Handle timeit result
128+ if ( result . status === "failed" ) {
129+ // timeit itself failed (this should be rare)
130+ throw result . error ;
131+ }
131132
132- // Handle successful execution
133- if ( result . status === "passed" ) {
134- let scenarioResult = result . value ;
135-
136- // Handle timeout errors from within scenario
137- if (
138- scenarioResult . status === "failed" &&
139- isTimeoutError ( scenarioResult . error )
140- ) {
141- const timeoutError = new ScenarioTimeoutError (
142- scenario . name ,
143- timeout ,
144- result . duration ,
145- { cause : scenarioResult . error } ,
146- ) ;
147- scenarioResult = {
148- ...scenarioResult ,
149- error : timeoutError ,
150- } ;
151- }
152-
153- return scenarioResult ;
154- } else {
155- // timeit itself failed (this should be rare)
156- throw result . error ;
157- }
158- } catch ( error ) {
159- // Catch timeout errors thrown by deadline
160- if ( isTimeoutError ( error ) ) {
161- const metadata = toScenarioMetadata ( scenario ) ;
162- return {
163- status : "failed" ,
164- duration : timeout ,
165- metadata,
166- steps : [ ] ,
167- error : new ScenarioTimeoutError (
168- scenario . name ,
169- timeout ,
170- timeout ,
171- { cause : error } ,
172- ) ,
173- } ;
174- }
175- throw error ;
133+ const scenarioResult = result . value ;
134+
135+ // Check if scenario failed due to timeout
136+ if (
137+ scenarioResult . status === "failed" &&
138+ isTimeoutError ( scenarioResult . error )
139+ ) {
140+ return {
141+ ...scenarioResult ,
142+ error : new ScenarioTimeoutError (
143+ scenario . name ,
144+ timeout ,
145+ result . duration ,
146+ { cause : scenarioResult . error } ,
147+ ) ,
148+ } ;
176149 }
150+
151+ return scenarioResult ;
177152 }
178153
179154 async #run(
@@ -198,11 +173,13 @@ export class Runner {
198173 signal ?. throwIfAborted ( ) ;
199174
200175 // Execute scenario with optional timeout
201- const scenarioResult = timeout > 0
176+ // Priority: scenario timeout > RunOptions timeout
177+ const effectiveTimeout = scenario . timeout ?? timeout ;
178+ const scenarioResult = effectiveTimeout > 0
202179 ? await this . #runWithTimeout(
203180 scenarioRunner ,
204181 scenario ,
205- timeout ,
182+ effectiveTimeout ,
206183 signal ,
207184 )
208185 : await scenarioRunner . run ( scenario , { signal } ) ;
@@ -221,5 +198,8 @@ export class Runner {
221198}
222199
223200function isTimeoutError ( error : unknown ) : boolean {
224- return error instanceof DOMException && error . name === "TimeoutError" ;
201+ return (
202+ ( error instanceof DOMException && error . name === "TimeoutError" ) ||
203+ error instanceof StepTimeoutError
204+ ) ;
225205}
0 commit comments