codeceptjs 3.7.6-beta.4 → 4.0.0-beta.10.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 (191) hide show
  1. package/README.md +1 -3
  2. package/bin/codecept.js +51 -53
  3. package/bin/test-server.js +14 -3
  4. package/docs/webapi/click.mustache +5 -1
  5. package/lib/actor.js +15 -11
  6. package/lib/ai.js +72 -107
  7. package/lib/assert/empty.js +9 -8
  8. package/lib/assert/equal.js +15 -17
  9. package/lib/assert/error.js +2 -2
  10. package/lib/assert/include.js +9 -11
  11. package/lib/assert/throws.js +1 -1
  12. package/lib/assert/truth.js +8 -5
  13. package/lib/assert.js +18 -18
  14. package/lib/codecept.js +102 -75
  15. package/lib/colorUtils.js +48 -50
  16. package/lib/command/check.js +32 -27
  17. package/lib/command/configMigrate.js +11 -10
  18. package/lib/command/definitions.js +16 -10
  19. package/lib/command/dryRun.js +16 -16
  20. package/lib/command/generate.js +62 -27
  21. package/lib/command/gherkin/init.js +36 -38
  22. package/lib/command/gherkin/snippets.js +14 -14
  23. package/lib/command/gherkin/steps.js +21 -18
  24. package/lib/command/info.js +8 -8
  25. package/lib/command/init.js +36 -29
  26. package/lib/command/interactive.js +11 -10
  27. package/lib/command/list.js +10 -9
  28. package/lib/command/run-multiple/chunk.js +5 -5
  29. package/lib/command/run-multiple/collection.js +5 -5
  30. package/lib/command/run-multiple/run.js +3 -3
  31. package/lib/command/run-multiple.js +16 -13
  32. package/lib/command/run-rerun.js +6 -7
  33. package/lib/command/run-workers.js +24 -9
  34. package/lib/command/run.js +23 -8
  35. package/lib/command/utils.js +20 -18
  36. package/lib/command/workers/runTests.js +197 -114
  37. package/lib/config.js +124 -51
  38. package/lib/container.js +438 -87
  39. package/lib/data/context.js +6 -5
  40. package/lib/data/dataScenarioConfig.js +1 -1
  41. package/lib/data/dataTableArgument.js +1 -1
  42. package/lib/data/table.js +1 -1
  43. package/lib/effects.js +94 -10
  44. package/lib/element/WebElement.js +2 -2
  45. package/lib/els.js +11 -9
  46. package/lib/event.js +11 -10
  47. package/lib/globals.js +141 -0
  48. package/lib/heal.js +12 -12
  49. package/lib/helper/AI.js +11 -11
  50. package/lib/helper/ApiDataFactory.js +50 -19
  51. package/lib/helper/Appium.js +19 -27
  52. package/lib/helper/FileSystem.js +32 -12
  53. package/lib/helper/GraphQL.js +3 -3
  54. package/lib/helper/GraphQLDataFactory.js +4 -4
  55. package/lib/helper/JSONResponse.js +25 -29
  56. package/lib/helper/Mochawesome.js +7 -4
  57. package/lib/helper/Playwright.js +902 -164
  58. package/lib/helper/Puppeteer.js +383 -76
  59. package/lib/helper/REST.js +29 -12
  60. package/lib/helper/WebDriver.js +268 -61
  61. package/lib/helper/clientscripts/PollyWebDriverExt.js +1 -1
  62. package/lib/helper/errors/ConnectionRefused.js +6 -6
  63. package/lib/helper/errors/ElementAssertion.js +11 -16
  64. package/lib/helper/errors/ElementNotFound.js +5 -9
  65. package/lib/helper/errors/RemoteBrowserConnectionRefused.js +5 -5
  66. package/lib/helper/extras/Console.js +11 -11
  67. package/lib/helper/extras/PlaywrightLocator.js +110 -0
  68. package/lib/helper/extras/PlaywrightPropEngine.js +18 -18
  69. package/lib/helper/extras/PlaywrightReactVueLocator.js +18 -9
  70. package/lib/helper/extras/PlaywrightRestartOpts.js +34 -23
  71. package/lib/helper/extras/Popup.js +1 -1
  72. package/lib/helper/extras/React.js +29 -30
  73. package/lib/helper/network/actions.js +29 -44
  74. package/lib/helper/network/utils.js +76 -83
  75. package/lib/helper/scripts/blurElement.js +6 -6
  76. package/lib/helper/scripts/focusElement.js +6 -6
  77. package/lib/helper/scripts/highlightElement.js +9 -9
  78. package/lib/helper/scripts/isElementClickable.js +34 -34
  79. package/lib/helper.js +2 -1
  80. package/lib/history.js +23 -20
  81. package/lib/hooks.js +10 -10
  82. package/lib/html.js +90 -100
  83. package/lib/index.js +48 -21
  84. package/lib/listener/config.js +19 -12
  85. package/lib/listener/emptyRun.js +6 -7
  86. package/lib/listener/enhancedGlobalRetry.js +6 -6
  87. package/lib/listener/exit.js +4 -3
  88. package/lib/listener/globalRetry.js +5 -5
  89. package/lib/listener/globalTimeout.js +30 -14
  90. package/lib/listener/helpers.js +39 -14
  91. package/lib/listener/mocha.js +3 -4
  92. package/lib/listener/result.js +4 -5
  93. package/lib/listener/retryEnhancer.js +3 -3
  94. package/lib/listener/steps.js +8 -7
  95. package/lib/listener/store.js +3 -3
  96. package/lib/locator.js +213 -192
  97. package/lib/mocha/asyncWrapper.js +105 -62
  98. package/lib/mocha/bdd.js +99 -13
  99. package/lib/mocha/cli.js +59 -26
  100. package/lib/mocha/factory.js +78 -19
  101. package/lib/mocha/featureConfig.js +1 -1
  102. package/lib/mocha/gherkin.js +56 -24
  103. package/lib/mocha/hooks.js +12 -3
  104. package/lib/mocha/index.js +13 -4
  105. package/lib/mocha/inject.js +22 -5
  106. package/lib/mocha/scenarioConfig.js +2 -2
  107. package/lib/mocha/suite.js +9 -2
  108. package/lib/mocha/test.js +10 -7
  109. package/lib/mocha/ui.js +28 -18
  110. package/lib/output.js +10 -8
  111. package/lib/parser.js +44 -44
  112. package/lib/pause.js +15 -16
  113. package/lib/plugin/analyze.js +19 -12
  114. package/lib/plugin/auth.js +20 -21
  115. package/lib/plugin/autoDelay.js +12 -8
  116. package/lib/plugin/coverage.js +28 -11
  117. package/lib/plugin/customLocator.js +3 -3
  118. package/lib/plugin/customReporter.js +3 -2
  119. package/lib/plugin/enhancedRetryFailedStep.js +6 -6
  120. package/lib/plugin/heal.js +14 -9
  121. package/lib/plugin/htmlReporter.js +724 -99
  122. package/lib/plugin/pageInfo.js +10 -10
  123. package/lib/plugin/pauseOnFail.js +4 -3
  124. package/lib/plugin/retryFailedStep.js +48 -5
  125. package/lib/plugin/screenshotOnFail.js +75 -37
  126. package/lib/plugin/stepByStepReport.js +14 -14
  127. package/lib/plugin/stepTimeout.js +4 -3
  128. package/lib/plugin/subtitles.js +6 -5
  129. package/lib/recorder.js +33 -14
  130. package/lib/rerun.js +69 -26
  131. package/lib/result.js +4 -4
  132. package/lib/retryCoordinator.js +2 -2
  133. package/lib/secret.js +18 -17
  134. package/lib/session.js +95 -89
  135. package/lib/step/base.js +7 -7
  136. package/lib/step/comment.js +2 -2
  137. package/lib/step/config.js +1 -1
  138. package/lib/step/func.js +3 -3
  139. package/lib/step/helper.js +3 -3
  140. package/lib/step/meta.js +5 -5
  141. package/lib/step/record.js +11 -11
  142. package/lib/step/retry.js +3 -3
  143. package/lib/step/section.js +3 -3
  144. package/lib/step.js +7 -10
  145. package/lib/steps.js +9 -5
  146. package/lib/store.js +1 -1
  147. package/lib/template/heal.js +1 -1
  148. package/lib/template/prompts/generatePageObject.js +31 -0
  149. package/lib/template/prompts/healStep.js +13 -0
  150. package/lib/template/prompts/writeStep.js +9 -0
  151. package/lib/test-server.js +17 -6
  152. package/lib/timeout.js +1 -7
  153. package/lib/transform.js +8 -8
  154. package/lib/translation.js +32 -18
  155. package/lib/utils/mask_data.js +4 -10
  156. package/lib/utils.js +66 -64
  157. package/lib/workerStorage.js +17 -17
  158. package/lib/workers.js +214 -84
  159. package/package.json +41 -37
  160. package/translations/de-DE.js +2 -2
  161. package/translations/fr-FR.js +2 -2
  162. package/translations/index.js +23 -10
  163. package/translations/it-IT.js +2 -2
  164. package/translations/ja-JP.js +2 -2
  165. package/translations/nl-NL.js +2 -2
  166. package/translations/pl-PL.js +2 -2
  167. package/translations/pt-BR.js +2 -2
  168. package/translations/ru-RU.js +2 -2
  169. package/translations/utils.js +4 -3
  170. package/translations/zh-CN.js +2 -2
  171. package/translations/zh-TW.js +2 -2
  172. package/typings/index.d.ts +5 -3
  173. package/typings/promiseBasedTypes.d.ts +4 -0
  174. package/typings/types.d.ts +4 -0
  175. package/lib/helper/Nightmare.js +0 -1486
  176. package/lib/helper/Protractor.js +0 -1840
  177. package/lib/helper/TestCafe.js +0 -1391
  178. package/lib/helper/clientscripts/nightmare.js +0 -213
  179. package/lib/helper/testcafe/testControllerHolder.js +0 -42
  180. package/lib/helper/testcafe/testcafe-utils.js +0 -61
  181. package/lib/plugin/allure.js +0 -15
  182. package/lib/plugin/autoLogin.js +0 -5
  183. package/lib/plugin/commentStep.js +0 -141
  184. package/lib/plugin/eachElement.js +0 -127
  185. package/lib/plugin/fakerTransform.js +0 -49
  186. package/lib/plugin/retryTo.js +0 -16
  187. package/lib/plugin/selenoid.js +0 -364
  188. package/lib/plugin/standardActingHelpers.js +0 -6
  189. package/lib/plugin/tryTo.js +0 -16
  190. package/lib/plugin/wdio.js +0 -247
  191. package/lib/within.js +0 -90
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
 
