codeceptjs 4.0.0-beta.5 → 4.0.0-beta.7.esm-aria

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/README.md +0 -45
  2. package/bin/codecept.js +46 -57
  3. package/lib/actor.js +15 -11
  4. package/lib/ai.js +6 -5
  5. package/lib/assert/empty.js +9 -8
  6. package/lib/assert/equal.js +15 -17
  7. package/lib/assert/error.js +2 -2
  8. package/lib/assert/include.js +9 -11
  9. package/lib/assert/throws.js +1 -1
  10. package/lib/assert/truth.js +8 -5
  11. package/lib/assert.js +18 -18
  12. package/lib/codecept.js +66 -107
  13. package/lib/colorUtils.js +48 -50
  14. package/lib/command/check.js +32 -27
  15. package/lib/command/configMigrate.js +11 -10
  16. package/lib/command/definitions.js +16 -10
  17. package/lib/command/dryRun.js +16 -16
  18. package/lib/command/generate.js +29 -26
  19. package/lib/command/gherkin/init.js +36 -38
  20. package/lib/command/gherkin/snippets.js +14 -14
  21. package/lib/command/gherkin/steps.js +21 -18
  22. package/lib/command/info.js +8 -8
  23. package/lib/command/init.js +34 -31
  24. package/lib/command/interactive.js +11 -10
  25. package/lib/command/list.js +10 -9
  26. package/lib/command/run-multiple/chunk.js +5 -5
  27. package/lib/command/run-multiple/collection.js +5 -5
  28. package/lib/command/run-multiple/run.js +3 -3
  29. package/lib/command/run-multiple.js +16 -13
  30. package/lib/command/run-rerun.js +6 -7
  31. package/lib/command/run-workers.js +10 -24
  32. package/lib/command/run.js +8 -8
  33. package/lib/command/utils.js +20 -18
  34. package/lib/command/workers/runTests.js +117 -269
  35. package/lib/config.js +111 -49
  36. package/lib/container.js +299 -102
  37. package/lib/data/context.js +6 -5
  38. package/lib/data/dataScenarioConfig.js +1 -1
  39. package/lib/data/dataTableArgument.js +1 -1
  40. package/lib/data/table.js +1 -1
  41. package/lib/effects.js +94 -10
  42. package/lib/els.js +11 -9
  43. package/lib/event.js +11 -10
  44. package/lib/globals.js +141 -0
  45. package/lib/heal.js +12 -12
  46. package/lib/helper/AI.js +1 -1
  47. package/lib/helper/ApiDataFactory.js +16 -13
  48. package/lib/helper/FileSystem.js +32 -12
  49. package/lib/helper/GraphQL.js +1 -1
  50. package/lib/helper/GraphQLDataFactory.js +1 -1
  51. package/lib/helper/JSONResponse.js +19 -30
  52. package/lib/helper/Mochawesome.js +9 -28
  53. package/lib/helper/Playwright.js +668 -265
  54. package/lib/helper/Puppeteer.js +284 -169
  55. package/lib/helper/REST.js +29 -12
  56. package/lib/helper/WebDriver.js +192 -71
  57. package/lib/helper/errors/ConnectionRefused.js +6 -6
  58. package/lib/helper/errors/ElementAssertion.js +11 -16
  59. package/lib/helper/errors/ElementNotFound.js +5 -9
  60. package/lib/helper/errors/RemoteBrowserConnectionRefused.js +5 -5
  61. package/lib/helper/extras/Console.js +11 -11
  62. package/lib/helper/extras/PlaywrightLocator.js +110 -0
  63. package/lib/helper/extras/PlaywrightPropEngine.js +18 -18
  64. package/lib/helper/extras/PlaywrightRestartOpts.js +23 -23
  65. package/lib/helper/extras/Popup.js +1 -1
  66. package/lib/helper/extras/React.js +29 -30
  67. package/lib/helper/network/actions.js +33 -48
  68. package/lib/helper/network/utils.js +76 -83
  69. package/lib/helper/scripts/blurElement.js +6 -6
  70. package/lib/helper/scripts/focusElement.js +6 -6
  71. package/lib/helper/scripts/highlightElement.js +9 -9
  72. package/lib/helper/scripts/isElementClickable.js +34 -34
  73. package/lib/helper.js +2 -1
  74. package/lib/history.js +23 -20
  75. package/lib/hooks.js +10 -10
  76. package/lib/html.js +90 -100
  77. package/lib/index.js +48 -21
  78. package/lib/listener/config.js +8 -9
  79. package/lib/listener/emptyRun.js +6 -7
  80. package/lib/listener/exit.js +4 -3
  81. package/lib/listener/globalRetry.js +5 -5
  82. package/lib/listener/globalTimeout.js +11 -10
  83. package/lib/listener/helpers.js +33 -14
  84. package/lib/listener/mocha.js +3 -4
  85. package/lib/listener/result.js +4 -5
  86. package/lib/listener/steps.js +7 -18
  87. package/lib/listener/store.js +3 -3
  88. package/lib/locator.js +213 -192
  89. package/lib/mocha/asyncWrapper.js +108 -75
  90. package/lib/mocha/bdd.js +99 -13
  91. package/lib/mocha/cli.js +60 -27
  92. package/lib/mocha/factory.js +75 -19
  93. package/lib/mocha/featureConfig.js +1 -1
  94. package/lib/mocha/gherkin.js +57 -25
  95. package/lib/mocha/hooks.js +12 -3
  96. package/lib/mocha/index.js +13 -4
  97. package/lib/mocha/inject.js +22 -5
  98. package/lib/mocha/scenarioConfig.js +2 -2
  99. package/lib/mocha/suite.js +9 -2
  100. package/lib/mocha/test.js +10 -13
  101. package/lib/mocha/ui.js +28 -31
  102. package/lib/output.js +11 -9
  103. package/lib/parser.js +44 -44
  104. package/lib/pause.js +15 -16
  105. package/lib/plugin/analyze.js +19 -12
  106. package/lib/plugin/auth.js +20 -21
  107. package/lib/plugin/autoDelay.js +12 -8
  108. package/lib/plugin/coverage.js +12 -8
  109. package/lib/plugin/customLocator.js +3 -3
  110. package/lib/plugin/customReporter.js +3 -2
  111. package/lib/plugin/heal.js +14 -9
  112. package/lib/plugin/pageInfo.js +10 -10
  113. package/lib/plugin/pauseOnFail.js +4 -3
  114. package/lib/plugin/retryFailedStep.js +47 -5
  115. package/lib/plugin/screenshotOnFail.js +75 -37
  116. package/lib/plugin/stepByStepReport.js +14 -14
  117. package/lib/plugin/stepTimeout.js +4 -3
  118. package/lib/plugin/subtitles.js +6 -5
  119. package/lib/recorder.js +33 -23
  120. package/lib/rerun.js +69 -26
  121. package/lib/result.js +4 -4
  122. package/lib/secret.js +18 -17
  123. package/lib/session.js +95 -89
  124. package/lib/step/base.js +6 -6
  125. package/lib/step/config.js +1 -1
  126. package/lib/step/func.js +3 -3
  127. package/lib/step/helper.js +3 -3
  128. package/lib/step/meta.js +4 -4
  129. package/lib/step/record.js +11 -11
  130. package/lib/step/retry.js +3 -3
  131. package/lib/step/section.js +3 -3
  132. package/lib/step.js +7 -10
  133. package/lib/steps.js +9 -5
  134. package/lib/store.js +1 -1
  135. package/lib/timeout.js +1 -7
  136. package/lib/transform.js +8 -8
  137. package/lib/translation.js +32 -18
  138. package/lib/utils.js +68 -97
  139. package/lib/workerStorage.js +16 -17
  140. package/lib/workers.js +145 -171
  141. package/package.json +58 -55
  142. package/translations/de-DE.js +2 -2
  143. package/translations/fr-FR.js +2 -2
  144. package/translations/index.js +23 -10
  145. package/translations/it-IT.js +2 -2
  146. package/translations/ja-JP.js +2 -2
  147. package/translations/nl-NL.js +2 -2
  148. package/translations/pl-PL.js +2 -2
  149. package/translations/pt-BR.js +2 -2
  150. package/translations/ru-RU.js +2 -2
  151. package/translations/utils.js +4 -3
  152. package/translations/zh-CN.js +2 -2
  153. package/translations/zh-TW.js +2 -2
  154. package/typings/index.d.ts +7 -18
  155. package/typings/promiseBasedTypes.d.ts +3769 -5450
  156. package/typings/types.d.ts +3953 -5778
  157. package/bin/test-server.js +0 -53
  158. package/lib/element/WebElement.js +0 -327
  159. package/lib/helper/Nightmare.js +0 -1486
  160. package/lib/helper/Protractor.js +0 -1840
  161. package/lib/helper/TestCafe.js +0 -1391
  162. package/lib/helper/clientscripts/nightmare.js +0 -213
  163. package/lib/helper/extras/PlaywrightReactVueLocator.js +0 -43
  164. package/lib/helper/testcafe/testControllerHolder.js +0 -42
  165. package/lib/helper/testcafe/testcafe-utils.js +0 -61
  166. package/lib/listener/retryEnhancer.js +0 -85
  167. package/lib/plugin/allure.js +0 -15
  168. package/lib/plugin/autoLogin.js +0 -5
  169. package/lib/plugin/commentStep.js +0 -141
  170. package/lib/plugin/eachElement.js +0 -127
  171. package/lib/plugin/fakerTransform.js +0 -49
  172. package/lib/plugin/htmlReporter.js +0 -1947
  173. package/lib/plugin/retryTo.js +0 -16
  174. package/lib/plugin/selenoid.js +0 -364
  175. package/lib/plugin/standardActingHelpers.js +0 -6
  176. package/lib/plugin/tryTo.js +0 -16
  177. package/lib/plugin/wdio.js +0 -247
  178. package/lib/test-server.js +0 -323
  179. package/lib/within.js +0 -90
