codeceptjs 3.5.10 → 3.5.12-beta.1

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 (225) hide show
  1. package/README.md +3 -3
  2. package/lib/command/run-multiple.js +3 -1
  3. package/lib/command/run-workers.js +32 -1
  4. package/lib/command/workers/runTests.js +18 -2
  5. package/lib/helper/Expect.js +33 -33
  6. package/lib/helper/Playwright.js +49 -26
  7. package/lib/helper/Puppeteer.js +41 -19
  8. package/lib/helper/WebDriver.js +155 -48
  9. package/lib/helper/extras/PlaywrightReactVueLocator.js +38 -0
  10. package/lib/locator.js +17 -4
  11. package/lib/plugin/retryFailedStep.js +6 -2
  12. package/lib/plugin/retryTo.js +2 -2
  13. package/lib/plugin/tryTo.js +5 -4
  14. package/package.json +25 -22
  15. package/typings/index.d.ts +8 -5
  16. package/typings/promiseBasedTypes.d.ts +203 -75
  17. package/typings/types.d.ts +213 -145
  18. package/docs/advanced.md +0 -351
  19. package/docs/ai.md +0 -248
  20. package/docs/api.md +0 -323
  21. package/docs/basics.md +0 -979
  22. package/docs/bdd.md +0 -539
  23. package/docs/best.md +0 -237
  24. package/docs/books.md +0 -37
  25. package/docs/bootstrap.md +0 -135
  26. package/docs/build/ApiDataFactory.js +0 -410
  27. package/docs/build/Appium.js +0 -2027
  28. package/docs/build/Expect.js +0 -422
  29. package/docs/build/FileSystem.js +0 -228
  30. package/docs/build/GraphQL.js +0 -229
  31. package/docs/build/GraphQLDataFactory.js +0 -309
  32. package/docs/build/JSONResponse.js +0 -338
  33. package/docs/build/Mochawesome.js +0 -71
  34. package/docs/build/Nightmare.js +0 -2152
  35. package/docs/build/OpenAI.js +0 -126
  36. package/docs/build/Playwright.js +0 -5078
  37. package/docs/build/Protractor.js +0 -2706
  38. package/docs/build/Puppeteer.js +0 -3874
  39. package/docs/build/REST.js +0 -344
  40. package/docs/build/TestCafe.js +0 -2125
  41. package/docs/build/WebDriver.js +0 -4124
  42. package/docs/changelog.md +0 -2572
  43. package/docs/commands.md +0 -266
  44. package/docs/community-helpers.md +0 -58
  45. package/docs/configuration.md +0 -157
  46. package/docs/continuous-integration.md +0 -22
  47. package/docs/custom-helpers.md +0 -306
  48. package/docs/data.md +0 -379
  49. package/docs/detox.md +0 -235
  50. package/docs/docker.md +0 -136
  51. package/docs/email.md +0 -183
  52. package/docs/examples.md +0 -149
  53. package/docs/helpers/ApiDataFactory.md +0 -266
  54. package/docs/helpers/Appium.md +0 -1374
  55. package/docs/helpers/Detox.md +0 -586
  56. package/docs/helpers/Expect.md +0 -275
  57. package/docs/helpers/FileSystem.md +0 -152
  58. package/docs/helpers/GraphQL.md +0 -151
  59. package/docs/helpers/GraphQLDataFactory.md +0 -226
  60. package/docs/helpers/JSONResponse.md +0 -254
  61. package/docs/helpers/Mochawesome.md +0 -8
  62. package/docs/helpers/MockRequest.md +0 -377
  63. package/docs/helpers/Nightmare.md +0 -1305
  64. package/docs/helpers/OpenAI.md +0 -70
  65. package/docs/helpers/Playwright.md +0 -2706
  66. package/docs/helpers/Polly.md +0 -44
  67. package/docs/helpers/Protractor.md +0 -1769
  68. package/docs/helpers/Puppeteer-firefox.md +0 -86
  69. package/docs/helpers/Puppeteer.md +0 -2291
  70. package/docs/helpers/REST.md +0 -218
  71. package/docs/helpers/TestCafe.md +0 -1321
  72. package/docs/helpers/WebDriver.md +0 -2460
  73. package/docs/hooks.md +0 -340
  74. package/docs/index.md +0 -111
  75. package/docs/installation.md +0 -75
  76. package/docs/internal-api.md +0 -266
  77. package/docs/locators.md +0 -331
  78. package/docs/mobile-react-native-locators.md +0 -67
  79. package/docs/mobile.md +0 -338
  80. package/docs/pageobjects.md +0 -291
  81. package/docs/parallel.md +0 -400
  82. package/docs/playwright.md +0 -632
  83. package/docs/plugins.md +0 -1259
  84. package/docs/puppeteer.md +0 -316
  85. package/docs/quickstart.md +0 -162
  86. package/docs/react.md +0 -69
  87. package/docs/reports.md +0 -392
  88. package/docs/secrets.md +0 -36
  89. package/docs/shadow.md +0 -68
  90. package/docs/shared/keys.mustache +0 -31
  91. package/docs/shared/react.mustache +0 -1
  92. package/docs/testcafe.md +0 -174
  93. package/docs/translation.md +0 -247
  94. package/docs/tutorial.md +0 -271
  95. package/docs/typescript.md +0 -180
  96. package/docs/ui.md +0 -59
  97. package/docs/videos.md +0 -28
  98. package/docs/visual.md +0 -202
  99. package/docs/vue.md +0 -121
  100. package/docs/webapi/amOnPage.mustache +0 -11
  101. package/docs/webapi/appendField.mustache +0 -11
  102. package/docs/webapi/attachFile.mustache +0 -12
  103. package/docs/webapi/blur.mustache +0 -18
  104. package/docs/webapi/checkOption.mustache +0 -13
  105. package/docs/webapi/clearCookie.mustache +0 -9
  106. package/docs/webapi/clearField.mustache +0 -9
  107. package/docs/webapi/click.mustache +0 -25
  108. package/docs/webapi/clickLink.mustache +0 -8
  109. package/docs/webapi/closeCurrentTab.mustache +0 -7
  110. package/docs/webapi/closeOtherTabs.mustache +0 -8
  111. package/docs/webapi/dontSee.mustache +0 -11
  112. package/docs/webapi/dontSeeCheckboxIsChecked.mustache +0 -10
  113. package/docs/webapi/dontSeeCookie.mustache +0 -8
  114. package/docs/webapi/dontSeeCurrentUrlEquals.mustache +0 -10
  115. package/docs/webapi/dontSeeElement.mustache +0 -8
  116. package/docs/webapi/dontSeeElementInDOM.mustache +0 -8
  117. package/docs/webapi/dontSeeInCurrentUrl.mustache +0 -4
  118. package/docs/webapi/dontSeeInField.mustache +0 -11
  119. package/docs/webapi/dontSeeInSource.mustache +0 -8
  120. package/docs/webapi/dontSeeInTitle.mustache +0 -8
  121. package/docs/webapi/doubleClick.mustache +0 -13
  122. package/docs/webapi/downloadFile.mustache +0 -12
  123. package/docs/webapi/dragAndDrop.mustache +0 -9
  124. package/docs/webapi/dragSlider.mustache +0 -11
  125. package/docs/webapi/executeAsyncScript.mustache +0 -24
  126. package/docs/webapi/executeScript.mustache +0 -26
  127. package/docs/webapi/fillField.mustache +0 -16
  128. package/docs/webapi/focus.mustache +0 -13
  129. package/docs/webapi/forceClick.mustache +0 -28
  130. package/docs/webapi/forceRightClick.mustache +0 -18
  131. package/docs/webapi/grabAllWindowHandles.mustache +0 -7
  132. package/docs/webapi/grabAttributeFrom.mustache +0 -10
  133. package/docs/webapi/grabAttributeFromAll.mustache +0 -9
  134. package/docs/webapi/grabBrowserLogs.mustache +0 -9
  135. package/docs/webapi/grabCookie.mustache +0 -11
  136. package/docs/webapi/grabCssPropertyFrom.mustache +0 -11
  137. package/docs/webapi/grabCssPropertyFromAll.mustache +0 -10
  138. package/docs/webapi/grabCurrentUrl.mustache +0 -9
  139. package/docs/webapi/grabCurrentWindowHandle.mustache +0 -6
  140. package/docs/webapi/grabDataFromPerformanceTiming.mustache +0 -20
  141. package/docs/webapi/grabElementBoundingRect.mustache +0 -20
  142. package/docs/webapi/grabGeoLocation.mustache +0 -8
  143. package/docs/webapi/grabHTMLFrom.mustache +0 -10
  144. package/docs/webapi/grabHTMLFromAll.mustache +0 -9
  145. package/docs/webapi/grabNumberOfOpenTabs.mustache +0 -8
  146. package/docs/webapi/grabNumberOfVisibleElements.mustache +0 -9
  147. package/docs/webapi/grabPageScrollPosition.mustache +0 -8
  148. package/docs/webapi/grabPopupText.mustache +0 -5
  149. package/docs/webapi/grabSource.mustache +0 -8
  150. package/docs/webapi/grabTextFrom.mustache +0 -10
  151. package/docs/webapi/grabTextFromAll.mustache +0 -9
  152. package/docs/webapi/grabTitle.mustache +0 -8
  153. package/docs/webapi/grabValueFrom.mustache +0 -9
  154. package/docs/webapi/grabValueFromAll.mustache +0 -8
  155. package/docs/webapi/grabWebElement.mustache +0 -9
  156. package/docs/webapi/grabWebElements.mustache +0 -9
  157. package/docs/webapi/moveCursorTo.mustache +0 -12
  158. package/docs/webapi/openNewTab.mustache +0 -7
  159. package/docs/webapi/pressKey.mustache +0 -12
  160. package/docs/webapi/pressKeyDown.mustache +0 -12
  161. package/docs/webapi/pressKeyUp.mustache +0 -12
  162. package/docs/webapi/pressKeyWithKeyNormalization.mustache +0 -60
  163. package/docs/webapi/refreshPage.mustache +0 -6
  164. package/docs/webapi/resizeWindow.mustache +0 -6
  165. package/docs/webapi/rightClick.mustache +0 -14
  166. package/docs/webapi/saveElementScreenshot.mustache +0 -10
  167. package/docs/webapi/saveScreenshot.mustache +0 -12
  168. package/docs/webapi/say.mustache +0 -10
  169. package/docs/webapi/scrollIntoView.mustache +0 -11
  170. package/docs/webapi/scrollPageToBottom.mustache +0 -6
  171. package/docs/webapi/scrollPageToTop.mustache +0 -6
  172. package/docs/webapi/scrollTo.mustache +0 -12
  173. package/docs/webapi/see.mustache +0 -11
  174. package/docs/webapi/seeAttributesOnElements.mustache +0 -9
  175. package/docs/webapi/seeCheckboxIsChecked.mustache +0 -10
  176. package/docs/webapi/seeCookie.mustache +0 -8
  177. package/docs/webapi/seeCssPropertiesOnElements.mustache +0 -9
  178. package/docs/webapi/seeCurrentUrlEquals.mustache +0 -11
  179. package/docs/webapi/seeElement.mustache +0 -8
  180. package/docs/webapi/seeElementInDOM.mustache +0 -8
  181. package/docs/webapi/seeInCurrentUrl.mustache +0 -8
  182. package/docs/webapi/seeInField.mustache +0 -12
  183. package/docs/webapi/seeInPopup.mustache +0 -8
  184. package/docs/webapi/seeInSource.mustache +0 -7
  185. package/docs/webapi/seeInTitle.mustache +0 -8
  186. package/docs/webapi/seeNumberOfElements.mustache +0 -11
  187. package/docs/webapi/seeNumberOfVisibleElements.mustache +0 -10
  188. package/docs/webapi/seeTextEquals.mustache +0 -9
  189. package/docs/webapi/seeTitleEquals.mustache +0 -8
  190. package/docs/webapi/selectOption.mustache +0 -21
  191. package/docs/webapi/setCookie.mustache +0 -16
  192. package/docs/webapi/setGeoLocation.mustache +0 -12
  193. package/docs/webapi/switchTo.mustache +0 -9
  194. package/docs/webapi/switchToNextTab.mustache +0 -10
  195. package/docs/webapi/switchToPreviousTab.mustache +0 -10
  196. package/docs/webapi/type.mustache +0 -21
  197. package/docs/webapi/uncheckOption.mustache +0 -13
  198. package/docs/webapi/wait.mustache +0 -8
  199. package/docs/webapi/waitForClickable.mustache +0 -11
  200. package/docs/webapi/waitForDetached.mustache +0 -10
  201. package/docs/webapi/waitForElement.mustache +0 -11
  202. package/docs/webapi/waitForEnabled.mustache +0 -6
  203. package/docs/webapi/waitForFunction.mustache +0 -17
  204. package/docs/webapi/waitForInvisible.mustache +0 -10
  205. package/docs/webapi/waitForText.mustache +0 -13
  206. package/docs/webapi/waitForValue.mustache +0 -10
  207. package/docs/webapi/waitForVisible.mustache +0 -10
  208. package/docs/webapi/waitInUrl.mustache +0 -9
  209. package/docs/webapi/waitNumberOfVisibleElements.mustache +0 -10
  210. package/docs/webapi/waitToHide.mustache +0 -10
  211. package/docs/webapi/waitUrlEquals.mustache +0 -10
  212. package/docs/webdriver.md +0 -655
  213. package/docs/wiki/Books-&-Posts.md +0 -27
  214. package/docs/wiki/Community-Helpers-&-Plugins.md +0 -53
  215. package/docs/wiki/Converting-Playwright-to-Istanbul-Coverage.md +0 -61
  216. package/docs/wiki/Examples.md +0 -145
  217. package/docs/wiki/Google-Summer-of-Code-(GSoC)-2020.md +0 -68
  218. package/docs/wiki/Home.md +0 -16
  219. package/docs/wiki/Migration-to-Appium-v2---CodeceptJS.md +0 -83
  220. package/docs/wiki/Release-Process.md +0 -24
  221. package/docs/wiki/Roadmap.md +0 -23
  222. package/docs/wiki/Tests.md +0 -1393
  223. package/docs/wiki/Upgrading-to-CodeceptJS-3.md +0 -153
  224. package/docs/wiki/Videos.md +0 -19
  225. package/lib/helper/extras/PlaywrightReact.js +0 -9
