codeceptjs 4.0.0-beta.4 → 4.0.0-beta.5
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 +134 -119
- package/bin/codecept.js +12 -2
- package/bin/test-server.js +53 -0
- package/docs/webapi/clearCookie.mustache +1 -1
- package/lib/actor.js +66 -102
- package/lib/ai.js +130 -121
- package/lib/assert/empty.js +3 -5
- package/lib/assert/equal.js +4 -7
- package/lib/assert/include.js +4 -6
- package/lib/assert/throws.js +2 -4
- package/lib/assert/truth.js +2 -2
- package/lib/codecept.js +139 -87
- package/lib/command/check.js +201 -0
- package/lib/command/configMigrate.js +2 -4
- package/lib/command/definitions.js +8 -26
- package/lib/command/generate.js +10 -14
- package/lib/command/gherkin/snippets.js +75 -73
- package/lib/command/gherkin/steps.js +1 -1
- package/lib/command/info.js +42 -8
- package/lib/command/init.js +13 -12
- package/lib/command/interactive.js +10 -2
- package/lib/command/list.js +1 -1
- package/lib/command/run-multiple/chunk.js +48 -45
- package/lib/command/run-multiple.js +12 -35
- package/lib/command/run-workers.js +21 -58
- package/lib/command/utils.js +5 -6
- package/lib/command/workers/runTests.js +262 -220
- package/lib/container.js +386 -238
- package/lib/data/context.js +10 -13
- package/lib/data/dataScenarioConfig.js +8 -8
- package/lib/data/dataTableArgument.js +6 -6
- package/lib/data/table.js +5 -11
- package/lib/effects.js +223 -0
- package/lib/element/WebElement.js +327 -0
- package/lib/els.js +158 -0
- package/lib/event.js +21 -17
- package/lib/heal.js +88 -80
- package/lib/helper/AI.js +2 -1
- package/lib/helper/ApiDataFactory.js +3 -6
- package/lib/helper/Appium.js +47 -51
- package/lib/helper/FileSystem.js +3 -3
- package/lib/helper/GraphQLDataFactory.js +3 -3
- package/lib/helper/JSONResponse.js +75 -37
- package/lib/helper/Mochawesome.js +31 -9
- package/lib/helper/Nightmare.js +35 -53
- package/lib/helper/Playwright.js +262 -267
- package/lib/helper/Protractor.js +54 -77
- package/lib/helper/Puppeteer.js +246 -260
- package/lib/helper/REST.js +5 -17
- package/lib/helper/TestCafe.js +21 -44
- package/lib/helper/WebDriver.js +151 -170
- package/lib/helper/extras/Popup.js +22 -22
- package/lib/helper/testcafe/testcafe-utils.js +26 -27
- package/lib/listener/emptyRun.js +55 -0
- package/lib/listener/exit.js +7 -10
- package/lib/listener/{retry.js → globalRetry.js} +5 -5
- package/lib/listener/globalTimeout.js +165 -0
- package/lib/listener/helpers.js +15 -15
- package/lib/listener/mocha.js +1 -1
- package/lib/listener/result.js +12 -0
- package/lib/listener/retryEnhancer.js +85 -0
- package/lib/listener/steps.js +32 -18
- package/lib/listener/store.js +20 -0
- package/lib/mocha/asyncWrapper.js +231 -0
- package/lib/{interfaces → mocha}/bdd.js +3 -3
- package/lib/mocha/cli.js +308 -0
- package/lib/mocha/factory.js +104 -0
- package/lib/{interfaces → mocha}/featureConfig.js +32 -12
- package/lib/{interfaces → mocha}/gherkin.js +26 -28
- package/lib/mocha/hooks.js +112 -0
- package/lib/mocha/index.js +12 -0
- package/lib/mocha/inject.js +29 -0
- package/lib/{interfaces → mocha}/scenarioConfig.js +31 -7
- package/lib/mocha/suite.js +82 -0
- package/lib/mocha/test.js +181 -0
- package/lib/mocha/types.d.ts +42 -0
- package/lib/mocha/ui.js +232 -0
- package/lib/output.js +82 -62
- package/lib/pause.js +160 -138
- package/lib/plugin/analyze.js +396 -0
- package/lib/plugin/auth.js +435 -0
- package/lib/plugin/autoDelay.js +8 -8
- package/lib/plugin/autoLogin.js +3 -338
- package/lib/plugin/commentStep.js +6 -1
- package/lib/plugin/coverage.js +10 -19
- package/lib/plugin/customLocator.js +3 -3
- package/lib/plugin/customReporter.js +52 -0
- package/lib/plugin/eachElement.js +1 -1
- package/lib/plugin/fakerTransform.js +1 -1
- package/lib/plugin/heal.js +36 -9
- package/lib/plugin/htmlReporter.js +1947 -0
- package/lib/plugin/pageInfo.js +140 -0
- package/lib/plugin/retryFailedStep.js +17 -18
- package/lib/plugin/retryTo.js +2 -113
- package/lib/plugin/screenshotOnFail.js +17 -58
- package/lib/plugin/selenoid.js +15 -35
- package/lib/plugin/standardActingHelpers.js +4 -1
- package/lib/plugin/stepByStepReport.js +56 -17
- package/lib/plugin/stepTimeout.js +5 -12
- package/lib/plugin/subtitles.js +4 -4
- package/lib/plugin/tryTo.js +3 -102
- package/lib/plugin/wdio.js +8 -10
- package/lib/recorder.js +155 -124
- package/lib/rerun.js +43 -42
- package/lib/result.js +161 -0
- package/lib/secret.js +1 -1
- 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 +21 -332
- package/lib/steps.js +50 -0
- package/lib/store.js +37 -5
- package/lib/template/heal.js +2 -11
- package/lib/test-server.js +323 -0
- package/lib/timeout.js +66 -0
- package/lib/utils.js +351 -218
- package/lib/within.js +75 -55
- package/lib/workerStorage.js +2 -1
- package/lib/workers.js +386 -276
- package/package.json +76 -70
- package/translations/de-DE.js +4 -3
- package/translations/fr-FR.js +4 -3
- package/translations/index.js +1 -0
- 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 +9 -0
- package/translations/zh-CN.js +4 -3
- package/translations/zh-TW.js +4 -3
- package/typings/index.d.ts +188 -186
- package/typings/promiseBasedTypes.d.ts +18 -705
- package/typings/types.d.ts +301 -804
- package/lib/cli.js +0 -256
- package/lib/helper/ExpectHelper.js +0 -391
- package/lib/helper/SoftExpectHelper.js +0 -381
- package/lib/listener/artifacts.js +0 -19
- package/lib/listener/timeout.js +0 -109
- package/lib/mochaFactory.js +0 -113
- package/lib/plugin/debugErrors.js +0 -67
- package/lib/scenario.js +0 -224
- package/lib/ui.js +0 -236
package/lib/container.js
CHANGED
|
@@ -1,31 +1,47 @@
|
|
|
1
|
-
const
|
|
2
|
-
const path = require('path')
|
|
3
|
-
const
|
|
4
|
-
const {
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
1
|
+
const { globSync } = require('glob')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const debug = require('debug')('codeceptjs:container')
|
|
4
|
+
const { MetaStep } = require('./step')
|
|
5
|
+
const { methodsOfObject, fileExists, isFunction, isAsyncFunction, installedLocally } = require('./utils')
|
|
6
|
+
const Translation = require('./translation')
|
|
7
|
+
const MochaFactory = require('./mocha/factory')
|
|
8
|
+
const recorder = require('./recorder')
|
|
9
|
+
const event = require('./event')
|
|
10
|
+
const WorkerStorage = require('./workerStorage')
|
|
11
|
+
const store = require('./store')
|
|
12
|
+
const Result = require('./result')
|
|
13
|
+
const ai = require('./ai')
|
|
14
|
+
|
|
15
|
+
let asyncHelperPromise
|
|
12
16
|
|
|
13
17
|
let container = {
|
|
14
18
|
helpers: {},
|
|
15
19
|
support: {},
|
|
20
|
+
proxySupport: {},
|
|
16
21
|
plugins: {},
|
|
22
|
+
actor: null,
|
|
17
23
|
/**
|
|
18
24
|
* @type {Mocha | {}}
|
|
19
25
|
* @ignore
|
|
20
26
|
*/
|
|
21
27
|
mocha: {},
|
|
22
28
|
translation: {},
|
|
23
|
-
}
|
|
29
|
+
/** @type {Result | null} */
|
|
30
|
+
result: null,
|
|
31
|
+
sharedKeys: new Set() // Track keys shared via share() function
|
|
32
|
+
}
|
|
24
33
|
|
|
25
34
|
/**
|
|
26
35
|
* Dependency Injection Container
|
|
27
36
|
*/
|
|
28
37
|
class Container {
|
|
38
|
+
/**
|
|
39
|
+
* Get the standard acting helpers of CodeceptJS Container
|
|
40
|
+
*
|
|
41
|
+
*/
|
|
42
|
+
static get STANDARD_ACTING_HELPERS() {
|
|
43
|
+
return ['Playwright', 'WebDriver', 'Puppeteer', 'Appium', 'TestCafe']
|
|
44
|
+
}
|
|
29
45
|
/**
|
|
30
46
|
* Create container with all required helpers and support objects
|
|
31
47
|
*
|
|
@@ -34,21 +50,32 @@ class Container {
|
|
|
34
50
|
* @param {*} opts
|
|
35
51
|
*/
|
|
36
52
|
static create(config, opts) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
this.createMocha()
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
container.support =
|
|
48
|
-
container.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
debug('creating container')
|
|
54
|
+
asyncHelperPromise = Promise.resolve()
|
|
55
|
+
|
|
56
|
+
// dynamically create mocha instance
|
|
57
|
+
const mochaConfig = config.mocha || {}
|
|
58
|
+
if (config.grep && !opts.grep) mochaConfig.grep = config.grep
|
|
59
|
+
this.createMocha = () => (container.mocha = MochaFactory.create(mochaConfig, opts || {}))
|
|
60
|
+
this.createMocha()
|
|
61
|
+
|
|
62
|
+
// create support objects
|
|
63
|
+
container.support = {}
|
|
64
|
+
container.helpers = createHelpers(config.helpers || {})
|
|
65
|
+
container.translation = loadTranslation(config.translation || null, config.vocabularies || [])
|
|
66
|
+
container.proxySupport = createSupportObjects(config.include || {})
|
|
67
|
+
container.plugins = createPlugins(config.plugins || {}, opts)
|
|
68
|
+
container.result = new Result()
|
|
69
|
+
|
|
70
|
+
createActor(config.include?.I)
|
|
71
|
+
|
|
72
|
+
if (opts && opts.ai) ai.enable(config.ai) // enable AI Assistant
|
|
73
|
+
if (config.gherkin) loadGherkinSteps(config.gherkin.steps || [])
|
|
74
|
+
if (opts && typeof opts.timeouts === 'boolean') store.timeouts = opts.timeouts
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
static actor() {
|
|
78
|
+
return container.support.I
|
|
52
79
|
}
|
|
53
80
|
|
|
54
81
|
/**
|
|
@@ -60,9 +87,9 @@ class Container {
|
|
|
60
87
|
*/
|
|
61
88
|
static plugins(name) {
|
|
62
89
|
if (!name) {
|
|
63
|
-
return container.plugins
|
|
90
|
+
return container.plugins
|
|
64
91
|
}
|
|
65
|
-
return container.plugins[name]
|
|
92
|
+
return container.plugins[name]
|
|
66
93
|
}
|
|
67
94
|
|
|
68
95
|
/**
|
|
@@ -74,9 +101,9 @@ class Container {
|
|
|
74
101
|
*/
|
|
75
102
|
static support(name) {
|
|
76
103
|
if (!name) {
|
|
77
|
-
return container.
|
|
104
|
+
return container.proxySupport
|
|
78
105
|
}
|
|
79
|
-
return container.support[name]
|
|
106
|
+
return container.support[name] || container.proxySupport[name]
|
|
80
107
|
}
|
|
81
108
|
|
|
82
109
|
/**
|
|
@@ -88,9 +115,9 @@ class Container {
|
|
|
88
115
|
*/
|
|
89
116
|
static helpers(name) {
|
|
90
117
|
if (!name) {
|
|
91
|
-
return container.helpers
|
|
118
|
+
return container.helpers
|
|
92
119
|
}
|
|
93
|
-
return container.helpers[name]
|
|
120
|
+
return container.helpers[name]
|
|
94
121
|
}
|
|
95
122
|
|
|
96
123
|
/**
|
|
@@ -99,7 +126,7 @@ class Container {
|
|
|
99
126
|
* @api
|
|
100
127
|
*/
|
|
101
128
|
static translation() {
|
|
102
|
-
return container.translation
|
|
129
|
+
return container.translation
|
|
103
130
|
}
|
|
104
131
|
|
|
105
132
|
/**
|
|
@@ -109,7 +136,19 @@ class Container {
|
|
|
109
136
|
* @returns { * }
|
|
110
137
|
*/
|
|
111
138
|
static mocha() {
|
|
112
|
-
return container.mocha
|
|
139
|
+
return container.mocha
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get result
|
|
144
|
+
*
|
|
145
|
+
* @returns {Result}
|
|
146
|
+
*/
|
|
147
|
+
static result() {
|
|
148
|
+
if (!container.result) {
|
|
149
|
+
container.result = new Result()
|
|
150
|
+
}
|
|
151
|
+
return container.result
|
|
113
152
|
}
|
|
114
153
|
|
|
115
154
|
/**
|
|
@@ -119,8 +158,9 @@ class Container {
|
|
|
119
158
|
* @param {Object<string, *>} newContainer
|
|
120
159
|
*/
|
|
121
160
|
static append(newContainer) {
|
|
122
|
-
const deepMerge = require('./utils').deepMerge
|
|
123
|
-
container = deepMerge(container, newContainer)
|
|
161
|
+
const deepMerge = require('./utils').deepMerge
|
|
162
|
+
container = deepMerge(container, newContainer)
|
|
163
|
+
debug('appended', JSON.stringify(newContainer).slice(0, 300))
|
|
124
164
|
}
|
|
125
165
|
|
|
126
166
|
/**
|
|
@@ -130,11 +170,26 @@ class Container {
|
|
|
130
170
|
* @param {Object<string, *>} newSupport
|
|
131
171
|
* @param {Object<string, *>} newPlugins
|
|
132
172
|
*/
|
|
133
|
-
static clear(newHelpers, newSupport, newPlugins) {
|
|
134
|
-
container.helpers = newHelpers
|
|
135
|
-
container.
|
|
136
|
-
container.
|
|
137
|
-
container.
|
|
173
|
+
static clear(newHelpers = {}, newSupport = {}, newPlugins = {}) {
|
|
174
|
+
container.helpers = newHelpers
|
|
175
|
+
container.translation = loadTranslation()
|
|
176
|
+
container.proxySupport = createSupportObjects(newSupport)
|
|
177
|
+
container.plugins = newPlugins
|
|
178
|
+
container.sharedKeys = new Set() // Clear shared keys
|
|
179
|
+
asyncHelperPromise = Promise.resolve()
|
|
180
|
+
store.actor = null
|
|
181
|
+
debug('container cleared')
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* @param {Function|null} fn
|
|
186
|
+
* @returns {Promise<void>}
|
|
187
|
+
*/
|
|
188
|
+
static async started(fn = null) {
|
|
189
|
+
if (fn) {
|
|
190
|
+
asyncHelperPromise = asyncHelperPromise.then(fn)
|
|
191
|
+
}
|
|
192
|
+
return asyncHelperPromise
|
|
138
193
|
}
|
|
139
194
|
|
|
140
195
|
/**
|
|
@@ -144,299 +199,392 @@ class Container {
|
|
|
144
199
|
* @param {Object} options - set {local: true} to not share among workers
|
|
145
200
|
*/
|
|
146
201
|
static share(data, options = {}) {
|
|
147
|
-
|
|
202
|
+
// Instead of using append which replaces the entire container,
|
|
203
|
+
// directly update the support object to maintain proxy references
|
|
204
|
+
Object.assign(container.support, data)
|
|
205
|
+
|
|
206
|
+
// Track which keys were explicitly shared
|
|
207
|
+
Object.keys(data).forEach(key => container.sharedKeys.add(key))
|
|
208
|
+
|
|
148
209
|
if (!options.local) {
|
|
149
|
-
WorkerStorage.share(data)
|
|
210
|
+
WorkerStorage.share(data)
|
|
150
211
|
}
|
|
151
212
|
}
|
|
213
|
+
|
|
214
|
+
static createMocha(config = {}, opts = {}) {
|
|
215
|
+
const mochaConfig = config?.mocha || {}
|
|
216
|
+
if (config?.grep && !opts?.grep) {
|
|
217
|
+
mochaConfig.grep = config.grep
|
|
218
|
+
}
|
|
219
|
+
container.mocha = MochaFactory.create(mochaConfig, opts || {})
|
|
220
|
+
}
|
|
152
221
|
}
|
|
153
222
|
|
|
154
|
-
module.exports = Container
|
|
223
|
+
module.exports = Container
|
|
155
224
|
|
|
156
225
|
function createHelpers(config) {
|
|
157
|
-
const helpers = {}
|
|
158
|
-
let
|
|
159
|
-
for (const helperName in config) {
|
|
226
|
+
const helpers = {}
|
|
227
|
+
for (let helperName in config) {
|
|
160
228
|
try {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
} else {
|
|
168
|
-
moduleName = `./helper/${helperName}`; // built-in helper
|
|
229
|
+
let HelperClass
|
|
230
|
+
|
|
231
|
+
// ESM import
|
|
232
|
+
if (helperName?.constructor === Function && helperName.prototype) {
|
|
233
|
+
HelperClass = helperName
|
|
234
|
+
helperName = HelperClass.constructor.name
|
|
169
235
|
}
|
|
170
236
|
|
|
171
|
-
//
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
if (moduleName.startsWith('./helper/')) {
|
|
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);
|
|
237
|
+
// classical require
|
|
238
|
+
if (!HelperClass) {
|
|
239
|
+
HelperClass = requireHelperFromModule(helperName, config)
|
|
179
240
|
}
|
|
180
241
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
242
|
+
// handle async CJS modules that use dynamic import
|
|
243
|
+
if (isAsyncFunction(HelperClass)) {
|
|
244
|
+
helpers[helperName] = {}
|
|
245
|
+
|
|
246
|
+
asyncHelperPromise = asyncHelperPromise
|
|
247
|
+
.then(() => HelperClass())
|
|
248
|
+
.then(ResolvedHelperClass => {
|
|
249
|
+
// Check if ResolvedHelperClass is a constructor function
|
|
250
|
+
if (typeof ResolvedHelperClass?.constructor !== 'function') {
|
|
251
|
+
throw new Error(`Helper class from module '${helperName}' is not a class. Use CJS async module syntax.`)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
debug(`helper ${helperName} async initialized`)
|
|
255
|
+
|
|
256
|
+
helpers[helperName] = new ResolvedHelperClass(config[helperName])
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
continue
|
|
193
260
|
}
|
|
194
|
-
|
|
261
|
+
|
|
262
|
+
checkHelperRequirements(HelperClass)
|
|
263
|
+
|
|
264
|
+
helpers[helperName] = new HelperClass(config[helperName])
|
|
265
|
+
debug(`helper ${helperName} initialized`)
|
|
195
266
|
} catch (err) {
|
|
196
|
-
throw new Error(`Could not load helper ${helperName}
|
|
267
|
+
throw new Error(`Could not load helper ${helperName} (${err.message})`)
|
|
197
268
|
}
|
|
198
269
|
}
|
|
199
270
|
|
|
200
271
|
for (const name in helpers) {
|
|
201
|
-
if (helpers[name]._init) helpers[name]._init()
|
|
272
|
+
if (helpers[name]._init) helpers[name]._init()
|
|
202
273
|
}
|
|
203
|
-
return helpers
|
|
274
|
+
return helpers
|
|
204
275
|
}
|
|
205
276
|
|
|
206
|
-
function
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
277
|
+
function checkHelperRequirements(HelperClass) {
|
|
278
|
+
if (HelperClass._checkRequirements) {
|
|
279
|
+
const requirements = HelperClass._checkRequirements()
|
|
280
|
+
if (requirements) {
|
|
281
|
+
let install
|
|
282
|
+
if (installedLocally()) {
|
|
283
|
+
install = `npm install --save-dev ${requirements.join(' ')}`
|
|
284
|
+
} else {
|
|
285
|
+
console.log('WARNING: CodeceptJS is not installed locally. It is recommended to switch to local installation')
|
|
286
|
+
install = `[sudo] npm install -g ${requirements.join(' ')}`
|
|
287
|
+
}
|
|
288
|
+
throw new Error(`Required modules are not installed.\n\nRUN: ${install}`)
|
|
218
289
|
}
|
|
219
290
|
}
|
|
291
|
+
}
|
|
220
292
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
293
|
+
function requireHelperFromModule(helperName, config, HelperClass) {
|
|
294
|
+
const moduleName = getHelperModuleName(helperName, config)
|
|
295
|
+
if (moduleName.startsWith('./helper/')) {
|
|
296
|
+
HelperClass = require(moduleName)
|
|
297
|
+
} else {
|
|
298
|
+
// check if the new syntax export default HelperName is used and loads the Helper, otherwise loads the module that used old syntax export = HelperName.
|
|
225
299
|
try {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
newObj._init();
|
|
300
|
+
const mod = require(moduleName)
|
|
301
|
+
if (!mod && !mod.default) {
|
|
302
|
+
throw new Error(`Helper module '${moduleName}' was not found. Make sure you have installed the package correctly.`)
|
|
230
303
|
}
|
|
304
|
+
HelperClass = mod.default || mod
|
|
231
305
|
} catch (err) {
|
|
232
|
-
|
|
306
|
+
if (err.code === 'MODULE_NOT_FOUND') {
|
|
307
|
+
throw new Error(`Helper module '${moduleName}' was not found. Make sure you have installed the package correctly.`)
|
|
308
|
+
}
|
|
309
|
+
throw err
|
|
233
310
|
}
|
|
234
|
-
return newObj;
|
|
235
311
|
}
|
|
312
|
+
return HelperClass
|
|
313
|
+
}
|
|
236
314
|
|
|
315
|
+
function createSupportObjects(config) {
|
|
237
316
|
const asyncWrapper = function (f) {
|
|
238
317
|
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
|
-
});
|
|
318
|
+
return f.apply(this, arguments).catch(e => {
|
|
319
|
+
recorder.saveFirstAsyncError(e)
|
|
320
|
+
throw e
|
|
321
|
+
})
|
|
322
|
+
}
|
|
323
|
+
}
|
|
255
324
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
325
|
+
function lazyLoad(name) {
|
|
326
|
+
return new Proxy(
|
|
327
|
+
{},
|
|
328
|
+
{
|
|
329
|
+
get(target, prop) {
|
|
330
|
+
// behavr like array or
|
|
331
|
+
if (prop === 'length') return Object.keys(config).length
|
|
332
|
+
if (prop === Symbol.iterator) {
|
|
333
|
+
return function* () {
|
|
334
|
+
for (let i = 0; i < Object.keys(config).length; i++) {
|
|
335
|
+
yield target[i]
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// load actual name from vocabulary
|
|
341
|
+
if (container.translation.name) {
|
|
342
|
+
name = container.translation.name
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (name === 'I') {
|
|
346
|
+
const actor = createActor(config.I)
|
|
347
|
+
methodsOfObject(actor)
|
|
348
|
+
return actor[prop]
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (!container.support[name] && typeof config[name] === 'object') {
|
|
352
|
+
container.support[name] = config[name]
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (!container.support[name]) {
|
|
356
|
+
// Load object on first access
|
|
357
|
+
const supportObject = loadSupportObject(config[name])
|
|
358
|
+
container.support[name] = supportObject
|
|
359
|
+
try {
|
|
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
|
+
}
|
|
367
|
+
}
|
|
270
368
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
369
|
+
const currentObject = container.support[name]
|
|
370
|
+
let currentValue = currentObject[prop]
|
|
371
|
+
|
|
372
|
+
if (isFunction(currentValue) || isAsyncFunction(currentValue)) {
|
|
373
|
+
const ms = new MetaStep(name, prop)
|
|
374
|
+
ms.setContext(currentObject)
|
|
375
|
+
if (isAsyncFunction(currentValue)) currentValue = asyncWrapper(currentValue)
|
|
376
|
+
debug(`metastep is created for ${name}.${prop.toString()}()`)
|
|
377
|
+
return ms.run.bind(ms, currentValue)
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return currentValue
|
|
381
|
+
},
|
|
382
|
+
has(target, prop) {
|
|
383
|
+
container.support[name] = container.support[name] || loadSupportObject(config[name])
|
|
384
|
+
return prop in container.support[name]
|
|
385
|
+
},
|
|
386
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
387
|
+
container.support[name] = container.support[name] || loadSupportObject(config[name])
|
|
388
|
+
return {
|
|
389
|
+
enumerable: true,
|
|
390
|
+
configurable: true,
|
|
391
|
+
value: this.get(target, prop),
|
|
392
|
+
}
|
|
393
|
+
},
|
|
394
|
+
ownKeys() {
|
|
395
|
+
container.support[name] = container.support[name] || loadSupportObject(config[name])
|
|
396
|
+
return Reflect.ownKeys(container.support[name])
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
)
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const keys = Reflect.ownKeys(config)
|
|
403
|
+
return new Proxy(
|
|
404
|
+
{},
|
|
405
|
+
{
|
|
406
|
+
has(target, key) {
|
|
407
|
+
return keys.includes(key) || container.sharedKeys.has(key)
|
|
408
|
+
},
|
|
409
|
+
ownKeys() {
|
|
410
|
+
// Return both original config keys and explicitly shared keys
|
|
411
|
+
return [...new Set([...keys, ...container.sharedKeys])]
|
|
412
|
+
},
|
|
413
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
414
|
+
return {
|
|
415
|
+
enumerable: true,
|
|
416
|
+
configurable: true,
|
|
417
|
+
value: this.get(target, prop),
|
|
276
418
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
419
|
+
},
|
|
420
|
+
get(target, key) {
|
|
421
|
+
// First check if this is an explicitly shared property
|
|
422
|
+
if (container.sharedKeys.has(key) && key in container.support) {
|
|
423
|
+
return container.support[key]
|
|
424
|
+
}
|
|
425
|
+
return lazyLoad(key)
|
|
426
|
+
},
|
|
280
427
|
},
|
|
281
|
-
|
|
428
|
+
)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function createActor(actorPath) {
|
|
432
|
+
if (container.support.I) return container.support.I
|
|
433
|
+
|
|
434
|
+
if (actorPath) {
|
|
435
|
+
container.support.I = loadSupportObject(actorPath)
|
|
436
|
+
} else {
|
|
437
|
+
const actor = require('./actor')
|
|
438
|
+
container.support.I = actor()
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return container.support.I
|
|
282
442
|
}
|
|
283
443
|
|
|
284
444
|
function createPlugins(config, options = {}) {
|
|
285
|
-
const plugins = {}
|
|
445
|
+
const plugins = {}
|
|
286
446
|
|
|
287
|
-
const enabledPluginsByOptions = (options.plugins || '').split(',')
|
|
447
|
+
const enabledPluginsByOptions = (options.plugins || '').split(',')
|
|
288
448
|
for (const pluginName in config) {
|
|
289
|
-
if (!config[pluginName]) config[pluginName] = {}
|
|
290
|
-
if (!config[pluginName].enabled &&
|
|
291
|
-
continue
|
|
449
|
+
if (!config[pluginName]) config[pluginName] = {}
|
|
450
|
+
if (!config[pluginName].enabled && enabledPluginsByOptions.indexOf(pluginName) < 0) {
|
|
451
|
+
continue // plugin is disabled
|
|
292
452
|
}
|
|
293
|
-
let module
|
|
453
|
+
let module
|
|
294
454
|
try {
|
|
295
455
|
if (config[pluginName].require) {
|
|
296
|
-
module = config[pluginName].require
|
|
297
|
-
if (module.startsWith('.')) {
|
|
298
|
-
|
|
456
|
+
module = config[pluginName].require
|
|
457
|
+
if (module.startsWith('.')) {
|
|
458
|
+
// local
|
|
459
|
+
module = path.resolve(global.codecept_dir, module) // custom plugin
|
|
299
460
|
}
|
|
300
461
|
} else {
|
|
301
|
-
module = `./plugin/${pluginName}
|
|
462
|
+
module = `./plugin/${pluginName}`
|
|
302
463
|
}
|
|
303
|
-
plugins[pluginName] = require(module)(config[pluginName])
|
|
464
|
+
plugins[pluginName] = require(module)(config[pluginName])
|
|
304
465
|
} catch (err) {
|
|
305
|
-
throw new Error(`Could not load plugin ${pluginName} from module '${module}':\n${err.message}\n${err.stack}`)
|
|
466
|
+
throw new Error(`Could not load plugin ${pluginName} from module '${module}':\n${err.message}\n${err.stack}`)
|
|
306
467
|
}
|
|
307
468
|
}
|
|
308
|
-
return plugins
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
function getSupportObject(config, name) {
|
|
312
|
-
const module = config[name];
|
|
313
|
-
if (typeof module === 'string') {
|
|
314
|
-
return loadSupportObject(module, name);
|
|
315
|
-
}
|
|
316
|
-
return module;
|
|
469
|
+
return plugins
|
|
317
470
|
}
|
|
318
471
|
|
|
319
472
|
function loadGherkinSteps(paths) {
|
|
320
|
-
global.Before = fn => event.dispatcher.on(event.test.started, fn)
|
|
321
|
-
global.After = fn => event.dispatcher.on(event.test.finished, fn)
|
|
322
|
-
global.Fail = fn => event.dispatcher.on(event.test.failed, fn)
|
|
473
|
+
global.Before = fn => event.dispatcher.on(event.test.started, fn)
|
|
474
|
+
global.After = fn => event.dispatcher.on(event.test.finished, fn)
|
|
475
|
+
global.Fail = fn => event.dispatcher.on(event.test.failed, fn)
|
|
323
476
|
|
|
324
477
|
// If gherkin.steps is string, then this will iterate through that folder and send all step def js files to loadSupportObject
|
|
325
478
|
// If gherkin.steps is Array, it will go the old way
|
|
326
479
|
// This is done so that we need not enter all Step Definition files under config.gherkin.steps
|
|
327
480
|
if (Array.isArray(paths)) {
|
|
328
481
|
for (const path of paths) {
|
|
329
|
-
loadSupportObject(path, `Step Definition from ${path}`)
|
|
482
|
+
loadSupportObject(path, `Step Definition from ${path}`)
|
|
330
483
|
}
|
|
331
484
|
} else {
|
|
332
|
-
const folderPath = paths.startsWith('.') ?
|
|
485
|
+
const folderPath = paths.startsWith('.') ? normalizeAndJoin(global.codecept_dir, paths) : ''
|
|
333
486
|
if (folderPath !== '') {
|
|
334
|
-
|
|
335
|
-
loadSupportObject(file, `Step Definition from ${file}`)
|
|
336
|
-
})
|
|
487
|
+
globSync(folderPath).forEach(file => {
|
|
488
|
+
loadSupportObject(file, `Step Definition from ${file}`)
|
|
489
|
+
})
|
|
337
490
|
}
|
|
338
491
|
}
|
|
339
492
|
|
|
340
|
-
delete global.Before
|
|
341
|
-
delete global.After
|
|
342
|
-
delete global.Fail
|
|
493
|
+
delete global.Before
|
|
494
|
+
delete global.After
|
|
495
|
+
delete global.Fail
|
|
343
496
|
}
|
|
344
497
|
|
|
345
498
|
function loadSupportObject(modulePath, supportObjectName) {
|
|
499
|
+
if (!modulePath) {
|
|
500
|
+
throw new Error(`Support object "${supportObjectName}" is not defined`)
|
|
501
|
+
}
|
|
346
502
|
if (modulePath.charAt(0) === '.') {
|
|
347
|
-
modulePath = path.join(global.codecept_dir, modulePath)
|
|
503
|
+
modulePath = path.join(global.codecept_dir, modulePath)
|
|
348
504
|
}
|
|
349
505
|
try {
|
|
350
|
-
const obj = require(modulePath)
|
|
506
|
+
const obj = require(modulePath)
|
|
351
507
|
|
|
508
|
+
// Handle different types of imports
|
|
352
509
|
if (typeof obj === 'function') {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
Object.keys(methods)
|
|
358
|
-
.forEach(key => {
|
|
359
|
-
fobj[key] = methods[key];
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
return methods;
|
|
510
|
+
// If it's a class (constructor function)
|
|
511
|
+
if (obj.prototype && obj.prototype.constructor === obj) {
|
|
512
|
+
const ClassName = obj
|
|
513
|
+
return new ClassName()
|
|
363
514
|
}
|
|
515
|
+
// If it's a regular function
|
|
516
|
+
return obj()
|
|
364
517
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
) {
|
|
369
|
-
const methods = getObjectMethods(obj);
|
|
370
|
-
Object.keys(methods)
|
|
371
|
-
.filter(key => !key.startsWith('_'))
|
|
372
|
-
.forEach(key => {
|
|
373
|
-
const currentMethod = methods[key];
|
|
374
|
-
if (isFunction(currentMethod) || isAsyncFunction(currentMethod)) {
|
|
375
|
-
const ms = new MetaStep(supportObjectName, key);
|
|
376
|
-
ms.setContext(methods);
|
|
377
|
-
methods[key] = ms.run.bind(ms, currentMethod);
|
|
378
|
-
}
|
|
379
|
-
});
|
|
380
|
-
return methods;
|
|
518
|
+
|
|
519
|
+
if (obj && Array.isArray(obj)) {
|
|
520
|
+
return obj
|
|
381
521
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
const currentMethod = obj[key];
|
|
387
|
-
if (isFunction(currentMethod) || isAsyncFunction(currentMethod)) {
|
|
388
|
-
const ms = new MetaStep(supportObjectName, key);
|
|
389
|
-
ms.setContext(obj);
|
|
390
|
-
obj[key] = ms.run.bind(ms, currentMethod);
|
|
391
|
-
}
|
|
392
|
-
});
|
|
522
|
+
|
|
523
|
+
// If it's a plain object
|
|
524
|
+
if (obj && typeof obj === 'object') {
|
|
525
|
+
return obj
|
|
393
526
|
}
|
|
394
527
|
|
|
395
|
-
|
|
528
|
+
throw new Error(`Support object "${supportObjectName}" should be an object, class, or function, but got ${typeof obj}`)
|
|
396
529
|
} catch (err) {
|
|
397
|
-
throw new Error(`Could not include object ${supportObjectName} from module '${modulePath}'\n${err.message}\n${err.stack}`)
|
|
530
|
+
throw new Error(`Could not include object ${supportObjectName} from module '${modulePath}'\n${err.message}\n${err.stack}`)
|
|
398
531
|
}
|
|
399
532
|
}
|
|
400
533
|
|
|
401
534
|
/**
|
|
402
535
|
* Method collect own property and prototype
|
|
403
536
|
*/
|
|
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
537
|
|
|
421
538
|
function loadTranslation(locale, vocabularies) {
|
|
422
539
|
if (!locale) {
|
|
423
|
-
return Translation.createEmpty()
|
|
540
|
+
return Translation.createEmpty()
|
|
424
541
|
}
|
|
425
542
|
|
|
426
|
-
let translation
|
|
543
|
+
let translation
|
|
427
544
|
|
|
428
545
|
// check if it is a known translation
|
|
429
546
|
if (Translation.langs[locale]) {
|
|
430
|
-
translation = new Translation(Translation.langs[locale])
|
|
547
|
+
translation = new Translation(Translation.langs[locale])
|
|
431
548
|
} else if (fileExists(path.join(global.codecept_dir, locale))) {
|
|
432
549
|
// get from a provided file instead
|
|
433
|
-
translation = Translation.createDefault()
|
|
434
|
-
translation.loadVocabulary(locale)
|
|
550
|
+
translation = Translation.createDefault()
|
|
551
|
+
translation.loadVocabulary(locale)
|
|
435
552
|
} else {
|
|
436
|
-
translation = Translation.createDefault()
|
|
553
|
+
translation = Translation.createDefault()
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
vocabularies.forEach(v => translation.loadVocabulary(v))
|
|
557
|
+
|
|
558
|
+
return translation
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function getHelperModuleName(helperName, config) {
|
|
562
|
+
// classical require
|
|
563
|
+
if (config[helperName].require) {
|
|
564
|
+
if (config[helperName].require.startsWith('.')) {
|
|
565
|
+
return path.resolve(global.codecept_dir, config[helperName].require) // custom helper
|
|
566
|
+
}
|
|
567
|
+
return config[helperName].require // plugin helper
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// built-in helpers
|
|
571
|
+
if (helperName.startsWith('@codeceptjs/')) {
|
|
572
|
+
return helperName
|
|
437
573
|
}
|
|
438
574
|
|
|
439
|
-
|
|
575
|
+
// built-in helpers
|
|
576
|
+
return `./helper/${helperName}`
|
|
577
|
+
}
|
|
578
|
+
function normalizeAndJoin(basePath, subPath) {
|
|
579
|
+
// Normalize and convert slashes to forward slashes in one step
|
|
580
|
+
const normalizedBase = path.posix.normalize(basePath.replace(/\\/g, '/'))
|
|
581
|
+
const normalizedSub = path.posix.normalize(subPath.replace(/\\/g, '/'))
|
|
582
|
+
|
|
583
|
+
// If subPath is absolute (starts with "/"), return it as the final path
|
|
584
|
+
if (normalizedSub.startsWith('/')) {
|
|
585
|
+
return normalizedSub
|
|
586
|
+
}
|
|
440
587
|
|
|
441
|
-
|
|
588
|
+
// Join the paths using POSIX-style
|
|
589
|
+
return path.posix.join(normalizedBase, normalizedSub)
|
|
442
590
|
}
|