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
@@ -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()
97
84
 
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)
85
+ // Execute test function
86
+ const result = testFn.call(test, args)
87
+
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,7 +111,7 @@ 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 => {
@@ -162,7 +158,8 @@ module.exports.injected = function (fn, suite, hookName) {
162
158
  async (retry, number) => {
163
159
  try {
164
160
  recorder.startUnlessRunning()
165
- await fn.call(this, { ...getInjectedArguments(fn), suite, test: currentTest })
161
+ const injectedArgs = await getInjectedArguments(fn, null, suite)
162
+ await fn.call(this, { ...injectedArgs, suite, test: currentTest })
166
163
  await recorder.promise().catch(err => retry(err))
167
164
  } catch (err) {
168
165
  retry(err)
@@ -196,36 +193,82 @@ module.exports.injected = function (fn, suite, hookName) {
196
193
  /**
197
194
  * Starts promise chain, so helpers could enqueue their hooks
198
195
  */
199
- module.exports.setup = function (suite) {
200
- const { enhanceMochaTest } = require('./test')
201
- return injectHook(() => {
196
+ export function setup(suite) {
197
+ return function (done) {
198
+ const doneFn = makeDoneCallableOnce(done)
202
199
  recorder.startUnlessRunning()
203
- event.emit(event.test.before, enhanceMochaTest(suite?.ctx?.currentTest))
204
- }, suite)
200
+ import('./test.js').then(testModule => {
201
+ const { enhanceMochaTest } = testModule.default || testModule
202
+ event.emit(event.test.before, enhanceMochaTest(suite?.ctx?.currentTest))
203
+ recorder.add(() => doneFn())
204
+ })
205
+ }
205
206
  }
206
207
 
207
- module.exports.teardown = function (suite) {
208
- const { enhanceMochaTest } = require('./test')
209
- return injectHook(() => {
208
+ export function teardown(suite) {
209
+ return function (done) {
210
+ const doneFn = makeDoneCallableOnce(done)
210
211
  recorder.startUnlessRunning()
211
- event.emit(event.test.after, enhanceMochaTest(suite?.ctx?.currentTest))
212
- }, suite)
212
+ import('./test.js').then(testModule => {
213
+ const { enhanceMochaTest } = testModule.default || testModule
214
+ event.emit(event.test.after, enhanceMochaTest(suite?.ctx?.currentTest))
215
+ recorder.add(() => doneFn())
216
+ })
217
+ }
213
218
  }
214
219
 
215
- module.exports.suiteSetup = function (suite) {
216
- const { enhanceMochaSuite } = require('./suite')
217
- return injectHook(() => {
220
+ export function suiteSetup(suite) {
221
+ return function (done) {
222
+ const doneFn = makeDoneCallableOnce(done)
218
223
  recorder.startUnlessRunning()
219
- event.emit(event.suite.before, enhanceMochaSuite(suite))
220
- }, suite)
224
+
225
+ // Set up error handler for suite setup
226
+ recorder.errHandler(err => {
227
+ doneFn(err)
228
+ })
229
+
230
+ import('./suite.js')
231
+ .then(suiteModule => {
232
+ const { enhanceMochaSuite } = suiteModule.default || suiteModule
233
+ event.emit(event.suite.before, enhanceMochaSuite(suite))
234
+ recorder.add(() => doneFn())
235
+ })
236
+ .catch(err => {
237
+ doneFn(err)
238
+ })
239
+ }
221
240
  }
222
241
 
223
- module.exports.suiteTeardown = function (suite) {
224
- const { enhanceMochaSuite } = require('./suite')
225
- return injectHook(() => {
242
+ export function suiteTeardown(suite) {
243
+ return function (done) {
244
+ const doneFn = makeDoneCallableOnce(done)
226
245
  recorder.startUnlessRunning()
227
- event.emit(event.suite.after, enhanceMochaSuite(suite))
228
- }, suite)
246
+
247
+ // Set up error handler for suite teardown
248
+ recorder.errHandler(err => {
249
+ doneFn(err)
250
+ })
251
+
252
+ import('./suite.js')
253
+ .then(suiteModule => {
254
+ const { enhanceMochaSuite } = suiteModule.default || suiteModule
255
+ event.emit(event.suite.after, enhanceMochaSuite(suite))
256
+ recorder.add(() => doneFn())
257
+ })
258
+ .catch(err => {
259
+ doneFn(err)
260
+ })
261
+ }
229
262
  }
230
263
 
231
- module.exports.getInjectedArguments = getInjectedArguments
264
+ export { getInjectedArguments }
265
+
266
+ export default {
267
+ test,
268
+ injected,
269
+ setup,
270
+ teardown,
271
+ suiteSetup,
272
+ suiteTeardown,
273
+ getInjectedArguments,
274
+ }
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
@@ -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,15 @@
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
+ import { initMochaGlobals } from '../globals.js'
10
+
11
+ const __filename = fileURLToPath(import.meta.url)
12
+ const __dirname = fsPath.dirname(__filename)
10
13
 
11
14
  let mocha
12
15
 
@@ -14,12 +17,22 @@ class MochaFactory {
14
17
  static create(config, opts) {
15
18
  mocha = new Mocha(Object.assign(config, opts))
16
19
  output.process(opts.child)
17
- mocha.ui(scenarioUi)
20
+ mocha.ui(scenarioUiFunction)
21
+
22
+ // Manually trigger UI setup for globals to be available in ESM context
23
+ // This ensures Feature, Scenario, Before, etc. are available immediately
24
+ if (mocha.suite && mocha.suite.emit) {
25
+ const context = {}
26
+ mocha.suite.emit('pre-require', context, '', mocha)
27
+ // Also set globals immediately so they're available when ESM modules load
28
+ initMochaGlobals(context)
29
+ }
18
30
 
19
31
  Mocha.Runner.prototype.uncaught = function (err) {
20
32
  if (err) {
21
33
  if (err.toString().indexOf('ECONNREFUSED') >= 0) {
22
- err = new ConnectionRefused(err)
34
+ // Handle ECONNREFUSED without dynamic import for now
35
+ err = new Error('Connection refused: ' + err.toString())
23
36
  }
24
37
  output.error(err)
25
38
  output.print(err.stack)
@@ -29,21 +42,64 @@ class MochaFactory {
29
42
  process.exit(1)
30
43
  }
31
44
 
32
- mocha.loadFiles = fn => {
45
+ // Override loadFiles to handle feature files
46
+ const originalLoadFiles = Mocha.prototype.loadFiles
47
+ mocha.loadFiles = function (fn) {
33
48
  // 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)))
49
+ const featureFiles = this.files.filter(file => file.match(/\.feature$/))
50
+ if (featureFiles.length > 0) {
51
+ // Load translations for Gherkin features
52
+ loadTranslations().catch(() => {
53
+ // Ignore if translations can't be loaded
54
+ })
36
55
 
37
- // remove feature files
38
- mocha.files = mocha.files.filter(file => !file.match(/\.feature$/))
56
+ for (const file of featureFiles) {
57
+ const suite = gherkinParser(fs.readFileSync(file, 'utf8'), file)
58
+ this.suite.addSuite(suite)
59
+ }
39
60
 
40
- Mocha.prototype.loadFiles.call(mocha, fn)
61
+ // remove feature files
62
+ const jsFiles = this.files.filter(file => !file.match(/\.feature$/))
63
+ this.files = this.files.filter(file => !file.match(/\.feature$/))
64
+
65
+ // Load JavaScript test files using ESM imports
66
+ if (jsFiles.length > 0) {
67
+ try {
68
+ // Try original loadFiles first for compatibility
69
+ originalLoadFiles.call(this, fn)
70
+ } catch (e) {
71
+ // If original loadFiles fails, load ESM files manually
72
+ if (e.message.includes('not in cache') || e.message.includes('ESM') || e.message.includes('getStatus')) {
73
+ // Load ESM files by importing them synchronously using top-level await workaround
74
+ for (const file of jsFiles) {
75
+ try {
76
+ // Convert file path to file:// URL for dynamic import
77
+ const fileUrl = `file://${file}`
78
+ // Use import() but don't await it - let it load in the background
79
+ import(fileUrl).catch(importErr => {
80
+ // If dynamic import fails, the file may have syntax errors or other issues
81
+ console.error(`Failed to load test file ${file}:`, importErr.message)
82
+ })
83
+ if (fn) fn()
84
+ } catch (fileErr) {
85
+ console.error(`Error processing test file ${file}:`, fileErr.message)
86
+ if (fn) fn(fileErr)
87
+ }
88
+ }
89
+ } else {
90
+ throw e
91
+ }
92
+ }
93
+ }
41
94
 
42
95
  // add ids for each test and check uniqueness
43
96
  const dupes = []
44
97
  let missingFeatureInFile = []
45
98
  const seenTests = []
46
- mocha.suite.eachTest(test => {
99
+ this.suite.eachTest(test => {
100
+ if (!test) {
101
+ return // Skip undefined tests
102
+ }
47
103
  const name = test.fullTitle()
48
104
  if (seenTests.includes(test.uid)) {
49
105
  dupes.push(name)
@@ -63,6 +119,9 @@ class MochaFactory {
63
119
  missingFeatureInFile = [...new Set(missingFeatureInFile)]
64
120
  output.error(`Missing Feature section in:\n${missingFeatureInFile.join('\n')}`)
65
121
  }
122
+ } else {
123
+ // Use original for non-feature files
124
+ originalLoadFiles.call(this, fn)
66
125
  }
67
126
  }
68
127
 
@@ -101,4 +160,4 @@ class MochaFactory {
101
160
  }
102
161
  }
103
162
 
104
- module.exports = MochaFactory
163
+ export default MochaFactory
@@ -86,4 +86,4 @@ class FeatureConfig {
86
86
  }
87
87
  }
88
88
 
89
- module.exports = FeatureConfig
89
+ export default FeatureConfig