codeceptjs 3.6.0-beta.1.ai-healers → 3.6.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 (130) hide show
  1. package/README.md +2 -2
  2. package/bin/codecept.js +2 -1
  3. package/docs/webapi/dontSeeTraffic.mustache +13 -0
  4. package/docs/webapi/flushNetworkTraffics.mustache +5 -0
  5. package/docs/webapi/grabRecordedNetworkTraffics.mustache +10 -0
  6. package/docs/webapi/seeTraffic.mustache +36 -0
  7. package/docs/webapi/startRecordingTraffic.mustache +8 -0
  8. package/docs/webapi/startRecordingWebSocketMessages.mustache +8 -0
  9. package/docs/webapi/stopRecordingTraffic.mustache +5 -0
  10. package/docs/webapi/stopRecordingWebSocketMessages.mustache +7 -0
  11. package/docs/webapi/waitForCookie.mustache +9 -0
  12. package/lib/actor.js +6 -3
  13. package/lib/command/dryRun.js +44 -13
  14. package/lib/helper/Appium.js +36 -12
  15. package/lib/helper/Expect.js +11 -8
  16. package/lib/helper/JSONResponse.js +8 -8
  17. package/lib/helper/MockServer.js +221 -0
  18. package/lib/helper/Playwright.js +107 -371
  19. package/lib/helper/Puppeteer.js +404 -71
  20. package/lib/helper/REST.js +4 -1
  21. package/lib/helper/WebDriver.js +189 -13
  22. package/lib/helper/errors/ElementAssertion.js +38 -0
  23. package/lib/helper/extras/PlaywrightReactVueLocator.js +6 -1
  24. package/lib/helper/network/actions.js +123 -0
  25. package/lib/helper/network/utils.js +187 -0
  26. package/lib/locator.js +36 -5
  27. package/lib/pause.js +4 -9
  28. package/lib/plugin/coverage.js +112 -99
  29. package/lib/step.js +3 -1
  30. package/package.json +49 -38
  31. package/typings/index.d.ts +19 -2
  32. package/typings/promiseBasedTypes.d.ts +505 -41
  33. package/typings/types.d.ts +531 -43
  34. package/docs/advanced.md +0 -351
  35. package/docs/ai.md +0 -365
  36. package/docs/api.md +0 -323
  37. package/docs/basics.md +0 -979
  38. package/docs/bdd.md +0 -539
  39. package/docs/best.md +0 -237
  40. package/docs/books.md +0 -37
  41. package/docs/bootstrap.md +0 -135
  42. package/docs/build/AI.js +0 -124
  43. package/docs/build/ApiDataFactory.js +0 -410
  44. package/docs/build/Appium.js +0 -2027
  45. package/docs/build/Expect.js +0 -422
  46. package/docs/build/FileSystem.js +0 -228
  47. package/docs/build/GraphQL.js +0 -229
  48. package/docs/build/GraphQLDataFactory.js +0 -309
  49. package/docs/build/JSONResponse.js +0 -338
  50. package/docs/build/Mochawesome.js +0 -71
  51. package/docs/build/Nightmare.js +0 -2152
  52. package/docs/build/OpenAI.js +0 -126
  53. package/docs/build/Playwright.js +0 -5110
  54. package/docs/build/Protractor.js +0 -2706
  55. package/docs/build/Puppeteer.js +0 -3905
  56. package/docs/build/REST.js +0 -344
  57. package/docs/build/TestCafe.js +0 -2125
  58. package/docs/build/WebDriver.js +0 -4240
  59. package/docs/changelog.md +0 -2572
  60. package/docs/commands.md +0 -266
  61. package/docs/community-helpers.md +0 -58
  62. package/docs/configuration.md +0 -157
  63. package/docs/continuous-integration.md +0 -22
  64. package/docs/custom-helpers.md +0 -306
  65. package/docs/data.md +0 -379
  66. package/docs/detox.md +0 -235
  67. package/docs/docker.md +0 -136
  68. package/docs/email.md +0 -183
  69. package/docs/examples.md +0 -149
  70. package/docs/heal.md +0 -186
  71. package/docs/helpers/ApiDataFactory.md +0 -266
  72. package/docs/helpers/Appium.md +0 -1374
  73. package/docs/helpers/Detox.md +0 -586
  74. package/docs/helpers/Expect.md +0 -275
  75. package/docs/helpers/FileSystem.md +0 -152
  76. package/docs/helpers/GraphQL.md +0 -151
  77. package/docs/helpers/GraphQLDataFactory.md +0 -226
  78. package/docs/helpers/JSONResponse.md +0 -254
  79. package/docs/helpers/Mochawesome.md +0 -8
  80. package/docs/helpers/MockRequest.md +0 -377
  81. package/docs/helpers/Nightmare.md +0 -1305
  82. package/docs/helpers/OpenAI.md +0 -70
  83. package/docs/helpers/Playwright.md +0 -2759
  84. package/docs/helpers/Polly.md +0 -44
  85. package/docs/helpers/Protractor.md +0 -1769
  86. package/docs/helpers/Puppeteer-firefox.md +0 -86
  87. package/docs/helpers/Puppeteer.md +0 -2317
  88. package/docs/helpers/REST.md +0 -218
  89. package/docs/helpers/TestCafe.md +0 -1321
  90. package/docs/helpers/WebDriver.md +0 -2547
  91. package/docs/hooks.md +0 -340
  92. package/docs/index.md +0 -111
  93. package/docs/installation.md +0 -75
  94. package/docs/internal-api.md +0 -266
  95. package/docs/locators.md +0 -339
  96. package/docs/mobile-react-native-locators.md +0 -67
  97. package/docs/mobile.md +0 -338
  98. package/docs/pageobjects.md +0 -291
  99. package/docs/parallel.md +0 -400
  100. package/docs/playwright.md +0 -632
  101. package/docs/plugins.md +0 -1247
  102. package/docs/puppeteer.md +0 -316
  103. package/docs/quickstart.md +0 -162
  104. package/docs/react.md +0 -70
  105. package/docs/reports.md +0 -392
  106. package/docs/secrets.md +0 -36
  107. package/docs/shadow.md +0 -68
  108. package/docs/shared/keys.mustache +0 -31
  109. package/docs/shared/react.mustache +0 -1
  110. package/docs/testcafe.md +0 -174
  111. package/docs/translation.md +0 -247
  112. package/docs/tutorial.md +0 -271
  113. package/docs/typescript.md +0 -180
  114. package/docs/ui.md +0 -59
  115. package/docs/videos.md +0 -28
  116. package/docs/visual.md +0 -202
  117. package/docs/vue.md +0 -143
  118. package/docs/webdriver.md +0 -701
  119. package/docs/wiki/Books-&-Posts.md +0 -27
  120. package/docs/wiki/Community-Helpers-&-Plugins.md +0 -53
  121. package/docs/wiki/Converting-Playwright-to-Istanbul-Coverage.md +0 -61
  122. package/docs/wiki/Examples.md +0 -145
  123. package/docs/wiki/Google-Summer-of-Code-(GSoC)-2020.md +0 -68
  124. package/docs/wiki/Home.md +0 -16
  125. package/docs/wiki/Migration-to-Appium-v2---CodeceptJS.md +0 -83
  126. package/docs/wiki/Release-Process.md +0 -24
  127. package/docs/wiki/Roadmap.md +0 -23
  128. package/docs/wiki/Tests.md +0 -1393
  129. package/docs/wiki/Upgrading-to-CodeceptJS-3.md +0 -153
  130. package/docs/wiki/Videos.md +0 -19
