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/container.js CHANGED
@@ -1,16 +1,19 @@
1
- const { globSync } = require('glob')
2
- const path = require('path')
3
- const debug = require('debug')('codeceptjs:container')
4
- const { MetaStep } = require('./step')
5
- const { methodsOfObject, fileExists, isFunction, isAsyncFunction, installedLocally } = require('./utils')
6
- const Translation = require('./translation')
7
- const MochaFactory = require('./mocha/factory')
8
- const recorder = require('./recorder')
9
- const event = require('./event')
10
- const WorkerStorage = require('./workerStorage')
11
- const store = require('./store')
12
- const Result = require('./result')
13
- const ai = require('./ai')
1
+ import { globSync } from 'glob'
2
+ import path from 'path'
3
+ import fs from 'fs'
4
+ import debugModule from 'debug'
5
+ const debug = debugModule('codeceptjs:container')
6
+ import { MetaStep } from './step.js'
7
+ import { methodsOfObject, fileExists, isFunction, isAsyncFunction, installedLocally, deepMerge } from './utils.js'
8
+ import Translation from './translation.js'
9
+ import MochaFactory from './mocha/factory.js'
10
+ import recorder from './recorder.js'
11
+ import event from './event.js'
12
+ import WorkerStorage from './workerStorage.js'
13
+ import store from './store.js'
14
+ import Result from './result.js'
15
+ import ai from './ai.js'
16
+ import actorFactory from './actor.js'
14
17
 
15
18
  let asyncHelperPromise
16
19
 
