codeceptjs 2.3.4 → 2.4.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 (269) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +28 -6
  3. package/bin/codecept.js +42 -0
  4. package/docs/advanced.md +45 -1
  5. package/docs/angular.md +3 -3
  6. package/docs/basics.md +162 -118
  7. package/docs/bdd.md +30 -5
  8. package/docs/best.md +8 -6
  9. package/docs/books.md +6 -1
  10. package/docs/build/Appium.js +95 -85
  11. package/docs/build/FileSystem.js +48 -3
  12. package/docs/build/GraphQL.js +3 -2
  13. package/docs/build/GraphQLDataFactory.js +1 -0
  14. package/docs/build/Mochawesome.js +3 -2
  15. package/docs/build/MockRequest.js +23 -5
  16. package/docs/build/Nightmare.js +87 -128
  17. package/docs/build/Protractor.js +107 -155
  18. package/docs/build/Puppeteer.js +190 -174
  19. package/docs/build/REST.js +13 -9
  20. package/docs/build/SeleniumWebdriver.js +0 -17
  21. package/docs/build/TestCafe.js +164 -158
  22. package/docs/build/WebDriver.js +236 -211
  23. package/docs/build/WebDriverIO.js +218 -187
  24. package/docs/changelog.md +57 -1
  25. package/docs/commands.md +41 -2
  26. package/docs/community-helpers.md +12 -1
  27. package/docs/configuration.md +5 -2
  28. package/docs/continuous-integration.md +22 -0
  29. package/docs/{helpers.md → custom-helpers.md} +16 -10
  30. package/docs/data.md +7 -6
  31. package/docs/detox.md +6 -6
  32. package/docs/email.md +4 -2
  33. package/docs/examples.md +22 -3
  34. package/docs/helpers/ApiDataFactory.md +15 -13
  35. package/docs/helpers/Appium.md +1011 -468
  36. package/docs/helpers/Detox.md +33 -26
  37. package/docs/helpers/FileSystem.md +43 -13
  38. package/docs/helpers/GraphQL.md +17 -15
  39. package/docs/helpers/GraphQLDataFactory.md +15 -13
  40. package/docs/helpers/Mochawesome.md +3 -1
  41. package/docs/helpers/MockRequest.md +37 -19
  42. package/docs/helpers/Nightmare.md +129 -240
  43. package/docs/helpers/Polly.md +1 -1
  44. package/docs/helpers/Protractor.md +157 -298
  45. package/docs/helpers/Puppeteer.md +216 -335
  46. package/docs/helpers/REST.md +29 -24
  47. package/docs/helpers/TestCafe.md +137 -235
  48. package/docs/helpers/WebDriver.md +250 -347
  49. package/docs/hooks.md +14 -10
  50. package/docs/index.md +112 -0
  51. package/docs/installation.md +3 -1
  52. package/docs/locators.md +19 -8
  53. package/docs/mobile-react-native-locators.md +2 -2
  54. package/docs/mobile.md +5 -3
  55. package/docs/nightmare.md +2 -1
  56. package/docs/pageobjects.md +4 -2
  57. package/docs/parallel.md +4 -2
  58. package/docs/plugins.md +41 -15
  59. package/docs/puppeteer.md +8 -6
  60. package/docs/quickstart.md +130 -0
  61. package/docs/react.md +4 -2
  62. package/docs/reports.md +6 -4
  63. package/docs/testcafe.md +10 -8
  64. package/docs/translation.md +4 -2
  65. package/docs/ui.md +56 -0
  66. package/docs/videos.md +11 -2
  67. package/docs/visual.md +7 -5
  68. package/docs/vue.md +121 -0
  69. package/docs/webapi/appendField.mustache +1 -1
  70. package/docs/webapi/attachFile.mustache +1 -1
  71. package/docs/webapi/checkOption.mustache +2 -2
  72. package/docs/webapi/clearCookie.mustache +1 -1
  73. package/docs/webapi/click.mustache +2 -2
  74. package/docs/webapi/clickLink.mustache +2 -2
  75. package/docs/webapi/dontSee.mustache +1 -2
  76. package/docs/webapi/dontSeeCheckboxIsChecked.mustache +1 -1
  77. package/docs/webapi/dontSeeElement.mustache +1 -1
  78. package/docs/webapi/dontSeeElementInDOM.mustache +1 -1
  79. package/docs/webapi/dontSeeInField.mustache +1 -1
  80. package/docs/webapi/doubleClick.mustache +2 -2
  81. package/docs/webapi/downloadFile.mustache +1 -1
  82. package/docs/webapi/dragSlider.mustache +1 -1
  83. package/docs/webapi/executeAsyncScript.mustache +2 -1
  84. package/docs/webapi/executeScript.mustache +2 -1
  85. package/docs/webapi/fillField.mustache +1 -1
  86. package/docs/webapi/grabAttributeFrom.mustache +1 -1
  87. package/docs/webapi/grabBrowserLogs.mustache +1 -1
  88. package/docs/webapi/grabCookie.mustache +2 -2
  89. package/docs/webapi/grabCssPropertyFrom.mustache +1 -1
  90. package/docs/webapi/grabHTMLFrom.mustache +1 -1
  91. package/docs/webapi/grabNumberOfVisibleElements.mustache +1 -1
  92. package/docs/webapi/grabPageScrollPosition.mustache +1 -1
  93. package/docs/webapi/grabTextFrom.mustache +2 -2
  94. package/docs/webapi/grabValueFrom.mustache +1 -1
  95. package/docs/webapi/moveCursorTo.mustache +3 -3
  96. package/docs/webapi/pressKey.mustache +1 -1
  97. package/docs/webapi/pressKeyWithKeyNormalization.mustache +1 -1
  98. package/docs/webapi/rightClick.mustache +2 -2
  99. package/docs/webapi/saveScreenshot.mustache +1 -1
  100. package/docs/webapi/scrollIntoView.mustache +10 -0
  101. package/docs/webapi/scrollTo.mustache +3 -3
  102. package/docs/webapi/see.mustache +1 -1
  103. package/docs/webapi/seeAttributesOnElements.mustache +1 -1
  104. package/docs/webapi/seeCheckboxIsChecked.mustache +1 -1
  105. package/docs/webapi/seeCssPropertiesOnElements.mustache +1 -1
  106. package/docs/webapi/seeElement.mustache +1 -1
  107. package/docs/webapi/seeElementInDOM.mustache +1 -1
  108. package/docs/webapi/seeInField.mustache +1 -1
  109. package/docs/webapi/seeNumberOfElements.mustache +1 -1
  110. package/docs/webapi/seeNumberOfVisibleElements.mustache +1 -1
  111. package/docs/webapi/seeTextEquals.mustache +8 -0
  112. package/docs/webapi/selectOption.mustache +2 -2
  113. package/docs/webapi/switchTo.mustache +1 -1
  114. package/docs/webapi/uncheckOption.mustache +2 -2
  115. package/docs/webapi/waitForClickable.mustache +10 -0
  116. package/docs/webapi/waitForDetached.mustache +2 -2
  117. package/docs/webapi/waitForElement.mustache +2 -2
  118. package/docs/webapi/waitForEnabled.mustache +2 -2
  119. package/docs/webapi/waitForFunction.mustache +2 -2
  120. package/docs/webapi/waitForInvisible.mustache +2 -2
  121. package/docs/webapi/waitForText.mustache +2 -2
  122. package/docs/webapi/waitForValue.mustache +1 -1
  123. package/docs/webapi/waitForVisible.mustache +2 -2
  124. package/docs/webapi/waitInUrl.mustache +1 -1
  125. package/docs/webapi/waitNumberOfVisibleElements.mustache +2 -2
  126. package/docs/webapi/waitToHide.mustache +2 -2
  127. package/docs/webapi/waitUntil.mustache +3 -2
  128. package/docs/webapi/waitUrlEquals.mustache +1 -1
  129. package/docs/webdriver.md +20 -18
  130. package/docs/wiki/.git/FETCH_HEAD +1 -0
  131. package/docs/wiki/.git/HEAD +1 -0
  132. package/docs/wiki/.git/ORIG_HEAD +1 -0
  133. package/docs/wiki/.git/config +11 -0
  134. package/docs/wiki/.git/description +1 -0
  135. package/docs/wiki/.git/hooks/applypatch-msg.sample +15 -0
  136. package/docs/wiki/.git/hooks/commit-msg.sample +24 -0
  137. package/docs/wiki/.git/hooks/fsmonitor-watchman.sample +114 -0
  138. package/docs/wiki/.git/hooks/post-update.sample +8 -0
  139. package/docs/wiki/.git/hooks/pre-applypatch.sample +14 -0
  140. package/docs/wiki/.git/hooks/pre-commit.sample +49 -0
  141. package/docs/wiki/.git/hooks/pre-push.sample +53 -0
  142. package/docs/wiki/.git/hooks/pre-rebase.sample +169 -0
  143. package/docs/wiki/.git/hooks/pre-receive.sample +24 -0
  144. package/docs/wiki/.git/hooks/prepare-commit-msg.sample +42 -0
  145. package/docs/wiki/.git/hooks/update.sample +128 -0
  146. package/docs/wiki/.git/index +0 -0
  147. package/docs/wiki/.git/info/exclude +6 -0
  148. package/docs/wiki/.git/logs/HEAD +4 -0
  149. package/docs/wiki/.git/logs/refs/heads/master +4 -0
  150. package/docs/wiki/.git/logs/refs/remotes/origin/HEAD +1 -0
  151. package/docs/wiki/.git/logs/refs/remotes/origin/master +3 -0
  152. package/docs/wiki/.git/objects/00/d216b0774d15db2d0a2a0d4ce249b5251acc55 +3 -0
  153. package/docs/wiki/.git/objects/09/01d87c5241905fdfe3493cfe8f04df4a2685ea +0 -0
  154. package/docs/wiki/.git/objects/0d/bdd0c20c4deb6a8cc81dbbf32ecf8c09238983 +2 -0
  155. package/docs/wiki/.git/objects/1a/c29e4fa82422c52392f22f0f2b8d1a759535bf +0 -0
  156. package/docs/wiki/.git/objects/27/12f92898d3e8f68e229b6cda76570d6c66d781 +0 -0
  157. package/docs/wiki/.git/objects/2d/dbe22c257166b648928eeb9460ecfb71ba702d +0 -0
  158. package/docs/wiki/.git/objects/2f/c942ec3773efd2678d9ff98035c61fcded81a1 +0 -0
  159. package/docs/wiki/.git/objects/40/a2856342c67796b48911a256b764fb06888b94 +5 -0
  160. package/docs/wiki/.git/objects/47/53181844fc4dc563cf3aa5e80462243cb58d38 +0 -0
  161. package/docs/wiki/.git/objects/4e/24a95fb2e4f8ffef51f19b694451a205c06f10 +3 -0
  162. package/docs/wiki/.git/objects/73/31ebd96f3c7e08a9f63f05a25f939afa0d4de1 +0 -0
  163. package/docs/wiki/.git/objects/86/19cbb2289caa502e33fccf0ed14eecf6ba2ba0 +0 -0
  164. package/docs/wiki/.git/objects/a4/72f797d9d74b87c9f71a2b1539d75bb07d1e35 +0 -0
  165. package/docs/wiki/.git/objects/c9/9f3e4bd227d6b050b2e416f9876df49583dbf6 +0 -0
  166. package/docs/wiki/.git/objects/ca/e609b4ef3e0ef85fcbe0d68d1a58246584b915 +0 -0
  167. package/docs/wiki/.git/objects/d5/8386ca72f6d550548f3d71d74e3ac73d5ad488 +0 -0
  168. package/docs/wiki/.git/objects/d9/c6874a6de524bdafeb563a20d847f4fdd59a86 +0 -0
  169. package/docs/wiki/.git/objects/f1/c944675bb38b40ae553b0be36c14674c79af54 +0 -0
  170. package/docs/wiki/.git/objects/pack/pack-28da0fc7e6c08d4c5350717bfbb7b1c53e8198ad.idx +0 -0
  171. package/docs/wiki/.git/objects/pack/pack-28da0fc7e6c08d4c5350717bfbb7b1c53e8198ad.pack +0 -0
  172. package/docs/wiki/.git/packed-refs +2 -0
  173. package/docs/wiki/.git/refs/heads/master +1 -0
  174. package/docs/wiki/.git/refs/remotes/origin/HEAD +1 -0
  175. package/docs/wiki/.git/refs/remotes/origin/master +1 -0
  176. package/docs/wiki/Books-&-Posts.md +27 -0
  177. package/docs/wiki/Community-Helpers.md +41 -0
  178. package/docs/wiki/Examples.md +138 -0
  179. package/docs/wiki/Home.md +11 -0
  180. package/docs/wiki/Release-process.md +25 -0
  181. package/docs/wiki/Roadmap.md +23 -0
  182. package/docs/wiki/Videos.md +19 -0
  183. package/lib/actor.js +18 -1
  184. package/lib/assert/error.js +3 -3
  185. package/lib/codecept.js +9 -6
  186. package/lib/command/configMigrate.js +7 -6
  187. package/lib/command/definitions.js +98 -350
  188. package/lib/command/generate.js +22 -17
  189. package/lib/command/gherkin/init.js +2 -1
  190. package/lib/command/gherkin/snippets.js +6 -6
  191. package/lib/command/gherkin/steps.js +0 -1
  192. package/lib/command/info.js +40 -0
  193. package/lib/command/init.js +54 -41
  194. package/lib/command/run-multiple.js +5 -4
  195. package/lib/command/run-rerun.js +39 -0
  196. package/lib/command/run-workers.js +4 -6
  197. package/lib/command/run.js +8 -18
  198. package/lib/command/utils.js +23 -2
  199. package/lib/command/workers/runTests.js +1 -2
  200. package/lib/config.js +10 -4
  201. package/lib/container.js +31 -6
  202. package/lib/data/dataTableArgument.js +31 -0
  203. package/lib/data/table.js +4 -0
  204. package/lib/event.js +65 -1
  205. package/lib/helper/Appium.js +52 -38
  206. package/lib/helper/FileSystem.js +48 -3
  207. package/lib/helper/GraphQL.js +3 -2
  208. package/lib/helper/GraphQLDataFactory.js +1 -0
  209. package/lib/helper/Mochawesome.js +3 -2
  210. package/lib/helper/MockRequest.js +23 -5
  211. package/lib/helper/Nightmare.js +5 -6
  212. package/lib/helper/Protractor.js +7 -8
  213. package/lib/helper/Puppeteer.js +76 -20
  214. package/lib/helper/REST.js +13 -9
  215. package/lib/helper/SeleniumWebdriver.js +0 -17
  216. package/lib/helper/TestCafe.js +84 -36
  217. package/lib/helper/WebDriver.js +113 -59
  218. package/lib/helper/WebDriverIO.js +43 -59
  219. package/lib/helper/clientscripts/nightmare.js +66 -4
  220. package/lib/helper/scripts/isElementClickable.js +24 -0
  221. package/lib/helper.js +34 -10
  222. package/lib/history.js +1 -1
  223. package/lib/hooks.js +2 -1
  224. package/lib/index.js +19 -0
  225. package/lib/interfaces/bdd.js +4 -0
  226. package/lib/interfaces/featureConfig.js +10 -3
  227. package/lib/interfaces/gherkin.js +6 -2
  228. package/lib/interfaces/scenarioConfig.js +17 -6
  229. package/lib/listener/config.js +1 -1
  230. package/lib/listener/exit.js +6 -0
  231. package/lib/listener/steps.js +0 -1
  232. package/lib/listener/trace.js +0 -1
  233. package/lib/locator.js +67 -2
  234. package/lib/output.js +53 -0
  235. package/lib/parser.js +2 -71
  236. package/lib/pause.js +3 -2
  237. package/lib/plugin/allure.js +41 -22
  238. package/lib/plugin/autoLogin.js +4 -1
  239. package/lib/plugin/pauseOnFail.js +38 -0
  240. package/lib/plugin/puppeteerCoverage.js +8 -7
  241. package/lib/plugin/screenshotOnFail.js +13 -8
  242. package/lib/plugin/stepByStepReport.js +7 -6
  243. package/lib/plugin/wdio.js +2 -1
  244. package/lib/recorder.js +85 -7
  245. package/lib/rerun.js +81 -0
  246. package/lib/secret.js +6 -0
  247. package/lib/session.js +9 -2
  248. package/lib/step.js +37 -2
  249. package/lib/store.js +5 -1
  250. package/lib/ui.js +34 -8
  251. package/lib/utils.js +6 -13
  252. package/lib/within.js +5 -0
  253. package/package.json +49 -29
  254. package/typings/Mocha.d.ts +21 -0
  255. package/typings/Protractor.d.ts +16 -0
  256. package/typings/index.d.ts +169 -0
  257. package/typings/jsdoc.conf.js +34 -0
  258. package/typings/jsdoc.namespace.js +29 -0
  259. package/typings/types.d.ts +9827 -0
  260. package/typings/utils.d.ts +7 -0
  261. package/docs/acceptance.md +0 -409
  262. package/docs/api/codecept.md +0 -75
  263. package/docs/api/config.md +0 -49
  264. package/docs/api/container.md +0 -66
  265. package/docs/api/helper.md +0 -116
  266. package/docs/api/output.md +0 -67
  267. package/docs/api/recorder.md +0 -63
  268. package/docs/helpers/SeleniumWebdriver.md +0 -92
  269. package/docs/helpers/WebDriverIO.md +0 -1671