@@ -5,6 +5,7 @@ const path = require('path');
5
5
 
6
6
  const Helper = require('@codeceptjs/helper');
7
7
  const { v4: uuidv4 } = require('uuid');
8
+ const promiseRetry = require('promise-retry');
8
9
  const Locator = require('../locator');
9
10
  const recorder = require('../recorder');
10
11
  const store = require('../store');
@@ -35,10 +36,14 @@ const ElementNotFound = require('./errors/ElementNotFound');
35
36
  const RemoteBrowserConnectionRefused = require('./errors/RemoteBrowserConnectionRefused');
36
37
  const Popup = require('./extras/Popup');
37
38
  const Console = require('./extras/Console');
38
- const findReact = require('./extras/React');
39
39
  const { highlightElement } = require('./scripts/highlightElement');
40
40
  const { blurElement } = require('./scripts/blurElement');
41
- const { focusElement } = require('./scripts/focusElement');
41
+ const {
42
+ dontSeeElementError, seeElementError, dontSeeElementInDOMError, seeElementInDOMError,
43
+ } = require('./errors/ElementAssertion');
44
+ const {
45
+ dontSeeTraffic, seeTraffic, grabRecordedNetworkTraffics, stopRecordingTraffic, flushNetworkTraffics,
46
+ } = require('./network/actions');
42
47
 
43
48
  let puppeteer;
44
49
  let perfTiming;
@@ -74,7 +79,7 @@ const consoleLogStore = new Console();
74
79
  * @prop {string} [browser=chrome] - can be changed to `firefox` when using [puppeteer-firefox](https://codecept.io/helpers/Puppeteer-firefox).
75
80
  * @prop {object} [chrome] - pass additional [Puppeteer run options](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions).
76
81
  * @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose).
77
- */
82
+ */
78
83
  const config = {};
79
84
 
