codeceptjs 4.0.0-beta.2 → 4.0.0-beta.21

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 (209) hide show
  1. package/README.md +133 -120
  2. package/bin/codecept.js +107 -96
  3. package/bin/test-server.js +64 -0
  4. package/docs/webapi/clearCookie.mustache +1 -1
  5. package/docs/webapi/click.mustache +5 -1
  6. package/lib/actor.js +73 -103
  7. package/lib/ai.js +159 -188
  8. package/lib/assert/empty.js +22 -24
  9. package/lib/assert/equal.js +30 -37
  10. package/lib/assert/error.js +14 -14
  11. package/lib/assert/include.js +43 -48
  12. package/lib/assert/throws.js +11 -11
  13. package/lib/assert/truth.js +22 -22
  14. package/lib/assert.js +20 -18
  15. package/lib/codecept.js +262 -162
  16. package/lib/colorUtils.js +50 -52
  17. package/lib/command/check.js +206 -0
  18. package/lib/command/configMigrate.js +56 -51
  19. package/lib/command/definitions.js +96 -109
  20. package/lib/command/dryRun.js +77 -79
  21. package/lib/command/generate.js +234 -194
  22. package/lib/command/gherkin/init.js +42 -33
  23. package/lib/command/gherkin/snippets.js +76 -74
  24. package/lib/command/gherkin/steps.js +20 -17
  25. package/lib/command/info.js +74 -38
  26. package/lib/command/init.js +301 -290
  27. package/lib/command/interactive.js +41 -32
  28. package/lib/command/list.js +28 -27
  29. package/lib/command/run-multiple/chunk.js +51 -48
  30. package/lib/command/run-multiple/collection.js +5 -5
  31. package/lib/command/run-multiple/run.js +5 -1
  32. package/lib/command/run-multiple.js +97 -97
  33. package/lib/command/run-rerun.js +19 -25
  34. package/lib/command/run-workers.js +68 -92
  35. package/lib/command/run.js +39 -27
  36. package/lib/command/utils.js +80 -64
  37. package/lib/command/workers/runTests.js +388 -226
  38. package/lib/config.js +109 -50
  39. package/lib/container.js +765 -261
  40. package/lib/data/context.js +60 -61
  41. package/lib/data/dataScenarioConfig.js +47 -47
  42. package/lib/data/dataTableArgument.js +32 -32
  43. package/lib/data/table.js +22 -22
  44. package/lib/effects.js +307 -0
  45. package/lib/element/WebElement.js +327 -0
  46. package/lib/els.js +160 -0
  47. package/lib/event.js +173 -163
  48. package/lib/globals.js +141 -0
  49. package/lib/heal.js +89 -85
  50. package/lib/helper/AI.js +131 -41
  51. package/lib/helper/ApiDataFactory.js +107 -75
  52. package/lib/helper/Appium.js +542 -404
  53. package/lib/helper/FileSystem.js +100 -79
  54. package/lib/helper/GraphQL.js +44 -43
  55. package/lib/helper/GraphQLDataFactory.js +52 -52
  56. package/lib/helper/JSONResponse.js +126 -88
  57. package/lib/helper/Mochawesome.js +54 -29
  58. package/lib/helper/Playwright.js +2547 -1316
  59. package/lib/helper/Puppeteer.js +1578 -1181
  60. package/lib/helper/REST.js +209 -68
  61. package/lib/helper/WebDriver.js +1482 -1342
  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 +17 -8
  70. package/lib/helper/extras/PlaywrightRestartOpts.js +25 -11
  71. package/lib/helper/extras/Popup.js +22 -22
  72. package/lib/helper/extras/React.js +27 -28
  73. package/lib/helper/network/actions.js +36 -42
  74. package/lib/helper/network/utils.js +78 -84
  75. package/lib/helper/scripts/blurElement.js +5 -5
  76. package/lib/helper/scripts/focusElement.js +5 -5
  77. package/lib/helper/scripts/highlightElement.js +8 -8
  78. package/lib/helper/scripts/isElementClickable.js +34 -34
  79. package/lib/helper.js +2 -3
  80. package/lib/history.js +23 -19
  81. package/lib/hooks.js +8 -8
  82. package/lib/html.js +94 -104
  83. package/lib/index.js +38 -27
  84. package/lib/listener/config.js +30 -23
  85. package/lib/listener/emptyRun.js +54 -0
  86. package/lib/listener/enhancedGlobalRetry.js +110 -0
  87. package/lib/listener/exit.js +16 -18
  88. package/lib/listener/globalRetry.js +70 -0
  89. package/lib/listener/globalTimeout.js +181 -0
  90. package/lib/listener/helpers.js +76 -51
  91. package/lib/listener/mocha.js +10 -11
  92. package/lib/listener/result.js +11 -0
  93. package/lib/listener/retryEnhancer.js +85 -0
  94. package/lib/listener/steps.js +71 -59
  95. package/lib/listener/store.js +20 -0
  96. package/lib/locator.js +214 -197
  97. package/lib/mocha/asyncWrapper.js +274 -0
  98. package/lib/mocha/bdd.js +167 -0
  99. package/lib/mocha/cli.js +341 -0
  100. package/lib/mocha/factory.js +163 -0
  101. package/lib/mocha/featureConfig.js +89 -0
  102. package/lib/mocha/gherkin.js +231 -0
  103. package/lib/mocha/hooks.js +121 -0
  104. package/lib/mocha/index.js +21 -0
  105. package/lib/mocha/inject.js +46 -0
  106. package/lib/{interfaces → mocha}/scenarioConfig.js +58 -34
  107. package/lib/mocha/suite.js +89 -0
  108. package/lib/mocha/test.js +184 -0
  109. package/lib/mocha/types.d.ts +42 -0
  110. package/lib/mocha/ui.js +242 -0
  111. package/lib/output.js +141 -71
  112. package/lib/parser.js +54 -44
  113. package/lib/pause.js +173 -145
  114. package/lib/plugin/analyze.js +403 -0
  115. package/lib/plugin/{autoLogin.js → auth.js} +178 -79
  116. package/lib/plugin/autoDelay.js +36 -40
  117. package/lib/plugin/coverage.js +131 -78
  118. package/lib/plugin/customLocator.js +22 -21
  119. package/lib/plugin/customReporter.js +53 -0
  120. package/lib/plugin/enhancedRetryFailedStep.js +99 -0
  121. package/lib/plugin/heal.js +101 -110
  122. package/lib/plugin/htmlReporter.js +3648 -0
  123. package/lib/plugin/pageInfo.js +140 -0
  124. package/lib/plugin/pauseOnFail.js +12 -11
  125. package/lib/plugin/retryFailedStep.js +82 -47
  126. package/lib/plugin/screenshotOnFail.js +111 -92
  127. package/lib/plugin/stepByStepReport.js +159 -101
  128. package/lib/plugin/stepTimeout.js +20 -25
  129. package/lib/plugin/subtitles.js +38 -38
  130. package/lib/recorder.js +193 -130
  131. package/lib/rerun.js +94 -49
  132. package/lib/result.js +238 -0
  133. package/lib/retryCoordinator.js +207 -0
  134. package/lib/secret.js +20 -18
  135. package/lib/session.js +95 -89
  136. package/lib/step/base.js +239 -0
  137. package/lib/step/comment.js +10 -0
  138. package/lib/step/config.js +50 -0
  139. package/lib/step/func.js +46 -0
  140. package/lib/step/helper.js +50 -0
  141. package/lib/step/meta.js +99 -0
  142. package/lib/step/record.js +74 -0
  143. package/lib/step/retry.js +11 -0
  144. package/lib/step/section.js +55 -0
  145. package/lib/step.js +18 -329
  146. package/lib/steps.js +54 -0
  147. package/lib/store.js +38 -7
  148. package/lib/template/heal.js +3 -12
  149. package/lib/template/prompts/generatePageObject.js +31 -0
  150. package/lib/template/prompts/healStep.js +13 -0
  151. package/lib/template/prompts/writeStep.js +9 -0
  152. package/lib/test-server.js +334 -0
  153. package/lib/timeout.js +60 -0
  154. package/lib/transform.js +8 -8
  155. package/lib/translation.js +34 -21
  156. package/lib/utils/loaderCheck.js +124 -0
  157. package/lib/utils/mask_data.js +47 -0
  158. package/lib/utils/typescript.js +237 -0
  159. package/lib/utils.js +411 -228
  160. package/lib/workerStorage.js +37 -34
  161. package/lib/workers.js +532 -296
  162. package/package.json +124 -95
  163. package/translations/de-DE.js +5 -3
  164. package/translations/fr-FR.js +5 -4
  165. package/translations/index.js +22 -12
  166. package/translations/it-IT.js +4 -3
  167. package/translations/ja-JP.js +4 -3
  168. package/translations/nl-NL.js +76 -0
  169. package/translations/pl-PL.js +4 -3
  170. package/translations/pt-BR.js +4 -3
  171. package/translations/ru-RU.js +4 -3
  172. package/translations/utils.js +10 -0
  173. package/translations/zh-CN.js +4 -3
  174. package/translations/zh-TW.js +4 -3
  175. package/typings/index.d.ts +546 -185
  176. package/typings/promiseBasedTypes.d.ts +150 -875
  177. package/typings/types.d.ts +547 -992
  178. package/lib/cli.js +0 -249
  179. package/lib/dirname.js +0 -5
  180. package/lib/helper/Expect.js +0 -425
  181. package/lib/helper/ExpectHelper.js +0 -399
  182. package/lib/helper/MockServer.js +0 -223
  183. package/lib/helper/Nightmare.js +0 -1411
  184. package/lib/helper/Protractor.js +0 -1835
  185. package/lib/helper/SoftExpectHelper.js +0 -381
  186. package/lib/helper/TestCafe.js +0 -1410
  187. package/lib/helper/clientscripts/nightmare.js +0 -213
  188. package/lib/helper/testcafe/testControllerHolder.js +0 -42
  189. package/lib/helper/testcafe/testcafe-utils.js +0 -63
  190. package/lib/interfaces/bdd.js +0 -98
  191. package/lib/interfaces/featureConfig.js +0 -69
  192. package/lib/interfaces/gherkin.js +0 -195
  193. package/lib/listener/artifacts.js +0 -19
  194. package/lib/listener/retry.js +0 -68
  195. package/lib/listener/timeout.js +0 -109
  196. package/lib/mochaFactory.js +0 -110
  197. package/lib/plugin/allure.js +0 -15
  198. package/lib/plugin/commentStep.js +0 -136
  199. package/lib/plugin/debugErrors.js +0 -67
  200. package/lib/plugin/eachElement.js +0 -127
  201. package/lib/plugin/fakerTransform.js +0 -49
  202. package/lib/plugin/retryTo.js +0 -121
  203. package/lib/plugin/selenoid.js +0 -371
  204. package/lib/plugin/standardActingHelpers.js +0 -9
  205. package/lib/plugin/tryTo.js +0 -105
  206. package/lib/plugin/wdio.js +0 -246
  207. package/lib/scenario.js +0 -222
  208. package/lib/ui.js +0 -238
  209. package/lib/within.js +0 -70
