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
  /**
@@ -753,6 +849,32 @@ class WebDriver extends Helper {
753
849
  return this.browser[clickMethod](getElementId(elem));
754
850
  }
755
851
 
852
+ /**
853
+ * {{> forceClick }}
854
+ *
855
+ * {{ react }}
856
+ */
857
+ async forceClick(locator, context = null) {
858
+ const locateFn = prepareLocateFn.call(this, context);
859
+
860
+ const res = await findClickable.call(this, locator, locateFn);
861
+ if (context) {
862
+ assertElementExists(res, locator, 'Clickable element', `was not found inside element ${new Locator(context)}`);
863
+ } else {
864
+ assertElementExists(res, locator, 'Clickable element');
865
+ }
866
+ const elem = usingFirstElement(res);
867
+
868
+ return this.executeScript((el) => {
869
+ if (document.activeElement instanceof HTMLElement) {
870
+ document.activeElement.blur();
871
+ }
872
+ const event = document.createEvent('MouseEvent');
873
+ event.initEvent('click', true, true);
874
+ return el.dispatchEvent(event);
875
+ }, elem);
876
+ }
877
+
756
878
  /**
757
879
  * {{> doubleClick }}
758
880
  *
@@ -1382,11 +1504,12 @@ class WebDriver extends Helper {
1382
1504
  * {{> moveCursorTo }}
1383
1505
  *
1384
1506
  */
1385
- async moveCursorTo(locator, offsetX = 0, offsetY = 0) {
1507
+ async moveCursorTo(locator, xOffset, yOffset) {
1386
1508
  const res = await this._locate(withStrictLocator(locator), true);
1387
1509
  assertElementExists(res, locator);
1388
1510
  const elem = usingFirstElement(res);
1389
- return elem.moveTo(offsetX, offsetY);
1511
+ if (isWebDriver5()) return elem.moveTo(xOffset, yOffset);
1512
+ return elem.moveTo({ xOffset, yOffset });
1390
1513
  }
1391
1514
 
1392
1515
  /**
@@ -1396,6 +1519,15 @@ class WebDriver extends Helper {
1396
1519
  async saveScreenshot(fileName, fullPage = false) {
1397
1520
  const outputFile = screenshotOutputFolder(fileName);
1398
1521
 
1522
+ if (this.activeSessionName) {
1523
+ const browser = this.sessionWindows[this.activeSessionName];
1524
+
1525
+ if (browser) {
1526
+ this.debug(`Screenshot of ${this.activeSessionName} session has been saved to ${outputFile}`);
1527
+ return browser.saveScreenshot(outputFile);
1528
+ }
1529
+ }
1530
+
1399
1531
  if (!fullPage) {
1400
1532
  this.debug(`Screenshot has been saved to ${outputFile}`);
1401
1533
  return this.browser.saveScreenshot(outputFile);
@@ -1604,6 +1736,18 @@ class WebDriver extends Helper {
1604
1736
  }
1605
1737
  }
1606
1738
 
1739
+ /**
1740
+ * {{> type }}
1741
+ * Type out given array of keys or a string of text
1742
+ */
1743
+ async type(keys) {
1744
+ if (Array.isArray(keys)) {
1745
+ await this.browser.keys(keys);
1746
+ return;
1747
+ }
1748
+ await this.browser.keys(keys.split(''));
1749
+ }
1750
+
1607
1751
  /**
1608
1752
  * {{> resizeWindow }}
1609
1753
  * Appium: not tested in web, in apps doesn't work
@@ -1748,6 +1892,19 @@ class WebDriver extends Helper {
1748
1892
  */
1749
1893
  async waitForEnabled(locator, sec = null) {
1750
1894
  const aSec = sec || this.options.waitForTimeout;
1895
+ if (isWebDriver5()) {
1896
+ return this.browser.waitUntil(async () => {
1897
+ const res = await this.$$(withStrictLocator(locator));
1898
+ if (!res || res.length === 0) {
1899
+ return false;
1900
+ }
1901
+ const selected = await forEachAsync(res, async el => this.browser.isElementEnabled(getElementId(el)));
1902
+ if (Array.isArray(selected)) {
1903
+ return selected.filter(val => val === true).length > 0;
1904
+ }
1905
+ return selected;
1906
+ }, aSec * 1000, `element (${new Locator(locator)}) still not enabled after ${aSec} sec`);
1907
+ }
1751
1908
  return this.browser.waitUntil(async () => {
1752
1909
  const res = await this.$$(withStrictLocator(locator));
1753
1910
  if (!res || res.length === 0) {
@@ -1758,7 +1915,10 @@ class WebDriver extends Helper {
1758
1915
  return selected.filter(val => val === true).length > 0;
1759
1916
  }
1760
1917
  return selected;
1761
- }, aSec * 1000, `element (${new Locator(locator)}) still not enabled after ${aSec} sec`);
1918
+ }, {
1919
+ timeout: aSec * 1000,
1920
+ timeoutMsg: `element (${new Locator(locator)}) still not enabled after ${aSec} sec`,
1921
+ });
1762
1922
  }
