@@ -141,7 +141,7 @@ Deno.test("Runner run runs multiple scenarios (skip)", async () => {
141141 } ) ;
142142} ) ;
143143
144- Deno . test ( "Runner run runs multiple scenarios (failed)" , async ( ) => {
144+ Deno . test ( "Runner run runs multiple scenarios (failed) - aborts on maxFailures " , async ( ) => {
145145 using signal = createScopedSignal ( ) ;
146146 const runner = new Runner ( reporter ) ;
147147 const scenarios = [
@@ -150,8 +150,8 @@ Deno.test("Runner run runs multiple scenarios (failed)", async () => {
150150 steps : [
151151 createTestStep ( {
152152 name : "Step 1" ,
153- fn : async ( ) => {
154- await delay ( 0 , { signal } ) ;
153+ fn : async ( ctx ) => {
154+ await delay ( 0 , { signal : ctx . signal } ) ;
155155 return "1" ;
156156 } ,
157157 } ) ,
@@ -162,8 +162,8 @@ Deno.test("Runner run runs multiple scenarios (failed)", async () => {
162162 steps : [
163163 createTestStep ( {
164164 name : "Step 1" ,
165- fn : async ( ) => {
166- await delay ( 50 , { signal } ) ;
165+ fn : async ( ctx ) => {
166+ await delay ( 50 , { signal : ctx . signal } ) ;
167167 throw "2" ;
168168 } ,
169169 } ) ,
@@ -174,8 +174,8 @@ Deno.test("Runner run runs multiple scenarios (failed)", async () => {
174174 steps : [
175175 createTestStep ( {
176176 name : "Step 1" ,
177- fn : async ( ) => {
178- await delay ( 100 , { signal } ) ;
177+ fn : async ( ctx ) => {
178+ await delay ( 100 , { signal : ctx . signal } ) ;
179179 return "3" ;
180180 } ,
181181 } ) ,
@@ -187,12 +187,219 @@ Deno.test("Runner run runs multiple scenarios (failed)", async () => {
187187 signal,
188188 maxFailures : 1 ,
189189 } ) ;
190+
191+ // When maxFailures is reached, all scenarios (running or pending) should be aborted
192+ // Scenario 1 passes (fastest, 0ms delay)
193+ // Scenario 2 fails (50ms delay, triggers maxFailures)
194+ // Scenario 3 should be aborted/skipped (100ms delay, still running when maxFailures reached)
190195 expect ( summary ) . toMatchObject ( {
191196 total : 3 ,
192197 passed : 1 ,
193- skipped : 1 ,
198+ skipped : 1 , // Scenario 3 aborted
199+ failed : 1 , // Scenario 2
200+ } ) ;
201+ } ) ;
202+
203+ Deno . test ( "Runner run with maxFailures skips remaining scenarios (sequential)" , async ( ) => {
204+ const runner = new Runner ( reporter ) ;
205+ const executionOrder : string [ ] = [ ] ;
206+ const scenarios = [
207+ createTestScenario ( {
208+ name : "Scenario 1" ,
209+ steps : [
210+ createTestStep ( {
211+ name : "Step 1" ,
212+ fn : ( ) => {
213+ executionOrder . push ( "Scenario 1" ) ;
214+ return "1" ;
215+ } ,
216+ } ) ,
217+ ] ,
218+ } ) ,
219+ createTestScenario ( {
220+ name : "Scenario 2" ,
221+ steps : [
222+ createTestStep ( {
223+ name : "Step 1" ,
224+ fn : ( ) => {
225+ executionOrder . push ( "Scenario 2" ) ;
226+ throw new Error ( "Scenario 2 failed" ) ;
227+ } ,
228+ } ) ,
229+ ] ,
230+ } ) ,
231+ createTestScenario ( {
232+ name : "Scenario 3" ,
233+ steps : [
234+ createTestStep ( {
235+ name : "Step 1" ,
236+ fn : ( ) => {
237+ executionOrder . push ( "Scenario 3" ) ;
238+ return "3" ;
239+ } ,
240+ } ) ,
241+ ] ,
242+ } ) ,
243+ createTestScenario ( {
244+ name : "Scenario 4" ,
245+ steps : [
246+ createTestStep ( {
247+ name : "Step 1" ,
248+ fn : ( ) => {
249+ executionOrder . push ( "Scenario 4" ) ;
250+ return "4" ;
251+ } ,
252+ } ) ,
253+ ] ,
254+ } ) ,
255+ ] ;
256+
257+ const summary = await runner . run ( scenarios , {
258+ maxConcurrency : 1 , // Sequential execution
259+ maxFailures : 1 ,
260+ } ) ;
261+
262+ // Scenario 1 should pass, Scenario 2 should fail, Scenario 3 and 4 should be skipped
263+ expect ( summary ) . toMatchObject ( {
264+ total : 4 ,
265+ passed : 1 ,
194266 failed : 1 ,
267+ skipped : 2 ,
268+ } ) ;
269+
270+ // Only Scenario 1 and 2 should have been executed
271+ expect ( executionOrder ) . toEqual ( [ "Scenario 1" , "Scenario 2" ] ) ;
272+
273+ // Check scenario results
274+ const results = summary . scenarios ;
275+ expect ( results . length ) . toBe ( 4 ) ;
276+ expect ( results [ 0 ] . status ) . toBe ( "passed" ) ;
277+ expect ( results [ 0 ] . metadata . name ) . toBe ( "Scenario 1" ) ;
278+ expect ( results [ 1 ] . status ) . toBe ( "failed" ) ;
279+ expect ( results [ 1 ] . metadata . name ) . toBe ( "Scenario 2" ) ;
280+ expect ( results [ 2 ] . status ) . toBe ( "skipped" ) ;
281+ expect ( results [ 2 ] . metadata . name ) . toBe ( "Scenario 3" ) ;
282+ expect ( results [ 3 ] . status ) . toBe ( "skipped" ) ;
283+ expect ( results [ 3 ] . metadata . name ) . toBe ( "Scenario 4" ) ;
284+ } ) ;
285+
286+ Deno . test ( "Runner run with maxFailures aborts in-progress scenarios (parallel)" , async ( ) => {
287+ using signal = createScopedSignal ( ) ;
288+ const runner = new Runner ( reporter ) ;
289+ const executionOrder : string [ ] = [ ] ;
290+ const scenarios = [
291+ createTestScenario ( {
292+ name : "Scenario 1" ,
293+ steps : [
294+ createTestStep ( {
295+ name : "Step 1" ,
296+ fn : async ( ctx ) => {
297+ executionOrder . push ( "Scenario 1 start" ) ;
298+ try {
299+ await delay ( 100 , { signal : ctx . signal } ) ; // Slow - will be aborted
300+ executionOrder . push ( "Scenario 1 end" ) ;
301+ return "1" ;
302+ } catch ( err ) {
303+ executionOrder . push ( "Scenario 1 aborted" ) ;
304+ throw err ;
305+ }
306+ } ,
307+ } ) ,
308+ ] ,
309+ } ) ,
310+ createTestScenario ( {
311+ name : "Scenario 2" ,
312+ steps : [
313+ createTestStep ( {
314+ name : "Step 1" ,
315+ fn : async ( ctx ) => {
316+ executionOrder . push ( "Scenario 2 start" ) ;
317+ await delay ( 10 , { signal : ctx . signal } ) ; // Fast
318+ executionOrder . push ( "Scenario 2 end" ) ;
319+ throw new Error ( "Scenario 2 failed" ) ;
320+ } ,
321+ } ) ,
322+ ] ,
323+ } ) ,
324+ createTestScenario ( {
325+ name : "Scenario 3" ,
326+ steps : [
327+ createTestStep ( {
328+ name : "Step 1" ,
329+ fn : async ( ctx ) => {
330+ executionOrder . push ( "Scenario 3 start" ) ;
331+ try {
332+ await delay ( 100 , { signal : ctx . signal } ) ; // Slow - will be aborted
333+ executionOrder . push ( "Scenario 3 end" ) ;
334+ return "3" ;
335+ } catch ( err ) {
336+ executionOrder . push ( "Scenario 3 aborted" ) ;
337+ throw err ;
338+ }
339+ } ,
340+ } ) ,
341+ ] ,
342+ } ) ,
343+ createTestScenario ( {
344+ name : "Scenario 4" ,
345+ steps : [
346+ createTestStep ( {
347+ name : "Step 1" ,
348+ fn : ( ) => {
349+ executionOrder . push ( "Scenario 4 start" ) ;
350+ return "4" ;
351+ } ,
352+ } ) ,
353+ ] ,
354+ } ) ,
355+ ] ;
356+
357+ const summary = await runner . run ( scenarios , {
358+ signal,
359+ maxConcurrency : 3 , // Scenarios 1-3 run in parallel, 4 is in next batch
360+ maxFailures : 1 ,
195361 } ) ;
362+
363+ // Scenario 1, 2, 3 all start in parallel
364+ // Scenario 2 finishes first and fails, triggers maxFailures
365+ // Scenarios 1, 3 are aborted immediately (signal.abort called)
366+ // Scenario 4 is in the next batch, never starts
367+ expect ( summary ) . toMatchObject ( {
368+ total : 4 ,
369+ passed : 0 ,
370+ failed : 1 , // Scenario 2
371+ skipped : 3 , // Scenarios 1, 3, 4 all skipped
372+ } ) ;
373+
374+ // Verify execution order
375+ expect ( executionOrder ) . toContain ( "Scenario 1 start" ) ;
376+ expect ( executionOrder ) . toContain ( "Scenario 1 aborted" ) ; // Aborted, not completed
377+ expect ( executionOrder ) . not . toContain ( "Scenario 1 end" ) ;
378+
379+ expect ( executionOrder ) . toContain ( "Scenario 2 start" ) ;
380+ expect ( executionOrder ) . toContain ( "Scenario 2 end" ) ;
381+
382+ expect ( executionOrder ) . toContain ( "Scenario 3 start" ) ;
383+ expect ( executionOrder ) . toContain ( "Scenario 3 aborted" ) ; // Aborted, not completed
384+ expect ( executionOrder ) . not . toContain ( "Scenario 3 end" ) ;
385+
386+ // Scenario 4 should never start
387+ expect ( executionOrder ) . not . toContain ( "Scenario 4 start" ) ;
388+
389+ // Check scenario results
390+ const results = summary . scenarios ;
391+ expect ( results . length ) . toBe ( 4 ) ;
392+
393+ // Find scenarios by name since order may vary in parallel execution
394+ const scenario1 = results . find ( ( r ) => r . metadata . name === "Scenario 1" ) ! ;
395+ const scenario2 = results . find ( ( r ) => r . metadata . name === "Scenario 2" ) ! ;
396+ const scenario3 = results . find ( ( r ) => r . metadata . name === "Scenario 3" ) ! ;
397+ const scenario4 = results . find ( ( r ) => r . metadata . name === "Scenario 4" ) ! ;
398+
399+ expect ( scenario1 . status ) . toBe ( "skipped" ) ;
400+ expect ( scenario2 . status ) . toBe ( "failed" ) ;
401+ expect ( scenario3 . status ) . toBe ( "skipped" ) ;
402+ expect ( scenario4 . status ) . toBe ( "skipped" ) ;
196403} ) ;
197404
198405Deno . test ( {
0 commit comments