@@ -1,20 +1,19 @@
1
- const { isMainThread, parentPort } = require('worker_threads');
1
+ import { isMainThread, parentPort } from 'worker_threads'
2
2
 
3
- const workerObjects = {};
4
- const shareEvent = 'share';
3
+ const workerObjects = {}
4
+ const shareEvent = 'share'
5
5
 
6
- const invokeWorkerListeners = (workerObj) => {
7
- const { threadId } = workerObj;
8
- workerObj.on('message', (messageData) => {
6
+ const invokeWorkerListeners = workerObj => {
7
+ const { threadId } = workerObj
8
+ workerObj.on('message', messageData => {
9
9
  if (messageData.event === shareEvent) {
10
- const Container = require('./container');
11
- Container.share(messageData.data);
10
+ share(messageData.data)
12
11
  }
13
- });
12
+ })
14
13
  workerObj.on('exit', () => {
15
- delete workerObjects[threadId];
16
- });
17
- };
14
+ delete workerObjects[threadId]
15
+ })
16
+ }
18
17
 
19
18
  class WorkerStorage {
20
19
  /**
@@ -24,8 +23,8 @@ class WorkerStorage {
24
23
  * @param {Worker} workerObj
25
24
  */
26
25
  static addWorker(workerObj) {
27
- workerObjects[workerObj.threadId] = workerObj;
28
- invokeWorkerListeners(workerObj);
26
+ workerObjects[workerObj.threadId] = workerObj
27
+ invokeWorkerListeners(workerObj)
29
28
  }
30
29
 
31
30
  /**
@@ -36,12 +35,12 @@ class WorkerStorage {
36
35
  static share(data) {
37
36
  if (isMainThread) {
38
37
  for (const workerObj of Object.values(workerObjects)) {
39
- workerObj.postMessage({ data });
38
+ workerObj.postMessage({ data })
40
39
  }
41
40
  } else {
42
- parentPort.postMessage({ data, event: shareEvent });
41
+ parentPort.postMessage({ data, event: shareEvent })
43
42
  }
44
43
  }
45
44
  }
46
45
 
47
- module.exports = WorkerStorage;
46
+ export default WorkerStorage
package/lib/workers.js CHANGED
@@ -1,36 +1,41 @@
1
- const path = require('path')
2
- const mkdirp = require('mkdirp')
3
- const { Worker } = require('worker_threads')
4
- const { EventEmitter } = require('events')
5
- const ms = require('ms')
6
- const Codecept = require('./codecept')
7
- const MochaFactory = require('./mocha/factory')
8
- const Container = require('./container')
9
- const { getTestRoot } = require('./command/utils')
10
- const { isFunction, fileExists } = require('./utils')
11
- const { replaceValueDeep, deepClone } = require('./utils')
12
- const mainConfig = require('./config')
13
- const output = require('./output')
14
- const event = require('./event')
15
- const { deserializeTest } = require('./mocha/test')
16
- const { deserializeSuite } = require('./mocha/suite')
17
- const recorder = require('./recorder')
18
- const runHook = require('./hooks')
19
- const WorkerStorage = require('./workerStorage')
20
- const collection = require('./command/run-multiple/collection')
1
+ import path from 'path'
2
+ import { fileURLToPath } from 'url'
3
+ import { dirname } from 'path'
4
+ import { mkdirp } from 'mkdirp'
5
+ import { Worker } from 'worker_threads'
6
+ import { EventEmitter } from 'events'
7
+ import ms from 'ms'
8
+
9
+ const __filename = fileURLToPath(import.meta.url)
10
+ const __dirname = dirname(__filename)
11
+ import Codecept from './codecept.js'
12
+ import MochaFactory from './mocha/factory.js'
13
+ import Container from './container.js'
14
+ import { getTestRoot } from './command/utils.js'
15
+ import { isFunction, fileExists, replaceValueDeep, deepClone } from './utils.js'
16
+ import mainConfig from './config.js'
17
+ import output from './output.js'
18
+ import event from './event.js'
19
+ import { deserializeTest } from './mocha/test.js'
20
+ import { deserializeSuite } from './mocha/suite.js'
21
+ import recorder from './recorder.js'
22
+ import runHook from './hooks.js'
23
+ import WorkerStorage from './workerStorage.js'
24
+ import { createRuns } from './command/run-multiple/collection.js'
21
25
 
22
26
  const pathToWorker = path.join(__dirname, 'command', 'workers', 'runTests.js')
23
27
 
24
- const initializeCodecept = (configPath, options = {}) => {
25
- const codecept = new Codecept(mainConfig.load(configPath || '.'), options)
26
- codecept.init(getTestRoot(configPath))
28
+ const initializeCodecept = async (configPath, options = {}) => {
29
+ const config = await mainConfig.load(configPath || '.')
30
+ const codecept = new Codecept(config, options)
31
+ await codecept.init(getTestRoot(configPath))
27
32
  codecept.loadTests()
28
33
 
29
34
  return codecept
30
35
  }
31
36
 
32
- const createOutputDir = configPath => {
33
- const config = mainConfig.load(configPath || '.')
37
+ const createOutputDir = async configPath => {
38
+ const config = await mainConfig.load(configPath || '.')
34
39
  const testRoot = getTestRoot(configPath)
35
40
  const outputDir = path.isAbsolute(config.output) ? config.output : path.join(testRoot, config.output)
36
41
 
@@ -49,14 +54,13 @@ const populateGroups = numberOfWorkers => {
49
54
  return groups
50
55
  }
51
56
 
52
- const createWorker = (workerObject, isPoolMode = false) => {
57
+ const createWorker = workerObject => {
53
58
  const worker = new Worker(pathToWorker, {
54
59
  workerData: {
55
60
  options: simplifyObject(workerObject.options),
56
61
  tests: workerObject.tests,
57
62
  testRoot: workerObject.testRoot,
58
63
  workerIndex: workerObject.workerIndex + 1,
59
- poolMode: isPoolMode,
60
64
  },
61
65
  })
62
66
  worker.on('error', err => output.error(`Worker Error: ${err.stack}`))
@@ -99,7 +103,7 @@ const createWorkerObjects = (testGroups, config, testRoot, options, selectedRuns
99
103
  currentMochaJunitReporterFile = config.mocha.reporterOptions['mocha-junit-reporter'].options.mochaFile
100
104
  }
101
105
 
102
- collection.createRuns(selectedRuns, config).forEach(worker => {
106
+ createRuns(selectedRuns, config).forEach(worker => {
103
107
  const separator = path.sep
104
108
  const _config = { ...config }
105
109
  let workerName = worker.name.replace(':', '_')
@@ -231,25 +235,43 @@ class Workers extends EventEmitter {
231
235
  constructor(numberOfWorkers, config = { by: 'test' }) {
232
236
  super()
233
237
  this.setMaxListeners(50)
234
- this.codecept = initializeCodecept(config.testConfig, config.options)
235
- this.options = config.options || {}
238
+ this.codeceptPromise = initializeCodecept(config.testConfig, config.options)
239
+ this.codecept = null
236
240
  this.errors = []
237
241
  this.numberOfWorkers = 0
238
242
  this.closedWorkers = 0
239
243
  this.workers = []
240
244
  this.testGroups = []
241
- this.testPool = []
242
- this.testPoolInitialized = false
243
- this.isPoolMode = config.by === 'pool'
244
- this.activeWorkers = new Map()
245
- this.maxWorkers = numberOfWorkers // Track original worker count for pool mode
245
+ this.config = config
246
+ this.numberOfWorkersRequested = numberOfWorkers
247
+ // Track emitted pass events to avoid double-counting duplicates from retries/race conditions
248
+ this._passedUids = new Set()
246
249
 
247
250
  createOutputDir(config.testConfig)
248
- if (numberOfWorkers) this._initWorkers(numberOfWorkers, config)
251
+ // Defer worker initialization until codecept is ready
252
+ }
253
+
254
+ async _ensureInitialized() {
255
+ if (!this.codecept) {
256
+ this.codecept = await this.codeceptPromise
257
+ // Initialize workers in these cases:
258
+ // 1. Positive number requested AND no manual workers pre-spawned
259
+ // 2. Function-based grouping (indicated by negative number) AND no manual workers pre-spawned
260
+ const shouldAutoInit = this.workers.length === 0 && (
261
+ (Number.isInteger(this.numberOfWorkersRequested) && this.numberOfWorkersRequested > 0) ||
262
+ (this.numberOfWorkersRequested < 0 && isFunction(this.config.by))
263
+ )
264
+
265
+ if (shouldAutoInit) {
266
+ this._initWorkers(this.numberOfWorkersRequested, this.config)
267
+ }
268
+ }
249
269
  }
250
270
 
251
271
  _initWorkers(numberOfWorkers, config) {
252
272
  this.splitTestsByGroups(numberOfWorkers, config)
273
+ // For function-based grouping, use the actual number of test groups created
274
+ const actualNumberOfWorkers = isFunction(config.by) ? this.testGroups.length : numberOfWorkers
253
275
  this.workers = createWorkerObjects(this.testGroups, this.codecept.config, config.testConfig, config.options, config.selectedRuns)
254
276
  this.numberOfWorkers = this.workers.length
255
277
  }
@@ -262,7 +284,6 @@ class Workers extends EventEmitter {
262
284
  *
263
285
  * - `suite`
264
286
  * - `test`
265
- * - `pool`
266
287
  * - function(numberOfWorkers)
267
288
  *
268
289
  * This method can be overridden for a better split.
@@ -278,11 +299,7 @@ class Workers extends EventEmitter {
278
299
  this.testGroups.push(convertToMochaTests(testGroup))
279
300
  }
280
301
  } else if (typeof numberOfWorkers === 'number' && numberOfWorkers > 0) {
281
- if (config.by === 'pool') {
282
- this.createTestPool(numberOfWorkers)
283
- } else {
284
- this.testGroups = config.by === 'suite' ? this.createGroupsOfSuites(numberOfWorkers) : this.createGroupsOfTests(numberOfWorkers)
285
- }
302
+ this.testGroups = config.by === 'suite' ? this.createGroupsOfSuites(numberOfWorkers) : this.createGroupsOfTests(numberOfWorkers)
286
303
  }
287
304
  }
288
305
 
@@ -302,7 +319,9 @@ class Workers extends EventEmitter {
302
319
  * @param {Number} numberOfWorkers
303
320
  */
304
321
  createGroupsOfTests(numberOfWorkers) {
305
- const files = this.codecept.testFiles
322
+ // If Codecept isn't initialized yet, return empty groups as a safe fallback
323
+ if (!this.codecept) return populateGroups(numberOfWorkers)
324
+ const files = this.codecept.testFiles
306
325
  const mocha = Container.mocha()
307
326
  mocha.files = files
308
327
  mocha.loadFiles()
@@ -320,90 +339,13 @@ class Workers extends EventEmitter {
320
339
  return groups
321
340
  }
322
341
 
323
- /**
324
- * @param {Number} numberOfWorkers
325
- */
326
- createTestPool(numberOfWorkers) {
327
- // For pool mode, create empty groups for each worker and initialize empty pool
328
- // Test pool will be populated lazily when getNextTest() is first called
329
- this.testPool = []
330
- this.testPoolInitialized = false
331
- this.testGroups = populateGroups(numberOfWorkers)
332
- }
333
-
334
- /**
335
- * Initialize the test pool if not already done
336
- * This is called lazily to avoid state pollution issues during construction
337
- */
338
- _initializeTestPool() {
339
- if (this.testPoolInitialized) {
340
- return
341
- }
342
-
343
- const files = this.codecept.testFiles
344
- if (!files || files.length === 0) {
345
- this.testPoolInitialized = true
346
- return
347
- }
348
-
349
- try {
350
- const mocha = Container.mocha()
351
- mocha.files = files
352
- mocha.loadFiles()
353
-
354
- mocha.suite.eachTest(test => {
355
- if (test) {
356
- this.testPool.push(test.uid)
357
- }
358
- })
359
- } catch (e) {
360
- // If mocha loading fails due to state pollution, skip
361
- }
362
-
363
- // If no tests were found, fallback to using createGroupsOfTests approach
364
- // This works around state pollution issues
365
- if (this.testPool.length === 0 && files.length > 0) {
366
- try {
367
- const testGroups = this.createGroupsOfTests(2) // Use 2 as a default for fallback
368
- for (const group of testGroups) {
369
- this.testPool.push(...group)
370
- }
371
- } catch (e) {
372
- // If createGroupsOfTests fails, fallback to simple file names
373
- for (const file of files) {
374
- this.testPool.push(`test_${file.replace(/[^a-zA-Z0-9]/g, '_')}`)
375
- }
376
- }
377
- }
378
-
379
- // Last resort fallback for unit tests - add dummy test UIDs
380
- if (this.testPool.length === 0) {
381
- for (let i = 0; i < Math.min(files.length, 5); i++) {
382
- this.testPool.push(`dummy_test_${i}_${Date.now()}`)
383
- }
384
- }
385
-
386
- this.testPoolInitialized = true
387
- }
388
-
389
- /**
390
- * Gets the next test from the pool
391
- * @returns {String|null} test uid or null if no tests available
392
- */
393
- getNextTest() {
394
- // Initialize test pool lazily on first access
395
- if (!this.testPoolInitialized) {
396
- this._initializeTestPool()
397
- }
398
-
399
- return this.testPool.shift() || null
400
- }
401
-
402
342
  /**
403
343
  * @param {Number} numberOfWorkers
404
344
  */
405
345
  createGroupsOfSuites(numberOfWorkers) {
406
- const files = this.codecept.testFiles
346
+ // If Codecept isn't initialized yet, return empty groups as a safe fallback
347
+ if (!this.codecept) return populateGroups(numberOfWorkers)
348
+ const files = this.codecept.testFiles
407
349
  const groups = populateGroups(numberOfWorkers)
408
350
 
409
351
  const mocha = Container.mocha()
@@ -430,20 +372,23 @@ class Workers extends EventEmitter {
430
372
  }
431
373
 
432
374
  async bootstrapAll() {
375
+ await this._ensureInitialized()
433
376
  return runHook(this.codecept.config.bootstrapAll, 'bootstrapAll')
434
377
  }
435
378
 
436
379
  async teardownAll() {
380
+ await this._ensureInitialized()
437
381
  return runHook(this.codecept.config.teardownAll, 'teardownAll')
438
382
  }
439
383
 
440
- run() {
384
+ async run() {
385
+ await this._ensureInitialized()
441
386
  recorder.startUnlessRunning()
442
387
  event.dispatcher.emit(event.workers.before)
443
388
  process.env.RUNS_WITH_WORKERS = 'true'
444
389
  recorder.add('starting workers', () => {
445
390
  for (const worker of this.workers) {
446
- const workerThread = createWorker(worker, this.isPoolMode)
391
+ const workerThread = createWorker(worker)
447
392
  this._listenWorkerEvents(workerThread)
448
393
  }
449
394
  })
@@ -467,27 +412,9 @@ class Workers extends EventEmitter {
467
412
  }
468
413
 
469
414
  _listenWorkerEvents(worker) {
470
- // Track worker thread for pool mode
471
- if (this.isPoolMode) {
472
- this.activeWorkers.set(worker, { available: true, workerIndex: null })
473
- }
474
-
475
415
  worker.on('message', message => {
476
416
  output.process(message.workerIndex)
477
417
 
478
- // Handle test requests for pool mode
479
- if (message.type === 'REQUEST_TEST') {
480
- if (this.isPoolMode) {
481
- const nextTest = this.getNextTest()
482
- if (nextTest) {
483
- worker.postMessage({ type: 'TEST_ASSIGNED', test: nextTest })
484
- } else {
485
- worker.postMessage({ type: 'NO_MORE_TESTS' })
486
- }
487
- }
488
- return
489
- }
490
-
491
418
  // deal with events that are not test cycle related
492
419
  if (!message.event) {
493
420
  return this.emit('message', message)
@@ -496,21 +423,11 @@ class Workers extends EventEmitter {
496
423
  switch (message.event) {
497
424
  case event.all.result:
498
425
  // we ensure consistency of result by adding tests in the very end
499
- // Check if message.data.stats is valid before adding
500
- if (message.data.stats) {
501
- Container.result().addStats(message.data.stats)
502
- }
503
-
504
- if (message.data.failures) {
505
- Container.result().addFailures(message.data.failures)
506
- }
507
-
508
- if (message.data.tests) {
509
- message.data.tests.forEach(test => {
510
- Container.result().addTest(deserializeTest(test))
511
- })
512
- }
513
-
426
+ Container.result().addFailures(message.data.failures)
427
+ Container.result().addStats(message.data.stats)
428
+ message.data.tests.forEach(test => {
429
+ Container.result().addTest(deserializeTest(test))
430
+ })
514
431
  break
515
432
  case event.suite.before:
516
433
  this.emit(event.suite.before, deserializeSuite(message.data))
@@ -522,16 +439,43 @@ class Workers extends EventEmitter {
522
439
  this.emit(event.test.started, deserializeTest(message.data))
523
440
  break
524
441
  case event.test.failed:
525
- this.emit(event.test.failed, deserializeTest(message.data))
442
+ // Skip individual failed events - we'll emit based on finished state
526
443
  break
527
444
  case event.test.passed:
528
- this.emit(event.test.passed, deserializeTest(message.data))
445
+ // Skip individual passed events - we'll emit based on finished state
529
446
  break
530
447
  case event.test.skipped:
531
448
  this.emit(event.test.skipped, deserializeTest(message.data))
532
449
  break
533
450
  case event.test.finished:
534
- this.emit(event.test.finished, deserializeTest(message.data))
451
+ // Handle different types of test completion properly
452
+ {
453
+ const data = message.data
454
+ const uid = data?.uid
455
+ const isFailed = !!data?.err || data?.state === 'failed'
456
+
457
+ if (uid) {
458
+ // Track states for each test UID
459
+ if (!this._testStates) this._testStates = new Map()
460
+
461
+ if (!this._testStates.has(uid)) {
462
+ this._testStates.set(uid, { states: [], lastData: data })
463
+ }
464
+
465
+ const testState = this._testStates.get(uid)
466
+ testState.states.push({ isFailed, data })
467
+ testState.lastData = data
468
+ } else {
469
+ // For tests without UID, emit immediately
470
+ if (isFailed) {
471
+ this.emit(event.test.failed, deserializeTest(data))
472
+ } else {
473
+ this.emit(event.test.passed, deserializeTest(data))
474
+ }
475
+ }
476
+
477
+ this.emit(event.test.finished, deserializeTest(data))
478
+ }
535
479
  break
536
480
  case event.test.after:
537
481
  this.emit(event.test.after, deserializeTest(message.data))
@@ -548,6 +492,11 @@ class Workers extends EventEmitter {
548
492
  case event.step.failed:
549
493
  this.emit(event.step.failed, message.data, message.data.error)
550
494
  break
495
+ case event.hook.failed:
496
+ // Count hook failures as test failures for event counting
497
+ this.emit(event.test.failed, { title: `Hook failure: ${message.data.hookName || 'unknown'}`, err: message.data.error })
498
+ this.emit(event.hook.failed, message.data)
499
+ break
551
500
  }
552
501
  })
553
502
 
@@ -557,14 +506,7 @@ class Workers extends EventEmitter {
557
506
 
558
507
  worker.on('exit', () => {
559
508
  this.closedWorkers += 1
560
-
561
- if (this.isPoolMode) {
562
- // Pool mode: finish when all workers have exited and no more tests
563
- if (this.closedWorkers === this.numberOfWorkers) {
564
- this._finishRun()
565
- }
566
- } else if (this.closedWorkers === this.numberOfWorkers) {
567
- // Regular mode: finish when all original workers have exited
509
+ if (this.closedWorkers === this.numberOfWorkers) {
568
510
  this._finishRun()
569
511
  }
570
512
  })
@@ -578,6 +520,38 @@ class Workers extends EventEmitter {
578
520
  process.exitCode = 0
579
521
  }
580
522
 
523
+ // Emit states for all tracked tests before emitting results
524
+ if (this._testStates) {
525
+ for (const [uid, { states, lastData }] of this._testStates) {
526
+ // For tests with retries configured, emit all failures + final success
527
+ // For tests without retries, emit only final state
528
+ const lastState = states[states.length - 1]
529
+
530
+ // Check if this test had retries by looking for failure followed by success
531
+ const hasRetryPattern = states.length > 1 &&
532
+ states.some((s, i) => s.isFailed && i < states.length - 1 && !states[i + 1].isFailed)
533
+
534
+ if (hasRetryPattern) {
535
+ // Emit all intermediate failures and final success for retries
536
+ for (const state of states) {
537
+ if (state.isFailed) {
538
+ this.emit(event.test.failed, deserializeTest(state.data))
539
+ } else {
540
+ this.emit(event.test.passed, deserializeTest(state.data))
541
+ }
542
+ }
543
+ } else {
544
+ // For non-retries (like step failures), emit only the final state
545
+ if (lastState.isFailed) {
546
+ this.emit(event.test.failed, deserializeTest(lastState.data))
547
+ } else {
548
+ this.emit(event.test.passed, deserializeTest(lastState.data))
549
+ }
550
+ }
551
+ }
552
+ this._testStates.clear()
553
+ }
554
+
581
555
  this.emit(event.all.result, Container.result())
582
556
  event.dispatcher.emit(event.workers.result, Container.result())
583
557
  this.emit('end') // internal event
@@ -602,10 +576,10 @@ class Workers extends EventEmitter {
602
576
  this.failuresLog.forEach(log => output.print(...log))
603
577
  }
604
578
 
605
- output.result(result.stats.passes, result.stats.failures, result.stats.pending, ms(result.duration), result.stats.failedHooks)
579
+ output.result(result.stats?.passes || 0, result.stats?.failures || 0, result.stats?.pending || 0, ms(result.duration), result.stats?.failedHooks || 0)
606
580
 
607
581
  process.env.RUNS_WITH_WORKERS = 'false'
608
582
  }
609
583
  }
610
584
 
611
- module.exports = Workers
585
+ export default Workers