codeceptjs 3.4.1 → 3.5.1-2.beta.7

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 (281) hide show
  1. package/README.md +31 -30
  2. package/bin/codecept.js +1 -1
  3. package/lib/actor.js +6 -3
  4. package/lib/ai.js +180 -0
  5. package/lib/cli.js +13 -3
  6. package/lib/codecept.js +8 -0
  7. package/lib/colorUtils.js +10 -0
  8. package/lib/command/definitions.js +2 -7
  9. package/lib/command/dryRun.js +11 -2
  10. package/lib/command/generate.js +46 -3
  11. package/lib/command/info.js +24 -0
  12. package/lib/command/init.js +64 -6
  13. package/lib/command/interactive.js +15 -1
  14. package/lib/command/run-multiple/collection.js +17 -5
  15. package/lib/command/run-multiple.js +4 -2
  16. package/lib/command/run-workers.js +68 -5
  17. package/lib/command/run.js +7 -0
  18. package/lib/command/workers/runTests.js +39 -0
  19. package/lib/container.js +13 -3
  20. package/lib/data/context.js +14 -6
  21. package/lib/event.js +4 -0
  22. package/lib/helper/ApiDataFactory.js +2 -1
  23. package/lib/helper/Appium.js +116 -29
  24. package/lib/helper/Expect.js +422 -0
  25. package/lib/helper/FileSystem.js +1 -1
  26. package/lib/helper/GraphQL.js +25 -0
  27. package/lib/helper/JSONResponse.js +4 -4
  28. package/lib/helper/Nightmare.js +10 -5
  29. package/lib/helper/OpenAI.js +126 -0
  30. package/lib/helper/Playwright.js +1298 -229
  31. package/lib/helper/Protractor.js +12 -7
  32. package/lib/helper/Puppeteer.js +204 -64
  33. package/lib/helper/REST.js +15 -5
  34. package/lib/helper/TestCafe.js +45 -10
  35. package/lib/helper/WebDriver.js +252 -83
  36. package/lib/helper/errors/ElementNotFound.js +2 -1
  37. package/lib/helper/extras/PlaywrightReactVueLocator.js +38 -0
  38. package/lib/helper/scripts/blurElement.js +17 -0
  39. package/lib/helper/scripts/focusElement.js +17 -0
  40. package/lib/helper/scripts/highlightElement.js +20 -0
  41. package/lib/html.js +258 -0
  42. package/lib/interfaces/bdd.js +1 -1
  43. package/lib/interfaces/gherkin.js +37 -3
  44. package/lib/interfaces/scenarioConfig.js +1 -0
  45. package/lib/listener/retry.js +2 -1
  46. package/lib/locator.js +17 -4
  47. package/lib/mochaFactory.js +2 -1
  48. package/lib/output.js +1 -1
  49. package/lib/pause.js +78 -19
  50. package/lib/plugin/autoLogin.js +45 -10
  51. package/lib/plugin/debugErrors.js +67 -0
  52. package/lib/plugin/fakerTransform.js +4 -6
  53. package/lib/plugin/heal.js +209 -0
  54. package/lib/plugin/retryFailedStep.js +10 -1
  55. package/lib/plugin/retryTo.js +2 -4
  56. package/lib/plugin/screenshotOnFail.js +11 -2
  57. package/lib/plugin/selenoid.js +6 -1
  58. package/lib/plugin/standardActingHelpers.js +0 -2
  59. package/lib/plugin/stepByStepReport.js +2 -2
  60. package/lib/plugin/tryTo.js +5 -7
  61. package/lib/plugin/wdio.js +0 -1
  62. package/lib/recorder.js +22 -11
  63. package/lib/secret.js +5 -4
  64. package/lib/session.js +1 -1
  65. package/lib/step.js +36 -12
  66. package/lib/ui.js +5 -3
  67. package/lib/utils.js +22 -1
  68. package/lib/workers.js +83 -10
  69. package/package.json +117 -95
  70. package/translations/de-DE.js +5 -0
  71. package/translations/fr-FR.js +14 -1
  72. package/translations/it-IT.js +1 -0
  73. package/translations/ja-JP.js +14 -9
  74. package/translations/pl-PL.js +5 -0
  75. package/translations/pt-BR.js +1 -0
  76. package/translations/ru-RU.js +1 -0
  77. package/translations/zh-CN.js +5 -0
  78. package/translations/zh-TW.js +5 -0
  79. package/typings/index.d.ts +51 -15
  80. package/typings/promiseBasedTypes.d.ts +864 -802
  81. package/typings/types.d.ts +1339 -744
  82. package/CHANGELOG.md +0 -2427
  83. package/docs/advanced.md +0 -351
  84. package/docs/api.md +0 -323
  85. package/docs/basics.md +0 -980
  86. package/docs/bdd.md +0 -535
  87. package/docs/best.md +0 -237
  88. package/docs/books.md +0 -37
  89. package/docs/bootstrap.md +0 -135
  90. package/docs/build/ApiDataFactory.js +0 -409
  91. package/docs/build/Appium.js +0 -1938
  92. package/docs/build/FileSystem.js +0 -228
  93. package/docs/build/GraphQL.js +0 -204
  94. package/docs/build/GraphQLDataFactory.js +0 -309
  95. package/docs/build/JSONResponse.js +0 -338
  96. package/docs/build/Mochawesome.js +0 -71
  97. package/docs/build/Nightmare.js +0 -2145
  98. package/docs/build/Playwright.js +0 -3986
  99. package/docs/build/Polly.js +0 -42
  100. package/docs/build/Protractor.js +0 -2699
  101. package/docs/build/Puppeteer.js +0 -3710
  102. package/docs/build/REST.js +0 -334
  103. package/docs/build/SeleniumWebdriver.js +0 -76
  104. package/docs/build/TestCafe.js +0 -2057
  105. package/docs/build/WebDriver.js +0 -4017
  106. package/docs/changelog.md +0 -2436
  107. package/docs/commands.md +0 -254
  108. package/docs/community-helpers.md +0 -58
  109. package/docs/configuration.md +0 -157
  110. package/docs/continuous-integration.md +0 -22
  111. package/docs/custom-helpers.md +0 -306
  112. package/docs/data.md +0 -375
  113. package/docs/detox.md +0 -235
  114. package/docs/docker.md +0 -137
  115. package/docs/email.md +0 -183
  116. package/docs/examples.md +0 -149
  117. package/docs/helpers/ApiDataFactory.md +0 -266
  118. package/docs/helpers/Appium.md +0 -1312
  119. package/docs/helpers/Detox.md +0 -586
  120. package/docs/helpers/FileSystem.md +0 -152
  121. package/docs/helpers/GraphQL.md +0 -130
  122. package/docs/helpers/GraphQLDataFactory.md +0 -226
  123. package/docs/helpers/JSONResponse.md +0 -254
  124. package/docs/helpers/Mochawesome.md +0 -8
  125. package/docs/helpers/MockRequest.md +0 -377
  126. package/docs/helpers/Nightmare.md +0 -1256
  127. package/docs/helpers/Playwright.md +0 -2208
  128. package/docs/helpers/Polly.md +0 -44
  129. package/docs/helpers/Puppeteer-firefox.md +0 -86
  130. package/docs/helpers/Puppeteer.md +0 -2141
  131. package/docs/helpers/REST.md +0 -217
  132. package/docs/helpers/TestCafe.md +0 -1222
  133. package/docs/helpers/WebDriver.md +0 -2319
  134. package/docs/hooks.md +0 -340
  135. package/docs/index.md +0 -111
  136. package/docs/installation.md +0 -75
  137. package/docs/internal-api.md +0 -265
  138. package/docs/locators.md +0 -331
  139. package/docs/mobile-react-native-locators.md +0 -67
  140. package/docs/mobile.md +0 -297
  141. package/docs/nightmare.md +0 -223
  142. package/docs/pageobjects.md +0 -291
  143. package/docs/parallel.md +0 -232
  144. package/docs/playwright.md +0 -609
  145. package/docs/plugins.md +0 -1171
  146. package/docs/puppeteer.md +0 -316
  147. package/docs/quickstart.md +0 -163
  148. package/docs/react.md +0 -69
  149. package/docs/reports.md +0 -392
  150. package/docs/secrets.md +0 -30
  151. package/docs/shadow.md +0 -68
  152. package/docs/shared/keys.mustache +0 -31
  153. package/docs/shared/react.mustache +0 -1
  154. package/docs/testcafe.md +0 -174
  155. package/docs/translation.md +0 -247
  156. package/docs/tutorial.md +0 -271
  157. package/docs/typescript.md +0 -180
  158. package/docs/ui.md +0 -59
  159. package/docs/videos.md +0 -28
  160. package/docs/visual.md +0 -202
  161. package/docs/vue.md +0 -121
  162. package/docs/webapi/amOnPage.mustache +0 -11
  163. package/docs/webapi/appendField.mustache +0 -9
  164. package/docs/webapi/attachFile.mustache +0 -12
  165. package/docs/webapi/checkOption.mustache +0 -13
  166. package/docs/webapi/clearCookie.mustache +0 -10
  167. package/docs/webapi/clearField.mustache +0 -9
  168. package/docs/webapi/click.mustache +0 -25
  169. package/docs/webapi/clickLink.mustache +0 -8
  170. package/docs/webapi/closeCurrentTab.mustache +0 -7
  171. package/docs/webapi/closeOtherTabs.mustache +0 -8
  172. package/docs/webapi/dontSee.mustache +0 -11
  173. package/docs/webapi/dontSeeCheckboxIsChecked.mustache +0 -10
  174. package/docs/webapi/dontSeeCookie.mustache +0 -8
  175. package/docs/webapi/dontSeeCurrentUrlEquals.mustache +0 -10
  176. package/docs/webapi/dontSeeElement.mustache +0 -8
  177. package/docs/webapi/dontSeeElementInDOM.mustache +0 -8
  178. package/docs/webapi/dontSeeInCurrentUrl.mustache +0 -4
  179. package/docs/webapi/dontSeeInField.mustache +0 -11
  180. package/docs/webapi/dontSeeInSource.mustache +0 -8
  181. package/docs/webapi/dontSeeInTitle.mustache +0 -8
  182. package/docs/webapi/doubleClick.mustache +0 -13
  183. package/docs/webapi/downloadFile.mustache +0 -12
  184. package/docs/webapi/dragAndDrop.mustache +0 -9
  185. package/docs/webapi/dragSlider.mustache +0 -11
  186. package/docs/webapi/executeAsyncScript.mustache +0 -24
  187. package/docs/webapi/executeScript.mustache +0 -26
  188. package/docs/webapi/fillField.mustache +0 -16
  189. package/docs/webapi/forceClick.mustache +0 -28
  190. package/docs/webapi/forceRightClick.mustache +0 -18
  191. package/docs/webapi/grabAllWindowHandles.mustache +0 -7
  192. package/docs/webapi/grabAttributeFrom.mustache +0 -10
  193. package/docs/webapi/grabAttributeFromAll.mustache +0 -9
  194. package/docs/webapi/grabBrowserLogs.mustache +0 -9
  195. package/docs/webapi/grabCookie.mustache +0 -11
  196. package/docs/webapi/grabCssPropertyFrom.mustache +0 -11
  197. package/docs/webapi/grabCssPropertyFromAll.mustache +0 -10
  198. package/docs/webapi/grabCurrentUrl.mustache +0 -9
  199. package/docs/webapi/grabCurrentWindowHandle.mustache +0 -6
  200. package/docs/webapi/grabDataFromPerformanceTiming.mustache +0 -20
  201. package/docs/webapi/grabElementBoundingRect.mustache +0 -20
  202. package/docs/webapi/grabGeoLocation.mustache +0 -8
  203. package/docs/webapi/grabHTMLFrom.mustache +0 -10
  204. package/docs/webapi/grabHTMLFromAll.mustache +0 -9
  205. package/docs/webapi/grabNumberOfOpenTabs.mustache +0 -8
  206. package/docs/webapi/grabNumberOfVisibleElements.mustache +0 -9
  207. package/docs/webapi/grabPageScrollPosition.mustache +0 -8
  208. package/docs/webapi/grabPopupText.mustache +0 -5
  209. package/docs/webapi/grabSource.mustache +0 -8
  210. package/docs/webapi/grabTextFrom.mustache +0 -10
  211. package/docs/webapi/grabTextFromAll.mustache +0 -9
  212. package/docs/webapi/grabTitle.mustache +0 -8
  213. package/docs/webapi/grabValueFrom.mustache +0 -9
  214. package/docs/webapi/grabValueFromAll.mustache +0 -8
  215. package/docs/webapi/moveCursorTo.mustache +0 -12
  216. package/docs/webapi/openNewTab.mustache +0 -7
  217. package/docs/webapi/pressKey.mustache +0 -12
  218. package/docs/webapi/pressKeyDown.mustache +0 -12
  219. package/docs/webapi/pressKeyUp.mustache +0 -12
  220. package/docs/webapi/pressKeyWithKeyNormalization.mustache +0 -60
  221. package/docs/webapi/refreshPage.mustache +0 -6
  222. package/docs/webapi/resizeWindow.mustache +0 -6
  223. package/docs/webapi/rightClick.mustache +0 -14
  224. package/docs/webapi/saveElementScreenshot.mustache +0 -10
  225. package/docs/webapi/saveScreenshot.mustache +0 -12
  226. package/docs/webapi/say.mustache +0 -10
  227. package/docs/webapi/scrollIntoView.mustache +0 -11
  228. package/docs/webapi/scrollPageToBottom.mustache +0 -6
  229. package/docs/webapi/scrollPageToTop.mustache +0 -6
  230. package/docs/webapi/scrollTo.mustache +0 -12
  231. package/docs/webapi/see.mustache +0 -11
  232. package/docs/webapi/seeAttributesOnElements.mustache +0 -9
  233. package/docs/webapi/seeCheckboxIsChecked.mustache +0 -10
  234. package/docs/webapi/seeCookie.mustache +0 -8
  235. package/docs/webapi/seeCssPropertiesOnElements.mustache +0 -9
  236. package/docs/webapi/seeCurrentUrlEquals.mustache +0 -11
  237. package/docs/webapi/seeElement.mustache +0 -8
  238. package/docs/webapi/seeElementInDOM.mustache +0 -8
  239. package/docs/webapi/seeInCurrentUrl.mustache +0 -8
  240. package/docs/webapi/seeInField.mustache +0 -12
  241. package/docs/webapi/seeInPopup.mustache +0 -8
  242. package/docs/webapi/seeInSource.mustache +0 -7
  243. package/docs/webapi/seeInTitle.mustache +0 -8
  244. package/docs/webapi/seeNumberOfElements.mustache +0 -11
  245. package/docs/webapi/seeNumberOfVisibleElements.mustache +0 -10
  246. package/docs/webapi/seeTextEquals.mustache +0 -9
  247. package/docs/webapi/seeTitleEquals.mustache +0 -8
  248. package/docs/webapi/selectOption.mustache +0 -21
  249. package/docs/webapi/setCookie.mustache +0 -16
  250. package/docs/webapi/setGeoLocation.mustache +0 -12
  251. package/docs/webapi/switchTo.mustache +0 -9
  252. package/docs/webapi/switchToNextTab.mustache +0 -10
  253. package/docs/webapi/switchToPreviousTab.mustache +0 -10
  254. package/docs/webapi/type.mustache +0 -18
  255. package/docs/webapi/uncheckOption.mustache +0 -13
  256. package/docs/webapi/wait.mustache +0 -8
  257. package/docs/webapi/waitForClickable.mustache +0 -11
  258. package/docs/webapi/waitForDetached.mustache +0 -10
  259. package/docs/webapi/waitForElement.mustache +0 -11
  260. package/docs/webapi/waitForEnabled.mustache +0 -6
  261. package/docs/webapi/waitForFunction.mustache +0 -17
  262. package/docs/webapi/waitForInvisible.mustache +0 -10
  263. package/docs/webapi/waitForText.mustache +0 -13
  264. package/docs/webapi/waitForValue.mustache +0 -10
  265. package/docs/webapi/waitForVisible.mustache +0 -10
  266. package/docs/webapi/waitInUrl.mustache +0 -9
  267. package/docs/webapi/waitNumberOfVisibleElements.mustache +0 -10
  268. package/docs/webapi/waitToHide.mustache +0 -10
  269. package/docs/webapi/waitUrlEquals.mustache +0 -10
  270. package/docs/webdriver.md +0 -657
  271. package/docs/wiki/Books-&-Posts.md +0 -27
  272. package/docs/wiki/Community-Helpers-&-Plugins.md +0 -49
  273. package/docs/wiki/Converting-Playwright-to-Istanbul-Coverage.md +0 -29
  274. package/docs/wiki/Examples.md +0 -139
  275. package/docs/wiki/Google-Summer-of-Code-(GSoC)-2020.md +0 -68
  276. package/docs/wiki/Home.md +0 -16
  277. package/docs/wiki/Release-Process.md +0 -24
  278. package/docs/wiki/Roadmap.md +0 -23
  279. package/docs/wiki/Tests.md +0 -1393
  280. package/docs/wiki/Upgrading-to-CodeceptJS-3.md +0 -153
  281. package/docs/wiki/Videos.md +0 -19
