codeceptjs 3.5.5 → 3.5.7-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 (151) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/docs/bdd.md +11 -7
  3. package/docs/build/ApiDataFactory.js +2 -1
  4. package/docs/build/Appium.js +25 -23
  5. package/docs/build/Expect.js +422 -0
  6. package/docs/build/FileSystem.js +1 -1
  7. package/docs/build/Nightmare.js +53 -56
  8. package/docs/build/Playwright.js +209 -135
  9. package/docs/build/Protractor.js +66 -69
  10. package/docs/build/Puppeteer.js +83 -83
  11. package/docs/build/TestCafe.js +56 -55
  12. package/docs/build/WebDriver.js +85 -86
  13. package/docs/changelog.md +63 -0
  14. package/docs/commands.md +12 -0
  15. package/docs/helpers/Appium.md +50 -32
  16. package/docs/helpers/Expect.md +275 -0
  17. package/docs/helpers/FileSystem.md +1 -1
  18. package/docs/helpers/Nightmare.md +141 -94
  19. package/docs/helpers/Playwright.md +281 -212
  20. package/docs/helpers/Protractor.md +229 -169
  21. package/docs/helpers/Puppeteer.md +257 -186
  22. package/docs/helpers/TestCafe.md +201 -149
  23. package/docs/helpers/WebDriver.md +253 -179
  24. package/docs/mobile.md +17 -21
  25. package/docs/plugins.md +35 -1
  26. package/docs/webapi/amOnPage.mustache +1 -1
  27. package/docs/webapi/appendField.mustache +1 -1
  28. package/docs/webapi/attachFile.mustache +1 -1
  29. package/docs/webapi/blur.mustache +1 -0
  30. package/docs/webapi/checkOption.mustache +1 -1
  31. package/docs/webapi/clearCookie.mustache +1 -1
  32. package/docs/webapi/clearField.mustache +1 -1
  33. package/docs/webapi/click.mustache +1 -1
  34. package/docs/webapi/clickLink.mustache +1 -1
  35. package/docs/webapi/closeCurrentTab.mustache +1 -1
  36. package/docs/webapi/closeOtherTabs.mustache +1 -1
  37. package/docs/webapi/dontSee.mustache +1 -1
  38. package/docs/webapi/dontSeeCheckboxIsChecked.mustache +1 -1
  39. package/docs/webapi/dontSeeCookie.mustache +1 -1
  40. package/docs/webapi/dontSeeCurrentUrlEquals.mustache +1 -1
  41. package/docs/webapi/dontSeeElement.mustache +1 -1
  42. package/docs/webapi/dontSeeElementInDOM.mustache +1 -1
  43. package/docs/webapi/dontSeeInCurrentUrl.mustache +1 -1
  44. package/docs/webapi/dontSeeInField.mustache +1 -1
  45. package/docs/webapi/dontSeeInSource.mustache +1 -1
  46. package/docs/webapi/dontSeeInTitle.mustache +1 -1
  47. package/docs/webapi/doubleClick.mustache +1 -1
  48. package/docs/webapi/downloadFile.mustache +1 -1
  49. package/docs/webapi/dragAndDrop.mustache +1 -1
  50. package/docs/webapi/dragSlider.mustache +1 -1
  51. package/docs/webapi/executeAsyncScript.mustache +0 -2
  52. package/docs/webapi/executeScript.mustache +0 -2
  53. package/docs/webapi/fillField.mustache +1 -1
  54. package/docs/webapi/focus.mustache +1 -0
  55. package/docs/webapi/forceClick.mustache +1 -1
  56. package/docs/webapi/forceRightClick.mustache +1 -1
  57. package/docs/webapi/grabCookie.mustache +1 -1
  58. package/docs/webapi/grabDataFromPerformanceTiming.mustache +1 -1
  59. package/docs/webapi/moveCursorTo.mustache +1 -1
  60. package/docs/webapi/openNewTab.mustache +1 -1
  61. package/docs/webapi/pressKey.mustache +1 -1
  62. package/docs/webapi/pressKeyDown.mustache +1 -1
  63. package/docs/webapi/pressKeyUp.mustache +1 -1
  64. package/docs/webapi/pressKeyWithKeyNormalization.mustache +1 -1
  65. package/docs/webapi/refreshPage.mustache +1 -1
  66. package/docs/webapi/resizeWindow.mustache +1 -1
  67. package/docs/webapi/rightClick.mustache +1 -1
  68. package/docs/webapi/saveElementScreenshot.mustache +1 -1
  69. package/docs/webapi/saveScreenshot.mustache +1 -1
  70. package/docs/webapi/say.mustache +1 -1
  71. package/docs/webapi/scrollIntoView.mustache +1 -1
  72. package/docs/webapi/scrollPageToBottom.mustache +1 -1
  73. package/docs/webapi/scrollPageToTop.mustache +1 -1
  74. package/docs/webapi/scrollTo.mustache +1 -1
  75. package/docs/webapi/see.mustache +1 -1
  76. package/docs/webapi/seeAttributesOnElements.mustache +1 -1
  77. package/docs/webapi/seeCheckboxIsChecked.mustache +1 -1
  78. package/docs/webapi/seeCookie.mustache +1 -1
  79. package/docs/webapi/seeCssPropertiesOnElements.mustache +1 -1
  80. package/docs/webapi/seeCurrentUrlEquals.mustache +1 -1
  81. package/docs/webapi/seeElement.mustache +1 -1
  82. package/docs/webapi/seeElementInDOM.mustache +1 -1
  83. package/docs/webapi/seeInCurrentUrl.mustache +1 -1
  84. package/docs/webapi/seeInField.mustache +1 -1
  85. package/docs/webapi/seeInPopup.mustache +1 -1
  86. package/docs/webapi/seeInSource.mustache +1 -1
  87. package/docs/webapi/seeInTitle.mustache +1 -1
  88. package/docs/webapi/seeNumberOfElements.mustache +1 -1
  89. package/docs/webapi/seeNumberOfVisibleElements.mustache +1 -1
  90. package/docs/webapi/seeTextEquals.mustache +1 -1
  91. package/docs/webapi/seeTitleEquals.mustache +1 -1
  92. package/docs/webapi/selectOption.mustache +1 -1
  93. package/docs/webapi/setCookie.mustache +1 -1
  94. package/docs/webapi/setGeoLocation.mustache +1 -1
  95. package/docs/webapi/switchTo.mustache +1 -1
  96. package/docs/webapi/switchToNextTab.mustache +1 -1
  97. package/docs/webapi/switchToPreviousTab.mustache +1 -1
  98. package/docs/webapi/type.mustache +1 -1
  99. package/docs/webapi/uncheckOption.mustache +1 -1
  100. package/docs/webapi/wait.mustache +1 -1
  101. package/docs/webapi/waitForClickable.mustache +1 -1
  102. package/docs/webapi/waitForDetached.mustache +1 -1
  103. package/docs/webapi/waitForElement.mustache +1 -1
  104. package/docs/webapi/waitForEnabled.mustache +1 -1
  105. package/docs/webapi/waitForFunction.mustache +1 -1
  106. package/docs/webapi/waitForInvisible.mustache +1 -1
  107. package/docs/webapi/waitForText.mustache +1 -1
  108. package/docs/webapi/waitForValue.mustache +1 -1
  109. package/docs/webapi/waitForVisible.mustache +1 -1
  110. package/docs/webapi/waitInUrl.mustache +1 -1
  111. package/docs/webapi/waitNumberOfVisibleElements.mustache +1 -1
  112. package/docs/webapi/waitToHide.mustache +1 -1
  113. package/docs/webapi/waitUrlEquals.mustache +1 -1
  114. package/lib/ai.js +12 -3
  115. package/lib/cli.js +3 -1
  116. package/lib/codecept.js +3 -0
  117. package/lib/command/dryRun.js +2 -1
  118. package/lib/command/info.js +24 -0
  119. package/lib/command/run-workers.js +3 -2
  120. package/lib/command/run.js +3 -2
  121. package/lib/data/context.js +14 -6
  122. package/lib/helper/ApiDataFactory.js +2 -1
  123. package/lib/helper/Appium.js +7 -5
  124. package/lib/helper/Expect.js +422 -0
  125. package/lib/helper/FileSystem.js +1 -1
  126. package/lib/helper/Playwright.js +134 -64
  127. package/lib/helper/Puppeteer.js +6 -6
  128. package/lib/helper/WebDriver.js +4 -4
  129. package/lib/helper/scripts/highlightElement.js +1 -1
  130. package/lib/html.js +3 -3
  131. package/lib/interfaces/gherkin.js +21 -2
  132. package/lib/output.js +1 -1
  133. package/lib/pause.js +6 -5
  134. package/lib/plugin/autoLogin.js +35 -8
  135. package/lib/plugin/heal.js +40 -7
  136. package/lib/plugin/retryTo.js +0 -2
  137. package/lib/plugin/tryTo.js +0 -3
  138. package/lib/recorder.js +12 -5
  139. package/lib/session.js +1 -1
  140. package/package.json +24 -17
  141. package/translations/de-DE.js +5 -0
  142. package/translations/fr-FR.js +14 -1
  143. package/translations/it-IT.js +1 -0
  144. package/translations/ja-JP.js +5 -0
  145. package/translations/pl-PL.js +5 -0
  146. package/translations/pt-BR.js +1 -0
  147. package/translations/ru-RU.js +1 -0
  148. package/translations/zh-CN.js +5 -0
  149. package/translations/zh-TW.js +5 -0
  150. package/typings/promiseBasedTypes.d.ts +905 -863
  151. package/typings/types.d.ts +909 -850