@@ -1,4 +1,9 @@
1
+ const axios = require('axios');
2
+ const fs = require('fs');
3
+ const fsExtra = require('fs-extra');
1
4
  const requireg = require('requireg');
5
+ const path = require('path');
6
+
2
7
  const Helper = require('../helper');
3
8
  const Locator = require('../locator');
4
9
  const recorder = require('../recorder');
@@ -7,11 +12,13 @@ const { urlEquals } = require('../assert/equal');
7
12
  const { equals } = require('../assert/equal');
8
13
  const { empty } = require('../assert/empty');
9
14
  const { truth } = require('../assert/truth');
15
+ const isElementClickable = require('./scripts/isElementClickable');
10
16
  const {
11
17
  xpathLocator,
12
18
  ucfirst,
13
19
  fileExists,
14
20
  chunkArray,
21
+ toCamelCase,
15
22
  convertCssPropertiesToCamelCase,
16
23
  screenshotOutputFolder,
17
24
  getNormalizedKeyAttributeValue,
@@ -21,18 +28,15 @@ const {
21
28
  isColorProperty,
22
29
  convertColorToRGBA,
23
30
  } = require('../colorUtils');
24
- const path = require('path');
25
31
  const ElementNotFound = require('./errors/ElementNotFound');
26
32
  const RemoteBrowserConnectionRefused = require('./errors/RemoteBrowserConnectionRefused');
27
33
  const Popup = require('./extras/Popup');
28
34
  const Console = require('./extras/Console');
29
35
  const findReact = require('./extras/React');
30
- const axios = require('axios');
31
- const fs = require('fs');
32
- const fsExtra = require('fs-extra');
33
36
 
34
37
  let puppeteer;
35
38
  let perfTiming;
39
+ let isAuthenticated = false;
36
40
  const popupStore = new Popup();
37
41
  const consoleLogStore = new Console();
38
42
 
@@ -50,6 +54,7 @@ const consoleLogStore = new Console();
50
54
  * This helper should be configured in codecept.json or codecept.conf.js
51
55
  *
52
56
  * * `url`: base url of website to be tested
57
+ * * `basicAuth`: (optional) the basic authentication to pass to base url. Example: {username: 'username', password: 'password'}
53
58
  * * `show`: (optional, default: false) - show Google Chrome window for debug.
54
59
  * * `restart`: (optional, default: true) - restart browser between tests.
55
60
  * * `disableScreenshots`: (optional, default: false) - don't save screenshot on failure.
@@ -127,6 +132,22 @@ const consoleLogStore = new Console();
127
132
  * }