1763
1923
 
1764
1924
  /**
@@ -1766,10 +1926,16 @@ class WebDriver extends Helper {
1766
1926
  */
1767
1927
  async waitForElement(locator, sec = null) {
1768
1928
  const aSec = sec || this.options.waitForTimeout;
1929
+ if (isWebDriver5()) {
1930
+ return this.browser.waitUntil(async () => {
1931
+ const res = await this.$$(withStrictLocator(locator));
1932
+ return res && res.length;
1933
+ }, aSec * 1000, `element (${locator}) still not present on page after ${aSec} sec`);
1934
+ }
1769
1935
  return this.browser.waitUntil(async () => {
1770
1936
  const res = await this.$$(withStrictLocator(locator));
1771
1937
  return res && res.length;
1772
- }, aSec * 1000, `element (${locator}) still not present on page after ${aSec} sec`);
1938
+ }, { timeout: aSec * 1000, timeoutMsg: `element (${locator}) still not present on page after ${aSec} sec` });
1773
1939
  }
1774
1940
 
1775
1941
  /**
@@ -1802,13 +1968,27 @@ class WebDriver extends Helper {
1802
1968
  const client = this.browser;
1803
1969
  const aSec = sec || this.options.waitForTimeout;
1804
1970
  let currUrl = '';
1971
+ if (isWebDriver5()) {
1972
+ return client
1973
+ .waitUntil(function () {
1974
+ return this.getUrl().then((res) => {
1975
+ currUrl = decodeUrl(res);
1976
+ return currUrl.indexOf(urlPart) > -1;
1977
+ });
1978
+ }, aSec * 1000).catch((e) => {
1979
+ if (e.message.indexOf('timeout')) {
1980
+ throw new Error(`expected url to include ${urlPart}, but found ${currUrl}`);
1981
+ }
1982
+ throw e;
1983
+ });
1984
+ }
1805
1985
  return client
1806
1986
  .waitUntil(function () {
1807
1987
  return this.getUrl().then((res) => {
1808
1988
  currUrl = decodeUrl(res);
1809
1989
  return currUrl.indexOf(urlPart) > -1;
1810
1990
  });
1811
- }, aSec * 1000).catch((e) => {
1991
+ }, { timeout: aSec * 1000 }).catch((e) => {
1812
1992
  if (e.message.indexOf('timeout')) {
1813
1993
  throw new Error(`expected url to include ${urlPart}, but found ${currUrl}`);
1814
1994
  }
@@ -1846,6 +2026,21 @@ class WebDriver extends Helper {
1846
2026
  async waitForText(text, sec = null, context = null) {
1847
2027
  const aSec = sec || this.options.waitForTimeout;
1848
2028
  const _context = context || this.root;
2029
+ if (isWebDriver5()) {
2030
+ return this.browser.waitUntil(
2031
+ async () => {
2032
+ const res = await this.$$(withStrictLocator.call(this, _context));
2033
+ if (!res || res.length === 0) return false;
2034
+ const selected = await forEachAsync(res, async el => this.browser.getElementText(getElementId(el)));
2035
+ if (Array.isArray(selected)) {
2036
+ return selected.filter(part => part.indexOf(text) >= 0).length > 0;
2037
+ }
2038
+ return selected.indexOf(text) >= 0;
2039
+ }, aSec * 1000,
2040
+ `element (${_context}) is not in DOM or there is no element(${_context}) with text "${text}" after ${aSec} sec`,
2041
+ );
2042
+ }
2043
+
1849
2044
  return this.browser.waitUntil(
1850
2045
  async () => {
1851
2046
  const res = await this.$$(withStrictLocator.call(this, _context));
@@ -1855,8 +2050,10 @@ class WebDriver extends Helper {
1855
2050
  return selected.filter(part => part.indexOf(text) >= 0).length > 0;
1856
2051
  }
1857
2052
  return selected.indexOf(text) >= 0;
1858
- }, aSec * 1000,
1859
- `element (${_context}) is not in DOM or there is no element(${_context}) with text "${text}" after ${aSec} sec`,
2053
+ }, {
2054
+ timeout: aSec * 1000,
2055
+ timeoutMsg: `element (${_context}) is not in DOM or there is no element(${_context}) with text "${text}" after ${aSec} sec`,
2056
+ },
1860
2057
  );
1861
2058
  }
1862
2059
 
@@ -1866,6 +2063,20 @@ class WebDriver extends Helper {
1866
2063
  async waitForValue(field, value, sec = null) {
1867
2064
  const client = this.browser;
1868
2065
  const aSec = sec || this.options.waitForTimeout;
2066
+ if (isWebDriver5()) {
2067
+ return client.waitUntil(
2068
+ async () => {
2069
+ const res = await findFields.call(this, field);
2070
+ if (!res || res.length === 0) return false;
2071
+ const selected = await forEachAsync(res, async el => el.getValue());
2072
+ if (Array.isArray(selected)) {
2073
+ return selected.filter(part => part.indexOf(value) >= 0).length > 0;
2074
+ }
2075
+ return selected.indexOf(value) >= 0;
2076
+ }, aSec * 1000,
2077
+ `element (${field}) is not in DOM or there is no element(${field}) with value "${value}" after ${aSec} sec`,
2078
+ );
2079
+ }
1869
2080
  return client.waitUntil(
1870
2081
  async () => {
1871
2082
  const res = await findFields.call(this, field);
@@ -1875,8 +2086,10 @@ class WebDriver extends Helper {
1875
2086
  return selected.filter(part => part.indexOf(value) >= 0).length > 0;
1876
2087
  }
1877
2088
  return selected.indexOf(value) >= 0;
1878
- }, aSec * 1000,
1879
- `element (${field}) is not in DOM or there is no element(${field}) with value "${value}" after ${aSec} sec`,
2089
+ }, {
2090
+ timeout: aSec * 1000,
2091
+ timeoutMsg: `element (${field}) is not in DOM or there is no element(${field}) with value "${value}" after ${aSec} sec`,
2092
+ },
1880
2093
  );
1881
2094
  }
1882
2095
 
@@ -1886,6 +2099,17 @@ class WebDriver extends Helper {
1886
2099
  */
