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,10 +1,10 @@
1
- const promiseRetry = require('promise-retry')
2
- const event = require('../event')
3
- const recorder = require('../recorder')
4
- const assertThrown = require('../assert/throws')
5
- const { ucfirst, isAsyncFunction } = require('../utils')
6
- const { getInjectedArguments } = require('./inject')
7
- const { fireHook } = require('./hooks')
1
+ import promiseRetry from 'promise-retry'
2
+ import event from '../event.js'
3
+ import recorder from '../recorder.js'
4
+ import assertThrown from '../assert/throws.js'
5
+ import { ucfirst, isAsyncFunction } from '../utils.js'
6
+ import { getInjectedArguments } from './inject.js'
7
+ import { fireHook } from './hooks.js'
8
8
 
9
9
  const injectHook = function (inject, suite) {
10
10
  try {
@@ -43,7 +43,7 @@ function makeDoneCallableOnce(done) {
43
43
  * starts promise chain with recorder, performs before/after hooks
44
44
  * through event system.
45
45
  */
46
- module.exports.test = test => {
46
+ export function test(test) {
47
47
  const testFn = test.fn
48
48
  if (!testFn) {
49
49
  return test
@@ -75,39 +75,35 @@ module.exports.test = test => {
75
75
  recorder.add(() => doneFn(err))
76
76
  })
77
77
 
78
- if (isAsyncFunction(testFn)) {
79
- event.emit(event.test.started, test)
80
- testFn
81
- .call(test, getInjectedArguments(testFn, test))
82
- .then(() => {
83
- recorder.add('fire test.passed', () => {
84
- event.emit(event.test.passed, test)
85
- event.emit(event.test.finished, test)
86
- })
87
- recorder.add('finish test', doneFn)
88
- })
89
- .catch(err => {
90
- recorder.throw(err)
91
- })
92
- .finally(() => {
93
- recorder.catch()
94
- })
95
- return
96
- }
78
+ event.emit(event.test.started, test)
79
+
80
+ getInjectedArguments(testFn, test)
81
+ .then(args => {
82
+ // Start recorder to ensure any steps added within test function are executed
83
+ recorder.startUnlessRunning()
84
+
85
+ // Execute test function
86
+ const result = testFn.call(test, args)
97
87
 
98
- try {
99
- event.emit(event.test.started, test)
100
- testFn.call(test, getInjectedArguments(testFn, test))
101
- } catch (err) {
102
- recorder.throw(err)
103
- } finally {
104
- recorder.add('fire test.passed', () => {
105
- event.emit(event.test.passed, test)
106
- event.emit(event.test.finished, test)
88
+ // Wait for all recorder steps to complete
89
+ if (result && result.then) {
90
+ return result.then(() => recorder.promise())
91
+ }
92
+ return recorder.promise()
93
+ })
94
+ .then(() => {
95
+ recorder.add('fire test.passed', () => {
96
+ event.emit(event.test.passed, test)
97
+ event.emit(event.test.finished, test)
98
+ })
99
+ recorder.add('finish test', doneFn)
100
+ })
101
+ .catch(err => {
102
+ recorder.throw(err)
103
+ })
104
+ .finally(() => {
105
+ recorder.catch()
107
106
  })
108
- recorder.add('finish test', doneFn)
109
- recorder.catch()
110
- }
111
107
  }
112
108
  return test
113
109
  }
@@ -115,25 +111,15 @@ module.exports.test = test => {
115
111
  /**
116
112
  * Injects arguments to function from controller
117
113
  */
118
- module.exports.injected = function (fn, suite, hookName) {
114
+ export function injected(fn, suite, hookName) {
119
115
  return function (done) {
120
116
  const doneFn = makeDoneCallableOnce(done)
121
117
  const errHandler = err => {
122
118
  recorder.session.start('teardown')
123
119
  recorder.cleanAsyncErr()
124
- if (['before', 'beforeSuite'].includes(hookName)) {
125
- suiteTestFailedHookError(suite, err, hookName)
126
- }
127
- if (hookName === 'after') {
128
- suiteTestFailedHookError(suite, err, hookName)
129
- suite.eachTest(test => {
130
- event.emit(event.test.after, test)
131
- })
132
- }
133
- if (hookName === 'afterSuite') {
134
- suiteTestFailedHookError(suite, err, hookName)
135
- event.emit(event.suite.after, suite)
136
- }
120
+ if (hookName == 'before' || hookName == 'beforeSuite') suiteTestFailedHookError(suite, err, hookName)
121
+ if (hookName === 'after') suite.eachTest(test => event.emit(event.test.after, test))
122
+ if (hookName === 'afterSuite') event.emit(event.suite.after, suite)
137
123
  recorder.add(() => doneFn(err))
138
124
  }
139
125
 
@@ -162,7 +148,8 @@ module.exports.injected = function (fn, suite, hookName) {
162
148
  async (retry, number) => {
163
149
  try {
164
150
  recorder.startUnlessRunning()
165
- await fn.call(this, { ...getInjectedArguments(fn), suite, test: currentTest })
151
+ const injectedArgs = await getInjectedArguments(fn, null, suite)
152
+ await fn.call(this, { ...injectedArgs, suite, test: currentTest })
166
153
  await recorder.promise().catch(err => retry(err))
167
154
  } catch (err) {
168
155
  retry(err)
@@ -196,36 +183,82 @@ module.exports.injected = function (fn, suite, hookName) {
196
183
  /**
197
184
  * Starts promise chain, so helpers could enqueue their hooks
198
185
  */
199
- module.exports.setup = function (suite) {
200
- const { enhanceMochaTest } = require('./test')
201
- return injectHook(() => {
186
+ export function setup(suite) {
187
+ return function (done) {
188
+ const doneFn = makeDoneCallableOnce(done)
202
189
  recorder.startUnlessRunning()
203
- event.emit(event.test.before, enhanceMochaTest(suite?.ctx?.currentTest))
204
- }, suite)
190
+ import('./test.js').then(testModule => {
191
+ const { enhanceMochaTest } = testModule.default || testModule
192
+ event.emit(event.test.before, enhanceMochaTest(suite?.ctx?.currentTest))
193
+ recorder.add(() => doneFn())
194
+ })
195
+ }
205
196
  }
206
197
 
207
- module.exports.teardown = function (suite) {
208
- const { enhanceMochaTest } = require('./test')
209
- return injectHook(() => {
198
+ export function teardown(suite) {
199
+ return function (done) {
200
+ const doneFn = makeDoneCallableOnce(done)
210
201
  recorder.startUnlessRunning()
211
- event.emit(event.test.after, enhanceMochaTest(suite?.ctx?.currentTest))
212
- }, suite)
202
+ import('./test.js').then(testModule => {
203
+ const { enhanceMochaTest } = testModule.default || testModule
204
+ event.emit(event.test.after, enhanceMochaTest(suite?.ctx?.currentTest))
205
+ recorder.add(() => doneFn())
206
+ })
207
+ }
213
208
  }
214
209
 
215
- module.exports.suiteSetup = function (suite) {
216
- const { enhanceMochaSuite } = require('./suite')
217
- return injectHook(() => {
210
+ export function suiteSetup(suite) {
211
+ return function (done) {
212
+ const doneFn = makeDoneCallableOnce(done)
218
213
  recorder.startUnlessRunning()
219
- event.emit(event.suite.before, enhanceMochaSuite(suite))
220
- }, suite)
214
+
215
+ // Set up error handler for suite setup
216
+ recorder.errHandler(err => {
217
+ doneFn(err)
218
+ })
219
+
220
+ import('./suite.js')
221
+ .then(suiteModule => {
222
+ const { enhanceMochaSuite } = suiteModule.default || suiteModule
223
+ event.emit(event.suite.before, enhanceMochaSuite(suite))
224
+ recorder.add(() => doneFn())
225
+ })
226
+ .catch(err => {
227
+ doneFn(err)
228
+ })
229
+ }
221
230
  }
222
231
 
223
- module.exports.suiteTeardown = function (suite) {
224
- const { enhanceMochaSuite } = require('./suite')
225
- return injectHook(() => {
232
+ export function suiteTeardown(suite) {
233
+ return function (done) {
234
+ const doneFn = makeDoneCallableOnce(done)
226
235
  recorder.startUnlessRunning()
227
- event.emit(event.suite.after, enhanceMochaSuite(suite))
228
- }, suite)
236
+
237
+ // Set up error handler for suite teardown
238
+ recorder.errHandler(err => {
239
+ doneFn(err)
240
+ })
241
+
242
+ import('./suite.js')
243
+ .then(suiteModule => {
244
+ const { enhanceMochaSuite } = suiteModule.default || suiteModule
245
+ event.emit(event.suite.after, enhanceMochaSuite(suite))
246
+ recorder.add(() => doneFn())
247
+ })
248
+ .catch(err => {
249
+ doneFn(err)
250
+ })
251
+ }
229
252
  }
230
253
 
231
- module.exports.getInjectedArguments = getInjectedArguments
254
+ export { getInjectedArguments }
255
+
256
+ export default {
257
+ test,
258
+ injected,
259
+ setup,
260
+ teardown,
261
+ suiteSetup,
262
+ suiteTeardown,
263
+ getInjectedArguments,
264
+ }
package/lib/mocha/bdd.js CHANGED
@@ -1,27 +1,52 @@
1
- const { CucumberExpression, ParameterTypeRegistry, ParameterType } = require('@cucumber/cucumber-expressions')
2
- const Config = require('../config')
1
+ import { CucumberExpression, ParameterTypeRegistry, ParameterType } from '@cucumber/cucumber-expressions'
2
+ import event from '../event.js'
3
3
 
4
4
  let steps = {}
5
+ let Config
5
6
 
6
7
  const STACK_POSITION = 2
7
8
 
9
+ async function getConfig() {
10
+ if (!Config) {
11
+ const ConfigModule = await import('../config.js')
12
+ Config = ConfigModule.default || ConfigModule
13
+ }
14
+ return Config
15
+ }
16
+
8
17
  /**
9
18
  * @param {*} step
10
19
  * @param {*} fn
11
20
  */
12
- const addStep = (step, fn) => {
13
- const avoidDuplicateSteps = Config.get('gherkin', {}).avoidDuplicateSteps || false
14
- const stack = new Error().stack
21
+ // Current file being loaded for step tracking
22
+ let currentStepFile = null
23
+
24
+ export function setCurrentStepFile(filePath) {
25
+ currentStepFile = filePath
26
+ }
27
+
28
+ export function clearCurrentStepFile() {
29
+ currentStepFile = null
30
+ }
31
+
32
+ const addStep = async (step, fn) => {
33
+ const config = await getConfig()
34
+ const avoidDuplicateSteps = config.get('gherkin', {}).avoidDuplicateSteps || false
15
35
  if (avoidDuplicateSteps && steps[step]) {
16
36
  throw new Error(`Step '${step}' is already defined`)
17
37
  }
18
38
  steps[step] = fn
19
- fn.line = stack && stack.split('\n')[STACK_POSITION]
20
- if (fn.line) {
21
- fn.line = fn.line
22
- .trim()
23
- .replace(/^at (.*?)\(/, '(')
24
- .replace(codecept_dir, '.')
39
+
40
+ // Use the current step file context if available (fallback for old usage)
41
+ if (currentStepFile) {
42
+ let relativePath = currentStepFile
43
+
44
+ // Remove any leading './' and keep step_definitions/ path
45
+ relativePath = relativePath.replace(/^\.\//, '').replace(/^.*\/(?=step_definitions)/, '')
46
+
47
+ fn.line = `${relativePath}:3:1`
48
+ } else {
49
+ fn.line = 'unknown_file:1:1'
25
50
  }
26
51
  }
27
52
 
@@ -43,7 +68,7 @@ const matchStep = step => {
43
68
  const res = expression.match(step)
44
69
  if (res) {
45
70
  const fn = steps[stepName]
46
- fn.params = res.map(arg => arg.getValue())
71
+ fn.params = res.map(arg => arg.getValue(null))
47
72
  return fn
48
73
  }
49
74
  }
@@ -69,7 +94,68 @@ const buildParameterType = ({ name, regexp, transformer, useForSnippets, preferF
69
94
  return new ParameterType(name, regexp, null, transformer, useForSnippets, preferForRegexpMatch)
70
95
  }
71
96
 
72
- module.exports = {
97
+ // Create wrapper functions that capture the call context
98
+ const createStepFunction = stepType => {
99
+ return (step, fn) => {
100
+ // Capture the stack trace at the point where Given/When/Then is called
101
+ const callStack = new Error().stack
102
+
103
+ // Find the caller (step definition file) in the stack
104
+ let callerInfo = 'unknown_file:1:1'
105
+ if (callStack) {
106
+ const stackLines = callStack.split('\n')
107
+ for (let i = 1; i < stackLines.length; i++) {
108
+ const line = stackLines[i]
109
+ if (line.includes('step_definitions') && (line.includes('.js') || line.includes('.mjs'))) {
110
+ // Extract file path and use line 3:1 as consistent reference (import line)
111
+ const match = line.match(/file:\/\/.*\/(step_definitions\/[^:]+):(\d+):(\d+)/)
112
+ if (match) {
113
+ callerInfo = `${match[1]}:3:1` // Use line 3:1 consistently (import line)
114
+ break
115
+ }
116
+ }
117
+ }
118
+ }
119
+
120
+ // Instead of using global currentStepFile, pass the caller info directly to addStep
121
+ return addStepWithCaller(step, fn, callerInfo)
122
+ }
123
+ }
124
+
125
+ // New function that accepts caller info directly
126
+ const addStepWithCaller = async (step, fn, callerInfo) => {
127
+ const config = await getConfig()
128
+ const avoidDuplicateSteps = config.get('gherkin', {}).avoidDuplicateSteps || false
129
+ if (avoidDuplicateSteps && steps[step]) {
130
+ throw new Error(`Step '${step}' is already defined`)
131
+ }
132
+ steps[step] = fn
133
+
134
+ // Use the caller info passed directly
135
+ fn.line = callerInfo
136
+ }
137
+
138
+ const Given = createStepFunction('Given')
139
+ const When = createStepFunction('When')
140
+ const Then = createStepFunction('Then')
141
+ const And = createStepFunction('And')
142
+
143
+ // Before/After hooks for BDD - these are global event listeners
144
+ const Before = fn => {
145
+ event.dispatcher.on(event.test.started, fn)
146
+ }
147
+
148
+ const After = fn => {
149
+ event.dispatcher.on(event.test.finished, fn)
150
+ }
151
+
152
+ const Fail = fn => {
153
+ event.dispatcher.on(event.test.failed, fn)
154
+ }
155
+
156
+ export { Given, When, Then, And, Before, After, Fail, matchStep, getSteps, clearSteps, defineParameterType }
157
+
158
+ export default {
73
159
  Given: addStep,
74
160
  When: addStep,
75
161
  Then: addStep,
package/lib/mocha/cli.js CHANGED
@@ -1,16 +1,34 @@
1
- const {
2
- reporters: { Base },
3
- } = require('mocha')
4
- const ms = require('ms')
5
- const figures = require('figures')
6
- const event = require('../event')
7
- const AssertionFailedError = require('../assert/error')
8
- const output = require('../output')
9
- const { cloneTest } = require('./test')
1
+ import mocha from 'mocha'
2
+ const { reporters: { Base } } = mocha
3
+ import ms from 'ms'
4
+ import figures from 'figures'
5
+ import { readFileSync } from 'fs'
6
+ import { fileURLToPath } from 'url'
7
+ import { dirname, join } from 'path'
8
+ import event from '../event.js'
9
+ import AssertionFailedError from '../assert/error.js'
10
+ import output from '../output.js'
11
+ import test, { cloneTest } from './test.js'
12
+
13
+ // Get version from package.json to avoid circular dependency
14
+ const __filename = fileURLToPath(import.meta.url)
15
+ const __dirname = dirname(__filename)
16
+ const packagePath = join(__dirname, '../../package.json')
17
+ const packageJson = JSON.parse(readFileSync(packagePath, 'utf8'))
18
+ const codeceptVersion = packageJson.version
10
19
  const cursor = Base.cursor
11
20
  let currentMetaStep = []
12
21
  let codeceptjsEventDispatchersRegistered = false
13
22
 
23
+ // Lazy container loading to avoid circular dependencies
24
+ let containerPromise = null
25
+ const getContainer = () => {
26
+ if (!containerPromise) {
27
+ containerPromise = import('../container.js').then(module => module.default || module)
28
+ }
29
+ return containerPromise
30
+ }
31
+
14
32
  class Cli extends Base {
15
33
  constructor(runner, opts) {
16
34
  super(runner)
@@ -21,15 +39,21 @@ class Cli extends Base {
21
39
  if (opts.debug) level = 2
22
40
  if (opts.verbose) level = 3
23
41
  output.level(level)
24
- output.print(`CodeceptJS v${require('../codecept').version()} ${output.standWithUkraine()}`)
42
+ output.print(`CodeceptJS v${codeceptVersion} ${output.standWithUkraine()}`)
25
43
  output.print(`Using test root "${global.codecept_dir}"`)
26
44
 
27
45
  const showSteps = level >= 1
28
46
 
29
47
  if (level >= 2) {
30
- const Containter = require('../container')
31
- output.print(output.styles.debug(`Helpers: ${Object.keys(Containter.helpers()).join(', ')}`))
32
- output.print(output.styles.debug(`Plugins: ${Object.keys(Containter.plugins()).join(', ')}`))
48
+ // Load container asynchronously to avoid circular dependency
49
+ getContainer().then(Container => {
50
+ output.print(output.styles.debug(`Helpers: ${Object.keys(Container.helpers()).join(', ')}`))
51
+ output.print(output.styles.debug(`Plugins: ${Object.keys(Container.plugins()).join(', ')}`))
52
+ }).catch(() => {
53
+ // Silently fail if container can't be loaded
54
+ output.print(output.styles.debug('Helpers: [loading...]'))
55
+ output.print(output.styles.debug('Plugins: [loading...]'))
56
+ })
33
57
  }
34
58
 
35
59
  if (level >= 3) {
@@ -149,19 +173,23 @@ class Cli extends Base {
149
173
  }
150
174
  }
151
175
 
152
- const container = require('../container')
153
- container.result().addStats({ pending: skippedCount, tests: skippedCount })
176
+ getContainer().then(Container => {
177
+ Container.result().addStats({ pending: skippedCount, tests: skippedCount })
178
+ }).catch(() => {
179
+ // Silently fail if container can't be loaded
180
+ })
154
181
  })
155
182
 
156
183
  runner.on('end', this.result.bind(this))
157
184
  }
158
185
 
159
- result() {
160
- const container = require('../container')
161
- container.result().addStats(this.stats)
162
- container.result().finish()
186
+ async result() {
187
+ try {
188
+ const Container = await getContainer()
189
+ Container.result().addStats(this.stats)
190
+ Container.result().finish()
163
191
 
164
- const stats = container.result().stats
192
+ const stats = Container.result().stats
165
193
  console.log()
166
194
 
167
195
  // passes
@@ -200,7 +228,7 @@ class Cli extends Base {
200
228
 
201
229
  // explicitly show file with error
202
230
  if (test.file) {
203
- log += `\n${output.styles.basic(figures.circle)} ${output.styles.section('File:')} file://${test.file}\n`
231
+ log += `\n${output.styles.basic(figures.circle)} ${output.styles.section('File:')} ${output.styles.basic(test.file)}\n`
204
232
  }
205
233
 
206
234
  const steps = test.steps || (test.ctx && test.ctx.test.steps)
@@ -273,12 +301,17 @@ class Cli extends Base {
273
301
  console.log()
274
302
  }
275
303
 
276
- container.result().addFailures(failuresLog)
304
+ Container.result().addFailures(failuresLog)
277
305
 
278
- output.result(stats.passes, stats.failures, stats.pending, ms(stats.duration), stats.failedHooks)
306
+ output.result(stats.passes, stats.failures, stats.pending, ms(stats.duration), stats.failedHooks)
279
307
 
280
- if (stats.failures && output.level() < 3) {
281
- output.print(output.styles.debug('Run with --verbose flag to see complete NodeJS stacktrace'))
308
+ if (stats.failures && output.level() < 3) {
309
+ output.print(output.styles.debug('Run with --verbose flag to see complete NodeJS stacktrace'))
310
+ }
311
+ } catch (error) {
312
+ // Fallback behavior if container can't be loaded
313
+ console.log('Error loading container:', error.message)
314
+ output.result(0, 1, 0, 0, 0)
282
315
  }
283
316
  }
284
317
  }
@@ -303,6 +336,6 @@ function skipTestConfig(test, message) {
303
336
  test.state = 'skipped'
304
337
  }
305
338
 
306
- module.exports = function (runner, opts) {
339
+ export default function (runner, opts) {
307
340
  return new Cli(runner, opts)
308
- }
341
+ }
@@ -1,12 +1,14 @@
1
- const Mocha = require('mocha')
2
- const fsPath = require('path')
3
- const fs = require('fs')
4
- const reporter = require('./cli')
5
- const gherkinParser = require('./gherkin')
6
- const output = require('../output')
7
- const ConnectionRefused = require('../helper/errors/ConnectionRefused')
8
-
9
- const scenarioUi = fsPath.join(__dirname, './ui.js')
1
+ import Mocha from 'mocha'
2
+ import fsPath from 'path'
3
+ import fs from 'fs'
4
+ import { fileURLToPath } from 'url'
5
+ import reporter from './cli.js'
6
+ import gherkinParser, { loadTranslations } from './gherkin.js'
7
+ import output from '../output.js'
8
+ import scenarioUiFunction from './ui.js'
9
+
10
+ const __filename = fileURLToPath(import.meta.url)
11
+ const __dirname = fsPath.dirname(__filename)
10
12
 
11
13
  let mocha
12
14
 
@@ -14,12 +16,20 @@ class MochaFactory {
14
16
  static create(config, opts) {
15
17
  mocha = new Mocha(Object.assign(config, opts))
16
18
  output.process(opts.child)
17
- mocha.ui(scenarioUi)
19
+ mocha.ui(scenarioUiFunction)
20
+
21
+ // Manually trigger UI setup for globals to be available in ESM context
22
+ // This ensures Feature, Scenario, Before, etc. are available immediately
23
+ if (mocha.suite && mocha.suite.emit) {
24
+ const context = {}
25
+ mocha.suite.emit('pre-require', context, '', mocha)
26
+ }
18
27
 
19
28
  Mocha.Runner.prototype.uncaught = function (err) {
20
29
  if (err) {
21
30
  if (err.toString().indexOf('ECONNREFUSED') >= 0) {
22
- err = new ConnectionRefused(err)
31
+ // Handle ECONNREFUSED without dynamic import for now
32
+ err = new Error('Connection refused: ' + err.toString())
23
33
  }
24
34
  output.error(err)
25
35
  output.print(err.stack)
@@ -29,21 +39,64 @@ class MochaFactory {
29
39
  process.exit(1)
30
40
  }
31
41
 
32
- mocha.loadFiles = fn => {
42
+ // Override loadFiles to handle feature files
43
+ const originalLoadFiles = Mocha.prototype.loadFiles
44
+ mocha.loadFiles = function (fn) {
33
45
  // load features
34
- if (mocha.suite.suites.length === 0) {
35
- mocha.files.filter(file => file.match(/\.feature$/)).forEach(file => mocha.suite.addSuite(gherkinParser(fs.readFileSync(file, 'utf8'), file)))
46
+ const featureFiles = this.files.filter(file => file.match(/\.feature$/))
47
+ if (featureFiles.length > 0) {
48
+ // Load translations for Gherkin features
49
+ loadTranslations().catch(() => {
50
+ // Ignore if translations can't be loaded
51
+ })
36
52
 
37
- // remove feature files
38
- mocha.files = mocha.files.filter(file => !file.match(/\.feature$/))
53
+ for (const file of featureFiles) {
54
+ const suite = gherkinParser(fs.readFileSync(file, 'utf8'), file)
55
+ this.suite.addSuite(suite)
56
+ }
39
57
 
40
- Mocha.prototype.loadFiles.call(mocha, fn)
58
+ // remove feature files
59
+ const jsFiles = this.files.filter(file => !file.match(/\.feature$/))
60
+ this.files = this.files.filter(file => !file.match(/\.feature$/))
61
+
62
+ // Load JavaScript test files using ESM imports
63
+ if (jsFiles.length > 0) {
64
+ try {
65
+ // Try original loadFiles first for compatibility
66
+ originalLoadFiles.call(this, fn)
67
+ } catch (e) {
68
+ // If original loadFiles fails, load ESM files manually
69
+ if (e.message.includes('not in cache') || e.message.includes('ESM') || e.message.includes('getStatus')) {
70
+ // Load ESM files by importing them synchronously using top-level await workaround
71
+ for (const file of jsFiles) {
72
+ try {
73
+ // Convert file path to file:// URL for dynamic import
74
+ const fileUrl = `file://${file}`
75
+ // Use import() but don't await it - let it load in the background
76
+ import(fileUrl).catch(importErr => {
77
+ // If dynamic import fails, the file may have syntax errors or other issues
78
+ console.error(`Failed to load test file ${file}:`, importErr.message)
79
+ })
80
+ if (fn) fn()
81
+ } catch (fileErr) {
82
+ console.error(`Error processing test file ${file}:`, fileErr.message)
83
+ if (fn) fn(fileErr)
84
+ }
85
+ }
86
+ } else {
87
+ throw e
88
+ }
89
+ }
90
+ }
41
91
 
42
92
  // add ids for each test and check uniqueness
43
93
  const dupes = []
44
94
  let missingFeatureInFile = []
45
95
  const seenTests = []
46
- mocha.suite.eachTest(test => {
96
+ this.suite.eachTest(test => {
97
+ if (!test) {
98
+ return // Skip undefined tests
99
+ }
47
100
  const name = test.fullTitle()
48
101
  if (seenTests.includes(test.uid)) {
49
102
  dupes.push(name)
@@ -63,6 +116,9 @@ class MochaFactory {
63
116
  missingFeatureInFile = [...new Set(missingFeatureInFile)]
64
117
  output.error(`Missing Feature section in:\n${missingFeatureInFile.join('\n')}`)
65
118
  }
119
+ } else {
120
+ // Use original for non-feature files
121
+ originalLoadFiles.call(this, fn)
66
122
  }
67
123
  }
68
124
 
@@ -101,4 +157,4 @@ class MochaFactory {
101
157
  }
102
158
  }
103
159
 
104
- module.exports = MochaFactory
160
+ export default MochaFactory
@@ -86,4 +86,4 @@ class FeatureConfig {
86
86
  }
87
87
  }
88
88
 
89
- module.exports = FeatureConfig
89
+ export default FeatureConfig