128
133
  * ```
129
134
  *
135
+ * #### Example #5: Target URL with provided basic authentication
136
+ *
137
+ * ```js
138
+ * {
139
+ * helpers: {
140
+ * Puppeteer : {
141
+ * url: 'http://localhost',
142
+ * basicAuth: {username: 'username', password: 'password'},
143
+ * show: true
144
+ * }
145
+ * }
146
+ * }
147
+ * ```
148
+ *
149
+ *
150
+ *
130
151
  * Note: When connecting to remote browser `show` and specific `chrome` options (e.g. `headless` or `devtools`) are ignored.
131
152
  *
132
153
  * ## Access From Helpers
@@ -185,7 +206,10 @@ class Puppeteer extends Helper {
185
206
 
186
207
  _setConfig(config) {
187
208
  this.options = this._validateConfig(config);
188
- this.puppeteerOptions = Object.assign({ headless: !this.options.show }, this._getOptions(config));
209
+ this.puppeteerOptions = {
210
+ headless: !this.options.show,
211
+ ...this._getOptions(config),
212
+ };
189
213
  this.isRemoteBrowser = !!this.puppeteerOptions.browserWSEndpoint;
190
214
  popupStore.defaultAction = this.options.defaultPopupAction;
191
215
  }
@@ -247,9 +271,13 @@ class Puppeteer extends Helper {
247
271
  this.debugSection('Session', 'cleaning cookies and localStorage');
248
272
  await this.clearCookie();
249
273
  }
250
- await this.executeScript('localStorage.clear();').catch((err) => {
251
- if (!(err.message.indexOf("Storage is disabled inside 'data:' URLs.") > -1)) throw err;
252
- });
274
+ const currentUrl = await this.grabCurrentUrl();
275
+
276
+ if (currentUrl.startsWith('http')) {
277
+ await this.executeScript('localStorage.clear();').catch((err) => {
278
+ if (!(err.message.indexOf("Storage is disabled inside 'data:' URLs.") > -1)) throw err;
279
+ });
280
+ }
253
281
  await this.closeOtherTabs();
254
282
  return this.browser;
255
283
  }
@@ -401,6 +429,7 @@ class Puppeteer extends Helper {
401
429
  * ```js
