codeceptjs 4.0.0-beta.4 → 4.0.0-beta.6.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 +89 -119
- package/bin/codecept.js +53 -54
- package/docs/webapi/clearCookie.mustache +1 -1
- package/lib/actor.js +70 -102
- package/lib/ai.js +131 -121
- package/lib/assert/empty.js +11 -12
- package/lib/assert/equal.js +16 -21
- package/lib/assert/error.js +2 -2
- package/lib/assert/include.js +11 -15
- package/lib/assert/throws.js +3 -5
- package/lib/assert/truth.js +10 -7
- package/lib/assert.js +18 -18
- package/lib/codecept.js +112 -101
- package/lib/colorUtils.js +48 -50
- package/lib/command/check.js +206 -0
- package/lib/command/configMigrate.js +13 -14
- package/lib/command/definitions.js +24 -36
- package/lib/command/dryRun.js +16 -16
- package/lib/command/generate.js +38 -39
- package/lib/command/gherkin/init.js +36 -38
- package/lib/command/gherkin/snippets.js +76 -74
- package/lib/command/gherkin/steps.js +21 -18
- package/lib/command/info.js +49 -15
- package/lib/command/init.js +41 -37
- package/lib/command/interactive.js +22 -13
- package/lib/command/list.js +11 -10
- package/lib/command/run-multiple/chunk.js +50 -47
- package/lib/command/run-multiple/collection.js +5 -5
- package/lib/command/run-multiple/run.js +3 -3
- package/lib/command/run-multiple.js +27 -47
- package/lib/command/run-rerun.js +6 -7
- package/lib/command/run-workers.js +15 -66
- package/lib/command/run.js +8 -8
- package/lib/command/utils.js +22 -21
- package/lib/command/workers/runTests.js +131 -241
- package/lib/config.js +111 -49
- package/lib/container.js +589 -244
- package/lib/data/context.js +16 -18
- package/lib/data/dataScenarioConfig.js +9 -9
- package/lib/data/dataTableArgument.js +7 -7
- package/lib/data/table.js +6 -12
- package/lib/effects.js +307 -0
- package/lib/els.js +160 -0
- package/lib/event.js +24 -19
- package/lib/globals.js +141 -0
- package/lib/heal.js +89 -81
- package/lib/helper/AI.js +3 -2
- package/lib/helper/ApiDataFactory.js +19 -19
- package/lib/helper/Appium.js +47 -51
- package/lib/helper/FileSystem.js +35 -15
- package/lib/helper/GraphQL.js +1 -1
- package/lib/helper/GraphQLDataFactory.js +4 -4
- package/lib/helper/JSONResponse.js +72 -45
- package/lib/helper/Mochawesome.js +14 -11
- package/lib/helper/Playwright.js +832 -434
- package/lib/helper/Puppeteer.js +393 -292
- package/lib/helper/REST.js +32 -27
- package/lib/helper/WebDriver.js +320 -219
- 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 +22 -22
- 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 +54 -0
- package/lib/listener/exit.js +10 -12
- package/lib/listener/{retry.js → globalRetry.js} +10 -10
- package/lib/listener/globalTimeout.js +166 -0
- package/lib/listener/helpers.js +43 -24
- package/lib/listener/mocha.js +4 -5
- package/lib/listener/result.js +11 -0
- package/lib/listener/steps.js +26 -23
- package/lib/listener/store.js +20 -0
- package/lib/locator.js +213 -192
- package/lib/mocha/asyncWrapper.js +264 -0
- package/lib/mocha/bdd.js +167 -0
- package/lib/mocha/cli.js +341 -0
- package/lib/mocha/factory.js +160 -0
- package/lib/{interfaces → mocha}/featureConfig.js +33 -13
- package/lib/{interfaces → mocha}/gherkin.js +75 -45
- 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 +32 -8
- package/lib/mocha/suite.js +89 -0
- package/lib/mocha/test.js +178 -0
- package/lib/mocha/types.d.ts +42 -0
- package/lib/mocha/ui.js +229 -0
- package/lib/output.js +86 -64
- package/lib/parser.js +44 -44
- package/lib/pause.js +160 -139
- package/lib/plugin/analyze.js +403 -0
- package/lib/plugin/{autoLogin.js → auth.js} +137 -43
- package/lib/plugin/autoDelay.js +19 -15
- package/lib/plugin/coverage.js +22 -27
- package/lib/plugin/customLocator.js +5 -5
- package/lib/plugin/customReporter.js +53 -0
- package/lib/plugin/heal.js +49 -17
- package/lib/plugin/pageInfo.js +140 -0
- package/lib/plugin/pauseOnFail.js +4 -3
- package/lib/plugin/retryFailedStep.js +60 -19
- package/lib/plugin/screenshotOnFail.js +80 -83
- package/lib/plugin/stepByStepReport.js +70 -31
- package/lib/plugin/stepTimeout.js +7 -13
- package/lib/plugin/subtitles.js +10 -9
- package/lib/recorder.js +167 -126
- package/lib/rerun.js +94 -50
- package/lib/result.js +161 -0
- package/lib/secret.js +18 -17
- 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 -332
- package/lib/steps.js +54 -0
- package/lib/store.js +37 -5
- package/lib/template/heal.js +2 -11
- package/lib/timeout.js +60 -0
- package/lib/transform.js +8 -8
- package/lib/translation.js +32 -18
- package/lib/utils.js +354 -250
- package/lib/workerStorage.js +16 -16
- package/lib/workers.js +366 -282
- package/package.json +107 -95
- package/translations/de-DE.js +5 -4
- package/translations/fr-FR.js +5 -4
- package/translations/index.js +23 -9
- package/translations/it-IT.js +5 -4
- package/translations/ja-JP.js +5 -4
- package/translations/nl-NL.js +76 -0
- package/translations/pl-PL.js +5 -4
- package/translations/pt-BR.js +5 -4
- package/translations/ru-RU.js +5 -4
- package/translations/utils.js +18 -0
- package/translations/zh-CN.js +5 -4
- package/translations/zh-TW.js +5 -4
- package/typings/index.d.ts +177 -186
- package/typings/promiseBasedTypes.d.ts +3573 -5941
- package/typings/types.d.ts +4042 -6370
- package/lib/cli.js +0 -256
- package/lib/helper/ExpectHelper.js +0 -391
- package/lib/helper/Nightmare.js +0 -1504
- package/lib/helper/Protractor.js +0 -1863
- package/lib/helper/SoftExpectHelper.js +0 -381
- package/lib/helper/TestCafe.js +0 -1414
- 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 -62
- package/lib/interfaces/bdd.js +0 -81
- package/lib/listener/artifacts.js +0 -19
- package/lib/listener/timeout.js +0 -109
- package/lib/mochaFactory.js +0 -113
- 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 -127
- package/lib/plugin/selenoid.js +0 -384
- package/lib/plugin/standardActingHelpers.js +0 -3
- package/lib/plugin/tryTo.js +0 -115
- package/lib/plugin/wdio.js +0 -249
- package/lib/scenario.js +0 -224
- package/lib/ui.js +0 -236
- package/lib/within.js +0 -70
package/lib/container.js
CHANGED
|
@@ -1,31 +1,48 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
import { globSync } from 'glob'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import debugModule from 'debug'
|
|
4
|
+
const debug = debugModule('codeceptjs:container')
|
|
5
|
+
import { MetaStep } from './step.js'
|
|
6
|
+
import { methodsOfObject, fileExists, isFunction, isAsyncFunction, installedLocally, deepMerge } from './utils.js'
|
|
7
|
+
import Translation from './translation.js'
|
|
8
|
+
import MochaFactory from './mocha/factory.js'
|
|
9
|
+
import recorder from './recorder.js'
|
|
10
|
+
import event from './event.js'
|
|
11
|
+
import WorkerStorage from './workerStorage.js'
|
|
12
|
+
import store from './store.js'
|
|
13
|
+
import Result from './result.js'
|
|
14
|
+
import ai from './ai.js'
|
|
15
|
+
import actorFactory from './actor.js'
|
|
16
|
+
|
|
17
|
+
let asyncHelperPromise
|
|
12
18
|
|
|
13
19
|
let container = {
|
|
14
20
|
helpers: {},
|
|
15
21
|
support: {},
|
|
22
|
+
proxySupport: {},
|
|
16
23
|
plugins: {},
|
|
24
|
+
actor: null,
|
|
17
25
|
/**
|
|
18
26
|
* @type {Mocha | {}}
|
|
19
27
|
* @ignore
|
|
20
28
|
*/
|
|
21
29
|
mocha: {},
|
|
22
30
|
translation: {},
|
|
23
|
-
}
|
|
31
|
+
/** @type {Result | null} */
|
|
32
|
+
result: null,
|
|
33
|
+
}
|
|
24
34
|
|
|
25
35
|
/**
|
|
26
36
|
* Dependency Injection Container
|
|
27
37
|
*/
|
|
28
38
|
class Container {
|
|
39
|
+
/**
|
|
40
|
+
* Get the standard acting helpers of CodeceptJS Container
|
|
41
|
+
*
|
|
42
|
+
*/
|
|
43
|
+
static get STANDARD_ACTING_HELPERS() {
|
|
44
|
+
return ['Playwright', 'WebDriver', 'Puppeteer', 'Appium']
|
|
45
|
+
}
|
|
29
46
|
/**
|
|
30
47
|
* Create container with all required helpers and support objects
|
|
31
48
|
*
|
|
@@ -33,22 +50,70 @@ class Container {
|
|
|
33
50
|
* @param {*} config
|
|
34
51
|
* @param {*} opts
|
|
35
52
|
*/
|
|
36
|
-
static create(config, opts) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
53
|
+
static async create(config, opts) {
|
|
54
|
+
debug('creating container')
|
|
55
|
+
asyncHelperPromise = Promise.resolve()
|
|
56
|
+
|
|
57
|
+
// dynamically create mocha instance
|
|
58
|
+
const mochaConfig = config.mocha || {}
|
|
59
|
+
if (config.grep && !opts.grep) mochaConfig.grep = config.grep
|
|
60
|
+
this.createMocha = () => (container.mocha = MochaFactory.create(mochaConfig, opts || {}))
|
|
61
|
+
this.createMocha()
|
|
62
|
+
|
|
63
|
+
// create support objects
|
|
64
|
+
container.support = {}
|
|
65
|
+
container.helpers = await createHelpers(config.helpers || {})
|
|
66
|
+
container.translation = await loadTranslation(config.translation || null, config.vocabularies || [])
|
|
67
|
+
container.proxySupport = createSupportObjects(config.include || {})
|
|
68
|
+
container.plugins = await createPlugins(config.plugins || {}, opts)
|
|
69
|
+
container.result = new Result()
|
|
70
|
+
|
|
71
|
+
// Preload includes (so proxies can expose real objects synchronously)
|
|
72
|
+
const includes = config.include || {}
|
|
73
|
+
|
|
74
|
+
// Ensure I is available for DI modules at import time
|
|
75
|
+
if (Object.prototype.hasOwnProperty.call(includes, 'I')) {
|
|
76
|
+
try {
|
|
77
|
+
const mod = includes.I
|
|
78
|
+
if (typeof mod === 'string') {
|
|
79
|
+
container.support.I = await loadSupportObject(mod, 'I')
|
|
80
|
+
} else if (typeof mod === 'function') {
|
|
81
|
+
container.support.I = await loadSupportObject(mod, 'I')
|
|
82
|
+
} else if (mod && typeof mod === 'object') {
|
|
83
|
+
container.support.I = mod
|
|
84
|
+
}
|
|
85
|
+
} catch (e) {
|
|
86
|
+
throw new Error(`Could not include object I: ${e.message}`)
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
// Create default actor if not provided via includes
|
|
90
|
+
createActor()
|
|
40
91
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
92
|
+
|
|
93
|
+
// Load remaining includes except I
|
|
94
|
+
for (const [name, mod] of Object.entries(includes)) {
|
|
95
|
+
if (name === 'I') continue
|
|
96
|
+
try {
|
|
97
|
+
if (typeof mod === 'string') {
|
|
98
|
+
container.support[name] = await loadSupportObject(mod, name)
|
|
99
|
+
} else if (typeof mod === 'function') {
|
|
100
|
+
// function or class
|
|
101
|
+
container.support[name] = await loadSupportObject(mod, name)
|
|
102
|
+
} else if (mod && typeof mod === 'object') {
|
|
103
|
+
container.support[name] = mod
|
|
104
|
+
}
|
|
105
|
+
} catch (e) {
|
|
106
|
+
throw new Error(`Could not include object ${name}: ${e.message}`)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (opts && opts.ai) ai.enable(config.ai) // enable AI Assistant
|
|
111
|
+
if (config.gherkin) await loadGherkinStepsAsync(config.gherkin.steps || [])
|
|
112
|
+
if (opts && typeof opts.timeouts === 'boolean') store.timeouts = opts.timeouts
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
static actor() {
|
|
116
|
+
return container.support.I
|
|
52
117
|
}
|
|
53
118
|
|
|
54
119
|
/**
|
|
@@ -60,9 +125,9 @@ class Container {
|
|
|
60
125
|
*/
|
|
61
126
|
static plugins(name) {
|
|
62
127
|
if (!name) {
|
|
63
|
-
return container.plugins
|
|
128
|
+
return container.plugins
|
|
64
129
|
}
|
|
65
|
-
return container.plugins[name]
|
|
130
|
+
return container.plugins[name]
|
|
66
131
|
}
|
|
67
132
|
|
|
68
133
|
/**
|
|
@@ -74,9 +139,10 @@ class Container {
|
|
|
74
139
|
*/
|
|
75
140
|
static support(name) {
|
|
76
141
|
if (!name) {
|
|
77
|
-
return container.
|
|
142
|
+
return container.proxySupport
|
|
78
143
|
}
|
|
79
|
-
return
|
|
144
|
+
// Always return the proxy to ensure MetaStep creation works
|
|
145
|
+
return container.proxySupport[name]
|
|
80
146
|
}
|
|
81
147
|
|
|
82
148
|
/**
|
|
@@ -88,9 +154,9 @@ class Container {
|
|
|
88
154
|
*/
|
|
89
155
|
static helpers(name) {
|
|
90
156
|
if (!name) {
|
|
91
|
-
return container.helpers
|
|
157
|
+
return container.helpers
|
|
92
158
|
}
|
|
93
|
-
return container.helpers[name]
|
|
159
|
+
return container.helpers[name]
|
|
94
160
|
}
|
|
95
161
|
|
|
96
162
|
/**
|
|
@@ -99,7 +165,7 @@ class Container {
|
|
|
99
165
|
* @api
|
|
100
166
|
*/
|
|
101
167
|
static translation() {
|
|
102
|
-
return container.translation
|
|
168
|
+
return container.translation
|
|
103
169
|
}
|
|
104
170
|
|
|
105
171
|
/**
|
|
@@ -109,7 +175,19 @@ class Container {
|
|
|
109
175
|
* @returns { * }
|
|
110
176
|
*/
|
|
111
177
|
static mocha() {
|
|
112
|
-
return container.mocha
|
|
178
|
+
return container.mocha
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Get result
|
|
183
|
+
*
|
|
184
|
+
* @returns {Result}
|
|
185
|
+
*/
|
|
186
|
+
static result() {
|
|
187
|
+
if (!container.result) {
|
|
188
|
+
container.result = new Result()
|
|
189
|
+
}
|
|
190
|
+
return container.result
|
|
113
191
|
}
|
|
114
192
|
|
|
115
193
|
/**
|
|
@@ -119,8 +197,15 @@ class Container {
|
|
|
119
197
|
* @param {Object<string, *>} newContainer
|
|
120
198
|
*/
|
|
121
199
|
static append(newContainer) {
|
|
122
|
-
|
|
123
|
-
|
|
200
|
+
container = deepMerge(container, newContainer)
|
|
201
|
+
|
|
202
|
+
// If new support objects are added, update the proxy support
|
|
203
|
+
if (newContainer.support) {
|
|
204
|
+
const newProxySupport = createSupportObjects(newContainer.support)
|
|
205
|
+
container.proxySupport = { ...container.proxySupport, ...newProxySupport }
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
debug('appended', JSON.stringify(newContainer).slice(0, 300))
|
|
124
209
|
}
|
|
125
210
|
|
|
126
211
|
/**
|
|
@@ -130,11 +215,25 @@ class Container {
|
|
|
130
215
|
* @param {Object<string, *>} newSupport
|
|
131
216
|
* @param {Object<string, *>} newPlugins
|
|
132
217
|
*/
|
|
133
|
-
static clear(newHelpers, newSupport, newPlugins) {
|
|
134
|
-
container.helpers = newHelpers
|
|
135
|
-
container.
|
|
136
|
-
container.
|
|
137
|
-
container.
|
|
218
|
+
static async clear(newHelpers = {}, newSupport = {}, newPlugins = {}) {
|
|
219
|
+
container.helpers = newHelpers
|
|
220
|
+
container.translation = await loadTranslation()
|
|
221
|
+
container.proxySupport = createSupportObjects(newSupport)
|
|
222
|
+
container.plugins = newPlugins
|
|
223
|
+
asyncHelperPromise = Promise.resolve()
|
|
224
|
+
store.actor = null
|
|
225
|
+
debug('container cleared')
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* @param {Function|null} fn
|
|
230
|
+
* @returns {Promise<void>}
|
|
231
|
+
*/
|
|
232
|
+
static async started(fn = null) {
|
|
233
|
+
if (fn) {
|
|
234
|
+
asyncHelperPromise = asyncHelperPromise.then(fn)
|
|
235
|
+
}
|
|
236
|
+
return asyncHelperPromise
|
|
138
237
|
}
|
|
139
238
|
|
|
140
239
|
/**
|
|
@@ -144,299 +243,545 @@ class Container {
|
|
|
144
243
|
* @param {Object} options - set {local: true} to not share among workers
|
|
145
244
|
*/
|
|
146
245
|
static share(data, options = {}) {
|
|
147
|
-
Container.append({ support: data })
|
|
246
|
+
Container.append({ support: data })
|
|
148
247
|
if (!options.local) {
|
|
149
|
-
WorkerStorage.share(data)
|
|
248
|
+
WorkerStorage.share(data)
|
|
150
249
|
}
|
|
151
250
|
}
|
|
251
|
+
|
|
252
|
+
static createMocha(config = {}, opts = {}) {
|
|
253
|
+
const mochaConfig = config?.mocha || {}
|
|
254
|
+
if (config?.grep && !opts?.grep) {
|
|
255
|
+
mochaConfig.grep = config.grep
|
|
256
|
+
}
|
|
257
|
+
container.mocha = MochaFactory.create(mochaConfig, opts || {})
|
|
258
|
+
}
|
|
152
259
|
}
|
|
153
260
|
|
|
154
|
-
|
|
261
|
+
export default Container
|
|
155
262
|
|
|
156
|
-
function createHelpers(config) {
|
|
157
|
-
const helpers = {}
|
|
158
|
-
let
|
|
159
|
-
for (const helperName in config) {
|
|
263
|
+
async function createHelpers(config) {
|
|
264
|
+
const helpers = {}
|
|
265
|
+
for (let helperName in config) {
|
|
160
266
|
try {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
167
|
-
} else {
|
|
168
|
-
moduleName = `./helper/${helperName}`; // built-in helper
|
|
267
|
+
let HelperClass
|
|
268
|
+
|
|
269
|
+
// Check if helper class was stored in config during ESM import processing
|
|
270
|
+
if (config[helperName]._helperClass) {
|
|
271
|
+
HelperClass = config[helperName]._helperClass
|
|
272
|
+
debug(`helper ${helperName} loaded from ESM import`)
|
|
169
273
|
}
|
|
170
274
|
|
|
171
|
-
//
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
HelperClass = require(moduleName);
|
|
176
|
-
} else {
|
|
177
|
-
// check if the new syntax export default HelperName is used and loads the Helper, otherwise loads the module that used old syntax export = HelperName.
|
|
178
|
-
HelperClass = require(moduleName).default || require(moduleName);
|
|
275
|
+
// ESM import (legacy check)
|
|
276
|
+
if (!HelperClass && typeof helperName === 'function' && helperName.prototype) {
|
|
277
|
+
HelperClass = helperName
|
|
278
|
+
helperName = HelperClass.constructor.name
|
|
179
279
|
}
|
|
180
280
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
281
|
+
// classical require - may be async for ESM modules
|
|
282
|
+
if (!HelperClass) {
|
|
283
|
+
const helperResult = requireHelperFromModule(helperName, config)
|
|
284
|
+
if (helperResult instanceof Promise) {
|
|
285
|
+
// Handle async ESM loading
|
|
286
|
+
helpers[helperName] = {}
|
|
287
|
+
asyncHelperPromise = asyncHelperPromise
|
|
288
|
+
.then(() => helperResult)
|
|
289
|
+
.then(async ResolvedHelperClass => {
|
|
290
|
+
debug(`helper ${helperName} resolved type: ${typeof ResolvedHelperClass}`, ResolvedHelperClass)
|
|
291
|
+
|
|
292
|
+
// Extract default export from ESM module wrapper if needed
|
|
293
|
+
if (ResolvedHelperClass && ResolvedHelperClass.__esModule && ResolvedHelperClass.default) {
|
|
294
|
+
ResolvedHelperClass = ResolvedHelperClass.default
|
|
295
|
+
debug(`extracted default export for ${helperName}, new type: ${typeof ResolvedHelperClass}`)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (typeof ResolvedHelperClass !== 'function') {
|
|
299
|
+
throw new Error(`Helper '${helperName}' is not a class. Got: ${typeof ResolvedHelperClass}`)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
checkHelperRequirements(ResolvedHelperClass)
|
|
303
|
+
helpers[helperName] = new ResolvedHelperClass(config[helperName])
|
|
304
|
+
if (helpers[helperName]._init) await helpers[helperName]._init()
|
|
305
|
+
debug(`helper ${helperName} async initialized`)
|
|
306
|
+
})
|
|
307
|
+
continue
|
|
308
|
+
} else {
|
|
309
|
+
HelperClass = helperResult
|
|
192
310
|
}
|
|
193
311
|
}
|
|
194
|
-
|
|
312
|
+
|
|
313
|
+
// handle async CJS modules that use dynamic import
|
|
314
|
+
if (isAsyncFunction(HelperClass)) {
|
|
315
|
+
helpers[helperName] = {}
|
|
316
|
+
|
|
317
|
+
asyncHelperPromise = asyncHelperPromise
|
|
318
|
+
.then(() => HelperClass())
|
|
319
|
+
.then(ResolvedHelperClass => {
|
|
320
|
+
// Check if ResolvedHelperClass is a constructor function
|
|
321
|
+
if (typeof ResolvedHelperClass?.constructor !== 'function') {
|
|
322
|
+
throw new Error(`Helper class from module '${helperName}' is not a class. Use CJS async module syntax.`)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
debug(`helper ${helperName} async initialized`)
|
|
326
|
+
|
|
327
|
+
helpers[helperName] = new ResolvedHelperClass(config[helperName])
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
continue
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
checkHelperRequirements(HelperClass)
|
|
334
|
+
|
|
335
|
+
helpers[helperName] = new HelperClass(config[helperName])
|
|
336
|
+
debug(`helper ${helperName} initialized`)
|
|
195
337
|
} catch (err) {
|
|
196
|
-
throw new Error(`Could not load helper ${helperName}
|
|
338
|
+
throw new Error(`Could not load helper ${helperName} (${err.message})`)
|
|
197
339
|
}
|
|
198
340
|
}
|
|
199
341
|
|
|
200
342
|
for (const name in helpers) {
|
|
201
|
-
if (helpers[name]._init) helpers[name]._init()
|
|
343
|
+
if (helpers[name]._init) await helpers[name]._init()
|
|
202
344
|
}
|
|
203
|
-
return helpers
|
|
345
|
+
return helpers
|
|
204
346
|
}
|
|
205
347
|
|
|
206
|
-
function
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
348
|
+
function checkHelperRequirements(HelperClass) {
|
|
349
|
+
if (HelperClass._checkRequirements) {
|
|
350
|
+
const requirements = HelperClass._checkRequirements()
|
|
351
|
+
if (requirements) {
|
|
352
|
+
let install
|
|
353
|
+
if (installedLocally()) {
|
|
354
|
+
install = `npm install --save-dev ${requirements.join(' ')}`
|
|
355
|
+
} else {
|
|
356
|
+
console.log('WARNING: CodeceptJS is not installed locally. It is recommended to switch to local installation')
|
|
357
|
+
install = `[sudo] npm install -g ${requirements.join(' ')}`
|
|
358
|
+
}
|
|
359
|
+
throw new Error(`Required modules are not installed.\n\nRUN: ${install}`)
|
|
218
360
|
}
|
|
219
361
|
}
|
|
362
|
+
}
|
|
220
363
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
let newObj = getSupportObject(config, name);
|
|
364
|
+
async function requireHelperFromModule(helperName, config, HelperClass) {
|
|
365
|
+
const moduleName = getHelperModuleName(helperName, config)
|
|
366
|
+
if (moduleName.startsWith('./helper/')) {
|
|
225
367
|
try {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
368
|
+
// For built-in helpers, use direct relative import with .js extension
|
|
369
|
+
const helperPath = `${moduleName}.js`
|
|
370
|
+
const mod = await import(helperPath)
|
|
371
|
+
HelperClass = mod.default || mod
|
|
372
|
+
} catch (err) {
|
|
373
|
+
throw err
|
|
374
|
+
}
|
|
375
|
+
} else {
|
|
376
|
+
// check if the new syntax export default HelperName is used and loads the Helper, otherwise loads the module that used old syntax export = HelperName.
|
|
377
|
+
try {
|
|
378
|
+
// Try dynamic import for both CommonJS and ESM modules
|
|
379
|
+
const mod = await import(moduleName)
|
|
380
|
+
if (!mod && !mod.default) {
|
|
381
|
+
throw new Error(`Helper module '${moduleName}' was not found. Make sure you have installed the package correctly.`)
|
|
230
382
|
}
|
|
383
|
+
HelperClass = mod.default || mod
|
|
231
384
|
} catch (err) {
|
|
232
|
-
|
|
385
|
+
if (err.code === 'ERR_REQUIRE_ESM' || (err.message && err.message.includes('ES module'))) {
|
|
386
|
+
// This is an ESM module, use dynamic import
|
|
387
|
+
try {
|
|
388
|
+
const pathModule = await import('path')
|
|
389
|
+
const absolutePath = pathModule.default.resolve(moduleName)
|
|
390
|
+
const mod = await import(absolutePath)
|
|
391
|
+
HelperClass = mod.default || mod
|
|
392
|
+
debug(`helper ${helperName} loaded via ESM import`)
|
|
393
|
+
} catch (importErr) {
|
|
394
|
+
throw new Error(`Helper module '${moduleName}' could not be imported as ESM: ${importErr.message}`)
|
|
395
|
+
}
|
|
396
|
+
} else if (err.code === 'MODULE_NOT_FOUND') {
|
|
397
|
+
throw new Error(`Helper module '${moduleName}' was not found. Make sure you have installed the package correctly.`)
|
|
398
|
+
} else {
|
|
399
|
+
throw err
|
|
400
|
+
}
|
|
233
401
|
}
|
|
234
|
-
return newObj;
|
|
235
402
|
}
|
|
403
|
+
return HelperClass
|
|
404
|
+
}
|
|
236
405
|
|
|
406
|
+
function createSupportObjects(config) {
|
|
237
407
|
const asyncWrapper = function (f) {
|
|
238
408
|
return function () {
|
|
239
|
-
return f.apply(this, arguments).catch(
|
|
240
|
-
recorder.saveFirstAsyncError(e)
|
|
241
|
-
throw e
|
|
242
|
-
})
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
Object.keys(objects).forEach((object) => {
|
|
247
|
-
const currentObject = objects[object];
|
|
248
|
-
Object.keys(currentObject).forEach((method) => {
|
|
249
|
-
const currentMethod = currentObject[method];
|
|
250
|
-
if (currentMethod && currentMethod[Symbol.toStringTag] === 'AsyncFunction') {
|
|
251
|
-
objects[object][method] = asyncWrapper(currentMethod);
|
|
252
|
-
}
|
|
253
|
-
});
|
|
254
|
-
});
|
|
409
|
+
return f.apply(this, arguments).catch(e => {
|
|
410
|
+
recorder.saveFirstAsyncError(e)
|
|
411
|
+
throw e
|
|
412
|
+
})
|
|
413
|
+
}
|
|
414
|
+
}
|
|
255
415
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
416
|
+
function lazyLoad(name) {
|
|
417
|
+
return new Proxy(
|
|
418
|
+
{},
|
|
419
|
+
{
|
|
420
|
+
get(target, prop) {
|
|
421
|
+
// behavr like array or
|
|
422
|
+
if (prop === 'length') return Object.keys(config).length
|
|
423
|
+
if (prop === Symbol.iterator) {
|
|
424
|
+
return function* () {
|
|
425
|
+
for (let i = 0; i < Object.keys(config).length; i++) {
|
|
426
|
+
yield target[i]
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// load actual name from vocabulary
|
|
432
|
+
if (container.translation && container.translation.I && name === 'I') {
|
|
433
|
+
// Use translated name for I
|
|
434
|
+
const actualName = container.translation.I
|
|
435
|
+
if (actualName !== 'I') {
|
|
436
|
+
name = actualName
|
|
437
|
+
}
|
|
438
|
+
}
|
|
270
439
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
440
|
+
if (name === 'I') {
|
|
441
|
+
if (!container.support.I) {
|
|
442
|
+
// Actor will be created during container.create()
|
|
443
|
+
return undefined
|
|
444
|
+
}
|
|
445
|
+
methodsOfObject(container.support.I)
|
|
446
|
+
return container.support.I[prop]
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (!container.support[name] && typeof config[name] === 'object') {
|
|
450
|
+
container.support[name] = config[name]
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (!container.support[name]) {
|
|
454
|
+
// Cannot load object synchronously in proxy getter
|
|
455
|
+
// Return undefined and log warning - object should be pre-loaded during container creation
|
|
456
|
+
debug(`Support object ${name} not pre-loaded, returning undefined`)
|
|
457
|
+
return undefined
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const currentObject = container.support[name]
|
|
461
|
+
let currentValue = currentObject[prop]
|
|
462
|
+
|
|
463
|
+
if (isFunction(currentValue) || isAsyncFunction(currentValue)) {
|
|
464
|
+
const ms = new MetaStep(name, prop)
|
|
465
|
+
ms.setContext(currentObject)
|
|
466
|
+
if (isAsyncFunction(currentValue)) currentValue = asyncWrapper(currentValue)
|
|
467
|
+
debug(`metastep is created for ${name}.${prop.toString()}()`)
|
|
468
|
+
return ms.run.bind(ms, currentValue)
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return currentValue
|
|
472
|
+
},
|
|
473
|
+
has(target, prop) {
|
|
474
|
+
if (!container.support[name]) {
|
|
475
|
+
// Note: This is sync, so we can't use async loadSupportObject here
|
|
476
|
+
// The object will be loaded lazily on first property access
|
|
477
|
+
return false
|
|
478
|
+
}
|
|
479
|
+
return prop in container.support[name]
|
|
480
|
+
},
|
|
481
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
482
|
+
if (!container.support[name]) {
|
|
483
|
+
// Object will be loaded on property access
|
|
484
|
+
return {
|
|
485
|
+
enumerable: true,
|
|
486
|
+
configurable: true,
|
|
487
|
+
value: undefined,
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return {
|
|
491
|
+
enumerable: true,
|
|
492
|
+
configurable: true,
|
|
493
|
+
value: container.support[name][prop],
|
|
494
|
+
}
|
|
495
|
+
},
|
|
496
|
+
ownKeys() {
|
|
497
|
+
if (!container.support[name]) {
|
|
498
|
+
return []
|
|
499
|
+
}
|
|
500
|
+
return Reflect.ownKeys(container.support[name])
|
|
501
|
+
},
|
|
502
|
+
},
|
|
503
|
+
)
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const keys = Reflect.ownKeys(config)
|
|
507
|
+
return new Proxy(
|
|
508
|
+
{},
|
|
509
|
+
{
|
|
510
|
+
has(target, key) {
|
|
511
|
+
return keys.includes(key)
|
|
512
|
+
},
|
|
513
|
+
ownKeys() {
|
|
514
|
+
return keys
|
|
515
|
+
},
|
|
516
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
517
|
+
return {
|
|
518
|
+
enumerable: true,
|
|
519
|
+
configurable: true,
|
|
520
|
+
value: target[prop],
|
|
276
521
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
522
|
+
},
|
|
523
|
+
get(target, key) {
|
|
524
|
+
if (typeof key === 'symbol') {
|
|
525
|
+
// safely ignore symbol-based meta properties used by tooling
|
|
526
|
+
return undefined
|
|
527
|
+
}
|
|
528
|
+
// Allow special I even if not declared in includes
|
|
529
|
+
if (key === 'I') {
|
|
530
|
+
return lazyLoad('I')
|
|
531
|
+
}
|
|
532
|
+
if (!keys.includes(key)) {
|
|
533
|
+
throw new Error(`Support object "${String(key)}" is not defined`)
|
|
534
|
+
}
|
|
535
|
+
return lazyLoad(key)
|
|
536
|
+
},
|
|
280
537
|
},
|
|
281
|
-
|
|
538
|
+
)
|
|
282
539
|
}
|
|
283
540
|
|
|
284
|
-
function
|
|
285
|
-
|
|
541
|
+
function createActor(actorPath) {
|
|
542
|
+
if (container.support.I) return container.support.I
|
|
286
543
|
|
|
287
|
-
|
|
544
|
+
// Default actor
|
|
545
|
+
container.support.I = actorFactory({}, Container)
|
|
546
|
+
|
|
547
|
+
return container.support.I
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
async function loadPluginAsync(modulePath, config) {
|
|
551
|
+
let pluginMod
|
|
552
|
+
try {
|
|
553
|
+
// Try dynamic import first (works for both ESM and CJS)
|
|
554
|
+
pluginMod = await import(modulePath)
|
|
555
|
+
} catch (err) {
|
|
556
|
+
throw new Error(`Could not load plugin from '${modulePath}': ${err.message}`)
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const pluginFactory = pluginMod.default || pluginMod
|
|
560
|
+
if (typeof pluginFactory !== 'function') {
|
|
561
|
+
throw new Error(`Plugin '${modulePath}' is not a function. Expected a plugin factory function.`)
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
return pluginFactory(config)
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
async function loadPluginFallback(modulePath, config) {
|
|
568
|
+
// This function is kept for backwards compatibility but now uses dynamic import
|
|
569
|
+
return await loadPluginAsync(modulePath, config)
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
async function createPlugins(config, options = {}) {
|
|
573
|
+
const plugins = {}
|
|
574
|
+
|
|
575
|
+
const enabledPluginsByOptions = (options.plugins || '').split(',')
|
|
288
576
|
for (const pluginName in config) {
|
|
289
|
-
if (!config[pluginName]) config[pluginName] = {}
|
|
290
|
-
if (!config[pluginName].enabled &&
|
|
291
|
-
continue
|
|
577
|
+
if (!config[pluginName]) config[pluginName] = {}
|
|
578
|
+
if (!config[pluginName].enabled && enabledPluginsByOptions.indexOf(pluginName) < 0) {
|
|
579
|
+
continue // plugin is disabled
|
|
292
580
|
}
|
|
293
|
-
let module
|
|
581
|
+
let module
|
|
294
582
|
try {
|
|
295
583
|
if (config[pluginName].require) {
|
|
296
|
-
module = config[pluginName].require
|
|
297
|
-
if (module.startsWith('.')) {
|
|
298
|
-
|
|
584
|
+
module = config[pluginName].require
|
|
585
|
+
if (module.startsWith('.')) {
|
|
586
|
+
// local
|
|
587
|
+
module = path.resolve(global.codecept_dir, module) // custom plugin
|
|
299
588
|
}
|
|
300
589
|
} else {
|
|
301
|
-
module = `./plugin/${pluginName}
|
|
590
|
+
module = `./plugin/${pluginName}.js`
|
|
302
591
|
}
|
|
303
|
-
|
|
592
|
+
|
|
593
|
+
// Use async loading for all plugins (ESM and CJS)
|
|
594
|
+
plugins[pluginName] = await loadPluginAsync(module, config[pluginName])
|
|
595
|
+
debug(`plugin ${pluginName} loaded via async import`)
|
|
304
596
|
} catch (err) {
|
|
305
|
-
throw new Error(`Could not load plugin ${pluginName} from module '${module}':\n${err.message}\n${err.stack}`)
|
|
597
|
+
throw new Error(`Could not load plugin ${pluginName} from module '${module}':\n${err.message}\n${err.stack}`)
|
|
306
598
|
}
|
|
307
599
|
}
|
|
308
|
-
return plugins
|
|
600
|
+
return plugins
|
|
309
601
|
}
|
|
310
602
|
|
|
311
|
-
function
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
}
|
|
316
|
-
return module;
|
|
317
|
-
}
|
|
603
|
+
async function loadGherkinStepsAsync(paths) {
|
|
604
|
+
global.Before = fn => event.dispatcher.on(event.test.started, fn)
|
|
605
|
+
global.After = fn => event.dispatcher.on(event.test.finished, fn)
|
|
606
|
+
global.Fail = fn => event.dispatcher.on(event.test.failed, fn)
|
|
318
607
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
global.After = fn => event.dispatcher.on(event.test.finished, fn);
|
|
322
|
-
global.Fail = fn => event.dispatcher.on(event.test.failed, fn);
|
|
608
|
+
// Import BDD module to access step file tracking functions
|
|
609
|
+
const bddModule = await import('./mocha/bdd.js')
|
|
323
610
|
|
|
324
611
|
// If gherkin.steps is string, then this will iterate through that folder and send all step def js files to loadSupportObject
|
|
325
612
|
// If gherkin.steps is Array, it will go the old way
|
|
326
613
|
// This is done so that we need not enter all Step Definition files under config.gherkin.steps
|
|
327
614
|
if (Array.isArray(paths)) {
|
|
328
615
|
for (const path of paths) {
|
|
329
|
-
|
|
616
|
+
// Set context for step definition file location tracking
|
|
617
|
+
bddModule.setCurrentStepFile(path)
|
|
618
|
+
await loadSupportObject(path, `Step Definition from ${path}`)
|
|
619
|
+
bddModule.clearCurrentStepFile()
|
|
330
620
|
}
|
|
331
621
|
} else {
|
|
332
|
-
const folderPath = paths.startsWith('.') ?
|
|
622
|
+
const folderPath = paths.startsWith('.') ? normalizeAndJoin(global.codecept_dir, paths) : ''
|
|
333
623
|
if (folderPath !== '') {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
624
|
+
const files = globSync(folderPath)
|
|
625
|
+
for (const file of files) {
|
|
626
|
+
// Set context for step definition file location tracking
|
|
627
|
+
bddModule.setCurrentStepFile(file)
|
|
628
|
+
await loadSupportObject(file, `Step Definition from ${file}`)
|
|
629
|
+
bddModule.clearCurrentStepFile()
|
|
630
|
+
}
|
|
337
631
|
}
|
|
338
632
|
}
|
|
339
633
|
|
|
340
|
-
delete global.Before
|
|
341
|
-
delete global.After
|
|
342
|
-
delete global.Fail
|
|
634
|
+
delete global.Before
|
|
635
|
+
delete global.After
|
|
636
|
+
delete global.Fail
|
|
343
637
|
}
|
|
344
638
|
|
|
345
|
-
function
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
try {
|
|
350
|
-
const obj = require(modulePath);
|
|
639
|
+
function loadGherkinSteps(paths) {
|
|
640
|
+
global.Before = fn => event.dispatcher.on(event.test.started, fn)
|
|
641
|
+
global.After = fn => event.dispatcher.on(event.test.finished, fn)
|
|
642
|
+
global.Fail = fn => event.dispatcher.on(event.test.failed, fn)
|
|
351
643
|
|
|
352
|
-
|
|
353
|
-
|
|
644
|
+
// Gherkin step loading must be handled asynchronously
|
|
645
|
+
throw new Error('Gherkin step loading must be converted to async. Use loadGherkinStepsAsync() instead.')
|
|
354
646
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
fobj[key] = methods[key];
|
|
360
|
-
});
|
|
647
|
+
delete global.Before
|
|
648
|
+
delete global.After
|
|
649
|
+
delete global.Fail
|
|
650
|
+
}
|
|
361
651
|
|
|
362
|
-
|
|
652
|
+
async function loadSupportObject(modulePath, supportObjectName) {
|
|
653
|
+
if (!modulePath) {
|
|
654
|
+
throw new Error(`Support object "${supportObjectName}" is not defined`)
|
|
655
|
+
}
|
|
656
|
+
// If function/class provided directly
|
|
657
|
+
if (typeof modulePath === 'function') {
|
|
658
|
+
try {
|
|
659
|
+
// class constructor
|
|
660
|
+
if (modulePath.prototype && modulePath.prototype.constructor === modulePath) {
|
|
661
|
+
return new modulePath()
|
|
363
662
|
}
|
|
663
|
+
// plain function factory
|
|
664
|
+
return modulePath()
|
|
665
|
+
} catch (err) {
|
|
666
|
+
throw new Error(`Could not include object ${supportObjectName} from function: ${err.message}`)
|
|
364
667
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
ms.setContext(methods);
|
|
377
|
-
methods[key] = ms.run.bind(ms, currentMethod);
|
|
378
|
-
}
|
|
379
|
-
});
|
|
380
|
-
return methods;
|
|
668
|
+
}
|
|
669
|
+
if (typeof modulePath === 'string' && modulePath.charAt(0) === '.') {
|
|
670
|
+
modulePath = path.join(global.codecept_dir, modulePath)
|
|
671
|
+
}
|
|
672
|
+
try {
|
|
673
|
+
// Use dynamic import for both ESM and CJS modules
|
|
674
|
+
let importPath = modulePath
|
|
675
|
+
// Append .js if no extension provided (ESM resolution requires it)
|
|
676
|
+
if (typeof importPath === 'string') {
|
|
677
|
+
const ext = path.extname(importPath)
|
|
678
|
+
if (!ext) importPath = `${importPath}.js`
|
|
381
679
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
obj[key] = ms.run.bind(ms, currentMethod);
|
|
391
|
-
}
|
|
392
|
-
});
|
|
680
|
+
const obj = await import(importPath)
|
|
681
|
+
|
|
682
|
+
// Handle ESM module wrapper
|
|
683
|
+
let actualObj = obj
|
|
684
|
+
if (obj && obj.__esModule && obj.default) {
|
|
685
|
+
actualObj = obj.default
|
|
686
|
+
} else if (obj.default) {
|
|
687
|
+
actualObj = obj.default
|
|
393
688
|
}
|
|
394
689
|
|
|
395
|
-
|
|
690
|
+
// Handle different types of imports
|
|
691
|
+
if (typeof actualObj === 'function') {
|
|
692
|
+
// If it's a class (constructor function)
|
|
693
|
+
if (actualObj.prototype && actualObj.prototype.constructor === actualObj) {
|
|
694
|
+
const ClassName = actualObj
|
|
695
|
+
return new ClassName()
|
|
696
|
+
}
|
|
697
|
+
// If it's a regular function
|
|
698
|
+
return actualObj()
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
if (actualObj && Array.isArray(actualObj)) {
|
|
702
|
+
return actualObj
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// If it's a plain object
|
|
706
|
+
if (actualObj && typeof actualObj === 'object') {
|
|
707
|
+
// Call _init if it exists (for page objects)
|
|
708
|
+
if (actualObj._init && typeof actualObj._init === 'function') {
|
|
709
|
+
actualObj._init()
|
|
710
|
+
}
|
|
711
|
+
return actualObj
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
throw new Error(`Support object "${supportObjectName}" should be an object, class, or function, but got ${typeof actualObj}`)
|
|
396
715
|
} catch (err) {
|
|
397
|
-
throw new Error(`Could not include object ${supportObjectName} from module '${modulePath}'\n${err.message}\n${err.stack}`)
|
|
716
|
+
throw new Error(`Could not include object ${supportObjectName} from module '${modulePath}'\n${err.message}\n${err.stack}`)
|
|
398
717
|
}
|
|
399
718
|
}
|
|
400
719
|
|
|
720
|
+
// Backwards compatibility function that throws an error for sync usage
|
|
721
|
+
function loadSupportObjectSync(modulePath, supportObjectName) {
|
|
722
|
+
throw new Error(`loadSupportObjectSync is deprecated. Support object "${supportObjectName || 'undefined'}" from '${modulePath}' must be loaded asynchronously. Use loadSupportObject() instead.`)
|
|
723
|
+
}
|
|
724
|
+
|
|
401
725
|
/**
|
|
402
726
|
* Method collect own property and prototype
|
|
403
727
|
*/
|
|
404
|
-
function getObjectMethods(obj) {
|
|
405
|
-
const methodsSet = new Set();
|
|
406
|
-
let protoObj = Reflect.getPrototypeOf(obj);
|
|
407
|
-
do {
|
|
408
|
-
if (protoObj.constructor.prototype !== Object.prototype) {
|
|
409
|
-
const keys = Reflect.ownKeys(protoObj);
|
|
410
|
-
keys.forEach(k => methodsSet.add(k));
|
|
411
|
-
}
|
|
412
|
-
} while (protoObj = Reflect.getPrototypeOf(protoObj));
|
|
413
|
-
Reflect.ownKeys(obj).forEach(k => methodsSet.add(k));
|
|
414
|
-
const methods = {};
|
|
415
|
-
for (const key of methodsSet.keys()) {
|
|
416
|
-
if (key !== 'constructor') methods[key] = obj[key];
|
|
417
|
-
}
|
|
418
|
-
return methods;
|
|
419
|
-
}
|
|
420
728
|
|
|
421
|
-
function loadTranslation(locale, vocabularies) {
|
|
729
|
+
async function loadTranslation(locale, vocabularies) {
|
|
422
730
|
if (!locale) {
|
|
423
|
-
return Translation.createEmpty()
|
|
731
|
+
return Translation.createEmpty()
|
|
424
732
|
}
|
|
425
733
|
|
|
426
|
-
let translation
|
|
734
|
+
let translation
|
|
427
735
|
|
|
428
736
|
// check if it is a known translation
|
|
429
|
-
|
|
430
|
-
|
|
737
|
+
const langs = await Translation.getLangs()
|
|
738
|
+
if (langs[locale]) {
|
|
739
|
+
translation = new Translation(langs[locale])
|
|
431
740
|
} else if (fileExists(path.join(global.codecept_dir, locale))) {
|
|
432
741
|
// get from a provided file instead
|
|
433
|
-
translation = Translation.createDefault()
|
|
434
|
-
translation.loadVocabulary(locale)
|
|
742
|
+
translation = Translation.createDefault()
|
|
743
|
+
translation.loadVocabulary(locale)
|
|
435
744
|
} else {
|
|
436
|
-
translation = Translation.createDefault()
|
|
745
|
+
translation = Translation.createDefault()
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
vocabularies.forEach(v => translation.loadVocabulary(v))
|
|
749
|
+
|
|
750
|
+
return translation
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
function getHelperModuleName(helperName, config) {
|
|
754
|
+
// classical require
|
|
755
|
+
if (config[helperName].require) {
|
|
756
|
+
if (config[helperName].require.startsWith('.')) {
|
|
757
|
+
let helperPath = path.resolve(global.codecept_dir, config[helperName].require)
|
|
758
|
+
// Add .js extension if not present for ESM compatibility
|
|
759
|
+
if (!path.extname(helperPath)) {
|
|
760
|
+
helperPath += '.js'
|
|
761
|
+
}
|
|
762
|
+
return helperPath // custom helper
|
|
763
|
+
}
|
|
764
|
+
return config[helperName].require // plugin helper
|
|
437
765
|
}
|
|
438
766
|
|
|
439
|
-
|
|
767
|
+
// built-in helpers
|
|
768
|
+
if (helperName.startsWith('@codeceptjs/')) {
|
|
769
|
+
return helperName
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// built-in helpers
|
|
773
|
+
return `./helper/${helperName}`
|
|
774
|
+
}
|
|
775
|
+
function normalizeAndJoin(basePath, subPath) {
|
|
776
|
+
// Normalize and convert slashes to forward slashes in one step
|
|
777
|
+
const normalizedBase = path.posix.normalize(basePath.replace(/\\/g, '/'))
|
|
778
|
+
const normalizedSub = path.posix.normalize(subPath.replace(/\\/g, '/'))
|
|
779
|
+
|
|
780
|
+
// If subPath is absolute (starts with "/"), return it as the final path
|
|
781
|
+
if (normalizedSub.startsWith('/')) {
|
|
782
|
+
return normalizedSub
|
|
783
|
+
}
|
|
440
784
|
|
|
441
|
-
|
|
785
|
+
// Join the paths using POSIX-style
|
|
786
|
+
return path.posix.join(normalizedBase, normalizedSub)
|
|
442
787
|
}
|