@@ -631,14 +631,16 @@ class Protractor extends Helper {
631
631
  * {{> seeInField }}
632
632
  */
633
633
  async seeInField(field, value) {
634
- return proceedSeeInField.call(this, 'assert', field, value);
634
+ const _value = (typeof value === 'boolean') ? value : value.toString();
635
+ return proceedSeeInField.call(this, 'assert', field, _value);
635
636
  }
636
637
 
637
638
  /**
638
639
  * {{> dontSeeInField }}
639
640
  */
640
641
  async dontSeeInField(field, value) {
641
- return proceedSeeInField.call(this, 'negate', field, value);
642
+ const _value = (typeof value === 'boolean') ? value : value.toString();
643
+ return proceedSeeInField.call(this, 'negate', field, _value);
642
644
  }
643
645
 
644
646
  /**
@@ -647,7 +649,7 @@ class Protractor extends Helper {
647
649
  async appendField(field, value) {
648
650
  const els = await findFields(this.browser, field);
649
651
  assertElementExists(els, field, 'Field');
650
- return els[0].sendKeys(value);
652
+ return els[0].sendKeys(value.toString());
651
653
  }
652
654
 
653
655
  /**
@@ -1053,7 +1055,7 @@ class Protractor extends Helper {
1053
1055
  const stream = fs.createWriteStream(outputFile);
1054
1056
  stream.write(Buffer.from(png, 'base64'));
1055
1057
  stream.end();
1056
- return new Promise(resolve => stream.on('finish', resolve));
1058
+ return new Promise(resolve => stream.on('finish', resolve)); // eslint-disable-line no-promise-executor-return
1057
1059
  };
1058
1060
 
1059
1061
  const res = await this._locate(locator);
@@ -1076,7 +1078,7 @@ class Protractor extends Helper {
1076
1078
  const stream = fs.createWriteStream(outputFile);
1077
1079
  stream.write(Buffer.from(png, 'base64'));
1078
1080
  stream.end();
1079
- return new Promise(resolve => stream.on('finish', resolve));
1081
+ return new Promise(resolve => stream.on('finish', resolve)); // eslint-disable-line no-promise-executor-return
1080
1082
  };
1081
1083
 
1082
1084
  if (!fullPage) {
@@ -1613,8 +1615,11 @@ class Protractor extends Helper {
1613
1615
  const body = document.body;
1614
1616
  const html = document.documentElement;
1615
1617
  window.scrollTo(0, Math.max(
1616
- body.scrollHeight, body.offsetHeight,
1617
- html.clientHeight, html.scrollHeight, html.offsetHeight
1618
+ body.scrollHeight,
1619
+ body.offsetHeight,
1620
+ html.clientHeight,
1621
+ html.scrollHeight,
1622
+ html.offsetHeight
1618
1623
  ));
1619
1624
  });
1620
1625
  /* eslint-enable */
@@ -4,8 +4,10 @@ const fsExtra = require('fs-extra');
4
4
  const path = require('path');
5
5
 
6
6
  const Helper = require('@codeceptjs/helper');
7
+ const { v4: uuidv4 } = require('uuid');
7
8
  const Locator = require('../locator');
8
9
  const recorder = require('../recorder');
10
+ const store = require('../store');
9
11
  const stringIncludes = require('../assert/include').includes;
10
12
  const { urlEquals } = require('../assert/equal');
11
13
  const { equals } = require('../assert/equal');
@@ -18,11 +20,12 @@ const {
18
20
  fileExists,
19
21
  chunkArray,
20
22
  toCamelCase,
23
+ clearString,
21
24
  convertCssPropertiesToCamelCase,
22
25
  screenshotOutputFolder,
23
26
  getNormalizedKeyAttributeValue,
24
27
  isModifierKey,
25
- requireWithFallback,
28
+ requireWithFallback, normalizeSpacesInString,
26
29
  } = require('../utils');
27
30
  const {
28
31
  isColorProperty,
@@ -33,6 +36,9 @@ const RemoteBrowserConnectionRefused = require('./errors/RemoteBrowserConnection
33
36
  const Popup = require('./extras/Popup');
34
37
  const Console = require('./extras/Console');
35
38
  const findReact = require('./extras/React');
39
+ const { highlightElement } = require('./scripts/highlightElement');
40
+ const { blurElement } = require('./scripts/blurElement');
41
+ const { focusElement } = require('./scripts/focusElement');
36
42
 
37
43
  let puppeteer;
38
44
  let perfTiming;
@@ -53,6 +59,8 @@ const consoleLogStore = new Console();
53
59
  * @prop {boolean} [disableScreenshots=false] - don't save screenshot on failure.
54
60
  * @prop {boolean} [fullPageScreenshots=false] - make full page screenshots on failure.
55
61
  * @prop {boolean} [uniqueScreenshotNames=false] - option to prevent screenshot override if you have scenarios with the same name in different suites.
62
+ * @prop {boolean} [trace=false] - record [tracing information](https://pptr.dev/api/puppeteer.tracing) with screenshots.
63
+ * @prop {boolean} [keepTraceForPassedTests=false] - save trace for passed tests.
56
64
  * @prop {boolean} [keepBrowserState=false] - keep browser state between tests when `restart` is set to false.
57
65
  * @prop {boolean} [keepCookies=false] - keep cookies between tests when `restart` is set to false.
58
66
  * @prop {number} [waitForAction=100] - how long to wait after click, doubleClick or PressKey actions in ms. Default: 100.
@@ -65,6 +73,7 @@ const consoleLogStore = new Console();
65
73
  * @prop {boolean} [manualStart=false] - do not start browser before a test, start it manually inside a helper with `this.helpers["Puppeteer"]._startBrowser()`.
66
74
  * @prop {string} [browser=chrome] - can be changed to `firefox` when using [puppeteer-firefox](https://codecept.io/helpers/Puppeteer-firefox).
67
75
  * @prop {object} [chrome] - pass additional [Puppeteer run options](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions).
76
+ * @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose).
68
77
  */
69
78
  const config = {};
70
79
 
@@ -87,6 +96,14 @@ const config = {};
87
96
  *
88
97
  * <!-- configuration -->
89
98
  *
99
+ * #### Trace Recording Customization
100
+ *
101
+ * Trace recording provides complete information on test execution and includes screenshots, and network requests logged during run.
102
+ * Traces will be saved to `output/trace`
103
+ *
104
+ * * `trace`: enables trace recording for failed tests; trace are saved into `output/trace` folder
105
+ * * `keepTraceForPassedTests`: - save trace for passed tests
106
+ *
90
107
  * #### Example #1: Wait for 0 network connections.
91
108
  *
92
109
  * ```js
@@ -226,6 +243,7 @@ class Puppeteer extends Helper {
226
243
  keepBrowserState: false,
227
244
  show: false,
228
245
  defaultPopupAction: 'accept',
246
+ highlightElement: false,
229
247
  };
230
248
 
231
249
  return Object.assign(defaults, config);
@@ -241,6 +259,7 @@ class Puppeteer extends Helper {
241
259
  headless: !this.options.show,
242
260
  ...this._getOptions(config),
243
261
  };
262
+ if (this.puppeteerOptions.headless) this.puppeteerOptions.headless = 'new';
244
263
  this.isRemoteBrowser = !!this.puppeteerOptions.browserWSEndpoint;
245
264
  popupStore.defaultAction = this.options.defaultPopupAction;
246
265
  }
@@ -275,10 +294,11 @@ class Puppeteer extends Helper {
275
294
  }
276
295
  }
277
296
 
278
- async _before() {
297
+ async _before(test) {
279
298
  this.sessionPages = {};
299
+ this.currentRunningTest = test;
280
300
  recorder.retry({
281
- retries: 3,
301
+ retries: process.env.FAILED_STEP_RETRIES || 3,
282
302
  when: err => {
283
303
  if (!err || typeof (err.message) !== 'string') {
284
304
  return false;
@@ -598,8 +618,8 @@ class Puppeteer extends Helper {
598
618
  return this.switchTo(null)
599
619
  .then(() => frame.reduce((p, frameLocator) => p.then(() => this.switchTo(frameLocator)), Promise.resolve()));
600
620
  }
601
- await this.switchTo(locator);
602
- this.withinLocator = new Locator(locator);
621
+ await this.switchTo(frame);
622
+ this.withinLocator = new Locator(frame);
603
623
  return;
604
624
  }
605
625
 
@@ -641,6 +661,14 @@ class Puppeteer extends Helper {
641
661
  }
642
662
  }
643
663
 
664
+ if (this.options.trace) {
665
+ const fileName = `${`${global.output_dir}${path.sep}trace${path.sep}${uuidv4()}_${clearString(this.currentRunningTest.title)}`.slice(0, 245)}.json`;
666
+ const dir = path.dirname(fileName);
667
+ if (!fileExists(dir)) fs.mkdirSync(dir);
668
+ await this.page.tracing.start({ screenshots: true, path: fileName });
669
+ this.currentRunningTest.artifacts.trace = fileName;
670
+ }
671
+
644
672
  await this.page.goto(url, { waitUntil: this.options.waitForNavigation });
645
673
 
646
674
  const performanceTiming = JSON.parse(await this.page.evaluate(() => JSON.stringify(window.performance.timing)));
@@ -657,11 +685,13 @@ class Puppeteer extends Helper {
657
685
  }
658
686
 
659
687
  /**
660
- * {{> resizeWindow }}
661
688
  *
662
689
  * Unlike other drivers Puppeteer changes the size of a viewport, not the window!
663
- * 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.
664
691
  * It also can't maximize a window.
692
+ *
693
+ * {{> resizeWindow }}
694
+ *
665
695
  */
666
696
  async resizeWindow(width, height) {
667
697
  if (width === 'maximize') {
@@ -676,14 +706,14 @@ class Puppeteer extends Helper {
676
706
  * Set headers for all next requests
677
707
  *
678
708
  * ```js
679
- * I.haveRequestHeaders({
709
+ * I.setPuppeteerRequestHeaders({
680
710
  * 'X-Sent-By': 'CodeceptJS',
681
711
  * });
682
712
  * ```
683
713
  *
684
714
  * @param {object} customHeaders headers to set
685
715
  */
686
- async haveRequestHeaders(customHeaders) {
716
+ async setPuppeteerRequestHeaders(customHeaders) {
687
717
  if (!customHeaders) {
688
718
  throw new Error('Cannot send empty headers.');
689
719
  }
@@ -704,6 +734,32 @@ class Puppeteer extends Helper {
704
734
  return this._waitForAction();
705
735
  }
706
736
 
737
+ /**
738
+ * {{> focus }}
739
+ *
740
+ */
741
+ async focus(locator) {
742
+ const els = await this._locate(locator);
743
+ assertElementExists(els, locator, 'Element to focus');
744
+ const el = els[0];
745
+
746
+ await el.click();
747
+ await el.focus();
748
+ return this._waitForAction();
749
+ }
750
+
751
+ /**
752
+ * {{> blur }}
753
+ *
754
+ */
755
+ async blur(locator) {
756
+ const els = await this._locate(locator);
757
+ assertElementExists(els, locator, 'Element to blur');
758
+
759
+ await blurElement(els[0], this.page);
760
+ return this._waitForAction();
761
+ }
762
+
707
763
  /**
708
764
  * {{> dragAndDrop }}
709
765
  */
@@ -735,8 +791,11 @@ class Puppeteer extends Helper {
735
791
  const body = document.body;
736
792
  const html = document.documentElement;
737
793
  window.scrollTo(0, Math.max(
738
- body.scrollHeight, body.offsetHeight,
739
- html.clientHeight, html.scrollHeight, html.offsetHeight,
794
+ body.scrollHeight,
795
+ body.offsetHeight,
796
+ html.clientHeight,
797
+ html.scrollHeight,
798
+ html.offsetHeight,
740
799
  ));
741
800
  });
742
801
  }
@@ -825,7 +884,7 @@ class Puppeteer extends Helper {
825
884
  }
826
885
 
827
886
  /**
828
- * Find a checkbox by providing human readable text:
887
+ * Find a checkbox by providing human-readable text:
829
888
  * NOTE: Assumes the checkable element exists
830
889
  *
831
890
  * ```js
@@ -840,7 +899,7 @@ class Puppeteer extends Helper {
840
899
  }
841
900
 
842
901
  /**
843
- * Find a clickable element by providing human readable text:
902
+ * Find a clickable element by providing human-readable text:
844
903
  *
845
904
  * ```js
846
905
  * this.helpers['Puppeteer']._locateClickable('Next page').then // ...
@@ -852,7 +911,7 @@ class Puppeteer extends Helper {
852
911
  }
853
912
 
854
913
  /**
855
- * Find field elements by providing human readable text:
914
+ * Find field elements by providing human-readable text:
856
915
  *
857
916
  * ```js
858
917
  * this.helpers['Puppeteer']._locateFields('Your email').then // ...
@@ -862,6 +921,14 @@ class Puppeteer extends Helper {
862
921
  return findFields.call(this, locator);
863
922
  }
864
923
 
924
+ /**
925
+ * {{> grabWebElements }}
926
+ *
927
+ */
928
+ async grabWebElements(locator) {
929
+ return this._locate(locator);
930
+ }
931
+
865
932
  /**
866
933
  * Switch focus to a particular tab by its number. It waits tabs loading and then switch tab
867
934
  *
@@ -1056,7 +1123,7 @@ class Puppeteer extends Helper {
1056
1123
  * Sets a directory to where save files. Allows to test file downloads.
1057
1124
  * Should be used with [FileSystem helper](https://codecept.io/helpers/FileSystem) to check that file were downloaded correctly.
1058
1125
  *
1059
- * By default files are saved to `output/downloads`.
1126
+ * By default, files are saved to `output/downloads`.
1060
1127
  * This directory is cleaned on every `handleDownloads` call, to ensure no old files are kept.
1061
1128
  *
1062
1129
  * ```js
@@ -1228,9 +1295,9 @@ class Puppeteer extends Helper {
1228
1295
  }
1229
1296
 
1230
1297
  /**
1231
- * {{> pressKeyWithKeyNormalization }}
1232
- *
1233
1298
  * _Note:_ Shortcuts like `'Meta'` + `'A'` do not work on macOS ([GoogleChrome/puppeteer#1313](https://github.com/GoogleChrome/puppeteer/issues/1313)).
1299
+ *
1300
+ * {{> pressKeyWithKeyNormalization }}
1234
1301
  */
1235
1302
  async pressKey(key) {
1236
1303
  const modifiers = [];
@@ -1262,6 +1329,7 @@ class Puppeteer extends Helper {
1262
1329
  */
1263
1330
  async type(keys, delay = null) {
1264
1331
  if (!Array.isArray(keys)) {
1332
+ keys = keys.toString();
1265
1333
  keys = keys.split('');
1266
1334
  }
1267
1335
 
@@ -1286,7 +1354,10 @@ class Puppeteer extends Helper {
1286
1354
  } else if (editable) {
1287
1355
  await this._evaluateHandeInContext(el => el.innerHTML = '', el);
1288
1356
  }
1357
+
1358
+ highlightActiveElement.call(this, el, await this._getContext());
1289
1359
  await el.type(value.toString(), { delay: this.options.pressKeyDelay });
1360
+
1290
1361
  return this._waitForAction();
1291
1362
  }
1292
1363
 
@@ -1305,8 +1376,9 @@ class Puppeteer extends Helper {
1305
1376
  async appendField(field, value) {
1306
1377
  const els = await findVisibleFields.call(this, field);
1307
1378
  assertElementExists(els, field, 'Field');
1379
+ highlightActiveElement.call(this, els[0], await this._getContext());
1308
1380
  await els[0].press('End');
1309
- await els[0].type(value, { delay: this.options.pressKeyDelay });
1381
+ await els[0].type(value.toString(), { delay: this.options.pressKeyDelay });
1310
1382
  return this._waitForAction();
1311
1383
  }
1312
1384
 
@@ -1314,20 +1386,22 @@ class Puppeteer extends Helper {
1314
1386
  * {{> seeInField }}
1315
1387
  */
1316
1388
  async seeInField(field, value) {
1317
- return proceedSeeInField.call(this, 'assert', field, value);
1389
+ const _value = (typeof value === 'boolean') ? value : value.toString();
1390
+ return proceedSeeInField.call(this, 'assert', field, _value);
1318
1391
  }
1319
1392
 
1320
1393
  /**
1321
1394
  * {{> dontSeeInField }}
1322
1395
  */
1323
1396
  async dontSeeInField(field, value) {
1324
- return proceedSeeInField.call(this, 'negate', field, value);
1397
+ const _value = (typeof value === 'boolean') ? value : value.toString();
1398
+ return proceedSeeInField.call(this, 'negate', field, _value);
1325
1399
  }
1326
1400
 
1327
1401
  /**
1328
- * {{> attachFile }}
1329
- *
1330
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 }}
1331
1405
  */
1332
1406
  async attachFile(locator, pathToFile) {
1333
1407
  const file = path.join(global.codecept_dir, pathToFile);
@@ -1351,6 +1425,7 @@ class Puppeteer extends Helper {
1351
1425
  if (await el.getProperty('tagName').then(t => t.jsonValue()) !== 'SELECT') {
1352
1426
  throw new Error('Element is not <select>');
1353
1427
  }
1428
+ highlightActiveElement.call(this, els[0], await this._getContext());
1354
1429
  if (!Array.isArray(option)) option = [option];
1355
1430
 
1356
1431
  for (const key in option) {
@@ -1557,9 +1632,9 @@ class Puppeteer extends Helper {
1557
1632
  }
1558
1633
 
1559
1634
  /**
1560
- * {{> executeScript }}
1635
+ * If a function returns a Promise, tt will wait for its resolution.
1561
1636
  *
1562
- * If a function returns a Promise It will wait for it resolution.
1637
+ * {{> executeScript }}
1563
1638
  */
1564
1639
  async executeScript(...args) {
1565
1640
  let context = this.page;
@@ -1570,9 +1645,8 @@ class Puppeteer extends Helper {
1570
1645
  }
1571
1646
 
1572
1647
  /**
1573
- * {{> executeAsyncScript }}
1574
- *
1575
1648
  * Asynchronous scripts can also be executed with `executeScript` if a function returns a Promise.
1649
+ * {{> executeAsyncScript }}
1576
1650
  */
1577
1651
  async executeAsyncScript(...args) {
1578
1652
  const asyncFn = function () {
@@ -1699,29 +1773,26 @@ class Puppeteer extends Helper {
1699
1773
 
1700
1774
  const cssPropertiesCamelCase = convertCssPropertiesToCamelCase(cssProperties);
1701
1775
  const elemAmount = res.length;
1702
- const commands = [];
1703
- res.forEach((el) => {
1704
- Object.keys(cssPropertiesCamelCase).forEach((prop) => {
1705
- commands.push(el.executionContext()
1706
- .evaluate((el) => {
1707
- const style = window.getComputedStyle ? getComputedStyle(el) : el.currentStyle;
1708
- return JSON.parse(JSON.stringify(style));
1709
- }, el)
1710
- .then((props) => {
1711
- if (isColorProperty(prop)) {
1712
- return convertColorToRGBA(props[prop]);
1713
- }
1714
- return props[prop];
1715
- }));
1716
- });
1717
- });
1718
- let props = await Promise.all(commands);
1776
+ let props = [];
1777
+
1778
+ for (const element of res) {
1779
+ for (const prop of Object.keys(cssProperties)) {
1780
+ const cssProp = await this.grabCssPropertyFrom(locator, prop);
1781
+ if (isColorProperty(prop)) {
1782
+ props.push(convertColorToRGBA(cssProp));
1783
+ } else {
1784
+ props.push(cssProp);
1785
+ }
1786
+ }
1787
+ }
1788
+
1719
1789
  const values = Object.keys(cssPropertiesCamelCase).map(key => cssPropertiesCamelCase[key]);
1720
1790
  if (!Array.isArray(props)) props = [props];
1721
1791
  let chunked = chunkArray(props, values.length);
1722
1792
  chunked = chunked.filter((val) => {
1723
1793
  for (let i = 0; i < val.length; ++i) {
1724
- if (val[i] !== values[i]) return false;
1794
+ // eslint-disable-next-line eqeqeq
1795
+ if (val[i] != values[i]) return false;
1725
1796
  }
1726
1797
  return true;
1727
1798
  });
@@ -1752,7 +1823,10 @@ class Puppeteer extends Helper {
1752
1823
  let chunked = chunkArray(attrs, values.length);
1753
1824
  chunked = chunked.filter((val) => {
1754
1825
  for (let i = 0; i < val.length; ++i) {
1755
- if (val[i] !== values[i]) return false;
1826
+ const _actual = Number.isNaN(val[i]) || (typeof values[i]) === 'string' ? val[i] : Number.parseInt(values[i], 10);
1827
+ const _expected = Number.isNaN(values[i]) || (typeof values[i]) === 'string' ? values[i] : Number.parseInt(values[i], 10);
1828
+ // if the attribute doesn't exist, returns false as well
1829
+ if (!_actual || !_actual.includes(_expected)) return false;
1756
1830
  }
1757
1831
  return true;
1758
1832
  });
@@ -1828,27 +1902,58 @@ class Puppeteer extends Helper {
1828
1902
  */
1829
1903
  async saveScreenshot(fileName, fullPage) {
1830
1904
  const fullPageOption = fullPage || this.options.fullPageScreenshots;
1831
- const outputFile = screenshotOutputFolder(fileName);
1905
+ let outputFile = screenshotOutputFolder(fileName);
1832
1906
 
1833
1907
  this.debug(`Screenshot is saving to ${outputFile}`);
1834
1908
 
1909
+ await this.page.screenshot({
1910
+ path: outputFile,
1911
+ fullPage: fullPageOption,
1912
+ type: 'png',
1913
+ });
1914
+
1835
1915
  if (this.activeSessionName) {
1836
- const activeSessionPage = this.sessionPages[this.activeSessionName];
1837
-
1838
- if (activeSessionPage) {
1839
- return activeSessionPage.screenshot({
1840
- path: outputFile,
1841
- fullPage: fullPageOption,
1842
- type: 'png',
1843
- });
1916
+ for (const sessionName in this.sessionPages) {
1917
+ const activeSessionPage = this.sessionPages[sessionName];
1918
+ outputFile = screenshotOutputFolder(`${sessionName}_${fileName}`);
1919
+
1920
+ this.debug(`${sessionName} - Screenshot is saving to ${outputFile}`);
1921
+
1922
+ if (activeSessionPage) {
1923
+ await activeSessionPage.screenshot({
1924
+ path: outputFile,
1925
+ fullPage: fullPageOption,
1926
+ type: 'png',
1927
+ });
1928
+ }
1844
1929
  }
1845
1930
  }
1931
+ }
1932
+
1933
+ async _failed(test) {
1934
+ await this._withinEnd();
1846
1935
 
1847
- return this.page.screenshot({ path: outputFile, fullPage: fullPageOption, type: 'png' });
1936
+ if (this.options.trace) {
1937
+ await this.page.tracing.stop();
1938
+ const _traceName = this.currentRunningTest.artifacts.trace.replace('.json', '.failed.json');
1939
+ fs.renameSync(this.currentRunningTest.artifacts.trace, _traceName);
1940
+ test.artifacts.trace = _traceName;
1941
+ }
1848
1942
  }
1849
1943
 
1850
- async _failed() {
1944
+ async _passed(test) {
1851
1945
  await this._withinEnd();
1946
+
1947
+ if (this.options.trace) {
1948
+ await this.page.tracing.stop();
1949
+ if (this.options.keepTraceForPassedTests) {
1950
+ const _traceName = this.currentRunningTest.artifacts.trace.replace('.json', '.passed.json');
1951
+ fs.renameSync(this.currentRunningTest.artifacts.trace, _traceName);
1952
+ test.artifacts.trace = _traceName;
1953
+ } else {
1954
+ fs.unlinkSync(this.currentRunningTest.artifacts.trace);
1955
+ }
1956
+ }
1852
1957
  }
1853
1958
 
1854
1959
  /**
@@ -1960,7 +2065,7 @@ class Puppeteer extends Helper {
1960
2065
  assertElementExists(els, locator);
1961
2066
 
1962
2067
  return this.waitForFunction(isElementClickable, [els[0]], waitTimeout).catch(async (e) => {
1963
- if (/failed: timeout/i.test(e.message)) {
2068
+ if (/Waiting failed/i.test(e.message) || /failed: timeout/i.test(e.message)) {
1964
2069
  throw new Error(`element ${new Locator(locator).toString()} still not clickable after ${waitTimeout || this.options.waitForTimeout / 1000} sec`);
1965
2070
  } else {
1966
2071
  throw e;
@@ -1991,7 +2096,7 @@ class Puppeteer extends Helper {
1991
2096
  /**
1992
2097
  * {{> waitForVisible }}
1993
2098
  *
1994
- * This method accepts [React selectors](https://codecept.io/react).
2099
+ * {{ react }}
1995
2100
  */
1996
2101
  async waitForVisible(locator, sec) {
1997
2102
  const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
@@ -2046,6 +2151,24 @@ class Puppeteer extends Helper {
2046
2151
  });
2047
2152
  }
2048
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
+
2049
2172
  async _getContext() {
2050
2173
  if (this.context && this.context.constructor.name === 'Frame') {
2051
2174
  return this.context;
@@ -2064,7 +2187,7 @@ class Puppeteer extends Helper {
2064
2187
  return currUrl.indexOf(urlPart) > -1;
2065
2188
  }, { timeout: waitTimeout }, urlPart).catch(async (e) => {
2066
2189
  const currUrl = await this._getPageUrl(); // Required because the waitForFunction can't return data.
2067
- if (/failed: timeout/i.test(e.message)) {
2190
+ if (/Waiting failed:/i.test(e.message) || /failed: timeout/i.test(e.message)) {
2068
2191
  throw new Error(`expected url to include ${urlPart}, but found ${currUrl}`);
2069
2192
  } else {
2070
2193
  throw e;
@@ -2088,7 +2211,7 @@ class Puppeteer extends Helper {
2088
2211
  return currUrl.indexOf(urlPart) > -1;
2089
2212
  }, { timeout: waitTimeout }, urlPart).catch(async (e) => {
2090
2213
  const currUrl = await this._getPageUrl(); // Required because the waitForFunction can't return data.
2091
- if (/failed: timeout/i.test(e.message)) {
2214
+ if (/Waiting failed/i.test(e.message) || /failed: timeout/i.test(e.message)) {
2092
2215
  throw new Error(`expected url to be ${urlPart}, but found ${currUrl}`);
2093
2216
  } else {
2094
2217
  throw e;
@@ -2220,9 +2343,9 @@ class Puppeteer extends Helper {
2220
2343
  }
2221
2344
 
2222
2345
  /**
2223
- * Waits for navigation to finish. By default takes configured `waitForNavigation` option.
2346
+ * Waits for navigation to finish. By default, takes configured `waitForNavigation` option.
2224
2347
  *
2225
- * 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)
2226
2349
  *
2227
2350
  * @param {*} opts
2228
2351
  */
@@ -2297,6 +2420,10 @@ async function findElements(matcher, locator) {
2297
2420
  if (locator.react) return findReact(matcher.executionContext(), locator);
2298
2421
  locator = new Locator(locator, 'css');
2299
2422
  if (!locator.isXPath()) return matcher.$$(locator.simplify());
2423
+ // puppeteer version < 19.4.0 is no longer supported. This one is backward support.
2424
+ if (puppeteer.default?.defaultBrowserRevision) {
2425
+ return matcher.$$(`xpath/${locator.value}`);
2426
+ }
2300
2427
  return matcher.$x(locator.value);
2301
2428
  }
2302
2429
 
@@ -2313,12 +2440,16 @@ async function proceedClick(locator, context = null, options = {}) {
2313
2440
  } else {
2314
2441
  assertElementExists(els, locator, 'Clickable element');
2315
2442
  }
2443
+
2444
+ highlightActiveElement.call(this, els[0], await this._getContext());
2445
+
2316
2446
  await els[0].click(options);
2317
2447
  const promises = [];
2318
2448
  if (options.waitForNavigation) {
2319
2449
  promises.push(this.waitForNavigation());
2320
2450
  }
2321
2451
  promises.push(this._waitForAction());
2452
+
2322
2453
  return Promise.all(promises);
2323
2454
  }
2324
2455
 
@@ -2370,7 +2501,7 @@ async function proceedSee(assertType, text, context, strict = false) {
2370
2501
  if (strict) {
2371
2502
  return allText.map(elText => equals(description)[assertType](text, elText));
2372
2503
  }
2373
- return stringIncludes(description)[assertType](text, allText.join(' | '));
2504
+ return stringIncludes(description)[assertType](normalizeSpacesInString(text), normalizeSpacesInString(allText.join(' | ')));
2374
2505
  }
2375
2506
 
2376
2507
  async function findCheckable(locator, context) {
@@ -2547,7 +2678,10 @@ async function elementSelected(element) {
2547
2678
 
2548
2679
  function isFrameLocator(locator) {
2549
2680
  locator = new Locator(locator);
2550
- if (locator.isFrame()) return locator.value;
2681
+ if (locator.isFrame()) {
2682
+ const _locator = new Locator(locator);
2683
+ return _locator.value;
2684
+ }
2551
2685
  return false;
2552
2686
  }
2553
2687
 
@@ -2663,3 +2797,9 @@ function getNormalizedKey(key) {
2663
2797
  }
2664
2798
  return normalizedKey;
2665
2799
  }
2800
+
2801
+ function highlightActiveElement(element, context) {
2802
+ if (this.options.highlightElement && global.debugMode) {
2803
+ highlightElement(element, context);
2804
+ }
2805
+ }