402
430
  * await I.grabPopupText();
403
431
  * ```
432
+ * @return {Promise<string | null>}
404
433
  */
405
434
  async grabPopupText() {
406
435
  if (popupStore.popup) {
@@ -513,6 +542,14 @@ class Puppeteer extends Helper {
513
542
  if (!(/^\w+\:\/\//.test(url))) {
514
543
  url = this.options.url + url;
515
544
  }
545
+
546
+ if (this.config.basicAuth && (isAuthenticated !== true)) {
547
+ if (url.includes(this.options.url)) {
548
+ this.page.authenticate(this.config.basicAuth);
549
+ isAuthenticated = true;
550
+ }
551
+ }
552
+
516
553
  await this.page.goto(url, { waitUntil: this.options.waitForNavigation });
517
554
 
518
555
  const performanceTiming = JSON.parse(await this.page.evaluate(() => JSON.stringify(window.performance.timing)));
@@ -1241,11 +1278,7 @@ class Puppeteer extends Helper {
1241
1278
  }
1242
1279
 
1243
1280
  /**
1244
- * Checks that text is equal to provided one.
1245
- *
1246
- * ```js
1247
- * I.seeTextEquals('text', 'h1');
1248
- * ```
1281
+ * {{> seeTextEquals }}
1249
1282
  */
1250
1283
  async seeTextEquals(text, context = null) {
1251
1284
  return proceedSee.call(this, 'assert', text, context, true);
@@ -1274,6 +1307,7 @@ class Puppeteer extends Helper {
1274
1307
  * let logs = await I.grabBrowserLogs();
1275
1308
  * console.log(JSON.stringify(logs))
1276
1309
  * ```