@@ -0,0 +1,274 @@
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
+
9
+ const injectHook = function (inject, suite) {
10
+ try {
11
+ inject()
12
+ } catch (err) {
13
+ recorder.throw(err)
14
+ }
15
+ recorder.catch(err => {
16
+ suiteTestFailedHookError(suite, err)
17
+ throw err
18
+ })
19
+ return recorder.promise()
20
+ }
21
+
22
+ function suiteTestFailedHookError(suite, err, hookName) {
23
+ suite.eachTest(test => {
24
+ test.err = err
25
+ if (hookName) hookName = ucfirst(hookName)
26
+ event.emit(event.test.failed, test, err, hookName)
27
+ })
28
+ }
29
+
30
+ function makeDoneCallableOnce(done) {
31
+ let called = false
32
+ return function (err) {
33
+ if (called) {
34
+ return
35
+ }
36
+ called = true
37
+ return done(err)
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Wraps test function, injects support objects from container,
43
+ * starts promise chain with recorder, performs before/after hooks
44
+ * through event system.
45
+ */
46
+ export function test(test) {
47
+ const testFn = test.fn
48
+ if (!testFn) {
49
+ return test
50
+ }
51
+
52
+ test.timeout(0)
53
+ test.async = true
54
+
55
+ test.fn = function (done) {
56
+ const doneFn = makeDoneCallableOnce(done)
57
+ recorder.errHandler(err => {
58
+ recorder.session.start('teardown')
59
+ recorder.cleanAsyncErr()
60
+ if (test.throws) {
61
+ // check that test should actually fail
62
+ try {
63
+ assertThrown(err, test.throws)
64
+ event.emit(event.test.passed, test)
65
+ event.emit(event.test.finished, test)
66
+ recorder.add(doneFn)
67
+ return
68
+ } catch (newErr) {
69
+ err = newErr
70
+ }
71
+ }
72
+ test.err = err
73
+ event.emit(event.test.failed, test, err)
74
+ event.emit(event.test.finished, test)
75
+ recorder.add(() => doneFn(err))
76
+ })
77
+
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)
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()
106
+ })
107
+ }
108
+ return test
109
+ }
110
+
111
+ /**
112
+ * Injects arguments to function from controller
113
+ */
114
+ export function injected(fn, suite, hookName) {
115
+ return function (done) {
116
+ const doneFn = makeDoneCallableOnce(done)
117
+ const errHandler = err => {
118
+ recorder.session.start('teardown')
119
+ recorder.cleanAsyncErr()
120
+ if (['before', 'beforeSuite'].includes(hookName)) {
121
+ suiteTestFailedHookError(suite, err, hookName)
122
+ }
123
+ if (hookName === 'after') {
124
+ suiteTestFailedHookError(suite, err, hookName)
125
+ suite.eachTest(test => {
126
+ event.emit(event.test.after, test)
127
+ })
128
+ }
129
+ if (hookName === 'afterSuite') {
130
+ suiteTestFailedHookError(suite, err, hookName)
131
+ event.emit(event.suite.after, suite)
132
+ }
133
+ recorder.add(() => doneFn(err))
134
+ }
135
+
136
+ recorder.errHandler(err => {
137
+ errHandler(err)
138
+ })
139
+
140
+ if (!fn) throw new Error('fn is not defined')
141
+
142
+ fireHook(event.hook.started, suite)
143
+
144
+ this.test.body = fn.toString()
145
+
146
+ if (!recorder.isRunning()) {
147
+ recorder.errHandler(err => {
148
+ errHandler(err)
149
+ })
150
+ }
151
+
152
+ const opts = suite.opts || {}
153
+ const retries = opts[`retry${ucfirst(hookName)}`] || 0
154
+
155
+ const currentTest = hookName === 'before' || hookName === 'after' ? suite?.ctx?.currentTest : null
156
+
157
+ promiseRetry(
158
+ async (retry, number) => {
159
+ try {
160
+ recorder.startUnlessRunning()
161
+ const injectedArgs = await getInjectedArguments(fn, null, suite)
162
+ await fn.call(this, { ...injectedArgs, suite, test: currentTest })
163
+ await recorder.promise().catch(err => retry(err))
164
+ } catch (err) {
165
+ retry(err)
166
+ } finally {
167
+ if (number < retries) {
168
+ recorder.stop()
169
+ recorder.start()
170
+ }
171
+ }
172
+ },
173
+ { retries },
174
+ )
175
+ .then(() => {
176
+ recorder.add('fire hook.passed', () => fireHook(event.hook.passed, suite))
177
+ recorder.add('fire hook.finished', () => fireHook(event.hook.finished, suite))
178
+ recorder.add(`finish ${hookName} hook`, doneFn)
179
+ recorder.catch()
180
+ })
181
+ .catch(e => {
182
+ recorder.throw(e)
183
+ recorder.catch(e => {
184
+ const err = recorder.getAsyncErr() === null ? e : recorder.getAsyncErr()
185
+ errHandler(err)
186
+ })
187
+ recorder.add('fire hook.failed', () => fireHook(event.hook.failed, suite, e))
188
+ recorder.add('fire hook.finished', () => fireHook(event.hook.finished, suite))
189
+ })
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Starts promise chain, so helpers could enqueue their hooks
195
+ */
196
+ export function setup(suite) {
197
+ return function (done) {
198
+ const doneFn = makeDoneCallableOnce(done)
199
+ recorder.startUnlessRunning()
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
+ }
206
+ }
207
+
208
+ export function teardown(suite) {
209
+ return function (done) {
210
+ const doneFn = makeDoneCallableOnce(done)
211
+ recorder.startUnlessRunning()
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
+ }
218
+ }
219
+
220
+ export function suiteSetup(suite) {
221
+ return function (done) {
222
+ const doneFn = makeDoneCallableOnce(done)
223
+ recorder.startUnlessRunning()
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
+ }
240
+ }
241
+
242
+ export function suiteTeardown(suite) {
243
+ return function (done) {
244
+ const doneFn = makeDoneCallableOnce(done)
245
+ recorder.startUnlessRunning()
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
+ }
262
+ }
263
+
264
+ export { getInjectedArguments }
265
+
266
+ export default {
267
+ test,
268
+ injected,
269
+ setup,
270
+ teardown,
271
+ suiteSetup,
272
+ suiteTeardown,
273
+ getInjectedArguments,
274
+ }
@@ -0,0 +1,167 @@
1
+ import { CucumberExpression, ParameterTypeRegistry, ParameterType } from '@cucumber/cucumber-expressions'
2
+ import event from '../event.js'
3
+
4
+ let steps = {}
5
+ let Config
6
+
7
+ const STACK_POSITION = 2
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
+
17
+ /**
18
+ * @param {*} step
19
+ * @param {*} fn
20
+ */
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
35
+ if (avoidDuplicateSteps && steps[step]) {
36
+ throw new Error(`Step '${step}' is already defined`)
37
+ }
38
+ steps[step] = fn
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'
50
+ }
51
+ }
52
+
53
+ const parameterTypeRegistry = new ParameterTypeRegistry()
54
+
55
+ const matchStep = step => {
56
+ for (const stepName in steps) {
57
+ if (stepName.indexOf('/') === 0) {
58
+ const regExpArr = stepName.match(/^\/(.*?)\/([gimy]*)$/) || []
59
+ const res = step.match(new RegExp(regExpArr[1], regExpArr[2]))
60
+ if (res) {
61
+ const fn = steps[stepName]
62
+ fn.params = res.slice(1)
63
+ return fn
64
+ }
65
+ continue
66
+ }
67
+ const expression = new CucumberExpression(stepName, parameterTypeRegistry)
68
+ const res = expression.match(step)
69
+ if (res) {
70
+ const fn = steps[stepName]
71
+ fn.params = res.map(arg => arg.getValue(null))
72
+ return fn
73
+ }
74
+ }
75
+ throw new Error(`No steps matching "${step.toString()}"`)
76
+ }
77
+
78
+ const clearSteps = () => {
79
+ steps = {}
80
+ }
81
+
82
+ const getSteps = () => {
83
+ return steps
84
+ }
85
+
86
+ const defineParameterType = options => {
87
+ const parameterType = buildParameterType(options)
88
+ parameterTypeRegistry.defineParameterType(parameterType)
89
+ }
90
+
91
+ const buildParameterType = ({ name, regexp, transformer, useForSnippets, preferForRegexpMatch }) => {
92
+ if (typeof useForSnippets !== 'boolean') useForSnippets = true
93
+ if (typeof preferForRegexpMatch !== 'boolean') preferForRegexpMatch = false
94
+ return new ParameterType(name, regexp, null, transformer, useForSnippets, preferForRegexpMatch)
95
+ }
96
+
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 {
159
+ Given: addStep,
160
+ When: addStep,
161
+ Then: addStep,
162
+ And: addStep,
163
+ matchStep,
164
+ getSteps,
165
+ clearSteps,
166
+ defineParameterType,
167
+ }