1887
2100
  async waitForVisible(locator, sec = null) {
1888
2101
  const aSec = sec || this.options.waitForTimeout;
2102
+ if (isWebDriver5()) {
2103
+ return this.browser.waitUntil(async () => {
2104
+ const res = await this.$$(withStrictLocator(locator));
2105
+ if (!res || res.length === 0) return false;
2106
+ const selected = await forEachAsync(res, async el => el.isDisplayed());
2107
+ if (Array.isArray(selected)) {
2108
+ return selected.filter(val => val === true).length > 0;
2109
+ }
2110
+ return selected;
2111
+ }, aSec * 1000, `element (${new Locator(locator)}) still not visible after ${aSec} sec`);
2112
+ }
1889
2113
  return this.browser.waitUntil(async () => {
1890
2114
  const res = await this.$$(withStrictLocator(locator));
1891
2115
  if (!res || res.length === 0) return false;
@@ -1894,7 +2118,7 @@ class WebDriver extends Helper {
1894
2118
  return selected.filter(val => val === true).length > 0;
1895
2119
  }
1896
2120
  return selected;
1897
- }, aSec * 1000, `element (${new Locator(locator)}) still not visible after ${aSec} sec`);
2121
+ }, { timeout: aSec * 1000, timeoutMsg: `element (${new Locator(locator)}) still not visible after ${aSec} sec` });
1898
2122
  }
