codeceptjs 2.4.3 → 2.6.2
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 +117 -0
- package/README.md +32 -7
- package/bin/codecept.js +3 -0
- package/docs/basics.md +11 -5
- package/docs/bdd.md +4 -4
- package/docs/build/MockRequest.js +3 -0
- package/docs/build/Nightmare.js +10 -2
- package/docs/build/Playwright.js +3187 -0
- package/docs/build/Protractor.js +16 -2
- package/docs/build/Puppeteer.js +126 -19
- package/docs/build/REST.js +3 -1
- package/docs/build/TestCafe.js +11 -3
- package/docs/build/WebDriver.js +361 -28
- package/docs/changelog.md +116 -0
- package/docs/configuration.md +2 -2
- package/docs/custom-helpers.md +55 -10
- package/docs/helpers/Appium.md +81 -1
- package/docs/helpers/MockRequest.md +281 -38
- package/docs/helpers/Nightmare.md +10 -2
- package/docs/helpers/Playwright.md +1770 -0
- package/docs/helpers/Protractor.md +15 -3
- package/docs/helpers/Puppeteer-firefox.md +32 -1
- package/docs/helpers/Puppeteer.md +126 -76
- package/docs/helpers/TestCafe.md +10 -2
- package/docs/helpers/WebDriver.md +208 -118
- package/docs/locators.md +2 -0
- package/docs/playwright.md +306 -0
- package/docs/plugins.md +103 -0
- package/docs/reports.md +12 -0
- package/docs/shadow.md +68 -0
- package/docs/visual.md +0 -73
- package/docs/webapi/forceClick.mustache +27 -0
- package/docs/webapi/seeInPopup.mustache +7 -0
- package/docs/webapi/setCookie.mustache +10 -2
- package/docs/webapi/type.mustache +12 -0
- package/docs/webdriver.md +7 -3
- package/lib/codecept.js +1 -1
- package/lib/command/definitions.js +2 -2
- package/lib/command/generate.js +4 -4
- package/lib/command/gherkin/snippets.js +4 -4
- package/lib/command/init.js +1 -1
- package/lib/command/interactive.js +3 -0
- package/lib/command/run-multiple.js +2 -2
- package/lib/command/run-rerun.js +2 -0
- package/lib/command/run-workers.js +22 -8
- package/lib/command/run.js +2 -0
- package/lib/command/workers/runTests.js +1 -0
- package/lib/container.js +1 -1
- package/lib/event.js +2 -0
- package/lib/helper/MockRequest.js +3 -0
- package/lib/helper/Playwright.js +2422 -0
- package/lib/helper/Protractor.js +1 -2
- package/lib/helper/Puppeteer.js +84 -19
- package/lib/helper/REST.js +3 -1
- package/lib/helper/TestCafe.js +1 -1
- package/lib/helper/WebDriver.js +313 -26
- package/lib/helper/extras/PlaywrightPropEngine.js +53 -0
- package/lib/helper/scripts/isElementClickable.js +54 -14
- package/lib/interfaces/gherkin.js +1 -1
- package/lib/listener/helpers.js +3 -0
- package/lib/locator.js +5 -0
- package/lib/mochaFactory.js +12 -10
- package/lib/plugin/allure.js +8 -1
- package/lib/plugin/autoDelay.js +1 -8
- package/lib/plugin/commentStep.js +133 -0
- package/lib/plugin/screenshotOnFail.js +3 -10
- package/lib/plugin/selenoid.js +2 -2
- package/lib/plugin/standardActingHelpers.js +13 -0
- package/lib/plugin/stepByStepReport.js +1 -8
- package/lib/plugin/wdio.js +10 -1
- package/lib/reporter/cli.js +30 -1
- package/lib/session.js +7 -4
- package/package.json +13 -10
- package/typings/Mocha.d.ts +567 -16
- package/typings/index.d.ts +9 -5
- package/typings/types.d.ts +1634 -74
package/docs/build/WebDriver.js
CHANGED
|
@@ -2,6 +2,7 @@ let webdriverio;
|
|
|
2
2
|
|
|
3
3
|
const assert = require('assert');
|
|
4
4
|
const path = require('path');
|
|
5
|
+
const fs = require('fs');
|
|
5
6
|
const requireg = require('requireg');
|
|
6
7
|
|
|
7
8
|
const Helper = require('../helper');
|
|
@@ -28,8 +29,10 @@ const ElementNotFound = require('./errors/ElementNotFound');
|
|
|
28
29
|
const ConnectionRefused = require('./errors/ConnectionRefused');
|
|
29
30
|
const Locator = require('../locator');
|
|
30
31
|
|
|
32
|
+
const SHADOW = 'shadow';
|
|
31
33
|
const webRoot = 'body';
|
|
32
34
|
|
|
35
|
+
let version;
|
|
33
36
|
/**
|
|
34
37
|
* WebDriver helper which wraps [webdriverio](http://webdriver.io/) library to
|
|
35
38
|
* manipulate browser using Selenium WebDriver or PhantomJS.
|
|
@@ -380,10 +383,22 @@ class WebDriver extends Helper {
|
|
|
380
383
|
if (webdriverio.VERSION && webdriverio.VERSION.indexOf('4') === 0) {
|
|
381
384
|
throw new Error(`This helper is compatible with "webdriverio@5". Current version: ${webdriverio.VERSION}. Please upgrade webdriverio to v5+ or use WebDriverIO helper instead`);
|
|
382
385
|
}
|
|
386
|
+
try {
|
|
387
|
+
version = JSON.parse(fs.readFileSync(path.join(requireg.resolve('webdriverio'), '/../../', 'package.json')).toString()).version;
|
|
388
|
+
} catch (err) {
|
|
389
|
+
this.debug('Can\'t detect webdriverio version, assuming webdriverio v6 is used');
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (isWebDriver5()) {
|
|
393
|
+
console.log('DEPRECATION NOTICE:');
|
|
394
|
+
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');
|
|
395
|
+
}
|
|
383
396
|
// set defaults
|
|
384
397
|
this.root = webRoot;
|
|
385
398
|
this.isWeb = true;
|
|
386
399
|
this.isRunning = false;
|
|
400
|
+
this.sessionWindows = {};
|
|
401
|
+
this.activeSessionName = '';
|
|
387
402
|
|
|
388
403
|
this._setConfig(config);
|
|
389
404
|
|
|
@@ -402,6 +417,7 @@ class WebDriver extends Helper {
|
|
|
402
417
|
_validateConfig(config) {
|
|
403
418
|
const defaults = {
|
|
404
419
|
logLevel: 'silent',
|
|
420
|
+
path: '/wd/hub',
|
|
405
421
|
// codeceptjs
|
|
406
422
|
remoteFileUpload: true,
|
|
407
423
|
smartWait: 0,
|
|
@@ -500,6 +516,11 @@ class WebDriver extends Helper {
|
|
|
500
516
|
if (this.options.multiremote) {
|
|
501
517
|
this.browser = await webdriverio.multiremote(this.options.multiremote);
|
|
502
518
|
} else {
|
|
519
|
+
// remove non w3c capabilities
|
|
520
|
+
delete this.options.capabilities.protocol;
|
|
521
|
+
delete this.options.capabilities.hostname;
|
|
522
|
+
delete this.options.capabilities.port;
|
|
523
|
+
delete this.options.capabilities.path;
|
|
503
524
|
this.browser = await webdriverio.remote(this.options);
|
|
504
525
|
}
|
|
505
526
|
} catch (err) {
|
|
@@ -563,12 +584,12 @@ class WebDriver extends Helper {
|
|
|
563
584
|
_session() {
|
|
564
585
|
const defaultSession = this.browser;
|
|
565
586
|
return {
|
|
566
|
-
start: async (opts) => {
|
|
587
|
+
start: async (sessionName, opts) => {
|
|
567
588
|
// opts.disableScreenshots = true; // screenshots cant be saved as session will be already closed
|
|
568
589
|
opts = this._validateConfig(Object.assign(this.options, opts));
|
|
569
590
|
this.debugSection('New Browser', JSON.stringify(opts));
|
|
570
591
|
const browser = await webdriverio.remote(opts);
|
|
571
|
-
|
|
592
|
+
this.activeSessionName = sessionName;
|
|
572
593
|
if (opts.timeouts && this.isWeb) {
|
|
573
594
|
await this._defineBrowserTimeout(browser, opts.timeouts);
|
|
574
595
|
}
|
|
@@ -578,14 +599,19 @@ class WebDriver extends Helper {
|
|
|
578
599
|
return browser;
|
|
579
600
|
},
|
|
580
601
|
stop: async (browser) => {
|
|
602
|
+
if (!browser) return;
|
|
581
603
|
return browser.deleteSession();
|
|
582
604
|
},
|
|
583
605
|
loadVars: async (browser) => {
|
|
584
606
|
if (this.context !== this.root) throw new Error('Can\'t start session inside within block');
|
|
585
607
|
this.browser = browser;
|
|
586
608
|
this.$$ = this.browser.$$.bind(this.browser);
|
|
609
|
+
this.sessionWindows[this.activeSessionName] = browser;
|
|
587
610
|
},
|
|
588
|
-
restoreVars: async () => {
|
|
611
|
+
restoreVars: async (session) => {
|
|
612
|
+
if (!session) {
|
|
613
|
+
this.activeSessionName = '';
|
|
614
|
+
}
|
|
589
615
|
this.browser = defaultSession;
|
|
590
616
|
this.$$ = this.browser.$$.bind(this.browser);
|
|
591
617
|
},
|
|
@@ -626,6 +652,62 @@ class WebDriver extends Helper {
|
|
|
626
652
|
this.$$ = this.browser.$$.bind(this.browser);
|
|
627
653
|
}
|
|
628
654
|
|
|
655
|
+
/**
|
|
656
|
+
* Check if locator is type of "Shadow"
|
|
657
|
+
*
|
|
658
|
+
* @param {object} locator
|
|
659
|
+
*/
|
|
660
|
+
_isShadowLocator(locator) {
|
|
661
|
+
return locator.type === SHADOW || locator[SHADOW];
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* Locate Element within the Shadow Dom
|
|
666
|
+
*
|
|
667
|
+
* @param {object} locator
|
|
668
|
+
*/
|
|
669
|
+
async _locateShadow(locator) {
|
|
670
|
+
const shadow = locator.value ? locator.value : locator[SHADOW];
|
|
671
|
+
const shadowSequence = [];
|
|
672
|
+
let elements;
|
|
673
|
+
|
|
674
|
+
if (!Array.isArray(shadow)) {
|
|
675
|
+
throw new Error(`Shadow '${shadow}' should be defined as an Array of elements.`);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// traverse through the Shadow locators in sequence
|
|
679
|
+
for (let index = 0; index < shadow.length; index++) {
|
|
680
|
+
const shadowElement = shadow[index];
|
|
681
|
+
shadowSequence.push(shadowElement);
|
|
682
|
+
|
|
683
|
+
if (!elements) {
|
|
684
|
+
elements = await (this.browser.$$(shadowElement));
|
|
685
|
+
} else if (Array.isArray(elements)) {
|
|
686
|
+
elements = await elements[0].shadow$$(shadowElement);
|
|
687
|
+
} else if (elements) {
|
|
688
|
+
elements = await elements.shadow$$(shadowElement);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
if (!elements || !elements[0]) {
|
|
692
|
+
throw new Error(`Shadow Element '${shadowElement}' is not found. It is possible the element is incorrect or elements sequence is incorrect. Please verify the sequence '${shadowSequence.join('>')}' is correctly chained.`);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
this.debugSection('Elements', `Found ${elements.length} '${SHADOW}' elements`);
|
|
697
|
+
|
|
698
|
+
return elements;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* Smart Wait to locate an element
|
|
703
|
+
*
|
|
704
|
+
* @param {object} locator
|
|
705
|
+
*/
|
|
706
|
+
async _smartWait(locator) {
|
|
707
|
+
this.debugSection(`SmartWait (${this.options.smartWait}ms)`, `Locating ${locator} in ${this.options.smartWait}`);
|
|
708
|
+
await this.defineTimeout({ implicit: this.options.smartWait });
|
|
709
|
+
}
|
|
710
|
+
|
|
629
711
|
/**
|
|
630
712
|
* Get elements by different locator types, including strict locator.
|
|
631
713
|
* Should be used in custom helpers:
|
|
@@ -640,6 +722,18 @@ class WebDriver extends Helper {
|
|
|
640
722
|
async _locate(locator, smartWait = false) {
|
|
641
723
|
if (require('../store').debugMode) smartWait = false;
|
|
642
724
|
|
|
725
|
+
// special locator type for Shadow DOM
|
|
726
|
+
if (this._isShadowLocator(locator)) {
|
|
727
|
+
if (!this.options.smartWait || !smartWait) {
|
|
728
|
+
const els = await this._locateShadow(locator);
|
|
729
|
+
return els;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
const els = await this._locateShadow(locator);
|
|
734
|
+
return els;
|
|
735
|
+
}
|
|
736
|
+
|
|
643
737
|
// special locator type for React
|
|
644
738
|
if (locator.react) {
|
|
645
739
|
const els = await this.browser.react$$(locator.react, locator.props || undefined, locator.state || undefined);
|
|
@@ -651,9 +745,9 @@ class WebDriver extends Helper {
|
|
|
651
745
|
const els = await this.$$(withStrictLocator(locator));
|
|
652
746
|
return els;
|
|
653
747
|
}
|
|
654
|
-
this.debugSection(`SmartWait (${this.options.smartWait}ms)`, `Locating ${locator} in ${this.options.smartWait}`);
|
|
655
748
|
|
|
656
|
-
await this.
|
|
749
|
+
await this._smartWait(locator);
|
|
750
|
+
|
|
657
751
|
const els = await this.$$(withStrictLocator(locator));
|
|
658
752
|
await this.defineTimeout({ implicit: 0 });
|
|
659
753
|
return els;
|
|
@@ -676,13 +770,15 @@ class WebDriver extends Helper {
|
|
|
676
770
|
* Find a clickable element by providing human readable text:
|
|
677
771
|
*
|
|
678
772
|
* ```js
|
|
679
|
-
* this.helpers
|
|
773
|
+
* const els = await this.helpers.WebDriver._locateClickable('Next page');
|
|
774
|
+
* const els = await this.helpers.WebDriver._locateClickable('Next page', '.pages');
|
|
680
775
|
* ```
|
|
681
776
|
*
|
|
682
777
|
* @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
|
|
683
778
|
*/
|
|
684
|
-
async _locateClickable(locator) {
|
|
685
|
-
|
|
779
|
+
async _locateClickable(locator, context) {
|
|
780
|
+
const locateFn = prepareLocateFn.call(this, context);
|
|
781
|
+
return findClickable.call(this, locator, locateFn);
|
|
686
782
|
}
|
|
687
783
|
|
|
688
784
|
/**
|
|
@@ -786,6 +882,59 @@ class WebDriver extends Helper {
|
|
|
786
882
|
return this.browser[clickMethod](getElementId(elem));
|
|
787
883
|
}
|
|
788
884
|
|
|
885
|
+
/**
|
|
886
|
+
* Perform an emulated click on a link or a button, given by a locator.
|
|
887
|
+
* Unlike normal click instead of sending native event, emulates a click with JavaScript.
|
|
888
|
+
* This works on hidden, animated or inactive elements as well.
|
|
889
|
+
*
|
|
890
|
+
* If a fuzzy locator is given, the page will be searched for a button, link, or image matching the locator string.
|
|
891
|
+
* For buttons, the "value" attribute, "name" attribute, and inner text are searched. For links, the link text is searched.
|
|
892
|
+
* For images, the "alt" attribute and inner text of any parent links are searched.
|
|
893
|
+
*
|
|
894
|
+
* The second parameter is a context (CSS or XPath locator) to narrow the search.
|
|
895
|
+
*
|
|
896
|
+
* ```js
|
|
897
|
+
* // simple link
|
|
898
|
+
* I.forceClick('Logout');
|
|
899
|
+
* // button of form
|
|
900
|
+
* I.forceClick('Submit');
|
|
901
|
+
* // CSS button
|
|
902
|
+
* I.forceClick('#form input[type=submit]');
|
|
903
|
+
* // XPath
|
|
904
|
+
* I.forceClick('//form/*[@type=submit]');
|
|
905
|
+
* // link in context
|
|
906
|
+
* I.forceClick('Logout', '#nav');
|
|
907
|
+
* // using strict locator
|
|
908
|
+
* I.forceClick({css: 'nav a.login'});
|
|
909
|
+
* ```
|
|
910
|
+
*
|
|
911
|
+
* @param {CodeceptJS.LocatorOrString} locator clickable link or button located by text, or any element located by CSS|XPath|strict locator.
|
|
912
|
+
* @param {?CodeceptJS.LocatorOrString} [context=null] (optional, `null` by default) element to search in CSS|XPath|Strict locator.
|
|
913
|
+
*
|
|
914
|
+
*
|
|
915
|
+
* {{ react }}
|
|
916
|
+
*/
|
|
917
|
+
async forceClick(locator, context = null) {
|
|
918
|
+
const locateFn = prepareLocateFn.call(this, context);
|
|
919
|
+
|
|
920
|
+
const res = await findClickable.call(this, locator, locateFn);
|
|
921
|
+
if (context) {
|
|
922
|
+
assertElementExists(res, locator, 'Clickable element', `was not found inside element ${new Locator(context)}`);
|
|
923
|
+
} else {
|
|
924
|
+
assertElementExists(res, locator, 'Clickable element');
|
|
925
|
+
}
|
|
926
|
+
const elem = usingFirstElement(res);
|
|
927
|
+
|
|
928
|
+
return this.executeScript((el) => {
|
|
929
|
+
if (document.activeElement instanceof HTMLElement) {
|
|
930
|
+
document.activeElement.blur();
|
|
931
|
+
}
|
|
932
|
+
const event = document.createEvent('MouseEvent');
|
|
933
|
+
event.initEvent('click', true, true);
|
|
934
|
+
return el.dispatchEvent(event);
|
|
935
|
+
}, elem);
|
|
936
|
+
}
|
|
937
|
+
|
|
789
938
|
/**
|
|
790
939
|
* Performs a double-click on an element matched by link|button|label|CSS or XPath.
|
|
791
940
|
* Context can be specified as second parameter to narrow search.
|
|
@@ -1844,11 +1993,12 @@ class WebDriver extends Helper {
|
|
|
1844
1993
|
*
|
|
1845
1994
|
*
|
|
1846
1995
|
*/
|
|
1847
|
-
async moveCursorTo(locator,
|
|
1996
|
+
async moveCursorTo(locator, xOffset, yOffset) {
|
|
1848
1997
|
const res = await this._locate(withStrictLocator(locator), true);
|
|
1849
1998
|
assertElementExists(res, locator);
|
|
1850
1999
|
const elem = usingFirstElement(res);
|
|
1851
|
-
return elem.moveTo(
|
|
2000
|
+
if (isWebDriver5()) return elem.moveTo(xOffset, yOffset);
|
|
2001
|
+
return elem.moveTo({ xOffset, yOffset });
|
|
1852
2002
|
}
|
|
1853
2003
|
|
|
1854
2004
|
/**
|
|
@@ -1868,6 +2018,15 @@ class WebDriver extends Helper {
|
|
|
1868
2018
|
async saveScreenshot(fileName, fullPage = false) {
|
|
1869
2019
|
const outputFile = screenshotOutputFolder(fileName);
|
|
1870
2020
|
|
|
2021
|
+
if (this.activeSessionName) {
|
|
2022
|
+
const browser = this.sessionWindows[this.activeSessionName];
|
|
2023
|
+
|
|
2024
|
+
if (browser) {
|
|
2025
|
+
this.debug(`Screenshot of ${this.activeSessionName} session has been saved to ${outputFile}`);
|
|
2026
|
+
return browser.saveScreenshot(outputFile);
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
|
|
1871
2030
|
if (!fullPage) {
|
|
1872
2031
|
this.debug(`Screenshot has been saved to ${outputFile}`);
|
|
1873
2032
|
return this.browser.saveScreenshot(outputFile);
|
|
@@ -1895,13 +2054,21 @@ class WebDriver extends Helper {
|
|
|
1895
2054
|
|
|
1896
2055
|
|
|
1897
2056
|
/**
|
|
1898
|
-
* Sets
|
|
2057
|
+
* Sets cookie(s).
|
|
2058
|
+
*
|
|
2059
|
+
* Can be a single cookie object or an array of cookies:
|
|
1899
2060
|
*
|
|
1900
2061
|
* ```js
|
|
1901
2062
|
* I.setCookie({name: 'auth', value: true});
|
|
2063
|
+
*
|
|
2064
|
+
* // as array
|
|
2065
|
+
* I.setCookie([
|
|
2066
|
+
* {name: 'auth', value: true},
|
|
2067
|
+
* {name: 'agree', value: true}
|
|
2068
|
+
* ]);
|
|
1902
2069
|
* ```
|
|
1903
2070
|
*
|
|
1904
|
-
* @param {object} cookie a cookie object.
|
|
2071
|
+
* @param {object|array} cookie a cookie object or array of cookie objects.
|
|
1905
2072
|
*
|
|
1906
2073
|
*
|
|
1907
2074
|
* Uses Selenium's JSON [cookie
|
|
@@ -2194,6 +2361,29 @@ class WebDriver extends Helper {
|
|
|
2194
2361
|
}
|
|
2195
2362
|
}
|
|
2196
2363
|
|
|
2364
|
+
/**
|
|
2365
|
+
*
|
|
2366
|
+
* Types out the given string or the array of keys provided.
|
|
2367
|
+
* _Note:_ Should only be used when using [`fillField`](#fillfield) is not an option.
|
|
2368
|
+
*
|
|
2369
|
+
* ```js
|
|
2370
|
+
* // When passing in a string
|
|
2371
|
+
* I.type('Type this out.');
|
|
2372
|
+
* // When passing in an array
|
|
2373
|
+
* I.type(['T', 'E', 'X', 'T']);
|
|
2374
|
+
* ```
|
|
2375
|
+
*
|
|
2376
|
+
* @param {string|string[]} key or array of keys to type.
|
|
2377
|
+
* Type out given array of keys or a string of text
|
|
2378
|
+
*/
|
|
2379
|
+
async type(keys) {
|
|
2380
|
+
if (Array.isArray(keys)) {
|
|
2381
|
+
await this.browser.keys(keys);
|
|
2382
|
+
return;
|
|
2383
|
+
}
|
|
2384
|
+
await this.browser.keys(keys.split(''));
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2197
2387
|
/**
|
|
2198
2388
|
* Resize the current window to provided width and height.
|
|
2199
2389
|
* First parameter can be set to `maximize`.
|
|
@@ -2368,6 +2558,19 @@ class WebDriver extends Helper {
|
|
|
2368
2558
|
*/
|
|
2369
2559
|
async waitForEnabled(locator, sec = null) {
|
|
2370
2560
|
const aSec = sec || this.options.waitForTimeout;
|
|
2561
|
+
if (isWebDriver5()) {
|
|
2562
|
+
return this.browser.waitUntil(async () => {
|
|
2563
|
+
const res = await this.$$(withStrictLocator(locator));
|
|
2564
|
+
if (!res || res.length === 0) {
|
|
2565
|
+
return false;
|
|
2566
|
+
}
|
|
2567
|
+
const selected = await forEachAsync(res, async el => this.browser.isElementEnabled(getElementId(el)));
|
|
2568
|
+
if (Array.isArray(selected)) {
|
|
2569
|
+
return selected.filter(val => val === true).length > 0;
|
|
2570
|
+
}
|
|
2571
|
+
return selected;
|
|
2572
|
+
}, aSec * 1000, `element (${new Locator(locator)}) still not enabled after ${aSec} sec`);
|
|
2573
|
+
}
|
|
2371
2574
|
return this.browser.waitUntil(async () => {
|
|
2372
2575
|
const res = await this.$$(withStrictLocator(locator));
|
|
2373
2576
|
if (!res || res.length === 0) {
|
|
@@ -2378,7 +2581,10 @@ class WebDriver extends Helper {
|
|
|
2378
2581
|
return selected.filter(val => val === true).length > 0;
|
|
2379
2582
|
}
|
|
2380
2583
|
return selected;
|
|
2381
|
-
},
|
|
2584
|
+
}, {
|
|
2585
|
+
timeout: aSec * 1000,
|
|
2586
|
+
timeoutMsg: `element (${new Locator(locator)}) still not enabled after ${aSec} sec`,
|
|
2587
|
+
});
|
|
2382
2588
|
}
|
|
2383
2589
|
|
|
2384
2590
|
/**
|
|
@@ -2395,10 +2601,16 @@ class WebDriver extends Helper {
|
|
|
2395
2601
|
*/
|
|
2396
2602
|
async waitForElement(locator, sec = null) {
|
|
2397
2603
|
const aSec = sec || this.options.waitForTimeout;
|
|
2604
|
+
if (isWebDriver5()) {
|
|
2605
|
+
return this.browser.waitUntil(async () => {
|
|
2606
|
+
const res = await this.$$(withStrictLocator(locator));
|
|
2607
|
+
return res && res.length;
|
|
2608
|
+
}, aSec * 1000, `element (${locator}) still not present on page after ${aSec} sec`);
|
|
2609
|
+
}
|
|
2398
2610
|
return this.browser.waitUntil(async () => {
|
|
2399
2611
|
const res = await this.$$(withStrictLocator(locator));
|
|
2400
2612
|
return res && res.length;
|
|
2401
|
-
}, aSec * 1000, `element (${locator}) still not present on page after ${aSec} sec`);
|
|
2613
|
+
}, { timeout: aSec * 1000, timeoutMsg: `element (${locator}) still not present on page after ${aSec} sec` });
|
|
2402
2614
|
}
|
|
2403
2615
|
|
|
2404
2616
|
/**
|
|
@@ -2447,13 +2659,27 @@ class WebDriver extends Helper {
|
|
|
2447
2659
|
const client = this.browser;
|
|
2448
2660
|
const aSec = sec || this.options.waitForTimeout;
|
|
2449
2661
|
let currUrl = '';
|
|
2662
|
+
if (isWebDriver5()) {
|
|
2663
|
+
return client
|
|
2664
|
+
.waitUntil(function () {
|
|
2665
|
+
return this.getUrl().then((res) => {
|
|
2666
|
+
currUrl = decodeUrl(res);
|
|
2667
|
+
return currUrl.indexOf(urlPart) > -1;
|
|
2668
|
+
});
|
|
2669
|
+
}, aSec * 1000).catch((e) => {
|
|
2670
|
+
if (e.message.indexOf('timeout')) {
|
|
2671
|
+
throw new Error(`expected url to include ${urlPart}, but found ${currUrl}`);
|
|
2672
|
+
}
|
|
2673
|
+
throw e;
|
|
2674
|
+
});
|
|
2675
|
+
}
|
|
2450
2676
|
return client
|
|
2451
2677
|
.waitUntil(function () {
|
|
2452
2678
|
return this.getUrl().then((res) => {
|
|
2453
2679
|
currUrl = decodeUrl(res);
|
|
2454
2680
|
return currUrl.indexOf(urlPart) > -1;
|
|
2455
2681
|
});
|
|
2456
|
-
}, aSec * 1000).catch((e) => {
|
|
2682
|
+
}, { timeout: aSec * 1000 }).catch((e) => {
|
|
2457
2683
|
if (e.message.indexOf('timeout')) {
|
|
2458
2684
|
throw new Error(`expected url to include ${urlPart}, but found ${currUrl}`);
|
|
2459
2685
|
}
|
|
@@ -2510,6 +2736,21 @@ class WebDriver extends Helper {
|
|
|
2510
2736
|
async waitForText(text, sec = null, context = null) {
|
|
2511
2737
|
const aSec = sec || this.options.waitForTimeout;
|
|
2512
2738
|
const _context = context || this.root;
|
|
2739
|
+
if (isWebDriver5()) {
|
|
2740
|
+
return this.browser.waitUntil(
|
|
2741
|
+
async () => {
|
|
2742
|
+
const res = await this.$$(withStrictLocator.call(this, _context));
|
|
2743
|
+
if (!res || res.length === 0) return false;
|
|
2744
|
+
const selected = await forEachAsync(res, async el => this.browser.getElementText(getElementId(el)));
|
|
2745
|
+
if (Array.isArray(selected)) {
|
|
2746
|
+
return selected.filter(part => part.indexOf(text) >= 0).length > 0;
|
|
2747
|
+
}
|
|
2748
|
+
return selected.indexOf(text) >= 0;
|
|
2749
|
+
}, aSec * 1000,
|
|
2750
|
+
`element (${_context}) is not in DOM or there is no element(${_context}) with text "${text}" after ${aSec} sec`,
|
|
2751
|
+
);
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2513
2754
|
return this.browser.waitUntil(
|
|
2514
2755
|
async () => {
|
|
2515
2756
|
const res = await this.$$(withStrictLocator.call(this, _context));
|
|
@@ -2519,8 +2760,10 @@ class WebDriver extends Helper {
|
|
|
2519
2760
|
return selected.filter(part => part.indexOf(text) >= 0).length > 0;
|
|
2520
2761
|
}
|
|
2521
2762
|
return selected.indexOf(text) >= 0;
|
|
2522
|
-
},
|
|
2523
|
-
|
|
2763
|
+
}, {
|
|
2764
|
+
timeout: aSec * 1000,
|
|
2765
|
+
timeoutMsg: `element (${_context}) is not in DOM or there is no element(${_context}) with text "${text}" after ${aSec} sec`,
|
|
2766
|
+
},
|
|
2524
2767
|
);
|
|
2525
2768
|
}
|
|
2526
2769
|
|
|
@@ -2538,6 +2781,20 @@ class WebDriver extends Helper {
|
|
|
2538
2781
|
async waitForValue(field, value, sec = null) {
|
|
2539
2782
|
const client = this.browser;
|
|
2540
2783
|
const aSec = sec || this.options.waitForTimeout;
|
|
2784
|
+
if (isWebDriver5()) {
|
|
2785
|
+
return client.waitUntil(
|
|
2786
|
+
async () => {
|
|
2787
|
+
const res = await findFields.call(this, field);
|
|
2788
|
+
if (!res || res.length === 0) return false;
|
|
2789
|
+
const selected = await forEachAsync(res, async el => el.getValue());
|
|
2790
|
+
if (Array.isArray(selected)) {
|
|
2791
|
+
return selected.filter(part => part.indexOf(value) >= 0).length > 0;
|
|
2792
|
+
}
|
|
2793
|
+
return selected.indexOf(value) >= 0;
|
|
2794
|
+
}, aSec * 1000,
|
|
2795
|
+
`element (${field}) is not in DOM or there is no element(${field}) with value "${value}" after ${aSec} sec`,
|
|
2796
|
+
);
|
|
2797
|
+
}
|
|
2541
2798
|
return client.waitUntil(
|
|
2542
2799
|
async () => {
|
|
2543
2800
|
const res = await findFields.call(this, field);
|
|
@@ -2547,8 +2804,10 @@ class WebDriver extends Helper {
|
|
|
2547
2804
|
return selected.filter(part => part.indexOf(value) >= 0).length > 0;
|
|
2548
2805
|
}
|
|
2549
2806
|
return selected.indexOf(value) >= 0;
|
|
2550
|
-
},
|
|
2551
|
-
|
|
2807
|
+
}, {
|
|
2808
|
+
timeout: aSec * 1000,
|
|
2809
|
+
timeoutMsg: `element (${field}) is not in DOM or there is no element(${field}) with value "${value}" after ${aSec} sec`,
|
|
2810
|
+
},
|
|
2552
2811
|
);
|
|
2553
2812
|
}
|
|
2554
2813
|
|
|
@@ -2566,6 +2825,17 @@ class WebDriver extends Helper {
|
|
|
2566
2825
|
*/
|
|
2567
2826
|
async waitForVisible(locator, sec = null) {
|
|
2568
2827
|
const aSec = sec || this.options.waitForTimeout;
|
|
2828
|
+
if (isWebDriver5()) {
|
|
2829
|
+
return this.browser.waitUntil(async () => {
|
|
2830
|
+
const res = await this.$$(withStrictLocator(locator));
|
|
2831
|
+
if (!res || res.length === 0) return false;
|
|
2832
|
+
const selected = await forEachAsync(res, async el => el.isDisplayed());
|
|
2833
|
+
if (Array.isArray(selected)) {
|
|
2834
|
+
return selected.filter(val => val === true).length > 0;
|
|
2835
|
+
}
|
|
2836
|
+
return selected;
|
|
2837
|
+
}, aSec * 1000, `element (${new Locator(locator)}) still not visible after ${aSec} sec`);
|
|
2838
|
+
}
|
|
2569
2839
|
return this.browser.waitUntil(async () => {
|
|
2570
2840
|
const res = await this.$$(withStrictLocator(locator));
|
|
2571
2841
|
if (!res || res.length === 0) return false;
|
|
@@ -2574,7 +2844,7 @@ class WebDriver extends Helper {
|
|
|
2574
2844
|
return selected.filter(val => val === true).length > 0;
|
|
2575
2845
|
}
|
|
2576
2846
|
return selected;
|
|
2577
|
-
}, aSec * 1000, `element (${new Locator(locator)}) still not visible after ${aSec} sec`);
|
|
2847
|
+
}, { timeout: aSec * 1000, timeoutMsg: `element (${new Locator(locator)}) still not visible after ${aSec} sec` });
|
|
2578
2848
|
}
|
|
2579
2849
|
|
|
2580
2850
|
/**
|
|
@@ -2590,6 +2860,16 @@ class WebDriver extends Helper {
|
|
|
2590
2860
|
*/
|
|
2591
2861
|
async waitNumberOfVisibleElements(locator, num, sec = null) {
|
|
2592
2862
|
const aSec = sec || this.options.waitForTimeout;
|
|
2863
|
+
if (isWebDriver5()) {
|
|
2864
|
+
return this.browser.waitUntil(async () => {
|
|
2865
|
+
const res = await this.$$(withStrictLocator(locator));
|
|
2866
|
+
if (!res || res.length === 0) return false;
|
|
2867
|
+
let selected = await forEachAsync(res, async el => el.isDisplayed());
|
|
2868
|
+
|
|
2869
|
+
if (!Array.isArray(selected)) selected = [selected];
|
|
2870
|
+
return selected.length === num;
|
|
2871
|
+
}, aSec * 1000, `The number of elements (${new Locator(locator)}) is not ${num} after ${aSec} sec`);
|
|
2872
|
+
}
|
|
2593
2873
|
return this.browser.waitUntil(async () => {
|
|
2594
2874
|
const res = await this.$$(withStrictLocator(locator));
|
|
2595
2875
|
if (!res || res.length === 0) return false;
|
|
@@ -2597,7 +2877,7 @@ class WebDriver extends Helper {
|
|
|
2597
2877
|
|
|
2598
2878
|
if (!Array.isArray(selected)) selected = [selected];
|
|
2599
2879
|
return selected.length === num;
|
|
2600
|
-
}, aSec * 1000, `The number of elements (${new Locator(locator)}) is not ${num} after ${aSec} sec`);
|
|
2880
|
+
}, { timeout: aSec * 1000, timeoutMsg: `The number of elements (${new Locator(locator)}) is not ${num} after ${aSec} sec` });
|
|
2601
2881
|
}
|
|
2602
2882
|
|
|
2603
2883
|
/**
|
|
@@ -2614,12 +2894,20 @@ class WebDriver extends Helper {
|
|
|
2614
2894
|
*/
|
|
2615
2895
|
async waitForInvisible(locator, sec = null) {
|
|
2616
2896
|
const aSec = sec || this.options.waitForTimeout;
|
|
2897
|
+
if (isWebDriver5()) {
|
|
2898
|
+
return this.browser.waitUntil(async () => {
|
|
2899
|
+
const res = await this.$$(withStrictLocator(locator));
|
|
2900
|
+
if (!res || res.length === 0) return true;
|
|
2901
|
+
const selected = await forEachAsync(res, async el => el.isDisplayed());
|
|
2902
|
+
return !selected.length;
|
|
2903
|
+
}, aSec * 1000, `element (${new Locator(locator)}) still visible after ${aSec} sec`);
|
|
2904
|
+
}
|
|
2617
2905
|
return this.browser.waitUntil(async () => {
|
|
2618
2906
|
const res = await this.$$(withStrictLocator(locator));
|
|
2619
2907
|
if (!res || res.length === 0) return true;
|
|
2620
2908
|
const selected = await forEachAsync(res, async el => el.isDisplayed());
|
|
2621
2909
|
return !selected.length;
|
|
2622
|
-
}, aSec * 1000, `element (${new Locator(locator)}) still visible after ${aSec} sec`);
|
|
2910
|
+
}, { timeout: aSec * 1000, timeoutMsg: `element (${new Locator(locator)}) still visible after ${aSec} sec` });
|
|
2623
2911
|
}
|
|
2624
2912
|
|
|
2625
2913
|
/**
|
|
@@ -2657,13 +2945,22 @@ class WebDriver extends Helper {
|
|
|
2657
2945
|
*/
|
|
2658
2946
|
async waitForDetached(locator, sec = null) {
|
|
2659
2947
|
const aSec = sec || this.options.waitForTimeout;
|
|
2948
|
+
if (isWebDriver5()) {
|
|
2949
|
+
return this.browser.waitUntil(async () => {
|
|
2950
|
+
const res = await this.$$(withStrictLocator(locator));
|
|
2951
|
+
if (!res || res.length === 0) {
|
|
2952
|
+
return true;
|
|
2953
|
+
}
|
|
2954
|
+
return false;
|
|
2955
|
+
}, aSec * 1000, `element (${new Locator(locator)}) still on page after ${aSec} sec`);
|
|
2956
|
+
}
|
|
2660
2957
|
return this.browser.waitUntil(async () => {
|
|
2661
2958
|
const res = await this.$$(withStrictLocator(locator));
|
|
2662
2959
|
if (!res || res.length === 0) {
|
|
2663
2960
|
return true;
|
|
2664
2961
|
}
|
|
2665
2962
|
return false;
|
|
2666
|
-
}, aSec * 1000, `element (${new Locator(locator)}) still on page after ${aSec} sec`);
|
|
2963
|
+
}, { timeout: aSec * 1000, timeoutMsg: `element (${new Locator(locator)}) still on page after ${aSec} sec` });
|
|
2667
2964
|
}
|
|
2668
2965
|
|
|
2669
2966
|
/**
|
|
@@ -2697,7 +2994,10 @@ class WebDriver extends Helper {
|
|
|
2697
2994
|
}
|
|
2698
2995
|
|
|
2699
2996
|
const aSec = sec || this.options.waitForTimeout;
|
|
2700
|
-
|
|
2997
|
+
if (isWebDriver5()) {
|
|
2998
|
+
return this.browser.waitUntil(async () => this.browser.execute(fn, ...args), aSec * 1000, '');
|
|
2999
|
+
}
|
|
3000
|
+
return this.browser.waitUntil(async () => this.browser.execute(fn, ...args), { timeout: aSec * 1000, timeoutMsg: '' });
|
|
2701
3001
|
}
|
|
2702
3002
|
|
|
2703
3003
|
/**
|
|
@@ -2717,7 +3017,10 @@ class WebDriver extends Helper {
|
|
|
2717
3017
|
async waitUntil(fn, sec = null, timeoutMsg = null, interval = null) {
|
|
2718
3018
|
const aSec = sec || this.options.waitForTimeout;
|
|
2719
3019
|
const _interval = typeof interval === 'number' ? interval * 1000 : null;
|
|
2720
|
-
|
|
3020
|
+
if (isWebDriver5()) {
|
|
3021
|
+
return this.browser.waitUntil(fn, aSec * 1000, timeoutMsg, _interval);
|
|
3022
|
+
}
|
|
3023
|
+
return this.browser.waitUntil(fn, { timeout: aSec * 1000, timeoutMsg, interval: _interval });
|
|
2721
3024
|
}
|
|
2722
3025
|
|
|
2723
3026
|
/**
|
|
@@ -2761,6 +3064,19 @@ class WebDriver extends Helper {
|
|
|
2761
3064
|
const aSec = sec || this.options.waitForTimeout;
|
|
2762
3065
|
let target;
|
|
2763
3066
|
const current = await this.browser.getWindowHandle();
|
|
3067
|
+
|
|
3068
|
+
if (isWebDriver5()) {
|
|
3069
|
+
await this.browser.waitUntil(async () => {
|
|
3070
|
+
await this.browser.getWindowHandles().then((handles) => {
|
|
3071
|
+
if (handles.indexOf(current) + num + 1 <= handles.length) {
|
|
3072
|
+
target = handles[handles.indexOf(current) + num];
|
|
3073
|
+
}
|
|
3074
|
+
});
|
|
3075
|
+
return target;
|
|
3076
|
+
}, aSec * 1000, `There is no ability to switch to next tab with offset ${num}`);
|
|
3077
|
+
return this.browser.switchToWindow(target);
|
|
3078
|
+
}
|
|
3079
|
+
|
|
2764
3080
|
await this.browser.waitUntil(async () => {
|
|
2765
3081
|
await this.browser.getWindowHandles().then((handles) => {
|
|
2766
3082
|
if (handles.indexOf(current) + num + 1 <= handles.length) {
|
|
@@ -2768,7 +3084,7 @@ class WebDriver extends Helper {
|
|
|
2768
3084
|
}
|
|
2769
3085
|
});
|
|
2770
3086
|
return target;
|
|
2771
|
-
}, aSec * 1000, `There is no ability to switch to next tab with offset ${num}`);
|
|
3087
|
+
}, { timeout: aSec * 1000, timeoutMsg: `There is no ability to switch to next tab with offset ${num}` });
|
|
2772
3088
|
return this.browser.switchToWindow(target);
|
|
2773
3089
|
}
|
|
2774
3090
|
|
|
@@ -2787,6 +3103,19 @@ class WebDriver extends Helper {
|
|
|
2787
3103
|
const aSec = sec || this.options.waitForTimeout;
|
|
2788
3104
|
const current = await this.browser.getWindowHandle();
|
|
2789
3105
|
let target;
|
|
3106
|
+
|
|
3107
|
+
if (isWebDriver5()) {
|
|
3108
|
+
await this.browser.waitUntil(async () => {
|
|
3109
|
+
await this.browser.getWindowHandles().then((handles) => {
|
|
3110
|
+
if (handles.indexOf(current) - num > -1) {
|
|
3111
|
+
target = handles[handles.indexOf(current) - num];
|
|
3112
|
+
}
|
|
3113
|
+
});
|
|
3114
|
+
return target;
|
|
3115
|
+
}, aSec * 1000, `There is no ability to switch to previous tab with offset ${num}`);
|
|
3116
|
+
return this.browser.switchToWindow(target);
|
|
3117
|
+
}
|
|
3118
|
+
|
|
2790
3119
|
await this.browser.waitUntil(async () => {
|
|
2791
3120
|
await this.browser.getWindowHandles().then((handles) => {
|
|
2792
3121
|
if (handles.indexOf(current) - num > -1) {
|
|
@@ -2794,7 +3123,7 @@ class WebDriver extends Helper {
|
|
|
2794
3123
|
}
|
|
2795
3124
|
});
|
|
2796
3125
|
return target;
|
|
2797
|
-
}, aSec * 1000, `There is no ability to switch to previous tab with offset ${num}`);
|
|
3126
|
+
}, { timeout: aSec * 1000, timeoutMsg: `There is no ability to switch to previous tab with offset ${num}` });
|
|
2798
3127
|
return this.browser.switchToWindow(target);
|
|
2799
3128
|
}
|
|
2800
3129
|
|
|
@@ -3092,7 +3421,6 @@ async function filterAsync(array, callback) {
|
|
|
3092
3421
|
return values;
|
|
3093
3422
|
}
|
|
3094
3423
|
|
|
3095
|
-
|
|
3096
3424
|
async function findClickable(locator, locateFn) {
|
|
3097
3425
|
locator = new Locator(locator);
|
|
3098
3426
|
if (locator.isAccessibilityId() && !this.isWeb) return locateFn(locator, true);
|
|
@@ -3116,6 +3444,7 @@ async function findClickable(locator, locateFn) {
|
|
|
3116
3444
|
|
|
3117
3445
|
async function findFields(locator) {
|
|
3118
3446
|
locator = new Locator(locator);
|
|
3447
|
+
|
|
3119
3448
|
if (locator.isAccessibilityId() && !this.isWeb) return this._locate(locator, true);
|
|
3120
3449
|
if (!locator.isFuzzy()) return this._locate(locator, true);
|
|
3121
3450
|
|
|
@@ -3430,4 +3759,8 @@ function prepareLocateFn(context) {
|
|
|
3430
3759
|
};
|
|
3431
3760
|
}
|
|
3432
3761
|
|
|
3762
|
+
function isWebDriver5() {
|
|
3763
|
+
return version && version.indexOf('5') === 0;
|
|
3764
|
+
}
|
|
3765
|
+
|
|
3433
3766
|
module.exports = WebDriver;
|