codeceptjs 3.5.11 → 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 (221) 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 +2 -2
  5. package/lib/helper/Playwright.js +21 -2
  6. package/lib/helper/Puppeteer.js +18 -0
  7. package/lib/helper/WebDriver.js +140 -31
  8. package/lib/locator.js +17 -4
  9. package/lib/plugin/retryFailedStep.js +5 -1
  10. package/lib/plugin/retryTo.js +2 -2
  11. package/package.json +14 -11
  12. package/typings/index.d.ts +9 -6
  13. package/typings/promiseBasedTypes.d.ts +84 -1
  14. package/typings/types.d.ts +102 -2
  15. package/docs/advanced.md +0 -351
  16. package/docs/ai.md +0 -248
  17. package/docs/api.md +0 -323
  18. package/docs/basics.md +0 -979
  19. package/docs/bdd.md +0 -539
  20. package/docs/best.md +0 -237
  21. package/docs/books.md +0 -37
  22. package/docs/bootstrap.md +0 -135
  23. package/docs/build/ApiDataFactory.js +0 -410
  24. package/docs/build/Appium.js +0 -2027
  25. package/docs/build/Expect.js +0 -422
  26. package/docs/build/FileSystem.js +0 -228
  27. package/docs/build/GraphQL.js +0 -229
  28. package/docs/build/GraphQLDataFactory.js +0 -309
  29. package/docs/build/JSONResponse.js +0 -338
  30. package/docs/build/Mochawesome.js +0 -71
  31. package/docs/build/Nightmare.js +0 -2152
  32. package/docs/build/OpenAI.js +0 -126
  33. package/docs/build/Playwright.js +0 -5082
  34. package/docs/build/Protractor.js +0 -2706
  35. package/docs/build/Puppeteer.js +0 -3878
  36. package/docs/build/REST.js +0 -344
  37. package/docs/build/TestCafe.js +0 -2125
  38. package/docs/build/WebDriver.js +0 -4122
  39. package/docs/changelog.md +0 -2572
  40. package/docs/commands.md +0 -266
  41. package/docs/community-helpers.md +0 -58
  42. package/docs/configuration.md +0 -157
  43. package/docs/continuous-integration.md +0 -22
  44. package/docs/custom-helpers.md +0 -306
  45. package/docs/data.md +0 -379
  46. package/docs/detox.md +0 -235
  47. package/docs/docker.md +0 -136
  48. package/docs/email.md +0 -183
  49. package/docs/examples.md +0 -149
  50. package/docs/helpers/ApiDataFactory.md +0 -266
  51. package/docs/helpers/Appium.md +0 -1374
  52. package/docs/helpers/Detox.md +0 -586
  53. package/docs/helpers/Expect.md +0 -275
  54. package/docs/helpers/FileSystem.md +0 -152
  55. package/docs/helpers/GraphQL.md +0 -151
  56. package/docs/helpers/GraphQLDataFactory.md +0 -226
  57. package/docs/helpers/JSONResponse.md +0 -254
  58. package/docs/helpers/Mochawesome.md +0 -8
  59. package/docs/helpers/MockRequest.md +0 -377
  60. package/docs/helpers/Nightmare.md +0 -1305
  61. package/docs/helpers/OpenAI.md +0 -70
  62. package/docs/helpers/Playwright.md +0 -2744
  63. package/docs/helpers/Polly.md +0 -44
  64. package/docs/helpers/Protractor.md +0 -1769
  65. package/docs/helpers/Puppeteer-firefox.md +0 -86
  66. package/docs/helpers/Puppeteer.md +0 -2302
  67. package/docs/helpers/REST.md +0 -218
  68. package/docs/helpers/TestCafe.md +0 -1321
  69. package/docs/helpers/WebDriver.md +0 -2473
  70. package/docs/hooks.md +0 -340
  71. package/docs/index.md +0 -111
  72. package/docs/installation.md +0 -75
  73. package/docs/internal-api.md +0 -266
  74. package/docs/locators.md +0 -331
  75. package/docs/mobile-react-native-locators.md +0 -67
  76. package/docs/mobile.md +0 -338
  77. package/docs/pageobjects.md +0 -291
  78. package/docs/parallel.md +0 -400
  79. package/docs/playwright.md +0 -632
  80. package/docs/plugins.md +0 -1259
  81. package/docs/puppeteer.md +0 -316
  82. package/docs/quickstart.md +0 -162
  83. package/docs/react.md +0 -70
  84. package/docs/reports.md +0 -392
  85. package/docs/secrets.md +0 -36
  86. package/docs/shadow.md +0 -68
  87. package/docs/shared/keys.mustache +0 -31
  88. package/docs/shared/react.mustache +0 -1
  89. package/docs/testcafe.md +0 -174
  90. package/docs/translation.md +0 -247
  91. package/docs/tutorial.md +0 -271
  92. package/docs/typescript.md +0 -180
  93. package/docs/ui.md +0 -59
  94. package/docs/videos.md +0 -28
  95. package/docs/visual.md +0 -202
  96. package/docs/vue.md +0 -143
  97. package/docs/webapi/amOnPage.mustache +0 -11
  98. package/docs/webapi/appendField.mustache +0 -11
  99. package/docs/webapi/attachFile.mustache +0 -12
  100. package/docs/webapi/blur.mustache +0 -18
  101. package/docs/webapi/checkOption.mustache +0 -13
  102. package/docs/webapi/clearCookie.mustache +0 -9
  103. package/docs/webapi/clearField.mustache +0 -9
  104. package/docs/webapi/click.mustache +0 -25
  105. package/docs/webapi/clickLink.mustache +0 -8
  106. package/docs/webapi/closeCurrentTab.mustache +0 -7
  107. package/docs/webapi/closeOtherTabs.mustache +0 -8
  108. package/docs/webapi/dontSee.mustache +0 -11
  109. package/docs/webapi/dontSeeCheckboxIsChecked.mustache +0 -10
  110. package/docs/webapi/dontSeeCookie.mustache +0 -8
  111. package/docs/webapi/dontSeeCurrentUrlEquals.mustache +0 -10
  112. package/docs/webapi/dontSeeElement.mustache +0 -8
  113. package/docs/webapi/dontSeeElementInDOM.mustache +0 -8
  114. package/docs/webapi/dontSeeInCurrentUrl.mustache +0 -4
  115. package/docs/webapi/dontSeeInField.mustache +0 -11
  116. package/docs/webapi/dontSeeInSource.mustache +0 -8
  117. package/docs/webapi/dontSeeInTitle.mustache +0 -8
  118. package/docs/webapi/doubleClick.mustache +0 -13
  119. package/docs/webapi/downloadFile.mustache +0 -12
  120. package/docs/webapi/dragAndDrop.mustache +0 -9
  121. package/docs/webapi/dragSlider.mustache +0 -11
  122. package/docs/webapi/executeAsyncScript.mustache +0 -24
  123. package/docs/webapi/executeScript.mustache +0 -26
  124. package/docs/webapi/fillField.mustache +0 -16
  125. package/docs/webapi/focus.mustache +0 -13
  126. package/docs/webapi/forceClick.mustache +0 -28
  127. package/docs/webapi/forceRightClick.mustache +0 -18
  128. package/docs/webapi/grabAllWindowHandles.mustache +0 -7
  129. package/docs/webapi/grabAttributeFrom.mustache +0 -10
  130. package/docs/webapi/grabAttributeFromAll.mustache +0 -9
  131. package/docs/webapi/grabBrowserLogs.mustache +0 -9
  132. package/docs/webapi/grabCookie.mustache +0 -11
  133. package/docs/webapi/grabCssPropertyFrom.mustache +0 -11
  134. package/docs/webapi/grabCssPropertyFromAll.mustache +0 -10
  135. package/docs/webapi/grabCurrentUrl.mustache +0 -9
  136. package/docs/webapi/grabCurrentWindowHandle.mustache +0 -6
  137. package/docs/webapi/grabDataFromPerformanceTiming.mustache +0 -20
  138. package/docs/webapi/grabElementBoundingRect.mustache +0 -20
  139. package/docs/webapi/grabGeoLocation.mustache +0 -8
  140. package/docs/webapi/grabHTMLFrom.mustache +0 -10
  141. package/docs/webapi/grabHTMLFromAll.mustache +0 -9
  142. package/docs/webapi/grabNumberOfOpenTabs.mustache +0 -8
  143. package/docs/webapi/grabNumberOfVisibleElements.mustache +0 -9
  144. package/docs/webapi/grabPageScrollPosition.mustache +0 -8
  145. package/docs/webapi/grabPopupText.mustache +0 -5
  146. package/docs/webapi/grabSource.mustache +0 -8
  147. package/docs/webapi/grabTextFrom.mustache +0 -10
  148. package/docs/webapi/grabTextFromAll.mustache +0 -9
  149. package/docs/webapi/grabTitle.mustache +0 -8
  150. package/docs/webapi/grabValueFrom.mustache +0 -9
  151. package/docs/webapi/grabValueFromAll.mustache +0 -8
  152. package/docs/webapi/grabWebElement.mustache +0 -9
  153. package/docs/webapi/grabWebElements.mustache +0 -9
  154. package/docs/webapi/moveCursorTo.mustache +0 -12
  155. package/docs/webapi/openNewTab.mustache +0 -7
  156. package/docs/webapi/pressKey.mustache +0 -12
  157. package/docs/webapi/pressKeyDown.mustache +0 -12
  158. package/docs/webapi/pressKeyUp.mustache +0 -12
  159. package/docs/webapi/pressKeyWithKeyNormalization.mustache +0 -60
  160. package/docs/webapi/refreshPage.mustache +0 -6
  161. package/docs/webapi/resizeWindow.mustache +0 -6
  162. package/docs/webapi/rightClick.mustache +0 -14
  163. package/docs/webapi/saveElementScreenshot.mustache +0 -10
  164. package/docs/webapi/saveScreenshot.mustache +0 -12
  165. package/docs/webapi/say.mustache +0 -10
  166. package/docs/webapi/scrollIntoView.mustache +0 -11
  167. package/docs/webapi/scrollPageToBottom.mustache +0 -6
  168. package/docs/webapi/scrollPageToTop.mustache +0 -6
  169. package/docs/webapi/scrollTo.mustache +0 -12
  170. package/docs/webapi/see.mustache +0 -11
  171. package/docs/webapi/seeAttributesOnElements.mustache +0 -9
  172. package/docs/webapi/seeCheckboxIsChecked.mustache +0 -10
  173. package/docs/webapi/seeCookie.mustache +0 -8
  174. package/docs/webapi/seeCssPropertiesOnElements.mustache +0 -9
  175. package/docs/webapi/seeCurrentUrlEquals.mustache +0 -11
  176. package/docs/webapi/seeElement.mustache +0 -8
  177. package/docs/webapi/seeElementInDOM.mustache +0 -8
  178. package/docs/webapi/seeInCurrentUrl.mustache +0 -8
  179. package/docs/webapi/seeInField.mustache +0 -12
  180. package/docs/webapi/seeInPopup.mustache +0 -8
  181. package/docs/webapi/seeInSource.mustache +0 -7
  182. package/docs/webapi/seeInTitle.mustache +0 -8
  183. package/docs/webapi/seeNumberOfElements.mustache +0 -11
  184. package/docs/webapi/seeNumberOfVisibleElements.mustache +0 -10
  185. package/docs/webapi/seeTextEquals.mustache +0 -9
  186. package/docs/webapi/seeTitleEquals.mustache +0 -8
  187. package/docs/webapi/selectOption.mustache +0 -21
  188. package/docs/webapi/setCookie.mustache +0 -16
  189. package/docs/webapi/setGeoLocation.mustache +0 -12
  190. package/docs/webapi/switchTo.mustache +0 -9
  191. package/docs/webapi/switchToNextTab.mustache +0 -10
  192. package/docs/webapi/switchToPreviousTab.mustache +0 -10
  193. package/docs/webapi/type.mustache +0 -21
  194. package/docs/webapi/uncheckOption.mustache +0 -13
  195. package/docs/webapi/wait.mustache +0 -8
  196. package/docs/webapi/waitForClickable.mustache +0 -11
  197. package/docs/webapi/waitForDetached.mustache +0 -10
  198. package/docs/webapi/waitForElement.mustache +0 -11
  199. package/docs/webapi/waitForEnabled.mustache +0 -6
  200. package/docs/webapi/waitForFunction.mustache +0 -17
  201. package/docs/webapi/waitForInvisible.mustache +0 -10
  202. package/docs/webapi/waitForText.mustache +0 -13
  203. package/docs/webapi/waitForValue.mustache +0 -10
  204. package/docs/webapi/waitForVisible.mustache +0 -10
  205. package/docs/webapi/waitInUrl.mustache +0 -9
  206. package/docs/webapi/waitNumberOfVisibleElements.mustache +0 -10
  207. package/docs/webapi/waitToHide.mustache +0 -10
  208. package/docs/webapi/waitUrlEquals.mustache +0 -10
  209. package/docs/webdriver.md +0 -655
  210. package/docs/wiki/Books-&-Posts.md +0 -27
  211. package/docs/wiki/Community-Helpers-&-Plugins.md +0 -53
  212. package/docs/wiki/Converting-Playwright-to-Istanbul-Coverage.md +0 -61
  213. package/docs/wiki/Examples.md +0 -145
  214. package/docs/wiki/Google-Summer-of-Code-(GSoC)-2020.md +0 -68
  215. package/docs/wiki/Home.md +0 -16
  216. package/docs/wiki/Migration-to-Appium-v2---CodeceptJS.md +0 -83
  217. package/docs/wiki/Release-Process.md +0 -24
  218. package/docs/wiki/Roadmap.md +0 -23
  219. package/docs/wiki/Tests.md +0 -1393
  220. package/docs/wiki/Upgrading-to-CodeceptJS-3.md +0 -153
  221. package/docs/wiki/Videos.md +0 -19
