@@ -3,6 +3,7 @@ const mkdirp = require('mkdirp')
33const { Worker } = require ( 'worker_threads' )
44const { EventEmitter } = require ( 'events' )
55const ms = require ( 'ms' )
6+ const colors = require ( 'chalk' )
67const Codecept = require ( './codecept' )
78const MochaFactory = require ( './mocha/factory' )
89const Container = require ( './container' )
@@ -507,7 +508,10 @@ class Workers extends EventEmitter {
507508
508509 if ( message . data . tests ) {
509510 message . data . tests . forEach ( test => {
510- Container . result ( ) . addTest ( deserializeTest ( test ) )
511+ const deserializedTest = deserializeTest ( test )
512+ // Add worker index to test for grouping
513+ deserializedTest . workerIndex = message . workerIndex
514+ Container . result ( ) . addTest ( deserializedTest )
511515 } )
512516 }
513517
@@ -516,25 +520,39 @@ class Workers extends EventEmitter {
516520 this . emit ( event . suite . before , deserializeSuite ( message . data ) )
517521 break
518522 case event . test . before :
519- this . emit ( event . test . before , deserializeTest ( message . data ) )
523+ const testBefore = deserializeTest ( message . data )
524+ testBefore . workerIndex = message . workerIndex
525+ this . emit ( event . test . before , testBefore )
520526 break
521527 case event . test . started :
522- this . emit ( event . test . started , deserializeTest ( message . data ) )
528+ const testStarted = deserializeTest ( message . data )
529+ testStarted . workerIndex = message . workerIndex
530+ this . emit ( event . test . started , testStarted )
523531 break
524532 case event . test . failed :
525- this . emit ( event . test . failed , deserializeTest ( message . data ) )
533+ const testFailed = deserializeTest ( message . data )
534+ testFailed . workerIndex = message . workerIndex
535+ this . emit ( event . test . failed , testFailed )
526536 break
527537 case event . test . passed :
528- this . emit ( event . test . passed , deserializeTest ( message . data ) )
538+ const testPassed = deserializeTest ( message . data )
539+ testPassed . workerIndex = message . workerIndex
540+ this . emit ( event . test . passed , testPassed )
529541 break
530542 case event . test . skipped :
531- this . emit ( event . test . skipped , deserializeTest ( message . data ) )
543+ const testSkipped = deserializeTest ( message . data )
544+ testSkipped . workerIndex = message . workerIndex
545+ this . emit ( event . test . skipped , testSkipped )
532546 break
533547 case event . test . finished :
534- this . emit ( event . test . finished , deserializeTest ( message . data ) )
548+ const testFinished = deserializeTest ( message . data )
549+ testFinished . workerIndex = message . workerIndex
550+ this . emit ( event . test . finished , testFinished )
535551 break
536552 case event . test . after :
537- this . emit ( event . test . after , deserializeTest ( message . data ) )
553+ const testAfter = deserializeTest ( message . data )
554+ testAfter . workerIndex = message . workerIndex
555+ this . emit ( event . test . after , testAfter )
538556 break
539557 case event . step . finished :
540558 this . emit ( event . step . finished , message . data )
@@ -591,6 +609,10 @@ class Workers extends EventEmitter {
591609 output . process ( null )
592610 output . print ( )
593611
612+ // Group tests by feature for better organization
613+ const testsByFeature = this . _groupTestsByFeature ( result . tests )
614+ const testsByWorker = this . _groupTestsByWorker ( result . tests )
615+
594616 this . failuresLog = result . failures
595617 . filter ( log => log . length && typeof log [ 1 ] === 'number' )
596618 // mocha/lib/reporters/base.js
@@ -602,10 +624,133 @@ class Workers extends EventEmitter {
602624 this . failuresLog . forEach ( log => output . print ( ...log ) )
603625 }
604626
605- output . result ( result . stats . passes , result . stats . failures , result . stats . pending , ms ( result . duration ) , result . stats . failedHooks )
627+ // Print enhanced summary with worker info and feature grouping
628+ this . _printEnhancedWorkersSummary ( result , testsByFeature , testsByWorker )
606629
607630 process . env . RUNS_WITH_WORKERS = 'false'
608631 }
632+
633+ /**
634+ * Groups tests by their feature/suite name
635+ * @private
636+ */
637+ _groupTestsByFeature ( tests ) {
638+ const groups = { }
639+ tests . forEach ( test => {
640+ const featureName = test . parent ?. title || test . suite || 'Ungrouped Tests'
641+ if ( ! groups [ featureName ] ) {
642+ groups [ featureName ] = {
643+ passed : 0 ,
644+ failed : 0 ,
645+ skipped : 0 ,
646+ tests : [ ] ,
647+ }
648+ }
649+ groups [ featureName ] . tests . push ( test )
650+ if ( test . state === 'passed' ) groups [ featureName ] . passed ++
651+ else if ( test . state === 'failed' ) groups [ featureName ] . failed ++
652+ else if ( test . state === 'skipped' || test . state === 'pending' ) groups [ featureName ] . skipped ++
653+ } )
654+ return groups
655+ }
656+
657+ /**
658+ * Groups tests by worker
659+ * @private
660+ */
661+ _groupTestsByWorker ( tests ) {
662+ const groups = { }
663+ tests . forEach ( test => {
664+ const workerIndex = test . workerIndex || 'unknown'
665+ if ( ! groups [ workerIndex ] ) {
666+ groups [ workerIndex ] = {
667+ passed : 0 ,
668+ failed : 0 ,
669+ skipped : 0 ,
670+ tests : [ ] ,
671+ }
672+ }
673+ groups [ workerIndex ] . tests . push ( test )
674+ if ( test . state === 'passed' ) groups [ workerIndex ] . passed ++
675+ else if ( test . state === 'failed' ) groups [ workerIndex ] . failed ++
676+ else if ( test . state === 'skipped' || test . state === 'pending' ) groups [ workerIndex ] . skipped ++
677+ } )
678+ return groups
679+ }
680+
681+ /**
682+ * Prints enhanced summary with worker info, feature grouping and metrics
683+ * @private
684+ */
685+ _printEnhancedWorkersSummary ( result , testsByFeature , testsByWorker ) {
686+ // Calculate accurate stats from actual test objects instead of relying on Container stats
687+ // which may not aggregate correctly in all scenarios
688+ let actualPassed = 0
689+ let actualFailed = 0
690+ let actualPending = 0
691+
692+ result . tests . forEach ( test => {
693+ if ( test . state === 'passed' ) actualPassed ++
694+ else if ( test . state === 'failed' ) actualFailed ++
695+ else if ( test . state === 'pending' || test . state === 'skipped' ) actualPending ++
696+ } )
697+
698+ const actualTotal = result . tests . length
699+ const actualFailedHooks = result . stats ?. failedHooks || 0
700+
701+ // Use result.duration (wall-clock time) instead of stats.duration (which gets overwritten)
702+ const duration = result . duration || result . stats ?. duration || 0
703+ output . print ( )
704+ output . print ( output . styles . bold ( '-- TEST SUMMARY:' ) )
705+ output . print ( )
706+
707+ // Print tests grouped by feature
708+ if ( Object . keys ( testsByFeature ) . length > 0 ) {
709+ output . print ( output . styles . bold ( 'Results by Feature:' ) )
710+ Object . entries ( testsByFeature ) . forEach ( ( [ featureName , data ] ) => {
711+ const totalTests = data . tests . length
712+ const passRate = totalTests > 0 ? Math . round ( ( data . passed / totalTests ) * 100 ) : 0
713+ const status = data . failed > 0 ? output . styles . error ( '✗' ) : output . styles . success ( '✓' )
714+ output . print ( ` ${ status } ${ output . styles . bold ( featureName ) } ` )
715+ output . print ( ` Passed: ${ output . styles . success ( data . passed ) } | Failed: ${ output . styles . error ( data . failed ) } | Skipped: ${ data . skipped } | Pass Rate: ${ passRate } %` )
716+ } )
717+ output . print ( )
718+ }
719+
720+ // Print worker statistics
721+ if ( Object . keys ( testsByWorker ) . length > 1 ) {
722+ output . print ( output . styles . bold ( 'Results by Worker:' ) )
723+ Object . entries ( testsByWorker )
724+ . sort ( ( a , b ) => parseInt ( a [ 0 ] ) - parseInt ( b [ 0 ] ) )
725+ . forEach ( ( [ workerIndex , data ] ) => {
726+ const totalTests = data . tests . length
727+ const passRate = totalTests > 0 ? Math . round ( ( data . passed / totalTests ) * 100 ) : 0
728+ const status = data . failed > 0 ? output . styles . error ( '✗' ) : output . styles . success ( '✓' )
729+ output . print ( ` ${ status } Worker ${ workerIndex } ` )
730+ output . print ( ` Tests: ${ totalTests } | Passed: ${ output . styles . success ( data . passed ) } | Failed: ${ output . styles . error ( data . failed ) } | Pass Rate: ${ passRate } %` )
731+ } )
732+ output . print ( )
733+ }
734+
735+ // Print overall metrics using accurate counts from test objects
736+ output . print ( output . styles . bold ( 'Overall Metrics:' ) )
737+ const passRate = actualTotal > 0 ? Math . round ( ( actualPassed / actualTotal ) * 100 ) : 0
738+ const failRate = actualTotal > 0 ? Math . round ( ( actualFailed / actualTotal ) * 100 ) : 0
739+ output . print ( ` Total Tests: ${ actualTotal } ` )
740+ output . print ( ` Passed: ${ output . styles . success ( actualPassed ) } (${ passRate } %)` )
741+ output . print ( ` Failed: ${ output . styles . error ( actualFailed ) } (${ failRate } %)` )
742+ output . print ( ` Skipped: ${ actualPending } ` )
743+ if ( actualFailedHooks > 0 ) {
744+ output . print ( ` Failed Hooks: ${ output . styles . error ( actualFailedHooks ) } ` )
745+ }
746+ output . print ( ` Duration: ${ ms ( duration ) } ` )
747+ output . print ( ` Workers: ${ this . numberOfWorkers } ` )
748+ output . print ( ` Strategy: ${ this . isPoolMode ? 'pool' : 'test/suite' } ` )
749+ output . print ( )
750+
751+ // Print the classic result line with accurate counts
752+ output . result ( actualPassed , actualFailed , actualPending , ms ( duration ) , actualFailedHooks )
753+ }
609754}
610755
611756module . exports = Workers
0 commit comments