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.
- package/README.md +133 -120
- package/bin/codecept.js +107 -96
- package/bin/test-server.js +64 -0
- package/docs/webapi/clearCookie.mustache +1 -1
- package/docs/webapi/click.mustache +5 -1
- package/lib/actor.js +71 -103
- package/lib/ai.js +159 -188
- package/lib/assert/empty.js +22 -24
- package/lib/assert/equal.js +30 -37
- package/lib/assert/error.js +14 -14
- package/lib/assert/include.js +43 -48
- package/lib/assert/throws.js +11 -11
- package/lib/assert/truth.js +22 -22
- package/lib/assert.js +20 -18
- package/lib/codecept.js +238 -162
- package/lib/colorUtils.js +50 -52
- package/lib/command/check.js +206 -0
- package/lib/command/configMigrate.js +56 -51
- package/lib/command/definitions.js +96 -109
- package/lib/command/dryRun.js +77 -79
- package/lib/command/generate.js +234 -194
- package/lib/command/gherkin/init.js +42 -33
- package/lib/command/gherkin/snippets.js +76 -74
- package/lib/command/gherkin/steps.js +20 -17
- package/lib/command/info.js +74 -38
- package/lib/command/init.js +300 -290
- package/lib/command/interactive.js +41 -32
- package/lib/command/list.js +28 -27
- package/lib/command/run-multiple/chunk.js +51 -48
- package/lib/command/run-multiple/collection.js +5 -5
- package/lib/command/run-multiple/run.js +5 -1
- package/lib/command/run-multiple.js +97 -97
- package/lib/command/run-rerun.js +19 -25
- package/lib/command/run-workers.js +68 -92
- package/lib/command/run.js +39 -27
- package/lib/command/utils.js +80 -64
- package/lib/command/workers/runTests.js +388 -226
- package/lib/config.js +124 -50
- package/lib/container.js +765 -260
- package/lib/data/context.js +60 -61
- package/lib/data/dataScenarioConfig.js +47 -47
- package/lib/data/dataTableArgument.js +32 -32
- package/lib/data/table.js +22 -22
- package/lib/effects.js +307 -0
- package/lib/element/WebElement.js +327 -0
- package/lib/els.js +160 -0
- package/lib/event.js +173 -163
- package/lib/globals.js +141 -0
- package/lib/heal.js +89 -85
- package/lib/helper/AI.js +131 -41
- package/lib/helper/ApiDataFactory.js +107 -75
- package/lib/helper/Appium.js +542 -404
- package/lib/helper/FileSystem.js +100 -79
- package/lib/helper/GraphQL.js +44 -43
- package/lib/helper/GraphQLDataFactory.js +52 -52
- package/lib/helper/JSONResponse.js +126 -88
- package/lib/helper/Mochawesome.js +54 -29
- package/lib/helper/Playwright.js +2547 -1316
- package/lib/helper/Puppeteer.js +1578 -1181
- package/lib/helper/REST.js +209 -68
- package/lib/helper/WebDriver.js +1482 -1342
- 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/PlaywrightReactVueLocator.js +17 -8
- package/lib/helper/extras/PlaywrightRestartOpts.js +25 -11
- package/lib/helper/extras/Popup.js +22 -22
- package/lib/helper/extras/React.js +27 -28
- package/lib/helper/network/actions.js +36 -42
- package/lib/helper/network/utils.js +78 -84
- package/lib/helper/scripts/blurElement.js +5 -5
- package/lib/helper/scripts/focusElement.js +5 -5
- package/lib/helper/scripts/highlightElement.js +8 -8
- package/lib/helper/scripts/isElementClickable.js +34 -34
- package/lib/helper.js +2 -3
- package/lib/history.js +23 -19
- package/lib/hooks.js +8 -8
- package/lib/html.js +94 -104
- package/lib/index.js +38 -27
- package/lib/listener/config.js +30 -23
- package/lib/listener/emptyRun.js +54 -0
- package/lib/listener/enhancedGlobalRetry.js +110 -0
- package/lib/listener/exit.js +16 -18
- package/lib/listener/globalRetry.js +70 -0
- package/lib/listener/globalTimeout.js +181 -0
- package/lib/listener/helpers.js +76 -51
- package/lib/listener/mocha.js +10 -11
- package/lib/listener/result.js +11 -0
- package/lib/listener/retryEnhancer.js +85 -0
- package/lib/listener/steps.js +71 -59
- package/lib/listener/store.js +20 -0
- package/lib/locator.js +214 -197
- package/lib/mocha/asyncWrapper.js +274 -0
- package/lib/mocha/bdd.js +167 -0
- package/lib/mocha/cli.js +341 -0
- package/lib/mocha/factory.js +163 -0
- package/lib/mocha/featureConfig.js +89 -0
- package/lib/mocha/gherkin.js +231 -0
- package/lib/mocha/hooks.js +121 -0
- package/lib/mocha/index.js +21 -0
- package/lib/mocha/inject.js +46 -0
- package/lib/{interfaces → mocha}/scenarioConfig.js +58 -34
- package/lib/mocha/suite.js +89 -0
- package/lib/mocha/test.js +184 -0
- package/lib/mocha/types.d.ts +42 -0
- package/lib/mocha/ui.js +242 -0
- package/lib/output.js +141 -71
- package/lib/parser.js +47 -44
- package/lib/pause.js +173 -145
- package/lib/plugin/analyze.js +403 -0
- package/lib/plugin/{autoLogin.js → auth.js} +178 -79
- package/lib/plugin/autoDelay.js +36 -40
- package/lib/plugin/coverage.js +131 -78
- package/lib/plugin/customLocator.js +22 -21
- package/lib/plugin/customReporter.js +53 -0
- package/lib/plugin/enhancedRetryFailedStep.js +99 -0
- package/lib/plugin/heal.js +101 -110
- package/lib/plugin/htmlReporter.js +3648 -0
- package/lib/plugin/pageInfo.js +140 -0
- package/lib/plugin/pauseOnFail.js +12 -11
- package/lib/plugin/retryFailedStep.js +82 -47
- package/lib/plugin/screenshotOnFail.js +111 -92
- package/lib/plugin/stepByStepReport.js +159 -101
- package/lib/plugin/stepTimeout.js +20 -25
- package/lib/plugin/subtitles.js +38 -38
- package/lib/recorder.js +193 -130
- package/lib/rerun.js +94 -49
- package/lib/result.js +238 -0
- package/lib/retryCoordinator.js +207 -0
- package/lib/secret.js +20 -18
- package/lib/session.js +95 -89
- package/lib/step/base.js +239 -0
- package/lib/step/comment.js +10 -0
- package/lib/step/config.js +50 -0
- package/lib/step/func.js +46 -0
- package/lib/step/helper.js +50 -0
- package/lib/step/meta.js +99 -0
- package/lib/step/record.js +74 -0
- package/lib/step/retry.js +11 -0
- package/lib/step/section.js +55 -0
- package/lib/step.js +18 -329
- package/lib/steps.js +54 -0
- package/lib/store.js +38 -7
- package/lib/template/heal.js +3 -12
- package/lib/template/prompts/generatePageObject.js +31 -0
- package/lib/template/prompts/healStep.js +13 -0
- package/lib/template/prompts/writeStep.js +9 -0
- package/lib/test-server.js +334 -0
- package/lib/timeout.js +60 -0
- package/lib/transform.js +8 -8
- package/lib/translation.js +34 -21
- package/lib/utils/mask_data.js +47 -0
- package/lib/utils.js +411 -228
- package/lib/workerStorage.js +37 -34
- package/lib/workers.js +532 -296
- package/package.json +115 -95
- package/translations/de-DE.js +5 -3
- package/translations/fr-FR.js +5 -4
- package/translations/index.js +22 -12
- package/translations/it-IT.js +4 -3
- package/translations/ja-JP.js +4 -3
- package/translations/nl-NL.js +76 -0
- package/translations/pl-PL.js +4 -3
- package/translations/pt-BR.js +4 -3
- package/translations/ru-RU.js +4 -3
- package/translations/utils.js +10 -0
- package/translations/zh-CN.js +4 -3
- package/translations/zh-TW.js +4 -3
- package/typings/index.d.ts +546 -185
- package/typings/promiseBasedTypes.d.ts +150 -879
- package/typings/types.d.ts +547 -996
- package/lib/cli.js +0 -249
- package/lib/dirname.js +0 -5
- package/lib/helper/Expect.js +0 -425
- package/lib/helper/ExpectHelper.js +0 -399
- package/lib/helper/MockServer.js +0 -223
- package/lib/helper/Nightmare.js +0 -1411
- package/lib/helper/Protractor.js +0 -1835
- package/lib/helper/SoftExpectHelper.js +0 -381
- package/lib/helper/TestCafe.js +0 -1410
- package/lib/helper/clientscripts/nightmare.js +0 -213
- package/lib/helper/testcafe/testControllerHolder.js +0 -42
- package/lib/helper/testcafe/testcafe-utils.js +0 -63
- package/lib/interfaces/bdd.js +0 -98
- package/lib/interfaces/featureConfig.js +0 -69
- package/lib/interfaces/gherkin.js +0 -195
- package/lib/listener/artifacts.js +0 -19
- package/lib/listener/retry.js +0 -68
- package/lib/listener/timeout.js +0 -109
- package/lib/mochaFactory.js +0 -110
- package/lib/plugin/allure.js +0 -15
- package/lib/plugin/commentStep.js +0 -136
- package/lib/plugin/debugErrors.js +0 -67
- package/lib/plugin/eachElement.js +0 -127
- package/lib/plugin/fakerTransform.js +0 -49
- package/lib/plugin/retryTo.js +0 -121
- package/lib/plugin/selenoid.js +0 -371
- package/lib/plugin/standardActingHelpers.js +0 -9
- package/lib/plugin/tryTo.js +0 -105
- package/lib/plugin/wdio.js +0 -246
- package/lib/scenario.js +0 -222
- package/lib/ui.js +0 -238
- package/lib/within.js +0 -70
package/lib/container.js
CHANGED
|
@@ -1,34 +1,50 @@
|
|
|
1
|
-
import
|
|
2
|
-
import path
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
|
|
12
|
-
import
|
|
13
|
-
import
|
|
14
|
-
import
|
|
15
|
-
import
|
|
16
|
-
import
|
|
17
|
-
|
|
18
|
-
|
|
1
|
+
import { globSync } from 'glob'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import fs from 'fs'
|
|
4
|
+
import debugModule from 'debug'
|
|
5
|
+
const debug = debugModule('codeceptjs:container')
|
|
6
|
+
import { MetaStep } from './step.js'
|
|
7
|
+
import { methodsOfObject, fileExists, isFunction, isAsyncFunction, installedLocally, deepMerge } from './utils.js'
|
|
8
|
+
import Translation from './translation.js'
|
|
9
|
+
import MochaFactory from './mocha/factory.js'
|
|
10
|
+
import recorder from './recorder.js'
|
|
11
|
+
import event from './event.js'
|
|
12
|
+
import WorkerStorage from './workerStorage.js'
|
|
13
|
+
import store from './store.js'
|
|
14
|
+
import Result from './result.js'
|
|
15
|
+
import ai from './ai.js'
|
|
16
|
+
import actorFactory from './actor.js'
|
|
17
|
+
|
|
18
|
+
let asyncHelperPromise
|
|
19
19
|
|
|
20
20
|
let container = {
|
|
21
21
|
helpers: {},
|
|
22
22
|
support: {},
|
|
23
|
+
proxySupport: {},
|
|
23
24
|
plugins: {},
|
|
25
|
+
actor: null,
|
|
26
|
+
/**
|
|
27
|
+
* @type {Mocha | {}}
|
|
28
|
+
* @ignore
|
|
29
|
+
*/
|
|
24
30
|
mocha: {},
|
|
25
31
|
translation: {},
|
|
26
|
-
}
|
|
32
|
+
/** @type {Result | null} */
|
|
33
|
+
result: null,
|
|
34
|
+
sharedKeys: new Set() // Track keys shared via share() function
|
|
35
|
+
}
|
|
27
36
|
|
|
28
37
|
/**
|
|
29
38
|
* Dependency Injection Container
|
|
30
39
|
*/
|
|
31
|
-
|
|
40
|
+
class Container {
|
|
41
|
+
/**
|
|
42
|
+
* Get the standard acting helpers of CodeceptJS Container
|
|
43
|
+
*
|
|
44
|
+
*/
|
|
45
|
+
static get STANDARD_ACTING_HELPERS() {
|
|
46
|
+
return ['Playwright', 'WebDriver', 'Puppeteer', 'Appium']
|
|
47
|
+
}
|
|
32
48
|
/**
|
|
33
49
|
* Create container with all required helpers and support objects
|
|
34
50
|
*
|
|
@@ -36,22 +52,70 @@ export default class Container {
|
|
|
36
52
|
* @param {*} config
|
|
37
53
|
* @param {*} opts
|
|
38
54
|
*/
|
|
39
|
-
static create(config, opts) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
55
|
+
static async create(config, opts) {
|
|
56
|
+
debug('creating container')
|
|
57
|
+
asyncHelperPromise = Promise.resolve()
|
|
58
|
+
|
|
59
|
+
// dynamically create mocha instance
|
|
60
|
+
const mochaConfig = config.mocha || {}
|
|
61
|
+
if (config.grep && !opts.grep) mochaConfig.grep = config.grep
|
|
62
|
+
this.createMocha = () => (container.mocha = MochaFactory.create(mochaConfig, opts || {}))
|
|
63
|
+
this.createMocha()
|
|
64
|
+
|
|
65
|
+
// create support objects
|
|
66
|
+
container.support = {}
|
|
67
|
+
container.helpers = await createHelpers(config.helpers || {})
|
|
68
|
+
container.translation = await loadTranslation(config.translation || null, config.vocabularies || [])
|
|
69
|
+
container.proxySupport = createSupportObjects(config.include || {})
|
|
70
|
+
container.plugins = await createPlugins(config.plugins || {}, opts)
|
|
71
|
+
container.result = new Result()
|
|
72
|
+
|
|
73
|
+
// Preload includes (so proxies can expose real objects synchronously)
|
|
74
|
+
const includes = config.include || {}
|
|
75
|
+
|
|
76
|
+
// Ensure I is available for DI modules at import time
|
|
77
|
+
if (Object.prototype.hasOwnProperty.call(includes, 'I')) {
|
|
78
|
+
try {
|
|
79
|
+
const mod = includes.I
|
|
80
|
+
if (typeof mod === 'string') {
|
|
81
|
+
container.support.I = await loadSupportObject(mod, 'I')
|
|
82
|
+
} else if (typeof mod === 'function') {
|
|
83
|
+
container.support.I = await loadSupportObject(mod, 'I')
|
|
84
|
+
} else if (mod && typeof mod === 'object') {
|
|
85
|
+
container.support.I = mod
|
|
86
|
+
}
|
|
87
|
+
} catch (e) {
|
|
88
|
+
throw new Error(`Could not include object I: ${e.message}`)
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
// Create default actor if not provided via includes
|
|
92
|
+
createActor()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Load remaining includes except I
|
|
96
|
+
for (const [name, mod] of Object.entries(includes)) {
|
|
97
|
+
if (name === 'I') continue
|
|
98
|
+
try {
|
|
99
|
+
if (typeof mod === 'string') {
|
|
100
|
+
container.support[name] = await loadSupportObject(mod, name)
|
|
101
|
+
} else if (typeof mod === 'function') {
|
|
102
|
+
// function or class
|
|
103
|
+
container.support[name] = await loadSupportObject(mod, name)
|
|
104
|
+
} else if (mod && typeof mod === 'object') {
|
|
105
|
+
container.support[name] = mod
|
|
106
|
+
}
|
|
107
|
+
} catch (e) {
|
|
108
|
+
throw new Error(`Could not include object ${name}: ${e.message}`)
|
|
109
|
+
}
|
|
43
110
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
container.
|
|
52
|
-
if (opts && opts.ai) ai.enable(config.ai); // enable AI Assistant
|
|
53
|
-
if (config.gherkin) loadGherkinSteps(config.gherkin.steps || []);
|
|
54
|
-
if (opts && typeof opts.timeouts === 'boolean') store.timeouts = opts.timeouts;
|
|
111
|
+
|
|
112
|
+
if (opts && opts.ai) ai.enable(config.ai) // enable AI Assistant
|
|
113
|
+
if (config.gherkin) await loadGherkinStepsAsync(config.gherkin.steps || [])
|
|
114
|
+
if (opts && typeof opts.timeouts === 'boolean') store.timeouts = opts.timeouts
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
static actor() {
|
|
118
|
+
return container.support.I
|
|
55
119
|
}
|
|
56
120
|
|
|
57
121
|
/**
|
|
@@ -62,7 +126,10 @@ export default class Container {
|
|
|
62
126
|
* @returns { * }
|
|
63
127
|
*/
|
|
64
128
|
static plugins(name) {
|
|
65
|
-
|
|
129
|
+
if (!name) {
|
|
130
|
+
return container.plugins
|
|
131
|
+
}
|
|
132
|
+
return container.plugins[name]
|
|
66
133
|
}
|
|
67
134
|
|
|
68
135
|
/**
|
|
@@ -74,9 +141,10 @@ export default class Container {
|
|
|
74
141
|
*/
|
|
75
142
|
static support(name) {
|
|
76
143
|
if (!name) {
|
|
77
|
-
return container.
|
|
144
|
+
return container.proxySupport
|
|
78
145
|
}
|
|
79
|
-
return
|
|
146
|
+
// Always return the proxy to ensure MetaStep creation works
|
|
147
|
+
return container.proxySupport[name]
|
|
80
148
|
}
|
|
81
149
|
|
|
82
150
|
/**
|
|
@@ -88,9 +156,9 @@ export default class Container {
|
|
|
88
156
|
*/
|
|
89
157
|
static helpers(name) {
|
|
90
158
|
if (!name) {
|
|
91
|
-
return container.helpers
|
|
159
|
+
return container.helpers
|
|
92
160
|
}
|
|
93
|
-
return container.helpers[name]
|
|
161
|
+
return container.helpers[name]
|
|
94
162
|
}
|
|
95
163
|
|
|
96
164
|
/**
|
|
@@ -99,7 +167,7 @@ export default class Container {
|
|
|
99
167
|
* @api
|
|
100
168
|
*/
|
|
101
169
|
static translation() {
|
|
102
|
-
return container.translation
|
|
170
|
+
return container.translation
|
|
103
171
|
}
|
|
104
172
|
|
|
105
173
|
/**
|
|
@@ -109,7 +177,19 @@ export default class Container {
|
|
|
109
177
|
* @returns { * }
|
|
110
178
|
*/
|
|
111
179
|
static mocha() {
|
|
112
|
-
return container.mocha
|
|
180
|
+
return container.mocha
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Get result
|
|
185
|
+
*
|
|
186
|
+
* @returns {Result}
|
|
187
|
+
*/
|
|
188
|
+
static result() {
|
|
189
|
+
if (!container.result) {
|
|
190
|
+
container.result = new Result()
|
|
191
|
+
}
|
|
192
|
+
return container.result
|
|
113
193
|
}
|
|
114
194
|
|
|
115
195
|
/**
|
|
@@ -119,7 +199,15 @@ export default class Container {
|
|
|
119
199
|
* @param {Object<string, *>} newContainer
|
|
120
200
|
*/
|
|
121
201
|
static append(newContainer) {
|
|
122
|
-
container = deepMerge(container, newContainer)
|
|
202
|
+
container = deepMerge(container, newContainer)
|
|
203
|
+
|
|
204
|
+
// If new support objects are added, update the proxy support
|
|
205
|
+
if (newContainer.support) {
|
|
206
|
+
const newProxySupport = createSupportObjects(newContainer.support)
|
|
207
|
+
container.proxySupport = { ...container.proxySupport, ...newProxySupport }
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
debug('appended', JSON.stringify(newContainer).slice(0, 300))
|
|
123
211
|
}
|
|
124
212
|
|
|
125
213
|
/**
|
|
@@ -129,11 +217,26 @@ export default class Container {
|
|
|
129
217
|
* @param {Object<string, *>} newSupport
|
|
130
218
|
* @param {Object<string, *>} newPlugins
|
|
131
219
|
*/
|
|
132
|
-
static clear(newHelpers, newSupport, newPlugins) {
|
|
133
|
-
container.helpers = newHelpers
|
|
134
|
-
container.
|
|
135
|
-
container.
|
|
136
|
-
container.
|
|
220
|
+
static async clear(newHelpers = {}, newSupport = {}, newPlugins = {}) {
|
|
221
|
+
container.helpers = newHelpers
|
|
222
|
+
container.translation = await loadTranslation()
|
|
223
|
+
container.proxySupport = createSupportObjects(newSupport)
|
|
224
|
+
container.plugins = newPlugins
|
|
225
|
+
container.sharedKeys = new Set() // Clear shared keys
|
|
226
|
+
asyncHelperPromise = Promise.resolve()
|
|
227
|
+
store.actor = null
|
|
228
|
+
debug('container cleared')
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* @param {Function|null} fn
|
|
233
|
+
* @returns {Promise<void>}
|
|
234
|
+
*/
|
|
235
|
+
static async started(fn = null) {
|
|
236
|
+
if (fn) {
|
|
237
|
+
asyncHelperPromise = asyncHelperPromise.then(fn)
|
|
238
|
+
}
|
|
239
|
+
return asyncHelperPromise
|
|
137
240
|
}
|
|
138
241
|
|
|
139
242
|
/**
|
|
@@ -143,308 +246,710 @@ export default class Container {
|
|
|
143
246
|
* @param {Object} options - set {local: true} to not share among workers
|
|
144
247
|
*/
|
|
145
248
|
static share(data, options = {}) {
|
|
146
|
-
|
|
249
|
+
// Instead of using append which replaces the entire container,
|
|
250
|
+
// directly update the support object to maintain proxy references
|
|
251
|
+
Object.assign(container.support, data)
|
|
252
|
+
|
|
253
|
+
// Track which keys were explicitly shared
|
|
254
|
+
Object.keys(data).forEach(key => container.sharedKeys.add(key))
|
|
255
|
+
|
|
147
256
|
if (!options.local) {
|
|
148
|
-
WorkerStorage.share(data)
|
|
257
|
+
WorkerStorage.share(data)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
static createMocha(config = {}, opts = {}) {
|
|
262
|
+
const mochaConfig = config?.mocha || {}
|
|
263
|
+
if (config?.grep && !opts?.grep) {
|
|
264
|
+
mochaConfig.grep = config.grep
|
|
149
265
|
}
|
|
266
|
+
container.mocha = MochaFactory.create(mochaConfig, opts || {})
|
|
150
267
|
}
|
|
151
268
|
}
|
|
152
269
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
270
|
+
export default Container
|
|
271
|
+
|
|
272
|
+
async function createHelpers(config) {
|
|
273
|
+
const helpers = {}
|
|
274
|
+
for (let helperName in config) {
|
|
157
275
|
try {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
} else {
|
|
166
|
-
moduleName = `./helper/${helperName}.js`; // built-in helper
|
|
276
|
+
let HelperClass
|
|
277
|
+
|
|
278
|
+
// Check if helper class was stored in config during ESM import processing
|
|
279
|
+
if (config[helperName]._helperClass) {
|
|
280
|
+
HelperClass = config[helperName]._helperClass
|
|
281
|
+
debug(`helper ${helperName} loaded from ESM import`)
|
|
167
282
|
}
|
|
168
283
|
|
|
169
|
-
//
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
174
|
-
HelperClass = importSync(path.resolve(__dirname, moduleName)).default;
|
|
175
|
-
} else {
|
|
176
|
-
// check if the new syntax export default HelperName is used and loads the Helper, otherwise loads the module that used old syntax export = HelperName.
|
|
177
|
-
HelperClass = importSync(path.resolve(moduleName)).default;
|
|
284
|
+
// ESM import (legacy check)
|
|
285
|
+
if (!HelperClass && typeof helperName === 'function' && helperName.prototype) {
|
|
286
|
+
HelperClass = helperName
|
|
287
|
+
helperName = HelperClass.constructor.name
|
|
178
288
|
}
|
|
179
289
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
290
|
+
// classical require - may be async for ESM modules
|
|
291
|
+
if (!HelperClass) {
|
|
292
|
+
const helperResult = requireHelperFromModule(helperName, config)
|
|
293
|
+
if (helperResult instanceof Promise) {
|
|
294
|
+
// Handle async ESM loading
|
|
295
|
+
helpers[helperName] = {}
|
|
296
|
+
asyncHelperPromise = asyncHelperPromise
|
|
297
|
+
.then(() => helperResult)
|
|
298
|
+
.then(async ResolvedHelperClass => {
|
|
299
|
+
debug(`helper ${helperName} resolved type: ${typeof ResolvedHelperClass}`, ResolvedHelperClass)
|
|
300
|
+
|
|
301
|
+
// Extract default export from ESM module wrapper if needed
|
|
302
|
+
if (ResolvedHelperClass && ResolvedHelperClass.__esModule && ResolvedHelperClass.default) {
|
|
303
|
+
ResolvedHelperClass = ResolvedHelperClass.default
|
|
304
|
+
debug(`extracted default export for ${helperName}, new type: ${typeof ResolvedHelperClass}`)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (typeof ResolvedHelperClass !== 'function') {
|
|
308
|
+
throw new Error(`Helper '${helperName}' is not a class. Got: ${typeof ResolvedHelperClass}`)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
checkHelperRequirements(ResolvedHelperClass)
|
|
312
|
+
helpers[helperName] = new ResolvedHelperClass(config[helperName])
|
|
313
|
+
if (helpers[helperName]._init) await helpers[helperName]._init()
|
|
314
|
+
debug(`helper ${helperName} async initialized`)
|
|
315
|
+
})
|
|
316
|
+
continue
|
|
317
|
+
} else {
|
|
318
|
+
HelperClass = helperResult
|
|
191
319
|
}
|
|
192
320
|
}
|
|
193
|
-
|
|
321
|
+
|
|
322
|
+
// handle async CJS modules that use dynamic import
|
|
323
|
+
if (isAsyncFunction(HelperClass)) {
|
|
324
|
+
helpers[helperName] = {}
|
|
325
|
+
|
|
326
|
+
asyncHelperPromise = asyncHelperPromise
|
|
327
|
+
.then(() => HelperClass())
|
|
328
|
+
.then(ResolvedHelperClass => {
|
|
329
|
+
// Check if ResolvedHelperClass is a constructor function
|
|
330
|
+
if (typeof ResolvedHelperClass?.constructor !== 'function') {
|
|
331
|
+
throw new Error(`Helper class from module '${helperName}' is not a class. Use CJS async module syntax.`)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
debug(`helper ${helperName} async initialized`)
|
|
335
|
+
|
|
336
|
+
helpers[helperName] = new ResolvedHelperClass(config[helperName])
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
continue
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
checkHelperRequirements(HelperClass)
|
|
343
|
+
|
|
344
|
+
helpers[helperName] = new HelperClass(config[helperName])
|
|
345
|
+
debug(`helper ${helperName} initialized`)
|
|
194
346
|
} catch (err) {
|
|
195
|
-
throw new Error(`Could not load helper ${helperName}
|
|
347
|
+
throw new Error(`Could not load helper ${helperName} (${err.message})`)
|
|
196
348
|
}
|
|
197
349
|
}
|
|
198
350
|
|
|
199
351
|
for (const name in helpers) {
|
|
200
|
-
if (helpers[name]._init) helpers[name]._init()
|
|
352
|
+
if (helpers[name]._init) await helpers[name]._init()
|
|
201
353
|
}
|
|
202
|
-
return helpers
|
|
354
|
+
return helpers
|
|
203
355
|
}
|
|
204
356
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
357
|
+
function checkHelperRequirements(HelperClass) {
|
|
358
|
+
if (HelperClass._checkRequirements) {
|
|
359
|
+
const requirements = HelperClass._checkRequirements()
|
|
360
|
+
if (requirements) {
|
|
361
|
+
let install
|
|
362
|
+
if (installedLocally()) {
|
|
363
|
+
install = `npm install --save-dev ${requirements.join(' ')}`
|
|
364
|
+
} else {
|
|
365
|
+
console.log('WARNING: CodeceptJS is not installed locally. It is recommended to switch to local installation')
|
|
366
|
+
install = `[sudo] npm install -g ${requirements.join(' ')}`
|
|
367
|
+
}
|
|
368
|
+
throw new Error(`Required modules are not installed.\n\nRUN: ${install}`)
|
|
217
369
|
}
|
|
218
370
|
}
|
|
371
|
+
}
|
|
219
372
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
let newObj = getSupportObject(config, name);
|
|
373
|
+
async function requireHelperFromModule(helperName, config, HelperClass) {
|
|
374
|
+
const moduleName = getHelperModuleName(helperName, config)
|
|
375
|
+
if (moduleName.startsWith('./helper/')) {
|
|
224
376
|
try {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
377
|
+
// For built-in helpers, use direct relative import with .js extension
|
|
378
|
+
const helperPath = `${moduleName}.js`
|
|
379
|
+
const mod = await import(helperPath)
|
|
380
|
+
HelperClass = mod.default || mod
|
|
381
|
+
} catch (err) {
|
|
382
|
+
throw err
|
|
383
|
+
}
|
|
384
|
+
} else {
|
|
385
|
+
// check if the new syntax export default HelperName is used and loads the Helper, otherwise loads the module that used old syntax export = HelperName.
|
|
386
|
+
try {
|
|
387
|
+
// Try dynamic import for both CommonJS and ESM modules
|
|
388
|
+
const mod = await import(moduleName)
|
|
389
|
+
if (!mod && !mod.default) {
|
|
390
|
+
throw new Error(`Helper module '${moduleName}' was not found. Make sure you have installed the package correctly.`)
|
|
229
391
|
}
|
|
392
|
+
HelperClass = mod.default || mod
|
|
230
393
|
} catch (err) {
|
|
231
|
-
|
|
394
|
+
if (err.code === 'ERR_REQUIRE_ESM' || (err.message && err.message.includes('ES module'))) {
|
|
395
|
+
// This is an ESM module, use dynamic import
|
|
396
|
+
try {
|
|
397
|
+
const pathModule = await import('path')
|
|
398
|
+
const absolutePath = pathModule.default.resolve(moduleName)
|
|
399
|
+
const mod = await import(absolutePath)
|
|
400
|
+
HelperClass = mod.default || mod
|
|
401
|
+
debug(`helper ${helperName} loaded via ESM import`)
|
|
402
|
+
} catch (importErr) {
|
|
403
|
+
throw new Error(`Helper module '${moduleName}' could not be imported as ESM: ${importErr.message}`)
|
|
404
|
+
}
|
|
405
|
+
} else if (err.code === 'MODULE_NOT_FOUND') {
|
|
406
|
+
throw new Error(`Helper module '${moduleName}' was not found. Make sure you have installed the package correctly.`)
|
|
407
|
+
} else {
|
|
408
|
+
throw err
|
|
409
|
+
}
|
|
232
410
|
}
|
|
233
|
-
return newObj;
|
|
234
411
|
}
|
|
412
|
+
return HelperClass
|
|
413
|
+
}
|
|
235
414
|
|
|
415
|
+
function createSupportObjects(config) {
|
|
236
416
|
const asyncWrapper = function (f) {
|
|
237
417
|
return function () {
|
|
238
|
-
return f.apply(this, arguments).catch(
|
|
239
|
-
recorder.saveFirstAsyncError(e)
|
|
240
|
-
throw e
|
|
241
|
-
})
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
Object.keys(objects).forEach((object) => {
|
|
246
|
-
const currentObject = objects[object];
|
|
247
|
-
Object.keys(currentObject).forEach((method) => {
|
|
248
|
-
const currentMethod = currentObject[method];
|
|
249
|
-
if (currentMethod && currentMethod[Symbol.toStringTag] === 'AsyncFunction') {
|
|
250
|
-
objects[object][method] = asyncWrapper(currentMethod);
|
|
251
|
-
}
|
|
252
|
-
});
|
|
253
|
-
});
|
|
418
|
+
return f.apply(this, arguments).catch(e => {
|
|
419
|
+
recorder.saveFirstAsyncError(e)
|
|
420
|
+
throw e
|
|
421
|
+
})
|
|
422
|
+
}
|
|
423
|
+
}
|
|
254
424
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
425
|
+
function lazyLoad(name) {
|
|
426
|
+
return new Proxy(
|
|
427
|
+
{},
|
|
428
|
+
{
|
|
429
|
+
get(target, prop) {
|
|
430
|
+
// behavr like array or
|
|
431
|
+
if (prop === 'length') return Object.keys(config).length
|
|
432
|
+
if (prop === Symbol.iterator) {
|
|
433
|
+
return function* () {
|
|
434
|
+
for (let i = 0; i < Object.keys(config).length; i++) {
|
|
435
|
+
yield target[i]
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// load actual name from vocabulary
|
|
441
|
+
if (container.translation && container.translation.I && name === 'I') {
|
|
442
|
+
// Use translated name for I
|
|
443
|
+
const actualName = container.translation.I
|
|
444
|
+
if (actualName !== 'I') {
|
|
445
|
+
name = actualName
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (name === 'I') {
|
|
450
|
+
if (!container.support.I) {
|
|
451
|
+
// Actor will be created during container.create()
|
|
452
|
+
return undefined
|
|
453
|
+
}
|
|
454
|
+
methodsOfObject(container.support.I)
|
|
455
|
+
return container.support.I[prop]
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (!container.support[name] && typeof config[name] === 'object') {
|
|
459
|
+
container.support[name] = config[name]
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (!container.support[name]) {
|
|
463
|
+
// Cannot load object synchronously in proxy getter
|
|
464
|
+
// Return undefined and log warning - object should be pre-loaded during container creation
|
|
465
|
+
debug(`Support object ${name} not pre-loaded, returning undefined`)
|
|
466
|
+
return undefined
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const currentObject = container.support[name]
|
|
470
|
+
let currentValue = currentObject[prop]
|
|
471
|
+
|
|
472
|
+
if (isFunction(currentValue) || isAsyncFunction(currentValue)) {
|
|
473
|
+
const ms = new MetaStep(name, prop)
|
|
474
|
+
ms.setContext(currentObject)
|
|
475
|
+
if (isAsyncFunction(currentValue)) currentValue = asyncWrapper(currentValue)
|
|
476
|
+
debug(`metastep is created for ${name}.${prop.toString()}()`)
|
|
477
|
+
return ms.run.bind(ms, currentValue)
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return currentValue
|
|
481
|
+
},
|
|
482
|
+
has(target, prop) {
|
|
483
|
+
if (!container.support[name]) {
|
|
484
|
+
// Note: This is sync, so we can't use async loadSupportObject here
|
|
485
|
+
// The object will be loaded lazily on first property access
|
|
486
|
+
return false
|
|
487
|
+
}
|
|
488
|
+
return prop in container.support[name]
|
|
489
|
+
},
|
|
490
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
491
|
+
if (!container.support[name]) {
|
|
492
|
+
// Object will be loaded on property access
|
|
493
|
+
return {
|
|
494
|
+
enumerable: true,
|
|
495
|
+
configurable: true,
|
|
496
|
+
value: undefined,
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
return {
|
|
500
|
+
enumerable: true,
|
|
501
|
+
configurable: true,
|
|
502
|
+
value: container.support[name][prop],
|
|
503
|
+
}
|
|
504
|
+
},
|
|
505
|
+
ownKeys() {
|
|
506
|
+
if (!container.support[name]) {
|
|
507
|
+
return []
|
|
508
|
+
}
|
|
509
|
+
return Reflect.ownKeys(container.support[name])
|
|
510
|
+
},
|
|
511
|
+
},
|
|
512
|
+
)
|
|
513
|
+
}
|
|
269
514
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
515
|
+
const keys = Reflect.ownKeys(config)
|
|
516
|
+
return new Proxy(
|
|
517
|
+
{},
|
|
518
|
+
{
|
|
519
|
+
has(target, key) {
|
|
520
|
+
return keys.includes(key) || container.sharedKeys.has(key)
|
|
521
|
+
},
|
|
522
|
+
ownKeys() {
|
|
523
|
+
// Return both original config keys and explicitly shared keys
|
|
524
|
+
return [...new Set([...keys, ...container.sharedKeys])]
|
|
525
|
+
},
|
|
526
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
527
|
+
return {
|
|
528
|
+
enumerable: true,
|
|
529
|
+
configurable: true,
|
|
530
|
+
value: target[prop],
|
|
275
531
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
532
|
+
},
|
|
533
|
+
get(target, key) {
|
|
534
|
+
// First check if this is an explicitly shared property
|
|
535
|
+
if (container.sharedKeys.has(key) && key in container.support) {
|
|
536
|
+
return container.support[key]
|
|
537
|
+
}
|
|
538
|
+
return lazyLoad(key)
|
|
539
|
+
},
|
|
279
540
|
},
|
|
280
|
-
|
|
541
|
+
)
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function createActor(actorPath) {
|
|
545
|
+
if (container.support.I) return container.support.I
|
|
546
|
+
|
|
547
|
+
// Default actor
|
|
548
|
+
container.support.I = actorFactory({}, Container)
|
|
549
|
+
|
|
550
|
+
return container.support.I
|
|
281
551
|
}
|
|
282
552
|
|
|
283
|
-
function
|
|
284
|
-
|
|
553
|
+
async function loadPluginAsync(modulePath, config) {
|
|
554
|
+
let pluginMod
|
|
555
|
+
try {
|
|
556
|
+
// Try dynamic import first (works for both ESM and CJS)
|
|
557
|
+
pluginMod = await import(modulePath)
|
|
558
|
+
} catch (err) {
|
|
559
|
+
throw new Error(`Could not load plugin from '${modulePath}': ${err.message}`)
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const pluginFactory = pluginMod.default || pluginMod
|
|
563
|
+
if (typeof pluginFactory !== 'function') {
|
|
564
|
+
throw new Error(`Plugin '${modulePath}' is not a function. Expected a plugin factory function.`)
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
return pluginFactory(config)
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
async function loadPluginFallback(modulePath, config) {
|
|
571
|
+
// This function is kept for backwards compatibility but now uses dynamic import
|
|
572
|
+
return await loadPluginAsync(modulePath, config)
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
async function createPlugins(config, options = {}) {
|
|
576
|
+
const plugins = {}
|
|
285
577
|
|
|
286
|
-
const enabledPluginsByOptions = (options.plugins || '').split(',')
|
|
578
|
+
const enabledPluginsByOptions = (options.plugins || '').split(',')
|
|
287
579
|
for (const pluginName in config) {
|
|
288
|
-
if (!config[pluginName]) config[pluginName] = {}
|
|
289
|
-
if (!config[pluginName].enabled &&
|
|
290
|
-
continue
|
|
580
|
+
if (!config[pluginName]) config[pluginName] = {}
|
|
581
|
+
if (!config[pluginName].enabled && enabledPluginsByOptions.indexOf(pluginName) < 0) {
|
|
582
|
+
continue // plugin is disabled
|
|
291
583
|
}
|
|
292
|
-
let module
|
|
584
|
+
let module
|
|
293
585
|
try {
|
|
294
586
|
if (config[pluginName].require) {
|
|
295
|
-
module = config[pluginName].require
|
|
296
|
-
if (module.startsWith('.')) {
|
|
297
|
-
//
|
|
298
|
-
module = path.resolve(global.codecept_dir, module)
|
|
587
|
+
module = config[pluginName].require
|
|
588
|
+
if (module.startsWith('.')) {
|
|
589
|
+
// local
|
|
590
|
+
module = path.resolve(global.codecept_dir, module) // custom plugin
|
|
299
591
|
}
|
|
300
592
|
} else {
|
|
301
|
-
module = `./plugin/${pluginName}.js
|
|
593
|
+
module = `./plugin/${pluginName}.js`
|
|
302
594
|
}
|
|
303
|
-
|
|
595
|
+
|
|
596
|
+
// Use async loading for all plugins (ESM and CJS)
|
|
597
|
+
plugins[pluginName] = await loadPluginAsync(module, config[pluginName])
|
|
598
|
+
debug(`plugin ${pluginName} loaded via async import`)
|
|
304
599
|
} catch (err) {
|
|
305
|
-
throw new Error(`Could not load plugin ${pluginName} from module '${module}':\n${err.message}\n${err.stack}`)
|
|
600
|
+
throw new Error(`Could not load plugin ${pluginName} from module '${module}':\n${err.message}\n${err.stack}`)
|
|
306
601
|
}
|
|
307
602
|
}
|
|
308
|
-
return plugins
|
|
603
|
+
return plugins
|
|
309
604
|
}
|
|
310
605
|
|
|
311
|
-
function
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
}
|
|
316
|
-
return module;
|
|
317
|
-
}
|
|
606
|
+
async function loadGherkinStepsAsync(paths) {
|
|
607
|
+
global.Before = fn => event.dispatcher.on(event.test.started, fn)
|
|
608
|
+
global.After = fn => event.dispatcher.on(event.test.finished, fn)
|
|
609
|
+
global.Fail = fn => event.dispatcher.on(event.test.failed, fn)
|
|
318
610
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
global.Before = fn => event.dispatcher.on(event.test.started, fn);
|
|
322
|
-
// @ts-ignore
|
|
323
|
-
global.After = fn => event.dispatcher.on(event.test.finished, fn);
|
|
324
|
-
global.Fail = fn => event.dispatcher.on(event.test.failed, fn);
|
|
611
|
+
// Import BDD module to access step file tracking functions
|
|
612
|
+
const bddModule = await import('./mocha/bdd.js')
|
|
325
613
|
|
|
326
614
|
// If gherkin.steps is string, then this will iterate through that folder and send all step def js files to loadSupportObject
|
|
327
615
|
// If gherkin.steps is Array, it will go the old way
|
|
328
616
|
// This is done so that we need not enter all Step Definition files under config.gherkin.steps
|
|
329
617
|
if (Array.isArray(paths)) {
|
|
330
618
|
for (const path of paths) {
|
|
331
|
-
|
|
619
|
+
// Set context for step definition file location tracking
|
|
620
|
+
bddModule.setCurrentStepFile(path)
|
|
621
|
+
await loadSupportObject(path, `Step Definition from ${path}`)
|
|
622
|
+
bddModule.clearCurrentStepFile()
|
|
332
623
|
}
|
|
333
624
|
} else {
|
|
334
|
-
|
|
335
|
-
const folderPath = paths.startsWith('.') ? path.join(global.codecept_dir, paths) : '';
|
|
625
|
+
const folderPath = paths.startsWith('.') ? normalizeAndJoin(global.codecept_dir, paths) : ''
|
|
336
626
|
if (folderPath !== '') {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
627
|
+
const files = globSync(folderPath)
|
|
628
|
+
for (const file of files) {
|
|
629
|
+
// Set context for step definition file location tracking
|
|
630
|
+
bddModule.setCurrentStepFile(file)
|
|
631
|
+
await loadSupportObject(file, `Step Definition from ${file}`)
|
|
632
|
+
bddModule.clearCurrentStepFile()
|
|
633
|
+
}
|
|
340
634
|
}
|
|
341
635
|
}
|
|
342
636
|
|
|
343
|
-
|
|
344
|
-
delete global.
|
|
345
|
-
|
|
346
|
-
delete global.After;
|
|
347
|
-
delete global.Fail;
|
|
637
|
+
delete global.Before
|
|
638
|
+
delete global.After
|
|
639
|
+
delete global.Fail
|
|
348
640
|
}
|
|
349
641
|
|
|
350
|
-
function
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
}
|
|
355
|
-
try {
|
|
356
|
-
const obj = importSync(modulePath).default || importSync(modulePath);
|
|
642
|
+
function loadGherkinSteps(paths) {
|
|
643
|
+
global.Before = fn => event.dispatcher.on(event.test.started, fn)
|
|
644
|
+
global.After = fn => event.dispatcher.on(event.test.finished, fn)
|
|
645
|
+
global.Fail = fn => event.dispatcher.on(event.test.failed, fn)
|
|
357
646
|
|
|
358
|
-
|
|
359
|
-
|
|
647
|
+
// Gherkin step loading must be handled asynchronously
|
|
648
|
+
throw new Error('Gherkin step loading must be converted to async. Use loadGherkinStepsAsync() instead.')
|
|
360
649
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
fobj[key] = methods[key];
|
|
366
|
-
});
|
|
650
|
+
delete global.Before
|
|
651
|
+
delete global.After
|
|
652
|
+
delete global.Fail
|
|
653
|
+
}
|
|
367
654
|
|
|
368
|
-
|
|
655
|
+
async function loadSupportObject(modulePath, supportObjectName) {
|
|
656
|
+
if (!modulePath) {
|
|
657
|
+
throw new Error(`Support object "${supportObjectName}" is not defined`)
|
|
658
|
+
}
|
|
659
|
+
// If function/class provided directly
|
|
660
|
+
if (typeof modulePath === 'function') {
|
|
661
|
+
try {
|
|
662
|
+
// class constructor
|
|
663
|
+
if (modulePath.prototype && modulePath.prototype.constructor === modulePath) {
|
|
664
|
+
return new modulePath()
|
|
369
665
|
}
|
|
666
|
+
// plain function factory
|
|
667
|
+
return modulePath()
|
|
668
|
+
} catch (err) {
|
|
669
|
+
throw new Error(`Could not include object ${supportObjectName} from function: ${err.message}`)
|
|
370
670
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
671
|
+
}
|
|
672
|
+
if (typeof modulePath === 'string' && modulePath.charAt(0) === '.') {
|
|
673
|
+
modulePath = path.join(global.codecept_dir, modulePath)
|
|
674
|
+
}
|
|
675
|
+
try {
|
|
676
|
+
// Use dynamic import for both ESM and CJS modules
|
|
677
|
+
let importPath = modulePath
|
|
678
|
+
let tempJsFile = null
|
|
679
|
+
|
|
680
|
+
if (typeof importPath === 'string') {
|
|
681
|
+
const ext = path.extname(importPath)
|
|
682
|
+
|
|
683
|
+
// Handle TypeScript files
|
|
684
|
+
if (ext === '.ts') {
|
|
685
|
+
try {
|
|
686
|
+
const { transpile } = await import('typescript')
|
|
687
|
+
|
|
688
|
+
// Recursively transpile the file and its dependencies
|
|
689
|
+
const transpileTS = (filePath) => {
|
|
690
|
+
const tsContent = fs.readFileSync(filePath, 'utf8')
|
|
691
|
+
|
|
692
|
+
// Transpile TypeScript to JavaScript with ES module output
|
|
693
|
+
let jsContent = transpile(tsContent, {
|
|
694
|
+
module: 99, // ModuleKind.ESNext
|
|
695
|
+
target: 99, // ScriptTarget.ESNext
|
|
696
|
+
esModuleInterop: true,
|
|
697
|
+
allowSyntheticDefaultImports: true,
|
|
698
|
+
})
|
|
699
|
+
|
|
700
|
+
// Check if the code uses __dirname or __filename (CommonJS globals)
|
|
701
|
+
const usesCommonJSGlobals = /__dirname|__filename/.test(jsContent)
|
|
702
|
+
|
|
703
|
+
if (usesCommonJSGlobals) {
|
|
704
|
+
// Inject ESM equivalents at the top of the file
|
|
705
|
+
const esmGlobals = `import { fileURLToPath as __fileURLToPath } from 'url';
|
|
706
|
+
import { dirname as __dirname_fn } from 'path';
|
|
707
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
708
|
+
const __dirname = __dirname_fn(__filename);
|
|
709
|
+
|
|
710
|
+
`
|
|
711
|
+
jsContent = esmGlobals + jsContent
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
return jsContent
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// Create a map to track transpiled files
|
|
718
|
+
const transpiledFiles = new Map()
|
|
719
|
+
const baseDir = path.dirname(importPath)
|
|
720
|
+
|
|
721
|
+
// Transpile main file
|
|
722
|
+
let jsContent = transpileTS(importPath)
|
|
723
|
+
|
|
724
|
+
// Find and transpile all relative TypeScript imports
|
|
725
|
+
// Match: import ... from './file' or '../file' or './file.ts'
|
|
726
|
+
const importRegex = /from\s+['"](\..+?)(?:\.ts)?['"]/g
|
|
727
|
+
let match
|
|
728
|
+
const imports = []
|
|
729
|
+
|
|
730
|
+
while ((match = importRegex.exec(jsContent)) !== null) {
|
|
731
|
+
imports.push(match[1])
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// Transpile each imported TypeScript file
|
|
735
|
+
for (const relativeImport of imports) {
|
|
736
|
+
let importedPath = path.resolve(baseDir, relativeImport)
|
|
737
|
+
|
|
738
|
+
// Handle .js extensions that might actually be .ts files
|
|
739
|
+
if (importedPath.endsWith('.js')) {
|
|
740
|
+
const tsVersion = importedPath.replace(/\.js$/, '.ts')
|
|
741
|
+
if (fs.existsSync(tsVersion)) {
|
|
742
|
+
importedPath = tsVersion
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// Try adding .ts extension if file doesn't exist and no extension provided
|
|
747
|
+
if (!path.extname(importedPath)) {
|
|
748
|
+
if (fs.existsSync(importedPath + '.ts')) {
|
|
749
|
+
importedPath = importedPath + '.ts'
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// If it's a TypeScript file, transpile it
|
|
754
|
+
if (importedPath.endsWith('.ts') && fs.existsSync(importedPath)) {
|
|
755
|
+
const transpiledImportContent = transpileTS(importedPath)
|
|
756
|
+
const tempImportFile = importedPath.replace(/\.ts$/, '.temp.mjs')
|
|
757
|
+
fs.writeFileSync(tempImportFile, transpiledImportContent)
|
|
758
|
+
transpiledFiles.set(importedPath, tempImportFile)
|
|
759
|
+
debug(`Transpiled dependency: ${importedPath} -> ${tempImportFile}`)
|
|
760
|
+
}
|
|
384
761
|
}
|
|
385
|
-
|
|
386
|
-
|
|
762
|
+
|
|
763
|
+
// Replace imports in the main file to point to temp .mjs files
|
|
764
|
+
jsContent = jsContent.replace(
|
|
765
|
+
/from\s+['"](\..+?)(?:\.ts)?['"]/g,
|
|
766
|
+
(match, importPath) => {
|
|
767
|
+
let resolvedPath = path.resolve(baseDir, importPath)
|
|
768
|
+
|
|
769
|
+
// Handle .js extension that might be .ts
|
|
770
|
+
if (resolvedPath.endsWith('.js')) {
|
|
771
|
+
const tsVersion = resolvedPath.replace(/\.js$/, '.ts')
|
|
772
|
+
if (transpiledFiles.has(tsVersion)) {
|
|
773
|
+
const tempFile = transpiledFiles.get(tsVersion)
|
|
774
|
+
const relPath = path.relative(baseDir, tempFile).replace(/\\/g, '/')
|
|
775
|
+
return `from './${relPath}'`
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// Try with .ts extension
|
|
780
|
+
const tsPath = resolvedPath.endsWith('.ts') ? resolvedPath : resolvedPath + '.ts'
|
|
781
|
+
|
|
782
|
+
// If we transpiled this file, use the temp file
|
|
783
|
+
if (transpiledFiles.has(tsPath)) {
|
|
784
|
+
const tempFile = transpiledFiles.get(tsPath)
|
|
785
|
+
// Get relative path from main temp file to this temp file
|
|
786
|
+
const relPath = path.relative(baseDir, tempFile).replace(/\\/g, '/')
|
|
787
|
+
return `from './${relPath}'`
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// Otherwise, keep the import as-is
|
|
791
|
+
return match
|
|
792
|
+
}
|
|
793
|
+
)
|
|
794
|
+
|
|
795
|
+
// Create a temporary JS file with .mjs extension for the main file
|
|
796
|
+
tempJsFile = importPath.replace(/\.ts$/, '.temp.mjs')
|
|
797
|
+
fs.writeFileSync(tempJsFile, jsContent)
|
|
798
|
+
|
|
799
|
+
// Store all temp files for cleanup
|
|
800
|
+
const allTempFiles = [tempJsFile, ...Array.from(transpiledFiles.values())]
|
|
801
|
+
|
|
802
|
+
// Attach cleanup handler
|
|
803
|
+
importPath = tempJsFile
|
|
804
|
+
// Store temp files list in a way that cleanup can access them
|
|
805
|
+
tempJsFile = allTempFiles
|
|
806
|
+
|
|
807
|
+
} catch (tsError) {
|
|
808
|
+
throw new Error(`Failed to load TypeScript file ${importPath}: ${tsError.message}. Make sure 'typescript' package is installed.`)
|
|
809
|
+
}
|
|
810
|
+
} else if (!ext) {
|
|
811
|
+
// Append .js if no extension provided (ESM resolution requires it)
|
|
812
|
+
importPath = `${importPath}.js`
|
|
813
|
+
}
|
|
387
814
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
815
|
+
|
|
816
|
+
let obj
|
|
817
|
+
try {
|
|
818
|
+
obj = await import(importPath)
|
|
819
|
+
} catch (importError) {
|
|
820
|
+
// Clean up temp files if created before rethrowing
|
|
821
|
+
if (tempJsFile) {
|
|
822
|
+
const filesToClean = Array.isArray(tempJsFile) ? tempJsFile : [tempJsFile]
|
|
823
|
+
for (const file of filesToClean) {
|
|
824
|
+
try {
|
|
825
|
+
if (fs.existsSync(file)) {
|
|
826
|
+
fs.unlinkSync(file)
|
|
827
|
+
}
|
|
828
|
+
} catch (cleanupError) {
|
|
829
|
+
// Ignore cleanup errors
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
throw importError
|
|
834
|
+
} finally {
|
|
835
|
+
// Clean up temp files if created
|
|
836
|
+
if (tempJsFile) {
|
|
837
|
+
const filesToClean = Array.isArray(tempJsFile) ? tempJsFile : [tempJsFile]
|
|
838
|
+
for (const file of filesToClean) {
|
|
839
|
+
try {
|
|
840
|
+
if (fs.existsSync(file)) {
|
|
841
|
+
fs.unlinkSync(file)
|
|
842
|
+
}
|
|
843
|
+
} catch (cleanupError) {
|
|
844
|
+
// Ignore cleanup errors
|
|
397
845
|
}
|
|
398
|
-
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// Handle ESM module wrapper
|
|
851
|
+
let actualObj = obj
|
|
852
|
+
if (obj && obj.__esModule && obj.default) {
|
|
853
|
+
actualObj = obj.default
|
|
854
|
+
} else if (obj.default) {
|
|
855
|
+
actualObj = obj.default
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// Handle different types of imports
|
|
859
|
+
if (typeof actualObj === 'function') {
|
|
860
|
+
// If it's a class (constructor function)
|
|
861
|
+
if (actualObj.prototype && actualObj.prototype.constructor === actualObj) {
|
|
862
|
+
const ClassName = actualObj
|
|
863
|
+
return new ClassName()
|
|
864
|
+
}
|
|
865
|
+
// If it's a regular function
|
|
866
|
+
return actualObj()
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
if (actualObj && Array.isArray(actualObj)) {
|
|
870
|
+
return actualObj
|
|
399
871
|
}
|
|
400
872
|
|
|
401
|
-
|
|
873
|
+
// If it's a plain object
|
|
874
|
+
if (actualObj && typeof actualObj === 'object') {
|
|
875
|
+
// Call _init if it exists (for page objects)
|
|
876
|
+
if (actualObj._init && typeof actualObj._init === 'function') {
|
|
877
|
+
actualObj._init()
|
|
878
|
+
}
|
|
879
|
+
return actualObj
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
throw new Error(`Support object "${supportObjectName}" should be an object, class, or function, but got ${typeof actualObj}`)
|
|
402
883
|
} catch (err) {
|
|
403
|
-
throw new Error(`Could not include object ${supportObjectName} from module '${modulePath}'\n${err.message}\n${err.stack}`)
|
|
884
|
+
throw new Error(`Could not include object ${supportObjectName} from module '${modulePath}'\n${err.message}\n${err.stack}`)
|
|
404
885
|
}
|
|
405
886
|
}
|
|
406
887
|
|
|
888
|
+
// Backwards compatibility function that throws an error for sync usage
|
|
889
|
+
function loadSupportObjectSync(modulePath, supportObjectName) {
|
|
890
|
+
throw new Error(`loadSupportObjectSync is deprecated. Support object "${supportObjectName || 'undefined'}" from '${modulePath}' must be loaded asynchronously. Use loadSupportObject() instead.`)
|
|
891
|
+
}
|
|
892
|
+
|
|
407
893
|
/**
|
|
408
894
|
* Method collect own property and prototype
|
|
409
895
|
*/
|
|
410
|
-
function getObjectMethods(obj) {
|
|
411
|
-
const methodsSet = new Set();
|
|
412
|
-
let protoObj = Reflect.getPrototypeOf(obj);
|
|
413
|
-
do {
|
|
414
|
-
if (protoObj?.constructor.prototype !== Object.prototype) {
|
|
415
|
-
const keys = Reflect.ownKeys(protoObj);
|
|
416
|
-
keys.forEach(k => methodsSet.add(k));
|
|
417
|
-
}
|
|
418
|
-
} while (protoObj = Reflect.getPrototypeOf(protoObj));
|
|
419
|
-
Reflect.ownKeys(obj).forEach(k => methodsSet.add(k));
|
|
420
|
-
const methods = {};
|
|
421
|
-
for (const key of methodsSet.keys()) {
|
|
422
|
-
if (key !== 'constructor') methods[key] = obj[key];
|
|
423
|
-
}
|
|
424
|
-
return methods;
|
|
425
|
-
}
|
|
426
896
|
|
|
427
|
-
function loadTranslation(locale, vocabularies) {
|
|
897
|
+
async function loadTranslation(locale, vocabularies) {
|
|
428
898
|
if (!locale) {
|
|
429
|
-
return Translation.createEmpty()
|
|
899
|
+
return Translation.createEmpty()
|
|
430
900
|
}
|
|
431
901
|
|
|
432
|
-
let translation
|
|
433
|
-
locale = locale.replace('-', '_');
|
|
902
|
+
let translation
|
|
434
903
|
|
|
435
904
|
// check if it is a known translation
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
//
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
905
|
+
const langs = await Translation.getLangs()
|
|
906
|
+
if (langs[locale]) {
|
|
907
|
+
translation = new Translation(langs[locale])
|
|
908
|
+
} else if (fileExists(path.join(global.codecept_dir, locale))) {
|
|
909
|
+
// get from a provided file instead
|
|
910
|
+
translation = Translation.createDefault()
|
|
911
|
+
translation.loadVocabulary(locale)
|
|
912
|
+
} else {
|
|
913
|
+
translation = Translation.createDefault()
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
vocabularies.forEach(v => translation.loadVocabulary(v))
|
|
917
|
+
|
|
918
|
+
return translation
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
function getHelperModuleName(helperName, config) {
|
|
922
|
+
// classical require
|
|
923
|
+
if (config[helperName].require) {
|
|
924
|
+
if (config[helperName].require.startsWith('.')) {
|
|
925
|
+
let helperPath = path.resolve(global.codecept_dir, config[helperName].require)
|
|
926
|
+
// Add .js extension if not present for ESM compatibility
|
|
927
|
+
if (!path.extname(helperPath)) {
|
|
928
|
+
helperPath += '.js'
|
|
929
|
+
}
|
|
930
|
+
return helperPath // custom helper
|
|
444
931
|
}
|
|
932
|
+
return config[helperName].require // plugin helper
|
|
445
933
|
}
|
|
446
934
|
|
|
447
|
-
|
|
935
|
+
// built-in helpers
|
|
936
|
+
if (helperName.startsWith('@codeceptjs/')) {
|
|
937
|
+
return helperName
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// built-in helpers
|
|
941
|
+
return `./helper/${helperName}`
|
|
942
|
+
}
|
|
943
|
+
function normalizeAndJoin(basePath, subPath) {
|
|
944
|
+
// Normalize and convert slashes to forward slashes in one step
|
|
945
|
+
const normalizedBase = path.posix.normalize(basePath.replace(/\\/g, '/'))
|
|
946
|
+
const normalizedSub = path.posix.normalize(subPath.replace(/\\/g, '/'))
|
|
947
|
+
|
|
948
|
+
// If subPath is absolute (starts with "/"), return it as the final path
|
|
949
|
+
if (normalizedSub.startsWith('/')) {
|
|
950
|
+
return normalizedSub
|
|
951
|
+
}
|
|
448
952
|
|
|
449
|
-
|
|
953
|
+
// Join the paths using POSIX-style
|
|
954
|
+
return path.posix.join(normalizedBase, normalizedSub)
|
|
450
955
|
}
|