codeceptjs 3.0.6 → 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 +34 -8
- package/bin/codecept.js +1 -0
- package/docs/build/Appium.js +1 -0
- package/docs/build/GraphQL.js +9 -10
- package/docs/build/Playwright.js +123 -22
- package/docs/build/REST.js +4 -3
- package/docs/build/WebDriver.js +82 -14
- package/docs/changelog.md +35 -9
- package/docs/email.md +8 -8
- package/docs/examples.md +3 -3
- package/docs/helpers/MockRequest.md +3 -3
- package/docs/helpers/Playwright.md +10 -1
- package/docs/helpers/WebDriver.md +1 -0
- package/docs/locators.md +27 -0
- package/docs/nightmare.md +0 -5
- package/docs/parallel.md +14 -7
- package/docs/playwright.md +6 -6
- package/docs/reports.md +5 -4
- package/lib/command/interactive.js +4 -2
- package/lib/helper/GraphQL.js +9 -10
- package/lib/helper/Playwright.js +46 -5
- package/lib/helper/WebDriver.js +82 -14
- package/lib/output.js +3 -0
- package/lib/plugin/screenshotOnFail.js +5 -0
- package/lib/ui.js +6 -2
- package/package.json +2 -2
- package/typings/index.d.ts +44 -21
- package/typings/types.d.ts +17 -5
package/lib/helper/GraphQL.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
const axios = require('axios').default;
|
|
2
2
|
const Helper = require('../helper');
|
|
3
3
|
|
|
4
|
-
let headers = {};
|
|
5
|
-
|
|
6
4
|
/**
|
|
7
5
|
* GraphQL helper allows to send additional requests to a GraphQl endpoint during acceptance tests.
|
|
8
6
|
* [Axios](https://github.com/axios/axios) library is used to perform requests.
|
|
@@ -41,15 +39,16 @@ let headers = {};
|
|
|
41
39
|
class GraphQL extends Helper {
|
|
42
40
|
constructor(config) {
|
|
43
41
|
super(config);
|
|
44
|
-
axios =
|
|
42
|
+
this.axios = axios.create();
|
|
43
|
+
this.headers = {};
|
|
45
44
|
this.options = {
|
|
46
45
|
timeout: 10000,
|
|
47
46
|
defaultHeaders: {},
|
|
48
47
|
endpoint: '',
|
|
49
48
|
};
|
|
50
49
|
this.options = Object.assign(this.options, config);
|
|
51
|
-
headers = { ...this.options.defaultHeaders };
|
|
52
|
-
axios.defaults.headers = this.options.defaultHeaders;
|
|
50
|
+
this.headers = { ...this.options.defaultHeaders };
|
|
51
|
+
this.axios.defaults.headers = this.options.defaultHeaders;
|
|
53
52
|
}
|
|
54
53
|
|
|
55
54
|
static _checkRequirements() {
|
|
@@ -66,10 +65,10 @@ class GraphQL extends Helper {
|
|
|
66
65
|
* @param {object} request
|
|
67
66
|
*/
|
|
68
67
|
async _executeQuery(request) {
|
|
69
|
-
axios.defaults.timeout = request.timeout || this.options.timeout;
|
|
68
|
+
this.axios.defaults.timeout = request.timeout || this.options.timeout;
|
|
70
69
|
|
|
71
|
-
if (headers && headers.auth) {
|
|
72
|
-
request.auth = headers.auth;
|
|
70
|
+
if (this.headers && this.headers.auth) {
|
|
71
|
+
request.auth = this.headers.auth;
|
|
73
72
|
}
|
|
74
73
|
|
|
75
74
|
request.headers = Object.assign(request.headers, {
|
|
@@ -84,7 +83,7 @@ class GraphQL extends Helper {
|
|
|
84
83
|
|
|
85
84
|
let response;
|
|
86
85
|
try {
|
|
87
|
-
response = await axios(request);
|
|
86
|
+
response = await this.axios(request);
|
|
88
87
|
} catch (err) {
|
|
89
88
|
if (!err.response) throw err;
|
|
90
89
|
this.debugSection(
|
package/lib/helper/Playwright.js
CHANGED
|
@@ -38,6 +38,7 @@ const consoleLogStore = new Console();
|
|
|
38
38
|
const availableBrowsers = ['chromium', 'webkit', 'firefox', 'electron'];
|
|
39
39
|
|
|
40
40
|
const { createValueEngine, createDisabledEngine } = require('./extras/PlaywrightPropEngine');
|
|
41
|
+
|
|
41
42
|
/**
|
|
42
43
|
* Uses [Playwright](https://github.com/microsoft/playwright) library to run tests inside:
|
|
43
44
|
*
|
|
@@ -515,6 +516,7 @@ class Playwright extends Helper {
|
|
|
515
516
|
if (!page) return;
|
|
516
517
|
page.setDefaultNavigationTimeout(this.options.getPageTimeout);
|
|
517
518
|
this.context = await this.page;
|
|
519
|
+
this.contextLocator = null;
|
|
518
520
|
if (this.config.browser === 'chrome') {
|
|
519
521
|
await page.bringToFront();
|
|
520
522
|
}
|
|
@@ -656,6 +658,7 @@ class Playwright extends Helper {
|
|
|
656
658
|
const els = await this._locate(locator);
|
|
657
659
|
assertElementExists(els, locator);
|
|
658
660
|
this.context = els[0];
|
|
661
|
+
this.contextLocator = locator;
|
|
659
662
|
|
|
660
663
|
this.withinLocator = new Locator(locator);
|
|
661
664
|
}
|
|
@@ -663,6 +666,7 @@ class Playwright extends Helper {
|
|
|
663
666
|
async _withinEnd() {
|
|
664
667
|
this.withinLocator = null;
|
|
665
668
|
this.context = await this.page;
|
|
669
|
+
this.contextLocator = null;
|
|
666
670
|
}
|
|
667
671
|
|
|
668
672
|
_extractDataFromPerformanceTiming(timing, ...dataNames) {
|
|
@@ -1562,15 +1566,32 @@ class Playwright extends Helper {
|
|
|
1562
1566
|
return context.evaluate.apply(context, [fn, arg]);
|
|
1563
1567
|
}
|
|
1564
1568
|
|
|
1569
|
+
/**
|
|
1570
|
+
* Grab Locator if called within Context
|
|
1571
|
+
*
|
|
1572
|
+
* @param {*} locator
|
|
1573
|
+
*/
|
|
1574
|
+
_contextLocator(locator) {
|
|
1575
|
+
locator = buildLocatorString(new Locator(locator, 'css'));
|
|
1576
|
+
|
|
1577
|
+
if (this.contextLocator) {
|
|
1578
|
+
const contextLocator = buildLocatorString(new Locator(this.contextLocator, 'css'));
|
|
1579
|
+
locator = `${contextLocator} >> ${locator}`;
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
return locator;
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1565
1585
|
/**
|
|
1566
1586
|
* {{> grabTextFrom }}
|
|
1567
1587
|
*
|
|
1568
1588
|
*/
|
|
1569
1589
|
async grabTextFrom(locator) {
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1590
|
+
locator = this._contextLocator(locator);
|
|
1591
|
+
const text = await this.page.textContent(locator);
|
|
1592
|
+
assertElementExists(text, locator);
|
|
1593
|
+
this.debugSection('Text', text);
|
|
1594
|
+
return text;
|
|
1574
1595
|
}
|
|
1575
1596
|
|
|
1576
1597
|
/**
|
|
@@ -2093,6 +2114,7 @@ class Playwright extends Helper {
|
|
|
2093
2114
|
|
|
2094
2115
|
if (locator >= 0 && locator < childFrames.length) {
|
|
2095
2116
|
this.context = childFrames[locator];
|
|
2117
|
+
this.contextLocator = locator;
|
|
2096
2118
|
} else {
|
|
2097
2119
|
throw new Error('Element #invalidIframeSelector was not found by text|CSS|XPath');
|
|
2098
2120
|
}
|
|
@@ -2100,6 +2122,7 @@ class Playwright extends Helper {
|
|
|
2100
2122
|
}
|
|
2101
2123
|
if (!locator) {
|
|
2102
2124
|
this.context = this.page;
|
|
2125
|
+
this.contextLocator = null;
|
|
2103
2126
|
return;
|
|
2104
2127
|
}
|
|
2105
2128
|
|
|
@@ -2110,8 +2133,10 @@ class Playwright extends Helper {
|
|
|
2110
2133
|
|
|
2111
2134
|
if (contentFrame) {
|
|
2112
2135
|
this.context = contentFrame;
|
|
2136
|
+
this.contextLocator = null;
|
|
2113
2137
|
} else {
|
|
2114
2138
|
this.context = els[0];
|
|
2139
|
+
this.contextLocator = locator;
|
|
2115
2140
|
}
|
|
2116
2141
|
}
|
|
2117
2142
|
|
|
@@ -2228,6 +2253,19 @@ async function findElements(matcher, locator) {
|
|
|
2228
2253
|
return matcher.$$(buildLocatorString(locator));
|
|
2229
2254
|
}
|
|
2230
2255
|
|
|
2256
|
+
async function getVisibleElements(elements) {
|
|
2257
|
+
const visibleElements = [];
|
|
2258
|
+
for (const element of elements) {
|
|
2259
|
+
if (await element.isVisible()) {
|
|
2260
|
+
visibleElements.push(element);
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
if (visibleElements.length === 0) {
|
|
2264
|
+
return elements;
|
|
2265
|
+
}
|
|
2266
|
+
return visibleElements;
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2231
2269
|
async function proceedClick(locator, context = null, options = {}) {
|
|
2232
2270
|
let matcher = await this._getContext();
|
|
2233
2271
|
if (context) {
|
|
@@ -2247,7 +2285,8 @@ async function proceedClick(locator, context = null, options = {}) {
|
|
|
2247
2285
|
if (options.force) {
|
|
2248
2286
|
await els[0].dispatchEvent('click');
|
|
2249
2287
|
} else {
|
|
2250
|
-
await els[0]
|
|
2288
|
+
const element = els.length > 1 ? (await getVisibleElements(els))[0] : els[0];
|
|
2289
|
+
await element.click(options);
|
|
2251
2290
|
}
|
|
2252
2291
|
const promises = [];
|
|
2253
2292
|
if (options.waitForNavigation) {
|
|
@@ -2505,11 +2544,13 @@ async function targetCreatedHandler(page) {
|
|
|
2505
2544
|
// we are inside iframe?
|
|
2506
2545
|
const frameEl = await this.context.frameElement();
|
|
2507
2546
|
this.context = await frameEl.contentFrame();
|
|
2547
|
+
this.contextLocator = null;
|
|
2508
2548
|
return;
|
|
2509
2549
|
}
|
|
2510
2550
|
// if context element was in iframe - keep it
|
|
2511
2551
|
// if (await this.context.ownerFrame()) return;
|
|
2512
2552
|
this.context = page;
|
|
2553
|
+
this.contextLocator = null;
|
|
2513
2554
|
});
|
|
2514
2555
|
});
|
|
2515
2556
|
page.on('console', (msg) => {
|
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
|
}
|
|
@@ -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
|
|
|
@@ -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
|
|
package/lib/output.js
CHANGED
|
@@ -107,6 +107,11 @@ module.exports = function (config) {
|
|
|
107
107
|
if (allureReporter) {
|
|
108
108
|
allureReporter.addAttachment('Last Seen Screenshot', fs.readFileSync(path.join(global.output_dir, fileName)), 'image/png');
|
|
109
109
|
}
|
|
110
|
+
|
|
111
|
+
const cucumberReporter = Container.plugins('cucumberJsonReporter');
|
|
112
|
+
if (cucumberReporter) {
|
|
113
|
+
cucumberReporter.addScreenshot(test.artifacts.screenshot);
|
|
114
|
+
}
|
|
110
115
|
} catch (err) {
|
|
111
116
|
output.plugin(err);
|
|
112
117
|
if (
|
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeceptjs",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.7",
|
|
4
4
|
"description": "Supercharged End 2 End Testing Framework for NodeJS",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"acceptance",
|
|
@@ -84,7 +84,6 @@
|
|
|
84
84
|
"promise-retry": "^1.1.1",
|
|
85
85
|
"requireg": "^0.2.2",
|
|
86
86
|
"resq": "^1.10.0",
|
|
87
|
-
"semver": "^6.2.0",
|
|
88
87
|
"sprintf-js": "^1.1.1"
|
|
89
88
|
},
|
|
90
89
|
"devDependencies": {
|
|
@@ -127,6 +126,7 @@
|
|
|
127
126
|
"qrcode-terminal": "^0.12.0",
|
|
128
127
|
"rosie": "^1.6.0",
|
|
129
128
|
"runok": "^0.9.2",
|
|
129
|
+
"semver": "^6.3.0",
|
|
130
130
|
"sinon": "^9.2.2",
|
|
131
131
|
"sinon-chai": "^3.5.0",
|
|
132
132
|
"testcafe": "^1.9.4",
|
package/typings/index.d.ts
CHANGED
|
@@ -8,13 +8,13 @@ declare namespace CodeceptJS {
|
|
|
8
8
|
import("./utils").Translate<T, Translation.Actions>;
|
|
9
9
|
|
|
10
10
|
type Cookie = {
|
|
11
|
-
name: string
|
|
12
|
-
value: string
|
|
13
|
-
}
|
|
11
|
+
name: string;
|
|
12
|
+
value: string;
|
|
13
|
+
};
|
|
14
14
|
|
|
15
15
|
interface PageScrollPosition {
|
|
16
|
-
x: number
|
|
17
|
-
y: number
|
|
16
|
+
x: number;
|
|
17
|
+
y: number;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
// Could get extended by user generated typings
|
|
@@ -23,7 +23,7 @@ declare namespace CodeceptJS {
|
|
|
23
23
|
interface IHook {}
|
|
24
24
|
interface IScenario {}
|
|
25
25
|
interface IFeature {
|
|
26
|
-
(title: string): FeatureConfig
|
|
26
|
+
(title: string): FeatureConfig;
|
|
27
27
|
}
|
|
28
28
|
interface CallbackOrder extends Array<any> {}
|
|
29
29
|
interface SupportObject {
|
|
@@ -51,25 +51,48 @@ declare namespace CodeceptJS {
|
|
|
51
51
|
| { frame: string }
|
|
52
52
|
| { android: string }
|
|
53
53
|
| { ios: string }
|
|
54
|
-
| { android: string
|
|
55
|
-
| { react: string }
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
| { android: string; ios: string }
|
|
55
|
+
| { react: string }
|
|
56
|
+
| { shadow: string }
|
|
57
|
+
| { custom: string };
|
|
58
|
+
|
|
59
|
+
interface CustomLocators {}
|
|
60
|
+
type LocatorOrString =
|
|
61
|
+
| string
|
|
62
|
+
| ILocator
|
|
63
|
+
| Locator
|
|
64
|
+
| CustomLocators[keyof CustomLocators];
|
|
59
65
|
|
|
60
66
|
type StringOrSecret = string | CodeceptJS.Secret;
|
|
61
67
|
|
|
62
|
-
interface HookCallback {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
interface
|
|
68
|
+
interface HookCallback {
|
|
69
|
+
(args: SupportObject): void;
|
|
70
|
+
}
|
|
71
|
+
interface Scenario extends IScenario {
|
|
72
|
+
only: IScenario;
|
|
73
|
+
skip: IScenario;
|
|
74
|
+
todo: IScenario;
|
|
75
|
+
}
|
|
76
|
+
interface Feature extends IFeature {
|
|
77
|
+
skip: IFeature;
|
|
78
|
+
}
|
|
79
|
+
interface IData {
|
|
80
|
+
Scenario: IScenario;
|
|
81
|
+
only: { Scenario: IScenario };
|
|
82
|
+
}
|
|
66
83
|
|
|
67
84
|
interface IScenario {
|
|
68
85
|
// Scenario.todo can be called only with a title.
|
|
69
86
|
(title: string, callback?: HookCallback): ScenarioConfig;
|
|
70
|
-
(
|
|
87
|
+
(
|
|
88
|
+
title: string,
|
|
89
|
+
opts: { [key: string]: any },
|
|
90
|
+
callback: HookCallback
|
|
91
|
+
): ScenarioConfig;
|
|
92
|
+
}
|
|
93
|
+
interface IHook {
|
|
94
|
+
(callback: HookCallback): void;
|
|
71
95
|
}
|
|
72
|
-
interface IHook { (callback: HookCallback): void; }
|
|
73
96
|
|
|
74
97
|
interface Globals {
|
|
75
98
|
codeceptjs: typeof codeceptjs;
|
|
@@ -164,12 +187,12 @@ declare namespace Mocha {
|
|
|
164
187
|
}
|
|
165
188
|
|
|
166
189
|
interface Suite extends SuiteRunnable {
|
|
167
|
-
tags: any[]
|
|
168
|
-
comment: string
|
|
169
|
-
feature: any
|
|
190
|
+
tags: any[];
|
|
191
|
+
comment: string;
|
|
192
|
+
feature: any;
|
|
170
193
|
}
|
|
171
194
|
|
|
172
|
-
interface Test
|
|
195
|
+
interface Test extends Runnable {
|
|
173
196
|
tags: any[];
|
|
174
197
|
}
|
|
175
198
|
}
|
package/typings/types.d.ts
CHANGED
|
@@ -2363,7 +2363,7 @@ declare namespace CodeceptJS {
|
|
|
2363
2363
|
* This helper should be configured in codecept.json or codecept.conf.js
|
|
2364
2364
|
*
|
|
2365
2365
|
* * `url`: base url of website to be tested
|
|
2366
|
-
* * `browser`: a browser to test on, either: `chromium`, `firefox`, `webkit`. Default: chromium.
|
|
2366
|
+
* * `browser`: a browser to test on, either: `chromium`, `firefox`, `webkit`, `electron`. Default: chromium.
|
|
2367
2367
|
* * `show`: (optional, default: false) - show browser window.
|
|
2368
2368
|
* * `restart`: (optional, default: true) - restart browser between tests.
|
|
2369
2369
|
* * `disableScreenshots`: (optional, default: false) - don't save screenshot on failure.
|
|
@@ -2382,6 +2382,7 @@ declare namespace CodeceptJS {
|
|
|
2382
2382
|
* * `userAgent`: (optional) user-agent string.
|
|
2383
2383
|
* * `manualStart`: (optional, default: false) - do not start browser before a test, start it manually inside a helper with `this.helpers["Playwright"]._startBrowser()`.
|
|
2384
2384
|
* * `chromium`: (optional) pass additional chromium options
|
|
2385
|
+
* * `electron`: (optional) pass additional electron options
|
|
2385
2386
|
*
|
|
2386
2387
|
* #### Example #1: Wait for 0 network connections.
|
|
2387
2388
|
*
|
|
@@ -2426,7 +2427,7 @@ declare namespace CodeceptJS {
|
|
|
2426
2427
|
* }
|
|
2427
2428
|
* ```
|
|
2428
2429
|
*
|
|
2429
|
-
* #### Example #4: Connect to remote browser by specifying [websocket endpoint](https://
|
|
2430
|
+
* #### Example #4: Connect to remote browser by specifying [websocket endpoint](https://playwright.dev/docs/api/class-browsertype#browsertypeconnectparams)
|
|
2430
2431
|
*
|
|
2431
2432
|
* ```js
|
|
2432
2433
|
* {
|
|
@@ -2434,7 +2435,7 @@ declare namespace CodeceptJS {
|
|
|
2434
2435
|
* Playwright: {
|
|
2435
2436
|
* url: "http://localhost",
|
|
2436
2437
|
* chromium: {
|
|
2437
|
-
* browserWSEndpoint:
|
|
2438
|
+
* browserWSEndpoint: { wsEndpoint: 'ws://localhost:9222/devtools/browser/c5aa6160-b5bc-4d53-bb49-6ecb36cd2e0a' }
|
|
2438
2439
|
* }
|
|
2439
2440
|
* }
|
|
2440
2441
|
* }
|
|
@@ -2452,6 +2453,7 @@ declare namespace CodeceptJS {
|
|
|
2452
2453
|
* url: "http://localhost",
|
|
2453
2454
|
* show: true // headless mode not supported for extensions
|
|
2454
2455
|
* chromium: {
|
|
2456
|
+
* userDataDir: '/tmp/playwright-tmp', // necessary to launch the browser in normal mode instead of incognito,
|
|
2455
2457
|
* args: [
|
|
2456
2458
|
* `--disable-extensions-except=${pathToExtension}`,
|
|
2457
2459
|
* `--load-extension=${pathToExtension}`
|
|
@@ -3466,6 +3468,10 @@ declare namespace CodeceptJS {
|
|
|
3466
3468
|
* @param [arg] - optional argument to pass to the function
|
|
3467
3469
|
*/
|
|
3468
3470
|
executeScript(fn: string | ((...params: any[]) => any), arg?: any): Promise<any>;
|
|
3471
|
+
/**
|
|
3472
|
+
* Grab Locator if called within Context
|
|
3473
|
+
*/
|
|
3474
|
+
_contextLocator(locator: any): void;
|
|
3469
3475
|
/**
|
|
3470
3476
|
* Retrieves a text from an element located by CSS or XPath and returns it to test.
|
|
3471
3477
|
* Resumes test execution, so **should be used inside async with `await`** operator.
|
|
@@ -8451,6 +8457,7 @@ declare namespace CodeceptJS {
|
|
|
8451
8457
|
* @param value - text value to fill.
|
|
8452
8458
|
*
|
|
8453
8459
|
* {{ react }}
|
|
8460
|
+
* {{ custom }}
|
|
8454
8461
|
*/
|
|
8455
8462
|
fillField(field: CodeceptJS.LocatorOrString, value: CodeceptJS.StringOrSecret): void;
|
|
8456
8463
|
/**
|
|
@@ -9753,7 +9760,7 @@ declare namespace CodeceptJS {
|
|
|
9753
9760
|
/**
|
|
9754
9761
|
* Get current config.
|
|
9755
9762
|
*/
|
|
9756
|
-
static get(key
|
|
9763
|
+
static get(key?: string, val?: any): any;
|
|
9757
9764
|
/**
|
|
9758
9765
|
* Appends values to current config
|
|
9759
9766
|
*/
|
|
@@ -9868,6 +9875,7 @@ declare namespace CodeceptJS {
|
|
|
9868
9875
|
before: 'global.before';
|
|
9869
9876
|
after: 'global.after';
|
|
9870
9877
|
result: 'global.result';
|
|
9878
|
+
failures: 'global.failures';
|
|
9871
9879
|
};
|
|
9872
9880
|
const multiple: {
|
|
9873
9881
|
before: 'multiple.before';
|
|
@@ -10048,7 +10056,7 @@ declare namespace CodeceptJS {
|
|
|
10048
10056
|
* Print information for a process
|
|
10049
10057
|
* Used in multiple-run
|
|
10050
10058
|
*/
|
|
10051
|
-
function process(process: string): string;
|
|
10059
|
+
function process(process: string | null): string;
|
|
10052
10060
|
/**
|
|
10053
10061
|
* Print information in --debug mode
|
|
10054
10062
|
*/
|
|
@@ -10149,6 +10157,10 @@ declare namespace CodeceptJS {
|
|
|
10149
10157
|
* Get a list of all chained tasks
|
|
10150
10158
|
*/
|
|
10151
10159
|
scheduled(): string;
|
|
10160
|
+
/**
|
|
10161
|
+
* Get the queue id
|
|
10162
|
+
*/
|
|
10163
|
+
getQueueId(): number;
|
|
10152
10164
|
/**
|
|
10153
10165
|
* Get a state of current queue and tasks
|
|
10154
10166
|
*/
|