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.
- package/README.md +0 -45
- package/bin/codecept.js +46 -57
- package/lib/actor.js +15 -11
- package/lib/ai.js +6 -5
- package/lib/assert/empty.js +9 -8
- package/lib/assert/equal.js +15 -17
- package/lib/assert/error.js +2 -2
- package/lib/assert/include.js +9 -11
- package/lib/assert/throws.js +1 -1
- package/lib/assert/truth.js +8 -5
- package/lib/assert.js +18 -18
- package/lib/codecept.js +66 -107
- package/lib/colorUtils.js +48 -50
- package/lib/command/check.js +32 -27
- package/lib/command/configMigrate.js +11 -10
- package/lib/command/definitions.js +16 -10
- package/lib/command/dryRun.js +16 -16
- package/lib/command/generate.js +29 -26
- package/lib/command/gherkin/init.js +36 -38
- package/lib/command/gherkin/snippets.js +14 -14
- package/lib/command/gherkin/steps.js +21 -18
- package/lib/command/info.js +8 -8
- package/lib/command/init.js +34 -31
- package/lib/command/interactive.js +11 -10
- package/lib/command/list.js +10 -9
- package/lib/command/run-multiple/chunk.js +5 -5
- package/lib/command/run-multiple/collection.js +5 -5
- package/lib/command/run-multiple/run.js +3 -3
- package/lib/command/run-multiple.js +16 -13
- package/lib/command/run-rerun.js +6 -7
- package/lib/command/run-workers.js +10 -24
- package/lib/command/run.js +8 -8
- package/lib/command/utils.js +20 -18
- package/lib/command/workers/runTests.js +117 -269
- package/lib/config.js +111 -49
- package/lib/container.js +299 -102
- package/lib/data/context.js +6 -5
- package/lib/data/dataScenarioConfig.js +1 -1
- package/lib/data/dataTableArgument.js +1 -1
- package/lib/data/table.js +1 -1
- package/lib/effects.js +94 -10
- package/lib/els.js +11 -9
- package/lib/event.js +11 -10
- package/lib/globals.js +141 -0
- package/lib/heal.js +12 -12
- package/lib/helper/AI.js +1 -1
- package/lib/helper/ApiDataFactory.js +16 -13
- package/lib/helper/FileSystem.js +32 -12
- package/lib/helper/GraphQL.js +1 -1
- package/lib/helper/GraphQLDataFactory.js +1 -1
- package/lib/helper/JSONResponse.js +19 -30
- package/lib/helper/Mochawesome.js +9 -28
- package/lib/helper/Playwright.js +668 -265
- package/lib/helper/Puppeteer.js +284 -169
- package/lib/helper/REST.js +29 -12
- package/lib/helper/WebDriver.js +192 -71
- package/lib/helper/errors/ConnectionRefused.js +6 -6
- package/lib/helper/errors/ElementAssertion.js +11 -16
- package/lib/helper/errors/ElementNotFound.js +5 -9
- package/lib/helper/errors/RemoteBrowserConnectionRefused.js +5 -5
- package/lib/helper/extras/Console.js +11 -11
- package/lib/helper/extras/PlaywrightLocator.js +110 -0
- package/lib/helper/extras/PlaywrightPropEngine.js +18 -18
- package/lib/helper/extras/PlaywrightRestartOpts.js +23 -23
- package/lib/helper/extras/Popup.js +1 -1
- package/lib/helper/extras/React.js +29 -30
- package/lib/helper/network/actions.js +33 -48
- package/lib/helper/network/utils.js +76 -83
- package/lib/helper/scripts/blurElement.js +6 -6
- package/lib/helper/scripts/focusElement.js +6 -6
- package/lib/helper/scripts/highlightElement.js +9 -9
- package/lib/helper/scripts/isElementClickable.js +34 -34
- package/lib/helper.js +2 -1
- package/lib/history.js +23 -20
- package/lib/hooks.js +10 -10
- package/lib/html.js +90 -100
- package/lib/index.js +48 -21
- package/lib/listener/config.js +8 -9
- package/lib/listener/emptyRun.js +6 -7
- package/lib/listener/exit.js +4 -3
- package/lib/listener/globalRetry.js +5 -5
- package/lib/listener/globalTimeout.js +11 -10
- package/lib/listener/helpers.js +33 -14
- package/lib/listener/mocha.js +3 -4
- package/lib/listener/result.js +4 -5
- package/lib/listener/steps.js +7 -18
- package/lib/listener/store.js +3 -3
- package/lib/locator.js +213 -192
- package/lib/mocha/asyncWrapper.js +108 -75
- package/lib/mocha/bdd.js +99 -13
- package/lib/mocha/cli.js +60 -27
- package/lib/mocha/factory.js +75 -19
- package/lib/mocha/featureConfig.js +1 -1
- package/lib/mocha/gherkin.js +57 -25
- package/lib/mocha/hooks.js +12 -3
- package/lib/mocha/index.js +13 -4
- package/lib/mocha/inject.js +22 -5
- package/lib/mocha/scenarioConfig.js +2 -2
- package/lib/mocha/suite.js +9 -2
- package/lib/mocha/test.js +10 -13
- package/lib/mocha/ui.js +28 -31
- package/lib/output.js +11 -9
- package/lib/parser.js +44 -44
- package/lib/pause.js +15 -16
- package/lib/plugin/analyze.js +19 -12
- package/lib/plugin/auth.js +20 -21
- package/lib/plugin/autoDelay.js +12 -8
- package/lib/plugin/coverage.js +12 -8
- package/lib/plugin/customLocator.js +3 -3
- package/lib/plugin/customReporter.js +3 -2
- package/lib/plugin/heal.js +14 -9
- package/lib/plugin/pageInfo.js +10 -10
- package/lib/plugin/pauseOnFail.js +4 -3
- package/lib/plugin/retryFailedStep.js +47 -5
- package/lib/plugin/screenshotOnFail.js +75 -37
- package/lib/plugin/stepByStepReport.js +14 -14
- package/lib/plugin/stepTimeout.js +4 -3
- package/lib/plugin/subtitles.js +6 -5
- package/lib/recorder.js +33 -23
- package/lib/rerun.js +69 -26
- package/lib/result.js +4 -4
- package/lib/secret.js +18 -17
- package/lib/session.js +95 -89
- package/lib/step/base.js +6 -6
- package/lib/step/config.js +1 -1
- package/lib/step/func.js +3 -3
- package/lib/step/helper.js +3 -3
- package/lib/step/meta.js +4 -4
- package/lib/step/record.js +11 -11
- package/lib/step/retry.js +3 -3
- package/lib/step/section.js +3 -3
- package/lib/step.js +7 -10
- package/lib/steps.js +9 -5
- package/lib/store.js +1 -1
- package/lib/timeout.js +1 -7
- package/lib/transform.js +8 -8
- package/lib/translation.js +32 -18
- package/lib/utils.js +68 -97
- package/lib/workerStorage.js +16 -17
- package/lib/workers.js +145 -171
- package/package.json +58 -55
- package/translations/de-DE.js +2 -2
- package/translations/fr-FR.js +2 -2
- package/translations/index.js +23 -10
- package/translations/it-IT.js +2 -2
- package/translations/ja-JP.js +2 -2
- package/translations/nl-NL.js +2 -2
- package/translations/pl-PL.js +2 -2
- package/translations/pt-BR.js +2 -2
- package/translations/ru-RU.js +2 -2
- package/translations/utils.js +4 -3
- package/translations/zh-CN.js +2 -2
- package/translations/zh-TW.js +2 -2
- package/typings/index.d.ts +7 -18
- package/typings/promiseBasedTypes.d.ts +3769 -5450
- package/typings/types.d.ts +3953 -5778
- package/bin/test-server.js +0 -53
- package/lib/element/WebElement.js +0 -327
- package/lib/helper/Nightmare.js +0 -1486
- package/lib/helper/Protractor.js +0 -1840
- package/lib/helper/TestCafe.js +0 -1391
- package/lib/helper/clientscripts/nightmare.js +0 -213
- package/lib/helper/extras/PlaywrightReactVueLocator.js +0 -43
- package/lib/helper/testcafe/testControllerHolder.js +0 -42
- package/lib/helper/testcafe/testcafe-utils.js +0 -61
- package/lib/listener/retryEnhancer.js +0 -85
- package/lib/plugin/allure.js +0 -15
- package/lib/plugin/autoLogin.js +0 -5
- package/lib/plugin/commentStep.js +0 -141
- package/lib/plugin/eachElement.js +0 -127
- package/lib/plugin/fakerTransform.js +0 -49
- package/lib/plugin/htmlReporter.js +0 -1947
- package/lib/plugin/retryTo.js +0 -16
- package/lib/plugin/selenoid.js +0 -364
- package/lib/plugin/standardActingHelpers.js +0 -6
- package/lib/plugin/tryTo.js +0 -16
- package/lib/plugin/wdio.js +0 -247
- package/lib/test-server.js +0 -323
- package/lib/within.js +0 -90
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
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 (
|
|
125
|
-
|
|
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
|
-
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
186
|
+
export function setup(suite) {
|
|
187
|
+
return function (done) {
|
|
188
|
+
const doneFn = makeDoneCallableOnce(done)
|
|
202
189
|
recorder.startUnlessRunning()
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
208
|
-
|
|
209
|
-
|
|
198
|
+
export function teardown(suite) {
|
|
199
|
+
return function (done) {
|
|
200
|
+
const doneFn = makeDoneCallableOnce(done)
|
|
210
201
|
recorder.startUnlessRunning()
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
210
|
+
export function suiteSetup(suite) {
|
|
211
|
+
return function (done) {
|
|
212
|
+
const doneFn = makeDoneCallableOnce(done)
|
|
218
213
|
recorder.startUnlessRunning()
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
232
|
+
export function suiteTeardown(suite) {
|
|
233
|
+
return function (done) {
|
|
234
|
+
const doneFn = makeDoneCallableOnce(done)
|
|
226
235
|
recorder.startUnlessRunning()
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
20
|
-
if (
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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${
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
153
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
186
|
+
async result() {
|
|
187
|
+
try {
|
|
188
|
+
const Container = await getContainer()
|
|
189
|
+
Container.result().addStats(this.stats)
|
|
190
|
+
Container.result().finish()
|
|
163
191
|
|
|
164
|
-
|
|
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:')}
|
|
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
|
-
|
|
304
|
+
Container.result().addFailures(failuresLog)
|
|
277
305
|
|
|
278
|
-
|
|
306
|
+
output.result(stats.passes, stats.failures, stats.pending, ms(stats.duration), stats.failedHooks)
|
|
279
307
|
|
|
280
|
-
|
|
281
|
-
|
|
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
|
-
|
|
339
|
+
export default function (runner, opts) {
|
|
307
340
|
return new Cli(runner, opts)
|
|
308
|
-
}
|
|
341
|
+
}
|
package/lib/mocha/factory.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
42
|
+
// Override loadFiles to handle feature files
|
|
43
|
+
const originalLoadFiles = Mocha.prototype.loadFiles
|
|
44
|
+
mocha.loadFiles = function (fn) {
|
|
33
45
|
// load features
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
38
|
-
|
|
53
|
+
for (const file of featureFiles) {
|
|
54
|
+
const suite = gherkinParser(fs.readFileSync(file, 'utf8'), file)
|
|
55
|
+
this.suite.addSuite(suite)
|
|
56
|
+
}
|
|
39
57
|
|
|
40
|
-
|
|
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
|
-
|
|
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
|
-
|
|
160
|
+
export default MochaFactory
|