@@ -244,6 +244,9 @@ class Workers extends EventEmitter {
244244 this . testGroups = [ ]
245245 this . config = config
246246 this . numberOfWorkersRequested = numberOfWorkers
247+ // Track emitted pass events to avoid double-counting duplicates from retries/race conditions
248+ this . _passedUids = new Set ( )
249+ this . _pendingPass = new Set ( )
247250
248251 createOutputDir ( config . testConfig )
249252 // Defer worker initialization until codecept is ready
@@ -252,7 +255,7 @@ class Workers extends EventEmitter {
252255 async _ensureInitialized ( ) {
253256 if ( ! this . codecept ) {
254257 this . codecept = await this . codeceptPromise
255- if ( this . numberOfWorkersRequested ) {
258+ if ( typeof this . numberOfWorkersRequested === 'number' && this . numberOfWorkersRequested > 0 ) {
256259 this . _initWorkers ( this . numberOfWorkersRequested , this . config )
257260 }
258261 }
@@ -298,6 +301,10 @@ class Workers extends EventEmitter {
298301 */
299302 spawn ( ) {
300303 const worker = new WorkerObject ( this . numberOfWorkers )
304+ // Default testRoot to the configured testConfig location for manual spawns
305+ if ( this . config ?. testConfig ) {
306+ worker . setTestRoot ( this . config . testConfig )
307+ }
301308 this . workers . push ( worker )
302309 this . numberOfWorkers += 1
303310 return worker
@@ -307,7 +314,9 @@ class Workers extends EventEmitter {
307314 * @param {Number } numberOfWorkers
308315 */
309316 createGroupsOfTests ( numberOfWorkers ) {
310- const files = this . codecept . testFiles
317+ // If Codecept isn't initialized yet, return empty groups as a safe fallback
318+ if ( ! this . codecept ) return populateGroups ( numberOfWorkers )
319+ const files = this . codecept . testFiles
311320 const mocha = Container . mocha ( )
312321 mocha . files = files
313322 mocha . loadFiles ( )
@@ -329,7 +338,9 @@ class Workers extends EventEmitter {
329338 * @param {Number } numberOfWorkers
330339 */
331340 createGroupsOfSuites ( numberOfWorkers ) {
332- const files = this . codecept . testFiles
341+ // If Codecept isn't initialized yet, return empty groups as a safe fallback
342+ if ( ! this . codecept ) return populateGroups ( numberOfWorkers )
343+ const files = this . codecept . testFiles
333344 const groups = populateGroups ( numberOfWorkers )
334345
335346 const mocha = Container . mocha ( )
@@ -423,15 +434,25 @@ class Workers extends EventEmitter {
423434 this . emit ( event . test . started , deserializeTest ( message . data ) )
424435 break
425436 case event . test . failed :
437+ if ( message ?. data ?. uid ) this . _pendingPass . delete ( message . data . uid )
426438 this . emit ( event . test . failed , deserializeTest ( message . data ) )
427439 break
428440 case event . test . passed :
429- this . emit ( event . test . passed , deserializeTest ( message . data ) )
441+ // Buffer pass until finished to avoid counting tests that will fail later
442+ if ( message ?. data ?. uid ) this . _pendingPass . add ( message . data . uid )
430443 break
431444 case event . test . skipped :
432445 this . emit ( event . test . skipped , deserializeTest ( message . data ) )
433446 break
434447 case event . test . finished :
448+ // Emit a deduped 'passed' only if it was pending and no error provided
449+ if ( message ?. data ?. uid && ! message ?. data ?. err ) {
450+ if ( ! this . _passedUids . has ( message . data . uid ) && this . _pendingPass . has ( message . data . uid ) ) {
451+ this . _passedUids . add ( message . data . uid )
452+ this . emit ( event . test . passed , deserializeTest ( message . data ) )
453+ }
454+ }
455+ if ( message ?. data ?. uid ) this . _pendingPass . delete ( message . data . uid )
435456 this . emit ( event . test . finished , deserializeTest ( message . data ) )
436457 break
437458 case event . test . after :
0 commit comments