@@ -259,6 +259,7 @@ class Puppeteer extends Helper {
259
259
  headless: !this.options.show,
260
260
  ...this._getOptions(config),
261
261
  };
262
+ if (this.puppeteerOptions.headless) this.puppeteerOptions.headless = 'new';
262
263
  this.isRemoteBrowser = !!this.puppeteerOptions.browserWSEndpoint;
263
264
  popupStore.defaultAction = this.options.defaultPopupAction;
264
265
  }
@@ -684,11 +685,13 @@ class Puppeteer extends Helper {
684
685
  }
685
686
 
686
687
  /**
687
- * {{> resizeWindow }}
688
688
  *
689
689
  * Unlike other drivers Puppeteer changes the size of a viewport, not the window!
690
- * Puppeteer does not control the window of a browser so it can't adjust its real size.
690
+ * Puppeteer does not control the window of a browser, so it can't adjust its real size.
691
691
  * It also can't maximize a window.
692
+ *
693
+ * {{> resizeWindow }}
694
+ *
692
695
  */
693
696
  async resizeWindow(width, height) {
694
697
  if (width === 'maximize') {
@@ -740,7 +743,8 @@ class Puppeteer extends Helper {
740
743
  assertElementExists(els, locator, 'Element to focus');
741
744
  const el = els[0];
742
745
 
743
- await focusElement(el, this.page);
746
+ await el.click();
747
+ await el.focus();
744
748
  return this._waitForAction();
745
749
  }
746
750
 
@@ -880,7 +884,7 @@ class Puppeteer extends Helper {
880
884
  }
881
885
 
882
886
  /**
883
- * Find a checkbox by providing human readable text:
887
+ * Find a checkbox by providing human-readable text:
884
888
  * NOTE: Assumes the checkable element exists
885
889
  *
886
890
  * ```js
@@ -895,7 +899,7 @@ class Puppeteer extends Helper {
895
899
  }
896
900
 
897
901
  /**
898
- * Find a clickable element by providing human readable text:
902
+ * Find a clickable element by providing human-readable text:
899
903
  *
900
904
  * ```js
901
905
  * this.helpers['Puppeteer']._locateClickable('Next page').then // ...
@@ -907,7 +911,7 @@ class Puppeteer extends Helper {
907
911
  }
908
912
 
909
913
  /**
910
- * Find field elements by providing human readable text:
914
+ * Find field elements by providing human-readable text:
911
915
  *
912
916
  * ```js
913
917
  * this.helpers['Puppeteer']._locateFields('Your email').then // ...
@@ -1119,7 +1123,7 @@ class Puppeteer extends Helper {
1119
1123
  * Sets a directory to where save files. Allows to test file downloads.
1120
1124
  * Should be used with [FileSystem helper](https://codecept.io/helpers/FileSystem) to check that file were downloaded correctly.
1121
1125
  *
1122
- * By default files are saved to `output/downloads`.
1126
+ * By default, files are saved to `output/downloads`.
1123
1127
  * This directory is cleaned on every `handleDownloads` call, to ensure no old files are kept.
1124
1128
  *
1125
1129
  * ```js
@@ -1291,9 +1295,9 @@ class Puppeteer extends Helper {
1291
1295
  }
1292
1296
 
1293
1297
  /**
1294
- * {{> pressKeyWithKeyNormalization }}
1295
- *
1296
1298
  * _Note:_ Shortcuts like `'Meta'` + `'A'` do not work on macOS ([GoogleChrome/puppeteer#1313](https://github.com/GoogleChrome/puppeteer/issues/1313)).
1299
+ *
1300
+ * {{> pressKeyWithKeyNormalization }}
1297
1301
  */
1298
1302
  async pressKey(key) {
1299
1303
  const modifiers = [];
@@ -1395,9 +1399,9 @@ class Puppeteer extends Helper {
1395
1399
  }
1396
1400
 
1397
1401
  /**
1398
- * {{> attachFile }}
1399
- *
1400
1402
  * > ⚠ There is an [issue with file upload in Puppeteer 2.1.0 & 2.1.1](https://github.com/puppeteer/puppeteer/issues/5420), downgrade to 2.0.0 if you face it.
1403
+ *
1404
+ * {{> attachFile }}
1401
1405
  */
1402
1406
  async attachFile(locator, pathToFile) {
1403
1407
  const file = path.join(global.codecept_dir, pathToFile);
@@ -1628,9 +1632,9 @@ class Puppeteer extends Helper {
1628
1632
  }
1629
1633
 
1630
1634
  /**
1631
- * {{> executeScript }}
1635
+ * If a function returns a Promise, tt will wait for its resolution.
1632
1636
  *
1633
- * If a function returns a Promise It will wait for it resolution.
1637
+ * {{> executeScript }}
1634
1638
  */
1635
1639
  async executeScript(...args) {
1636
1640
  let context = this.page;
@@ -1641,9 +1645,8 @@ class Puppeteer extends Helper {
1641
1645
  }
1642
1646
 
1643
1647
  /**
1644
- * {{> executeAsyncScript }}
1645
- *
1646
1648
  * Asynchronous scripts can also be executed with `executeScript` if a function returns a Promise.
1649
+ * {{> executeAsyncScript }}
1647
1650
  */
1648
1651
  async executeAsyncScript(...args) {
1649
1652
  const asyncFn = function () {
@@ -1822,7 +1825,8 @@ class Puppeteer extends Helper {
1822
1825
  for (let i = 0; i < val.length; ++i) {
1823
1826
  const _actual = Number.isNaN(val[i]) || (typeof values[i]) === 'string' ? val[i] : Number.parseInt(values[i], 10);
1824
1827
  const _expected = Number.isNaN(values[i]) || (typeof values[i]) === 'string' ? values[i] : Number.parseInt(values[i], 10);
1825
- if (!_actual.includes(_expected)) return false;
1828
+ // if the attribute doesn't exist, returns false as well
1829
+ if (!_actual || !_actual.includes(_expected)) return false;
1826
1830
  }
1827
1831
  return true;
1828
1832
  });
@@ -2092,7 +2096,7 @@ class Puppeteer extends Helper {
2092
2096
  /**
2093
2097
  * {{> waitForVisible }}
2094
2098
  *
2095
- * This method accepts [React selectors](https://codecept.io/react).
2099
+ * {{ react }}
2096
2100
  */
2097
2101
  async waitForVisible(locator, sec) {
2098
2102
  const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
@@ -2147,6 +2151,24 @@ class Puppeteer extends Helper {
2147
2151
  });
2148
2152
  }
2149
2153
 
2154
+ /**
2155
+ * {{> waitForNumberOfTabs }}
2156
+ */
2157
+ async waitForNumberOfTabs(expectedTabs, sec) {
2158
+ const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
2159
+ let currentTabs;
2160
+ let count = 0;
2161
+
2162
+ do {
2163
+ currentTabs = await this.grabNumberOfOpenTabs();
2164
+ await this.wait(1);
2165
+ count += 1000;
2166
+ if (currentTabs >= expectedTabs) return;
2167
+ } while (count <= waitTimeout);
2168
+
2169
+ throw new Error(`Expected ${expectedTabs} tabs are not met after ${waitTimeout / 1000} sec.`);
2170
+ }
2171
+
2150
2172
  async _getContext() {
2151
2173
  if (this.context && this.context.constructor.name === 'Frame') {
2152
2174
  return this.context;
@@ -2321,9 +2343,9 @@ class Puppeteer extends Helper {
2321
2343
  }
2322
2344
 
2323
2345
  /**
2324
- * Waits for navigation to finish. By default takes configured `waitForNavigation` option.
2346
+ * Waits for navigation to finish. By default, takes configured `waitForNavigation` option.
2325
2347
  *
2326
- * See [Pupeteer's reference](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagewaitfornavigationoptions)
2348
+ * See [Puppeteer's reference](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagewaitfornavigationoptions)
2327
2349
  *
2328
2350
  * @param {*} opts
2329
2351
  */
@@ -63,6 +63,8 @@ const webRoot = 'body';
63
63
  * @prop {boolean} [manualStart=false] - do not start browser before a test, start it manually inside a helper with `this.helpers["WebDriver"]._startBrowser()`.
64
64
  * @prop {object} [timeouts] [WebDriver timeouts](http://webdriver.io/docs/timeouts.html) defined as hash.
65
65
  * @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose).
66
+ * @prop {string} [logLevel=silent] - level of logging verbosity. Default: silent. Options: trace | debug | info | warn | error | silent. More info: https://webdriver.io/docs/configuration/#loglevel
67
+ * @prop {boolean} [devtoolsProtocol=false] - enable devtools protocol. Default: false. More info: https://webdriver.io/docs/automationProtocols/#devtools-protocol.
66
68
  */
67
69
  const config = {};
68
70
 
@@ -72,6 +74,13 @@ const config = {};
72
74
  *
73
75
  * WebDriver requires Selenium Server and ChromeDriver/GeckoDriver to be installed. Those tools can be easily installed via NPM. Please check [Testing with WebDriver](https://codecept.io/webdriver/#testing-with-webdriver) for more details.
74
76
  *
77
+ * With the release of WebdriverIO version v8.14.0, and onwards, all driver management hassles are now a thing of the past 🙌. Read more [here](https://webdriver.io/blog/2023/07/31/driver-management/).
78
+ * One of the significant advantages of this update is that you can now get rid of any driver services you previously had to manage, such as
79
+ * `wdio-chromedriver-service`, `wdio-geckodriver-service`, `wdio-edgedriver-service`, `wdio-safaridriver-service`, and even `@wdio/selenium-standalone-service`.
80
+ *
81
+ * For those who require custom driver options, fear not; WebDriver Helper allows you to pass in driver options through custom WebDriver configuration.
82
+ * If you have a custom grid, use a cloud service, or prefer to run your own driver, there's no need to worry since WebDriver Helper will only start a driver when there are no other connection information settings like hostname or port specified.
83
+ *
75
84
  * <!-- configuration -->
76
85
  *
77
86
  * Example:
@@ -93,6 +102,28 @@ const config = {};
93
102
  * }
94
103
  * ```
95
104
  *
105
+ * Testing Chrome locally is now more convenient than ever. You can define a browser channel, and WebDriver Helper will take care of downloading the specified browser version for you.
106
+ * For example:
107
+ *
108
+ * ```js
109
+ * {
110
+ * helpers: {
111
+ * WebDriver : {
112
+ * smartWait: 5000,
113
+ * browser: "chrome",
114
+ * browserVersion: '116.0.5793.0', // or 'stable', 'beta', 'dev' or 'canary'
115
+ * restart: false,
116
+ * windowSize: "maximize",
117
+ * timeouts: {
118
+ * "script": 60000,
119
+ * "page load": 10000
120
+ * }
121
+ * }
122
+ * }
123
+ * }
124
+ * ```
125
+ *
126
+ *
96
127
  * Example with basic authentication
97
128
  * ```js
98
129
  * {
@@ -133,6 +164,25 @@ const config = {};
133
164
  * }
134
165
  * ```
135
166
  *
167
+ * ### Running with devtools protocol
168
+ *
169
+ * ```js
170
+ * {
171
+ * helpers: {
172
+ * WebDriver : {
173
+ * url: "http://localhost",
174
+ * browser: "chrome",
175
+ * devtoolsProtocol: true,
176
+ * desiredCapabilities: {
177
+ * chromeOptions: {
178
+ * args: [ "--headless", "--disable-gpu", "--no-sandbox" ]
179
+ * }
180
+ * }
181
+ * }
182
+ * }
183
+ * }
184
+ * ```
185
+ *
136
186
  * ### Internet Explorer
137
187
  *
138
188
  * Additional configuration params can be used from [IE options](https://seleniumhq.github.io/selenium/docs/api/rb/Selenium/WebDriver/IE/Options.html)
@@ -415,7 +465,6 @@ class WebDriver extends Helper {
415
465
  _validateConfig(config) {
416
466
  const defaults = {
417
467
  logLevel: 'silent',
418
- path: '/wd/hub',
419
468
  // codeceptjs
420
469
  remoteFileUpload: true,
421
470
  smartWait: 0,
@@ -435,12 +484,17 @@ class WebDriver extends Helper {
435
484
  // override defaults with config
436
485
  config = Object.assign(defaults, config);
437
486
 
438
- if (typeof config.host !== 'undefined') config.hostname = config.host; // webdriverio spec
487
+ if (config.host) {
488
+ // webdriverio spec
489
+ config.hostname = config.host;
490
+ config.path = '/wd/hub';
491
+ }
439
492
  config.baseUrl = config.url || config.baseUrl;
440
493
  if (config.desiredCapabilities && Object.keys(config.desiredCapabilities).length) {
441
494
  config.capabilities = config.desiredCapabilities;
442
495
  }
443
496
  config.capabilities.browserName = config.browser || config.capabilities.browserName;
497
+ config.capabilities.browserVersion = config.browserVersion || config.capabilities.browserVersion;
444
498
  if (config.capabilities.chromeOptions) {
445
499
  config.capabilities['goog:chromeOptions'] = config.capabilities.chromeOptions;
446
500
  delete config.capabilities.chromeOptions;
@@ -542,6 +596,10 @@ class WebDriver extends Helper {
542
596
  delete this.options.capabilities.hostname;
543
597
  delete this.options.capabilities.port;
544
598
  delete this.options.capabilities.path;
599
+ if (this.options.devtoolsProtocol) {
600
+ if (!['chrome', 'chromium'].includes(this.options.browser.toLowerCase())) throw Error('The devtools protocol is only working with Chrome or Chromium');
601
+ this.options.automationProtocol = 'devtools';
602
+ }
545
603
  this.browser = await webdriverio.remote(this.options);
546
604
  }
547
605
  } catch (err) {
@@ -1043,7 +1101,8 @@ class WebDriver extends Helper {
1043
1101
  assertElementExists(res, field, 'Field');
1044
1102
  const elem = usingFirstElement(res);
1045
1103
  highlightActiveElement.call(this, elem);
1046
- return elem.setValue(value.toString());
1104
+ await elem.clearValue();
1105
+ await elem.setValue(value.toString());
1047
1106
  }
1048
1107
 
1049
1108
  /**
@@ -1055,6 +1114,10 @@ class WebDriver extends Helper {
1055
1114
  assertElementExists(res, field, 'Field');
1056
1115
  const elem = usingFirstElement(res);
1057
1116
  highlightActiveElement.call(this, elem);
1117
+ if (this.options.automationProtocol) {
1118
+ const curentValue = await elem.getValue();
1119
+ return elem.setValue(curentValue + value.toString());
1120
+ }
1058
1121
  return elem.addValue(value.toString());
1059
1122
  }
1060
1123
 
@@ -1067,6 +1130,9 @@ class WebDriver extends Helper {
1067
1130
  assertElementExists(res, field, 'Field');
1068
1131
  const elem = usingFirstElement(res);
1069
1132
  highlightActiveElement.call(this, elem);
1133
+ if (this.options.automationProtocol) {
1134
+ return elem.setValue('');
1135
+ }
1070
1136
  return elem.clearValue(getElementId(elem));
1071
1137
  }
1072
1138
 
@@ -1104,8 +1170,9 @@ class WebDriver extends Helper {
1104
1170
  }
1105
1171
 
1106
1172
  /**
1107
- * {{> attachFile }}
1108
1173
  * Appium: not tested
1174
+ *
1175
+ * {{> attachFile }}
1109
1176
  */
1110
1177
  async attachFile(locator, pathToFile) {
1111
1178
  let file = path.join(global.codecept_dir, pathToFile);
@@ -1119,7 +1186,7 @@ class WebDriver extends Helper {
1119
1186
  const el = usingFirstElement(res);
1120
1187
 
1121
1188
  // Remote Upload (when running Selenium Server)
1122
- if (this.options.remoteFileUpload) {
1189
+ if (this.options.remoteFileUpload && !this.options.automationProtocol) {
1123
1190
  try {
1124
1191
  this.debugSection('File', 'Uploading file to remote server');
1125
1192
  file = await this.browser.uploadFile(file);
@@ -1132,8 +1199,8 @@ class WebDriver extends Helper {
1132
1199
  }
1133
1200
 
1134
1201
  /**
1135
- * {{> checkOption }}
1136
1202
  * Appium: not tested
1203
+ * {{> checkOption }}
1137
1204
  */
1138
1205
  async checkOption(field, context = null) {
1139
1206
  const clickMethod = this.browser.isMobile && !this.browser.isW3C ? 'touchClick' : 'elementClick';
@@ -1152,8 +1219,8 @@ class WebDriver extends Helper {
1152
1219
  }
1153
1220
 
1154
1221
  /**
1155
- * {{> uncheckOption }}
1156
1222
  * Appium: not tested
1223
+ * {{> uncheckOption }}
1157
1224
  */
1158
1225
  async uncheckOption(field, context = null) {
1159
1226
  const clickMethod = this.browser.isMobile && !this.browser.isW3C ? 'touchClick' : 'elementClick';
@@ -1370,16 +1437,16 @@ class WebDriver extends Helper {
1370
1437
  }
1371
1438
 
1372
1439
  /**
1373
- * {{> seeCheckboxIsChecked }}
1374
1440
  * Appium: not tested
1441
+ * {{> seeCheckboxIsChecked }}
1375
1442
  */
1376
1443
  async seeCheckboxIsChecked(field) {
1377
1444
  return proceedSeeCheckbox.call(this, 'assert', field);
1378
1445
  }
1379
1446
 
1380
1447
  /**
1381
- * {{> dontSeeCheckboxIsChecked }}
1382
1448
  * Appium: not tested
1449
+ * {{> dontSeeCheckboxIsChecked }}
1383
1450
  */
1384
1451
  async dontSeeCheckboxIsChecked(field) {
1385
1452
  return proceedSeeCheckbox.call(this, 'negate', field);
@@ -1497,35 +1564,33 @@ class WebDriver extends Helper {
1497
1564
  async seeCssPropertiesOnElements(locator, cssProperties) {
1498
1565
  const res = await this._locate(locator);
1499
1566
  assertElementExists(res, locator);
1567
+
1568
+ const cssPropertiesCamelCase = convertCssPropertiesToCamelCase(cssProperties);
1500
1569
  const elemAmount = res.length;
1570
+ let props = [];
1501
1571
 
1502
- let props = await forEachAsync(res, async (el) => {
1503
- return forEachAsync(Object.keys(cssProperties), async (prop) => {
1504
- const propValue = await this.browser.getElementCSSValue(getElementId(el), prop);
1505
- if (isColorProperty(prop) && propValue && propValue.value) {
1506
- return convertColorToRGBA(propValue.value);
1572
+ for (const element of res) {
1573
+ for (const prop of Object.keys(cssProperties)) {
1574
+ const cssProp = await this.grabCssPropertyFrom(locator, prop);
1575
+ if (isColorProperty(prop)) {
1576
+ props.push(convertColorToRGBA(cssProp));
1577
+ } else {
1578
+ props.push(cssProp);
1507
1579
  }
1508
- return propValue;
1509
- });
1510
- });
1511
-
1512
- const cssPropertiesCamelCase = convertCssPropertiesToCamelCase(cssProperties);
1580
+ }
1581
+ }
1513
1582
 
1514
1583
  const values = Object.keys(cssPropertiesCamelCase).map(key => cssPropertiesCamelCase[key]);
1515
1584
  if (!Array.isArray(props)) props = [props];
1516
1585
  let chunked = chunkArray(props, values.length);
1517
1586
  chunked = chunked.filter((val) => {
1518
1587
  for (let i = 0; i < val.length; ++i) {
1519
- const _acutal = Number.isNaN(val[i]) || (typeof values[i]) === 'string' ? val[i] : Number.parseInt(val[i], 10);
1520
- const _expected = Number.isNaN(values[i]) || (typeof values[i]) === 'string' ? values[i] : Number.parseInt(values[i], 10);
1521
- if (_acutal !== _expected) return false;
1588
+ // eslint-disable-next-line eqeqeq
1589
+ if (val[i] != values[i]) return false;
1522
1590
  }
1523
1591
  return true;
1524
1592
  });
1525
- return assert.ok(
1526
- chunked.length === elemAmount,
1527
- `expected all elements (${(new Locator(locator))}) to have CSS property ${JSON.stringify(cssProperties)}`,
1528
- );
1593
+ return equals(`all elements (${(new Locator(locator))}) to have CSS property ${JSON.stringify(cssProperties)}`).assert(chunked.length, elemAmount);
1529
1594
  }
1530
1595
 
1531
1596
  /**
@@ -1545,9 +1610,9 @@ class WebDriver extends Helper {
1545
1610
  let chunked = chunkArray(attrs, values.length);
1546
1611
  chunked = chunked.filter((val) => {
1547
1612
  for (let i = 0; i < val.length; ++i) {
1548
- const _acutal = Number.isNaN(val[i]) || (typeof values[i]) === 'string' ? val[i] : Number.parseInt(val[i], 10);
1613
+ const _actual = Number.isNaN(val[i]) || (typeof values[i]) === 'string' ? val[i] : Number.parseInt(val[i], 10);
1549
1614
  const _expected = Number.isNaN(values[i]) || (typeof values[i]) === 'string' ? values[i] : Number.parseInt(values[i], 10);
1550
- if (_acutal !== _expected) return false;
1615
+ if (_actual !== _expected) return false;
1551
1616
  }
1552
1617
  return true;
1553
1618
  });
@@ -1606,10 +1671,9 @@ class WebDriver extends Helper {
1606
1671
  }
1607
1672
 
1608
1673
  /**
1609
- * {{> executeScript }}
1610
- *
1611
- *
1612
1674
  * Wraps [execute](http://webdriver.io/api/protocol/execute.html) command.
1675
+ *
1676
+ * {{> executeScript }}
1613
1677
  */
1614
1678
  executeScript(...args) {
1615
1679
  return this.browser.execute.apply(this.browser, args);
@@ -1672,7 +1736,11 @@ class WebDriver extends Helper {
1672
1736
  const res = await this._locate(withStrictLocator(locator), true);
1673
1737
  assertElementExists(res, locator);
1674
1738
  const elem = usingFirstElement(res);
1675
- return elem.moveTo({ xOffset, yOffset });
1739
+ try {
1740
+ await elem.moveTo({ xOffset, yOffset });
1741
+ } catch (e) {
1742
+ debug(e.message);
1743
+ }
1676
1744
  }
1677
1745
 
1678
1746
  /**
@@ -1731,11 +1799,8 @@ class WebDriver extends Helper {
1731
1799
  }
1732
1800
 
1733
1801
  /**
1802
+ * Uses Selenium's JSON [cookie format](https://code.google.com/p/selenium/wiki/JsonWireProtocol#Cookie_JSON_Object).
1734
1803
  * {{> setCookie }}
1735
- *
1736
- *
1737
- * Uses Selenium's JSON [cookie
1738
- * format](https://code.google.com/p/selenium/wiki/JsonWireProtocol#Cookie_JSON_Object).
1739
1804
  */
1740
1805
  async setCookie(cookie) {
1741
1806
  return this.browser.setCookies(cookie);
@@ -1788,7 +1853,7 @@ class WebDriver extends Helper {
1788
1853
  }
1789
1854
 
1790
1855
  /**
1791
- * Dismisses the active JavaScript popup, as created by window.alert|window.confirm|window.prompt.
1856
+ * Dismisses the active JavaScript popup, as created by `window.alert|window.confirm|window.prompt`.
1792
1857
  *
1793
1858
  */
1794
1859
  async cancelPopup() {
@@ -1862,9 +1927,9 @@ class WebDriver extends Helper {
1862
1927
  }
1863
1928
 
1864
1929
  /**
1865
- * {{> pressKeyWithKeyNormalization }}
1866
- *
1867
1930
  * _Note:_ In case a text field or textarea is focused be aware that some browsers do not respect active modifier when combining modifier keys with other keys.
1931
+ *
1932
+ * {{> pressKeyWithKeyNormalization }}
1868
1933
  */
1869
1934
  async pressKey(key) {
1870
1935
  const modifiers = [];
@@ -1923,11 +1988,12 @@ class WebDriver extends Helper {
1923
1988
  }
1924
1989
 
1925
1990
  /**
1926
- * {{> resizeWindow }}
1927
1991
  * Appium: not tested in web, in apps doesn't work
1992
+ *
1993
+ * {{> resizeWindow }}
1928
1994
  */
1929
1995
  async resizeWindow(width, height) {
1930
- return this._resizeBrowserWindow(this.browser, width, height);
1996
+ return this.browser.setWindowSize(width, height);
1931
1997
  }
1932
1998
 
1933
1999
  async _resizeBrowserWindow(browser, width, height) {
@@ -1976,8 +2042,8 @@ class WebDriver extends Helper {
1976
2042
  }
1977
2043
 
1978
2044
  /**
1979
- * {{> dragAndDrop }}
1980
2045
  * Appium: not tested
2046
+ * {{> dragAndDrop }}
1981
2047
  */
1982
2048
  async dragAndDrop(srcElement, destElement) {
1983
2049
  let sourceEl = await this._locate(srcElement);
@@ -2308,12 +2374,33 @@ class WebDriver extends Helper {
2308
2374
  return this.browser.waitUntil(async () => this.browser.execute(fn, ...args), { timeout: aSec * 1000, timeoutMsg: '' });
2309
2375
  }
2310
2376
 
2377
+ /**
2378
+ * {{> waitForNumberOfTabs }}
2379
+ */
2380
+ async waitForNumberOfTabs(expectedTabs, sec) {
2381
+ const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeoutInSeconds;
2382
+ let currentTabs;
2383
+ let count = 0;
2384
+
2385
+ do {
2386
+ currentTabs = await this.grabNumberOfOpenTabs();
2387
+ await this.wait(1);
2388
+ count += 1000;
2389
+ if (currentTabs >= expectedTabs) return;
2390
+ } while (count <= waitTimeout);
2391
+
2392
+ throw new Error(`Expected ${expectedTabs} tabs are not met after ${waitTimeout / 1000} sec.`);
2393
+ }
2394
+
2311
2395
  /**
2312
2396
  * {{> switchTo }}
2313
2397
  */
2314
2398
  async switchTo(locator) {
2315
2399
  this.browser.isInsideFrame = true;
2316
2400
  if (Number.isInteger(locator)) {
2401
+ if (this.options.automationProtocol) {
2402
+ return this.browser.switchToFrame(locator + 1);
2403
+ }
2317
2404
  return this.browser.switchToFrame(locator);
2318
2405
  }
2319
2406
  if (!locator) {
@@ -2450,21 +2537,41 @@ class WebDriver extends Helper {
2450
2537
  }
2451
2538
 
2452
2539
  /**
2540
+ * This method is **deprecated**.
2541
+ *
2542
+ *
2453
2543
  * {{> setGeoLocation }}
2454
2544
  */
2455
- async setGeoLocation(latitude, longitude, altitude = null) {
2456
- if (altitude) {
2457
- return this.browser.setGeoLocation({ latitude, longitude });
2545
+ async setGeoLocation(latitude, longitude) {
2546
+ if (!this.options.automationProtocol) {
2547
+ console.log(`setGeoLocation deprecated:
2548
+ * This command is deprecated due to using deprecated JSON Wire Protocol command. More info: https://webdriver.io/docs/api/jsonwp/#setgeolocation
2549
+ * Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration`);
2550
+ return;
2458
2551
  }
2459
- return this.browser.setGeoLocation({ latitude, longitude, altitude });
2552
+ this.geoLocation = { latitude, longitude };
2553
+ const puppeteerBrowser = await this.browser.getPuppeteer();
2554
+ await this.browser.call(async () => {
2555
+ const pages = await puppeteerBrowser.pages();
2556
+ await pages[0].setGeolocation({ latitude, longitude });
2557
+ });
2460
2558
  }
2461
2559
 
2462
2560
  /**
2561
+ * This method is **deprecated**.
2562
+ *
2463
2563
  * {{> grabGeoLocation }}
2464
2564
  *
2465
2565
  */
2466
2566
  async grabGeoLocation() {
2467
- return this.browser.getGeoLocation();
2567
+ if (!this.options.automationProtocol) {
2568
+ console.log(`grabGeoLocation deprecated:
2569
+ * This command is deprecated due to using deprecated JSON Wire Protocol command. More info: https://webdriver.io/docs/api/jsonwp/#getgeolocation
2570
+ * Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration`);
2571
+ return;
2572
+ }
2573
+ if (!this.geoLocation) return 'No GeoLocation is set!';
2574
+ return this.geoLocation;
2468
2575
  }
2469
2576
 
2470
2577
  /**
@@ -2660,7 +2767,7 @@ async function proceedSeeField(assertType, field, value) {
2660
2767
  }
2661
2768
  };
2662
2769
 
2663
- const proceedSingle = el => this.browser.getElementAttribute(getElementId(el), 'value').then((res) => {
2770
+ const proceedSingle = el => el.getValue().then((res) => {
2664
2771
  if (res === null) {
2665
2772
  throw new Error(`Element ${el.selector} has no value attribute`);
2666
2773
  }
@@ -0,0 +1,38 @@
1
+ async function findReact(matcher, locator) {
2
+ let _locator = `_react=${locator.react}`;
3
+ let props = '';
4
+
5
+ if (locator.props) {
6
+ props += propBuilder(locator.props);
7
+ _locator += props;
8
+ }
9
+ return matcher.locator(_locator).all();
10
+ }
11
+
12
+ async function findVue(matcher, locator) {
13
+ let _locator = `_vue=${locator.vue}`;
14
+ let props = '';
15
+
16
+ if (locator.props) {
17
+ props += propBuilder(locator.props);
18
+ _locator += props;
19
+ }
20
+ return matcher.locator(_locator).all();
21
+ }
22
+
23
+ function propBuilder(props) {
24
+ let _props = '';
25
+
26
+ for (const [key, value] of Object.entries(props)) {
27
+ if (typeof value === 'object') {
28
+ for (const [k, v] of Object.entries(value)) {
29
+ _props += `[${key}.${k} = "${v}"]`;
30
+ }
31
+ } else {
32
+ _props += `[${key} = "${value}"]`;
33
+ }
34
+ }
35
+ return _props;
36
+ }
37
+
38
+ module.exports = { findReact, findVue };
package/lib/locator.js CHANGED
@@ -1,4 +1,4 @@
1
- const cssToXPath = require('convert-cssxpath');
1
+ const cssToXPath = require('csstoxpath');
2
2
  const { sprintf } = require('sprintf-js');
3
3
 
4
4
  const { xpathLocator } = require('./utils');
@@ -158,11 +158,12 @@ class Locator {
158
158
  }
159
159
 
160
160
  /**
161
+ * @param {string} [pseudoSelector] CSS to XPath extension pseudo: https://www.npmjs.com/package/csstoxpath?activeTab=explore#extension-pseudos
161
162
  * @returns {string}
162
163
  */
163
- toXPath() {
164
+ toXPath(pseudoSelector = '') {
164
165
  if (this.isXPath()) return this.value;
165
- if (this.isCSS()) return cssToXPath.convert(this.value);
166
+ if (this.isCSS()) return cssToXPath(`${this.value}${pseudoSelector}`);
166
167
 
167
168
  throw new Error('Can\'t be converted to XPath');
168
169
  }
@@ -243,12 +244,24 @@ class Locator {
243
244
  }
244
245
 
245
246
  /**
247
+ * Find an element containing a text
246
248
  * @param {string} text
247
249
  * @returns {Locator}
248
250
  */
249
251
  withText(text) {
250
252
  text = xpathLocator.literal(text);
251
- const xpath = sprintf('%s[%s]', this.toXPath(), `contains(., ${text})`);
253
+ const xpath = this.toXPath(`:text-contains-case(${text})`);
254
+ return new Locator({ xpath });
255
+ }
256
+
257
+ /**
258
+ * Find an element with exact text
259
+ * @param {string} text
260
+ * @returns {Locator}
261
+ */
262
+ withTextEquals(text) {
263
+ text = xpathLocator.literal(text);
264
+ const xpath = this.toXPath(`:text-case(${text})`);
252
265
  return new Locator({ xpath });
253
266
  }
254
267