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