codeceptjs 3.5.4-beta.1 → 3.5.5
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 +368 -0
- package/README.md +0 -2
- package/docs/build/Appium.js +48 -7
- package/docs/build/GraphQL.js +25 -0
- package/docs/build/Nightmare.js +15 -6
- package/docs/build/Playwright.js +436 -197
- package/docs/build/Protractor.js +17 -8
- package/docs/build/Puppeteer.js +37 -20
- package/docs/build/TestCafe.js +19 -10
- package/docs/build/WebDriver.js +45 -37
- package/docs/changelog.md +375 -0
- package/docs/community-helpers.md +8 -4
- package/docs/examples.md +8 -2
- package/docs/helpers/Appium.md +39 -2
- package/docs/helpers/GraphQL.md +21 -0
- package/docs/helpers/Nightmare.md +1260 -0
- package/docs/helpers/Playwright.md +223 -119
- package/docs/helpers/Protractor.md +1711 -0
- package/docs/helpers/Puppeteer.md +31 -29
- package/docs/helpers/TestCafe.md +18 -17
- package/docs/helpers/WebDriver.md +34 -32
- package/docs/playwright.md +24 -1
- package/docs/webapi/dontSeeInField.mustache +1 -1
- package/docs/webapi/executeAsyncScript.mustache +2 -0
- package/docs/webapi/executeScript.mustache +2 -0
- package/docs/webapi/seeInField.mustache +1 -1
- package/docs/wiki/Books-&-Posts.md +0 -0
- package/docs/wiki/Community-Helpers-&-Plugins.md +8 -4
- package/docs/wiki/Converting-Playwright-to-Istanbul-Coverage.md +46 -14
- package/docs/wiki/Examples.md +8 -2
- package/docs/wiki/Google-Summer-of-Code-(GSoC)-2020.md +0 -0
- package/docs/wiki/Home.md +0 -0
- package/docs/wiki/Migration-to-Appium-v2---CodeceptJS.md +83 -0
- package/docs/wiki/Release-Process.md +0 -0
- package/docs/wiki/Roadmap.md +0 -0
- package/docs/wiki/Tests.md +0 -0
- package/docs/wiki/Upgrading-to-CodeceptJS-3.md +0 -0
- package/docs/wiki/Videos.md +0 -0
- package/lib/codecept.js +1 -0
- package/lib/command/definitions.js +2 -7
- package/lib/command/init.js +40 -4
- package/lib/command/run-multiple/collection.js +17 -5
- package/lib/command/run-workers.js +4 -0
- package/lib/command/run.js +6 -0
- package/lib/helper/Appium.js +46 -5
- package/lib/helper/GraphQL.js +25 -0
- package/lib/helper/Nightmare.js +1415 -0
- package/lib/helper/Playwright.js +336 -62
- package/lib/helper/Protractor.js +1837 -0
- package/lib/helper/Puppeteer.js +31 -18
- package/lib/helper/TestCafe.js +15 -8
- package/lib/helper/WebDriver.js +39 -35
- package/lib/helper/clientscripts/nightmare.js +213 -0
- package/lib/helper/errors/ElementNotFound.js +2 -1
- package/lib/helper/scripts/highlightElement.js +1 -1
- package/lib/interfaces/bdd.js +1 -1
- package/lib/mochaFactory.js +2 -1
- package/lib/pause.js +6 -4
- package/lib/plugin/heal.js +2 -3
- package/lib/plugin/selenoid.js +6 -1
- package/lib/step.js +27 -10
- package/lib/utils.js +4 -0
- package/lib/workers.js +3 -1
- package/package.json +87 -87
- package/typings/promiseBasedTypes.d.ts +163 -126
- package/typings/types.d.ts +183 -144
- package/docs/build/Polly.js +0 -42
- package/docs/build/SeleniumWebdriver.js +0 -76
package/lib/helper/Puppeteer.js
CHANGED
|
@@ -23,7 +23,7 @@ const {
|
|
|
23
23
|
screenshotOutputFolder,
|
|
24
24
|
getNormalizedKeyAttributeValue,
|
|
25
25
|
isModifierKey,
|
|
26
|
-
requireWithFallback,
|
|
26
|
+
requireWithFallback, normalizeSpacesInString,
|
|
27
27
|
} = require('../utils');
|
|
28
28
|
const {
|
|
29
29
|
isColorProperty,
|
|
@@ -69,7 +69,7 @@ const consoleLogStore = new Console();
|
|
|
69
69
|
* @prop {boolean} [manualStart=false] - do not start browser before a test, start it manually inside a helper with `this.helpers["Puppeteer"]._startBrowser()`.
|
|
70
70
|
* @prop {string} [browser=chrome] - can be changed to `firefox` when using [puppeteer-firefox](https://codecept.io/helpers/Puppeteer-firefox).
|
|
71
71
|
* @prop {object} [chrome] - pass additional [Puppeteer run options](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions).
|
|
72
|
-
* @prop {boolean} [highlightElement] - highlight the interacting elements
|
|
72
|
+
* @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false
|
|
73
73
|
*/
|
|
74
74
|
const config = {};
|
|
75
75
|
|
|
@@ -231,6 +231,7 @@ class Puppeteer extends Helper {
|
|
|
231
231
|
keepBrowserState: false,
|
|
232
232
|
show: false,
|
|
233
233
|
defaultPopupAction: 'accept',
|
|
234
|
+
highlightElement: false,
|
|
234
235
|
};
|
|
235
236
|
|
|
236
237
|
return Object.assign(defaults, config);
|
|
@@ -603,8 +604,8 @@ class Puppeteer extends Helper {
|
|
|
603
604
|
return this.switchTo(null)
|
|
604
605
|
.then(() => frame.reduce((p, frameLocator) => p.then(() => this.switchTo(frameLocator)), Promise.resolve()));
|
|
605
606
|
}
|
|
606
|
-
await this.switchTo(
|
|
607
|
-
this.withinLocator = new Locator(
|
|
607
|
+
await this.switchTo(frame);
|
|
608
|
+
this.withinLocator = new Locator(frame);
|
|
608
609
|
return;
|
|
609
610
|
}
|
|
610
611
|
|
|
@@ -765,8 +766,11 @@ class Puppeteer extends Helper {
|
|
|
765
766
|
const body = document.body;
|
|
766
767
|
const html = document.documentElement;
|
|
767
768
|
window.scrollTo(0, Math.max(
|
|
768
|
-
body.scrollHeight,
|
|
769
|
-
|
|
769
|
+
body.scrollHeight,
|
|
770
|
+
body.offsetHeight,
|
|
771
|
+
html.clientHeight,
|
|
772
|
+
html.scrollHeight,
|
|
773
|
+
html.offsetHeight,
|
|
770
774
|
));
|
|
771
775
|
});
|
|
772
776
|
}
|
|
@@ -1318,7 +1322,7 @@ class Puppeteer extends Helper {
|
|
|
1318
1322
|
await this._evaluateHandeInContext(el => el.innerHTML = '', el);
|
|
1319
1323
|
}
|
|
1320
1324
|
|
|
1321
|
-
highlightActiveElement.call(this, el, this.
|
|
1325
|
+
highlightActiveElement.call(this, el, await this._getContext());
|
|
1322
1326
|
await el.type(value.toString(), { delay: this.options.pressKeyDelay });
|
|
1323
1327
|
|
|
1324
1328
|
return this._waitForAction();
|
|
@@ -1339,7 +1343,7 @@ class Puppeteer extends Helper {
|
|
|
1339
1343
|
async appendField(field, value) {
|
|
1340
1344
|
const els = await findVisibleFields.call(this, field);
|
|
1341
1345
|
assertElementExists(els, field, 'Field');
|
|
1342
|
-
highlightActiveElement.call(this, els[0], this.
|
|
1346
|
+
highlightActiveElement.call(this, els[0], await this._getContext());
|
|
1343
1347
|
await els[0].press('End');
|
|
1344
1348
|
await els[0].type(value.toString(), { delay: this.options.pressKeyDelay });
|
|
1345
1349
|
return this._waitForAction();
|
|
@@ -1349,14 +1353,16 @@ class Puppeteer extends Helper {
|
|
|
1349
1353
|
* {{> seeInField }}
|
|
1350
1354
|
*/
|
|
1351
1355
|
async seeInField(field, value) {
|
|
1352
|
-
|
|
1356
|
+
const _value = (typeof value === 'boolean') ? value : value.toString();
|
|
1357
|
+
return proceedSeeInField.call(this, 'assert', field, _value);
|
|
1353
1358
|
}
|
|
1354
1359
|
|
|
1355
1360
|
/**
|
|
1356
1361
|
* {{> dontSeeInField }}
|
|
1357
1362
|
*/
|
|
1358
1363
|
async dontSeeInField(field, value) {
|
|
1359
|
-
|
|
1364
|
+
const _value = (typeof value === 'boolean') ? value : value.toString();
|
|
1365
|
+
return proceedSeeInField.call(this, 'negate', field, _value);
|
|
1360
1366
|
}
|
|
1361
1367
|
|
|
1362
1368
|
/**
|
|
@@ -1386,7 +1392,7 @@ class Puppeteer extends Helper {
|
|
|
1386
1392
|
if (await el.getProperty('tagName').then(t => t.jsonValue()) !== 'SELECT') {
|
|
1387
1393
|
throw new Error('Element is not <select>');
|
|
1388
1394
|
}
|
|
1389
|
-
highlightActiveElement.call(this, els[0], this.
|
|
1395
|
+
highlightActiveElement.call(this, els[0], await this._getContext());
|
|
1390
1396
|
if (!Array.isArray(option)) option = [option];
|
|
1391
1397
|
|
|
1392
1398
|
for (const key in option) {
|
|
@@ -2005,7 +2011,7 @@ class Puppeteer extends Helper {
|
|
|
2005
2011
|
assertElementExists(els, locator);
|
|
2006
2012
|
|
|
2007
2013
|
return this.waitForFunction(isElementClickable, [els[0]], waitTimeout).catch(async (e) => {
|
|
2008
|
-
if (/failed: timeout/i.test(e.message)) {
|
|
2014
|
+
if (/Waiting failed/i.test(e.message) || /failed: timeout/i.test(e.message)) {
|
|
2009
2015
|
throw new Error(`element ${new Locator(locator).toString()} still not clickable after ${waitTimeout || this.options.waitForTimeout / 1000} sec`);
|
|
2010
2016
|
} else {
|
|
2011
2017
|
throw e;
|
|
@@ -2109,7 +2115,7 @@ class Puppeteer extends Helper {
|
|
|
2109
2115
|
return currUrl.indexOf(urlPart) > -1;
|
|
2110
2116
|
}, { timeout: waitTimeout }, urlPart).catch(async (e) => {
|
|
2111
2117
|
const currUrl = await this._getPageUrl(); // Required because the waitForFunction can't return data.
|
|
2112
|
-
if (/failed: timeout/i.test(e.message)) {
|
|
2118
|
+
if (/Waiting failed:/i.test(e.message) || /failed: timeout/i.test(e.message)) {
|
|
2113
2119
|
throw new Error(`expected url to include ${urlPart}, but found ${currUrl}`);
|
|
2114
2120
|
} else {
|
|
2115
2121
|
throw e;
|
|
@@ -2133,7 +2139,7 @@ class Puppeteer extends Helper {
|
|
|
2133
2139
|
return currUrl.indexOf(urlPart) > -1;
|
|
2134
2140
|
}, { timeout: waitTimeout }, urlPart).catch(async (e) => {
|
|
2135
2141
|
const currUrl = await this._getPageUrl(); // Required because the waitForFunction can't return data.
|
|
2136
|
-
if (/failed: timeout/i.test(e.message)) {
|
|
2142
|
+
if (/Waiting failed/i.test(e.message) || /failed: timeout/i.test(e.message)) {
|
|
2137
2143
|
throw new Error(`expected url to be ${urlPart}, but found ${currUrl}`);
|
|
2138
2144
|
} else {
|
|
2139
2145
|
throw e;
|
|
@@ -2342,6 +2348,10 @@ async function findElements(matcher, locator) {
|
|
|
2342
2348
|
if (locator.react) return findReact(matcher.executionContext(), locator);
|
|
2343
2349
|
locator = new Locator(locator, 'css');
|
|
2344
2350
|
if (!locator.isXPath()) return matcher.$$(locator.simplify());
|
|
2351
|
+
// puppeteer version < 19.4.0 is no longer supported. This one is backward support.
|
|
2352
|
+
if (puppeteer.default?.defaultBrowserRevision) {
|
|
2353
|
+
return matcher.$$(`xpath/${locator.value}`);
|
|
2354
|
+
}
|
|
2345
2355
|
return matcher.$x(locator.value);
|
|
2346
2356
|
}
|
|
2347
2357
|
|
|
@@ -2359,7 +2369,7 @@ async function proceedClick(locator, context = null, options = {}) {
|
|
|
2359
2369
|
assertElementExists(els, locator, 'Clickable element');
|
|
2360
2370
|
}
|
|
2361
2371
|
|
|
2362
|
-
highlightActiveElement.call(this, els[0], this.
|
|
2372
|
+
highlightActiveElement.call(this, els[0], await this._getContext());
|
|
2363
2373
|
|
|
2364
2374
|
await els[0].click(options);
|
|
2365
2375
|
const promises = [];
|
|
@@ -2419,7 +2429,7 @@ async function proceedSee(assertType, text, context, strict = false) {
|
|
|
2419
2429
|
if (strict) {
|
|
2420
2430
|
return allText.map(elText => equals(description)[assertType](text, elText));
|
|
2421
2431
|
}
|
|
2422
|
-
return stringIncludes(description)[assertType](text, allText.join(' | '));
|
|
2432
|
+
return stringIncludes(description)[assertType](normalizeSpacesInString(text), normalizeSpacesInString(allText.join(' | ')));
|
|
2423
2433
|
}
|
|
2424
2434
|
|
|
2425
2435
|
async function findCheckable(locator, context) {
|
|
@@ -2596,7 +2606,10 @@ async function elementSelected(element) {
|
|
|
2596
2606
|
|
|
2597
2607
|
function isFrameLocator(locator) {
|
|
2598
2608
|
locator = new Locator(locator);
|
|
2599
|
-
if (locator.isFrame())
|
|
2609
|
+
if (locator.isFrame()) {
|
|
2610
|
+
const _locator = new Locator(locator);
|
|
2611
|
+
return _locator.value;
|
|
2612
|
+
}
|
|
2600
2613
|
return false;
|
|
2601
2614
|
}
|
|
2602
2615
|
|
|
@@ -2714,7 +2727,7 @@ function getNormalizedKey(key) {
|
|
|
2714
2727
|
}
|
|
2715
2728
|
|
|
2716
2729
|
function highlightActiveElement(element, context) {
|
|
2717
|
-
if (!this.options.
|
|
2730
|
+
if (!this.options.highlightElement && !store.debugMode) return;
|
|
2718
2731
|
|
|
2719
2732
|
highlightElement(element, context);
|
|
2720
2733
|
}
|
package/lib/helper/TestCafe.js
CHANGED
|
@@ -20,7 +20,7 @@ const { urlEquals } = require('../assert/equal');
|
|
|
20
20
|
const { empty } = require('../assert/empty');
|
|
21
21
|
const { truth } = require('../assert/truth');
|
|
22
22
|
const {
|
|
23
|
-
xpathLocator,
|
|
23
|
+
xpathLocator, normalizeSpacesInString,
|
|
24
24
|
} = require('../utils');
|
|
25
25
|
const Locator = require('../locator');
|
|
26
26
|
|
|
@@ -637,9 +637,9 @@ class TestCafe extends Helper {
|
|
|
637
637
|
async see(text, context = null) {
|
|
638
638
|
let els;
|
|
639
639
|
if (context) {
|
|
640
|
-
els = (await findElements.call(this, this.context, context)).withText(text);
|
|
640
|
+
els = (await findElements.call(this, this.context, context)).withText(normalizeSpacesInString(text));
|
|
641
641
|
} else {
|
|
642
|
-
els = (await findElements.call(this, this.context, '*')).withText(text);
|
|
642
|
+
els = (await findElements.call(this, this.context, '*')).withText(normalizeSpacesInString(text));
|
|
643
643
|
}
|
|
644
644
|
|
|
645
645
|
return this.t
|
|
@@ -727,13 +727,14 @@ class TestCafe extends Helper {
|
|
|
727
727
|
* {{> seeInField }}
|
|
728
728
|
*/
|
|
729
729
|
async seeInField(field, value) {
|
|
730
|
+
const _value = (typeof value === 'boolean') ? value : value.toString();
|
|
730
731
|
// const expectedValue = findElements.call(this, this.context, field).value;
|
|
731
732
|
const els = await findFields.call(this, field);
|
|
732
733
|
assertElementExists(els, field, 'Field');
|
|
733
734
|
const el = await els.nth(0);
|
|
734
735
|
|
|
735
736
|
return this.t
|
|
736
|
-
.expect(await el.value).eql(
|
|
737
|
+
.expect(await el.value).eql(_value)
|
|
737
738
|
.catch(mapError);
|
|
738
739
|
}
|
|
739
740
|
|
|
@@ -741,13 +742,14 @@ class TestCafe extends Helper {
|
|
|
741
742
|
* {{> dontSeeInField }}
|
|
742
743
|
*/
|
|
743
744
|
async dontSeeInField(field, value) {
|
|
745
|
+
const _value = (typeof value === 'boolean') ? value : value.toString();
|
|
744
746
|
// const expectedValue = findElements.call(this, this.context, field).value;
|
|
745
747
|
const els = await findFields.call(this, field);
|
|
746
748
|
assertElementExists(els, field, 'Field');
|
|
747
749
|
const el = await els.nth(0);
|
|
748
750
|
|
|
749
751
|
return this.t
|
|
750
|
-
.expect(el.value).notEql(
|
|
752
|
+
.expect(el.value).notEql(_value)
|
|
751
753
|
.catch(mapError);
|
|
752
754
|
}
|
|
753
755
|
|
|
@@ -960,8 +962,11 @@ class TestCafe extends Helper {
|
|
|
960
962
|
const body = document.body;
|
|
961
963
|
const html = document.documentElement;
|
|
962
964
|
window.scrollTo(0, Math.max(
|
|
963
|
-
body.scrollHeight,
|
|
964
|
-
|
|
965
|
+
body.scrollHeight,
|
|
966
|
+
body.offsetHeight,
|
|
967
|
+
html.clientHeight,
|
|
968
|
+
html.scrollHeight,
|
|
969
|
+
html.offsetHeight,
|
|
965
970
|
));
|
|
966
971
|
}).with({ boundTestRun: this.t })().catch(mapError);
|
|
967
972
|
}
|
|
@@ -1220,7 +1225,9 @@ class TestCafe extends Helper {
|
|
|
1220
1225
|
}
|
|
1221
1226
|
|
|
1222
1227
|
async function waitForFunction(browserFn, waitTimeout) {
|
|
1223
|
-
const pause = () => new Promise((done =>
|
|
1228
|
+
const pause = () => new Promise((done => {
|
|
1229
|
+
setTimeout(done, 50);
|
|
1230
|
+
}));
|
|
1224
1231
|
|
|
1225
1232
|
const start = Date.now();
|
|
1226
1233
|
// eslint-disable-next-line no-constant-condition
|
package/lib/helper/WebDriver.js
CHANGED
|
@@ -62,7 +62,7 @@ const webRoot = 'body';
|
|
|
62
62
|
* @prop {object} [desiredCapabilities] Selenium's [desired capabilities](https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities).
|
|
63
63
|
* @prop {boolean} [manualStart=false] - do not start browser before a test, start it manually inside a helper with `this.helpers["WebDriver"]._startBrowser()`.
|
|
64
64
|
* @prop {object} [timeouts] [WebDriver timeouts](http://webdriver.io/docs/timeouts.html) defined as hash.
|
|
65
|
-
* @prop {boolean} [highlightElement] - highlight the interacting elements
|
|
65
|
+
* @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false
|
|
66
66
|
*/
|
|
67
67
|
const config = {};
|
|
68
68
|
|
|
@@ -429,6 +429,7 @@ class WebDriver extends Helper {
|
|
|
429
429
|
keepCookies: false,
|
|
430
430
|
keepBrowserState: false,
|
|
431
431
|
deprecationWarnings: false,
|
|
432
|
+
highlightElement: false,
|
|
432
433
|
};
|
|
433
434
|
|
|
434
435
|
// override defaults with config
|
|
@@ -1347,7 +1348,8 @@ class WebDriver extends Helper {
|
|
|
1347
1348
|
*
|
|
1348
1349
|
*/
|
|
1349
1350
|
async seeInField(field, value) {
|
|
1350
|
-
|
|
1351
|
+
const _value = (typeof value === 'boolean') ? value : value.toString();
|
|
1352
|
+
return proceedSeeField.call(this, 'assert', field, _value);
|
|
1351
1353
|
}
|
|
1352
1354
|
|
|
1353
1355
|
/**
|
|
@@ -1355,7 +1357,8 @@ class WebDriver extends Helper {
|
|
|
1355
1357
|
*
|
|
1356
1358
|
*/
|
|
1357
1359
|
async dontSeeInField(field, value) {
|
|
1358
|
-
|
|
1360
|
+
const _value = (typeof value === 'boolean') ? value : value.toString();
|
|
1361
|
+
return proceedSeeField.call(this, 'negate', field, _value);
|
|
1359
1362
|
}
|
|
1360
1363
|
|
|
1361
1364
|
/**
|
|
@@ -2062,7 +2065,9 @@ class WebDriver extends Helper {
|
|
|
2062
2065
|
* {{> wait }}
|
|
2063
2066
|
*/
|
|
2064
2067
|
async wait(sec) {
|
|
2065
|
-
return new Promise(resolve =>
|
|
2068
|
+
return new Promise(resolve => {
|
|
2069
|
+
setTimeout(resolve, sec * 1000);
|
|
2070
|
+
});
|
|
2066
2071
|
}
|
|
2067
2072
|
|
|
2068
2073
|
/**
|
|
@@ -2167,20 +2172,18 @@ class WebDriver extends Helper {
|
|
|
2167
2172
|
const aSec = sec || this.options.waitForTimeoutInSeconds;
|
|
2168
2173
|
const _context = context || this.root;
|
|
2169
2174
|
|
|
2170
|
-
return this.browser.waitUntil(
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
},
|
|
2183
|
-
);
|
|
2175
|
+
return this.browser.waitUntil(async () => {
|
|
2176
|
+
const res = await this.$$(withStrictLocator.call(this, _context));
|
|
2177
|
+
if (!res || res.length === 0) return false;
|
|
2178
|
+
const selected = await forEachAsync(res, async el => this.browser.getElementText(getElementId(el)));
|
|
2179
|
+
if (Array.isArray(selected)) {
|
|
2180
|
+
return selected.filter(part => part.indexOf(text) >= 0).length > 0;
|
|
2181
|
+
}
|
|
2182
|
+
return selected.indexOf(text) >= 0;
|
|
2183
|
+
}, {
|
|
2184
|
+
timeout: aSec * 1000,
|
|
2185
|
+
timeoutMsg: `element (${_context}) is not in DOM or there is no element(${_context}) with text "${text}" after ${aSec} sec`,
|
|
2186
|
+
});
|
|
2184
2187
|
}
|
|
2185
2188
|
|
|
2186
2189
|
/**
|
|
@@ -2190,20 +2193,18 @@ class WebDriver extends Helper {
|
|
|
2190
2193
|
const client = this.browser;
|
|
2191
2194
|
const aSec = sec || this.options.waitForTimeoutInSeconds;
|
|
2192
2195
|
|
|
2193
|
-
return client.waitUntil(
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
},
|
|
2206
|
-
);
|
|
2196
|
+
return client.waitUntil(async () => {
|
|
2197
|
+
const res = await findFields.call(this, field);
|
|
2198
|
+
if (!res || res.length === 0) return false;
|
|
2199
|
+
const selected = await forEachAsync(res, async el => el.getValue());
|
|
2200
|
+
if (Array.isArray(selected)) {
|
|
2201
|
+
return selected.filter(part => part.indexOf(value) >= 0).length > 0;
|
|
2202
|
+
}
|
|
2203
|
+
return selected.indexOf(value) >= 0;
|
|
2204
|
+
}, {
|
|
2205
|
+
timeout: aSec * 1000,
|
|
2206
|
+
timeoutMsg: `element (${field}) is not in DOM or there is no element(${field}) with value "${value}" after ${aSec} sec`,
|
|
2207
|
+
});
|
|
2207
2208
|
}
|
|
2208
2209
|
|
|
2209
2210
|
/**
|
|
@@ -2411,8 +2412,11 @@ class WebDriver extends Helper {
|
|
|
2411
2412
|
const body = document.body;
|
|
2412
2413
|
const html = document.documentElement;
|
|
2413
2414
|
window.scrollTo(0, Math.max(
|
|
2414
|
-
body.scrollHeight,
|
|
2415
|
-
|
|
2415
|
+
body.scrollHeight,
|
|
2416
|
+
body.offsetHeight,
|
|
2417
|
+
html.clientHeight,
|
|
2418
|
+
html.scrollHeight,
|
|
2419
|
+
html.offsetHeight
|
|
2416
2420
|
));
|
|
2417
2421
|
});
|
|
2418
2422
|
/* eslint-enable */
|
|
@@ -2914,7 +2918,7 @@ function isModifierKey(key) {
|
|
|
2914
2918
|
}
|
|
2915
2919
|
|
|
2916
2920
|
function highlightActiveElement(element) {
|
|
2917
|
-
if (!this.options.
|
|
2921
|
+
if (!this.options.highlightElement && !store.debugMode) return;
|
|
2918
2922
|
|
|
2919
2923
|
highlightElement(element, this.browser);
|
|
2920
2924
|
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
if (!window.codeceptjs) {
|
|
2
|
+
/**
|
|
3
|
+
* @alias CodeceptJS.browserCodecept
|
|
4
|
+
* @namespace
|
|
5
|
+
*/
|
|
6
|
+
const codeceptjs = {};
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* all found elements are stored here for reuse
|
|
10
|
+
* @inner
|
|
11
|
+
* @type {Node[]}
|
|
12
|
+
*/
|
|
13
|
+
codeceptjs.elements = [];
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* global context changer
|
|
17
|
+
* @inner
|
|
18
|
+
* @type {?Node}
|
|
19
|
+
*/
|
|
20
|
+
codeceptjs.within = null;
|
|
21
|
+
|
|
22
|
+
// save
|
|
23
|
+
const storeElement = function (el) {
|
|
24
|
+
if (!el) return;
|
|
25
|
+
return codeceptjs.elements.push(el) - 1; // return index
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const storeElements = function (els) {
|
|
29
|
+
return els.map(el => storeElement(el));
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* finders
|
|
34
|
+
* @param {number} id
|
|
35
|
+
* @return {Node}
|
|
36
|
+
*/
|
|
37
|
+
codeceptjs.fetchElement = function (id) {
|
|
38
|
+
if (!this.elements[id]) throw new Error(`Element (${id}) is not accessible`);
|
|
39
|
+
return this.elements[id];
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param {string} by
|
|
44
|
+
* @param {CodeceptJS.ILocator} locator
|
|
45
|
+
* @param {*} [contextEl]
|
|
46
|
+
* @return {number[]}
|
|
47
|
+
*/
|
|
48
|
+
codeceptjs.findAndStoreElements = function (by, locator, contextEl) {
|
|
49
|
+
return storeElements(this.findElements(by, locator, contextEl));
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @param {string} by
|
|
54
|
+
* @param {CodeceptJS.ILocator} locator
|
|
55
|
+
* @param {*} [contextEl]
|
|
56
|
+
* @return {number | undefined}
|
|
57
|
+
*/
|
|
58
|
+
codeceptjs.findAndStoreElement = function (by, locator, contextEl) {
|
|
59
|
+
return storeElement(this.findElement(by, locator, contextEl));
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @param {string} by
|
|
64
|
+
* @param {CodeceptJS.ILocator} locator
|
|
65
|
+
*/
|
|
66
|
+
codeceptjs.setWithin = function (by, locator) {
|
|
67
|
+
this.within = this.findElement(by, locator);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @param {string} by
|
|
72
|
+
* @param {CodeceptJS.ILocator} locator
|
|
73
|
+
* @param {*} [contextEl]
|
|
74
|
+
* @return {Node[]}
|
|
75
|
+
*/
|
|
76
|
+
codeceptjs.findElements = function (by, locator, contextEl) {
|
|
77
|
+
let context;
|
|
78
|
+
if (typeof contextEl !== 'number') {
|
|
79
|
+
context = this.within || document;
|
|
80
|
+
} else {
|
|
81
|
+
context = this.fetchElement(contextEl);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (by === 'name') {
|
|
85
|
+
by = 'css';
|
|
86
|
+
locator = `*[name="${locator}"]`;
|
|
87
|
+
}
|
|
88
|
+
if (by === 'id') {
|
|
89
|
+
by = 'css';
|
|
90
|
+
locator = `#${locator}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (by === 'css') {
|
|
94
|
+
const found = context.querySelectorAll(locator);
|
|
95
|
+
const res = [];
|
|
96
|
+
for (let i = 0; i < found.length; i++) {
|
|
97
|
+
res.push(found[i]);
|
|
98
|
+
}
|
|
99
|
+
return res;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (by === 'xpath') {
|
|
103
|
+
const found = document.evaluate(locator, context, null, 5, null);
|
|
104
|
+
const res = [];
|
|
105
|
+
let current = null;
|
|
106
|
+
while (current = found.iterateNext()) {
|
|
107
|
+
res.push(current);
|
|
108
|
+
}
|
|
109
|
+
return res;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (by === 'frame') {
|
|
113
|
+
if (!Array.isArray(locator)) locator = [locator];
|
|
114
|
+
return [locator.reduce((parent, child) => parent.querySelector(child).contentDocument, window.document).querySelector('body')];
|
|
115
|
+
}
|
|
116
|
+
return [];
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @param {string} by
|
|
121
|
+
* @param {CodeceptJS.ILocator} locator
|
|
122
|
+
* @param {*} [context]
|
|
123
|
+
* @return {?Node}
|
|
124
|
+
*/
|
|
125
|
+
codeceptjs.findElement = function (by, locator, context) {
|
|
126
|
+
return this.findElements(by, locator, context)[0] || null;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// actions
|
|
130
|
+
/**
|
|
131
|
+
* @param {number} el
|
|
132
|
+
* @return {boolean}
|
|
133
|
+
*/
|
|
134
|
+
codeceptjs.clickEl = function (el) {
|
|
135
|
+
if (document.activeElement instanceof HTMLElement) {
|
|
136
|
+
document.activeElement.blur();
|
|
137
|
+
}
|
|
138
|
+
const event = document.createEvent('MouseEvent');
|
|
139
|
+
event.initEvent('click', true, true);
|
|
140
|
+
return this.fetchElement(el).dispatchEvent(event);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/** @param {number} el */
|
|
144
|
+
codeceptjs.doubleClickEl = function (el) {
|
|
145
|
+
if (document.activeElement instanceof HTMLElement) {
|
|
146
|
+
document.activeElement.blur();
|
|
147
|
+
}
|
|
148
|
+
const event = document.createEvent('MouseEvent');
|
|
149
|
+
event.initEvent('dblclick', true, true);
|
|
150
|
+
this.fetchElement(el).dispatchEvent(event);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* @param {number} el
|
|
155
|
+
* @param {number | undefined} x
|
|
156
|
+
* @param {number | undefined} y
|
|
157
|
+
*/
|
|
158
|
+
codeceptjs.hoverEl = function (el, x, y) {
|
|
159
|
+
if (document.activeElement instanceof HTMLElement) {
|
|
160
|
+
document.activeElement.blur();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const event = new MouseEvent('mouseover', {
|
|
164
|
+
bubbles: true,
|
|
165
|
+
cancelable: true,
|
|
166
|
+
screenX: x,
|
|
167
|
+
screenY: y,
|
|
168
|
+
clientX: x,
|
|
169
|
+
clientY: y,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
this.fetchElement(el).dispatchEvent(event);
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
/** @param {number} el */
|
|
176
|
+
codeceptjs.rightClickEl = function (el) {
|
|
177
|
+
const event = new MouseEvent('contextmenu', {
|
|
178
|
+
bubbles: true,
|
|
179
|
+
cancelable: true,
|
|
180
|
+
view: window,
|
|
181
|
+
buttons: 2,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
this.fetchElement(el).dispatchEvent(event);
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* @param {number} el
|
|
189
|
+
* @return {boolean | undefined}
|
|
190
|
+
*/
|
|
191
|
+
codeceptjs.checkEl = function (el) {
|
|
192
|
+
const element = this.fetchElement(el);
|
|
193
|
+
const event = document.createEvent('HTMLEvents');
|
|
194
|
+
if (element.checked) return;
|
|
195
|
+
element.checked = true;
|
|
196
|
+
event.initEvent('change', true, true);
|
|
197
|
+
return element.dispatchEvent(event);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* @param {number} el
|
|
202
|
+
* @return {boolean}
|
|
203
|
+
*/
|
|
204
|
+
codeceptjs.unCheckEl = function (el) {
|
|
205
|
+
const element = this.fetchElement(el);
|
|
206
|
+
const event = document.createEvent('HTMLEvents');
|
|
207
|
+
element.checked = false;
|
|
208
|
+
event.initEvent('change', true, true);
|
|
209
|
+
return element.dispatchEvent(event);
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
window.codeceptjs = codeceptjs;
|
|
213
|
+
}
|
|
@@ -5,7 +5,8 @@ const Locator = require('../../locator');
|
|
|
5
5
|
*/
|
|
6
6
|
class ElementNotFound {
|
|
7
7
|
constructor(
|
|
8
|
-
locator,
|
|
8
|
+
locator,
|
|
9
|
+
prefixMessage = 'Element',
|
|
9
10
|
postfixMessage = 'was not found by text|CSS|XPath',
|
|
10
11
|
) {
|
|
11
12
|
if (typeof locator === 'object') {
|
|
@@ -8,7 +8,7 @@ module.exports.highlightElement = (element, context) => {
|
|
|
8
8
|
|
|
9
9
|
try {
|
|
10
10
|
// Playwright, Puppeteer
|
|
11
|
-
context.evaluate(clientSideHighlightFn, element);
|
|
11
|
+
context.evaluate(clientSideHighlightFn, element).catch(err => console.error(err));
|
|
12
12
|
} catch (e) {
|
|
13
13
|
// WebDriver
|
|
14
14
|
try {
|
package/lib/interfaces/bdd.js
CHANGED
|
@@ -30,7 +30,7 @@ const parameterTypeRegistry = new ParameterTypeRegistry();
|
|
|
30
30
|
const matchStep = (step) => {
|
|
31
31
|
for (const stepName in steps) {
|
|
32
32
|
if (stepName.indexOf('/') === 0) {
|
|
33
|
-
const regExpArr = stepName.match(
|
|
33
|
+
const regExpArr = stepName.match(/^\/(.*?)\/([gimy]*)$/) || [];
|
|
34
34
|
const res = step.match(new RegExp(regExpArr[1], regExpArr[2]));
|
|
35
35
|
if (res) {
|
|
36
36
|
const fn = steps[stepName];
|
package/lib/mochaFactory.js
CHANGED
|
@@ -97,7 +97,8 @@ class MochaFactory {
|
|
|
97
97
|
const attributes = Object.getOwnPropertyDescriptor(reporterOptions, 'codeceptjs-cli-reporter');
|
|
98
98
|
if (reporterOptions['codeceptjs-cli-reporter'] && attributes) {
|
|
99
99
|
Object.defineProperty(
|
|
100
|
-
reporterOptions,
|
|
100
|
+
reporterOptions,
|
|
101
|
+
'codeceptjs/lib/cli',
|
|
101
102
|
attributes,
|
|
102
103
|
);
|
|
103
104
|
delete reporterOptions['codeceptjs-cli-reporter'];
|
package/lib/pause.js
CHANGED
|
@@ -22,6 +22,7 @@ const aiAssistant = new AiAssistant();
|
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Pauses test execution and starts interactive shell
|
|
25
|
+
* @param {Object<string, *>} [passedObject]
|
|
25
26
|
*/
|
|
26
27
|
const pause = function (passedObject = {}) {
|
|
27
28
|
if (store.dryRun) return;
|
|
@@ -67,6 +68,7 @@ function pauseSession(passedObject = {}) {
|
|
|
67
68
|
});
|
|
68
69
|
return new Promise(((resolve) => {
|
|
69
70
|
finish = resolve;
|
|
71
|
+
// eslint-disable-next-line
|
|
70
72
|
return askForStep();
|
|
71
73
|
}));
|
|
72
74
|
}
|
|
@@ -114,13 +116,13 @@ async function parseInput(cmd) {
|
|
|
114
116
|
isAiCommand = true;
|
|
115
117
|
executeCommand = executeCommand.then(async () => {
|
|
116
118
|
try {
|
|
117
|
-
const html = await res;
|
|
119
|
+
const html = await res;
|
|
118
120
|
aiAssistant.setHtmlContext(html);
|
|
119
121
|
} catch (err) {
|
|
120
122
|
output.print(output.styles.error(' ERROR '), 'Can\'t get HTML context', err.stack);
|
|
121
123
|
return;
|
|
122
124
|
} finally {
|
|
123
|
-
output.level(currentOutputLevel);
|
|
125
|
+
output.level(currentOutputLevel);
|
|
124
126
|
}
|
|
125
127
|
// aiAssistant.mockResponse("```js\nI.click('Sign in');\n```");
|
|
126
128
|
const spinner = ora("Processing OpenAI request...").start();
|
|
@@ -148,12 +150,12 @@ async function parseInput(cmd) {
|
|
|
148
150
|
})
|
|
149
151
|
|
|
150
152
|
const val = await executeCommand;
|
|
151
|
-
|
|
153
|
+
|
|
152
154
|
if (isCustomCommand) {
|
|
153
155
|
if (val !== undefined) console.log('Result', '$res=', val); // eslint-disable-line
|
|
154
156
|
$res = val;
|
|
155
157
|
}
|
|
156
|
-
|
|
158
|
+
|
|
157
159
|
if (cmd?.startsWith('I.see') || cmd?.startsWith('I.dontSee')) {
|
|
158
160
|
output.print(output.styles.success(' OK '), cmd);
|
|
159
161
|
}
|