package/README.md CHANGED
@@ -304,8 +304,8 @@ Thanks all to those who are and will have contributing to this awesome project!
304
304
  <a href="https://github.com/APshenkin"><img src="https://avatars.githubusercontent.com/u/14344430?v=4" title="APshenkin" width="80" height="80"></a>
305
305
  <a href="https://github.com/fabioel"><img src="https://avatars.githubusercontent.com/u/9824235?v=4" title="fabioel" width="80" height="80"></a>
306
306
  <a href="https://github.com/pablopaul"><img src="https://avatars.githubusercontent.com/u/635526?v=4" title="pablopaul" width="80" height="80"></a>
307
- <a href="https://github.com/Georgegriff"><img src="https://avatars.githubusercontent.com/u/9056958?v=4" title="Georgegriff" width="80" height="80"></a>
308
307
  <a href="https://github.com/mirao"><img src="https://avatars.githubusercontent.com/u/12584138?v=4" title="mirao" width="80" height="80"></a>
308
+ <a href="https://github.com/Georgegriff"><img src="https://avatars.githubusercontent.com/u/9056958?v=4" title="Georgegriff" width="80" height="80"></a>
309
309
  <a href="https://github.com/KMKoushik"><img src="https://avatars.githubusercontent.com/u/24666922?v=4" title="KMKoushik" width="80" height="80"></a>