80
85
  /**
@@ -222,6 +227,17 @@ class Puppeteer extends Helper {
222
227
  this.sessionPages = {};
223
228
  this.activeSessionName = '';
224
229
 
230
+ // for network stuff
231
+ this.requests = [];
232
+ this.recording = false;
233
+ this.recordedAtLeastOnce = false;
234
+
235
+ // for websocket messages
236
+ this.webSocketMessages = [];
237
+ this.recordingWebSocketMessages = false;
238
+ this.recordedWebSocketMessagesAtLeastOnce = false;
239
+ this.cdpSession = null;
240
+
225
241
  // override defaults with config
226
242
  this._setConfig(config);
227
243
  }
@@ -365,7 +381,7 @@ class Puppeteer extends Helper {
365
381
  this.debugSection('Incognito Tab', 'opened');
366
382
  this.activeSessionName = name;
367
383
 
368
- const bc = await this.browser.createIncognitoBrowserContext();
384
+ const bc = await this.browser.createBrowserContext();
369
385
  await bc.newPage();
370
386
 
371
387
  // Create a new page inside context.
@@ -482,7 +498,7 @@ class Puppeteer extends Helper {
482
498
  if (!page) return;
483
499
  page.setDefaultNavigationTimeout(this.options.getPageTimeout);
484
500
  this.context = await this.page.$('body');
485
- if (this.config.browser === 'chrome') {
501
+ if (this.options.browser === 'chrome') {
486
502
  await page.bringToFront();
487
503
  }
488
504
  }
@@ -595,14 +611,7 @@ class Puppeteer extends Helper {
595
611
  }
596
612
 
597
613
  async _evaluateHandeInContext(...args) {
598
- let context = await this._getContext();
599
-
600
- if (context.constructor.name === 'Frame') {
601
- // Currently there is no evalateHandle for the Frame object
602
- // https://github.com/GoogleChrome/puppeteer/issues/1051
603
- context = await context.executionContext();
604
- }
605
-
614
+ const context = await this._getContext();
606
615
  return context.evaluateHandle(...args);
607
616
  }
608
617
 
@@ -654,9 +663,9 @@ class Puppeteer extends Helper {
654
663
  url = this.options.url + url;
655
664
  }
656
665
 
657
- if (this.config.basicAuth && (this.isAuthenticated !== true)) {
666
+ if (this.options.basicAuth && (this.isAuthenticated !== true)) {
658
667
  if (url.includes(this.options.url)) {
659
- await this.page.authenticate(this.config.basicAuth);
668
+ await this.page.authenticate(this.options.basicAuth);
660
669
  this.isAuthenticated = true;
661
670
  }
662
671
  }
@@ -880,7 +889,8 @@ class Puppeteer extends Helper {
880
889
  * {{ react }}
881
890
  */
882
891
  async _locate(locator) {
883
- return findElements(await this.context, locator);
892
+ const context = await this.context;
893
+ return findElements.call(this, context, locator);
884
894
  }
885
895
 
886
896
  /**
@@ -906,7 +916,7 @@ class Puppeteer extends Helper {
906
916
  * ```
907
917
  */
908
918
  async _locateClickable(locator) {
909
- const context = await this._getContext();
919
+ const context = await this.context;
910
920
  return findClickable.call(this, context, locator);
911
921
  }
912
922
 