@@ -47,7 +47,6 @@ const {
47
47
  setRestartStrategy, restartsSession, restartsContext, restartsBrowser,
48
48
  } = require('./extras/PlaywrightRestartOpts');
49
49
  const { createValueEngine, createDisabledEngine } = require('./extras/PlaywrightPropEngine');
50
- const { highlightElement } = require('./scripts/highlightElement');
51
50
 
52
51
  const pathSeparator = path.sep;
53
52
 
@@ -94,7 +93,7 @@ const pathSeparator = path.sep;
94
93
  * @prop {string[]} [ignoreLog] - An array with console message types that are not logged to debug log. Default value is `['warning', 'log']`. E.g. you can set `[]` to log all messages. See all possible [values](https://playwright.dev/docs/api/class-consolemessage#console-message-type).
95
94
  * @prop {boolean} [ignoreHTTPSErrors] - Allows access to untrustworthy pages, e.g. to a page with an expired certificate. Default value is `false`
96
95
  * @prop {boolean} [bypassCSP] - bypass Content Security Policy or CSP
97
- * @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false
96
+ * @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose).
98
97
  */
99
98
  const config = {};
100
99
 
@@ -827,8 +826,9 @@ class Playwright extends Helper {
827
826
 
828
827
  async _stopBrowser() {
829
828
  this.withinLocator = null;
830
- this._setPage(null);
829
+ await this._setPage(null);
831
830
  this.context = null;
831
+ this.frame = null;
832
832
  popupStore.clear();
833
833
  await this.browser.close();
834
834
  }
@@ -867,6 +867,7 @@ class Playwright extends Helper {
867
867
  this.withinLocator = null;
868
868
  this.context = await this.page;
869
869
  this.contextLocator = null;
870
+ this.frame = null;
870
871
  }
871
872
 
872
873
  _extractDataFromPerformanceTiming(timing, ...dataNames) {
@@ -941,14 +942,14 @@ class Playwright extends Helper {
941
942
  * Set headers for all next requests
942
943
  *
943
944
  * ```js
944
- * I.haveRequestHeaders({
945
+ * I.setPlaywrightRequestHeaders({
945
946
  * 'X-Sent-By': 'CodeceptJS',
946
947
  * });
947
948
  * ```
948
949
  *
949
950
  * @param {object} customHeaders headers to set
950
951
  */
951
- async haveRequestHeaders(customHeaders) {
952
+ async setPlaywrightRequestHeaders(customHeaders) {
952
953
  if (!customHeaders) {
953
954
  throw new Error('Cannot send empty headers.');
954
955
  }
@@ -1156,6 +1157,9 @@ class Playwright extends Helper {
1156
1157
  */
1157
1158
  async _locate(locator) {
1158
1159
  const context = await this.context || await this._getContext();
1160
+
1161
+ if (this.frame) return findElements(this.frame, locator);
1162
+
1159
1163
  return findElements(context, locator);
1160
1164
  }
1161
1165
 
@@ -1579,7 +1583,7 @@ class Playwright extends Helper {
1579
1583
 
1580
1584
  await el.clear();
1581
1585
 
1582
- highlightActiveElement.call(this, el, await this._getContext());
1586
+ await highlightActiveElement.call(this, el);
1583
1587
 
1584
1588
  await el.type(value.toString(), { delay: this.options.pressKeyDelay });
1585
1589
 
@@ -1609,7 +1613,7 @@ class Playwright extends Helper {
1609
1613
 
1610
1614
  const el = els[0];
1611
1615
 
1612
- highlightActiveElement.call(this, el, this.page);
1616
+ await highlightActiveElement.call(this, el);
1613
1617
 
1614
1618
  await el.clear();
1615
1619
 
@@ -1624,7 +1628,7 @@ class Playwright extends Helper {
1624
1628
  async appendField(field, value) {
1625
1629
  const els = await findFields.call(this, field);
1626
1630
  assertElementExists(els, field, 'Field');
1627
- highlightActiveElement.call(this, els[0], await this._getContext());
1631
+ await highlightActiveElement.call(this, els[0]);
1628
1632
  await els[0].press('End');
1629
1633
  await els[0].type(value.toString(), { delay: this.options.pressKeyDelay });
1630
1634
  return this._waitForAction();
@@ -1670,7 +1674,7 @@ class Playwright extends Helper {
1670
1674
  assertElementExists(els, select, 'Selectable field');
1671
1675
  const el = els[0];
1672
1676
 
1673
- highlightActiveElement.call(this, el, await this._getContext());
1677
+ await highlightActiveElement.call(this, el);
1674
1678
 
1675
1679
  if (!Array.isArray(option)) option = [option];
1676
1680
 
@@ -1882,11 +1886,11 @@ class Playwright extends Helper {
1882
1886
  * @returns {Promise<any>}
1883
1887
  */
1884
1888
  async executeScript(fn, arg) {
1885
- let context = this.page;
1886
- if (this.context && this.context.constructor.name === 'Frame') {
1887
- context = this.context; // switching to iframe context
1889
+ if (this.context && this.context.constructor.name === 'FrameLocator') {
1890
+ // switching to iframe context
1891
+ return this.context.locator(':root').evaluate(fn, arg);
1888
1892
  }
1889
- return context.evaluate.apply(context, [fn, arg]);
1893
+ return this.page.evaluate.apply(this.page, [fn, arg]);
1890
1894
  }
1891
1895
 
1892
1896
  /**
@@ -2361,10 +2365,11 @@ class Playwright extends Helper {
2361
2365
  locator = new Locator(locator, 'css');
2362
2366
 
2363
2367
  const context = await this._getContext();
2364
- const waiter = context.waitForSelector(buildLocatorString(locator), { timeout: waitTimeout, state: 'attached' });
2365
- return waiter.catch((err) => {
2366
- throw new Error(`element (${locator.toString()}) still not present on page after ${waitTimeout / 1000} sec\n${err.message}`);
2367
- });
2368
+ try {
2369
+ await context.locator(buildLocatorString(locator)).first().waitFor({ timeout: waitTimeout, state: 'attached' });
2370
+ } catch (e) {
2371
+ throw new Error(`element (${locator.toString()}) still not present on page after ${waitTimeout / 1000} sec\n${e.message}`);
2372
+ }
2368
2373
  }
2369
2374
 
2370
2375
  /**
@@ -2376,10 +2381,26 @@ class Playwright extends Helper {
2376
2381
  const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
2377
2382
  locator = new Locator(locator, 'css');
2378
2383
  const context = await this._getContext();
2379
- const waiter = context.waitForSelector(buildLocatorString(locator), { timeout: waitTimeout, state: 'visible' });
2380
- return waiter.catch((err) => {
2381
- throw new Error(`element (${locator.toString()}) still not visible after ${waitTimeout / 1000} sec\n${err.message}`);
2382
- });
2384
+ let count = 0;
2385
+
2386
+ // we have this as https://github.com/microsoft/playwright/issues/26829 is not yet implemented
2387
+ let waiter;
2388
+ if (this.frame) {
2389
+ do {
2390
+ waiter = await this.frame.locator(buildLocatorString(locator)).first().isVisible();
2391
+ await this.wait(1);
2392
+ count += 1000;
2393
+ if (waiter) break;
2394
+ } while (count <= waitTimeout);
2395
+
2396
+ if (!waiter) throw new Error(`element (${locator.toString()}) still not visible after ${waitTimeout / 1000} sec.`);
2397
+ }
2398
+
2399
+ try {
2400
+ await context.locator(buildLocatorString(locator)).first().waitFor({ timeout: waitTimeout, state: 'visible' });
2401
+ } catch (e) {
2402
+ throw new Error(`element (${locator.toString()}) still not visible after ${waitTimeout / 1000} sec\n${e.message}`);
2403
+ }
2383
2404
  }
2384
2405
 
2385
2406
  /**
@@ -2389,10 +2410,27 @@ class Playwright extends Helper {
2389
2410
  const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
2390
2411
  locator = new Locator(locator, 'css');
2391
2412
  const context = await this._getContext();
2392
- const waiter = context.waitForSelector(buildLocatorString(locator), { timeout: waitTimeout, state: 'hidden' });
2393
- return waiter.catch((err) => {
2394
- throw new Error(`element (${locator.toString()}) still visible after ${waitTimeout / 1000} sec\n${err.message}`);
2395
- });
2413
+ let waiter;
2414
+ let count = 0;
2415
+
2416
+ // we have this as https://github.com/microsoft/playwright/issues/26829 is not yet implemented
2417
+ if (this.frame) {
2418
+ do {
2419
+ waiter = await this.frame.locator(buildLocatorString(locator)).first().isHidden();
2420
+ await this.wait(1);
2421
+ count += 1000;
2422
+ if (waiter) break;
2423
+ } while (count <= waitTimeout);
2424
+
2425
+ if (!waiter) throw new Error(`element (${locator.toString()}) still visible after ${waitTimeout / 1000} sec.`);
2426
+ return;
2427
+ }
2428
+
2429
+ try {
2430
+ await context.locator(buildLocatorString(locator)).first().waitFor({ timeout: waitTimeout, state: 'hidden' });
2431
+ } catch (e) {
2432
+ throw new Error(`element (${locator.toString()}) still visible after ${waitTimeout / 1000} sec\n${e.message}`);
2433
+ }
2396
2434
  }
2397
2435
 
2398
2436
  /**
@@ -2402,13 +2440,29 @@ class Playwright extends Helper {
2402
2440
  const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
2403
2441
  locator = new Locator(locator, 'css');
2404
2442
  const context = await this._getContext();
2405
- return context.waitForSelector(buildLocatorString(locator), { timeout: waitTimeout, state: 'hidden' }).catch((err) => {
2443
+ let waiter;
2444
+ let count = 0;
2445
+
2446
+ // we have this as https://github.com/microsoft/playwright/issues/26829 is not yet implemented
2447
+ if (this.frame) {
2448
+ do {
2449
+ waiter = await this.frame.locator(buildLocatorString(locator)).first().isHidden();
2450
+ await this.wait(1);
2451
+ count += 1000;
2452
+ if (waiter) break;
2453
+ } while (count <= waitTimeout);
2454
+
2455
+ if (!waiter) throw new Error(`element (${locator.toString()}) still not hidden after ${waitTimeout / 1000} sec.`);
2456
+ return;
2457
+ }
2458
+
2459
+ return context.locator(buildLocatorString(locator)).first().waitFor({ timeout: waitTimeout, state: 'hidden' }).catch((err) => {
2406
2460
  throw new Error(`element (${locator.toString()}) still not hidden after ${waitTimeout / 1000} sec\n${err.message}`);
2407
2461
  });
2408
2462
  }
2409
2463
 
2410
2464
  async _getContext() {
2411
- if (this.context && this.context.constructor.name === 'Frame') {
2465
+ if (this.context && this.context.constructor.name === 'FrameLocator') {
2412
2466
  return this.context;
2413
2467
  }
2414
2468
  return this.page;
@@ -2469,7 +2523,12 @@ class Playwright extends Helper {
2469
2523
  if (context) {
2470
2524
  const locator = new Locator(context, 'css');
2471
2525
  if (!locator.isXPath()) {
2472
- waiter = contextObject.waitForSelector(`${locator.isCustom() ? `${locator.type}=${locator.value}` : locator.simplify()} >> text=${text}`, { timeout: waitTimeout, state: 'visible' });
2526
+ try {
2527
+ await contextObject.locator(`${locator.isCustom() ? `${locator.type}=${locator.value}` : locator.simplify()} >> text=${text}`).first().waitFor({ timeout: waitTimeout, state: 'visible' });
2528
+ } catch (e) {
2529
+ console.log(e);
2530
+ throw new Error(`Text "${text}" was not found on page after ${waitTimeout / 1000} sec\n${e.message}`);
2531
+ }
2473
2532
  }
2474
2533
 
2475
2534
  if (locator.isXPath()) {
@@ -2481,11 +2540,18 @@ class Playwright extends Helper {
2481
2540
  }, [locator.value, text, $XPath.toString()], { timeout: waitTimeout });
2482
2541
  }
2483
2542
  } else {
2484
- waiter = contextObject.waitForFunction(text => document.body && document.body.innerText.indexOf(text) > -1, text, { timeout: waitTimeout });
2543
+ // we have this as https://github.com/microsoft/playwright/issues/26829 is not yet implemented
2544
+ // eslint-disable-next-line no-lonely-if
2545
+ const _contextObject = this.frame ? this.frame : contextObject;
2546
+ let count = 0;
2547
+ do {
2548
+ waiter = await _contextObject.locator(`:has-text('${text}')`).first().isVisible();
2549
+ await this.wait(1);
2550
+ count += 1000;
2551
+ } while (count <= waitTimeout);
2552
+
2553
+ if (!waiter) throw new Error(`Text "${text}" was not found on page after ${waitTimeout / 1000} sec`);
2485
2554
  }
2486
- return waiter.catch((err) => {
2487
- throw new Error(`Text "${text}" was not found on page after ${waitTimeout / 1000} sec\n${err.message}`);
2488
- });
2489
2555
  }
2490
2556
 
2491
2557
  /**
@@ -2535,37 +2601,37 @@ class Playwright extends Helper {
2535
2601
  }
2536
2602
 
2537
2603
  if (locator >= 0 && locator < childFrames.length) {
2538
- this.context = childFrames[locator];
2604
+ this.context = await this.page.frameLocator('iframe').nth(locator);
2539
2605
  this.contextLocator = locator;
2540
2606
  } else {
2541
2607
  throw new Error('Element #invalidIframeSelector was not found by text|CSS|XPath');
2542
2608
  }
2543
2609
  return;
2544
2610
  }
2545
- let contentFrame;
2546
2611
 
2547
2612
  if (!locator) {
2548
- this.context = await this.page.frames()[0];
2613
+ this.context = this.page;
2549
2614
  this.contextLocator = null;
2615
+ this.frame = null;
2550
2616
  return;
2551
2617
  }
2552
2618
 
2553
2619
  // iframe by selector
2554
- const els = await this._locate(locator);
2555
- if (!els[0]) {
2556
- throw new Error(`Element ${JSON.stringify(locator)} was not found by text|CSS|XPath`);
2620
+ locator = buildLocatorString(new Locator(locator, 'css'));
2621
+ const frame = await this._locateElement(locator);
2622
+
2623
+ if (!frame) {
2624
+ throw new Error(`Frame ${JSON.stringify(locator)} was not found by text|CSS|XPath`);
2557
2625
  }
2558
2626
 
2559
- // get content of the first iframe
2560
- locator = new Locator(locator, 'css');
2561
- if ((locator.frame && locator.frame === 'iframe') || locator.value.toLowerCase() === 'iframe') {
2562
- contentFrame = await this.page.frames()[1];
2563
- // get content of the iframe using its name
2564
- } else if (locator.value.toLowerCase().includes('name=')) {
2565
- const frameName = locator.value.split('=')[1].replace(/"/g, '').replaceAll(/]/g, '');
2566
- contentFrame = await this.page.frame(frameName);
2627
+ if (this.frame) {
2628
+ this.frame = await this.frame.frameLocator(locator);
2629
+ } else {
2630
+ this.frame = await this.page.frameLocator(locator);
2567
2631
  }
2568
2632
 
2633
+ const contentFrame = this.frame;
2634
+
2569
2635
  if (contentFrame) {
2570
2636
  this.context = contentFrame;
2571
2637
  this.contextLocator = null;
@@ -2644,17 +2710,21 @@ class Playwright extends Helper {
2644
2710
  let waiter;
2645
2711
  const context = await this._getContext();
2646
2712
  if (!locator.isXPath()) {
2647
- waiter = context.waitForSelector(`${locator.isCustom() ? `${locator.type}=${locator.value}` : locator.simplify()}`, { timeout: waitTimeout, state: 'detached' });
2713
+ try {
2714
+ await context.locator(`${locator.isCustom() ? `${locator.type}=${locator.value}` : locator.simplify()}`).first().waitFor({ timeout: waitTimeout, state: 'detached' });
2715
+ } catch (e) {
2716
+ throw new Error(`element (${locator.toString()}) still on page after ${waitTimeout / 1000} sec\n${e.message}`);
2717
+ }
2648
2718
  } else {
2649
2719
  const visibleFn = function ([locator, $XPath]) {
2650
2720
  eval($XPath); // eslint-disable-line no-eval
2651
2721
  return $XPath(null, locator).length === 0;
2652
2722
  };
2653
2723
  waiter = context.waitForFunction(visibleFn, [locator.value, $XPath.toString()], { timeout: waitTimeout });
2724
+ return waiter.catch((err) => {
2725
+ throw new Error(`element (${locator.toString()}) still on page after ${waitTimeout / 1000} sec\n${err.message}`);
2726
+ });
2654
2727
  }
2655
- return waiter.catch((err) => {
2656
- throw new Error(`element (${locator.toString()}) still on page after ${waitTimeout / 1000} sec\n${err.message}`);
2657
- });
2658
2728
  }
2659
2729
 
2660
2730
  async _waitForAction() {
@@ -3260,7 +3330,7 @@ async function findElement(matcher, locator) {
3260
3330
  if (locator.react) return findReact(matcher, locator);
3261
3331
  locator = new Locator(locator, 'css');
3262
3332
 
3263
- return matcher.locator(buildLocatorString(locator));
3333
+ return matcher.locator(buildLocatorString(locator)).first();
3264
3334
  }
3265
3335
 
3266
3336
  async function getVisibleElements(elements) {
@@ -3290,7 +3360,7 @@ async function proceedClick(locator, context = null, options = {}) {
3290
3360
  assertElementExists(els, locator, 'Clickable element');
3291
3361
  }
3292
3362
 
3293
- highlightActiveElement.call(this, els[0], await this._getContext());
3363
+ await highlightActiveElement.call(this, els[0]);
3294
3364
 
3295
3365
  /*
3296
3366
  using the force true options itself but instead dispatching a click
@@ -3340,13 +3410,10 @@ async function proceedSee(assertType, text, context, strict = false) {
3340
3410
  let allText;
3341
3411
 
3342
3412
  if (!context) {
3343
- let el = await this.context;
3344
- if (el && !el.getProperty) {
3345
- // Fallback to body
3346
- el = await this.page.$('body');
3347
- }
3413
+ const el = await this.context;
3414
+
3415
+ allText = el.constructor.name ? [await el.locator('body').innerText()] : [await el.innerText()];
3348
3416
 
3349
- allText = [await el.innerText()];
3350
3417
  description = 'web application';
3351
3418
  } else {
3352
3419
  const locator = new Locator(context, 'css');
@@ -3519,8 +3586,7 @@ async function elementSelected(element) {
3519
3586
  function isFrameLocator(locator) {
3520
3587
  locator = new Locator(locator);
3521
3588
  if (locator.isFrame()) {
3522
- const _locator = new Locator(locator.value);
3523
- return _locator.value;
3589
+ return locator.value;
3524
3590
  }
3525
3591
  return false;
3526
3592
  }
@@ -3716,10 +3782,14 @@ async function saveTraceForContext(context, name) {
3716
3782
  return fileName;
3717
3783
  }
3718
3784
 
3719
- function highlightActiveElement(element, context) {
3720
- if (!this.options.highlightElement && !store.debugMode) return;
3721
-
3722
- highlightElement(element, context);
3785
+ async function highlightActiveElement(element) {
3786
+ if (this.options.highlightElement && global.debugMode) {
3787
+ await element.evaluate(el => {
3788
+ const prevStyle = el.style.boxShadow;
3789
+ el.style.boxShadow = '0px 0px 4px 3px rgba(255, 0, 0, 0.7)';
3790
+ setTimeout(() => el.style.boxShadow = prevStyle, 2000);
3791
+ });
3792
+ }
3723
3793
  }
3724
3794
 
3725
3795
  const createAdvancedTestResults = (url, dataToCheck, requests) => {
@@ -69,7 +69,7 @@ const consoleLogStore = new Console();
69
69
  * @prop {boolean} [manualStart=false] - do not start browser before a test, start it manually inside a helper with `this.helpers["Puppeteer"]._startBrowser()`.
70
70
  * @prop {string} [browser=chrome] - can be changed to `firefox` when using [puppeteer-firefox](https://codecept.io/helpers/Puppeteer-firefox).
71
71
  * @prop {object} [chrome] - pass additional [Puppeteer run options](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions).
72
- * @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false
72
+ * @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose).
73
73
  */
74
74
  const config = {};
75
75
 
@@ -682,14 +682,14 @@ class Puppeteer extends Helper {
682
682
  * Set headers for all next requests
683
683
  *
684
684
  * ```js
685
- * I.haveRequestHeaders({
685
+ * I.setPuppeteerRequestHeaders({
686
686
  * 'X-Sent-By': 'CodeceptJS',
687
687
  * });
688
688
  * ```
689
689
  *
690
690
  * @param {object} customHeaders headers to set
691
691
  */
692
- async haveRequestHeaders(customHeaders) {
692
+ async setPuppeteerRequestHeaders(customHeaders) {
693
693
  if (!customHeaders) {
694
694
  throw new Error('Cannot send empty headers.');
695
695
  }
@@ -2727,7 +2727,7 @@ function getNormalizedKey(key) {
2727
2727
  }
2728
2728
 
2729
2729
  function highlightActiveElement(element, context) {
2730
- if (!this.options.highlightElement && !store.debugMode) return;
2731
-
2732
- highlightElement(element, context);
2730
+ if (this.options.highlightElement && global.debugMode) {
2731
+ highlightElement(element, context);
2732
+ }
2733
2733
  }
@@ -62,7 +62,7 @@ const webRoot = 'body';
62
62
  * @prop {object} [desiredCapabilities] Selenium's [desired capabilities](https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities).
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
- * @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false
65
+ * @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose).
66
66
  */
67
67
  const config = {};
68
68
 
@@ -2918,9 +2918,9 @@ function isModifierKey(key) {
2918
2918
  }
2919
2919
 
2920
2920
  function highlightActiveElement(element) {
2921
- if (!this.options.highlightElement && !store.debugMode) return;
2922
-
2923
- highlightElement(element, this.browser);
2921
+ if (this.options.highlightElement && global.debugMode) {
2922
+ highlightElement(element, this.browser);
2923
+ }
2924
2924
  }
2925
2925
 
2926
2926
  function prepareLocateFn(context) {
@@ -7,7 +7,7 @@ module.exports.highlightElement = (element, context) => {
7
7
  };
8
8
 
9
9
  try {
10
- // Playwright, Puppeteer
10
+ // Puppeteer
11
11
  context.evaluate(clientSideHighlightFn, element).catch(err => console.error(err));
12
12
  } catch (e) {
13
13
  // WebDriver
package/lib/html.js CHANGED
@@ -1,7 +1,7 @@
1
1
  const { parse, serialize } = require('parse5');
2
- const { minify } = require('html-minifier');
2
+ const { minify } = require('html-minifier-terser');
3
3
 
4
- function minifyHtml(html) {
4
+ async function minifyHtml(html) {
5
5
  return minify(html, {
6
6
  collapseWhitespace: true,
7
7
  removeComments: true,
@@ -11,7 +11,7 @@ function minifyHtml(html) {
11
11
  removeStyleLinkTypeAttributes: true,
12
12
  collapseBooleanAttributes: true,
13
13
  useShortDoctype: true,
14
- }).toString();
14
+ });
15
15
  }
16
16
 
17
17
  const defaultHtmlOpts = {
@@ -18,6 +18,11 @@ parser.stopAtFirstError = false;
18
18
 
19
19
  module.exports = (text, file) => {
20
20
  const ast = parser.parse(text);
21
+ let currentLanguage;
22
+
23
+ if (ast.feature) {
24
+ currentLanguage = getTranslation(ast.feature.language);
25
+ }
21
26
 
22
27
  if (!ast.feature) {
23
28
  throw new Error(`No 'Features' available in Gherkin '${file}' provided!`);
@@ -96,7 +101,7 @@ module.exports = (text, file) => {
96
101
  suite.beforeEach('Before', scenario.injected(async () => runSteps(child.background.steps), suite, 'before'));
97
102
  continue;
98
103
  }
99
- if (child.scenario && child.scenario.keyword === 'Scenario Outline') {
104
+ if (child.scenario && (currentLanguage ? child.scenario.keyword === currentLanguage.contexts.ScenarioOutline : child.scenario.keyword === 'Scenario Outline')) {
100
105
  for (const examples of child.scenario.examples) {
101
106
  const fields = examples.tableHeader.cells.map(c => c.value);
102
107
  for (const example of examples.tableBody) {
@@ -141,7 +146,7 @@ function transformTable(table) {
141
146
  let str = '';
142
147
  for (const id in table.rows) {
143
148
  const cells = table.rows[id].cells;
144
- str += cells.map(c => c.value).map(c => c.slice(0, 15).padEnd(15)).join(' | ');
149
+ str += cells.map(c => c.value).map(c => c.padEnd(15)).join(' | ');
145
150
  str += '\n';
146
151
  }
147
152
  return str;
@@ -162,3 +167,17 @@ function addExampleInTable(exampleSteps, placeholders) {
162
167
  }
163
168
  return steps;
164
169
  }
170
+
171
+ function getTranslation(language) {
172
+ const translations = Object.keys(require('../../translations'));
173
+
174
+ for (const availableTranslation of translations) {
175
+ if (!language) {
176
+ break;
177
+ }
178
+
179
+ if (availableTranslation.includes(language)) {
180
+ return require('../../translations')[availableTranslation];
181
+ }
182
+ }
183
+ }
package/lib/output.js CHANGED
@@ -106,7 +106,7 @@ module.exports = {
106
106
  if (!step) return;
107
107
  // Avoid to print non-gherkin steps, when gherkin is running for --steps mode
108
108
  if (outputLevel === 1) {
109
- if (step.hasBDDAncestor()) {
109
+ if (typeof step === 'object' && step.hasBDDAncestor()) {
110
110
  return;
111
111
  }
112
112
  }
package/lib/pause.js CHANGED
@@ -18,8 +18,7 @@ let nextStep;
18
18
  let finish;
19
19
  let next;
20
20
  let registeredVariables = {};
21
- const aiAssistant = new AiAssistant();
22
-
21
+ let aiAssistant;
23
22
  /**
24
23
  * Pauses test execution and starts interactive shell
25
24
  * @param {Object<string, *>} [passedObject]
@@ -45,6 +44,8 @@ function pauseSession(passedObject = {}) {
45
44
  let vars = Object.keys(registeredVariables).join(', ');
46
45
  if (vars) vars = `(vars: ${vars})`;
47
46
 
47
+ aiAssistant = AiAssistant.getInstance();
48
+
48
49
  output.print(colors.yellow(' Interactive shell started'));
49
50
  output.print(colors.yellow(' Use JavaScript syntax to try steps in action'));
50
51
  output.print(colors.yellow(` - Press ${colors.bold('ENTER')} to run the next step`));
@@ -78,7 +79,6 @@ async function parseInput(cmd) {
78
79
  rl.pause();
79
80
  next = false;
80
81
  recorder.session.start('pause');
81
- store.debugMode = false;
82
82
  if (cmd === '') next = true;
83
83
  if (!cmd || cmd === 'resume' || cmd === 'exit') {
84
84
  finish();
@@ -98,13 +98,14 @@ async function parseInput(cmd) {
98
98
  return cmd;
99
99
  };
100
100
 
101
- store.debugMode = true;
102
101
  let isCustomCommand = false;
103
102
  let lastError = null;
104
103
  let isAiCommand = false;
105
104
  let $res;
106
105
  try {
106
+ // eslint-disable-next-line
107
107
  const locate = global.locate; // enable locate in this context
108
+ // eslint-disable-next-line
108
109
  const I = container.support('I');
109
110
  if (cmd.trim().startsWith('=>')) {
110
111
  isCustomCommand = true;
@@ -117,7 +118,7 @@ async function parseInput(cmd) {
117
118
  executeCommand = executeCommand.then(async () => {
118
119
  try {
119
120
  const html = await res;
120
- aiAssistant.setHtmlContext(html);
121
+ await aiAssistant.setHtmlContext(html);
121
122
  } catch (err) {
122
123
  output.print(output.styles.error(' ERROR '), 'Can\'t get HTML context', err.stack);
123
124
  return;