codeceptjs 3.0.3 → 3.0.7
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/CHANGELOG.md +114 -18
- package/bin/codecept.js +1 -0
- package/docs/basics.md +2 -2
- package/docs/bdd.md +12 -1
- package/docs/build/Appium.js +2 -1
- package/docs/build/GraphQL.js +9 -10
- package/docs/build/Nightmare.js +4 -5
- package/docs/build/Playwright.js +164 -37
- package/docs/build/Protractor.js +1 -1
- package/docs/build/Puppeteer.js +1 -1
- package/docs/build/REST.js +24 -4
- package/docs/build/TestCafe.js +1 -1
- package/docs/build/WebDriver.js +85 -17
- package/docs/changelog.md +114 -18
- package/docs/data.md +5 -5
- package/docs/detox.md +2 -2
- package/docs/docker.md +11 -11
- package/docs/email.md +8 -8
- package/docs/helpers/Appium.md +1 -1
- package/docs/helpers/Nightmare.md +4 -5
- package/docs/helpers/Playwright.md +94 -64
- package/docs/helpers/Protractor.md +1 -1
- package/docs/helpers/Puppeteer.md +1 -1
- package/docs/helpers/REST.md +9 -0
- package/docs/helpers/TestCafe.md +1 -1
- package/docs/helpers/WebDriver.md +2 -1
- package/docs/locators.md +29 -2
- package/docs/mobile-react-native-locators.md +2 -2
- package/docs/mobile.md +3 -3
- package/docs/nightmare.md +0 -5
- package/docs/pageobjects.md +3 -1
- package/docs/parallel.md +35 -10
- package/docs/playwright.md +55 -8
- package/docs/plugins.md +73 -29
- package/docs/reports.md +8 -7
- package/docs/typescript.md +47 -5
- package/docs/webapi/fillField.mustache +1 -1
- package/lib/cli.js +25 -10
- package/lib/codecept.js +9 -1
- package/lib/command/interactive.js +10 -9
- package/lib/command/run.js +1 -1
- package/lib/command/workers/runTests.js +11 -6
- package/lib/config.js +8 -3
- package/lib/event.js +2 -0
- package/lib/helper/Appium.js +1 -0
- package/lib/helper/GraphQL.js +9 -10
- package/lib/helper/Nightmare.js +1 -1
- package/lib/helper/Playwright.js +131 -38
- package/lib/helper/REST.js +24 -4
- package/lib/helper/WebDriver.js +84 -16
- package/lib/interfaces/gherkin.js +11 -4
- package/lib/output.js +7 -4
- package/lib/plugin/allure.js +3 -7
- package/lib/plugin/fakerTransform.js +51 -0
- package/lib/plugin/screenshotOnFail.js +6 -2
- package/lib/recorder.js +9 -0
- package/lib/step.js +2 -1
- package/lib/transform.js +26 -0
- package/lib/ui.js +6 -2
- package/lib/within.js +1 -1
- package/lib/workers.js +39 -25
- package/package.json +14 -9
- package/typings/index.d.ts +49 -21
- package/typings/types.d.ts +72 -26
package/lib/helper/WebDriver.js
CHANGED
|
@@ -396,6 +396,7 @@ class WebDriver extends Helper {
|
|
|
396
396
|
this.isRunning = false;
|
|
397
397
|
this.sessionWindows = {};
|
|
398
398
|
this.activeSessionName = '';
|
|
399
|
+
this.customLocatorStrategies = config.customLocatorStrategies;
|
|
399
400
|
|
|
400
401
|
this._setConfig(config);
|
|
401
402
|
|
|
@@ -503,6 +504,33 @@ class WebDriver extends Helper {
|
|
|
503
504
|
}
|
|
504
505
|
}
|
|
505
506
|
|
|
507
|
+
_lookupCustomLocator(customStrategy) {
|
|
508
|
+
if (typeof (this.customLocatorStrategies) !== 'object') {
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
const strategy = this.customLocatorStrategies[customStrategy];
|
|
512
|
+
return typeof (strategy) === 'function' ? strategy : null;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
_isCustomLocator(locator) {
|
|
516
|
+
const locatorObj = new Locator(locator);
|
|
517
|
+
if (locatorObj.isCustom()) {
|
|
518
|
+
const customLocator = this._lookupCustomLocator(locatorObj.type);
|
|
519
|
+
if (customLocator) {
|
|
520
|
+
return true;
|
|
521
|
+
}
|
|
522
|
+
throw new Error('Please define "customLocatorStrategies" as an Object and the Locator Strategy as a "function".');
|
|
523
|
+
}
|
|
524
|
+
return false;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
async _res(locator) {
|
|
528
|
+
const res = (this._isShadowLocator(locator) || this._isCustomLocator(locator))
|
|
529
|
+
? await this._locate(locator)
|
|
530
|
+
: await this.$$(withStrictLocator(locator));
|
|
531
|
+
return res;
|
|
532
|
+
}
|
|
533
|
+
|
|
506
534
|
async _startBrowser() {
|
|
507
535
|
try {
|
|
508
536
|
if (this.options.multiremote) {
|
|
@@ -530,9 +558,22 @@ class WebDriver extends Helper {
|
|
|
530
558
|
await this._resizeWindowIfNeeded(this.browser, this.options.windowSize);
|
|
531
559
|
|
|
532
560
|
this.$$ = this.browser.$$.bind(this.browser);
|
|
561
|
+
|
|
562
|
+
if (this._isCustomLocatorStrategyDefined()) {
|
|
563
|
+
Object.keys(this.customLocatorStrategies).forEach(async (customLocator) => {
|
|
564
|
+
this.debugSection('Weddriver', `adding custom locator strategy: ${customLocator}`);
|
|
565
|
+
const locatorFunction = this._lookupCustomLocator(customLocator);
|
|
566
|
+
this.browser.addLocatorStrategy(customLocator, locatorFunction);
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
|
|
533
570
|
return this.browser;
|
|
534
571
|
}
|
|
535
572
|
|
|
573
|
+
_isCustomLocatorStrategyDefined() {
|
|
574
|
+
return this.customLocatorStrategies && Object.keys(this.customLocatorStrategies).length;
|
|
575
|
+
}
|
|
576
|
+
|
|
536
577
|
async _stopBrowser() {
|
|
537
578
|
if (this.browser && this.isRunning) await this.browser.deleteSession();
|
|
538
579
|
}
|
|
@@ -718,7 +759,7 @@ class WebDriver extends Helper {
|
|
|
718
759
|
* @param {object} locator
|
|
719
760
|
*/
|
|
720
761
|
async _smartWait(locator) {
|
|
721
|
-
this.debugSection(`SmartWait (${this.options.smartWait}ms)`, `Locating ${locator} in ${this.options.smartWait}`);
|
|
762
|
+
this.debugSection(`SmartWait (${this.options.smartWait}ms)`, `Locating ${JSON.stringify(locator)} in ${this.options.smartWait}`);
|
|
722
763
|
await this.defineTimeout({ implicit: this.options.smartWait });
|
|
723
764
|
}
|
|
724
765
|
|
|
@@ -755,17 +796,34 @@ class WebDriver extends Helper {
|
|
|
755
796
|
}
|
|
756
797
|
|
|
757
798
|
if (!this.options.smartWait || !smartWait) {
|
|
799
|
+
if (this._isCustomLocator(locator)) {
|
|
800
|
+
const locatorObj = new Locator(locator);
|
|
801
|
+
return this.browser.custom$$(locatorObj.type, locatorObj.value);
|
|
802
|
+
}
|
|
803
|
+
|
|
758
804
|
const els = await this.$$(withStrictLocator(locator));
|
|
759
805
|
return els;
|
|
760
806
|
}
|
|
761
807
|
|
|
762
808
|
await this._smartWait(locator);
|
|
763
809
|
|
|
810
|
+
if (this._isCustomLocator(locator)) {
|
|
811
|
+
const locatorObj = new Locator(locator);
|
|
812
|
+
return this.browser.custom$$(locatorObj.type, locatorObj.value);
|
|
813
|
+
}
|
|
814
|
+
|
|
764
815
|
const els = await this.$$(withStrictLocator(locator));
|
|
765
816
|
await this.defineTimeout({ implicit: 0 });
|
|
766
817
|
return els;
|
|
767
818
|
}
|
|
768
819
|
|
|
820
|
+
_grabCustomLocator(locator) {
|
|
821
|
+
if (typeof locator === 'string') {
|
|
822
|
+
locator = new Locator(locator);
|
|
823
|
+
}
|
|
824
|
+
return locator.value ? locator.value : locator.custom;
|
|
825
|
+
}
|
|
826
|
+
|
|
769
827
|
/**
|
|
770
828
|
* Find a checkbox by providing human readable text:
|
|
771
829
|
*
|
|
@@ -962,6 +1020,7 @@ class WebDriver extends Helper {
|
|
|
962
1020
|
/**
|
|
963
1021
|
* {{> fillField }}
|
|
964
1022
|
* {{ react }}
|
|
1023
|
+
* {{ custom }}
|
|
965
1024
|
*
|
|
966
1025
|
*/
|
|
967
1026
|
async fillField(field, value) {
|
|
@@ -1335,7 +1394,7 @@ class WebDriver extends Helper {
|
|
|
1335
1394
|
*
|
|
1336
1395
|
*/
|
|
1337
1396
|
async seeElementInDOM(locator) {
|
|
1338
|
-
const res = await this
|
|
1397
|
+
const res = await this._res(locator);
|
|
1339
1398
|
return empty('elements').negate(res);
|
|
1340
1399
|
}
|
|
1341
1400
|
|
|
@@ -1344,7 +1403,7 @@ class WebDriver extends Helper {
|
|
|
1344
1403
|
*
|
|
1345
1404
|
*/
|
|
1346
1405
|
async dontSeeElementInDOM(locator) {
|
|
1347
|
-
const res = await this
|
|
1406
|
+
const res = await this._res(locator);
|
|
1348
1407
|
return empty('elements').assert(res);
|
|
1349
1408
|
}
|
|
1350
1409
|
|
|
@@ -1370,7 +1429,7 @@ class WebDriver extends Helper {
|
|
|
1370
1429
|
*/
|
|
1371
1430
|
async grabBrowserLogs() {
|
|
1372
1431
|
if (this.browser.isW3C) {
|
|
1373
|
-
this.debug('Logs not
|
|
1432
|
+
this.debug('Logs not available in W3C specification');
|
|
1374
1433
|
return;
|
|
1375
1434
|
}
|
|
1376
1435
|
return this.browser.getLogs('browser');
|
|
@@ -1991,7 +2050,7 @@ class WebDriver extends Helper {
|
|
|
1991
2050
|
}, aSec * 1000, `element (${new Locator(locator)}) still not enabled after ${aSec} sec`);
|
|
1992
2051
|
}
|
|
1993
2052
|
return this.browser.waitUntil(async () => {
|
|
1994
|
-
const res = await this
|
|
2053
|
+
const res = await this._res(locator);
|
|
1995
2054
|
if (!res || res.length === 0) {
|
|
1996
2055
|
return false;
|
|
1997
2056
|
}
|
|
@@ -2018,7 +2077,7 @@ class WebDriver extends Helper {
|
|
|
2018
2077
|
}, aSec * 1000, `element (${locator}) still not present on page after ${aSec} sec`);
|
|
2019
2078
|
}
|
|
2020
2079
|
return this.browser.waitUntil(async () => {
|
|
2021
|
-
const res = await this
|
|
2080
|
+
const res = await this._res(locator);
|
|
2022
2081
|
return res && res.length;
|
|
2023
2082
|
}, { timeout: aSec * 1000, timeoutMsg: `element (${locator}) still not present on page after ${aSec} sec` });
|
|
2024
2083
|
}
|
|
@@ -2188,9 +2247,7 @@ class WebDriver extends Helper {
|
|
|
2188
2247
|
}, aSec * 1000, `element (${new Locator(locator)}) still not visible after ${aSec} sec`);
|
|
2189
2248
|
}
|
|
2190
2249
|
return this.browser.waitUntil(async () => {
|
|
2191
|
-
const res =
|
|
2192
|
-
? await this._locate(withStrictLocator(locator))
|
|
2193
|
-
: await this.$$(withStrictLocator(locator));
|
|
2250
|
+
const res = await this._res(locator);
|
|
2194
2251
|
if (!res || res.length === 0) return false;
|
|
2195
2252
|
const selected = await forEachAsync(res, async el => el.isDisplayed());
|
|
2196
2253
|
if (Array.isArray(selected)) {
|
|
@@ -2217,7 +2274,7 @@ class WebDriver extends Helper {
|
|
|
2217
2274
|
}, aSec * 1000, `The number of elements (${new Locator(locator)}) is not ${num} after ${aSec} sec`);
|
|
2218
2275
|
}
|
|
2219
2276
|
return this.browser.waitUntil(async () => {
|
|
2220
|
-
const res = await this
|
|
2277
|
+
const res = await this._res(locator);
|
|
2221
2278
|
if (!res || res.length === 0) return false;
|
|
2222
2279
|
let selected = await forEachAsync(res, async el => el.isDisplayed());
|
|
2223
2280
|
|
|
@@ -2241,7 +2298,7 @@ class WebDriver extends Helper {
|
|
|
2241
2298
|
}, aSec * 1000, `element (${new Locator(locator)}) still visible after ${aSec} sec`);
|
|
2242
2299
|
}
|
|
2243
2300
|
return this.browser.waitUntil(async () => {
|
|
2244
|
-
const res = await this
|
|
2301
|
+
const res = await this._res(locator);
|
|
2245
2302
|
if (!res || res.length === 0) return true;
|
|
2246
2303
|
const selected = await forEachAsync(res, async el => el.isDisplayed());
|
|
2247
2304
|
return !selected.length;
|
|
@@ -2262,7 +2319,7 @@ class WebDriver extends Helper {
|
|
|
2262
2319
|
const aSec = sec || this.options.waitForTimeout;
|
|
2263
2320
|
if (isWebDriver5()) {
|
|
2264
2321
|
return this.browser.waitUntil(async () => {
|
|
2265
|
-
const res = await this
|
|
2322
|
+
const res = await this._res(locator);
|
|
2266
2323
|
if (!res || res.length === 0) {
|
|
2267
2324
|
return true;
|
|
2268
2325
|
}
|
|
@@ -2270,7 +2327,7 @@ class WebDriver extends Helper {
|
|
|
2270
2327
|
}, aSec * 1000, `element (${new Locator(locator)}) still on page after ${aSec} sec`);
|
|
2271
2328
|
}
|
|
2272
2329
|
return this.browser.waitUntil(async () => {
|
|
2273
|
-
const res = await this
|
|
2330
|
+
const res = await this._res(locator);
|
|
2274
2331
|
if (!res || res.length === 0) {
|
|
2275
2332
|
return true;
|
|
2276
2333
|
}
|
|
@@ -2543,12 +2600,9 @@ async function proceedSee(assertType, text, context, strict = false) {
|
|
|
2543
2600
|
}
|
|
2544
2601
|
|
|
2545
2602
|
const smartWaitEnabled = assertType === 'assert';
|
|
2546
|
-
|
|
2547
2603
|
const res = await this._locate(withStrictLocator(context), smartWaitEnabled);
|
|
2548
2604
|
assertElementExists(res, context);
|
|
2549
|
-
|
|
2550
2605
|
const selected = await forEachAsync(res, async el => this.browser.getElementText(getElementId(el)));
|
|
2551
|
-
|
|
2552
2606
|
if (strict) {
|
|
2553
2607
|
if (Array.isArray(selected) && selected.length !== 0) {
|
|
2554
2608
|
return selected.map(elText => equals(description)[assertType](text, elText));
|
|
@@ -2616,6 +2670,11 @@ async function filterAsync(array, callback) {
|
|
|
2616
2670
|
|
|
2617
2671
|
async function findClickable(locator, locateFn) {
|
|
2618
2672
|
locator = new Locator(locator);
|
|
2673
|
+
|
|
2674
|
+
if (this._isCustomLocator(locator)) {
|
|
2675
|
+
return locateFn(locator.value);
|
|
2676
|
+
}
|
|
2677
|
+
|
|
2619
2678
|
if (locator.isAccessibilityId() && !this.isWeb) return locateFn(locator, true);
|
|
2620
2679
|
if (!locator.isFuzzy()) return locateFn(locator, true);
|
|
2621
2680
|
|
|
@@ -2637,6 +2696,10 @@ async function findClickable(locator, locateFn) {
|
|
|
2637
2696
|
async function findFields(locator) {
|
|
2638
2697
|
locator = new Locator(locator);
|
|
2639
2698
|
|
|
2699
|
+
if (this._isCustomLocator(locator)) {
|
|
2700
|
+
return this._locate(locator);
|
|
2701
|
+
}
|
|
2702
|
+
|
|
2640
2703
|
if (locator.isAccessibilityId() && !this.isWeb) return this._locate(locator, true);
|
|
2641
2704
|
if (!locator.isFuzzy()) return this._locate(locator, true);
|
|
2642
2705
|
|
|
@@ -2742,6 +2805,10 @@ async function findCheckable(locator, locateFn) {
|
|
|
2742
2805
|
let els;
|
|
2743
2806
|
locator = new Locator(locator);
|
|
2744
2807
|
|
|
2808
|
+
if (this._isCustomLocator(locator)) {
|
|
2809
|
+
return locateFn(locator.value);
|
|
2810
|
+
}
|
|
2811
|
+
|
|
2745
2812
|
if (locator.isAccessibilityId() && !this.isWeb) return locateFn(locator, true);
|
|
2746
2813
|
if (!locator.isFuzzy()) return locateFn(locator, true);
|
|
2747
2814
|
|
|
@@ -2787,6 +2854,7 @@ function getElementId(el) {
|
|
|
2787
2854
|
if (el.ELEMENT) {
|
|
2788
2855
|
return el.ELEMENT;
|
|
2789
2856
|
}
|
|
2857
|
+
|
|
2790
2858
|
return null;
|
|
2791
2859
|
}
|
|
2792
2860
|
|
|
@@ -6,6 +6,7 @@ const event = require('../event');
|
|
|
6
6
|
const scenario = require('../scenario');
|
|
7
7
|
const Step = require('../step');
|
|
8
8
|
const DataTableArgument = require('../data/dataTableArgument');
|
|
9
|
+
const transform = require('../transform');
|
|
9
10
|
|
|
10
11
|
const parser = new Parser();
|
|
11
12
|
parser.stopAtFirstError = false;
|
|
@@ -31,7 +32,13 @@ module.exports = (text) => {
|
|
|
31
32
|
const metaStep = new Step.MetaStep(null, step.text);
|
|
32
33
|
metaStep.actor = step.keyword.trim();
|
|
33
34
|
const setMetaStep = (step) => {
|
|
34
|
-
if (step.metaStep)
|
|
35
|
+
if (step.metaStep) {
|
|
36
|
+
if (step.metaStep === metaStep) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
setMetaStep(step.metaStep);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
35
42
|
step.metaStep = metaStep;
|
|
36
43
|
};
|
|
37
44
|
const fn = matchStep(step.text);
|
|
@@ -46,14 +53,14 @@ module.exports = (text) => {
|
|
|
46
53
|
step.startTime = Date.now();
|
|
47
54
|
step.match = fn.line;
|
|
48
55
|
event.emit(event.bddStep.before, step);
|
|
49
|
-
event.dispatcher.
|
|
56
|
+
event.dispatcher.prependListener(event.step.before, setMetaStep);
|
|
50
57
|
try {
|
|
51
58
|
await fn(...fn.params);
|
|
52
59
|
step.status = 'passed';
|
|
53
60
|
} catch (err) {
|
|
54
61
|
step.status = 'failed';
|
|
55
62
|
step.err = err;
|
|
56
|
-
|
|
63
|
+
throw err;
|
|
57
64
|
} finally {
|
|
58
65
|
step.endTime = Date.now();
|
|
59
66
|
event.dispatcher.removeListener(event.step.before, setMetaStep);
|
|
@@ -75,7 +82,7 @@ module.exports = (text) => {
|
|
|
75
82
|
const current = {};
|
|
76
83
|
for (const index in example.cells) {
|
|
77
84
|
const placeholder = fields[index];
|
|
78
|
-
const value = example.cells[index].value;
|
|
85
|
+
const value = transform('gherkin.examples', example.cells[index].value);
|
|
79
86
|
current[placeholder] = value;
|
|
80
87
|
exampleSteps = exampleSteps.map((step) => {
|
|
81
88
|
step = { ...step };
|
package/lib/output.js
CHANGED
|
@@ -39,12 +39,12 @@ module.exports = {
|
|
|
39
39
|
/**
|
|
40
40
|
* Print information for a process
|
|
41
41
|
* Used in multiple-run
|
|
42
|
-
* @param {string} process
|
|
42
|
+
* @param {string | null} process
|
|
43
43
|
* @returns {string}
|
|
44
44
|
*/
|
|
45
45
|
process(process) {
|
|
46
46
|
if (process === null) return outputProcess = '';
|
|
47
|
-
if (process) outputProcess = `[${process}]`;
|
|
47
|
+
if (process) outputProcess = String(process).length === 1 ? `[0${process}]` : `[${process}]`;
|
|
48
48
|
return outputProcess;
|
|
49
49
|
},
|
|
50
50
|
|
|
@@ -102,14 +102,14 @@ module.exports = {
|
|
|
102
102
|
if (!step) return;
|
|
103
103
|
// Avoid to print non-gherkin steps, when gherkin is running for --steps mode
|
|
104
104
|
if (outputLevel === 1) {
|
|
105
|
-
if (
|
|
105
|
+
if (step.hasBDDAncestor()) {
|
|
106
106
|
return;
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
let stepLine = step.toString();
|
|
111
111
|
if (step.metaStep && outputLevel >= 1) {
|
|
112
|
-
this.stepShift += 2;
|
|
112
|
+
// this.stepShift += 2;
|
|
113
113
|
stepLine = colors.green(truncate(stepLine, this.spaceShift));
|
|
114
114
|
}
|
|
115
115
|
if (step.comment) {
|
|
@@ -190,6 +190,9 @@ module.exports = {
|
|
|
190
190
|
* @param {string} [color]
|
|
191
191
|
*/
|
|
192
192
|
say(message, color = 'cyan') {
|
|
193
|
+
if (colors[color] === undefined) {
|
|
194
|
+
color = 'cyan';
|
|
195
|
+
}
|
|
193
196
|
if (outputLevel >= 1) print(` ${colors[color].bold(message)}`);
|
|
194
197
|
},
|
|
195
198
|
|
package/lib/plugin/allure.js
CHANGED
|
@@ -261,13 +261,9 @@ module.exports = (config) => {
|
|
|
261
261
|
if (isHookSteps === false) {
|
|
262
262
|
startMetaStep(step.metaStep);
|
|
263
263
|
if (currentStep !== step) {
|
|
264
|
-
//
|
|
265
|
-
//
|
|
266
|
-
|
|
267
|
-
// generate the report
|
|
268
|
-
if (step.actor.includes('\u001b[36m')) {
|
|
269
|
-
step.actor = step.actor.replace('\u001b[36m', '').replace('\u001b[39m', '');
|
|
270
|
-
}
|
|
264
|
+
// In multi-session scenarios, actors' names will be highlighted with ANSI
|
|
265
|
+
// escape sequences which are invalid XML values
|
|
266
|
+
step.actor = step.actor.replace(ansiRegExp(), '');
|
|
271
267
|
reporter.startStep(step.toString());
|
|
272
268
|
currentStep = step;
|
|
273
269
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const faker = require('faker');
|
|
2
|
+
const transform = require('../transform');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Use the [faker.js](https://www.npmjs.com/package/faker) package to generate fake data inside examples on your gherkin tests
|
|
6
|
+
*
|
|
7
|
+
* 
|
|
8
|
+
*
|
|
9
|
+
* #### Usage
|
|
10
|
+
*
|
|
11
|
+
* To start please install `faker.js` package
|
|
12
|
+
*
|
|
13
|
+
* ```
|
|
14
|
+
* npm install -D faker
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* ```
|
|
18
|
+
* yarn add -D faker
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* Add this plugin to config file:
|
|
22
|
+
*
|
|
23
|
+
* ```js
|
|
24
|
+
* plugins: {
|
|
25
|
+
* fakerTransform: {
|
|
26
|
+
* enabled: true
|
|
27
|
+
* }
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* Add the faker API using a mustache string format inside examples tables in your gherkin scenario outline
|
|
32
|
+
*
|
|
33
|
+
* ```feature
|
|
34
|
+
* Scenario Outline: ...
|
|
35
|
+
* Given ...
|
|
36
|
+
* When ...
|
|
37
|
+
* Then ...
|
|
38
|
+
* Examples:
|
|
39
|
+
* | productName | customer | email | anythingMore |
|
|
40
|
+
* | {{commerce.product}} | Dr. {{name.findName}} | {{internet.email}} | staticData |
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
*/
|
|
44
|
+
module.exports = function (config) {
|
|
45
|
+
transform.addTransformerBeforeAll('gherkin.examples', (value) => {
|
|
46
|
+
if (typeof value === 'string' && value.length > 0) {
|
|
47
|
+
return faker.fake(value);
|
|
48
|
+
}
|
|
49
|
+
return value;
|
|
50
|
+
});
|
|
51
|
+
};
|
|
@@ -99,8 +99,7 @@ module.exports = function (config) {
|
|
|
99
99
|
await helper.saveScreenshot(fileName, options.fullPageScreenshots);
|
|
100
100
|
|
|
101
101
|
test.artifacts.screenshot = path.join(global.output_dir, fileName);
|
|
102
|
-
|
|
103
|
-
if (Container.mocha().options.reporterOptions['mocha-junit-reporter'] && Container.mocha().options.reporterOptions['mocha-junit-reporter'].attachments) {
|
|
102
|
+
if (Container.mocha().options.reporterOptions['mocha-junit-reporter'] && Container.mocha().options.reporterOptions['mocha-junit-reporter'].options.attachments) {
|
|
104
103
|
test.attachments = [path.join(global.output_dir, fileName)];
|
|
105
104
|
}
|
|
106
105
|
|
|
@@ -108,6 +107,11 @@ module.exports = function (config) {
|
|
|
108
107
|
if (allureReporter) {
|
|
109
108
|
allureReporter.addAttachment('Last Seen Screenshot', fs.readFileSync(path.join(global.output_dir, fileName)), 'image/png');
|
|
110
109
|
}
|
|
110
|
+
|
|
111
|
+
const cucumberReporter = Container.plugins('cucumberJsonReporter');
|
|
112
|
+
if (cucumberReporter) {
|
|
113
|
+
cucumberReporter.addScreenshot(test.artifacts.screenshot);
|
|
114
|
+
}
|
|
111
115
|
} catch (err) {
|
|
112
116
|
output.plugin(err);
|
|
113
117
|
if (
|
package/lib/recorder.js
CHANGED
|
@@ -321,6 +321,15 @@ module.exports = {
|
|
|
321
321
|
return tasks.join('\n');
|
|
322
322
|
},
|
|
323
323
|
|
|
324
|
+
/**
|
|
325
|
+
* Get the queue id
|
|
326
|
+
* @return {number}
|
|
327
|
+
* @inner
|
|
328
|
+
*/
|
|
329
|
+
getQueueId() {
|
|
330
|
+
return queueId;
|
|
331
|
+
},
|
|
332
|
+
|
|
324
333
|
/**
|
|
325
334
|
* Get a state of current queue and tasks
|
|
326
335
|
* @return {string}
|
package/lib/step.js
CHANGED
|
@@ -208,9 +208,10 @@ class MetaStep extends Step {
|
|
|
208
208
|
let result;
|
|
209
209
|
|
|
210
210
|
const registerStep = (step) => {
|
|
211
|
+
this.metaStep = null;
|
|
211
212
|
step.metaStep = this;
|
|
212
213
|
};
|
|
213
|
-
event.dispatcher.
|
|
214
|
+
event.dispatcher.prependListener(event.step.before, registerStep);
|
|
214
215
|
try {
|
|
215
216
|
this.startTime = Date.now();
|
|
216
217
|
result = fn.apply(this.context, this.args);
|
package/lib/transform.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const transformers = {
|
|
2
|
+
'gherkin.examples': [],
|
|
3
|
+
};
|
|
4
|
+
|
|
5
|
+
function transform(target, value) {
|
|
6
|
+
if (target in transformers) {
|
|
7
|
+
for (const transform of transformers[target]) {
|
|
8
|
+
value = transform(value);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
transform.addTransformer = function (target, transformer) {
|
|
15
|
+
if (target in transformers) {
|
|
16
|
+
transformers[target].push(transformer);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
transform.addTransformerBeforeAll = function (target, transformer) {
|
|
21
|
+
if (target in transformers) {
|
|
22
|
+
transformers[target].unshift(transformer);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
module.exports = transform;
|
package/lib/ui.js
CHANGED
|
@@ -176,8 +176,12 @@ module.exports = function (suite) {
|
|
|
176
176
|
* @kind constant
|
|
177
177
|
* @type {CodeceptJS.IScenario}
|
|
178
178
|
*/
|
|
179
|
-
context.xScenario = context.Scenario.skip = function (title) {
|
|
180
|
-
|
|
179
|
+
context.xScenario = context.Scenario.skip = function (title, opts = {}, fn) {
|
|
180
|
+
if (typeof opts === 'function' && !fn) {
|
|
181
|
+
opts = {};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return context.Scenario(title, opts);
|
|
181
185
|
};
|
|
182
186
|
|
|
183
187
|
/**
|
package/lib/within.js
CHANGED
|
@@ -20,7 +20,7 @@ function within(context, fn) {
|
|
|
20
20
|
const defineMetaStep = step => step.metaStep = metaStep;
|
|
21
21
|
recorder.session.start('within');
|
|
22
22
|
|
|
23
|
-
event.dispatcher.
|
|
23
|
+
event.dispatcher.prependListener(event.step.before, defineMetaStep);
|
|
24
24
|
|
|
25
25
|
Object.keys(helpers).forEach((helper) => {
|
|
26
26
|
if (helpers[helper]._withinBegin) recorder.add(`[${helper}] start within`, () => helpers[helper]._withinBegin(context));
|
package/lib/workers.js
CHANGED
|
@@ -4,6 +4,7 @@ const path = require('path');
|
|
|
4
4
|
const mkdirp = require('mkdirp');
|
|
5
5
|
const { Worker } = require('worker_threads');
|
|
6
6
|
const { Suite, Test, reporters: { Base } } = require('mocha');
|
|
7
|
+
const ms = require('ms');
|
|
7
8
|
const Codecept = require('./codecept');
|
|
8
9
|
const MochaFactory = require('./mochaFactory');
|
|
9
10
|
const Container = require('./container');
|
|
@@ -164,7 +165,7 @@ class Workers extends EventEmitter {
|
|
|
164
165
|
super();
|
|
165
166
|
this.setMaxListeners(50);
|
|
166
167
|
this.codecept = initializeCodecept(config.testConfig, config.options);
|
|
167
|
-
this.
|
|
168
|
+
this.failuresLog = [];
|
|
168
169
|
this.errors = [];
|
|
169
170
|
this.numberOfWorkers = 0;
|
|
170
171
|
this.closedWorkers = 0;
|
|
@@ -318,29 +319,45 @@ class Workers extends EventEmitter {
|
|
|
318
319
|
_listenWorkerEvents(worker) {
|
|
319
320
|
worker.on('message', (message) => {
|
|
320
321
|
output.process(message.workerIndex);
|
|
322
|
+
|
|
323
|
+
// deal with events that are not test cycle related
|
|
324
|
+
if (!message.event) {
|
|
325
|
+
return this.emit('message', message);
|
|
326
|
+
}
|
|
327
|
+
|
|
321
328
|
switch (message.event) {
|
|
329
|
+
case event.all.failures:
|
|
330
|
+
this.failuresLog = this.failuresLog.concat(message.data.failuresLog);
|
|
331
|
+
this._appendStats(message.data.stats);
|
|
332
|
+
break;
|
|
333
|
+
case event.suite.before:
|
|
334
|
+
this.emit(event.suite.before, repackTest(message.data));
|
|
335
|
+
break;
|
|
322
336
|
case event.hook.failed:
|
|
323
337
|
this.emit(event.hook.failed, repackTest(message.data));
|
|
324
338
|
this.errors.push(message.data.err);
|
|
325
339
|
break;
|
|
340
|
+
case event.test.before:
|
|
341
|
+
this.emit(event.test.before, repackTest(message.data));
|
|
342
|
+
break;
|
|
343
|
+
case event.test.started:
|
|
344
|
+
this.emit(event.test.started, repackTest(message.data));
|
|
345
|
+
break;
|
|
326
346
|
case event.test.failed:
|
|
327
|
-
this._updateFinishedTests(repackTest(message.data));
|
|
328
347
|
this.emit(event.test.failed, repackTest(message.data));
|
|
329
348
|
break;
|
|
330
349
|
case event.test.passed:
|
|
331
|
-
this._updateFinishedTests(repackTest(message.data));
|
|
332
350
|
this.emit(event.test.passed, repackTest(message.data));
|
|
333
351
|
break;
|
|
334
352
|
case event.test.skipped:
|
|
335
|
-
this._updateFinishedTests(repackTest(message.data));
|
|
336
353
|
this.emit(event.test.skipped, repackTest(message.data));
|
|
337
354
|
break;
|
|
338
|
-
case event.test.finished:
|
|
355
|
+
case event.test.finished:
|
|
356
|
+
this.emit(event.test.finished, repackTest(message.data));
|
|
357
|
+
break;
|
|
339
358
|
case event.test.after:
|
|
340
359
|
this.emit(event.test.after, repackTest(message.data));
|
|
341
360
|
break;
|
|
342
|
-
case event.all.after:
|
|
343
|
-
this._appendStats(message.data); break;
|
|
344
361
|
}
|
|
345
362
|
});
|
|
346
363
|
|
|
@@ -363,7 +380,8 @@ class Workers extends EventEmitter {
|
|
|
363
380
|
} else {
|
|
364
381
|
process.exitCode = 0;
|
|
365
382
|
}
|
|
366
|
-
this.
|
|
383
|
+
// removed this.finishedTests because in all /lib only first argument (!this.isFailed()) is used)
|
|
384
|
+
this.emit(event.all.result, !this.isFailed());
|
|
367
385
|
this.emit('end'); // internal event
|
|
368
386
|
}
|
|
369
387
|
|
|
@@ -374,30 +392,26 @@ class Workers extends EventEmitter {
|
|
|
374
392
|
this.stats.pending += newStats.pending;
|
|
375
393
|
}
|
|
376
394
|
|
|
377
|
-
_updateFinishedTests(test) {
|
|
378
|
-
const { id } = test;
|
|
379
|
-
if (this.finishedTests[id]) {
|
|
380
|
-
const stats = { passes: 0, failures: -1, tests: 0 };
|
|
381
|
-
this._appendStats(stats);
|
|
382
|
-
}
|
|
383
|
-
this.finishedTests[id] = test;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
395
|
printResults() {
|
|
387
396
|
this.stats.end = new Date();
|
|
388
397
|
this.stats.duration = this.stats.end - this.stats.start;
|
|
398
|
+
|
|
399
|
+
// Reset process for logs in main thread
|
|
400
|
+
output.process(null);
|
|
389
401
|
output.print();
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
402
|
+
|
|
403
|
+
this.failuresLog = this.failuresLog
|
|
404
|
+
.filter(log => log.length && typeof log[1] === 'number')
|
|
405
|
+
// mocha/lib/reporters/base.js
|
|
406
|
+
.map(([format, num, title, message, stack], i) => [format, i + 1, title, message, stack]);
|
|
407
|
+
|
|
408
|
+
if (this.failuresLog.length) {
|
|
394
409
|
output.print();
|
|
395
410
|
output.print('-- FAILURES:');
|
|
396
|
-
|
|
397
|
-
.filter(key => this.finishedTests[key].err)
|
|
398
|
-
.map(key => this.finishedTests[key]);
|
|
399
|
-
Base.list(failedList);
|
|
411
|
+
this.failuresLog.forEach(log => output.print(...log));
|
|
400
412
|
}
|
|
413
|
+
|
|
414
|
+
output.result(this.stats.passes, this.stats.failures, this.stats.pending, ms(this.stats.duration));
|
|
401
415
|
}
|
|
402
416
|
}
|
|
403
417
|
|