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