@@ -244,6 +244,9 @@ class Workers extends EventEmitter {
244
244
this . testGroups = [ ]
245
245
this . config = config
246
246
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 ( )
247
250
248
251
createOutputDir ( config . testConfig )
249
252
// Defer worker initialization until codecept is ready
@@ -252,7 +255,7 @@ class Workers extends EventEmitter {
252
255
async _ensureInitialized ( ) {
253
256
if ( ! this . codecept ) {
254
257
this . codecept = await this . codeceptPromise
255
- if ( this . numberOfWorkersRequested ) {
258
+ if ( typeof this . numberOfWorkersRequested === 'number' && this . numberOfWorkersRequested > 0 ) {
256
259
this . _initWorkers ( this . numberOfWorkersRequested , this . config )
257
260
}
258
261
}
@@ -298,6 +301,10 @@ class Workers extends EventEmitter {
298
301
*/
299
302
spawn ( ) {
300
303
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
+ }
301
308
this . workers . push ( worker )
302
309
this . numberOfWorkers += 1
303
310
return worker
@@ -307,7 +314,9 @@ class Workers extends EventEmitter {
307
314
* @param {Number } numberOfWorkers
308
315
*/
309
316
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
311
320
const mocha = Container . mocha ( )
312
321
mocha . files = files
313
322
mocha . loadFiles ( )
@@ -329,7 +338,9 @@ class Workers extends EventEmitter {
329
338
* @param {Number } numberOfWorkers
330
339
*/
331
340
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
333
344
const groups = populateGroups ( numberOfWorkers )
334
345
335
346
const mocha = Container . mocha ( )
@@ -423,15 +434,25 @@ class Workers extends EventEmitter {
423
434
this . emit ( event . test . started , deserializeTest ( message . data ) )
424
435
break
425
436
case event . test . failed :
437
+ if ( message ?. data ?. uid ) this . _pendingPass . delete ( message . data . uid )
426
438
this . emit ( event . test . failed , deserializeTest ( message . data ) )
427
439
break
428
440
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 )
430
443
break
431
444
case event . test . skipped :
432
445
this . emit ( event . test . skipped , deserializeTest ( message . data ) )
433
446
break
434
447
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 )
435
456
this . emit ( event . test . finished , deserializeTest ( message . data ) )
436
457
break
437
458
case event . test . after :
0 commit comments