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