1899
2123
 
1900
2124
  /**
@@ -1902,6 +2126,16 @@ class WebDriver extends Helper {
1902
2126
  */
1903
2127
  async waitNumberOfVisibleElements(locator, num, sec = null) {
1904
2128
  const aSec = sec || this.options.waitForTimeout;
2129
+ if (isWebDriver5()) {
2130
+ return this.browser.waitUntil(async () => {
2131
+ const res = await this.$$(withStrictLocator(locator));
2132
+ if (!res || res.length === 0) return false;
2133
+ let selected = await forEachAsync(res, async el => el.isDisplayed());
2134
+
2135
+ if (!Array.isArray(selected)) selected = [selected];
2136
+ return selected.length === num;
2137
+ }, aSec * 1000, `The number of elements (${new Locator(locator)}) is not ${num} after ${aSec} sec`);
2138
+ }
1905
2139
  return this.browser.waitUntil(async () => {
1906
2140
  const res = await this.$$(withStrictLocator(locator));
1907
2141
  if (!res || res.length === 0) return false;
@@ -1909,7 +2143,7 @@ class WebDriver extends Helper {
1909
2143
 
1910
2144
  if (!Array.isArray(selected)) selected = [selected];
1911
2145
  return selected.length === num;
1912
- }, aSec * 1000, `The number of elements (${new Locator(locator)}) is not ${num} after ${aSec} sec`);
2146
+ }, { timeout: aSec * 1000, timeoutMsg: `The number of elements (${new Locator(locator)}) is not ${num} after ${aSec} sec` });
1913
2147
  }
1914
2148
 
1915
2149
  /**
@@ -1918,12 +2152,20 @@ class WebDriver extends Helper {
1918
2152
  */
1919
2153
  async waitForInvisible(locator, sec = null) {
1920
2154
  const aSec = sec || this.options.waitForTimeout;
2155
+ if (isWebDriver5()) {
2156
+ return this.browser.waitUntil(async () => {
2157
+ const res = await this.$$(withStrictLocator(locator));
2158
+ if (!res || res.length === 0) return true;
2159
+ const selected = await forEachAsync(res, async el => el.isDisplayed());
2160
+ return !selected.length;
2161
+ }, aSec * 1000, `element (${new Locator(locator)}) still visible after ${aSec} sec`);
2162
+ }
1921
2163
  return this.browser.waitUntil(async () => {
1922
2164
  const res = await this.$$(withStrictLocator(locator));
1923
2165
  if (!res || res.length === 0) return true;
1924
2166
  const selected = await forEachAsync(res, async el => el.isDisplayed());
1925
2167
  return !selected.length;
1926
- }, aSec * 1000, `element (${new Locator(locator)}) still visible after ${aSec} sec`);
2168
+ }, { timeout: aSec * 1000, timeoutMsg: `element (${new Locator(locator)}) still visible after ${aSec} sec` });
1927
2169
  }
1928
2170
 
1929
2171
  /**
@@ -1945,13 +2187,22 @@ class WebDriver extends Helper {
1945
2187
  */