@@ -58,8 +63,28 @@ const createWorker = (workerObject, isPoolMode = false) => {
58
63
  workerIndex: workerObject.workerIndex + 1,
59
64
  poolMode: isPoolMode,
60
65
  },
66
+ stdout: true,
67
+ stderr: true,
68
+ })
69
+
70
+ // Pipe worker stdout/stderr to main process
71
+ if (worker.stdout) {
72
+ worker.stdout.setEncoding('utf8')
73
+ worker.stdout.on('data', (data) => {
74
+ process.stdout.write(data)
75
+ })
76
+ }
77
+ if (worker.stderr) {
78
+ worker.stderr.setEncoding('utf8')
79
+ worker.stderr.on('data', (data) => {
80
+ process.stderr.write(data)
81
+ })
82
+ }
83
+
84
+ worker.on('error', err => {
85
+ console.error(`[Main] Worker Error:`, err)
86
+ output.error(`Worker Error: ${err.stack}`)
61
87
  })
62
- worker.on('error', err => output.error(`Worker Error: ${err.stack}`))
63
88
 
64
89
  WorkerStorage.addWorker(worker)
65
90
  return worker
@@ -99,7 +124,7 @@ const createWorkerObjects = (testGroups, config, testRoot, options, selectedRuns
99
124
  currentMochaJunitReporterFile = config.mocha.reporterOptions['mocha-junit-reporter'].options.mochaFile
100
125
  }
