codeceptjs 3.6.10-beta.1 → 3.7.0-beta.1

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