1946
2188
  async waitForDetached(locator, sec = null) {
1947
2189
  const aSec = sec || this.options.waitForTimeout;
2190
+ if (isWebDriver5()) {
2191
+ return this.browser.waitUntil(async () => {
2192
+ const res = await this.$$(withStrictLocator(locator));
2193
+ if (!res || res.length === 0) {
2194
+ return true;
2195
+ }
2196
+ return false;
2197
+ }, aSec * 1000, `element (${new Locator(locator)}) still on page after ${aSec} sec`);
2198
+ }
1948
2199
  return this.browser.waitUntil(async () => {
1949
2200
  const res = await this.$$(withStrictLocator(locator));
1950
2201
  if (!res || res.length === 0) {
1951
2202
  return true;
1952
2203
  }
1953
2204
  return false;
1954
- }, aSec * 1000, `element (${new Locator(locator)}) still on page after ${aSec} sec`);
2205
+ }, { timeout: aSec * 1000, timeoutMsg: `element (${new Locator(locator)}) still on page after ${aSec} sec` });
1955
2206
  }
1956
2207
 
1957
2208
  /**
@@ -1969,7 +2220,10 @@ class WebDriver extends Helper {
1969
2220
  }
1970
2221
 
1971
2222
  const aSec = sec || this.options.waitForTimeout;
1972
- return this.browser.waitUntil(async () => this.browser.execute(fn, ...args), aSec * 1000);
2223
+ if (isWebDriver5()) {
2224
+ return this.browser.waitUntil(async () => this.browser.execute(fn, ...args), aSec * 1000, '');
2225
+ }
2226
+ return this.browser.waitUntil(async () => this.browser.execute(fn, ...args), { timeout: aSec * 1000, timeoutMsg: '' });
1973
2227
  }
1974
2228
 
1975
2229
  /**
@@ -1979,7 +2233,10 @@ class WebDriver extends Helper {
1979
2233
  async waitUntil(fn, sec = null, timeoutMsg = null, interval = null) {
1980
2234
  const aSec = sec || this.options.waitForTimeout;
1981
2235
  const _interval = typeof interval === 'number' ? interval * 1000 : null;
1982
- return this.browser.waitUntil(fn, aSec * 1000, timeoutMsg, _interval);
2236
+ if (isWebDriver5()) {
2237
+ return this.browser.waitUntil(fn, aSec * 1000, timeoutMsg, _interval);
2238
+ }
2239
+ return this.browser.waitUntil(fn, { timeout: aSec * 1000, timeoutMsg, interval: _interval });
1983
2240
  }
1984
2241
 
1985
2242
  /**
@@ -2016,6 +2273,19 @@ class WebDriver extends Helper {
2016
2273
  const aSec = sec || this.options.waitForTimeout;
2017
2274
  let target;
2018
2275
  const current = await this.browser.getWindowHandle();
2276
+
2277
+ if (isWebDriver5()) {
2278
+ await this.browser.waitUntil(async () => {
2279
+ await this.browser.getWindowHandles().then((handles) => {
2280
+ if (handles.indexOf(current) + num + 1 <= handles.length) {
2281
+ target = handles[handles.indexOf(current) + num];
2282
+ }
2283
+ });
2284
+ return target;
2285
+ }, aSec * 1000, `There is no ability to switch to next tab with offset ${num}`);
2286
+ return this.browser.switchToWindow(target);
2287
+ }
2288
+
2019
2289
  await this.browser.waitUntil(async () => {
2020
2290
  await this.browser.getWindowHandles().then((handles) => {
2021
2291
  if (handles.indexOf(current) + num + 1 <= handles.length) {
@@ -2023,7 +2293,7 @@ class WebDriver extends Helper {
2023
2293
  }
2024
2294
  });
2025
2295
  return target;
2026
- }, aSec * 1000, `There is no ability to switch to next tab with offset ${num}`);
2296
+ }, { timeout: aSec * 1000, timeoutMsg: `There is no ability to switch to next tab with offset ${num}` });
2027
2297
  return this.browser.switchToWindow(target);
2028
2298
  }
2029
2299
 
@@ -2042,6 +2312,19 @@ class WebDriver extends Helper {
2042
2312
  const aSec = sec || this.options.waitForTimeout;
2043
2313
  const current = await this.browser.getWindowHandle();
2044
2314
  let target;
2315
+
2316
+ if (isWebDriver5()) {
2317
+ await this.browser.waitUntil(async () => {
2318
+ await this.browser.getWindowHandles().then((handles) => {
2319
+ if (handles.indexOf(current) - num > -1) {
2320
+ target = handles[handles.indexOf(current) - num];
2321
+ }
2322
+ });
2323
+ return target;
2324
+ }, aSec * 1000, `There is no ability to switch to previous tab with offset ${num}`);
2325
+ return this.browser.switchToWindow(target);
2326
+ }
2327
+
2045
2328
  await this.browser.waitUntil(async () => {
2046
2329
  await this.browser.getWindowHandles().then((handles) => {
2047
2330
  if (handles.indexOf(current) - num > -1) {
@@ -2049,7 +2332,7 @@ class WebDriver extends Helper {
2049
2332
  }
2050
2333
  });
2051
2334
  return target;
2052
- }, aSec * 1000, `There is no ability to switch to previous tab with offset ${num}`);
2335
+ }, { timeout: aSec * 1000, timeoutMsg: `There is no ability to switch to previous tab with offset ${num}` });
2053
2336
  return this.browser.switchToWindow(target);
2054
2337
  }
2055
2338
 
@@ -2284,7 +2567,6 @@ async function filterAsync(array, callback) {
2284
2567
  return values;
2285
2568
  }
2286
2569
 
2287
-
2288
2570
  async function findClickable(locator, locateFn) {
2289
2571
  locator = new Locator(locator);
2290
2572
  if (locator.isAccessibilityId() && !this.isWeb) return locateFn(locator, true);
@@ -2308,6 +2590,7 @@ async function findClickable(locator, locateFn) {
2308
2590
 
2309
2591
  async function findFields(locator) {
2310
2592
  locator = new Locator(locator);
2593
+
2311
2594
  if (locator.isAccessibilityId() && !this.isWeb) return this._locate(locator, true);
2312
2595
  if (!locator.isFuzzy()) return this._locate(locator, true);
2313
2596
 
@@ -2622,4 +2905,8 @@ function prepareLocateFn(context) {
2622
2905
  };
2623
2906
  }
2624
2907
 
2908
+ function isWebDriver5() {
2909
+ return version && version.indexOf('5') === 0;
2910
+ }
2911
+
2625
2912
  module.exports = WebDriver;
@@ -0,0 +1,53 @@
1
+ module.exports.createValueEngine = () => {
2
+ return {
3
+ // Creates a selector that matches given target when queried at the root.
4
+ // Can return undefined if unable to create one.
5
+ create(root, target) {
6
+ return null;
7
+ },
8
+
9
+ // Returns the first element matching given selector in the root's subtree.
10
+ query(root, selector) {
11
+ if (!root) {
12
+ return null;
13
+ }
14
+ return `${root.value}`.includes(selector) ? root : null;
15
+ },
16
+
17
+ // Returns all elements matching given selector in the root's subtree.
18
+ queryAll(root, selector) {
19
+ if (!root) {
20
+ return null;
21
+ }
22
+ return `${root.value}`.includes(selector) ? root : null;
23
+ },
24
+ };
25
+ };
26
+
27
+ module.exports.createDisabledEngine = () => {
28
+ return {
29
+ // Creates a selector that matches given target when queried at the root.
30
+ // Can return undefined if unable to create one.
31
+ create(root, target) {
32
+ return null;
33
+ },
34
+
35
+ // Returns the first element matching given selector in the root's subtree.
36
+ query(root, value) {
37
+ const bool = value === 'true';
38
+ if (!root) {
39
+ return null;
40
+ }
41
+ return root.disabled === bool ? root : null;
42
+ },
43
+
44
+ // Returns all elements matching given selector in the root's subtree.
45
+ queryAll(root, value) {
46
+ const bool = value === 'true';
47
+ if (!root) {
48
+ return null;
49
+ }
50
+ return root.disabled === bool ? root : null;
51
+ },
52
+ };
53
+ };