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.
Files changed (76) hide show
  1. package/CHANGELOG.md +117 -0
  2. package/README.md +32 -7
  3. package/bin/codecept.js +3 -0
  4. package/docs/basics.md +11 -5
  5. package/docs/bdd.md +4 -4
  6. package/docs/build/MockRequest.js +3 -0
  7. package/docs/build/Nightmare.js +10 -2
  8. package/docs/build/Playwright.js +3187 -0
  9. package/docs/build/Protractor.js +16 -2
  10. package/docs/build/Puppeteer.js +126 -19
  11. package/docs/build/REST.js +3 -1
  12. package/docs/build/TestCafe.js +11 -3
  13. package/docs/build/WebDriver.js +361 -28
  14. package/docs/changelog.md +116 -0
  15. package/docs/configuration.md +2 -2
  16. package/docs/custom-helpers.md +55 -10
  17. package/docs/helpers/Appium.md +81 -1
  18. package/docs/helpers/MockRequest.md +281 -38
  19. package/docs/helpers/Nightmare.md +10 -2
  20. package/docs/helpers/Playwright.md +1770 -0
  21. package/docs/helpers/Protractor.md +15 -3
  22. package/docs/helpers/Puppeteer-firefox.md +32 -1
  23. package/docs/helpers/Puppeteer.md +126 -76
  24. package/docs/helpers/TestCafe.md +10 -2
  25. package/docs/helpers/WebDriver.md +208 -118
  26. package/docs/locators.md +2 -0
  27. package/docs/playwright.md +306 -0
  28. package/docs/plugins.md +103 -0
  29. package/docs/reports.md +12 -0
  30. package/docs/shadow.md +68 -0
  31. package/docs/visual.md +0 -73
  32. package/docs/webapi/forceClick.mustache +27 -0
  33. package/docs/webapi/seeInPopup.mustache +7 -0
  34. package/docs/webapi/setCookie.mustache +10 -2
  35. package/docs/webapi/type.mustache +12 -0
  36. package/docs/webdriver.md +7 -3
  37. package/lib/codecept.js +1 -1
  38. package/lib/command/definitions.js +2 -2
  39. package/lib/command/generate.js +4 -4
  40. package/lib/command/gherkin/snippets.js +4 -4
  41. package/lib/command/init.js +1 -1
  42. package/lib/command/interactive.js +3 -0
  43. package/lib/command/run-multiple.js +2 -2
  44. package/lib/command/run-rerun.js +2 -0
  45. package/lib/command/run-workers.js +22 -8
  46. package/lib/command/run.js +2 -0
  47. package/lib/command/workers/runTests.js +1 -0
  48. package/lib/container.js +1 -1
  49. package/lib/event.js +2 -0
  50. package/lib/helper/MockRequest.js +3 -0
  51. package/lib/helper/Playwright.js +2422 -0
  52. package/lib/helper/Protractor.js +1 -2
  53. package/lib/helper/Puppeteer.js +84 -19
  54. package/lib/helper/REST.js +3 -1
  55. package/lib/helper/TestCafe.js +1 -1
  56. package/lib/helper/WebDriver.js +313 -26
  57. package/lib/helper/extras/PlaywrightPropEngine.js +53 -0
  58. package/lib/helper/scripts/isElementClickable.js +54 -14
  59. package/lib/interfaces/gherkin.js +1 -1
  60. package/lib/listener/helpers.js +3 -0
  61. package/lib/locator.js +5 -0
  62. package/lib/mochaFactory.js +12 -10
  63. package/lib/plugin/allure.js +8 -1
  64. package/lib/plugin/autoDelay.js +1 -8
  65. package/lib/plugin/commentStep.js +133 -0
  66. package/lib/plugin/screenshotOnFail.js +3 -10
  67. package/lib/plugin/selenoid.js +2 -2
  68. package/lib/plugin/standardActingHelpers.js +13 -0
  69. package/lib/plugin/stepByStepReport.js +1 -8
  70. package/lib/plugin/wdio.js +10 -1
  71. package/lib/reporter/cli.js +30 -1
  72. package/lib/session.js +7 -4
  73. package/package.json +13 -10
  74. package/typings/Mocha.d.ts +567 -16
  75. package/typings/index.d.ts +9 -5
  76. package/typings/types.d.ts +1634 -74
@@ -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.defineTimeout({ implicit: this.options.smartWait });
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['WebDriver']._locateClickable('Next page').then // ...
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
- return findClickable.call(this, locator, this.$$.bind(this)).then(res => res);
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, offsetX = 0, offsetY = 0) {
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(offsetX, offsetY);
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 a cookie.
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
- }, aSec * 1000, `element (${new Locator(locator)}) still not enabled after ${aSec} sec`);
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
- }, aSec * 1000,
2523
- `element (${_context}) is not in DOM or there is no element(${_context}) with text "${text}" after ${aSec} sec`,
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
- }, aSec * 1000,
2551
- `element (${field}) is not in DOM or there is no element(${field}) with value "${value}" after ${aSec} sec`,
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
- return this.browser.waitUntil(async () => this.browser.execute(fn, ...args), aSec * 1000);
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
- return this.browser.waitUntil(fn, aSec * 1000, timeoutMsg, _interval);
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;