codeceptjs 3.6.6 → 4.0.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.
- package/bin/codecept.js +81 -84
- package/lib/actor.js +13 -13
- package/lib/ai.js +13 -10
- package/lib/assert/empty.js +21 -20
- package/lib/assert/equal.js +39 -37
- package/lib/assert/error.js +14 -14
- package/lib/assert/include.js +47 -46
- package/lib/assert/throws.js +11 -13
- package/lib/assert/truth.js +22 -19
- package/lib/assert.js +2 -4
- package/lib/cli.js +49 -57
- package/lib/codecept.js +155 -142
- package/lib/colorUtils.js +3 -3
- package/lib/command/configMigrate.js +52 -58
- package/lib/command/definitions.js +89 -88
- package/lib/command/dryRun.js +68 -71
- package/lib/command/generate.js +188 -197
- package/lib/command/gherkin/init.js +16 -27
- package/lib/command/gherkin/snippets.js +20 -20
- package/lib/command/gherkin/steps.js +8 -8
- package/lib/command/info.js +38 -40
- package/lib/command/init.js +288 -290
- package/lib/command/interactive.js +32 -32
- package/lib/command/list.js +26 -26
- package/lib/command/run-multiple/chunk.js +5 -5
- package/lib/command/run-multiple/collection.js +3 -3
- package/lib/command/run-multiple/run.js +2 -6
- package/lib/command/run-multiple.js +93 -113
- package/lib/command/run-rerun.js +25 -20
- package/lib/command/run-workers.js +66 -64
- package/lib/command/run.js +29 -26
- package/lib/command/utils.js +65 -80
- package/lib/command/workers/runTests.js +10 -10
- package/lib/config.js +9 -10
- package/lib/container.js +48 -40
- package/lib/data/context.js +59 -60
- package/lib/data/dataScenarioConfig.js +47 -47
- package/lib/data/dataTableArgument.js +29 -29
- package/lib/data/table.js +20 -26
- package/lib/dirname.js +5 -0
- package/lib/event.js +167 -163
- package/lib/heal.js +17 -13
- package/lib/helper/AI.js +41 -130
- package/lib/helper/ApiDataFactory.js +69 -73
- package/lib/helper/Appium.js +381 -412
- package/lib/helper/Expect.js +425 -0
- package/lib/helper/ExpectHelper.js +48 -40
- package/lib/helper/FileSystem.js +79 -80
- package/lib/helper/GraphQL.js +43 -44
- package/lib/helper/GraphQLDataFactory.js +50 -50
- package/lib/helper/JSONResponse.js +62 -65
- package/lib/helper/Mochawesome.js +28 -28
- package/lib/helper/MockServer.js +14 -12
- package/lib/helper/Nightmare.js +566 -662
- package/lib/helper/Playwright.js +1216 -1361
- package/lib/helper/Protractor.js +627 -663
- package/lib/helper/Puppeteer.js +1128 -1231
- package/lib/helper/REST.js +68 -159
- package/lib/helper/SoftExpectHelper.js +2 -2
- package/lib/helper/TestCafe.js +484 -490
- package/lib/helper/WebDriver.js +1156 -1297
- package/lib/helper/clientscripts/PollyWebDriverExt.js +1 -1
- package/lib/helper/errors/ConnectionRefused.js +1 -1
- package/lib/helper/errors/ElementAssertion.js +2 -2
- package/lib/helper/errors/ElementNotFound.js +2 -2
- package/lib/helper/errors/RemoteBrowserConnectionRefused.js +1 -1
- package/lib/helper/extras/Console.js +1 -1
- package/lib/helper/extras/PlaywrightPropEngine.js +2 -2
- package/lib/helper/extras/PlaywrightReactVueLocator.js +1 -1
- package/lib/helper/extras/PlaywrightRestartOpts.js +18 -21
- package/lib/helper/extras/Popup.js +1 -1
- package/lib/helper/extras/React.js +3 -3
- package/lib/helper/network/actions.js +7 -14
- package/lib/helper/network/utils.js +2 -3
- package/lib/helper/scripts/blurElement.js +1 -1
- package/lib/helper/scripts/focusElement.js +1 -1
- package/lib/helper/scripts/highlightElement.js +1 -1
- package/lib/helper/scripts/isElementClickable.js +1 -1
- package/lib/helper/testcafe/testControllerHolder.js +1 -1
- package/lib/helper/testcafe/testcafe-utils.js +7 -6
- package/lib/helper.js +3 -1
- package/lib/history.js +5 -6
- package/lib/hooks.js +6 -6
- package/lib/html.js +7 -7
- package/lib/index.js +41 -25
- package/lib/interfaces/bdd.js +64 -47
- package/lib/interfaces/featureConfig.js +19 -19
- package/lib/interfaces/gherkin.js +118 -124
- package/lib/interfaces/scenarioConfig.js +29 -29
- package/lib/listener/artifacts.js +9 -9
- package/lib/listener/config.js +24 -24
- package/lib/listener/exit.js +12 -12
- package/lib/listener/helpers.js +42 -42
- package/lib/listener/mocha.js +11 -11
- package/lib/listener/retry.js +30 -32
- package/lib/listener/steps.js +53 -50
- package/lib/listener/timeout.js +54 -54
- package/lib/locator.js +10 -6
- package/lib/mochaFactory.js +15 -18
- package/lib/output.js +10 -6
- package/lib/parser.js +12 -15
- package/lib/pause.js +33 -40
- package/lib/plugin/allure.js +15 -15
- package/lib/plugin/autoDelay.js +37 -29
- package/lib/plugin/autoLogin.js +65 -70
- package/lib/plugin/commentStep.js +18 -18
- package/lib/plugin/coverage.js +67 -115
- package/lib/plugin/customLocator.js +20 -21
- package/lib/plugin/debugErrors.js +24 -24
- package/lib/plugin/eachElement.js +38 -38
- package/lib/plugin/fakerTransform.js +6 -6
- package/lib/plugin/heal.js +108 -67
- package/lib/plugin/pauseOnFail.js +11 -11
- package/lib/plugin/retryFailedStep.js +39 -32
- package/lib/plugin/retryTo.js +40 -46
- package/lib/plugin/screenshotOnFail.js +87 -109
- package/lib/plugin/selenoid.js +118 -131
- package/lib/plugin/standardActingHelpers.js +8 -2
- package/lib/plugin/stepByStepReport.js +91 -110
- package/lib/plugin/stepTimeout.js +23 -24
- package/lib/plugin/subtitles.js +35 -34
- package/lib/plugin/tryTo.js +30 -40
- package/lib/plugin/wdio.js +75 -78
- package/lib/recorder.js +17 -14
- package/lib/rerun.js +10 -11
- package/lib/scenario.js +23 -25
- package/lib/secret.js +2 -4
- package/lib/session.js +10 -10
- package/lib/step.js +9 -12
- package/lib/store.js +3 -2
- package/lib/transform.js +1 -1
- package/lib/translation.js +8 -7
- package/lib/ui.js +14 -12
- package/lib/utils.js +72 -70
- package/lib/within.js +10 -10
- package/lib/workerStorage.js +25 -27
- package/lib/workers.js +32 -29
- package/package.json +53 -51
- package/translations/de-DE.js +1 -1
- package/translations/fr-FR.js +1 -1
- package/translations/index.js +13 -9
- package/translations/it-IT.js +1 -1
- package/translations/ja-JP.js +1 -1
- package/translations/pl-PL.js +1 -1
- package/translations/pt-BR.js +1 -1
- package/translations/ru-RU.js +1 -1
- package/translations/zh-CN.js +1 -1
- package/translations/zh-TW.js +1 -1
- package/typings/index.d.ts +65 -415
- package/typings/promiseBasedTypes.d.ts +32 -0
- package/typings/types.d.ts +32 -0
package/lib/container.js
CHANGED
|
@@ -1,23 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
import glob from 'glob';
|
|
2
|
+
import path, { dirname } from 'path';
|
|
3
|
+
import importSync from 'import-sync';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { MetaStep } from './step.js';
|
|
6
|
+
import {
|
|
7
|
+
fileExists, isFunction, isAsyncFunction, deepMerge,
|
|
8
|
+
} from './utils.js';
|
|
9
|
+
import Translation from './translation.js';
|
|
10
|
+
import { MochaFactory } from './mochaFactory.js';
|
|
11
|
+
// eslint-disable-next-line import/no-named-as-default
|
|
12
|
+
import recorder from './recorder.js';
|
|
13
|
+
import * as event from './event.js';
|
|
14
|
+
import * as WorkerStorage from './workerStorage.js';
|
|
15
|
+
import { store } from './store.js';
|
|
16
|
+
import { actor } from './actor.js';
|
|
17
|
+
|
|
18
|
+
import ai from './ai.js';
|
|
12
19
|
|
|
13
20
|
let container = {
|
|
14
21
|
helpers: {},
|
|
15
22
|
support: {},
|
|
16
23
|
plugins: {},
|
|
17
|
-
/**
|
|
18
|
-
* @type {Mocha | {}}
|
|
19
|
-
* @ignore
|
|
20
|
-
*/
|
|
21
24
|
mocha: {},
|
|
22
25
|
translation: {},
|
|
23
26
|
};
|
|
@@ -25,7 +28,7 @@ let container = {
|
|
|
25
28
|
/**
|
|
26
29
|
* Dependency Injection Container
|
|
27
30
|
*/
|
|
28
|
-
class Container {
|
|
31
|
+
export default class Container {
|
|
29
32
|
/**
|
|
30
33
|
* Create container with all required helpers and support objects
|
|
31
34
|
*
|
|
@@ -59,10 +62,7 @@ class Container {
|
|
|
59
62
|
* @returns { * }
|
|
60
63
|
*/
|
|
61
64
|
static plugins(name) {
|
|
62
|
-
|
|
63
|
-
return container.plugins;
|
|
64
|
-
}
|
|
65
|
-
return container.plugins[name];
|
|
65
|
+
return name ? container.plugins[name] : container.plugins;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
/**
|
|
@@ -119,7 +119,6 @@ class Container {
|
|
|
119
119
|
* @param {Object<string, *>} newContainer
|
|
120
120
|
*/
|
|
121
121
|
static append(newContainer) {
|
|
122
|
-
const deepMerge = require('./utils').deepMerge;
|
|
123
122
|
container = deepMerge(container, newContainer);
|
|
124
123
|
}
|
|
125
124
|
|
|
@@ -151,8 +150,6 @@ class Container {
|
|
|
151
150
|
}
|
|
152
151
|
}
|
|
153
152
|
|
|
154
|
-
module.exports = Container;
|
|
155
|
-
|
|
156
153
|
function createHelpers(config) {
|
|
157
154
|
const helpers = {};
|
|
158
155
|
let moduleName;
|
|
@@ -160,29 +157,31 @@ function createHelpers(config) {
|
|
|
160
157
|
try {
|
|
161
158
|
if (config[helperName].require) {
|
|
162
159
|
if (config[helperName].require.startsWith('.')) {
|
|
160
|
+
// @ts-ignore
|
|
163
161
|
moduleName = path.resolve(global.codecept_dir, config[helperName].require); // custom helper
|
|
164
162
|
} else {
|
|
165
163
|
moduleName = config[helperName].require; // plugin helper
|
|
166
164
|
}
|
|
167
165
|
} else {
|
|
168
|
-
moduleName = `./helper/${helperName}`; // built-in helper
|
|
166
|
+
moduleName = `./helper/${helperName}.js`; // built-in helper
|
|
169
167
|
}
|
|
170
168
|
|
|
171
169
|
// @ts-ignore
|
|
172
170
|
let HelperClass;
|
|
173
171
|
// check if the helper is the built-in, use the require() syntax.
|
|
174
172
|
if (moduleName.startsWith('./helper/')) {
|
|
175
|
-
|
|
173
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
174
|
+
HelperClass = importSync(path.resolve(__dirname, moduleName)).default;
|
|
176
175
|
} else {
|
|
177
176
|
// check if the new syntax export default HelperName is used and loads the Helper, otherwise loads the module that used old syntax export = HelperName.
|
|
178
|
-
HelperClass =
|
|
177
|
+
HelperClass = importSync(path.resolve(moduleName)).default;
|
|
179
178
|
}
|
|
180
179
|
|
|
181
|
-
if (HelperClass._checkRequirements) {
|
|
180
|
+
if (HelperClass && HelperClass._checkRequirements) {
|
|
182
181
|
const requirements = HelperClass._checkRequirements();
|
|
183
182
|
if (requirements) {
|
|
184
183
|
let install;
|
|
185
|
-
if (
|
|
184
|
+
if (importSync('./utils.js').installedLocally()) {
|
|
186
185
|
install = `npm install --save-dev ${requirements.join(' ')}`;
|
|
187
186
|
} else {
|
|
188
187
|
console.log('WARNING: CodeceptJS is not installed locally. It is recommended to switch to local installation');
|
|
@@ -203,7 +202,7 @@ function createHelpers(config) {
|
|
|
203
202
|
return helpers;
|
|
204
203
|
}
|
|
205
204
|
|
|
206
|
-
function createSupportObjects(config) {
|
|
205
|
+
export function createSupportObjects(config) {
|
|
207
206
|
const objects = {};
|
|
208
207
|
|
|
209
208
|
for (const name in config) {
|
|
@@ -211,7 +210,7 @@ function createSupportObjects(config) {
|
|
|
211
210
|
}
|
|
212
211
|
|
|
213
212
|
if (!config.I) {
|
|
214
|
-
objects.I =
|
|
213
|
+
objects.I = actor();
|
|
215
214
|
|
|
216
215
|
if (container.translation.I !== 'I') {
|
|
217
216
|
objects[container.translation.I] = objects.I;
|
|
@@ -295,12 +294,13 @@ function createPlugins(config, options = {}) {
|
|
|
295
294
|
if (config[pluginName].require) {
|
|
296
295
|
module = config[pluginName].require;
|
|
297
296
|
if (module.startsWith('.')) { // local
|
|
297
|
+
// @ts-ignore
|
|
298
298
|
module = path.resolve(global.codecept_dir, module); // custom plugin
|
|
299
299
|
}
|
|
300
300
|
} else {
|
|
301
|
-
module = `./plugin/${pluginName}`;
|
|
301
|
+
module = `./plugin/${pluginName}.js`;
|
|
302
302
|
}
|
|
303
|
-
plugins[pluginName] =
|
|
303
|
+
plugins[pluginName] = importSync(module).default(config[pluginName]);
|
|
304
304
|
} catch (err) {
|
|
305
305
|
throw new Error(`Could not load plugin ${pluginName} from module '${module}':\n${err.message}\n${err.stack}`);
|
|
306
306
|
}
|
|
@@ -316,8 +316,10 @@ function getSupportObject(config, name) {
|
|
|
316
316
|
return module;
|
|
317
317
|
}
|
|
318
318
|
|
|
319
|
-
function loadGherkinSteps(paths) {
|
|
319
|
+
export function loadGherkinSteps(paths) {
|
|
320
|
+
// @ts-ignore
|
|
320
321
|
global.Before = fn => event.dispatcher.on(event.test.started, fn);
|
|
322
|
+
// @ts-ignore
|
|
321
323
|
global.After = fn => event.dispatcher.on(event.test.finished, fn);
|
|
322
324
|
global.Fail = fn => event.dispatcher.on(event.test.failed, fn);
|
|
323
325
|
|
|
@@ -329,6 +331,7 @@ function loadGherkinSteps(paths) {
|
|
|
329
331
|
loadSupportObject(path, `Step Definition from ${path}`);
|
|
330
332
|
}
|
|
331
333
|
} else {
|
|
334
|
+
// @ts-ignore
|
|
332
335
|
const folderPath = paths.startsWith('.') ? path.join(global.codecept_dir, paths) : '';
|
|
333
336
|
if (folderPath !== '') {
|
|
334
337
|
glob.sync(folderPath).forEach((file) => {
|
|
@@ -337,17 +340,20 @@ function loadGherkinSteps(paths) {
|
|
|
337
340
|
}
|
|
338
341
|
}
|
|
339
342
|
|
|
343
|
+
// @ts-ignore
|
|
340
344
|
delete global.Before;
|
|
345
|
+
// @ts-ignore
|
|
341
346
|
delete global.After;
|
|
342
347
|
delete global.Fail;
|
|
343
348
|
}
|
|
344
349
|
|
|
345
350
|
function loadSupportObject(modulePath, supportObjectName) {
|
|
346
351
|
if (modulePath.charAt(0) === '.') {
|
|
352
|
+
// @ts-ignore
|
|
347
353
|
modulePath = path.join(global.codecept_dir, modulePath);
|
|
348
354
|
}
|
|
349
355
|
try {
|
|
350
|
-
const obj =
|
|
356
|
+
const obj = importSync(modulePath).default || importSync(modulePath);
|
|
351
357
|
|
|
352
358
|
if (typeof obj === 'function') {
|
|
353
359
|
const fobj = obj();
|
|
@@ -405,7 +411,7 @@ function getObjectMethods(obj) {
|
|
|
405
411
|
const methodsSet = new Set();
|
|
406
412
|
let protoObj = Reflect.getPrototypeOf(obj);
|
|
407
413
|
do {
|
|
408
|
-
if (protoObj
|
|
414
|
+
if (protoObj?.constructor.prototype !== Object.prototype) {
|
|
409
415
|
const keys = Reflect.ownKeys(protoObj);
|
|
410
416
|
keys.forEach(k => methodsSet.add(k));
|
|
411
417
|
}
|
|
@@ -424,16 +430,18 @@ function loadTranslation(locale, vocabularies) {
|
|
|
424
430
|
}
|
|
425
431
|
|
|
426
432
|
let translation;
|
|
433
|
+
locale = locale.replace('-', '_');
|
|
427
434
|
|
|
428
435
|
// check if it is a known translation
|
|
429
436
|
if (Translation.langs[locale]) {
|
|
430
437
|
translation = new Translation(Translation.langs[locale]);
|
|
431
|
-
} else
|
|
432
|
-
// get from a provided file instead
|
|
433
|
-
translation = Translation.createDefault();
|
|
434
|
-
translation.loadVocabulary(locale);
|
|
435
|
-
} else {
|
|
438
|
+
} else { // @ts-ignore
|
|
436
439
|
translation = Translation.createDefault();
|
|
440
|
+
// @ts-ignore
|
|
441
|
+
if (fileExists(path.join(global.codecept_dir, locale))) {
|
|
442
|
+
// get from a provided file instead
|
|
443
|
+
translation.loadVocabulary(locale);
|
|
444
|
+
}
|
|
437
445
|
}
|
|
438
446
|
|
|
439
447
|
vocabularies.forEach(v => translation.loadVocabulary(v));
|
package/lib/data/context.js
CHANGED
|
@@ -1,129 +1,128 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const Secret = require('../secret')
|
|
1
|
+
import { isGenerator } from '../utils.js';
|
|
2
|
+
import DataTable from './table.js';
|
|
3
|
+
import DataScenarioConfig from './dataScenarioConfig.js';
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
export default function (context) {
|
|
7
6
|
context.Data = function (dataTable) {
|
|
8
|
-
const data = detectDataType(dataTable)
|
|
7
|
+
const data = detectDataType(dataTable);
|
|
9
8
|
return {
|
|
10
9
|
Scenario(title, opts, fn) {
|
|
11
|
-
const scenarios = []
|
|
10
|
+
const scenarios = [];
|
|
12
11
|
if (typeof opts === 'function' && !fn) {
|
|
13
|
-
fn = opts
|
|
14
|
-
opts = {}
|
|
12
|
+
fn = opts;
|
|
13
|
+
opts = {};
|
|
15
14
|
}
|
|
16
|
-
opts.data = data.map(
|
|
15
|
+
opts.data = data.map(dataRow => dataRow.data);
|
|
17
16
|
data.forEach((dataRow) => {
|
|
18
|
-
const dataTitle = replaceTitle(title, dataRow)
|
|
17
|
+
const dataTitle = replaceTitle(title, dataRow);
|
|
19
18
|
if (dataRow.skip) {
|
|
20
|
-
context.xScenario(dataTitle)
|
|
19
|
+
context.xScenario(dataTitle);
|
|
21
20
|
} else {
|
|
22
|
-
scenarios.push(context.Scenario(dataTitle, opts, fn)
|
|
21
|
+
scenarios.push(context.Scenario(dataTitle, opts, fn)
|
|
22
|
+
.inject({ current: dataRow.data }));
|
|
23
23
|
}
|
|
24
|
-
})
|
|
25
|
-
maskSecretInTitle(scenarios)
|
|
26
|
-
return new DataScenarioConfig(scenarios)
|
|
24
|
+
});
|
|
25
|
+
maskSecretInTitle(scenarios);
|
|
26
|
+
return new DataScenarioConfig(scenarios);
|
|
27
27
|
},
|
|
28
28
|
only: {
|
|
29
29
|
Scenario(title, opts, fn) {
|
|
30
|
-
const scenarios = []
|
|
30
|
+
const scenarios = [];
|
|
31
31
|
if (typeof opts === 'function' && !fn) {
|
|
32
|
-
fn = opts
|
|
33
|
-
opts = {}
|
|
32
|
+
fn = opts;
|
|
33
|
+
opts = {};
|
|
34
34
|
}
|
|
35
|
-
opts.data = data.map(
|
|
35
|
+
opts.data = data.map(dataRow => dataRow.data);
|
|
36
36
|
data.forEach((dataRow) => {
|
|
37
|
-
const dataTitle = replaceTitle(title, dataRow)
|
|
37
|
+
const dataTitle = replaceTitle(title, dataRow);
|
|
38
38
|
if (dataRow.skip) {
|
|
39
|
-
context.xScenario(dataTitle)
|
|
39
|
+
context.xScenario(dataTitle);
|
|
40
40
|
} else {
|
|
41
|
-
scenarios.push(context.Scenario.only(dataTitle, opts, fn)
|
|
41
|
+
scenarios.push(context.Scenario.only(dataTitle, opts, fn)
|
|
42
|
+
.inject({ current: dataRow.data }));
|
|
42
43
|
}
|
|
43
|
-
})
|
|
44
|
-
maskSecretInTitle(scenarios)
|
|
45
|
-
return new DataScenarioConfig(scenarios)
|
|
44
|
+
});
|
|
45
|
+
maskSecretInTitle(scenarios);
|
|
46
|
+
return new DataScenarioConfig(scenarios);
|
|
46
47
|
},
|
|
47
48
|
},
|
|
48
|
-
}
|
|
49
|
-
}
|
|
49
|
+
};
|
|
50
|
+
};
|
|
50
51
|
|
|
51
52
|
context.xData = function (dataTable) {
|
|
52
|
-
const data = detectDataType(dataTable)
|
|
53
|
+
const data = detectDataType(dataTable);
|
|
53
54
|
return {
|
|
54
55
|
Scenario: (title) => {
|
|
55
56
|
data.forEach((dataRow) => {
|
|
56
|
-
const dataTitle = replaceTitle(title, dataRow)
|
|
57
|
-
context.xScenario(dataTitle)
|
|
58
|
-
})
|
|
59
|
-
return new DataScenarioConfig([])
|
|
57
|
+
const dataTitle = replaceTitle(title, dataRow);
|
|
58
|
+
context.xScenario(dataTitle);
|
|
59
|
+
});
|
|
60
|
+
return new DataScenarioConfig([]);
|
|
60
61
|
},
|
|
61
|
-
}
|
|
62
|
-
}
|
|
62
|
+
};
|
|
63
|
+
};
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
function replaceTitle(title, dataRow) {
|
|
66
67
|
if (typeof dataRow.data !== 'object') {
|
|
67
|
-
return `${title} | {${JSON.stringify(dataRow.data)}}
|
|
68
|
+
return `${title} | {${JSON.stringify(dataRow.data)}}`;
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
// if `dataRow` is object and has own `toString()` method,
|
|
71
72
|
// it should be printed
|
|
72
|
-
if (
|
|
73
|
-
|
|
74
|
-
dataRow.data
|
|
75
|
-
) {
|
|
76
|
-
return `${title} | ${dataRow.data}`
|
|
73
|
+
if (Object.prototype.toString.call(dataRow.data) === (Object()).toString()
|
|
74
|
+
&& dataRow.data.toString() !== (Object()).toString()) {
|
|
75
|
+
return `${title} | ${dataRow.data}`;
|
|
77
76
|
}
|
|
78
77
|
|
|
79
|
-
return `${title} | ${JSON.stringify(dataRow.data)}
|
|
78
|
+
return `${title} | ${JSON.stringify(dataRow.data)}`;
|
|
80
79
|
}
|
|
81
80
|
|
|
82
81
|
function isTableDataRow(row) {
|
|
83
|
-
const has = Object.prototype.hasOwnProperty
|
|
84
|
-
return has.call(row, 'data') && has.call(row, 'skip')
|
|
82
|
+
const has = Object.prototype.hasOwnProperty;
|
|
83
|
+
return has.call(row, 'data') && has.call(row, 'skip');
|
|
85
84
|
}
|
|
86
85
|
|
|
87
86
|
function detectDataType(dataTable) {
|
|
88
87
|
if (dataTable instanceof DataTable) {
|
|
89
|
-
return dataTable.rows
|
|
88
|
+
return dataTable.rows;
|
|
90
89
|
}
|
|
91
90
|
|
|
92
91
|
if (isGenerator(dataTable)) {
|
|
93
|
-
const data = []
|
|
92
|
+
const data = [];
|
|
94
93
|
for (const dataRow of dataTable()) {
|
|
95
94
|
data.push({
|
|
96
95
|
data: dataRow,
|
|
97
|
-
})
|
|
96
|
+
});
|
|
98
97
|
}
|
|
99
|
-
return data
|
|
98
|
+
return data;
|
|
100
99
|
}
|
|
101
100
|
if (typeof dataTable === 'function') {
|
|
102
|
-
return dataTable()
|
|
101
|
+
return dataTable();
|
|
103
102
|
}
|
|
104
103
|
if (Array.isArray(dataTable)) {
|
|
105
104
|
return dataTable.map((item) => {
|
|
106
105
|
if (isTableDataRow(item)) {
|
|
107
|
-
return item
|
|
106
|
+
return item;
|
|
108
107
|
}
|
|
109
108
|
return {
|
|
110
109
|
data: item,
|
|
111
110
|
skip: false,
|
|
112
|
-
}
|
|
113
|
-
})
|
|
111
|
+
};
|
|
112
|
+
});
|
|
114
113
|
}
|
|
115
114
|
|
|
116
|
-
throw new Error('Invalid data type. Data accepts either: DataTable || generator || Array || function')
|
|
115
|
+
throw new Error('Invalid data type. Data accepts either: DataTable || generator || Array || function');
|
|
117
116
|
}
|
|
118
117
|
|
|
119
118
|
function maskSecretInTitle(scenarios) {
|
|
120
|
-
scenarios.forEach(
|
|
121
|
-
const res = []
|
|
119
|
+
scenarios.forEach(scenario => {
|
|
120
|
+
const res = [];
|
|
122
121
|
|
|
123
|
-
scenario.test.title.split(',').forEach(
|
|
124
|
-
res.push(item.replace(/{"_secret":"(.*)"}/, '"*****"'))
|
|
125
|
-
})
|
|
122
|
+
scenario.test.title.split(',').forEach(item => {
|
|
123
|
+
res.push(item.replace(/{"_secret":"(.*)"}/, '"*****"'));
|
|
124
|
+
});
|
|
126
125
|
|
|
127
|
-
scenario.test.title = res.join(',')
|
|
128
|
-
})
|
|
126
|
+
scenario.test.title = res.join(',');
|
|
127
|
+
});
|
|
129
128
|
}
|
|
@@ -1,84 +1,84 @@
|
|
|
1
1
|
class DataScenarioConfig {
|
|
2
2
|
constructor(scenarios) {
|
|
3
|
-
this.scenarios = scenarios
|
|
3
|
+
this.scenarios = scenarios;
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
* Declares that test throws error.
|
|
8
|
+
* Can pass an Error object or regex matching expected message.
|
|
9
|
+
*
|
|
10
|
+
* @param {*} err
|
|
11
|
+
*/
|
|
12
12
|
throws(err) {
|
|
13
|
-
this.scenarios.forEach(
|
|
14
|
-
return this
|
|
13
|
+
this.scenarios.forEach(scenario => scenario.throws(err));
|
|
14
|
+
return this;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
* Declares that test should fail.
|
|
19
|
+
* If test passes - throws an error.
|
|
20
|
+
* Can pass an Error object or regex matching expected message.
|
|
21
|
+
*
|
|
22
|
+
*/
|
|
23
23
|
fails() {
|
|
24
|
-
this.scenarios.forEach(
|
|
25
|
-
return this
|
|
24
|
+
this.scenarios.forEach(scenario => scenario.fails());
|
|
25
|
+
return this;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
* Retry this test for x times
|
|
30
|
+
*
|
|
31
|
+
* @param {*} retries
|
|
32
|
+
*/
|
|
33
33
|
retry(retries) {
|
|
34
|
-
this.scenarios.forEach(
|
|
35
|
-
return this
|
|
34
|
+
this.scenarios.forEach(scenario => scenario.retry(retries));
|
|
35
|
+
return this;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
* Set timeout for this test
|
|
40
|
+
* @param {*} timeout
|
|
41
|
+
*/
|
|
42
42
|
timeout(timeout) {
|
|
43
|
-
this.scenarios.forEach(
|
|
44
|
-
return this
|
|
43
|
+
this.scenarios.forEach(scenario => scenario.timeout(timeout));
|
|
44
|
+
return this;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
* Configures a helper.
|
|
49
|
+
* Helper name can be omitted and values will be applied to first helper.
|
|
50
|
+
*/
|
|
51
51
|
config(helper, obj) {
|
|
52
|
-
this.scenarios.forEach(
|
|
53
|
-
return this
|
|
52
|
+
this.scenarios.forEach(scenario => scenario.config(helper, obj));
|
|
53
|
+
return this;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
* Append a tag name to scenario title
|
|
58
|
+
* @param {*} tagName
|
|
59
|
+
*/
|
|
60
60
|
tag(tagName) {
|
|
61
|
-
this.scenarios.forEach(
|
|
62
|
-
return this
|
|
61
|
+
this.scenarios.forEach(scenario => scenario.tag(tagName));
|
|
62
|
+
return this;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
* Pass in additional objects to inject into test
|
|
67
|
+
* @param {*} obj
|
|
68
|
+
*/
|
|
69
69
|
inject(obj) {
|
|
70
|
-
this.scenarios.forEach(
|
|
71
|
-
return this
|
|
70
|
+
this.scenarios.forEach(scenario => scenario.inject(obj));
|
|
71
|
+
return this;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
* Dynamically injects dependencies, see https://codecept.io/pageobjects/#dynamic-injection
|
|
76
|
+
* @param {*} dependencies
|
|
77
|
+
*/
|
|
78
78
|
injectDependencies(dependencies) {
|
|
79
|
-
this.scenarios.forEach(
|
|
80
|
-
return this
|
|
79
|
+
this.scenarios.forEach(scenario => scenario.injectDependencies(dependencies));
|
|
80
|
+
return this;
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
export default DataScenarioConfig;
|
|
@@ -7,60 +7,60 @@ class DataTableArgument {
|
|
|
7
7
|
constructor(gherkinDataTable) {
|
|
8
8
|
this.rawData = gherkinDataTable.rows.map((row) => {
|
|
9
9
|
return row.cells.map((cell) => {
|
|
10
|
-
return cell.value
|
|
11
|
-
})
|
|
12
|
-
})
|
|
10
|
+
return cell.value;
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
/** Returns the table as a 2-D array
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
* @returns {string[][]}
|
|
17
|
+
*/
|
|
18
18
|
raw() {
|
|
19
|
-
return this.rawData.slice(0)
|
|
19
|
+
return this.rawData.slice(0);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
/** Returns the table as a 2-D array, without the first row
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
* @returns {string[][]}
|
|
24
|
+
*/
|
|
25
25
|
rows() {
|
|
26
|
-
const copy = this.raw()
|
|
27
|
-
copy.shift()
|
|
28
|
-
return copy
|
|
26
|
+
const copy = this.raw();
|
|
27
|
+
copy.shift();
|
|
28
|
+
return copy;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/** Returns an array of objects where each row is converted to an object (column header is the key)
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
* @returns {any[]}
|
|
33
|
+
*/
|
|
34
34
|
hashes() {
|
|
35
|
-
const copy = this.raw()
|
|
36
|
-
const header = copy.shift()
|
|
35
|
+
const copy = this.raw();
|
|
36
|
+
const header = copy.shift();
|
|
37
37
|
return copy.map((row) => {
|
|
38
|
-
const r = {}
|
|
39
|
-
row.forEach((cell, index) =>
|
|
40
|
-
return r
|
|
41
|
-
})
|
|
38
|
+
const r = {};
|
|
39
|
+
row.forEach((cell, index) => r[header[index]] = cell);
|
|
40
|
+
return r;
|
|
41
|
+
});
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
/** Returns an object where each row corresponds to an entry
|
|
45
45
|
* (first column is the key, second column is the value)
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
* @returns {Record<string, string>}
|
|
47
|
+
*/
|
|
48
48
|
rowsHash() {
|
|
49
|
-
const rows = this.raw()
|
|
50
|
-
const everyRowHasTwoColumns = rows.every((row) => row.length === 2)
|
|
49
|
+
const rows = this.raw();
|
|
50
|
+
const everyRowHasTwoColumns = rows.every((row) => row.length === 2);
|
|
51
51
|
if (!everyRowHasTwoColumns) {
|
|
52
|
-
throw new Error('rowsHash can only be called on a data table where all rows have exactly two columns')
|
|
52
|
+
throw new Error('rowsHash can only be called on a data table where all rows have exactly two columns');
|
|
53
53
|
}
|
|
54
54
|
/** @type {Record<string, string>} */
|
|
55
|
-
const result = {}
|
|
56
|
-
rows.forEach((x) => (result[x[0]] = x[1]))
|
|
57
|
-
return result
|
|
55
|
+
const result = {};
|
|
56
|
+
rows.forEach((x) => (result[x[0]] = x[1]));
|
|
57
|
+
return result;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
/** Transposed the data */
|
|
61
61
|
transpose() {
|
|
62
|
-
this.rawData = this.rawData[0].map((x, i) => this.rawData.map((y) => y[i]))
|
|
62
|
+
this.rawData = this.rawData[0].map((x, i) => this.rawData.map((y) => y[i]));
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
export default DataTableArgument;
|