codeceptjs 4.0.0-beta.1 → 4.0.0-beta.3
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 +84 -81
- package/lib/actor.js +13 -13
- package/lib/ai.js +10 -13
- package/lib/assert/empty.js +20 -21
- package/lib/assert/equal.js +37 -39
- package/lib/assert/error.js +14 -14
- package/lib/assert/include.js +46 -47
- package/lib/assert/throws.js +13 -11
- package/lib/assert/truth.js +19 -22
- package/lib/assert.js +4 -2
- package/lib/cli.js +57 -49
- package/lib/codecept.js +142 -155
- package/lib/colorUtils.js +3 -3
- package/lib/command/configMigrate.js +58 -52
- package/lib/command/definitions.js +88 -89
- package/lib/command/dryRun.js +71 -68
- package/lib/command/generate.js +197 -188
- package/lib/command/gherkin/init.js +27 -16
- package/lib/command/gherkin/snippets.js +20 -20
- package/lib/command/gherkin/steps.js +8 -8
- package/lib/command/info.js +40 -38
- package/lib/command/init.js +290 -288
- 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 +6 -2
- package/lib/command/run-multiple.js +113 -93
- package/lib/command/run-rerun.js +20 -25
- package/lib/command/run-workers.js +64 -66
- package/lib/command/run.js +26 -29
- package/lib/command/utils.js +80 -65
- package/lib/command/workers/runTests.js +10 -10
- package/lib/config.js +10 -9
- package/lib/container.js +40 -48
- package/lib/data/context.js +60 -59
- package/lib/data/dataScenarioConfig.js +47 -47
- package/lib/data/dataTableArgument.js +29 -29
- package/lib/data/table.js +26 -20
- package/lib/event.js +163 -167
- package/lib/heal.js +13 -17
- package/lib/helper/AI.js +130 -41
- package/lib/helper/ApiDataFactory.js +73 -69
- package/lib/helper/Appium.js +413 -382
- package/lib/helper/ExpectHelper.js +40 -48
- package/lib/helper/FileSystem.js +80 -79
- package/lib/helper/GraphQL.js +44 -43
- package/lib/helper/GraphQLDataFactory.js +50 -50
- package/lib/helper/JSONResponse.js +65 -62
- package/lib/helper/Mochawesome.js +28 -28
- package/lib/helper/MockServer.js +12 -14
- package/lib/helper/Nightmare.js +662 -566
- package/lib/helper/Playwright.js +1361 -1216
- package/lib/helper/Protractor.js +663 -627
- package/lib/helper/Puppeteer.js +1231 -1128
- package/lib/helper/REST.js +159 -68
- package/lib/helper/SoftExpectHelper.js +2 -2
- package/lib/helper/TestCafe.js +490 -484
- package/lib/helper/WebDriver.js +1297 -1156
- 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 +21 -18
- package/lib/helper/extras/Popup.js +1 -1
- package/lib/helper/extras/React.js +3 -3
- package/lib/helper/network/actions.js +14 -7
- package/lib/helper/network/utils.js +3 -2
- 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 +6 -7
- package/lib/helper.js +1 -3
- package/lib/history.js +6 -5
- package/lib/hooks.js +6 -6
- package/lib/html.js +7 -7
- package/lib/index.js +25 -41
- package/lib/interfaces/bdd.js +47 -64
- package/lib/interfaces/featureConfig.js +19 -19
- package/lib/interfaces/gherkin.js +124 -118
- 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 +32 -30
- package/lib/listener/steps.js +50 -53
- package/lib/listener/timeout.js +54 -54
- package/lib/locator.js +6 -10
- package/lib/mochaFactory.js +18 -15
- package/lib/output.js +6 -10
- package/lib/parser.js +15 -12
- package/lib/pause.js +40 -33
- package/lib/plugin/allure.js +15 -15
- package/lib/plugin/autoDelay.js +29 -37
- package/lib/plugin/autoLogin.js +70 -65
- package/lib/plugin/commentStep.js +18 -18
- package/lib/plugin/coverage.js +115 -67
- package/lib/plugin/customLocator.js +21 -20
- 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 +67 -108
- package/lib/plugin/pauseOnFail.js +11 -11
- package/lib/plugin/retryFailedStep.js +32 -39
- package/lib/plugin/retryTo.js +46 -40
- package/lib/plugin/screenshotOnFail.js +109 -87
- package/lib/plugin/selenoid.js +131 -118
- package/lib/plugin/standardActingHelpers.js +2 -8
- package/lib/plugin/stepByStepReport.js +110 -91
- package/lib/plugin/stepTimeout.js +24 -23
- package/lib/plugin/subtitles.js +34 -35
- package/lib/plugin/tryTo.js +40 -30
- package/lib/plugin/wdio.js +78 -75
- package/lib/recorder.js +14 -17
- package/lib/rerun.js +11 -10
- package/lib/scenario.js +25 -23
- package/lib/secret.js +4 -2
- package/lib/session.js +10 -10
- package/lib/step.js +12 -9
- package/lib/store.js +2 -3
- package/lib/transform.js +1 -1
- package/lib/translation.js +7 -8
- package/lib/ui.js +12 -14
- package/lib/utils.js +70 -72
- package/lib/within.js +10 -10
- package/lib/workerStorage.js +27 -25
- package/lib/workers.js +29 -32
- package/package.json +56 -57
- package/translations/de-DE.js +1 -1
- package/translations/fr-FR.js +1 -1
- package/translations/index.js +9 -13
- 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 +415 -65
- package/typings/promiseBasedTypes.d.ts +32 -0
- package/typings/types.d.ts +32 -0
- package/lib/dirname.js +0 -5
- package/lib/helper/Expect.js +0 -425
package/lib/container.js
CHANGED
|
@@ -1,26 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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';
|
|
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');
|
|
19
12
|
|
|
20
13
|
let container = {
|
|
21
14
|
helpers: {},
|
|
22
15
|
support: {},
|
|
23
16
|
plugins: {},
|
|
17
|
+
/**
|
|
18
|
+
* @type {Mocha | {}}
|
|
19
|
+
* @ignore
|
|
20
|
+
*/
|
|
24
21
|
mocha: {},
|
|
25
22
|
translation: {},
|
|
26
23
|
};
|
|
@@ -28,7 +25,7 @@ let container = {
|
|
|
28
25
|
/**
|
|
29
26
|
* Dependency Injection Container
|
|
30
27
|
*/
|
|
31
|
-
|
|
28
|
+
class Container {
|
|
32
29
|
/**
|
|
33
30
|
* Create container with all required helpers and support objects
|
|
34
31
|
*
|
|
@@ -62,7 +59,10 @@ export default class Container {
|
|
|
62
59
|
* @returns { * }
|
|
63
60
|
*/
|
|
64
61
|
static plugins(name) {
|
|
65
|
-
|
|
62
|
+
if (!name) {
|
|
63
|
+
return container.plugins;
|
|
64
|
+
}
|
|
65
|
+
return container.plugins[name];
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
/**
|
|
@@ -119,6 +119,7 @@ export default class Container {
|
|
|
119
119
|
* @param {Object<string, *>} newContainer
|
|
120
120
|
*/
|
|
121
121
|
static append(newContainer) {
|
|
122
|
+
const deepMerge = require('./utils').deepMerge;
|
|
122
123
|
container = deepMerge(container, newContainer);
|
|
123
124
|
}
|
|
124
125
|
|
|
@@ -150,6 +151,8 @@ export default class Container {
|
|
|
150
151
|
}
|
|
151
152
|
}
|
|
152
153
|
|
|
154
|
+
module.exports = Container;
|
|
155
|
+
|
|
153
156
|
function createHelpers(config) {
|
|
154
157
|
const helpers = {};
|
|
155
158
|
let moduleName;
|
|
@@ -157,31 +160,29 @@ function createHelpers(config) {
|
|
|
157
160
|
try {
|
|
158
161
|
if (config[helperName].require) {
|
|
159
162
|
if (config[helperName].require.startsWith('.')) {
|
|
160
|
-
// @ts-ignore
|
|
161
163
|
moduleName = path.resolve(global.codecept_dir, config[helperName].require); // custom helper
|
|
162
164
|
} else {
|
|
163
165
|
moduleName = config[helperName].require; // plugin helper
|
|
164
166
|
}
|
|
165
167
|
} else {
|
|
166
|
-
moduleName = `./helper/${helperName}
|
|
168
|
+
moduleName = `./helper/${helperName}`; // built-in helper
|
|
167
169
|
}
|
|
168
170
|
|
|
169
171
|
// @ts-ignore
|
|
170
172
|
let HelperClass;
|
|
171
173
|
// check if the helper is the built-in, use the require() syntax.
|
|
172
174
|
if (moduleName.startsWith('./helper/')) {
|
|
173
|
-
|
|
174
|
-
HelperClass = importSync(path.resolve(__dirname, moduleName)).default;
|
|
175
|
+
HelperClass = require(moduleName);
|
|
175
176
|
} else {
|
|
176
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.
|
|
177
|
-
HelperClass =
|
|
178
|
+
HelperClass = require(moduleName).default || require(moduleName);
|
|
178
179
|
}
|
|
179
180
|
|
|
180
|
-
if (HelperClass
|
|
181
|
+
if (HelperClass._checkRequirements) {
|
|
181
182
|
const requirements = HelperClass._checkRequirements();
|
|
182
183
|
if (requirements) {
|
|
183
184
|
let install;
|
|
184
|
-
if (
|
|
185
|
+
if (require('./utils').installedLocally()) {
|
|
185
186
|
install = `npm install --save-dev ${requirements.join(' ')}`;
|
|
186
187
|
} else {
|
|
187
188
|
console.log('WARNING: CodeceptJS is not installed locally. It is recommended to switch to local installation');
|
|
@@ -202,7 +203,7 @@ function createHelpers(config) {
|
|
|
202
203
|
return helpers;
|
|
203
204
|
}
|
|
204
205
|
|
|
205
|
-
|
|
206
|
+
function createSupportObjects(config) {
|
|
206
207
|
const objects = {};
|
|
207
208
|
|
|
208
209
|
for (const name in config) {
|
|
@@ -210,7 +211,7 @@ export function createSupportObjects(config) {
|
|
|
210
211
|
}
|
|
211
212
|
|
|
212
213
|
if (!config.I) {
|
|
213
|
-
objects.I = actor();
|
|
214
|
+
objects.I = require('./actor')();
|
|
214
215
|
|
|
215
216
|
if (container.translation.I !== 'I') {
|
|
216
217
|
objects[container.translation.I] = objects.I;
|
|
@@ -294,13 +295,12 @@ function createPlugins(config, options = {}) {
|
|
|
294
295
|
if (config[pluginName].require) {
|
|
295
296
|
module = config[pluginName].require;
|
|
296
297
|
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}`;
|
|
302
302
|
}
|
|
303
|
-
plugins[pluginName] =
|
|
303
|
+
plugins[pluginName] = require(module)(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,10 +316,8 @@ function getSupportObject(config, name) {
|
|
|
316
316
|
return module;
|
|
317
317
|
}
|
|
318
318
|
|
|
319
|
-
|
|
320
|
-
// @ts-ignore
|
|
319
|
+
function loadGherkinSteps(paths) {
|
|
321
320
|
global.Before = fn => event.dispatcher.on(event.test.started, fn);
|
|
322
|
-
// @ts-ignore
|
|
323
321
|
global.After = fn => event.dispatcher.on(event.test.finished, fn);
|
|
324
322
|
global.Fail = fn => event.dispatcher.on(event.test.failed, fn);
|
|
325
323
|
|
|
@@ -331,7 +329,6 @@ export function loadGherkinSteps(paths) {
|
|
|
331
329
|
loadSupportObject(path, `Step Definition from ${path}`);
|
|
332
330
|
}
|
|
333
331
|
} else {
|
|
334
|
-
// @ts-ignore
|
|
335
332
|
const folderPath = paths.startsWith('.') ? path.join(global.codecept_dir, paths) : '';
|
|
336
333
|
if (folderPath !== '') {
|
|
337
334
|
glob.sync(folderPath).forEach((file) => {
|
|
@@ -340,20 +337,17 @@ export function loadGherkinSteps(paths) {
|
|
|
340
337
|
}
|
|
341
338
|
}
|
|
342
339
|
|
|
343
|
-
// @ts-ignore
|
|
344
340
|
delete global.Before;
|
|
345
|
-
// @ts-ignore
|
|
346
341
|
delete global.After;
|
|
347
342
|
delete global.Fail;
|
|
348
343
|
}
|
|
349
344
|
|
|
350
345
|
function loadSupportObject(modulePath, supportObjectName) {
|
|
351
346
|
if (modulePath.charAt(0) === '.') {
|
|
352
|
-
// @ts-ignore
|
|
353
347
|
modulePath = path.join(global.codecept_dir, modulePath);
|
|
354
348
|
}
|
|
355
349
|
try {
|
|
356
|
-
const obj =
|
|
350
|
+
const obj = require(modulePath);
|
|
357
351
|
|
|
358
352
|
if (typeof obj === 'function') {
|
|
359
353
|
const fobj = obj();
|
|
@@ -411,7 +405,7 @@ function getObjectMethods(obj) {
|
|
|
411
405
|
const methodsSet = new Set();
|
|
412
406
|
let protoObj = Reflect.getPrototypeOf(obj);
|
|
413
407
|
do {
|
|
414
|
-
if (protoObj
|
|
408
|
+
if (protoObj.constructor.prototype !== Object.prototype) {
|
|
415
409
|
const keys = Reflect.ownKeys(protoObj);
|
|
416
410
|
keys.forEach(k => methodsSet.add(k));
|
|
417
411
|
}
|
|
@@ -430,18 +424,16 @@ function loadTranslation(locale, vocabularies) {
|
|
|
430
424
|
}
|
|
431
425
|
|
|
432
426
|
let translation;
|
|
433
|
-
locale = locale.replace('-', '_');
|
|
434
427
|
|
|
435
428
|
// check if it is a known translation
|
|
436
429
|
if (Translation.langs[locale]) {
|
|
437
430
|
translation = new Translation(Translation.langs[locale]);
|
|
438
|
-
} else
|
|
431
|
+
} else if (fileExists(path.join(global.codecept_dir, locale))) {
|
|
432
|
+
// get from a provided file instead
|
|
433
|
+
translation = Translation.createDefault();
|
|
434
|
+
translation.loadVocabulary(locale);
|
|
435
|
+
} else {
|
|
439
436
|
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
|
-
}
|
|
445
437
|
}
|
|
446
438
|
|
|
447
439
|
vocabularies.forEach(v => translation.loadVocabulary(v));
|
package/lib/data/context.js
CHANGED
|
@@ -1,128 +1,129 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
const { isGenerator } = require('../utils')
|
|
2
|
+
const DataTable = require('./table')
|
|
3
|
+
const DataScenarioConfig = require('./dataScenarioConfig')
|
|
4
|
+
const Secret = require('../secret')
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
module.exports = function (context) {
|
|
6
7
|
context.Data = function (dataTable) {
|
|
7
|
-
const data = detectDataType(dataTable)
|
|
8
|
+
const data = detectDataType(dataTable)
|
|
8
9
|
return {
|
|
9
10
|
Scenario(title, opts, fn) {
|
|
10
|
-
const scenarios = []
|
|
11
|
+
const scenarios = []
|
|
11
12
|
if (typeof opts === 'function' && !fn) {
|
|
12
|
-
fn = opts
|
|
13
|
-
opts = {}
|
|
13
|
+
fn = opts
|
|
14
|
+
opts = {}
|
|
14
15
|
}
|
|
15
|
-
opts.data = data.map(dataRow => dataRow.data)
|
|
16
|
+
opts.data = data.map((dataRow) => dataRow.data)
|
|
16
17
|
data.forEach((dataRow) => {
|
|
17
|
-
const dataTitle = replaceTitle(title, dataRow)
|
|
18
|
+
const dataTitle = replaceTitle(title, dataRow)
|
|
18
19
|
if (dataRow.skip) {
|
|
19
|
-
context.xScenario(dataTitle)
|
|
20
|
+
context.xScenario(dataTitle)
|
|
20
21
|
} else {
|
|
21
|
-
scenarios.push(context.Scenario(dataTitle, opts, fn)
|
|
22
|
-
.inject({ current: dataRow.data }));
|
|
22
|
+
scenarios.push(context.Scenario(dataTitle, opts, fn).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(dataRow => dataRow.data)
|
|
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)
|
|
42
|
-
.inject({ current: dataRow.data }));
|
|
41
|
+
scenarios.push(context.Scenario.only(dataTitle, opts, fn).inject({ current: dataRow.data }))
|
|
43
42
|
}
|
|
44
|
-
})
|
|
45
|
-
maskSecretInTitle(scenarios)
|
|
46
|
-
return new DataScenarioConfig(scenarios)
|
|
43
|
+
})
|
|
44
|
+
maskSecretInTitle(scenarios)
|
|
45
|
+
return new DataScenarioConfig(scenarios)
|
|
47
46
|
},
|
|
48
47
|
},
|
|
49
|
-
}
|
|
50
|
-
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
51
50
|
|
|
52
51
|
context.xData = function (dataTable) {
|
|
53
|
-
const data = detectDataType(dataTable)
|
|
52
|
+
const data = detectDataType(dataTable)
|
|
54
53
|
return {
|
|
55
54
|
Scenario: (title) => {
|
|
56
55
|
data.forEach((dataRow) => {
|
|
57
|
-
const dataTitle = replaceTitle(title, dataRow)
|
|
58
|
-
context.xScenario(dataTitle)
|
|
59
|
-
})
|
|
60
|
-
return new DataScenarioConfig([])
|
|
56
|
+
const dataTitle = replaceTitle(title, dataRow)
|
|
57
|
+
context.xScenario(dataTitle)
|
|
58
|
+
})
|
|
59
|
+
return new DataScenarioConfig([])
|
|
61
60
|
},
|
|
62
|
-
}
|
|
63
|
-
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
64
63
|
}
|
|
65
64
|
|
|
66
65
|
function replaceTitle(title, dataRow) {
|
|
67
66
|
if (typeof dataRow.data !== 'object') {
|
|
68
|
-
return `${title} | {${JSON.stringify(dataRow.data)}}
|
|
67
|
+
return `${title} | {${JSON.stringify(dataRow.data)}}`
|
|
69
68
|
}
|
|
70
69
|
|
|
71
70
|
// if `dataRow` is object and has own `toString()` method,
|
|
72
71
|
// it should be printed
|
|
73
|
-
if (
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
if (
|
|
73
|
+
Object.prototype.toString.call(dataRow.data) === Object().toString() &&
|
|
74
|
+
dataRow.data.toString() !== Object().toString()
|
|
75
|
+
) {
|
|
76
|
+
return `${title} | ${dataRow.data}`
|
|
76
77
|
}
|
|
77
78
|
|
|
78
|
-
return `${title} | ${JSON.stringify(dataRow.data)}
|
|
79
|
+
return `${title} | ${JSON.stringify(dataRow.data)}`
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
function isTableDataRow(row) {
|
|
82
|
-
const has = Object.prototype.hasOwnProperty
|
|
83
|
-
return has.call(row, 'data') && has.call(row, 'skip')
|
|
83
|
+
const has = Object.prototype.hasOwnProperty
|
|
84
|
+
return has.call(row, 'data') && has.call(row, 'skip')
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
function detectDataType(dataTable) {
|
|
87
88
|
if (dataTable instanceof DataTable) {
|
|
88
|
-
return dataTable.rows
|
|
89
|
+
return dataTable.rows
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
if (isGenerator(dataTable)) {
|
|
92
|
-
const data = []
|
|
93
|
+
const data = []
|
|
93
94
|
for (const dataRow of dataTable()) {
|
|
94
95
|
data.push({
|
|
95
96
|
data: dataRow,
|
|
96
|
-
})
|
|
97
|
+
})
|
|
97
98
|
}
|
|
98
|
-
return data
|
|
99
|
+
return data
|
|
99
100
|
}
|
|
100
101
|
if (typeof dataTable === 'function') {
|
|
101
|
-
return dataTable()
|
|
102
|
+
return dataTable()
|
|
102
103
|
}
|
|
103
104
|
if (Array.isArray(dataTable)) {
|
|
104
105
|
return dataTable.map((item) => {
|
|
105
106
|
if (isTableDataRow(item)) {
|
|
106
|
-
return item
|
|
107
|
+
return item
|
|
107
108
|
}
|
|
108
109
|
return {
|
|
109
110
|
data: item,
|
|
110
111
|
skip: false,
|
|
111
|
-
}
|
|
112
|
-
})
|
|
112
|
+
}
|
|
113
|
+
})
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
throw new Error('Invalid data type. Data accepts either: DataTable || generator || Array || function')
|
|
116
|
+
throw new Error('Invalid data type. Data accepts either: DataTable || generator || Array || function')
|
|
116
117
|
}
|
|
117
118
|
|
|
118
119
|
function maskSecretInTitle(scenarios) {
|
|
119
|
-
scenarios.forEach(scenario => {
|
|
120
|
-
const res = []
|
|
120
|
+
scenarios.forEach((scenario) => {
|
|
121
|
+
const res = []
|
|
121
122
|
|
|
122
|
-
scenario.test.title.split(',').forEach(item => {
|
|
123
|
-
res.push(item.replace(/{"_secret":"(.*)"}/, '"*****"'))
|
|
124
|
-
})
|
|
123
|
+
scenario.test.title.split(',').forEach((item) => {
|
|
124
|
+
res.push(item.replace(/{"_secret":"(.*)"}/, '"*****"'))
|
|
125
|
+
})
|
|
125
126
|
|
|
126
|
-
scenario.test.title = res.join(',')
|
|
127
|
-
})
|
|
127
|
+
scenario.test.title = res.join(',')
|
|
128
|
+
})
|
|
128
129
|
}
|
|
@@ -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(scenario => scenario.throws(err))
|
|
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(scenario => scenario.fails())
|
|
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(scenario => scenario.retry(retries))
|
|
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(scenario => scenario.timeout(timeout))
|
|
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(scenario => scenario.config(helper, obj))
|
|
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(scenario => scenario.tag(tagName))
|
|
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(scenario => scenario.inject(obj))
|
|
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(scenario => scenario.injectDependencies(dependencies))
|
|
80
|
-
return this
|
|
79
|
+
this.scenarios.forEach((scenario) => scenario.injectDependencies(dependencies))
|
|
80
|
+
return this
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
module.exports = 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) => r[header[index]] = cell)
|
|
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
|
+
module.exports = DataTableArgument
|