@@ -1038,8 +1048,11 @@ class Puppeteer extends Helper {
1038
1048
  els = (await Promise.all(els.map(el => el.boundingBox() && el))).filter(v => v);
1039
1049
  // Puppeteer visibility was ignored? | Remove when Puppeteer is fixed
1040
1050
  els = await Promise.all(els.map(async el => (await el.evaluate(node => window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none')) && el));
1041
-
1042
- return empty('visible elements').negate(els.filter(v => v).fill('ELEMENT'));
1051
+ try {
1052
+ return empty('visible elements').negate(els.filter(v => v).fill('ELEMENT'));
1053
+ } catch (e) {
1054
+ dontSeeElementError(locator);
1055
+ }
1043
1056
  }
1044
1057
 
1045
1058
  /**
@@ -1051,8 +1064,11 @@ class Puppeteer extends Helper {
1051
1064
  els = (await Promise.all(els.map(el => el.boundingBox() && el))).filter(v => v);
1052
1065
  // Puppeteer visibility was ignored? | Remove when Puppeteer is fixed
1053
1066
  els = await Promise.all(els.map(async el => (await el.evaluate(node => window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none')) && el));
1054
-
1055
- return empty('visible elements').assert(els.filter(v => v).fill('ELEMENT'));
1067
+ try {
1068
+ return empty('visible elements').assert(els.filter(v => v).fill('ELEMENT'));
1069
+ } catch (e) {
1070
+ seeElementError(locator);
1071
+ }
1056
1072
  }
1057
1073
 
1058
1074
  /**
@@ -1060,7 +1076,11 @@ class Puppeteer extends Helper {
1060
1076
  */
1061
1077
  async seeElementInDOM(locator) {
1062
1078
  const els = await this._locate(locator);
1063
- return empty('elements on page').negate(els.filter(v => v).fill('ELEMENT'));
1079
+ try {
1080
+ return empty('elements on page').negate(els.filter(v => v).fill('ELEMENT'));
1081
+ } catch (e) {
1082
+ dontSeeElementInDOMError(locator);
1083
+ }
1064
1084
  }
1065
1085
 
1066
1086
  /**
@@ -1068,7 +1088,11 @@ class Puppeteer extends Helper {
1068
1088
  */
1069
1089
  async dontSeeElementInDOM(locator) {
1070
1090
  const els = await this._locate(locator);
1071
- return empty('elements on a page').assert(els.filter(v => v).fill('ELEMENT'));
1091
+ try {
1092
+ return empty('elements on a page').assert(els.filter(v => v).fill('ELEMENT'));
1093
+ } catch (e) {
1094
+ seeElementInDOMError(locator);
1095
+ }
1072
1096
  }
1073
1097
 
1074
1098
  /**
@@ -1618,6 +1642,38 @@ class Puppeteer extends Helper {
1618
1642
  if (cookie[0]) return cookie[0];
1619
1643
  }
1620
1644
 
1645
+ /**
1646
+ * {{> waitForCookie }}
1647
+ */
1648
+ async waitForCookie(name, sec) {
1649
+ // by default, we will retry 3 times
1650
+ let retries = 3;
1651
+ const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
1652
+
1653
+ if (sec) {
1654
+ retries = sec;
1655
+ } else {
1656
+ retries = Math.ceil(waitTimeout / 1000) - 1;
1657
+ }
1658
+
1659
+ return promiseRetry(async (retry, number) => {
1660
+ const _grabCookie = async (name) => {
1661
+ const cookies = await this.page.cookies();
1662
+ const cookie = cookies.filter(c => c.name === name);
1663
+ if (cookie.length === 0) throw Error(`Cookie ${name} is not found after ${retries}s`);
1664
+ };
1665
+
1666
+ this.debugSection('Wait for cookie: ', name);
1667
+ if (number > 1) this.debugSection('Retrying... Attempt #', number);
1668
+
1669
+ try {
1670
+ await _grabCookie(name);
1671
+ } catch (e) {
1672
+ retry(e);
1673
+ }
1674
+ }, { retries, maxTimeout: 1000 });
1675
+ }
1676
+
1621
1677
  /**
1622
1678
  * {{> clearCookie }}
1623
1679
  */
@@ -1637,8 +1693,8 @@ class Puppeteer extends Helper {
1637
1693
  * {{> executeScript }}
1638
1694
  */
1639
1695
  async executeScript(...args) {
1640
- let context = this.page;
1641
- if (this.context && this.context.constructor.name === 'Frame') {
1696
+ let context = await this._getContext();
1697
+ if (this.context && this.context.constructor.name === 'CdpFrame') {
1642
1698
  context = this.context; // switching to iframe context
1643
1699
  }
1644
1700
  return context.evaluate.apply(context, args);
@@ -1719,7 +1775,7 @@ class Puppeteer extends Helper {
1719
1775
  */
1720
1776
  async grabHTMLFromAll(locator) {
1721
1777
  const els = await this._locate(locator);
1722
- const values = await Promise.all(els.map(el => el.executionContext().evaluate(element => element.innerHTML, el)));
1778
+ const values = await Promise.all(els.map(el => el.evaluate(element => element.innerHTML, el)));
1723
1779
  return values;
1724
1780
  }
1725
1781
 
@@ -1742,7 +1798,7 @@ class Puppeteer extends Helper {
1742
1798
  */
1743
1799
  async grabCssPropertyFromAll(locator, cssProperty) {
1744
1800
  const els = await this._locate(locator);
1745
- const res = await Promise.all(els.map(el => el.executionContext().evaluate(el => JSON.parse(JSON.stringify(getComputedStyle(el))), el)));
1801
+ const res = await Promise.all(els.map(el => el.evaluate(el => JSON.parse(JSON.stringify(getComputedStyle(el))), el)));
1746
1802
  const cssValues = res.map(props => props[toCamelCase(cssProperty)]);
1747
1803
 
1748
1804
  return cssValues;
@@ -1804,33 +1860,34 @@ class Puppeteer extends Helper {
1804
1860
  * {{ react }}
1805
1861
  */
1806
1862
  async seeAttributesOnElements(locator, attributes) {
1807
- const res = await this._locate(locator);
1808
- assertElementExists(res, locator);
1863
+ const elements = await this._locate(locator);
1864
+ assertElementExists(elements, locator);
1809
1865
 
1810
- const elemAmount = res.length;
1811
- const commands = [];
1812
- res.forEach((el) => {
1813
- Object.keys(attributes).forEach((prop) => {
1814
- commands.push(el
1815
- .executionContext()
1816
- .evaluateHandle((el, attr) => el[attr] || el.getAttribute(attr), el, prop)
1817
- .then(el => el.jsonValue()));
1818
- });
1819
- });
1820
- let attrs = await Promise.all(commands);
1821
- const values = Object.keys(attributes).map(key => attributes[key]);
1822
- if (!Array.isArray(attrs)) attrs = [attrs];
1823
- let chunked = chunkArray(attrs, values.length);
1824
- chunked = chunked.filter((val) => {
1825
- for (let i = 0; i < val.length; ++i) {
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;
1830
- }
1831
- return true;
1866
+ const expectedAttributes = Object.entries(attributes);
1867
+
1868
+ const valuesPromises = elements.map(async (element) => {
1869
+ const elementAttributes = {};
1870
+ await Promise.all(expectedAttributes.map(async ([attribute, expectedValue]) => {
1871
+ const actualValue = await element.evaluate((el, attr) => el[attr] || el.getAttribute(attr), attribute);
1872
+ elementAttributes[attribute] = actualValue;
1873
+ }));
1874
+ return elementAttributes;
1832
1875
  });
1833
- return equals(`all elements (${(new Locator(locator))}) to have attributes ${JSON.stringify(attributes)}`).assert(chunked.length, elemAmount);
1876
+
1877
+ const actualAttributes = await Promise.all(valuesPromises);
1878
+
1879
+ const matchingElements = actualAttributes.filter((attrs) => expectedAttributes.every(([attribute, expectedValue]) => {
1880
+ const actualValue = attrs[attribute];
1881
+ if (!actualValue) return false;
1882
+ if (actualValue.toString().match(new RegExp(expectedValue.toString()))) return true;
1883
+ return expectedValue === actualValue;
1884
+ }));
1885
+
1886
+ const elementsCount = elements.length;
1887
+ const matchingCount = matchingElements.length;
1888
+
1889
+ return equals(`all elements (${(new Locator(locator))}) to have attributes ${JSON.stringify(attributes)}`)
1890
+ .assert(matchingCount, elementsCount);
1834
1891
  }
1835
1892
 
1836
1893
  /**
@@ -2033,7 +2090,6 @@ class Puppeteer extends Helper {
2033
2090
  async waitNumberOfVisibleElements(locator, num, sec) {
2034
2091
  const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
2035
2092
  locator = new Locator(locator, 'css');
2036
- await this.context;
2037
2093
  let waiter;
2038
2094
  const context = await this._getContext();
2039
2095
  if (locator.isCSS()) {
@@ -2086,7 +2142,7 @@ class Puppeteer extends Helper {
2086
2142
  if (locator.isCSS()) {
2087
2143
  waiter = context.waitForSelector(locator.simplify(), { timeout: waitTimeout });
2088
2144
  } else {
2089
- waiter = context.waitForXPath(locator.value, { timeout: waitTimeout });
2145
+ waiter = _waitForElement.call(this, locator, { timeout: waitTimeout });
2090
2146
  }
2091
2147
  return waiter.catch((err) => {
2092
2148
  throw new Error(`element (${locator.toString()}) still not present on page after ${waitTimeout / 1000} sec\n${err.message}`);
@@ -2107,7 +2163,7 @@ class Puppeteer extends Helper {
2107
2163
  if (locator.isCSS()) {
2108
2164
  waiter = context.waitForSelector(locator.simplify(), { timeout: waitTimeout, visible: true });
2109
2165
  } else {
2110
- waiter = context.waitForXPath(locator.value, { timeout: waitTimeout, visible: true });
2166
+ waiter = _waitForElement.call(this, locator, { timeout: waitTimeout, visible: true });
2111
2167
  }
2112
2168
  return waiter.catch((err) => {
2113
2169
  throw new Error(`element (${locator.toString()}) still not visible after ${waitTimeout / 1000} sec\n${err.message}`);
@@ -2126,7 +2182,7 @@ class Puppeteer extends Helper {
2126
2182
  if (locator.isCSS()) {
2127
2183
  waiter = context.waitForSelector(locator.simplify(), { timeout: waitTimeout, hidden: true });
2128
2184
  } else {
2129
- waiter = context.waitForXPath(locator.value, { timeout: waitTimeout, hidden: true });
2185
+ waiter = _waitForElement.call(this, locator, { timeout: waitTimeout, hidden: true });
2130
2186
  }
2131
2187
  return waiter.catch((err) => {
2132
2188
  throw new Error(`element (${locator.toString()}) still visible after ${waitTimeout / 1000} sec\n${err.message}`);
@@ -2144,7 +2200,7 @@ class Puppeteer extends Helper {
2144
2200
  if (locator.isCSS()) {
2145
2201
  waiter = context.waitForSelector(locator.simplify(), { timeout: waitTimeout, hidden: true });
2146
2202
  } else {
2147
- waiter = context.waitForXPath(locator.value, { timeout: waitTimeout, hidden: true });
2203
+ waiter = _waitForElement.call(this, locator, { timeout: waitTimeout, hidden: true });
2148
2204
  }
2149
2205
  return waiter.catch((err) => {
2150
2206
  throw new Error(`element (${locator.toString()}) still not hidden after ${waitTimeout / 1000} sec\n${err.message}`);
@@ -2170,7 +2226,7 @@ class Puppeteer extends Helper {
2170
2226
  }
2171
2227
 
2172
2228
  async _getContext() {
2173
- if (this.context && this.context.constructor.name === 'Frame') {
2229
+ if (this.context && this.context.constructor.name === 'CdpFrame') {
2174
2230
  return this.context;
2175
2231
  }
2176
2232
  return this.page;
@@ -2293,35 +2349,37 @@ class Puppeteer extends Helper {
2293
2349
  async switchTo(locator) {
2294
2350
  if (Number.isInteger(locator)) {
2295
2351
  // Select by frame index of current context
2296
-
2297
- let childFrames = null;
2352
+ let frames = [];
2298
2353
  if (this.context && typeof this.context.childFrames === 'function') {
2299
- childFrames = this.context.childFrames();
2354
+ frames = await this.context.childFrames();
2300
2355
  } else {
2301
- childFrames = this.page.mainFrame().childFrames();
2356
+ frames = await this.page.mainFrame().childFrames();
2302
2357
  }
2303
2358
 
2304
- if (locator >= 0 && locator < childFrames.length) {
2305
- this.context = childFrames[locator];
2359
+ if (locator >= 0 && locator < frames.length) {
2360
+ this.context = frames[locator];
2306
2361
  } else {
2307
- throw new Error('Element #invalidIframeSelector was not found by text|CSS|XPath');
2362
+ throw new Error('Frame index out of bounds');
2308
2363
  }
2309
2364
  return;
2310
2365
  }
2366
+
2311
2367
  if (!locator) {
2312
- this.context = await this.page.mainFrame().$('body');
2368
+ this.context = await this.page.mainFrame();
2313
2369
  return;
2314
2370
  }
2315
2371
 
2316
- // iframe by selector
2372
+ // Select iframe by selector
2317
2373
  const els = await this._locate(locator);
2318
2374
  assertElementExists(els, locator);
2319
- const contentFrame = await els[0].contentFrame();
2375
+
2376
+ const iframeElement = els[0];
2377
+ const contentFrame = await iframeElement.contentFrame();
2320
2378
 
2321
2379
  if (contentFrame) {
2322
2380
  this.context = contentFrame;
2323
2381
  } else {
2324
- this.context = els[0];
2382
+ throw new Error('Element "#invalidIframeSelector" was not found by text|CSS|XPath');
2325
2383
  }
2326
2384
  }
2327
2385
 
@@ -2412,12 +2470,220 @@ class Puppeteer extends Helper {
2412
2470
  if (prop) return rect[prop];
2413
2471
  return rect;
2414
2472
  }
2473
+
2474
+ /**
2475
+ * Mocks network request using [`Request Interception`](https://pptr.dev/next/guides/request-interception)
2476
+ *
2477
+ * ```js
2478
+ * I.mockRoute(/(\.png$)|(\.jpg$)/, route => route.abort());
2479
+ * ```
2480
+ * This method allows intercepting and mocking requests & responses. [Learn more about it](https://pptr.dev/next/guides/request-interception)
2481
+ *
2482
+ * @param {string|RegExp} [url] URL, regex or pattern for to match URL
2483
+ * @param {function} [handler] a function to process request
2484
+ */
2485
+ async mockRoute(url, handler) {
2486
+ await this.page.setRequestInterception(true);
2487
+
2488
+ this.page.on('request', interceptedRequest => {
2489
+ if (interceptedRequest.url().match(url)) {
2490
+ // @ts-ignore
2491
+ handler(interceptedRequest);
2492
+ } else {
2493
+ interceptedRequest.continue();
2494
+ }
2495
+ });
2496
+ }
2497
+
2498
+ /**
2499
+ * Stops network mocking created by `mockRoute`.
2500
+ *
2501
+ * ```js
2502
+ * I.stopMockingRoute(/(\.png$)|(\.jpg$)/);
2503
+ * ```
2504
+ *
2505
+ * @param {string|RegExp} [url] URL, regex or pattern for to match URL
2506
+ */
2507
+ async stopMockingRoute(url) {
2508
+ await this.page.setRequestInterception(true);
2509
+
2510
+ this.page.off('request');
2511
+
2512
+ // Resume normal request handling for the given URL
2513
+ this.page.on('request', interceptedRequest => {
2514
+ if (interceptedRequest.url().includes(url)) {
2515
+ interceptedRequest.continue();
2516
+ } else {
2517
+ interceptedRequest.continue();
2518
+ }
2519
+ });
2520
+ }
2521
+
2522
+ /**
2523
+ *
2524
+ * {{> flushNetworkTraffics }}
2525
+ */
2526
+ flushNetworkTraffics() {
2527
+ flushNetworkTraffics.call(this);
2528
+ }
2529
+
2530
+ /**
2531
+ *
2532
+ * {{> stopRecordingTraffic }}
2533
+ */
2534
+ stopRecordingTraffic() {
2535
+ stopRecordingTraffic.call(this);
2536
+ }
2537
+
2538
+ /**
2539
+ * {{> startRecordingTraffic }}
2540
+ *
2541
+ */
2542
+ async startRecordingTraffic() {
2543
+ this.flushNetworkTraffics();
2544
+ this.recording = true;
2545
+ this.recordedAtLeastOnce = true;
2546
+
2547
+ await this.page.setRequestInterception(true);
2548
+
2549
+ this.page.on('request', (request) => {
2550
+ const information = {
2551
+ url: request.url(),
2552
+ method: request.method(),
2553
+ requestHeaders: request.headers(),
2554
+ requestPostData: request.postData(),
2555
+ response: request.response(),
2556
+ };
2557
+
2558
+ this.debugSection('REQUEST: ', JSON.stringify(information));
2559
+
2560
+ if (typeof information.requestPostData === 'object') {
2561
+ information.requestPostData = JSON.parse(information.requestPostData);
2562
+ }
2563
+ request.continue();
2564
+ this.requests.push(information);
2565
+ });
2566
+ }
2567
+
2568
+ /**
2569
+ *
2570
+ * {{> grabRecordedNetworkTraffics }}
2571
+ */
2572
+ async grabRecordedNetworkTraffics() {
2573
+ return grabRecordedNetworkTraffics.call(this);
2574
+ }
2575
+
2576
+ /**
2577
+ *
2578
+ * {{> seeTraffic }}
2579
+ */
2580
+ async seeTraffic({
2581
+ name, url, parameters, requestPostData, timeout = 10,
2582
+ }) {
2583
+ await seeTraffic.call(this, ...arguments);
2584
+ }
2585
+
2586
+ /**
2587
+ *
2588
+ * {{> dontSeeTraffic }}
2589
+ *
2590
+ */
2591
+ dontSeeTraffic({ name, url }) {
2592
+ dontSeeTraffic.call(this, ...arguments);
2593
+ }
2594
+
2595
+ async getNewCDPSession() {
2596
+ const client = await this.page.target().createCDPSession();
2597
+ return client;
2598
+ }
2599
+
2600
+ /**
2601
+ * {{> startRecordingWebSocketMessages }}
2602
+ */
2603
+ async startRecordingWebSocketMessages() {
2604
+ this.flushWebSocketMessages();
2605
+ this.recordingWebSocketMessages = true;
2606
+ this.recordedWebSocketMessagesAtLeastOnce = true;
2607
+
2608
+ this.cdpSession = await this.getNewCDPSession();
2609
+ await this.cdpSession.send('Network.enable');
2610
+ await this.cdpSession.send('Page.enable');
2611
+
2612
+ this.cdpSession.on(
2613
+ 'Network.webSocketFrameReceived',
2614
+ (payload) => {
2615
+ this._logWebsocketMessages(this._getWebSocketLog('RECEIVED', payload));
2616
+ },
2617
+ );
2618
+
2619
+ this.cdpSession.on(
2620
+ 'Network.webSocketFrameSent',
2621
+ (payload) => {
2622
+ this._logWebsocketMessages(this._getWebSocketLog('SENT', payload));
2623
+ },
2624
+ );
2625
+
2626
+ this.cdpSession.on(
2627
+ 'Network.webSocketFrameError',
2628
+ (payload) => {
2629
+ this._logWebsocketMessages(this._getWebSocketLog('ERROR', payload));
2630
+ },
2631
+ );
2632
+ }
2633
+
2634
+ /**
2635
+ * {{> stopRecordingWebSocketMessages }}
2636
+ */
2637
+ async stopRecordingWebSocketMessages() {
2638
+ await this.cdpSession.send('Network.disable');
2639
+ await this.cdpSession.send('Page.disable');
2640
+ this.page.removeAllListeners('Network');
2641
+ this.recordingWebSocketMessages = false;
2642
+ }
2643
+
2644
+ /**
2645
+ * Grab the recording WS messages
2646
+ *
2647
+ * @return { Array<any>|undefined }
2648
+ *
2649
+ */
2650
+ grabWebSocketMessages() {
2651
+ if (!this.recordingWebSocketMessages) {
2652
+ if (!this.recordedWebSocketMessagesAtLeastOnce) {
2653
+ throw new Error('Failure in test automation. You use "I.grabWebSocketMessages", but "I.startRecordingWebSocketMessages" was never called before.');
2654
+ }
2655
+ }
2656
+ return this.webSocketMessages;
2657
+ }
2658
+
2659
+ /**
2660
+ * Resets all recorded WS messages.
2661
+ */
2662
+ flushWebSocketMessages() {
2663
+ this.webSocketMessages = [];
2664
+ }
2665
+
2666
+ _getWebSocketMessage(payload) {
2667
+ if (payload.errorMessage) {
2668
+ return payload.errorMessage;
2669
+ }
2670
+
2671
+ return payload.response.payloadData;
2672
+ }
2673
+
2674
+ _getWebSocketLog(prefix, payload) {
2675
+ return `${prefix} ID: ${payload.requestId} TIMESTAMP: ${payload.timestamp} (${new Date().toISOString()})\n\n${this._getWebSocketMessage(payload)}\n\n`;
2676
+ }
2677
+
2678
+ _logWebsocketMessages(message) {
2679
+ this.webSocketMessages += message;
2680
+ }
2415
2681
  }
2416
2682
 
2417
2683
  module.exports = Puppeteer;
2418
2684
 
2419
2685
  async function findElements(matcher, locator) {
2420
- if (locator.react) return findReact(matcher.executionContext(), locator);
2686
+ if (locator.react) return findReactElements.call(this, locator);
2421
2687
  locator = new Locator(locator, 'css');
2422
2688
  if (!locator.isXPath()) return matcher.$$(locator.simplify());
2423
2689
  // puppeteer version < 19.4.0 is no longer supported. This one is backward support.
@@ -2454,7 +2720,7 @@ async function proceedClick(locator, context = null, options = {}) {
2454
2720
  }
2455
2721
 
2456
2722
  async function findClickable(matcher, locator) {
2457
- if (locator.react) return findReact(matcher.executionContext(), locator);
2723
+ if (locator.react) return findReactElements.call(this, locator);
2458
2724
  locator = new Locator(locator);
2459
2725
  if (!locator.isFuzzy()) return findElements.call(this, matcher, locator);
2460
2726
 
@@ -2803,3 +3069,70 @@ function highlightActiveElement(element, context) {
2803
3069
  highlightElement(element, context);
2804
3070
  }
2805
3071
  }
3072
+
3073
+ function _waitForElement(locator, options) {
3074
+ try {
3075
+ return this.context.waitForXPath(locator.value, options);
3076
+ } catch (e) {
3077
+ return this.context.waitForSelector(`::-p-xpath(${locator.value})`, options);
3078
+ }
3079
+ }
3080
+
3081
+ async function findReactElements(locator, props = {}, state = {}) {
3082
+ const resqScript = await fs.promises.readFile(require.resolve('resq'), 'utf-8');
3083
+ await this.page.evaluate(resqScript.toString());
3084
+
3085
+ await this.page.evaluate(() => window.resq.waitToLoadReact());
3086
+ const arrayHandle = await this.page.evaluateHandle((obj) => {
3087
+ const { selector, props, state } = obj;
3088
+ let elements = window.resq.resq$$(selector);
3089
+ if (Object.keys(props).length) {
3090
+ elements = elements.byProps(props);
3091
+ }
3092
+ if (Object.keys(state).length) {
3093
+ elements = elements.byState(state);
3094
+ }
3095
+
3096
+ if (!elements.length) {
3097
+ return [];
3098
+ }
3099
+
3100
+ // resq returns an array of HTMLElements if the React component is a fragment
3101
+ // this avoids having nested arrays of nodes which the driver does not understand
3102
+ // [[div, div], [div, div]] => [div, div, div, div]
3103
+ let nodes = [];
3104
+
3105
+ elements.forEach((element) => {
3106
+ let { node, isFragment } = element;
3107
+
3108
+ if (!node) {
3109
+ isFragment = true;
3110
+ node = element.children;
3111
+ }
3112
+
3113
+ if (isFragment) {
3114
+ nodes = nodes.concat(node);
3115
+ } else {
3116
+ nodes.push(node);
3117
+ }
3118
+ });
3119
+
3120
+ return [...nodes];
3121
+ }, {
3122
+ selector: locator.react,
3123
+ props: locator.props || {},
3124
+ state: locator.state || {},
3125
+ });
3126
+
3127
+ const properties = await arrayHandle.getProperties();
3128
+ const result = [];
3129
+ for (const property of properties.values()) {
3130
+ const elementHandle = property.asElement();
3131
+ if (elementHandle) {
3132
+ result.push(elementHandle);
3133
+ }
3134
+ }
3135
+
3136
+ await arrayHandle.dispose();
3137
+ return result;
3138
+ }