codeceptjs 4.0.0-beta.1 → 4.0.0-beta.11.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 (207) 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 +71 -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 +238 -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 +300 -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 +124 -50
  39. package/lib/container.js +765 -260
  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 +47 -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/mask_data.js +47 -0
  157. package/lib/utils.js +411 -228
  158. package/lib/workerStorage.js +37 -34
  159. package/lib/workers.js +532 -296
  160. package/package.json +115 -95
  161. package/translations/de-DE.js +5 -3
  162. package/translations/fr-FR.js +5 -4
  163. package/translations/index.js +22 -12
  164. package/translations/it-IT.js +4 -3
  165. package/translations/ja-JP.js +4 -3
  166. package/translations/nl-NL.js +76 -0
  167. package/translations/pl-PL.js +4 -3
  168. package/translations/pt-BR.js +4 -3
  169. package/translations/ru-RU.js +4 -3
  170. package/translations/utils.js +10 -0
  171. package/translations/zh-CN.js +4 -3
  172. package/translations/zh-TW.js +4 -3
  173. package/typings/index.d.ts +546 -185
  174. package/typings/promiseBasedTypes.d.ts +150 -879
  175. package/typings/types.d.ts +547 -996
  176. package/lib/cli.js +0 -249
  177. package/lib/dirname.js +0 -5
  178. package/lib/helper/Expect.js +0 -425
  179. package/lib/helper/ExpectHelper.js +0 -399
  180. package/lib/helper/MockServer.js +0 -223
  181. package/lib/helper/Nightmare.js +0 -1411
  182. package/lib/helper/Protractor.js +0 -1835
  183. package/lib/helper/SoftExpectHelper.js +0 -381
  184. package/lib/helper/TestCafe.js +0 -1410
  185. package/lib/helper/clientscripts/nightmare.js +0 -213
  186. package/lib/helper/testcafe/testControllerHolder.js +0 -42
  187. package/lib/helper/testcafe/testcafe-utils.js +0 -63
  188. package/lib/interfaces/bdd.js +0 -98
  189. package/lib/interfaces/featureConfig.js +0 -69
  190. package/lib/interfaces/gherkin.js +0 -195
  191. package/lib/listener/artifacts.js +0 -19
  192. package/lib/listener/retry.js +0 -68
  193. package/lib/listener/timeout.js +0 -109
  194. package/lib/mochaFactory.js +0 -110
  195. package/lib/plugin/allure.js +0 -15
  196. package/lib/plugin/commentStep.js +0 -136
  197. package/lib/plugin/debugErrors.js +0 -67
  198. package/lib/plugin/eachElement.js +0 -127
  199. package/lib/plugin/fakerTransform.js +0 -49
  200. package/lib/plugin/retryTo.js +0 -121
  201. package/lib/plugin/selenoid.js +0 -371
  202. package/lib/plugin/standardActingHelpers.js +0 -9
  203. package/lib/plugin/tryTo.js +0 -105
  204. package/lib/plugin/wdio.js +0 -246
  205. package/lib/scenario.js +0 -222
  206. package/lib/ui.js +0 -238
  207. 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
+ }