codeceptjs 3.3.8-beta.1 → 3.4.0
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 +47 -0
- package/README.md +31 -32
- package/docs/advanced.md +48 -24
- package/docs/basics.md +115 -40
- package/docs/best.md +2 -2
- package/docs/build/ApiDataFactory.js +14 -9
- package/docs/build/Appium.js +2 -19
- package/docs/build/FileSystem.js +3 -3
- package/docs/build/GraphQL.js +1 -1
- package/docs/build/GraphQLDataFactory.js +3 -3
- package/docs/build/JSONResponse.js +1 -1
- package/docs/build/Mochawesome.js +1 -1
- package/docs/build/Nightmare.js +1 -1
- package/docs/build/Playwright.js +4 -3
- package/docs/build/Protractor.js +1 -1
- package/docs/build/Puppeteer.js +1 -1
- package/docs/build/REST.js +1 -1
- package/docs/build/TestCafe.js +5 -5
- package/docs/build/WebDriver.js +30 -165
- package/docs/changelog.md +49 -2
- package/docs/helpers/ApiDataFactory.md +6 -6
- package/docs/helpers/FileSystem.md +2 -2
- package/docs/helpers/GraphQLDataFactory.md +2 -2
- package/docs/helpers/Playwright.md +2 -1
- package/docs/index.md +1 -1
- package/docs/plugins.md +42 -125
- package/docs/reports.md +0 -56
- package/docs/tutorial.md +271 -0
- package/docs/typescript.md +2 -8
- package/lib/actor.js +2 -1
- package/lib/cli.js +3 -3
- package/lib/codecept.js +2 -1
- package/lib/command/generate.js +3 -1
- package/lib/command/gherkin/snippets.js +8 -4
- package/lib/command/init.js +0 -8
- package/lib/command/run-workers.js +3 -6
- package/lib/command/utils.js +0 -10
- package/lib/command/workers/runTests.js +2 -2
- package/lib/config.js +5 -1
- package/lib/helper/ApiDataFactory.js +14 -9
- package/lib/helper/Appium.js +2 -19
- package/lib/helper/FileSystem.js +3 -3
- package/lib/helper/GraphQL.js +1 -1
- package/lib/helper/GraphQLDataFactory.js +3 -3
- package/lib/helper/JSONResponse.js +1 -1
- package/lib/helper/Mochawesome.js +1 -1
- package/lib/helper/Nightmare.js +1 -1
- package/lib/helper/Playwright.js +4 -3
- package/lib/helper/Protractor.js +1 -1
- package/lib/helper/Puppeteer.js +1 -1
- package/lib/helper/REST.js +1 -1
- package/lib/helper/TestCafe.js +5 -5
- package/lib/helper/WebDriver.js +30 -165
- package/lib/helper.js +0 -2
- package/lib/interfaces/bdd.js +1 -1
- package/lib/interfaces/featureConfig.js +1 -0
- package/lib/interfaces/gherkin.js +38 -25
- package/lib/listener/exit.js +2 -2
- package/lib/listener/retry.js +67 -0
- package/lib/listener/steps.js +1 -1
- package/lib/listener/timeout.js +47 -10
- package/lib/mochaFactory.js +3 -3
- package/lib/plugin/allure.js +14 -323
- package/lib/plugin/fakerTransform.js +2 -2
- package/lib/recorder.js +1 -1
- package/lib/scenario.js +25 -18
- package/lib/utils.js +6 -0
- package/lib/workers.js +4 -7
- package/package.json +14 -18
- package/typings/index.d.ts +76 -1
- package/typings/promiseBasedTypes.d.ts +12 -12
- package/typings/types.d.ts +95 -262
package/lib/helper/WebDriver.js
CHANGED
|
@@ -4,12 +4,12 @@ const assert = require('assert');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
|
|
7
|
-
const Helper = require('
|
|
7
|
+
const Helper = require('@codeceptjs/helper');
|
|
8
8
|
const stringIncludes = require('../assert/include').includes;
|
|
9
9
|
const { urlEquals, equals } = require('../assert/equal');
|
|
10
10
|
const { debug } = require('../output');
|
|
11
|
-
const empty = require('../assert/empty')
|
|
12
|
-
const truth = require('../assert/truth')
|
|
11
|
+
const { empty } = require('../assert/empty');
|
|
12
|
+
const { truth } = require('../assert/truth');
|
|
13
13
|
const {
|
|
14
14
|
xpathLocator,
|
|
15
15
|
fileExists,
|
|
@@ -31,8 +31,6 @@ const Locator = require('../locator');
|
|
|
31
31
|
const SHADOW = 'shadow';
|
|
32
32
|
const webRoot = 'body';
|
|
33
33
|
|
|
34
|
-
let version;
|
|
35
|
-
|
|
36
34
|
/**
|
|
37
35
|
* ## Configuration
|
|
38
36
|
*
|
|
@@ -266,7 +264,7 @@ const config = {};
|
|
|
266
264
|
* ```js
|
|
267
265
|
* plugins: {
|
|
268
266
|
* wdio: {
|
|
269
|
-
|
|
267
|
+
* enabled: true,
|
|
270
268
|
* services: ['sauce'],
|
|
271
269
|
* user: ... ,// saucelabs username
|
|
272
270
|
* key: ... // saucelabs api key
|
|
@@ -294,7 +292,7 @@ const config = {};
|
|
|
294
292
|
* ```js
|
|
295
293
|
* plugins: {
|
|
296
294
|
* wdio: {
|
|
297
|
-
|
|
295
|
+
* enabled: true,
|
|
298
296
|
* services: ['browserstack'],
|
|
299
297
|
* user: ... ,// browserstack username
|
|
300
298
|
* key: ... // browserstack api key
|
|
@@ -386,16 +384,6 @@ class WebDriver extends Helper {
|
|
|
386
384
|
super(config);
|
|
387
385
|
webdriverio = require('webdriverio');
|
|
388
386
|
|
|
389
|
-
try {
|
|
390
|
-
version = JSON.parse(fs.readFileSync(path.join(require.resolve('webdriverio'), '/../../', 'package.json')).toString()).version;
|
|
391
|
-
} catch (err) {
|
|
392
|
-
this.debug('Can\'t detect webdriverio version, assuming webdriverio v6 is used');
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
if (isWebDriver5()) {
|
|
396
|
-
console.log('DEPRECATION NOTICE:');
|
|
397
|
-
console.log('You are using webdriverio v5. It is recommended to update to webdriverio@6.\nSupport of webdriverio v5 is deprecated and will be removed in CodeceptJS 3.0\n');
|
|
398
|
-
}
|
|
399
387
|
// set defaults
|
|
400
388
|
this.root = webRoot;
|
|
401
389
|
this.isWeb = true;
|
|
@@ -661,23 +649,23 @@ class WebDriver extends Helper {
|
|
|
661
649
|
}
|
|
662
650
|
|
|
663
651
|
/**
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
652
|
+
* Use [webdriverio](https://webdriver.io/docs/api.html) API inside a test.
|
|
653
|
+
*
|
|
654
|
+
* First argument is a description of an action.
|
|
655
|
+
* Second argument is async function that gets this helper as parameter.
|
|
656
|
+
*
|
|
657
|
+
* { [`browser`](https://webdriver.io/docs/api.html)) } object from WebDriver API is available.
|
|
658
|
+
*
|
|
659
|
+
* ```js
|
|
660
|
+
* I.useWebDriverTo('open multiple windows', async ({ browser }) {
|
|
661
|
+
* // create new window
|
|
662
|
+
* await browser.newWindow('https://webdriver.io');
|
|
663
|
+
* });
|
|
664
|
+
* ```
|
|
665
|
+
*
|
|
666
|
+
* @param {string} description used to show in logs.
|
|
667
|
+
* @param {function} fn async functuion that executed with WebDriver helper as argument
|
|
668
|
+
*/
|
|
681
669
|
useWebDriverTo(description, fn) {
|
|
682
670
|
return this._useTo(...arguments);
|
|
683
671
|
}
|
|
@@ -1654,7 +1642,6 @@ class WebDriver extends Helper {
|
|
|
1654
1642
|
const res = await this._locate(withStrictLocator(locator), true);
|
|
1655
1643
|
assertElementExists(res, locator);
|
|
1656
1644
|
const elem = usingFirstElement(res);
|
|
1657
|
-
if (isWebDriver5()) return elem.moveTo(xOffset, yOffset);
|
|
1658
1645
|
return elem.moveTo({ xOffset, yOffset });
|
|
1659
1646
|
}
|
|
1660
1647
|
|
|
@@ -2043,19 +2030,7 @@ class WebDriver extends Helper {
|
|
|
2043
2030
|
*/
|
|
2044
2031
|
async waitForEnabled(locator, sec = null) {
|
|
2045
2032
|
const aSec = sec || this.options.waitForTimeoutInSeconds;
|
|
2046
|
-
|
|
2047
|
-
return this.browser.waitUntil(async () => {
|
|
2048
|
-
const res = await this.$$(withStrictLocator(locator));
|
|
2049
|
-
if (!res || res.length === 0) {
|
|
2050
|
-
return false;
|
|
2051
|
-
}
|
|
2052
|
-
const selected = await forEachAsync(res, async el => this.browser.isElementEnabled(getElementId(el)));
|
|
2053
|
-
if (Array.isArray(selected)) {
|
|
2054
|
-
return selected.filter(val => val === true).length > 0;
|
|
2055
|
-
}
|
|
2056
|
-
return selected;
|
|
2057
|
-
}, aSec * 1000, `element (${new Locator(locator)}) still not enabled after ${aSec} sec`);
|
|
2058
|
-
}
|
|
2033
|
+
|
|
2059
2034
|
return this.browser.waitUntil(async () => {
|
|
2060
2035
|
const res = await this._res(locator);
|
|
2061
2036
|
if (!res || res.length === 0) {
|
|
@@ -2077,12 +2052,7 @@ class WebDriver extends Helper {
|
|
|
2077
2052
|
*/
|
|
2078
2053
|
async waitForElement(locator, sec = null) {
|
|
2079
2054
|
const aSec = sec || this.options.waitForTimeoutInSeconds;
|
|
2080
|
-
|
|
2081
|
-
return this.browser.waitUntil(async () => {
|
|
2082
|
-
const res = await this.$$(withStrictLocator(locator));
|
|
2083
|
-
return res && res.length;
|
|
2084
|
-
}, aSec * 1000, `element (${(new Locator(locator))}) still not present on page after ${aSec} sec`);
|
|
2085
|
-
}
|
|
2055
|
+
|
|
2086
2056
|
return this.browser.waitUntil(async () => {
|
|
2087
2057
|
const res = await this._res(locator);
|
|
2088
2058
|
return res && res.length;
|
|
@@ -2111,20 +2081,7 @@ class WebDriver extends Helper {
|
|
|
2111
2081
|
const client = this.browser;
|
|
2112
2082
|
const aSec = sec || this.options.waitForTimeoutInSeconds;
|
|
2113
2083
|
let currUrl = '';
|
|
2114
|
-
|
|
2115
|
-
return client
|
|
2116
|
-
.waitUntil(function () {
|
|
2117
|
-
return this.getUrl().then((res) => {
|
|
2118
|
-
currUrl = decodeUrl(res);
|
|
2119
|
-
return currUrl.indexOf(urlPart) > -1;
|
|
2120
|
-
});
|
|
2121
|
-
}, aSec * 1000).catch((e) => {
|
|
2122
|
-
if (e.message.indexOf('timeout')) {
|
|
2123
|
-
throw new Error(`expected url to include ${urlPart}, but found ${currUrl}`);
|
|
2124
|
-
}
|
|
2125
|
-
throw e;
|
|
2126
|
-
});
|
|
2127
|
-
}
|
|
2084
|
+
|
|
2128
2085
|
return client
|
|
2129
2086
|
.waitUntil(function () {
|
|
2130
2087
|
return this.getUrl().then((res) => {
|
|
@@ -2169,20 +2126,6 @@ class WebDriver extends Helper {
|
|
|
2169
2126
|
async waitForText(text, sec = null, context = null) {
|
|
2170
2127
|
const aSec = sec || this.options.waitForTimeoutInSeconds;
|
|
2171
2128
|
const _context = context || this.root;
|
|
2172
|
-
if (isWebDriver5()) {
|
|
2173
|
-
return this.browser.waitUntil(
|
|
2174
|
-
async () => {
|
|
2175
|
-
const res = await this.$$(withStrictLocator.call(this, _context));
|
|
2176
|
-
if (!res || res.length === 0) return false;
|
|
2177
|
-
const selected = await forEachAsync(res, async el => this.browser.getElementText(getElementId(el)));
|
|
2178
|
-
if (Array.isArray(selected)) {
|
|
2179
|
-
return selected.filter(part => part.indexOf(text) >= 0).length > 0;
|
|
2180
|
-
}
|
|
2181
|
-
return selected.indexOf(text) >= 0;
|
|
2182
|
-
}, aSec * 1000,
|
|
2183
|
-
`element (${_context}) is not in DOM or there is no element(${_context}) with text "${text}" after ${aSec} sec`,
|
|
2184
|
-
);
|
|
2185
|
-
}
|
|
2186
2129
|
|
|
2187
2130
|
return this.browser.waitUntil(
|
|
2188
2131
|
async () => {
|
|
@@ -2206,20 +2149,7 @@ class WebDriver extends Helper {
|
|
|
2206
2149
|
async waitForValue(field, value, sec = null) {
|
|
2207
2150
|
const client = this.browser;
|
|
2208
2151
|
const aSec = sec || this.options.waitForTimeoutInSeconds;
|
|
2209
|
-
|
|
2210
|
-
return client.waitUntil(
|
|
2211
|
-
async () => {
|
|
2212
|
-
const res = await findFields.call(this, field);
|
|
2213
|
-
if (!res || res.length === 0) return false;
|
|
2214
|
-
const selected = await forEachAsync(res, async el => el.getValue());
|
|
2215
|
-
if (Array.isArray(selected)) {
|
|
2216
|
-
return selected.filter(part => part.indexOf(value) >= 0).length > 0;
|
|
2217
|
-
}
|
|
2218
|
-
return selected.indexOf(value) >= 0;
|
|
2219
|
-
}, aSec * 1000,
|
|
2220
|
-
`element (${field}) is not in DOM or there is no element(${field}) with value "${value}" after ${aSec} sec`,
|
|
2221
|
-
);
|
|
2222
|
-
}
|
|
2152
|
+
|
|
2223
2153
|
return client.waitUntil(
|
|
2224
2154
|
async () => {
|
|
2225
2155
|
const res = await findFields.call(this, field);
|
|
@@ -2242,17 +2172,7 @@ class WebDriver extends Helper {
|
|
|
2242
2172
|
*/
|
|
2243
2173
|
async waitForVisible(locator, sec = null) {
|
|
2244
2174
|
const aSec = sec || this.options.waitForTimeoutInSeconds;
|
|
2245
|
-
|
|
2246
|
-
return this.browser.waitUntil(async () => {
|
|
2247
|
-
const res = await this.$$(withStrictLocator(locator));
|
|
2248
|
-
if (!res || res.length === 0) return false;
|
|
2249
|
-
const selected = await forEachAsync(res, async el => el.isDisplayed());
|
|
2250
|
-
if (Array.isArray(selected)) {
|
|
2251
|
-
return selected.filter(val => val === true).length > 0;
|
|
2252
|
-
}
|
|
2253
|
-
return selected;
|
|
2254
|
-
}, aSec * 1000, `element (${new Locator(locator)}) still not visible after ${aSec} sec`);
|
|
2255
|
-
}
|
|
2175
|
+
|
|
2256
2176
|
return this.browser.waitUntil(async () => {
|
|
2257
2177
|
const res = await this._res(locator);
|
|
2258
2178
|
if (!res || res.length === 0) return false;
|
|
@@ -2269,17 +2189,7 @@ class WebDriver extends Helper {
|
|
|
2269
2189
|
*/
|
|
2270
2190
|
async waitNumberOfVisibleElements(locator, num, sec = null) {
|
|
2271
2191
|
const aSec = sec || this.options.waitForTimeoutInSeconds;
|
|
2272
|
-
if (isWebDriver5()) {
|
|
2273
|
-
return this.browser.waitUntil(async () => {
|
|
2274
|
-
const res = await this.$$(withStrictLocator(locator));
|
|
2275
|
-
if (!res || res.length === 0) return false;
|
|
2276
|
-
let selected = await forEachAsync(res, async el => el.isDisplayed());
|
|
2277
2192
|
|
|
2278
|
-
if (!Array.isArray(selected)) selected = [selected];
|
|
2279
|
-
selected = selected.filter(val => val === true);
|
|
2280
|
-
return selected.length === num;
|
|
2281
|
-
}, aSec * 1000, `The number of elements (${new Locator(locator)}) is not ${num} after ${aSec} sec`);
|
|
2282
|
-
}
|
|
2283
2193
|
return this.browser.waitUntil(async () => {
|
|
2284
2194
|
const res = await this._res(locator);
|
|
2285
2195
|
if (!res || res.length === 0) return false;
|
|
@@ -2296,14 +2206,7 @@ class WebDriver extends Helper {
|
|
|
2296
2206
|
*/
|
|
2297
2207
|
async waitForInvisible(locator, sec = null) {
|
|
2298
2208
|
const aSec = sec || this.options.waitForTimeoutInSeconds;
|
|
2299
|
-
|
|
2300
|
-
return this.browser.waitUntil(async () => {
|
|
2301
|
-
const res = await this.$$(withStrictLocator(locator));
|
|
2302
|
-
if (!res || res.length === 0) return true;
|
|
2303
|
-
const selected = await forEachAsync(res, async el => el.isDisplayed());
|
|
2304
|
-
return !selected.length;
|
|
2305
|
-
}, aSec * 1000, `element (${new Locator(locator)}) still visible after ${aSec} sec`);
|
|
2306
|
-
}
|
|
2209
|
+
|
|
2307
2210
|
return this.browser.waitUntil(async () => {
|
|
2308
2211
|
const res = await this._res(locator);
|
|
2309
2212
|
if (!res || res.length === 0) return true;
|
|
@@ -2324,15 +2227,7 @@ class WebDriver extends Helper {
|
|
|
2324
2227
|
*/
|
|
2325
2228
|
async waitForDetached(locator, sec = null) {
|
|
2326
2229
|
const aSec = sec || this.options.waitForTimeoutInSeconds;
|
|
2327
|
-
|
|
2328
|
-
return this.browser.waitUntil(async () => {
|
|
2329
|
-
const res = await this._res(locator);
|
|
2330
|
-
if (!res || res.length === 0) {
|
|
2331
|
-
return true;
|
|
2332
|
-
}
|
|
2333
|
-
return false;
|
|
2334
|
-
}, aSec * 1000, `element (${new Locator(locator)}) still on page after ${aSec} sec`);
|
|
2335
|
-
}
|
|
2230
|
+
|
|
2336
2231
|
return this.browser.waitUntil(async () => {
|
|
2337
2232
|
const res = await this._res(locator);
|
|
2338
2233
|
if (!res || res.length === 0) {
|
|
@@ -2356,9 +2251,7 @@ class WebDriver extends Helper {
|
|
|
2356
2251
|
}
|
|
2357
2252
|
|
|
2358
2253
|
const aSec = sec || this.options.waitForTimeoutInSeconds;
|
|
2359
|
-
|
|
2360
|
-
return this.browser.waitUntil(async () => this.browser.execute(fn, ...args), aSec * 1000, '');
|
|
2361
|
-
}
|
|
2254
|
+
|
|
2362
2255
|
return this.browser.waitUntil(async () => this.browser.execute(fn, ...args), { timeout: aSec * 1000, timeoutMsg: '' });
|
|
2363
2256
|
}
|
|
2364
2257
|
|
|
@@ -2388,18 +2281,6 @@ class WebDriver extends Helper {
|
|
|
2388
2281
|
let target;
|
|
2389
2282
|
const current = await this.browser.getWindowHandle();
|
|
2390
2283
|
|
|
2391
|
-
if (isWebDriver5()) {
|
|
2392
|
-
await this.browser.waitUntil(async () => {
|
|
2393
|
-
await this.browser.getWindowHandles().then((handles) => {
|
|
2394
|
-
if (handles.indexOf(current) + num + 1 <= handles.length) {
|
|
2395
|
-
target = handles[handles.indexOf(current) + num];
|
|
2396
|
-
}
|
|
2397
|
-
});
|
|
2398
|
-
return target;
|
|
2399
|
-
}, aSec * 1000, `There is no ability to switch to next tab with offset ${num}`);
|
|
2400
|
-
return this.browser.switchToWindow(target);
|
|
2401
|
-
}
|
|
2402
|
-
|
|
2403
2284
|
await this.browser.waitUntil(async () => {
|
|
2404
2285
|
await this.browser.getWindowHandles().then((handles) => {
|
|
2405
2286
|
if (handles.indexOf(current) + num + 1 <= handles.length) {
|
|
@@ -2419,18 +2300,6 @@ class WebDriver extends Helper {
|
|
|
2419
2300
|
const current = await this.browser.getWindowHandle();
|
|
2420
2301
|
let target;
|
|
2421
2302
|
|
|
2422
|
-
if (isWebDriver5()) {
|
|
2423
|
-
await this.browser.waitUntil(async () => {
|
|
2424
|
-
await this.browser.getWindowHandles().then((handles) => {
|
|
2425
|
-
if (handles.indexOf(current) - num > -1) {
|
|
2426
|
-
target = handles[handles.indexOf(current) - num];
|
|
2427
|
-
}
|
|
2428
|
-
});
|
|
2429
|
-
return target;
|
|
2430
|
-
}, aSec * 1000, `There is no ability to switch to previous tab with offset ${num}`);
|
|
2431
|
-
return this.browser.switchToWindow(target);
|
|
2432
|
-
}
|
|
2433
|
-
|
|
2434
2303
|
await this.browser.waitUntil(async () => {
|
|
2435
2304
|
await this.browser.getWindowHandles().then((handles) => {
|
|
2436
2305
|
if (handles.indexOf(current) - num > -1) {
|
|
@@ -3018,8 +2887,4 @@ function prepareLocateFn(context) {
|
|
|
3018
2887
|
};
|
|
3019
2888
|
}
|
|
3020
2889
|
|
|
3021
|
-
function isWebDriver5() {
|
|
3022
|
-
return version && version.indexOf('5') === 0;
|
|
3023
|
-
}
|
|
3024
|
-
|
|
3025
2890
|
module.exports = WebDriver;
|
package/lib/helper.js
CHANGED
package/lib/interfaces/bdd.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { CucumberExpression, ParameterTypeRegistry, ParameterType } = require('cucumber-expressions');
|
|
1
|
+
const { CucumberExpression, ParameterTypeRegistry, ParameterType } = require('@cucumber/cucumber-expressions');
|
|
2
2
|
const Config = require('../config');
|
|
3
3
|
|
|
4
4
|
let steps = {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
const
|
|
1
|
+
const Gherkin = require('@cucumber/gherkin');
|
|
2
|
+
const Messages = require('@cucumber/messages');
|
|
2
3
|
const { Context, Suite, Test } = require('mocha');
|
|
3
4
|
|
|
4
5
|
const { matchStep } = require('./bdd');
|
|
@@ -8,7 +9,10 @@ const Step = require('../step');
|
|
|
8
9
|
const DataTableArgument = require('../data/dataTableArgument');
|
|
9
10
|
const transform = require('../transform');
|
|
10
11
|
|
|
11
|
-
const
|
|
12
|
+
const uuidFn = Messages.IdGenerator.uuid();
|
|
13
|
+
const builder = new Gherkin.AstBuilder(uuidFn);
|
|
14
|
+
const matcher = new Gherkin.GherkinClassicTokenMatcher();
|
|
15
|
+
const parser = new Gherkin.Parser(builder, matcher);
|
|
12
16
|
parser.stopAtFirstError = false;
|
|
13
17
|
|
|
14
18
|
module.exports = (text, file) => {
|
|
@@ -46,14 +50,20 @@ module.exports = (text, file) => {
|
|
|
46
50
|
step.metaStep = metaStep;
|
|
47
51
|
};
|
|
48
52
|
const fn = matchStep(step.text);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
|
|
54
|
+
if (step.dataTable) {
|
|
55
|
+
fn.params.push({
|
|
56
|
+
...step.dataTable,
|
|
57
|
+
parse: () => new DataTableArgument(step.dataTable),
|
|
58
|
+
});
|
|
59
|
+
metaStep.comment = `\n${transformTable(step.dataTable)}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (step.docString) {
|
|
63
|
+
fn.params.push(step.docString);
|
|
64
|
+
metaStep.comment = `\n"""\n${step.docString.content}\n"""`;
|
|
56
65
|
}
|
|
66
|
+
|
|
57
67
|
step.startTime = Date.now();
|
|
58
68
|
step.match = fn.line;
|
|
59
69
|
event.emit(event.bddStep.before, step);
|
|
@@ -74,15 +84,15 @@ module.exports = (text, file) => {
|
|
|
74
84
|
};
|
|
75
85
|
|
|
76
86
|
for (const child of ast.feature.children) {
|
|
77
|
-
if (child.
|
|
78
|
-
suite.beforeEach('Before', scenario.injected(async () => runSteps(child.steps), suite, 'before'));
|
|
87
|
+
if (child.background) {
|
|
88
|
+
suite.beforeEach('Before', scenario.injected(async () => runSteps(child.background.steps), suite, 'before'));
|
|
79
89
|
continue;
|
|
80
90
|
}
|
|
81
|
-
if (child.
|
|
82
|
-
for (const examples of child.examples) {
|
|
91
|
+
if (child.scenario && child.scenario.keyword === 'Scenario Outline') {
|
|
92
|
+
for (const examples of child.scenario.examples) {
|
|
83
93
|
const fields = examples.tableHeader.cells.map(c => c.value);
|
|
84
94
|
for (const example of examples.tableBody) {
|
|
85
|
-
let exampleSteps = [...child.steps];
|
|
95
|
+
let exampleSteps = [...child.scenario.steps];
|
|
86
96
|
const current = {};
|
|
87
97
|
for (const index in example.cells) {
|
|
88
98
|
const placeholder = fields[index];
|
|
@@ -95,8 +105,8 @@ module.exports = (text, file) => {
|
|
|
95
105
|
return step;
|
|
96
106
|
});
|
|
97
107
|
}
|
|
98
|
-
const tags = child.tags.map(t => t.name).concat(examples.tags.map(t => t.name));
|
|
99
|
-
const title = `${child.name} ${JSON.stringify(current)} ${tags.join(' ')}`.trim();
|
|
108
|
+
const tags = child.scenario.tags.map(t => t.name).concat(examples.tags.map(t => t.name));
|
|
109
|
+
const title = `${child.scenario.name} ${JSON.stringify(current)} ${tags.join(' ')}`.trim();
|
|
100
110
|
const test = new Test(title, async () => runSteps(addExampleInTable(exampleSteps, current)));
|
|
101
111
|
test.tags = suite.tags.concat(tags);
|
|
102
112
|
test.file = file;
|
|
@@ -105,12 +115,15 @@ module.exports = (text, file) => {
|
|
|
105
115
|
}
|
|
106
116
|
continue;
|
|
107
117
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
118
|
+
|
|
119
|
+
if (child.scenario) {
|
|
120
|
+
const tags = child.scenario.tags.map(t => t.name);
|
|
121
|
+
const title = `${child.scenario.name} ${tags.join(' ')}`.trim();
|
|
122
|
+
const test = new Test(title, async () => runSteps(child.scenario.steps));
|
|
123
|
+
test.tags = suite.tags.concat(tags);
|
|
124
|
+
test.file = file;
|
|
125
|
+
suite.addTest(scenario.test(test));
|
|
126
|
+
}
|
|
114
127
|
}
|
|
115
128
|
|
|
116
129
|
return suite;
|
|
@@ -130,9 +143,9 @@ function addExampleInTable(exampleSteps, placeholders) {
|
|
|
130
143
|
for (const placeholder in placeholders) {
|
|
131
144
|
steps.map((step) => {
|
|
132
145
|
step = { ...step };
|
|
133
|
-
if (step.
|
|
134
|
-
for (const id in step.
|
|
135
|
-
const cells = step.
|
|
146
|
+
if (step.dataTable) {
|
|
147
|
+
for (const id in step.dataTable.rows) {
|
|
148
|
+
const cells = step.dataTable.rows[id].cells;
|
|
136
149
|
cells.map(c => (c.value = c.value.replace(`<${placeholder}>`, placeholders[placeholder])));
|
|
137
150
|
}
|
|
138
151
|
}
|
package/lib/listener/exit.js
CHANGED
|
@@ -6,7 +6,7 @@ module.exports = function () {
|
|
|
6
6
|
event.dispatcher.on(event.test.failed, (testOrSuite) => {
|
|
7
7
|
// NOTE When an error happens in one of the hooks (BeforeAll/BeforeEach...) the event object
|
|
8
8
|
// is a suite and not a test
|
|
9
|
-
const id = testOrSuite.
|
|
9
|
+
const id = testOrSuite.uid || (testOrSuite.ctx && testOrSuite.ctx.test.uid) || 'empty';
|
|
10
10
|
failedTests.push(id);
|
|
11
11
|
});
|
|
12
12
|
|
|
@@ -14,7 +14,7 @@ module.exports = function () {
|
|
|
14
14
|
event.dispatcher.on(event.test.passed, (testOrSuite) => {
|
|
15
15
|
// NOTE When an error happens in one of the hooks (BeforeAll/BeforeEach...) the event object
|
|
16
16
|
// is a suite and not a test
|
|
17
|
-
const id = testOrSuite.
|
|
17
|
+
const id = testOrSuite.uid || (testOrSuite.ctx && testOrSuite.ctx.test.uid) || 'empty';
|
|
18
18
|
failedTests = failedTests.filter(failed => id !== failed);
|
|
19
19
|
});
|
|
20
20
|
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const event = require('../event');
|
|
2
|
+
const output = require('../output');
|
|
3
|
+
const Config = require('../config');
|
|
4
|
+
const { isNotSet } = require('../utils');
|
|
5
|
+
|
|
6
|
+
const hooks = ['Before', 'After', 'BeforeSuite', 'AfterSuite'];
|
|
7
|
+
|
|
8
|
+
module.exports = function () {
|
|
9
|
+
event.dispatcher.on(event.suite.before, (suite) => {
|
|
10
|
+
let retryConfig = Config.get('retry');
|
|
11
|
+
if (!retryConfig) return;
|
|
12
|
+
|
|
13
|
+
if (Number.isInteger(+retryConfig)) {
|
|
14
|
+
// is number
|
|
15
|
+
const retryNum = +retryConfig;
|
|
16
|
+
output.log(`Retries: ${retryNum}`);
|
|
17
|
+
suite.retries(retryNum);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!Array.isArray(retryConfig)) {
|
|
22
|
+
retryConfig = [retryConfig];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
for (const config of retryConfig) {
|
|
26
|
+
if (config.grep) {
|
|
27
|
+
if (!suite.title.includes(config.grep)) continue;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
hooks.filter(hook => !!config[hook]).forEach((hook) => {
|
|
31
|
+
if (isNotSet(suite.opts[`retry${hook}`])) suite.opts[`retry${hook}`] = config[hook];
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (config.Feature) {
|
|
35
|
+
if (isNotSet(suite.retries())) suite.retries(config.Feature);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
output.log(`Retries: ${JSON.stringify(config)}`);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
event.dispatcher.on(event.test.before, (test) => {
|
|
43
|
+
let retryConfig = Config.get('retry');
|
|
44
|
+
if (!retryConfig) return;
|
|
45
|
+
|
|
46
|
+
if (Number.isInteger(+retryConfig)) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!Array.isArray(retryConfig)) {
|
|
51
|
+
retryConfig = [retryConfig];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
retryConfig = retryConfig.filter(config => !!config.Scenario);
|
|
55
|
+
|
|
56
|
+
for (const config of retryConfig) {
|
|
57
|
+
if (config.grep) {
|
|
58
|
+
if (!test.fullTitle().includes(config.grep)) continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (config.Scenario) {
|
|
62
|
+
if (isNotSet(test.retries())) test.retries(config.Scenario);
|
|
63
|
+
output.log(`Retries: ${config.Scenario}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
};
|
package/lib/listener/steps.js
CHANGED
|
@@ -81,6 +81,6 @@ module.exports = function () {
|
|
|
81
81
|
if (store.debugMode) return;
|
|
82
82
|
step.finishedAt = +new Date();
|
|
83
83
|
if (step.startedAt) step.duration = step.finishedAt - step.startedAt;
|
|
84
|
-
debug(`Step '${step}' finished; Duration: ${step.duration}ms`);
|
|
84
|
+
debug(`Step '${step}' finished; Duration: ${step.duration || 0}ms`);
|
|
85
85
|
});
|
|
86
86
|
};
|
package/lib/listener/timeout.js
CHANGED
|
@@ -7,7 +7,7 @@ const TIMEOUT_ORDER = require('../step').TIMEOUT_ORDER;
|
|
|
7
7
|
|
|
8
8
|
module.exports = function () {
|
|
9
9
|
let timeout;
|
|
10
|
-
let
|
|
10
|
+
let suiteTimeout = [];
|
|
11
11
|
let currentTest;
|
|
12
12
|
let currentTimeout;
|
|
13
13
|
|
|
@@ -17,21 +17,52 @@ module.exports = function () {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
event.dispatcher.on(event.suite.before, (suite) => {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
suiteTimeout = [];
|
|
21
|
+
let timeoutConfig = Config.get('timeout');
|
|
22
|
+
|
|
23
|
+
if (timeoutConfig) {
|
|
24
|
+
if (!Number.isNaN(+timeoutConfig)) {
|
|
25
|
+
checkForSeconds(timeoutConfig);
|
|
26
|
+
suiteTimeout.push(timeoutConfig);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!Array.isArray(timeoutConfig)) {
|
|
30
|
+
timeoutConfig = [timeoutConfig];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
for (const config of timeoutConfig.filter(c => !!c.Feature)) {
|
|
34
|
+
if (config.grep) {
|
|
35
|
+
if (!suite.title.includes(config.grep)) continue;
|
|
36
|
+
}
|
|
37
|
+
suiteTimeout.push(config.Feature);
|
|
25
38
|
}
|
|
26
|
-
timeoutStack.push(globalTimeout);
|
|
27
39
|
}
|
|
28
|
-
|
|
29
|
-
|
|
40
|
+
|
|
41
|
+
if (suite.totalTimeout) suiteTimeout.push(suite.totalTimeout);
|
|
42
|
+
output.log(`Timeouts: ${suiteTimeout}`);
|
|
30
43
|
});
|
|
31
44
|
|
|
32
45
|
event.dispatcher.on(event.test.before, (test) => {
|
|
33
46
|
currentTest = test;
|
|
34
|
-
|
|
47
|
+
let testTimeout = null;
|
|
48
|
+
|
|
49
|
+
let timeoutConfig = Config.get('timeout');
|
|
50
|
+
|
|
51
|
+
if (typeof timeoutConfig === 'object' || Array.isArray(timeoutConfig)) {
|
|
52
|
+
if (!Array.isArray(timeoutConfig)) {
|
|
53
|
+
timeoutConfig = [timeoutConfig];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
for (const config of timeoutConfig.filter(c => !!c.Scenario)) {
|
|
57
|
+
console.log('Test Timeout', config, test.title.includes(config.grep));
|
|
58
|
+
if (config.grep) {
|
|
59
|
+
if (!test.title.includes(config.grep)) continue;
|
|
60
|
+
}
|
|
61
|
+
testTimeout = config.Scenario;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
timeout = test.totalTimeout || testTimeout || suiteTimeout[suiteTimeout.length - 1];
|
|
35
66
|
if (!timeout) return;
|
|
36
67
|
currentTimeout = timeout;
|
|
37
68
|
output.debug(`Test Timeout: ${timeout}s`);
|
|
@@ -70,3 +101,9 @@ module.exports = function () {
|
|
|
70
101
|
}
|
|
71
102
|
});
|
|
72
103
|
};
|
|
104
|
+
|
|
105
|
+
function checkForSeconds(timeout) {
|
|
106
|
+
if (timeout >= 1000) {
|
|
107
|
+
console.log(`Warning: Timeout was set to ${timeout}secs.\nGlobal timeout should be specified in seconds.`);
|
|
108
|
+
}
|
|
109
|
+
}
|
package/lib/mochaFactory.js
CHANGED
|
@@ -47,13 +47,13 @@ class MochaFactory {
|
|
|
47
47
|
let missingFeatureInFile = [];
|
|
48
48
|
const seenTests = [];
|
|
49
49
|
mocha.suite.eachTest(test => {
|
|
50
|
-
test.
|
|
50
|
+
test.uid = genTestId(test);
|
|
51
51
|
|
|
52
52
|
const name = test.fullTitle();
|
|
53
|
-
if (seenTests.includes(test.
|
|
53
|
+
if (seenTests.includes(test.uid)) {
|
|
54
54
|
dupes.push(name);
|
|
55
55
|
}
|
|
56
|
-
seenTests.push(test.
|
|
56
|
+
seenTests.push(test.uid);
|
|
57
57
|
|
|
58
58
|
if (name.slice(0, name.indexOf(':')) === '') {
|
|
59
59
|
missingFeatureInFile.push(test.file);
|