@@ -257,16 +257,6 @@ class FileTest extends Test {
257257 item . data . details . error = deserializeError ( item . data . details . error ) ;
258258 }
259259
260- if ( item . type === 'test:fail' && this . root . harness . bail && ! this . root . harness . bailedOut ) {
261- // Trigger bail if enabled and this is the first failure
262- this . root . harness . bailedOut = true ;
263- this . reporter [ kEmitMessage ] ( 'test:bail' , {
264- __proto__ : null ,
265- file : this . name ,
266- test : item . data ,
267- } ) ;
268- }
269-
270260 if ( item . type === 'test:pass' || item . type === 'test:fail' ) {
271261 item . data . testNumber = isTopLevel ? ( this . root . harness . counters . topLevel + 1 ) : item . data . testNumber ;
272262 countCompletedTest ( {
@@ -776,15 +766,28 @@ function run(options = kEmptyObject) {
776766 if ( watch ) {
777767 throw new ERR_INVALID_ARG_VALUE ( 'options.bail' , watch , 'bail not supported with watch mode' ) ;
778768 }
779- if ( isolation === 'none' ) {
780- throw new ERR_INVALID_ARG_VALUE ( 'options.bail' , isolation , 'bail not supported with \'none\' isolation' ) ;
781- }
782769 }
783770
784771 let teardown ;
785772 let postRun ;
786773 let filesWatcher ;
787774 let runFiles ;
775+
776+ if ( bail ) {
777+ root . reporter . on ( 'test:fail' , ( item ) => {
778+ if ( root . harness . bail && ! root . harness . bailedOut ) {
779+ root . harness . bailedOut = true ;
780+ queueMicrotask ( ( ) => {
781+ root . reporter [ kEmitMessage ] ( 'test:bail' , {
782+ __proto__ : null ,
783+ file : item . name ,
784+ test : item . data ,
785+ } ) ;
786+ } ) ;
787+ }
788+ } ) ;
789+ }
790+
788791 const opts = {
789792 __proto__ : null ,
790793 root,
@@ -854,7 +857,6 @@ function run(options = kEmptyObject) {
854857 }
855858 }
856859 } ;
857-
858860 } else {
859861 runFiles = ( ) => {
860862 root . harness . bootstrapPromise = null ;
@@ -878,17 +880,21 @@ function run(options = kEmptyObject) {
878880 return subtest ;
879881 } ;
880882 } else {
883+
881884 runFiles = async ( ) => {
882- const { promise, resolve : finishBootstrap } = PromiseWithResolvers ( ) ;
885+ // Ensure global bootstrap is completed before running files, then allow
886+ // subtests to start immediately so bail can stop further file imports.
887+ if ( root . harness . bootstrapPromise ) {
888+ await root . harness . bootstrapPromise ;
889+ root . harness . bootstrapPromise = null ;
890+ }
891+ root . harness . buildPromise = null ;
883892
884893 await root . runInAsyncScope ( async ( ) => {
885894 const parentURL = pathToFileURL ( cwd + sep ) . href ;
886895 const cascadedLoader = esmLoader . getOrInitializeCascadedLoader ( ) ;
887896 let topLevelTestCount = 0 ;
888-
889- root . harness . bootstrapPromise = root . harness . bootstrapPromise ?
890- SafePromiseAllReturnVoid ( [ root . harness . bootstrapPromise , promise ] ) :
891- promise ;
897+ let failedCount = root . harness . counters . failed ;
892898
893899 // We need to setup the user modules in the test runner if we are running with
894900 // --test-isolation=none and --test in order to avoid loading the user modules
@@ -906,7 +912,12 @@ function run(options = kEmptyObject) {
906912 }
907913
908914 for ( let i = 0 ; i < testFiles . length ; ++ i ) {
915+ if ( root . harness . bail && root . harness . bailedOut ) {
916+ break ;
917+ }
918+
909919 const testFile = testFiles [ i ] ;
920+ const testFilePath = resolve ( cwd , testFile ) ;
910921 const fileURL = pathToFileURL ( resolve ( cwd , testFile ) ) ;
911922 const parent = i === 0 ? undefined : parentURL ;
912923 let threw = false ;
@@ -933,22 +944,54 @@ function run(options = kEmptyObject) {
933944 if ( threw ) {
934945 subtest . fail ( importError ) ;
935946 }
936- startSubtestAfterBootstrap ( subtest ) ;
947+ startSubtestAfterBootstrap ( subtest ) ;
937948 }
938949
939950 topLevelTestCount = root . subtests . length ;
951+
952+ // Run pending subtests created so far; if bail triggers, stop loading more files.
953+ await root . processPendingSubtests ( ) ;
954+ // Allow reporter events (including bail) to flush before continuing.
955+ await new Promise ( ( resolve ) => setImmediate ( resolve ) ) ;
956+ // Ensure any subtest chains spawned by this file are finished.
957+ if ( root . subtestsPromise ?. promise ) {
958+ await root . subtestsPromise . promise ;
959+ }
960+
961+ if ( root . harness . bail ) {
962+ if ( root . harness . counters . failed > failedCount ) {
963+ root . harness . bailedOut = true ;
964+ } else {
965+ const fileFailed = ArrayPrototypeSome (
966+ root . subtests ,
967+ ( t ) => t . loc ?. file === testFilePath && t . error != null ,
968+ ) ;
969+ if ( fileFailed ) {
970+ root . harness . bailedOut = true ;
971+ }
972+ }
973+ }
974+
975+ if ( root . harness . bail && root . harness . bailedOut ) {
976+ break ;
977+ }
978+
979+ failedCount = root . harness . counters . failed ;
940980 }
941981 } ) ;
942982
943983 debug ( 'beginning test execution' ) ;
944984 root . entryFile = null ;
945- finishBootstrap ( ) ;
985+ if ( root . harness . bail && root . harness . bailedOut ) {
986+ return ;
987+ }
946988 return root . processPendingSubtests ( ) ;
947989 } ;
948990 }
949991 }
950992
951993 const runChain = async ( ) => {
994+
952995 if ( root . harness ?. bootstrapPromise ) {
953996 await root . harness . bootstrapPromise ;
954997 }
0 commit comments