310
310
  <a href="https://github.com/nikocanvacom"><img src="https://avatars.githubusercontent.com/u/83254493?v=4" title="nikocanvacom" width="80" height="80"></a>
311
311
  <a href="https://github.com/elukoyanov"><img src="https://avatars.githubusercontent.com/u/11647141?v=4" title="elukoyanov" width="80" height="80"></a>
@@ -321,9 +321,9 @@ Thanks all to those who are and will have contributing to this awesome project!
321
321
  <a href="https://github.com/ngraf"><img src="https://avatars.githubusercontent.com/u/7094389?v=4" title="ngraf" width="80" height="80"></a>
322
322
  <a href="https://github.com/maojunxyz"><img src="https://avatars.githubusercontent.com/u/28778042?v=4" title="maojunxyz" width="80" height="80"></a>
323
323
  <a href="https://github.com/abhimanyupandian"><img src="https://avatars.githubusercontent.com/u/36107381?v=4" title="abhimanyupandian" width="80" height="80"></a>
324
- <a href="https://github.com/hatufacci"><img src="https://avatars.githubusercontent.com/u/4963181?v=4" title="hatufacci" width="80" height="80"></a>
325
324
  <a href="https://github.com/martomo"><img src="https://avatars.githubusercontent.com/u/1850135?v=4" title="martomo" width="80" height="80"></a>