101
126
 
102
- collection.createRuns(selectedRuns, config).forEach(worker => {
127
+ createRuns(selectedRuns, config).forEach(worker => {
103
128
  const separator = path.sep
104
129
  const _config = { ...config }
105
130
  let workerName = worker.name.replace(':', '_')
@@ -196,9 +221,31 @@ class WorkerObject {
196
221
 
197
222
  addConfig(config) {
198
223
  const oldConfig = JSON.parse(this.options.override || '{}')
224
+
225
+ // Remove customLocatorStrategies from both old and new config before JSON serialization
226
+ // since functions cannot be serialized and will be lost, causing workers to have empty strategies
227
+ const configWithoutFunctions = { ...config }
228
+
229
+ // Clean both old and new config
230
+ const cleanConfig = (cfg) => {
231
+ if (cfg.helpers) {
232
+ cfg.helpers = { ...cfg.helpers }
233
+ Object.keys(cfg.helpers).forEach(helperName => {
234
+ if (cfg.helpers[helperName] && cfg.helpers[helperName].customLocatorStrategies !== undefined) {
235
+ cfg.helpers[helperName] = { ...cfg.helpers[helperName] }
236
+ delete cfg.helpers[helperName].customLocatorStrategies
237
+ }
238
+ })
239
+ }
240
+ return cfg
241
+ }
242
+
243
+ const cleanedOldConfig = cleanConfig(oldConfig)
244
+ const cleanedNewConfig = cleanConfig(configWithoutFunctions)
245
+
199
246
  const newConfig = {
200
- ...oldConfig,
201
- ...config,
247
+ ...cleanedOldConfig,
248
+ ...cleanedNewConfig,
202
249
  }
203
250
  this.options.override = JSON.stringify(newConfig)
204
251
  }
@@ -231,7 +278,10 @@ class Workers extends EventEmitter {
231
278
  constructor(numberOfWorkers, config = { by: 'test' }) {
232
279
  super()
233
280
  this.setMaxListeners(50)
234
- this.codecept = initializeCodecept(config.testConfig, config.options)
281
+ this.codeceptPromise = initializeCodecept(config.testConfig, config.options)
282
+ this.codecept = null
283
+ this.config = config // Save config
284
+ this.numberOfWorkersRequested = numberOfWorkers // Save requested worker count
235
285
  this.options = config.options || {}
236
286
  this.errors = []
237
287
  this.numberOfWorkers = 0
@@ -245,11 +295,30 @@ class Workers extends EventEmitter {
245
295
  this.maxWorkers = numberOfWorkers // Track original worker count for pool mode
246
296
 
247
297
  createOutputDir(config.testConfig)
248
- if (numberOfWorkers) this._initWorkers(numberOfWorkers, config)
298
+ // Defer worker initialization until codecept is ready
299
+ }
300
+
301
+ async _ensureInitialized() {
302
+ if (!this.codecept) {
303
+ this.codecept = await this.codeceptPromise
304
+ // Initialize workers in these cases:
305
+ // 1. Positive number requested AND no manual workers pre-spawned
306
+ // 2. Function-based grouping (indicated by negative number) AND no manual workers pre-spawned
307
+ const shouldAutoInit = this.workers.length === 0 && (
308
+ (Number.isInteger(this.numberOfWorkersRequested) && this.numberOfWorkersRequested > 0) ||
309
+ (this.numberOfWorkersRequested < 0 && isFunction(this.config.by))
310
+ )
311
+
312
+ if (shouldAutoInit) {
313
+ this._initWorkers(this.numberOfWorkersRequested, this.config)
314
+ }
315
+ }
249
316
  }
250
317
 
251
318
  _initWorkers(numberOfWorkers, config) {
252
319
  this.splitTestsByGroups(numberOfWorkers, config)
320
+ // For function-based grouping, use the actual number of test groups created
321
+ const actualNumberOfWorkers = isFunction(config.by) ? this.testGroups.length : numberOfWorkers
253
322
  this.workers = createWorkerObjects(this.testGroups, this.codecept.config, config.testConfig, config.options, config.selectedRuns)
254
323
  this.numberOfWorkers = this.workers.length
255
324
  }
@@ -302,7 +371,9 @@ class Workers extends EventEmitter {
302
371
  * @param {Number} numberOfWorkers
303
372
  */
304
373
  createGroupsOfTests(numberOfWorkers) {
305
- const files = this.codecept.testFiles
374
+ // If Codecept isn't initialized yet, return empty groups as a safe fallback
375
+ if (!this.codecept) return populateGroups(numberOfWorkers)
376
+ const files = this.codecept.testFiles
306
377
  const mocha = Container.mocha()
307
378
  mocha.files = files
308
379
  mocha.loadFiles()
@@ -340,70 +411,49 @@ class Workers extends EventEmitter {
340
411
  return
341
412
  }
342
413
 
343
- const files = this.codecept.testFiles
344
- if (!files || files.length === 0) {
414
+ // Ensure codecept is initialized
415
+ if (!this.codecept) {
416
+ output.log('Warning: codecept not initialized when initializing test pool')
345
417
  this.testPoolInitialized = true
346
418
  return
347
419
  }
348
420
 
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
- }
421
+ const files = this.codecept.testFiles
422
+ if (!files || files.length === 0) {
423
+ this.testPoolInitialized = true
424
+ return
377
425
  }
378
426
 
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
- }
427
+ // In ESM, test UIDs are not stable across different mocha instances
428
+ // So instead of using UIDs, we distribute test FILES
429
+ // Each file may contain multiple tests
430
+ for (const file of files) {
431
+ this.testPool.push(file)
384
432
  }
385
-
433
+
386
434
  this.testPoolInitialized = true
387
435
  }
388
436
 
389
437
  /**
390
438
  * Gets the next test from the pool
391
- * @returns {String|null} test uid or null if no tests available
439
+ * @returns {String|null} test file path or null if no tests available
392
440
  */
393
441
  getNextTest() {
394
- // Initialize test pool lazily on first access
442
+ // Lazy initialization of test pool on first call
395
443
  if (!this.testPoolInitialized) {
396
444
  this._initializeTestPool()
397
445
  }
398
-
399
- return this.testPool.shift() || null
446
+
447
+ return this.testPool.shift()
400
448
  }
401
449
 
402
450
  /**
403
451
  * @param {Number} numberOfWorkers
404
452
  */
405
453
  createGroupsOfSuites(numberOfWorkers) {
406
- const files = this.codecept.testFiles
454
+ // If Codecept isn't initialized yet, return empty groups as a safe fallback
455
+ if (!this.codecept) return populateGroups(numberOfWorkers)
456
+ const files = this.codecept.testFiles
407
457
  const groups = populateGroups(numberOfWorkers)
408
458
 
409
459
  const mocha = Container.mocha()
@@ -430,23 +480,34 @@ class Workers extends EventEmitter {
430
480
  }
431
481
 
432
482
  async bootstrapAll() {
483
+ await this._ensureInitialized()
433
484
  return runHook(this.codecept.config.bootstrapAll, 'bootstrapAll')
434
485
  }
435
486
 
436
487
  async teardownAll() {
488
+ await this._ensureInitialized()
437
489
  return runHook(this.codecept.config.teardownAll, 'teardownAll')
438
490
  }
439
491
 
440
- run() {
492
+ async run() {
493
+ await this._ensureInitialized()
441
494
  recorder.startUnlessRunning()
442
495
  event.dispatcher.emit(event.workers.before)
443
496
  process.env.RUNS_WITH_WORKERS = 'true'
444
- recorder.add('starting workers', () => {
445
- for (const worker of this.workers) {
446
- const workerThread = createWorker(worker, this.isPoolMode)
447
- this._listenWorkerEvents(workerThread)
448
- }
497
+
498
+ // Create workers and set up message handlers immediately (not in recorder queue)
499
+ // This prevents a race condition where workers start sending messages before handlers are attached
500
+ const workerThreads = []
501
+ for (const worker of this.workers) {
502
+ const workerThread = createWorker(worker, this.isPoolMode)
503
+ this._listenWorkerEvents(workerThread)
504
+ workerThreads.push(workerThread)
505
+ }
506
+
507
+ recorder.add('workers started', () => {
508
+ // Workers are already running, this is just a placeholder step
449
509
  })
510
+
450
511
  return new Promise(resolve => {
451
512
  this.on('end', resolve)
452
513
  })
@@ -522,16 +583,48 @@ class Workers extends EventEmitter {
522
583
  this.emit(event.test.started, deserializeTest(message.data))
523
584
  break
524
585
  case event.test.failed:
525
- this.emit(event.test.failed, deserializeTest(message.data))
586
+ // For hook failures, emit immediately as there won't be a test.finished event
587
+ // Regular test failures are handled via test.finished to support retries
588
+ if (message.data?.hookName) {
589
+ this.emit(event.test.failed, deserializeTest(message.data))
590
+ }
591
+ // Otherwise skip - we'll emit based on finished state
526
592
  break
527
593
  case event.test.passed:
528
- this.emit(event.test.passed, deserializeTest(message.data))
594
+ // Skip individual passed events - we'll emit based on finished state
529
595
  break
530
596
  case event.test.skipped:
531
597
  this.emit(event.test.skipped, deserializeTest(message.data))
532
598
  break
533
599
  case event.test.finished:
534
- this.emit(event.test.finished, deserializeTest(message.data))
600
+ // Handle different types of test completion properly
601
+ {
602
+ const data = message.data
603
+ const uid = data?.uid
604
+ const isFailed = !!data?.err || data?.state === 'failed'
605
+
606
+ if (uid) {
607
+ // Track states for each test UID
608
+ if (!this._testStates) this._testStates = new Map()
609
+
610
+ if (!this._testStates.has(uid)) {
611
+ this._testStates.set(uid, { states: [], lastData: data })
612
+ }
613
+
614
+ const testState = this._testStates.get(uid)
615
+ testState.states.push({ isFailed, data })
616
+ testState.lastData = data
617
+ } else {
618
+ // For tests without UID, emit immediately
619
+ if (isFailed) {
620
+ this.emit(event.test.failed, deserializeTest(data))
621
+ } else {
622
+ this.emit(event.test.passed, deserializeTest(data))
623
+ }
624
+ }
625
+
626
+ this.emit(event.test.finished, deserializeTest(data))
627
+ }
535
628
  break
536
629
  case event.test.after:
537
630
  this.emit(event.test.after, deserializeTest(message.data))
@@ -548,6 +641,11 @@ class Workers extends EventEmitter {
548
641
  case event.step.failed:
549
642
  this.emit(event.step.failed, message.data, message.data.error)
550
643
  break
644
+ case event.hook.failed:
645
+ // Hook failures are already reported as test failures by the worker
646
+ // Just emit the hook.failed event for listeners
647
+ this.emit(event.hook.failed, message.data)
648
+ break
551
649
  }
552
650
  })
553
651
 
@@ -578,6 +676,38 @@ class Workers extends EventEmitter {
578
676
  process.exitCode = 0
579
677
  }
580
678
 
679
+ // Emit states for all tracked tests before emitting results
680
+ if (this._testStates) {
681
+ for (const [uid, { states, lastData }] of this._testStates) {
682
+ // For tests with retries configured, emit all failures + final success
683
+ // For tests without retries, emit only final state
684
+ const lastState = states[states.length - 1]
685
+
686
+ // Check if this test had retries by looking for failure followed by success
687
+ const hasRetryPattern = states.length > 1 &&
688
+ states.some((s, i) => s.isFailed && i < states.length - 1 && !states[i + 1].isFailed)
689
+
690
+ if (hasRetryPattern) {
691
+ // Emit all intermediate failures and final success for retries
692
+ for (const state of states) {
693
+ if (state.isFailed) {
694
+ this.emit(event.test.failed, deserializeTest(state.data))
695
+ } else {
696
+ this.emit(event.test.passed, deserializeTest(state.data))
697
+ }
698
+ }
699
+ } else {
700
+ // For non-retries (like step failures), emit only the final state
701
+ if (lastState.isFailed) {
702
+ this.emit(event.test.failed, deserializeTest(lastState.data))
703
+ } else {
704
+ this.emit(event.test.passed, deserializeTest(lastState.data))
705
+ }
706
+ }
707
+ }
708
+ this._testStates.clear()
709
+ }
710
+
581
711
  this.emit(event.all.result, Container.result())
582
712
  event.dispatcher.emit(event.workers.result, Container.result())
583
713
  this.emit('end') // internal event
@@ -602,10 +732,10 @@ class Workers extends EventEmitter {
602
732
  this.failuresLog.forEach(log => output.print(...log))
603
733
  }
604
734
 
605
- output.result(result.stats.passes, result.stats.failures, result.stats.pending, ms(result.duration), result.stats.failedHooks)
735
+ output.result(result.stats?.passes || 0, result.stats?.failures || 0, result.stats?.pending || 0, ms(result.duration), result.stats?.failedHooks || 0)
606
736
 
607
737
  process.env.RUNS_WITH_WORKERS = 'false'
608
738
  }
609
739
  }
610
740
 
611
- module.exports = Workers
741
+ export default Workers
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "codeceptjs",
3
- "version": "3.7.6-beta.4",
3
+ "version": "4.0.0-beta.10.esm-aria",
4
+ "type": "module",
4
5
  "description": "Supercharged End 2 End Testing Framework for NodeJS",
5
6
  "keywords": [
6
7
  "acceptance",
@@ -8,7 +9,6 @@
8
9
  "end 2 end",
9
10
  "puppeteer",
10
11
  "webdriver",
11
- "testcafe",
12
12
  "playwright",
13
13
  "bdd",
14
14
  "tdd",
@@ -40,47 +40,49 @@
40
40
  "./lib/*": "./lib/*.js",
41
41
  "./els": "./lib/els.js",
42
42
  "./effects": "./lib/effects.js",
43
- "./steps": "./lib/steps.js"
43
+ "./steps": "./lib/steps.js",
44
+ "./store": "./lib/store.js"
44
45
  },
45
46
  "bin": {
46
47
  "codeceptjs": "./bin/codecept.js"
47
48
  },
48
49
  "repository": "Codeception/codeceptjs",
49
50
  "scripts": {
50
- "test-server": "node bin/test-server.js test/data/rest/db.json --host 0.0.0.0 -p 8010",
51
+ "test-server": "node bin/test-server.js test/data/rest/db.json --host 0.0.0.0 -p 8010 --read-only",
52
+ "test-server:writable": "node bin/test-server.js test/data/rest/db.json --host 0.0.0.0 -p 8010",
51
53
  "mock-server:start": "node test/mock-server/start-mock-server.js",
52
54
  "mock-server:stop": "kill -9 $(lsof -t -i:3001)",
53
55
  "test:with-mock-server": "npm run mock-server:start && npm test",
54
56
  "json-server:graphql": "node test/data/graphql/index.js",
55
- "lint": "eslint bin/ examples/ lib/ test/ translations/ runok.js",
56
- "lint-fix": "eslint bin/ examples/ lib/ test/ translations/ runok.js --fix",
57
- "prettier": "prettier --config prettier.config.js --write bin/**/*.js lib/**/*.js test/**/*.js translations/**/*.js runok.js",
58
- "docs": "./runok.js docs",
59
- "test:unit": "mocha test/unit --recursive --timeout 10000",
60
- "test:runner": "mocha test/runner --recursive --timeout 10000",
61
- "test": "npm run test:unit && npm run test:runner",
62
- "test:appium-quick": "mocha test/helper/Appium_test.js --grep 'quick'",
63
- "test:appium-other": "mocha test/helper/Appium_test.js --grep 'second'",
64
- "test:ios:appium-quick": "mocha test/helper/Appium_ios_test.js --grep 'quick'",
65
- "test:ios:appium-other": "mocha test/helper/Appium_ios_test.js --grep 'second'",
57
+ "lint": "eslint bin/ examples/ lib/ test/ translations/ runok.cjs",
58
+ "lint-fix": "eslint bin/ examples/ lib/ test/ translations/ runok.cjs --fix",
59
+ "prettier": "prettier --config prettier.config.js --write bin/**/*.js lib/**/*.js test/**/*.js translations/**/*.js runok.cjs",
60
+ "docs": "./runok.cjs docs",
61
+ "test:unit": "mocha test/unit --recursive --timeout 10000 --reporter @testomatio/reporter/mocha",
62
+ "test:rest": "mocha test/rest --recursive --timeout 20000 --reporter @testomatio/reporter/mocha",
63
+ "test:runner": "mocha test/runner --recursive --timeout 10000 --reporter @testomatio/reporter/mocha",
64
+ "test": "npm run test:unit && npm run test:rest && npm run test:runner",
65
+ "test:appium-quick": "mocha test/helper/Appium_test.js --grep 'quick' --reporter @testomatio/reporter/mocha",
66
+ "test:appium-other": "mocha test/helper/Appium_test.js --grep 'second' --reporter @testomatio/reporter/mocha",
67
+ "test:ios:appium-quick": "mocha test/helper/Appium_ios_test.js --grep 'quick' --reporter @testomatio/reporter/mocha",
68
+ "test:ios:appium-other": "mocha test/helper/Appium_ios_test.js --grep 'second' --reporter @testomatio/reporter/mocha",
66
69
  "test-app:start": "php -S 127.0.0.1:8000 -t test/data/app",
67
70
  "test-app:stop": "kill -9 $(lsof -t -i:8000)",
68
- "test:unit:webbapi:playwright": "mocha test/helper/Playwright_test.js",
69
- "test:unit:webbapi:puppeteer": "mocha test/helper/Puppeteer_test.js",
70
- "test:unit:webbapi:webDriver": "mocha test/helper/WebDriver_test.js --timeout 10000",
71
- "test:unit:webbapi:webDriver:noSeleniumServer": "mocha test/helper/WebDriver.noSeleniumServer_test.js --timeout 10000",
72
- "test:unit:webbapi:testCafe": "mocha test/helper/TestCafe_test.js",
73
- "test:unit:expect": "mocha test/helper/Expect_test.js",
74
- "test:plugin": "mocha test/plugin/plugin_test.js",
75
- "def": "./runok.js def",
71
+ "test:unit:webbapi:playwright": "mocha test/helper/Playwright_test.js --reporter @testomatio/reporter/mocha",
72
+ "test:unit:webbapi:puppeteer": "mocha test/helper/Puppeteer_test.js --reporter @testomatio/reporter/mocha",
73
+ "test:unit:webbapi:webDriver": "mocha test/helper/WebDriver_test.js --timeout 10000 --reporter @testomatio/reporter/mocha",
74
+ "test:unit:webbapi:webDriver:noSeleniumServer": "mocha test/helper/WebDriver.noSeleniumServer_test.js --timeout 10000 --reporter @testomatio/reporter/mocha",
75
+ "test:unit:expect": "mocha test/helper/Expect_test.js --reporter @testomatio/reporter/mocha",
76
+ "test:plugin": "mocha test/plugin/plugin_test.js --reporter @testomatio/reporter/mocha",
77
+ "def": "./runok.cjs def",
76
78
  "dev:graphql": "node test/data/graphql/index.js",
77
- "publish:site": "./runok.js publish:site",
78
- "update-contributor-faces": "./runok.js contributor:faces",
79
+ "publish:site": "./runok.cjs publish:site",
80
+ "update-contributor-faces": "./runok.cjs contributor:faces",
79
81
  "types-fix": "node typings/fixDefFiles.js",
80
82
  "dtslint": "npm run types-fix && tsd",
81
83
  "prepare": "husky install",
82
- "prepare-release": "./runok.js versioning && ./runok.js get:commit-log",
83
- "publish-beta": "./runok.js publish:next-beta-version"
84
+ "prepare-release": "./runok.cjs versioning && ./runok.cjs get:commit-log",
85
+ "publish-beta": "./runok.cjs publish:next-beta-version"
84
86
  },
85
87
  "dependencies": {
86
88
  "@codeceptjs/configure": "1.0.6",
@@ -90,6 +92,7 @@
90
92
  "@cucumber/messages": "29.0.1",
91
93
  "@xmldom/xmldom": "0.9.8",
92
94
  "acorn": "8.15.0",
95
+ "ai": "^5.0.60",
93
96
  "arrify": "3.0.0",
94
97
  "axios": "1.12.2",
95
98
  "chalk": "4.1.2",
@@ -99,7 +102,7 @@
99
102
  "cross-spawn": "7.0.6",
100
103
  "css-to-xpath": "0.1.0",
101
104
  "csstoxpath": "1.6.0",
102
- "envinfo": "7.15.0",
105
+ "envinfo": "7.20.0",
103
106
  "escape-string-regexp": "4.0.0",
104
107
  "figures": "3.2.0",
105
108
  "fn-args": "4.0.0",
@@ -115,7 +118,7 @@
115
118
  "lodash.merge": "4.6.2",
116
119
  "lodash.shuffle": "4.2.0",
117
120
  "mkdirp": "3.0.1",
118
- "mocha": "11.7.2",
121
+ "mocha": "11.7.5",
119
122
  "monocart-coverage-reports": "2.12.9",
120
123
  "ms": "2.1.3",
121
124
  "multer": "^2.0.2",
@@ -137,16 +140,19 @@
137
140
  "@eslint/eslintrc": "3.3.1",
138
141
  "@eslint/js": "9.36.0",
139
142
  "@faker-js/faker": "9.8.0",
143
+ "@inquirer/testing": "^2.1.49",
140
144
  "@pollyjs/adapter-puppeteer": "6.0.6",
141
145
  "@pollyjs/core": "6.0.6",
146
+ "@testomatio/reporter": "^2.3.1",
142
147
  "@types/chai": "5.2.2",
143
148
  "@types/inquirer": "9.0.9",
144
- "@types/node": "^24.6.2",
149
+ "@types/node": "^24.9.2",
145
150
  "@wdio/sauce-service": "9.12.5",
146
151
  "@wdio/selenium-standalone-service": "8.15.0",
147
- "@wdio/utils": "9.19.2",
152
+ "@wdio/utils": "9.20.0",
148
153
  "@xmldom/xmldom": "0.9.8",
149
- "chai": "^4.0.0",
154
+ "bunosh": "latest",
155
+ "chai": "^4.5.0",
150
156
  "chai-as-promised": "7.1.2",
151
157
  "chai-subset": "1.6.0",
152
158
  "documentation": "14.0.3",
@@ -160,7 +166,6 @@
160
166
  "graphql": "16.11.0",
161
167
  "graphql-tag": "^2.12.6",
162
168
  "husky": "9.1.7",
163
- "inquirer-test": "2.0.1",
164
169
  "jsdoc": "^3.6.11",
165
170
  "jsdoc-typeof-plugin": "1.0.0",
166
171
  "json-server": "0.17.4",
@@ -170,12 +175,11 @@
170
175
  "puppeteer": "24.15.0",
171
176
  "qrcode-terminal": "0.12.0",
172
177
  "rosie": "2.1.1",
173
- "runok": "0.9.3",
174
- "semver": "7.7.2",
178
+ "runok": "^0.9.3",
179
+ "semver": "7.7.3",
175
180
  "sinon": "21.0.0",
176
181
  "sinon-chai": "3.7.0",
177
- "testcafe": "3.7.2",
178
- "ts-morph": "26.0.0",
182
+ "ts-morph": "27.0.2",
179
183
  "ts-node": "10.9.2",
180
184
  "tsd": "^0.33.0",
181
185
  "tsd-jsdoc": "2.5.0",
@@ -1,7 +1,7 @@
1
- const { gherkinTranslations } = require('./utils')
1
+ import { gherkinTranslations } from './utils.js'
2
2
  const langCode = 'de'
3
3
 
4
- module.exports = {
4
+ export default {
5
5
  I: 'Ich',
6
6
  contexts: {
7
7
  ...gherkinTranslations(langCode),
@@ -1,7 +1,7 @@
1
- const { gherkinTranslations } = require('./utils')
1
+ import { gherkinTranslations } from './utils.js'
2
2
  const langCode = 'fr'
3
3
 
4
- module.exports = {
4
+ export default {
5
5
  I: 'Je',
6
6
  contexts: {
7
7
  ...gherkinTranslations(langCode),
@@ -1,10 +1,23 @@
1
- exports['de-DE'] = require('./de-DE')
2
- exports['it-IT'] = require('./it-IT')
3
- exports['fr-FR'] = require('./fr-FR')
4
- exports['ja-JP'] = require('./ja-JP')
5
- exports['pl-PL'] = require('./pl-PL')
6
- exports['pt-BR'] = require('./pt-BR')
7
- exports['ru-RU'] = require('./ru-RU')
8
- exports['zh-CN'] = require('./zh-CN')
9
- exports['zh-TW'] = require('./zh-TW')
10
- exports['nl-NL'] = require('./nl-NL')
1
+ import deDE from './de-DE.js'
2
+ import itIT from './it-IT.js'
3
+ import frFR from './fr-FR.js'
4
+ import jaJP from './ja-JP.js'
5
+ import plPL from './pl-PL.js'
6
+ import ptBR from './pt-BR.js'
7
+ import ruRU from './ru-RU.js'
8
+ import zhCN from './zh-CN.js'
9
+ import zhTW from './zh-TW.js'
10
+ import nlNL from './nl-NL.js'
11
+
12
+ export default {
13
+ 'de-DE': deDE,
14
+ 'it-IT': itIT,
15
+ 'fr-FR': frFR,
16
+ 'ja-JP': jaJP,
17
+ 'pl-PL': plPL,
18
+ 'pt-BR': ptBR,
19
+ 'ru-RU': ruRU,
20
+ 'zh-CN': zhCN,
21
+ 'zh-TW': zhTW,
22
+ 'nl-NL': nlNL
23
+ }