@@ -40,7 +43,7 @@ class Container {
40
43
  *
41
44
  */
42
45
  static get STANDARD_ACTING_HELPERS() {
43
- return ['Playwright', 'WebDriver', 'Puppeteer', 'Appium', 'TestCafe']
46
+ return ['Playwright', 'WebDriver', 'Puppeteer', 'Appium']
44
47
  }
45
48
  /**
46
49
  * Create container with all required helpers and support objects
@@ -49,7 +52,7 @@ class Container {
49
52
  * @param {*} config
50
53
  * @param {*} opts
51
54
  */
52
- static create(config, opts) {
55
+ static async create(config, opts) {
53
56
  debug('creating container')
54
57
  asyncHelperPromise = Promise.resolve()
55
58
 
@@ -61,16 +64,53 @@ class Container {
61
64
 
62
65
  // create support objects
63
66
  container.support = {}
64
- container.helpers = createHelpers(config.helpers || {})
65
- container.translation = loadTranslation(config.translation || null, config.vocabularies || [])
67
+ container.helpers = await createHelpers(config.helpers || {})
68
+ container.translation = await loadTranslation(config.translation || null, config.vocabularies || [])
66
69
  container.proxySupport = createSupportObjects(config.include || {})
67
- container.plugins = createPlugins(config.plugins || {}, opts)
70
+ container.plugins = await createPlugins(config.plugins || {}, opts)
68
71
  container.result = new Result()
69
72
 
70
- createActor(config.include?.I)
73
+ // Preload includes (so proxies can expose real objects synchronously)
74
+ const includes = config.include || {}
75
+
76
+ // Ensure I is available for DI modules at import time
77
+ if (Object.prototype.hasOwnProperty.call(includes, 'I')) {
78
+ try {
79
+ const mod = includes.I
80
+ if (typeof mod === 'string') {
81
+ container.support.I = await loadSupportObject(mod, 'I')
82
+ } else if (typeof mod === 'function') {
83
+ container.support.I = await loadSupportObject(mod, 'I')
84
+ } else if (mod && typeof mod === 'object') {
85
+ container.support.I = mod
86
+ }
87
+ } catch (e) {
88
+ throw new Error(`Could not include object I: ${e.message}`)
89
+ }
90
+ } else {
91
+ // Create default actor if not provided via includes
92
+ createActor()
93
+ }
94
+
95
+ // Load remaining includes except I
96
+ for (const [name, mod] of Object.entries(includes)) {
97
+ if (name === 'I') continue
98
+ try {
99
+ if (typeof mod === 'string') {
100
+ container.support[name] = await loadSupportObject(mod, name)
101
+ } else if (typeof mod === 'function') {
102
+ // function or class
103
+ container.support[name] = await loadSupportObject(mod, name)
104
+ } else if (mod && typeof mod === 'object') {
105
+ container.support[name] = mod
106
+ }
107
+ } catch (e) {
108
+ throw new Error(`Could not include object ${name}: ${e.message}`)
109
+ }
110
+ }
71
111
 
72
112
  if (opts && opts.ai) ai.enable(config.ai) // enable AI Assistant
73
- if (config.gherkin) loadGherkinSteps(config.gherkin.steps || [])
113
+ if (config.gherkin) await loadGherkinStepsAsync(config.gherkin.steps || [])
74
114
  if (opts && typeof opts.timeouts === 'boolean') store.timeouts = opts.timeouts
75
115
  }
76
116
 
@@ -103,7 +143,8 @@ class Container {
103
143
  if (!name) {
104
144
  return container.proxySupport
105
145
  }
106
- return container.support[name] || container.proxySupport[name]
146
+ // Always return the proxy to ensure MetaStep creation works
147
+ return container.proxySupport[name]
107
148
  }
108
149
 
109
150
  /**
@@ -158,8 +199,14 @@ class Container {
158
199
  * @param {Object<string, *>} newContainer
159
200
  */
160
201
  static append(newContainer) {
161
- const deepMerge = require('./utils').deepMerge
162
202
  container = deepMerge(container, newContainer)
203
+
204
+ // If new support objects are added, update the proxy support
205
+ if (newContainer.support) {
206
+ const newProxySupport = createSupportObjects(newContainer.support)
207
+ container.proxySupport = { ...container.proxySupport, ...newProxySupport }
208
+ }
209
+
163
210
  debug('appended', JSON.stringify(newContainer).slice(0, 300))
164
211
  }
165
212
 
@@ -170,9 +217,9 @@ class Container {
170
217
  * @param {Object<string, *>} newSupport
171
218
  * @param {Object<string, *>} newPlugins
172
219
  */
173
- static clear(newHelpers = {}, newSupport = {}, newPlugins = {}) {
220
+ static async clear(newHelpers = {}, newSupport = {}, newPlugins = {}) {
174
221
  container.helpers = newHelpers
175
- container.translation = loadTranslation()
222
+ container.translation = await loadTranslation()
176
223
  container.proxySupport = createSupportObjects(newSupport)
177
224
  container.plugins = newPlugins
178
225
  container.sharedKeys = new Set() // Clear shared keys
@@ -220,23 +267,56 @@ class Container {
220
267
  }
221
268
  }
222
269
 
223
- module.exports = Container
270
+ export default Container
224
271
 
225
- function createHelpers(config) {
272
+ async function createHelpers(config) {
226
273
  const helpers = {}
227
274
  for (let helperName in config) {
228
275
  try {
229
276
  let HelperClass
230
277
 
231
- // ESM import
232
- if (helperName?.constructor === Function && helperName.prototype) {
278
+ // Check if helper class was stored in config during ESM import processing
279
+ if (config[helperName]._helperClass) {
280
+ HelperClass = config[helperName]._helperClass
281
+ debug(`helper ${helperName} loaded from ESM import`)
282
+ }
283
+
284
+ // ESM import (legacy check)
285
+ if (!HelperClass && typeof helperName === 'function' && helperName.prototype) {
233
286
  HelperClass = helperName
234
287
  helperName = HelperClass.constructor.name
235
288
  }
236
289
 
237
- // classical require
290
+ // classical require - may be async for ESM modules
238
291
  if (!HelperClass) {
239
- HelperClass = requireHelperFromModule(helperName, config)
292
+ const helperResult = requireHelperFromModule(helperName, config)
293
+ if (helperResult instanceof Promise) {
294
+ // Handle async ESM loading
295
+ helpers[helperName] = {}
296
+ asyncHelperPromise = asyncHelperPromise
297
+ .then(() => helperResult)
298
+ .then(async ResolvedHelperClass => {
299
+ debug(`helper ${helperName} resolved type: ${typeof ResolvedHelperClass}`, ResolvedHelperClass)
300
+
301
+ // Extract default export from ESM module wrapper if needed
302
+ if (ResolvedHelperClass && ResolvedHelperClass.__esModule && ResolvedHelperClass.default) {
303
+ ResolvedHelperClass = ResolvedHelperClass.default
304
+ debug(`extracted default export for ${helperName}, new type: ${typeof ResolvedHelperClass}`)
305
+ }
306
+
307
+ if (typeof ResolvedHelperClass !== 'function') {
308
+ throw new Error(`Helper '${helperName}' is not a class. Got: ${typeof ResolvedHelperClass}`)
309
+ }
310
+
311
+ checkHelperRequirements(ResolvedHelperClass)
312
+ helpers[helperName] = new ResolvedHelperClass(config[helperName])
313
+ if (helpers[helperName]._init) await helpers[helperName]._init()
314
+ debug(`helper ${helperName} async initialized`)
315
+ })
316
+ continue
317
+ } else {
318
+ HelperClass = helperResult
319
+ }
240
320
  }
241
321
 
242
322
  // handle async CJS modules that use dynamic import
@@ -269,7 +349,7 @@ function createHelpers(config) {
269
349
  }
270
350
 
271
351
  for (const name in helpers) {
272
- if (helpers[name]._init) helpers[name]._init()
352
+ if (helpers[name]._init) await helpers[name]._init()
273
353
  }
274
354
  return helpers
275
355
  }
@@ -290,23 +370,43 @@ function checkHelperRequirements(HelperClass) {
290
370
  }
291
371
  }
292
372
 
293
- function requireHelperFromModule(helperName, config, HelperClass) {
373
+ async function requireHelperFromModule(helperName, config, HelperClass) {
294
374
  const moduleName = getHelperModuleName(helperName, config)
295
375
  if (moduleName.startsWith('./helper/')) {
296
- HelperClass = require(moduleName)
376
+ try {
377
+ // For built-in helpers, use direct relative import with .js extension
378
+ const helperPath = `${moduleName}.js`
379
+ const mod = await import(helperPath)
380
+ HelperClass = mod.default || mod
381
+ } catch (err) {
382
+ throw err
383
+ }
297
384
  } else {
298
385
  // check if the new syntax export default HelperName is used and loads the Helper, otherwise loads the module that used old syntax export = HelperName.
299
386
  try {
300
- const mod = require(moduleName)
387
+ // Try dynamic import for both CommonJS and ESM modules
388
+ const mod = await import(moduleName)
301
389
  if (!mod && !mod.default) {
302
390
  throw new Error(`Helper module '${moduleName}' was not found. Make sure you have installed the package correctly.`)
303
391
  }
304
392
  HelperClass = mod.default || mod
305
393
  } catch (err) {
306
- if (err.code === 'MODULE_NOT_FOUND') {
394
+ if (err.code === 'ERR_REQUIRE_ESM' || (err.message && err.message.includes('ES module'))) {
395
+ // This is an ESM module, use dynamic import
396
+ try {
397
+ const pathModule = await import('path')
398
+ const absolutePath = pathModule.default.resolve(moduleName)
399
+ const mod = await import(absolutePath)
400
+ HelperClass = mod.default || mod
401
+ debug(`helper ${helperName} loaded via ESM import`)
402
+ } catch (importErr) {
403
+ throw new Error(`Helper module '${moduleName}' could not be imported as ESM: ${importErr.message}`)
404
+ }
405
+ } else if (err.code === 'MODULE_NOT_FOUND') {
307
406
  throw new Error(`Helper module '${moduleName}' was not found. Make sure you have installed the package correctly.`)
407
+ } else {
408
+ throw err
308
409
  }
309
- throw err
310
410
  }
311
411
  }
312
412
  return HelperClass
@@ -338,14 +438,21 @@ function createSupportObjects(config) {
338
438
  }
339
439
 
340
440
  // load actual name from vocabulary
341
- if (container.translation.name) {
342
- name = container.translation.name
441
+ if (container.translation && container.translation.I && name === 'I') {
442
+ // Use translated name for I
443
+ const actualName = container.translation.I
444
+ if (actualName !== 'I') {
445
+ name = actualName
446
+ }
343
447
  }
344
448
 
345
449
  if (name === 'I') {
346
- const actor = createActor(config.I)
347
- methodsOfObject(actor)
348
- return actor[prop]
450
+ if (!container.support.I) {
451
+ // Actor will be created during container.create()
452
+ return undefined
453
+ }
454
+ methodsOfObject(container.support.I)
455
+ return container.support.I[prop]
349
456
  }
350
457
 
351
458
  if (!container.support[name] && typeof config[name] === 'object') {
@@ -353,17 +460,10 @@ function createSupportObjects(config) {
353
460
  }
354
461
 
355
462
  if (!container.support[name]) {
356
- // Load object on first access
357
- const supportObject = loadSupportObject(config[name])
358
- container.support[name] = supportObject
359
- try {
360
- if (container.support[name]._init) {
361
- container.support[name]._init()
362
- }
363
- debug(`support object ${name} initialized`)
364
- } catch (err) {
365
- throw new Error(`Initialization failed for ${name}: ${container.support[name]}\n${err.message}\n${err.stack}`)
366
- }
463
+ // Cannot load object synchronously in proxy getter
464
+ // Return undefined and log warning - object should be pre-loaded during container creation
465
+ debug(`Support object ${name} not pre-loaded, returning undefined`)
466
+ return undefined
367
467
  }
368
468
 
369
469
  const currentObject = container.support[name]
@@ -380,19 +480,32 @@ function createSupportObjects(config) {
380
480
  return currentValue
381
481
  },
382
482
  has(target, prop) {
383
- container.support[name] = container.support[name] || loadSupportObject(config[name])
483
+ if (!container.support[name]) {
484
+ // Note: This is sync, so we can't use async loadSupportObject here
485
+ // The object will be loaded lazily on first property access
486
+ return false
487
+ }
384
488
  return prop in container.support[name]
385
489
  },
386
490
  getOwnPropertyDescriptor(target, prop) {
387
- container.support[name] = container.support[name] || loadSupportObject(config[name])
491
+ if (!container.support[name]) {
492
+ // Object will be loaded on property access
493
+ return {
494
+ enumerable: true,
495
+ configurable: true,
496
+ value: undefined,
497
+ }
498
+ }
388
499
  return {
389
500
  enumerable: true,
390
501
  configurable: true,
391
- value: this.get(target, prop),
502
+ value: container.support[name][prop],
392
503
  }
393
504
  },
394
505
  ownKeys() {
395
- container.support[name] = container.support[name] || loadSupportObject(config[name])
506
+ if (!container.support[name]) {
507
+ return []
508
+ }
396
509
  return Reflect.ownKeys(container.support[name])
397
510
  },
398
511
  },
@@ -414,7 +527,7 @@ function createSupportObjects(config) {
414
527
  return {
415
528
  enumerable: true,
416
529
  configurable: true,
417
- value: this.get(target, prop),
530
+ value: target[prop],
418
531
  }
419
532
  },
420
533
  get(target, key) {
@@ -431,17 +544,35 @@ function createSupportObjects(config) {
431
544
  function createActor(actorPath) {
432
545
  if (container.support.I) return container.support.I
433
546
 
434
- if (actorPath) {
435
- container.support.I = loadSupportObject(actorPath)
436
- } else {
437
- const actor = require('./actor')
438
- container.support.I = actor()
439
- }
547
+ // Default actor
548
+ container.support.I = actorFactory({}, Container)
440
549
 
441
550
  return container.support.I
442
551
  }
443
552
 
444
- function createPlugins(config, options = {}) {
553
+ async function loadPluginAsync(modulePath, config) {
554
+ let pluginMod
555
+ try {
556
+ // Try dynamic import first (works for both ESM and CJS)
557
+ pluginMod = await import(modulePath)
558
+ } catch (err) {
559
+ throw new Error(`Could not load plugin from '${modulePath}': ${err.message}`)
560
+ }
561
+
562
+ const pluginFactory = pluginMod.default || pluginMod
563
+ if (typeof pluginFactory !== 'function') {
564
+ throw new Error(`Plugin '${modulePath}' is not a function. Expected a plugin factory function.`)
565
+ }
566
+
567
+ return pluginFactory(config)
568
+ }
569
+
570
+ async function loadPluginFallback(modulePath, config) {
571
+ // This function is kept for backwards compatibility but now uses dynamic import
572
+ return await loadPluginAsync(modulePath, config)
573
+ }
574
+
575
+ async function createPlugins(config, options = {}) {
445
576
  const plugins = {}
446
577
 
447
578
  const enabledPluginsByOptions = (options.plugins || '').split(',')
@@ -459,9 +590,12 @@ function createPlugins(config, options = {}) {
459
590
  module = path.resolve(global.codecept_dir, module) // custom plugin
460
591
  }
461
592
  } else {
462
- module = `./plugin/${pluginName}`
593
+ module = `./plugin/${pluginName}.js`
463
594
  }
464
- plugins[pluginName] = require(module)(config[pluginName])
595
+
596
+ // Use async loading for all plugins (ESM and CJS)
597
+ plugins[pluginName] = await loadPluginAsync(module, config[pluginName])
598
+ debug(`plugin ${pluginName} loaded via async import`)
465
599
  } catch (err) {
466
600
  throw new Error(`Could not load plugin ${pluginName} from module '${module}':\n${err.message}\n${err.stack}`)
467
601
  }
@@ -469,24 +603,34 @@ function createPlugins(config, options = {}) {
469
603
  return plugins
470
604
  }
471
605
 
472
- function loadGherkinSteps(paths) {
606
+ async function loadGherkinStepsAsync(paths) {
473
607
  global.Before = fn => event.dispatcher.on(event.test.started, fn)
474
608
  global.After = fn => event.dispatcher.on(event.test.finished, fn)
475
609
  global.Fail = fn => event.dispatcher.on(event.test.failed, fn)
476
610
 
611
+ // Import BDD module to access step file tracking functions
612
+ const bddModule = await import('./mocha/bdd.js')
613
+
477
614
  // If gherkin.steps is string, then this will iterate through that folder and send all step def js files to loadSupportObject
478
615
  // If gherkin.steps is Array, it will go the old way
479
616
  // This is done so that we need not enter all Step Definition files under config.gherkin.steps
480
617
  if (Array.isArray(paths)) {
481
618
  for (const path of paths) {
482
- loadSupportObject(path, `Step Definition from ${path}`)
619
+ // Set context for step definition file location tracking
620
+ bddModule.setCurrentStepFile(path)
621
+ await loadSupportObject(path, `Step Definition from ${path}`)
622
+ bddModule.clearCurrentStepFile()
483
623
  }
484
624
  } else {
485
625
  const folderPath = paths.startsWith('.') ? normalizeAndJoin(global.codecept_dir, paths) : ''
486
626
  if (folderPath !== '') {
487
- globSync(folderPath).forEach(file => {
488
- loadSupportObject(file, `Step Definition from ${file}`)
489
- })
627
+ const files = globSync(folderPath)
628
+ for (const file of files) {
629
+ // Set context for step definition file location tracking
630
+ bddModule.setCurrentStepFile(file)
631
+ await loadSupportObject(file, `Step Definition from ${file}`)
632
+ bddModule.clearCurrentStepFile()
633
+ }
490
634
  }
491
635
  }
492
636
 
@@ -495,47 +639,248 @@ function loadGherkinSteps(paths) {
495
639
  delete global.Fail
496
640
  }
497
641
 
498
- function loadSupportObject(modulePath, supportObjectName) {
642
+ function loadGherkinSteps(paths) {
643
+ global.Before = fn => event.dispatcher.on(event.test.started, fn)
644
+ global.After = fn => event.dispatcher.on(event.test.finished, fn)
645
+ global.Fail = fn => event.dispatcher.on(event.test.failed, fn)
646
+
647
+ // Gherkin step loading must be handled asynchronously
648
+ throw new Error('Gherkin step loading must be converted to async. Use loadGherkinStepsAsync() instead.')
649
+
650
+ delete global.Before
651
+ delete global.After
652
+ delete global.Fail
653
+ }
654
+
655
+ async function loadSupportObject(modulePath, supportObjectName) {
499
656
  if (!modulePath) {
500
657
  throw new Error(`Support object "${supportObjectName}" is not defined`)
501
658
  }
502
- if (modulePath.charAt(0) === '.') {
659
+ // If function/class provided directly
660
+ if (typeof modulePath === 'function') {
661
+ try {
662
+ // class constructor
663
+ if (modulePath.prototype && modulePath.prototype.constructor === modulePath) {
664
+ return new modulePath()
665
+ }
666
+ // plain function factory
667
+ return modulePath()
668
+ } catch (err) {
669
+ throw new Error(`Could not include object ${supportObjectName} from function: ${err.message}`)
670
+ }
671
+ }
672
+ if (typeof modulePath === 'string' && modulePath.charAt(0) === '.') {
503
673
  modulePath = path.join(global.codecept_dir, modulePath)
504
674
  }
505
675
  try {
506
- const obj = require(modulePath)
676
+ // Use dynamic import for both ESM and CJS modules
677
+ let importPath = modulePath
678
+ let tempJsFile = null
679
+
680
+ if (typeof importPath === 'string') {
681
+ const ext = path.extname(importPath)
682
+
683
+ // Handle TypeScript files
684
+ if (ext === '.ts') {
685
+ try {
686
+ const { transpile } = await import('typescript')
687
+
688
+ // Recursively transpile the file and its dependencies
689
+ const transpileTS = (filePath) => {
690
+ const tsContent = fs.readFileSync(filePath, 'utf8')
691
+
692
+ // Transpile TypeScript to JavaScript with ES module output
693
+ const jsContent = transpile(tsContent, {
694
+ module: 99, // ModuleKind.ESNext
695
+ target: 99, // ScriptTarget.ESNext
696
+ esModuleInterop: true,
697
+ allowSyntheticDefaultImports: true,
698
+ })
699
+
700
+ return jsContent
701
+ }
702
+
703
+ // Create a map to track transpiled files
704
+ const transpiledFiles = new Map()
705
+ const baseDir = path.dirname(importPath)
706
+
707
+ // Transpile main file
708
+ let jsContent = transpileTS(importPath)
709
+
710
+ // Find and transpile all relative TypeScript imports
711
+ // Match: import ... from './file' or '../file' or './file.ts'
712
+ const importRegex = /from\s+['"](\..+?)(?:\.ts)?['"]/g
713
+ let match
714
+ const imports = []
715
+
716
+ while ((match = importRegex.exec(jsContent)) !== null) {
717
+ imports.push(match[1])
718
+ }
719
+
720
+ // Transpile each imported TypeScript file
721
+ for (const relativeImport of imports) {
722
+ let importedPath = path.resolve(baseDir, relativeImport)
723
+
724
+ // Handle .js extensions that might actually be .ts files
725
+ if (importedPath.endsWith('.js')) {
726
+ const tsVersion = importedPath.replace(/\.js$/, '.ts')
727
+ if (fs.existsSync(tsVersion)) {
728
+ importedPath = tsVersion
729
+ }
730
+ }
731
+
732
+ // Try adding .ts extension if file doesn't exist and no extension provided
733
+ if (!path.extname(importedPath)) {
734
+ if (fs.existsSync(importedPath + '.ts')) {
735
+ importedPath = importedPath + '.ts'
736
+ }
737
+ }
738
+
739
+ // If it's a TypeScript file, transpile it
740
+ if (importedPath.endsWith('.ts') && fs.existsSync(importedPath)) {
741
+ const transpiledImportContent = transpileTS(importedPath)
742
+ const tempImportFile = importedPath.replace(/\.ts$/, '.temp.mjs')
743
+ fs.writeFileSync(tempImportFile, transpiledImportContent)
744
+ transpiledFiles.set(importedPath, tempImportFile)
745
+ debug(`Transpiled dependency: ${importedPath} -> ${tempImportFile}`)
746
+ }
747
+ }
748
+
749
+ // Replace imports in the main file to point to temp .mjs files
750
+ jsContent = jsContent.replace(
751
+ /from\s+['"](\..+?)(?:\.ts)?['"]/g,
752
+ (match, importPath) => {
753
+ let resolvedPath = path.resolve(baseDir, importPath)
754
+
755
+ // Handle .js extension that might be .ts
756
+ if (resolvedPath.endsWith('.js')) {
757
+ const tsVersion = resolvedPath.replace(/\.js$/, '.ts')
758
+ if (transpiledFiles.has(tsVersion)) {
759
+ const tempFile = transpiledFiles.get(tsVersion)
760
+ const relPath = path.relative(baseDir, tempFile).replace(/\\/g, '/')
761
+ return `from './${relPath}'`
762
+ }
763
+ }
764
+
765
+ // Try with .ts extension
766
+ const tsPath = resolvedPath.endsWith('.ts') ? resolvedPath : resolvedPath + '.ts'
767
+
768
+ // If we transpiled this file, use the temp file
769
+ if (transpiledFiles.has(tsPath)) {
770
+ const tempFile = transpiledFiles.get(tsPath)
771
+ // Get relative path from main temp file to this temp file
772
+ const relPath = path.relative(baseDir, tempFile).replace(/\\/g, '/')
773
+ return `from './${relPath}'`
774
+ }
775
+
776
+ // Otherwise, keep the import as-is
777
+ return match
778
+ }
779
+ )
780
+
781
+ // Create a temporary JS file with .mjs extension for the main file
782
+ tempJsFile = importPath.replace(/\.ts$/, '.temp.mjs')
783
+ fs.writeFileSync(tempJsFile, jsContent)
784
+
785
+ // Store all temp files for cleanup
786
+ const allTempFiles = [tempJsFile, ...Array.from(transpiledFiles.values())]
787
+
788
+ // Attach cleanup handler
789
+ importPath = tempJsFile
790
+ // Store temp files list in a way that cleanup can access them
791
+ tempJsFile = allTempFiles
792
+
793
+ } catch (tsError) {
794
+ throw new Error(`Failed to load TypeScript file ${importPath}: ${tsError.message}. Make sure 'typescript' package is installed.`)
795
+ }
796
+ } else if (!ext) {
797
+ // Append .js if no extension provided (ESM resolution requires it)
798
+ importPath = `${importPath}.js`
799
+ }
800
+ }
801
+
802
+ let obj
803
+ try {
804
+ obj = await import(importPath)
805
+ } catch (importError) {
806
+ // Clean up temp files if created before rethrowing
807
+ if (tempJsFile) {
808
+ const filesToClean = Array.isArray(tempJsFile) ? tempJsFile : [tempJsFile]
809
+ for (const file of filesToClean) {
810
+ try {
811
+ if (fs.existsSync(file)) {
812
+ fs.unlinkSync(file)
813
+ }
814
+ } catch (cleanupError) {
815
+ // Ignore cleanup errors
816
+ }
817
+ }
818
+ }
819
+ throw importError
820
+ } finally {
821
+ // Clean up temp files if created
822
+ if (tempJsFile) {
823
+ const filesToClean = Array.isArray(tempJsFile) ? tempJsFile : [tempJsFile]
824
+ for (const file of filesToClean) {
825
+ try {
826
+ if (fs.existsSync(file)) {
827
+ fs.unlinkSync(file)
828
+ }
829
+ } catch (cleanupError) {
830
+ // Ignore cleanup errors
831
+ }
832
+ }
833
+ }
834
+ }
835
+
836
+ // Handle ESM module wrapper
837
+ let actualObj = obj
838
+ if (obj && obj.__esModule && obj.default) {
839
+ actualObj = obj.default
840
+ } else if (obj.default) {
841
+ actualObj = obj.default
842
+ }
507
843
 
508
844
  // Handle different types of imports
509
- if (typeof obj === 'function') {
845
+ if (typeof actualObj === 'function') {
510
846
  // If it's a class (constructor function)
511
- if (obj.prototype && obj.prototype.constructor === obj) {
512
- const ClassName = obj
847
+ if (actualObj.prototype && actualObj.prototype.constructor === actualObj) {
848
+ const ClassName = actualObj
513
849
  return new ClassName()
514
850
  }
515
851
  // If it's a regular function
516
- return obj()
852
+ return actualObj()
517
853
  }
518
854
 
519
- if (obj && Array.isArray(obj)) {
520
- return obj
855
+ if (actualObj && Array.isArray(actualObj)) {
856
+ return actualObj
521
857
  }
522
858
 
523
859
  // If it's a plain object
524
- if (obj && typeof obj === 'object') {
525
- return obj
860
+ if (actualObj && typeof actualObj === 'object') {
861
+ // Call _init if it exists (for page objects)
862
+ if (actualObj._init && typeof actualObj._init === 'function') {
863
+ actualObj._init()
864
+ }
865
+ return actualObj
526
866
  }
527
867
 
528
- throw new Error(`Support object "${supportObjectName}" should be an object, class, or function, but got ${typeof obj}`)
868
+ throw new Error(`Support object "${supportObjectName}" should be an object, class, or function, but got ${typeof actualObj}`)
529
869
  } catch (err) {
530
870
  throw new Error(`Could not include object ${supportObjectName} from module '${modulePath}'\n${err.message}\n${err.stack}`)
531
871
  }
532
872
  }
533
873
 
874
+ // Backwards compatibility function that throws an error for sync usage
875
+ function loadSupportObjectSync(modulePath, supportObjectName) {
876
+ throw new Error(`loadSupportObjectSync is deprecated. Support object "${supportObjectName || 'undefined'}" from '${modulePath}' must be loaded asynchronously. Use loadSupportObject() instead.`)
877
+ }
878
+
534
879
  /**
535
880
  * Method collect own property and prototype
536
881
  */
537
882
 
538
- function loadTranslation(locale, vocabularies) {
883
+ async function loadTranslation(locale, vocabularies) {
539
884
  if (!locale) {
540
885
  return Translation.createEmpty()
541
886
  }
@@ -543,8 +888,9 @@ function loadTranslation(locale, vocabularies) {
543
888
  let translation
544
889
 
545
890
  // check if it is a known translation
546
- if (Translation.langs[locale]) {
547
- translation = new Translation(Translation.langs[locale])
891
+ const langs = await Translation.getLangs()
892
+ if (langs[locale]) {
893
+ translation = new Translation(langs[locale])
548
894
  } else if (fileExists(path.join(global.codecept_dir, locale))) {
549
895
  // get from a provided file instead
550
896
  translation = Translation.createDefault()
@@ -562,7 +908,12 @@ function getHelperModuleName(helperName, config) {
562
908
  // classical require
563
909
  if (config[helperName].require) {
564
910
  if (config[helperName].require.startsWith('.')) {
565
- return path.resolve(global.codecept_dir, config[helperName].require) // custom helper
911
+ let helperPath = path.resolve(global.codecept_dir, config[helperName].require)
912
+ // Add .js extension if not present for ESM compatibility
913
+ if (!path.extname(helperPath)) {
914
+ helperPath += '.js'
915
+ }
916
+ return helperPath // custom helper
566
917
  }
567
918
  return config[helperName].require // plugin helper
568
919
  }