1310
+ * @return {Promise<any[]>}
1277
1311
  */
1278
1312
  async grabBrowserLogs() {
1279
1313
  const logs = consoleLogStore.entries;
@@ -1441,7 +1475,7 @@ class Puppeteer extends Helper {
1441
1475
  async grabHTMLFrom(locator) {
1442
1476
  const els = await this._locate(locator);
1443
1477
  assertElementExists(els, locator);
1444
- const values = await Promise.all(els.map(el => this.page.evaluate(element => element.innerHTML, el)));
1478
+ const values = await Promise.all(els.map(el => el.executionContext().evaluate(element => element.innerHTML, el)));
1445
1479
  if (Array.isArray(values) && values.length === 1) {
1446
1480
  return values[0];
1447
1481
  }
@@ -1455,7 +1489,7 @@ class Puppeteer extends Helper {
1455
1489
  async grabCssPropertyFrom(locator, cssProperty) {
1456
1490
  const els = await this._locate(locator);
1457
1491
  const res = await Promise.all(els.map(el => el.executionContext().evaluate(el => JSON.parse(JSON.stringify(getComputedStyle(el))), el)));
1458
- const cssValues = res.map(props => props[cssProperty]);
1492
+ const cssValues = res.map(props => props[toCamelCase(cssProperty)]);
1459
1493
 
1460
1494
  if (res.length > 0) {
1461
1495
  return cssValues;
@@ -1692,6 +1726,22 @@ class Puppeteer extends Helper {
1692
1726
  });
1693
1727
  }
1694
1728
 
1729
+ /**
1730
+ * {{> waitForClickable }}
1731
+ */
1732
+ async waitForClickable(locator, waitTimeout) {
1733
+ const els = await this._locate(locator);
1734
+ assertElementExists(els, locator);
1735
+
1736
+ return this.waitForFunction(isElementClickable, [els[0]], waitTimeout).catch(async (e) => {
1737
+ if (/failed: timeout/i.test(e.message)) {
1738
+ throw new Error(`element ${new Locator(locator).toString()} still not clickable after ${waitTimeout || this.options.waitForTimeout / 1000} sec`);
1739
+ } else {
1740
+ throw e;
1741
+ }
1742
+ });
1743
+ }
1744
+
1695
1745
  /**
1696
1746
  * {{> waitForElement }}
1697
1747
  * {{ react }}
@@ -1841,10 +1891,11 @@ class Puppeteer extends Helper {
1841
1891
 
1842
1892
  if (locator.isXPath()) {
1843
1893
  waiter = contextObject.waitForFunction((locator, text, $XPath) => {
1894
+ eval($XPath); // eslint-disable-line no-eval
1844
1895
  const el = $XPath(null, locator);
1845
1896
  if (!el.length) return false;
1846
1897
  return el[0].innerText.indexOf(text) > -1;
1847
- }, { timeout: waitTimeout }, locator.value, text, $XPath);
1898
+ }, { timeout: waitTimeout }, locator.value, text, $XPath.toString());
1848
1899
  }
1849
1900
  } else {
1850
1901
  waiter = contextObject.waitForFunction(text => document.body.innerText.indexOf(text) > -1, { timeout: waitTimeout }, text);
@@ -1864,7 +1915,7 @@ class Puppeteer extends Helper {
1864
1915
  * ```
1865
1916
  *
1866
1917
  * @param {string|function} urlOrPredicate
1867
- * @param {number?} [sec=null] seconds to wait
1918
+ * @param {?number} [sec=null] seconds to wait
1868
1919
  */
1869
1920
  async waitForRequest(urlOrPredicate, sec = null) {
1870
1921
  const timeout = sec ? sec * 1000 : this.options.waitForTimeout;
@@ -1880,7 +1931,7 @@ class Puppeteer extends Helper {
1880
1931
  * ```
1881
1932
  *
1882
1933
  * @param {string|function} urlOrPredicate
1883
- * @param {number?} [sec=null] number of seconds to wait
1934
+ * @param {?number} [sec=null] number of seconds to wait
1884
1935
  */
1885
1936
  async waitForResponse(urlOrPredicate, sec = null) {
1886
1937
  const timeout = sec ? sec * 1000 : this.options.waitForTimeout;
@@ -1907,7 +1958,8 @@ class Puppeteer extends Helper {
1907
1958
  throw new Error('Element #invalidIframeSelector was not found by text|CSS|XPath');
1908
1959
  }
1909
1960
  return;
1910
- } else if (!locator) {
1961
+ }
1962
+ if (!locator) {
1911
1963
  this.context = await this.page.mainFrame().$('body');
1912
1964
  return;
1913
1965
  }
@@ -1949,7 +2001,11 @@ class Puppeteer extends Helper {
1949
2001
  * @param {*} opts
1950
2002
  */
1951
2003
  async waitForNavigation(opts = {}) {
1952
- opts = Object.assign({ timeout: this.options.getPageTimeout, waitUntil: this.options.waitForNavigation }, opts);
2004
+ opts = {
2005
+ timeout: this.options.getPageTimeout,
2006
+ waitUntil: this.options.waitForNavigation,
2007
+ ...opts,
2008
+ };
1953
2009
  return this.page.waitForNavigation(opts);
1954
2010
  }
1955
2011
 
@@ -1,6 +1,7 @@
1
- const Helper = require('../helper');
2
- const requireg = require('requireg');
3
1
  const axios = require('axios').default;
2
+ const requireg = require('requireg');
3
+
4
+ const Helper = require('../helper');
4
5
 
5
6
  /**
6
7
  * REST helper allows to send additional requests to the REST API during acceptance tests.
@@ -16,12 +17,15 @@ const axios = require('axios').default;
16
17
  * ## Example
17
18
  *
18
19
  * ```js
19
- * REST: {
20
- * endpoint: 'http://site.com/api',
21
- * onRequest: (request) => {
22
- * request.headers.auth = '123';
23
- * }
24
- * }
20
+ *{
21
+ * helpers: {
22
+ * REST: {
23
+ * endpoint: 'http://site.com/api',
24
+ * onRequest: (request) => {
25
+ * request.headers.auth = '123';
26
+ * }
27
+ * }
28
+ *}
25
29
  * ```
26
30
  *
27
31
  * ## Access From Helpers
@@ -46,7 +50,7 @@ class REST extends Helper {
46
50
  endpoint: '',
47
51
  };
48
52
  this.options = Object.assign(this.options, config);
49
- this.headers = Object.assign({}, this.options.defaultHeaders);
53
+ this.headers = { ...this.options.defaultHeaders };
50
54
  axios.defaults.headers = this.options.defaultHeaders;
51
55
  }
52
56
 
@@ -1,21 +1,4 @@
1
- const requireg = require('requireg');
2
- const Locator = require('../locator');
3
1
  const Helper = require('../helper');
4
- const stringIncludes = require('../assert/include').includes;
5
- const urlEquals = require('../assert/equal').urlEquals;
6
- const equals = require('../assert/equal').equals;
7
- const empty = require('../assert/empty').empty;
8
- const truth = require('../assert/truth').truth;
9
- const xpathLocator = require('../utils').xpathLocator;
10
- const fileExists = require('../utils').fileExists;
11
- const clearString = require('../utils').clearString;
12
- const co = require('co');
13
- const path = require('path');
14
- const recorder = require('../recorder');
15
- const ElementNotFound = require('./errors/ElementNotFound');
16
- const Protractor = require('./Protractor');
17
-
18
- const withinStore = {};
19
2
 
20
3
  /**
21
4
  * SeleniumWebdriver helper is based on the official [Selenium Webdriver JS](https://www.npmjs.com/package/selenium-webdriver)
@@ -2,11 +2,12 @@
2
2
  const fs = require('fs');
3
3
  const assert = require('assert');
4
4
  const path = require('path');
5
+ const qrcode = require('qrcode-terminal');
5
6
  const requireg = require('requireg');
6
7
  const createTestCafe = require('testcafe');
7
8
  const { Selector, ClientFunction } = require('testcafe');
8
- const ElementNotFound = require('./errors/ElementNotFound');
9
9
 
10
+ const ElementNotFound = require('./errors/ElementNotFound');
10
11
  const testControllerHolder = require('./testcafe/testControllerHolder');
11
12
  const {
12
13
  mapError,
@@ -67,6 +68,21 @@ const getHtmlSource = t => ClientFunction(() => document.getElementsByTagName('h
67
68
  * }
68
69
  * ```
69
70
  *
71
+ * To use remote device you can provide 'remote' as browser parameter this will display a link with QR Code
72
+ * See https://devexpress.github.io/testcafe/documentation/recipes/test-on-remote-computers-and-mobile-devices.html
73
+ * #### Example #2: Remote browser connection
74
+ *
75
+ * ```js
76
+ * {
77
+ * helpers: {
78
+ * TestCafe : {
79
+ * url: "http://localhost",
80
+ * waitForTimeout: 15000,
81
+ * browser: "remote"
82
+ * }
83
+ * }
84
+ * }
85
+ * ```
70
86
  *
71
87
  * ## Access From Helpers
72
88
  *
@@ -96,7 +112,7 @@ class TestCafe extends Helper {
96
112
  this.context = undefined; // TODO Not sure if this applies to testcafe
97
113
 
98
114
 
99
- this.options = Object.assign({
115
+ this.options = {
100
116
  url: 'http://localhost',
101
117
  show: false,
102
118
  browser: 'chrome',
@@ -108,7 +124,8 @@ class TestCafe extends Helper {
108
124
  fullPageScreenshots: false,
109
125
  disableScreenshots: false,
110
126
  windowSize: undefined,
111
- }, config);
127
+ ...config,
128
+ };
112
129
  }
113
130
 
114
131
  // TOOD Do a requirements check
@@ -130,12 +147,12 @@ class TestCafe extends Helper {
130
147
  ];
131
148
  }
132
149
 
133
- async _startBrowser() {
150
+ async _configureAndStartBrowser() {
134
151
  this.dummyTestcafeFile = createTestFile(global.output_dir); // create a dummy test file to get hold of the test controller
135
152
 
136
153
  this.iteration += 2; // Use different ports for each test run
137
154
  // @ts-ignore
138
- this.testcafe = await createTestCafe('localhost', null, null);
155
+ this.testcafe = await createTestCafe('', null, null);
139
156
 
140
157
  this.debugSection('_before', 'Starting testcafe browser...');
141
158
 
@@ -143,6 +160,19 @@ class TestCafe extends Helper {
143
160
 
144
161
  // TODO Do we have to cleanup the runner?
145
162
  const runner = this.testcafe.createRunner();
163
+
164
+ this.options.browser !== 'remote' ? this._startBrowser(runner) : this._startRemoteBrowser(runner);
165
+
166
+ this.t = await testControllerHolder.get();
167
+ assert(this.t, 'Expected to have the testcafe test controller');
168
+
169
+ if (this.options.windowSize && this.options.windowSize.indexOf('x') > 0) {
170
+ const dimensions = this.options.windowSize.split('x');
171
+ await this.t.resizeWindow(parseInt(dimensions[0], 10), parseInt(dimensions[1], 10));
172
+ }
173
+ }
174
+
175
+ async _startBrowser(runner) {
146
176
  runner
147
177
  .src(this.dummyTestcafeFile)
148
178
  .screenshots(global.output_dir, !this.options.disableScreenshots)
@@ -166,16 +196,31 @@ class TestCafe extends Helper {
166
196
  this.isRunning = false;
167
197
  this.testcafe.close();
168
198
  });
199
+ }
169
200
 
170
- this.t = await testControllerHolder.get();
171
- assert(this.t, 'Expected to have the testcafe test controller');
172
-
173
- if (this.options.windowSize && this.options.windowSize.indexOf('x') > 0) {
174
- const dimensions = this.options.windowSize.split('x');
175
- await this.t.resizeWindow(parseInt(dimensions[0], 10), parseInt(dimensions[1], 10));
176
- }
201
+ async _startRemoteBrowser(runner) {
202
+ const remoteConnection = await this.testcafe.createBrowserConnection();
203
+ console.log('Connect your device to the following URL or scan QR Code: ', remoteConnection.url);
204
+ qrcode.generate(remoteConnection.url);
205
+ remoteConnection.once('ready', () => {
206
+ runner
207
+ .src(this.dummyTestcafeFile)
208
+ .browsers(remoteConnection)
209
+ .reporter('minimal')
210
+ .run({
211
+ selectorTimeout: this.options.waitForTimeout,
212
+ skipJsErrors: true,
213
+ skipUncaughtErrors: true,
214
+ })
215
+ .catch((err) => {
216
+ this.debugSection('_before', `Error ${err.toString()}`);
217
+ this.isRunning = false;
218
+ this.testcafe.close();
219
+ });
220
+ });
177
221
  }
178
222
 
223
+
179
224
  async _stopBrowser() {
180
225
  this.debugSection('_after', 'Stopping testcafe browser...');
181
226
 
@@ -190,19 +235,20 @@ class TestCafe extends Helper {
190
235
  this.isRunning = false;
191
236
  }
192
237
 
193
- _init() {}
238
+ _init() {
239
+ }
194
240
 
195
241
  async _beforeSuite() {
196
242
  if (!this.options.restart && !this.options.manualStart && !this.isRunning) {
197
243
  this.debugSection('Session', 'Starting singleton browser session');
198
- return this._startBrowser();
244
+ return this._configureAndStartBrowser();
199
245
  }
200
246
  }
201
247
 
202
248
 
203
249
  async _before() {
204
- if (this.options.restart && !this.options.manualStart) return this._startBrowser();
205
- if (!this.isRunning && !this.options.manualStart) return this._startBrowser();
250
+ if (this.options.restart && !this.options.manualStart) return this._configureAndStartBrowser();
251
+ if (!this.isRunning && !this.options.manualStart) return this._configureAndStartBrowser();
206
252
  this.context = null;
207
253
  }
208
254
 
@@ -331,9 +377,10 @@ class TestCafe extends Helper {
331
377
  assertElementExists(els, field, 'Field');
332
378
  const el = await els.nth(0);
333
379
 
334
- return this.t
335
- .click(el)
336
- .pressKey('ctrl+a delete');
380
+ const res = await this.t
381
+ .selectText(el)
382
+ .pressKey('delete');
383
+ return res;
337
384
  }
338
385
 
339
386
  /**
@@ -494,8 +541,9 @@ class TestCafe extends Helper {
494
541
  await this.t.click(optEl, clickOpts).catch(mapError);
495
542
  continue;
496
543
  }
497
- // eslint-disable-next-line no-empty
498
- } catch (err) {}
544
+ // eslint-disable-next-line no-empty
545
+ } catch (err) {
546
+ }
499
547
 
500
548
  try {
501
549
  const sel = `[value="${opt}"]`;
@@ -503,8 +551,9 @@ class TestCafe extends Helper {
503
551
  if (await optEl.count) {
504
552
  await this.t.click(optEl, clickOpts).catch(mapError);
505
553
  }
506
- // eslint-disable-next-line no-empty
507
- } catch (err) {}
554
+ // eslint-disable-next-line no-empty
555
+ } catch (err) {
556
+ }
508
557
  }
509
558
  }
510
559
 
@@ -516,22 +565,22 @@ class TestCafe extends Helper {
516
565
  }
517
566
 
518
567
  /**
519
- * {{> dontSeeInCurrentUrl }}
520
- */
568
+ * {{> dontSeeInCurrentUrl }}
569
+ */
521
570
  async dontSeeInCurrentUrl(url) {
522
571
  stringIncludes('url').negate(url, await getPageUrl(this.t)().catch(mapError));
523
572
  }
524
573
 
525
574
  /**
526
- * {{> seeCurrentUrlEquals }}
527
- */
575
+ * {{> seeCurrentUrlEquals }}
576
+ */
528
577
  async seeCurrentUrlEquals(url) {
529
578
  urlEquals(this.options.url).assert(url, await getPageUrl(this.t)().catch(mapError));
530
579
  }
531
580
 
532
581
  /**
533
- * {{> dontSeeCurrentUrlEquals }}
534
- */
582
+ * {{> dontSeeCurrentUrlEquals }}
583
+ */
535
584
  async dontSeeCurrentUrlEquals(url) {
536
585
  urlEquals(this.options.url).negate(url, await getPageUrl(this.t)().catch(mapError));
537
586
  }
@@ -746,14 +795,16 @@ class TestCafe extends Helper {
746
795
  async grabAttributeFrom(locator, attr) {
747
796
  const sel = await findElements.call(this, this.context, locator);
748
797
  assertElementExists(sel);
749
- return (await sel.nth(0)).value;
798
+ return (await sel.nth(0)).getAttribute(attr);
750
799
  }
751
800
 
752
801
  /**
753
802
  * {{> grabValueFrom }}
754
803
  */
755
804
  async grabValueFrom(locator) {
756
- return this.grabAttributeFrom(locator, 'value');
805
+ const sel = await findElements.call(this, this.context, locator);
806
+ assertElementExists(sel);
807
+ return (await sel.nth(0)).value;
757
808
  }
758
809
 
759
810
  /**
@@ -990,10 +1041,7 @@ class TestCafe extends Helper {
990
1041
  }
991
1042
  const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
992
1043
 
993
- const clientFn = createClientFunction((urlPart) => {
994
- const currUrl = decodeURIComponent(decodeURIComponent(decodeURIComponent(window.location.href)));
995
- return currUrl.indexOf(urlPart) > -1;
996
- }, args);
1044
+ const clientFn = createClientFunction(fn, args).with({ boundTestRun: this.t });
997
1045
 
998
1046
  return waitForFunction(clientFn, waitTimeout);
999
1047
  }
@@ -1076,7 +1124,7 @@ async function waitForFunction(browserFn, waitTimeout) {
1076
1124
  let result;
1077
1125
  try {
1078
1126
  result = await browserFn();
1079
- // eslint-disable-next-line no-empty
1127
+ // eslint-disable-next-line no-empty
1080
1128
  } catch (err) {
1081
1129
  throw new Error(`Error running function ${err.toString()}`);
1082
1130
  }