326
- <a href="https://github.com/denis-sokolov"><img src="https://avatars.githubusercontent.com/u/113721?v=4" title="denis-sokolov" width="80" height="80"></a>
325
+ <a href="https://github.com/hatufacci"><img src="https://avatars.githubusercontent.com/u/4963181?v=4" title="hatufacci" width="80" height="80"></a>
326
+ <a href="https://github.com/johnyb"><img src="https://avatars.githubusercontent.com/u/86358?v=4" title="johnyb" width="80" height="80"></a>
327
327
 
328
328
  [//]: contributor-faces
329
329
 
@@ -28,7 +28,9 @@ let processesDone;
28
28
 
29
29
  module.exports = async function (selectedRuns, options) {
30
30
  // registering options globally to use in config
31
- process.env.profile = options.profile;
31
+ if (options.profile) {
32
+ process.env.profile = options.profile;
33
+ }
32
34
  const configFile = options.config;
33
35
 
34
36
  const testRoot = getTestRoot(configFile);
@@ -11,6 +11,7 @@ module.exports = async function (workerCount, selectedRuns, options) {
11
11
  const passedTestArr = [];
12
12
  const failedTestArr = [];
13
13
  const skippedTestArr = [];
14
+ const stepArr = [];
14
15
 
15
16
  const { config: testConfig, override = '' } = options;
16
17
  const overrideConfigs = tryOrDefault(() => JSON.parse(override), {});
@@ -36,6 +37,14 @@ module.exports = async function (workerCount, selectedRuns, options) {
36
37
  suiteArr.push(suite);
37
38
  });
38
39
 
40
+ workers.on(event.step.passed, (step) => {
41
+ stepArr.push(step);
42
+ });
43
+
44
+ workers.on(event.step.failed, (step) => {
45
+ stepArr.push(step);
46
+ });
47
+
39
48
  workers.on(event.test.failed, (test) => {
40
49
  failedTestArr.push(test);
41
50
  output.test.failed(test);
@@ -48,11 +57,33 @@ module.exports = async function (workerCount, selectedRuns, options) {
48
57
 
49
58
  workers.on(event.test.skipped, (test) => {
50
59
  skippedTestArr.push(test);
51
- output.test.passed(test);
60
+ output.test.skipped(test);
52
61
  });
53
62
 
54
63
  workers.on(event.all.result, () => {
55
64
  // expose test stats after all workers finished their execution
65
+ function addStepsToTest(test, stepArr) {
66
+ stepArr.test.steps.forEach(step => {
67
+ if (test.steps.length === 0) {
68
+ test.steps.push(step);
69
+ }
70
+ });
71
+ }
72
+
73
+ stepArr.forEach(step => {
74
+ passedTestArr.forEach(test => {
75
+ if (step.test.title === test.title) {
76
+ addStepsToTest(test, step);
77
+ }
78
+ });
79
+
80
+ failedTestArr.forEach(test => {
81
+ if (step.test.title === test.title) {
82
+ addStepsToTest(test, step);
83
+ }
84
+ });
85
+ });
86
+
56
87
  event.dispatcher.emit(event.workers.result, {
57
88
  suites: suiteArr,
58
89
  tests: {
@@ -132,7 +132,7 @@ function initializeListeners() {
132
132
  duration: test.duration || 0,
133
133
  err,
134
134
  parent,
135
- steps: test.steps ? simplifyStepsInTestObject(test.steps, err) : [],
135
+ steps: test.steps && test.steps.length > 0 ? simplifyStepsInTestObject(test.steps, err) : [],
136
136
  };
137
137
  }
138
138
 
@@ -161,7 +161,7 @@ function initializeListeners() {
161
161
  actor: step.actor,
162
162
  name: step.name,
163
163
  status: step.status,
164
- agrs: _args,
164
+ args: _args,
165
165
  startedAt: step.startedAt,
166
166
  startTime: step.startTime,
167
167
  endTime: step.endTime,
@@ -517,8 +517,9 @@ class Playwright extends Helper {
517
517
  if (this.options.userAgent) contextOptions.userAgent = this.options.userAgent;
518
518
  if (this.options.locale) contextOptions.locale = this.options.locale;
519
519
  if (this.options.colorScheme) contextOptions.colorScheme = this.options.colorScheme;
520
+ this.contextOptions = contextOptions;
520
521
  if (!this.browserContext || !restartsSession()) {
521
- this.browserContext = await this.browser.newContext(contextOptions); // Adding the HTTPSError ignore in the context so that we can ignore those errors
522
+ this.browserContext = await this.browser.newContext(this.contextOptions); // Adding the HTTPSError ignore in the context so that we can ignore those errors
522
523
  }
523
524
  }
524
525
 
@@ -606,7 +607,7 @@ class Playwright extends Helper {
606
607
  page = await browser.firstWindow();
607
608
  } else {
608
609
  try {
609
- browserContext = await this.browser.newContext(Object.assign(this.options, config));
610
+ browserContext = await this.browser.newContext(Object.assign(this.contextOptions, config));
610
611
  page = await browserContext.newPage();
611
612
  } catch (e) {
612
613
  if (this.playwrightOptions.userDataDir) {
@@ -2583,6 +2584,24 @@ class Playwright extends Helper {
2583
2584
  });
2584
2585
  }
2585
2586
 
2587
+ /**
2588
+ * {{> waitForNumberOfTabs }}
2589
+ */
2590
+ async waitForNumberOfTabs(expectedTabs, sec) {
2591
+ const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
2592
+ let currentTabs;
2593
+ let count = 0;
2594
+
2595
+ do {
2596
+ currentTabs = await this.grabNumberOfOpenTabs();
2597
+ await this.wait(1);
2598
+ count += 1000;
2599
+ if (currentTabs >= expectedTabs) return;
2600
+ } while (count <= waitTimeout);
2601
+
2602
+ throw new Error(`Expected ${expectedTabs} tabs are not met after ${waitTimeout / 1000} sec.`);
2603
+ }
2604
+
2586
2605
  async _getContext() {
2587
2606
  if (this.context && this.context.constructor.name === 'FrameLocator') {
2588
2607
  return this.context;
@@ -2151,6 +2151,24 @@ class Puppeteer extends Helper {
2151
2151
  });
2152
2152
  }
2153
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
+
2154
2172
  async _getContext() {
2155
2173
  if (this.context && this.context.constructor.name === 'Frame') {
2156
2174
  return this.context;
@@ -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
 
@@ -1120,7 +1186,7 @@ class WebDriver extends Helper {
1120
1186
  const el = usingFirstElement(res);
1121
1187
 
1122
1188
  // Remote Upload (when running Selenium Server)
1123
- if (this.options.remoteFileUpload) {
1189
+ if (this.options.remoteFileUpload && !this.options.automationProtocol) {
1124
1190
  try {
1125
1191
  this.debugSection('File', 'Uploading file to remote server');
1126
1192
  file = await this.browser.uploadFile(file);
@@ -1498,35 +1564,33 @@ class WebDriver extends Helper {
1498
1564
  async seeCssPropertiesOnElements(locator, cssProperties) {
1499
1565
  const res = await this._locate(locator);
1500
1566
  assertElementExists(res, locator);
1567
+
1568
+ const cssPropertiesCamelCase = convertCssPropertiesToCamelCase(cssProperties);
1501
1569
  const elemAmount = res.length;
1570
+ let props = [];
1502
1571
 
1503
- let props = await forEachAsync(res, async (el) => {
1504
- return forEachAsync(Object.keys(cssProperties), async (prop) => {
1505
- const propValue = await this.browser.getElementCSSValue(getElementId(el), prop);
1506
- if (isColorProperty(prop) && propValue && propValue.value) {
1507
- 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);
1508
1579
  }
1509
- return propValue;
1510
- });
1511
- });
1512
-
1513
- const cssPropertiesCamelCase = convertCssPropertiesToCamelCase(cssProperties);
1580
+ }
1581
+ }
1514
1582
 
1515
1583
  const values = Object.keys(cssPropertiesCamelCase).map(key => cssPropertiesCamelCase[key]);
1516
1584
  if (!Array.isArray(props)) props = [props];
1517
1585
  let chunked = chunkArray(props, values.length);
1518
1586
  chunked = chunked.filter((val) => {
1519
1587
  for (let i = 0; i < val.length; ++i) {
1520
- const _acutal = Number.isNaN(val[i]) || (typeof values[i]) === 'string' ? val[i] : Number.parseInt(val[i], 10);
1521
- const _expected = Number.isNaN(values[i]) || (typeof values[i]) === 'string' ? values[i] : Number.parseInt(values[i], 10);
1522
- if (_acutal !== _expected) return false;
1588
+ // eslint-disable-next-line eqeqeq
1589
+ if (val[i] != values[i]) return false;
1523
1590
  }
1524
1591
  return true;
1525
1592
  });
1526
- return assert.ok(
1527
- chunked.length === elemAmount,
1528
- `expected all elements (${(new Locator(locator))}) to have CSS property ${JSON.stringify(cssProperties)}`,
1529
- );
1593
+ return equals(`all elements (${(new Locator(locator))}) to have CSS property ${JSON.stringify(cssProperties)}`).assert(chunked.length, elemAmount);
1530
1594
  }
1531
1595
 
1532
1596
  /**
@@ -1546,9 +1610,9 @@ class WebDriver extends Helper {
1546
1610
  let chunked = chunkArray(attrs, values.length);
1547
1611
  chunked = chunked.filter((val) => {
1548
1612
  for (let i = 0; i < val.length; ++i) {
1549
- 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);
1550
1614
  const _expected = Number.isNaN(values[i]) || (typeof values[i]) === 'string' ? values[i] : Number.parseInt(values[i], 10);
1551
- if (_acutal !== _expected) return false;
1615
+ if (_actual !== _expected) return false;
1552
1616
  }
1553
1617
  return true;
1554
1618
  });
@@ -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
  /**
@@ -1925,7 +1993,7 @@ class WebDriver extends Helper {
1925
1993
  * {{> resizeWindow }}
1926
1994
  */
1927
1995
  async resizeWindow(width, height) {
1928
- return this._resizeBrowserWindow(this.browser, width, height);
1996
+ return this.browser.setWindowSize(width, height);
1929
1997
  }
1930
1998
 
1931
1999
  async _resizeBrowserWindow(browser, width, height) {
@@ -2306,12 +2374,33 @@ class WebDriver extends Helper {
2306
2374
  return this.browser.waitUntil(async () => this.browser.execute(fn, ...args), { timeout: aSec * 1000, timeoutMsg: '' });
2307
2375
  }
2308
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
+
2309
2395
  /**
2310
2396
  * {{> switchTo }}
2311
2397
  */
2312
2398
  async switchTo(locator) {
2313
2399
  this.browser.isInsideFrame = true;
2314
2400
  if (Number.isInteger(locator)) {
2401
+ if (this.options.automationProtocol) {
2402
+ return this.browser.switchToFrame(locator + 1);
2403
+ }
2315
2404
  return this.browser.switchToFrame(locator);
2316
2405
  }
2317
2406
  if (!locator) {
@@ -2448,21 +2537,41 @@ class WebDriver extends Helper {
2448
2537
  }
2449
2538
 
2450
2539
  /**
2540
+ * This method is **deprecated**.
2541
+ *
2542
+ *
2451
2543
  * {{> setGeoLocation }}
2452
2544
  */
2453
- async setGeoLocation(latitude, longitude, altitude = null) {
2454
- if (altitude) {
2455
- 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;
2456
2551
  }
2457
- 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
+ });
2458
2558
  }
2459
2559
 
2460
2560
  /**
2561
+ * This method is **deprecated**.
2562
+ *
2461
2563
  * {{> grabGeoLocation }}
2462
2564
  *
2463
2565
  */
2464
2566
  async grabGeoLocation() {
2465
- 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;
2466
2575
  }
2467
2576
 
2468
2577
  /**
@@ -2658,7 +2767,7 @@ async function proceedSeeField(assertType, field, value) {
2658
2767
  }
2659
2768
  };
2660
2769
 
2661
- const proceedSingle = el => this.browser.getElementAttribute(getElementId(el), 'value').then((res) => {
2770
+ const proceedSingle = el => el.getValue().then((res) => {
2662
2771
  if (res === null) {
2663
2772
  throw new Error(`Element ${el.selector} has no value attribute`);
2664
2773
  }
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
 
@@ -1,6 +1,7 @@
1
1
  const event = require('../event');
2
2
  const recorder = require('../recorder');
3
3
  const container = require('../container');
4
+ const { log } = require('../output');
4
5
 
5
6
  const defaultConfig = {
6
7
  retries: 3,
@@ -99,7 +100,10 @@ module.exports = (config) => {
99
100
  config.when = when;
100
101
 
101
102
  event.dispatcher.on(event.step.started, (step) => {
102
- if (process.env.TRY_TO) return;
103
+ if (process.env.TRY_TO === 'true') {
104
+ log('Info: RetryFailedStep plugin is disabled inside tryTo block');
105
+ return;
106
+ }
103
107
 
104
108
  // if a step is ignored - return
105
109
  for (const ignored of config.ignoredSteps) {
@@ -89,9 +89,9 @@ module.exports = function (config) {
89
89
  let err = null;
90
90
 
91
91
  return new Promise((done) => {
92
- const tryBlock = () => {
92
+ const tryBlock = async () => {
93
93
  recorder.session.start(`retryTo ${tries}`);
94
- callback(tries);
94
+ await callback(tries);
95
95
  recorder.add(() => {
96
96
  recorder.session.restore(`retryTo ${tries}`);
97
97
  done(null);