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
@@ -4,8 +4,8 @@ const fs = require('fs');
4
4
  const Helper = require('@codeceptjs/helper');
5
5
  const { v4: uuidv4 } = require('uuid');
6
6
  const assert = require('assert');
7
+ const promiseRetry = require('promise-retry');
7
8
  const Locator = require('../locator');
8
- const store = require('../store');
9
9
  const recorder = require('../recorder');
10
10
  const stringIncludes = require('../assert/include').includes;
11
11
  const { urlEquals } = require('../assert/equal');
@@ -33,7 +33,7 @@ const ElementNotFound = require('./errors/ElementNotFound');
33
33
  const RemoteBrowserConnectionRefused = require('./errors/RemoteBrowserConnectionRefused');
34
34
  const Popup = require('./extras/Popup');
35
35
  const Console = require('./extras/Console');
36
- const { findReact, findVue } = require('./extras/PlaywrightReactVueLocator');
36
+ const { findReact, findVue, findByPlaywrightLocator } = require('./extras/PlaywrightReactVueLocator');
37
37
 
38
38
  let playwright;
39
39
  let perfTiming;
@@ -47,6 +47,12 @@ const {
47
47
  setRestartStrategy, restartsSession, restartsContext, restartsBrowser,
48
48
  } = require('./extras/PlaywrightRestartOpts');
49
49
  const { createValueEngine, createDisabledEngine } = require('./extras/PlaywrightPropEngine');
50
+ const {
51
+ seeElementError, dontSeeElementError, dontSeeElementInDOMError, seeElementInDOMError,
52
+ } = require('./errors/ElementAssertion');
53
+ const {
54
+ dontSeeTraffic, seeTraffic, grabRecordedNetworkTraffics, stopRecordingTraffic, flushNetworkTraffics,
55
+ } = require('./network/actions');
50
56
 
51
57
  const pathSeparator = path.sep;
52
58
 
@@ -95,6 +101,7 @@ const pathSeparator = path.sep;
95
101
  * @prop {boolean} [bypassCSP] - bypass Content Security Policy or CSP
96
102
  * @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose).
97
103
  * @prop {object} [recordHar] - record HAR and will be saved to `output/har`. See more of [HAR options](https://playwright.dev/docs/api/class-browser#browser-new-context-option-record-har).
104
+ * @prop {string} [testIdAttribute=data-testid] - locate elements based on the testIdAttribute. See more of [locate by test id](https://playwright.dev/docs/locators#locate-by-test-id).
98
105
  */
99
106
  const config = {};
100
107
 
@@ -374,6 +381,7 @@ class Playwright extends Helper {
374
381
  highlightElement: false,
375
382
  };
376
383
 
384
+ process.env.testIdAttribute = 'data-testid';
377
385
  config = Object.assign(defaults, config);
378
386
 
379
387
  if (availableBrowsers.indexOf(config.browser) < 0) {
@@ -459,6 +467,7 @@ class Playwright extends Helper {
459
467
  try {
460
468
  await playwright.selectors.register('__value', createValueEngine);
461
469
  await playwright.selectors.register('__disabled', createDisabledEngine);
470
+ if (process.env.testIdAttribute) await playwright.selectors.setTestIdAttribute(process.env.testIdAttribute);
462
471
  } catch (e) {
463
472
  console.warn(e);
464
473
  }
@@ -745,9 +754,7 @@ class Playwright extends Helper {
745
754
  });
746
755
  this.context = await this.page;
747
756
  this.contextLocator = null;
748
- if (this.options.browser === 'chrome') {
749
- await page.bringToFront();
750
- }
757
+ await page.bringToFront();
751
758
  }
752
759
 
753
760
  /**
@@ -1451,7 +1458,11 @@ class Playwright extends Helper {
1451
1458
  async seeElement(locator) {
1452
1459
  let els = await this._locate(locator);
1453
1460
  els = await Promise.all(els.map(el => el.isVisible()));
1454
- return empty('visible elements').negate(els.filter(v => v).fill('ELEMENT'));
1461
+ try {
1462
+ return empty('visible elements').negate(els.filter(v => v).fill('ELEMENT'));
1463
+ } catch (e) {
1464
+ dontSeeElementError(locator);
1465
+ }
1455
1466
  }
1456
1467
 
1457
1468
  /**
@@ -1461,7 +1472,11 @@ class Playwright extends Helper {
1461
1472
  async dontSeeElement(locator) {
1462
1473
  let els = await this._locate(locator);
1463
1474
  els = await Promise.all(els.map(el => el.isVisible()));
1464
- return empty('visible elements').assert(els.filter(v => v).fill('ELEMENT'));
1475
+ try {
1476
+ return empty('visible elements').assert(els.filter(v => v).fill('ELEMENT'));
1477
+ } catch (e) {
1478
+ seeElementError(locator);
1479
+ }
1465
1480
  }
1466
1481
 
1467
1482
  /**
@@ -1469,7 +1484,11 @@ class Playwright extends Helper {
1469
1484
  */
1470
1485
  async seeElementInDOM(locator) {
1471
1486
  const els = await this._locate(locator);
1472
- return empty('elements on page').negate(els.filter(v => v).fill('ELEMENT'));
1487
+ try {
1488
+ return empty('elements on page').negate(els.filter(v => v).fill('ELEMENT'));
1489
+ } catch (e) {
1490
+ dontSeeElementInDOMError(locator);
1491
+ }
1473
1492
  }
1474
1493
 
1475
1494
  /**
@@ -1477,7 +1496,11 @@ class Playwright extends Helper {
1477
1496
  */
1478
1497
  async dontSeeElementInDOM(locator) {
1479
1498
  const els = await this._locate(locator);
1480
- return empty('elements on a page').assert(els.filter(v => v).fill('ELEMENT'));
1499
+ try {
1500
+ return empty('elements on a page').assert(els.filter(v => v).fill('ELEMENT'));
1501
+ } catch (e) {
1502
+ seeElementInDOMError(locator);
1503
+ }
1481
1504
  }
1482
1505
 
1483
1506
  /**
@@ -1786,7 +1809,7 @@ class Playwright extends Helper {
1786
1809
  let optionToSelect = '';
1787
1810
 
1788
1811
  try {
1789
- optionToSelect = await el.locator('option', { hasText: option }).textContent();
1812
+ optionToSelect = (await el.locator('option', { hasText: option }).textContent()).trim();
1790
1813
  } catch (e) {
1791
1814
  optionToSelect = option;
1792
1815
  }
@@ -2169,6 +2192,8 @@ class Playwright extends Helper {
2169
2192
  let chunked = chunkArray(attrs, values.length);
2170
2193
  chunked = chunked.filter((val) => {
2171
2194
  for (let i = 0; i < val.length; ++i) {
2195
+ // the attribute could be a boolean
2196
+ if (typeof val[i] === 'boolean') return val[i] === values[i];
2172
2197
  // if the attribute doesn't exist, returns false as well
2173
2198
  if (!val[i] || !val[i].includes(values[i])) return false;
2174
2199
  }
@@ -2447,7 +2472,7 @@ class Playwright extends Helper {
2447
2472
  async waitNumberOfVisibleElements(locator, num, sec) {
2448
2473
  const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
2449
2474
  locator = new Locator(locator, 'css');
2450
- await this.context;
2475
+
2451
2476
  let waiter;
2452
2477
  const context = await this._getContext();
2453
2478
  if (locator.isCSS()) {
@@ -2657,6 +2682,7 @@ class Playwright extends Helper {
2657
2682
  */
2658
2683
  async waitForText(text, sec = null, context = null) {
2659
2684
  const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
2685
+ const errorMessage = `Text "${text}" was not found on page after ${waitTimeout / 1000} sec.`;
2660
2686
  let waiter;
2661
2687
 
2662
2688
  const contextObject = await this._getContext();
@@ -2667,18 +2693,21 @@ class Playwright extends Helper {
2667
2693
  try {
2668
2694
  await contextObject.locator(`${locator.isCustom() ? `${locator.type}=${locator.value}` : locator.simplify()} >> text=${text}`).first().waitFor({ timeout: waitTimeout, state: 'visible' });
2669
2695
  } catch (e) {
2670
- console.log(e);
2671
- throw new Error(`Text "${text}" was not found on page after ${waitTimeout / 1000} sec\n${e.message}`);
2696
+ throw new Error(`${errorMessage}\n${e.message}`);
2672
2697
  }
2673
2698
  }
2674
2699
 
2675
2700
  if (locator.isXPath()) {
2676
- waiter = contextObject.waitForFunction(([locator, text, $XPath]) => {
2677
- eval($XPath); // eslint-disable-line no-eval
2678
- const el = $XPath(null, locator);
2679
- if (!el.length) return false;
2680
- return el[0].innerText.indexOf(text) > -1;
2681
- }, [locator.value, text, $XPath.toString()], { timeout: waitTimeout });
2701
+ try {
2702
+ await contextObject.waitForFunction(([locator, text, $XPath]) => {
2703
+ eval($XPath); // eslint-disable-line no-eval
2704
+ const el = $XPath(null, locator);
2705
+ if (!el.length) return false;
2706
+ return el[0].innerText.indexOf(text) > -1;
2707
+ }, [locator.value, text, $XPath.toString()], { timeout: waitTimeout });
2708
+ } catch (e) {
2709
+ throw new Error(`${errorMessage}\n${e.message}`);
2710
+ }
2682
2711
  }
2683
2712
  } else {
2684
2713
  // we have this as https://github.com/microsoft/playwright/issues/26829 is not yet implemented
@@ -2692,7 +2721,7 @@ class Playwright extends Helper {
2692
2721
  count += 1000;
2693
2722
  } while (count <= waitTimeout);
2694
2723
 
2695
- if (!waiter) throw new Error(`Text "${text}" was not found on page after ${waitTimeout / 1000} sec`);
2724
+ if (!waiter) throw new Error(`${errorMessage}`);
2696
2725
  }
2697
2726
  }
2698
2727
 
@@ -2869,6 +2898,38 @@ class Playwright extends Helper {
2869
2898
  }
2870
2899
  }
2871
2900
 
2901
+ /**
2902
+ * {{> waitForCookie }}
2903
+ */
2904
+ async waitForCookie(name, sec) {
2905
+ // by default, we will retry 3 times
2906
+ let retries = 3;
2907
+ const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
2908
+
2909
+ if (sec) {
2910
+ retries = sec;
2911
+ } else {
2912
+ retries = Math.ceil(waitTimeout / 1000) - 1;
2913
+ }
2914
+
2915
+ return promiseRetry(async (retry, number) => {
2916
+ const _grabCookie = async (name) => {
2917
+ const cookies = await this.browserContext.cookies();
2918
+ const cookie = cookies.filter(c => c.name === name);
2919
+ if (cookie.length === 0) throw Error(`Cookie ${name} is not found after ${retries}s`);
2920
+ };
2921
+
2922
+ this.debugSection('Wait for cookie: ', name);
2923
+ if (number > 1) this.debugSection('Retrying... Attempt #', number);
2924
+
2925
+ try {
2926
+ await _grabCookie(name);
2927
+ } catch (e) {
2928
+ retry(e);
2929
+ }
2930
+ }, { retries, maxTimeout: 1000 });
2931
+ }
2932
+
2872
2933
  async _waitForAction() {
2873
2934
  return this.wait(this.options.waitForAction / 1000);
2874
2935
  }
@@ -2900,7 +2961,7 @@ class Playwright extends Helper {
2900
2961
  * This method allows intercepting and mocking requests & responses. [Learn more about it](https://playwright.dev/docs/network#handle-requests)
2901
2962
  *
2902
2963
  * @param {string|RegExp} [url] URL, regex or pattern for to match URL
2903
- * @param {function} [handler] a function to process reques
2964
+ * @param {function} [handler] a function to process request
2904
2965
  */
2905
2966
  async mockRoute(url, handler) {
2906
2967
  return this.browserContext.route(...arguments);
@@ -2916,21 +2977,15 @@ class Playwright extends Helper {
2916
2977
  * If no handler is passed, all mock requests for the rote are disabled.
2917
2978
  *
2918
2979
  * @param {string|RegExp} [url] URL, regex or pattern for to match URL
2919
- * @param {function} [handler] a function to process reques
2980
+ * @param {function} [handler] a function to process request
2920
2981
  */
2921
2982
  async stopMockingRoute(url, handler) {
2922
2983
  return this.browserContext.unroute(...arguments);
2923
2984
  }
2924
2985
 
2925
2986
  /**
2926
- * Starts recording the network traffics.
2927
- * This also resets recorded network requests.
2928
- *
2929
- * ```js
2930
- * I.startRecordingTraffic();
2931
- * ```
2987
+ * {{> flushNetworkTraffics }}
2932
2988
  *
2933
- * @return {void}
2934
2989
  */
2935
2990
  startRecordingTraffic() {
2936
2991
  this.flushNetworkTraffics();
@@ -2956,47 +3011,6 @@ class Playwright extends Helper {
2956
3011
  });
2957
3012
  }
2958
3013
 
2959
- /**
2960
- * Grab the recording network traffics
2961
- *
2962
- * ```js
2963
- * const traffics = await I.grabRecordedNetworkTraffics();
2964
- * expect(traffics[0].url).to.equal('https://reqres.in/api/comments/1');
2965
- * expect(traffics[0].response.status).to.equal(200);
2966
- * expect(traffics[0].response.body).to.contain({ name: 'this was mocked' });
2967
- * ```
2968
- *
2969
- * @return { Promise<Array<any>> }
2970
- *
2971
- */
2972
- async grabRecordedNetworkTraffics() {
2973
- if (!this.recording || !this.recordedAtLeastOnce) {
2974
- throw new Error('Failure in test automation. You use "I.grabRecordedNetworkTraffics", but "I.startRecordingTraffic" was never called before.');
2975
- }
2976
-
2977
- const requests = await this.requests;
2978
- const promises = requests.map(async (request) => request.response.then(
2979
- async (response) => {
2980
- let body;
2981
- try {
2982
- // There's no 'body' for some requests (redirect etc...)
2983
- body = JSON.parse((await response.body()).toString());
2984
- } catch (e) {
2985
- // only interested in JSON, not HTML responses.
2986
- }
2987
-
2988
- request.response = {
2989
- status: response.status(),
2990
- statusText: response.statusText(),
2991
- body,
2992
- };
2993
- },
2994
- ));
2995
- await Promise.all(promises);
2996
-
2997
- return this.requests;
2998
- }
2999
-
3000
3014
  /**
3001
3015
  * Blocks traffic of a given URL or a list of URLs.
3002
3016
  *
@@ -3076,106 +3090,19 @@ class Playwright extends Helper {
3076
3090
  }
3077
3091
 
3078
3092
  /**
3079
- * Resets all recorded network requests.
3093
+ *
3094
+ * {{> flushNetworkTraffics }}
3080
3095
  */
3081
3096
  flushNetworkTraffics() {
3082
- this.requests = [];
3097
+ flushNetworkTraffics.call(this);
3083
3098
  }
3084
3099
 
3085
3100
  /**
3086
- * Stops recording of network traffic. Recorded traffic is not flashed.
3087
3101
  *
3088
- * ```js
3089
- * I.stopRecordingTraffic();
3090
- * ```
3102
+ * {{> stopRecordingTraffic }}
3091
3103
  */
3092
3104
  stopRecordingTraffic() {
3093
- this.page.removeAllListeners('request');
3094
- this.recording = false;
3095
- }
3096
-
3097
- /**
3098
- * Verifies that a certain request is part of network traffic.
3099
- *
3100
- * ```js
3101
- * // checking the request url contains certain query strings
3102
- * I.amOnPage('https://openai.com/blog/chatgpt');
3103
- * I.startRecordingTraffic();
3104
- * await I.seeTraffic({
3105
- * name: 'sentry event',
3106
- * url: 'https://images.openai.com/blob/cf717bdb-0c8c-428a-b82b-3c3add87a600',
3107
- * parameters: {
3108
- * width: '1919',
3109
- * height: '1138',
3110
- * },
3111
- * });
3112
- * ```
3113
- *
3114
- * ```js
3115
- * // checking the request url contains certain post data
3116
- * I.amOnPage('https://openai.com/blog/chatgpt');
3117
- * I.startRecordingTraffic();
3118
- * await I.seeTraffic({
3119
- * name: 'event',
3120
- * url: 'https://cloudflareinsights.com/cdn-cgi/rum',
3121
- * requestPostData: {
3122
- * st: 2,
3123
- * },
3124
- * });
3125
- * ```
3126
- *
3127
- * @param {Object} opts - options when checking the traffic network.
3128
- * @param {string} opts.name A name of that request. Can be any value. Only relevant to have a more meaningful error message in case of fail.
3129
- * @param {string} opts.url Expected URL of request in network traffic
3130
- * @param {Object} [opts.parameters] Expected parameters of that request in network traffic
3131
- * @param {Object} [opts.requestPostData] Expected that request contains post data in network traffic
3132
- * @param {number} [opts.timeout] Timeout to wait for request in seconds. Default is 10 seconds.
3133
- * @return { Promise<*> }
3134
- */
3135
- async seeTraffic({
3136
- name, url, parameters, requestPostData, timeout = 10,
3137
- }) {
3138
- if (!name) {
3139
- throw new Error('Missing required key "name" in object given to "I.seeTraffic".');
3140
- }
3141
-
3142
- if (!url) {
3143
- throw new Error('Missing required key "url" in object given to "I.seeTraffic".');
3144
- }
3145
-
3146
- if (!this.recording || !this.recordedAtLeastOnce) {
3147
- throw new Error('Failure in test automation. You use "I.seeTraffic", but "I.startRecordingTraffic" was never called before.');
3148
- }
3149
-
3150
- for (let i = 0; i <= timeout * 2; i++) {
3151
- const found = this._isInTraffic(url, parameters);
3152
- if (found) {
3153
- return true;
3154
- }
3155
- await new Promise((done) => {
3156
- setTimeout(done, 1000);
3157
- });
3158
- }
3159
-
3160
- // check request post data
3161
- if (requestPostData && this._isInTraffic(url)) {
3162
- const advancedTestResults = createAdvancedTestResults(url, requestPostData, this.requests);
3163
-
3164
- assert.equal(advancedTestResults, true, `Traffic named "${name}" found correct URL ${url}, BUT the post data did not match:\n ${advancedTestResults}`);
3165
- } else if (parameters && this._isInTraffic(url)) {
3166
- const advancedTestResults = createAdvancedTestResults(url, parameters, this.requests);
3167
-
3168
- assert.fail(
3169
- `Traffic named "${name}" found correct URL ${url}, BUT the query parameters did not match:\n`
3170
- + `${advancedTestResults}`,
3171
- );
3172
- } else {
3173
- assert.fail(
3174
- `Traffic named "${name}" not found in recorded traffic within ${timeout} seconds.\n`
3175
- + `Expected url: ${url}.\n`
3176
- + `Recorded traffic:\n${this._getTrafficDump()}`,
3177
- );
3178
- }
3105
+ stopRecordingTraffic.call(this);
3179
3106
  }
3180
3107
 
3181
3108
  /**
@@ -3212,94 +3139,34 @@ class Playwright extends Helper {
3212
3139
  }
3213
3140
 
3214
3141
  /**
3215
- * Verifies that a certain request is not part of network traffic.
3216
- *
3217
- * Examples:
3218
- *
3219
- * ```js
3220
- * I.dontSeeTraffic({ name: 'Unexpected API Call', url: 'https://api.example.com' });
3221
- * I.dontSeeTraffic({ name: 'Unexpected API Call of "user" endpoint', url: /api.example.com.*user/ });
3222
- * ```
3223
- *
3224
- * @param {Object} opts - options when checking the traffic network.
3225
- * @param {string} opts.name A name of that request. Can be any value. Only relevant to have a more meaningful error message in case of fail.
3226
- * @param {string|RegExp} opts.url Expected URL of request in network traffic. Can be a string or a regular expression.
3227
3142
  *
3143
+ * {{> grabRecordedNetworkTraffics }}
3228
3144
  */
3229
- dontSeeTraffic({ name, url }) {
3230
- if (!this.recordedAtLeastOnce) {
3231
- throw new Error('Failure in test automation. You use "I.dontSeeTraffic", but "I.startRecordingTraffic" was never called before.');
3232
- }
3233
-
3234
- if (!name) {
3235
- throw new Error('Missing required key "name" in object given to "I.dontSeeTraffic".');
3236
- }
3237
-
3238
- if (!url) {
3239
- throw new Error('Missing required key "url" in object given to "I.dontSeeTraffic".');
3240
- }
3241
-
3242
- if (this._isInTraffic(url)) {
3243
- assert.fail(`Traffic with name "${name}" (URL: "${url}') found, but was not expected to be found.`);
3244
- }
3145
+ async grabRecordedNetworkTraffics() {
3146
+ return grabRecordedNetworkTraffics.call(this);
3245
3147
  }
3246
3148
 
3247
3149
  /**
3248
- * Checks if URL with parameters is part of network traffic. Returns true or false. Internal method for this helper.
3249
3150
  *
3250
- * @param url URL to look for.
3251
- * @param [parameters] Parameters that this URL needs to contain
3252
- * @return {boolean} Whether or not URL with parameters is part of network traffic.
3253
- * @private
3151
+ * {{> seeTraffic }}
3254
3152
  */
3255
- _isInTraffic(url, parameters) {
3256
- let isInTraffic = false;
3257
- this.requests.forEach((request) => {
3258
- if (isInTraffic) {
3259
- return; // We already found traffic. Continue with next request
3260
- }
3261
-
3262
- if (!request.url.match(new RegExp(url))) {
3263
- return; // url not found in this request. continue with next request
3264
- }
3265
-
3266
- // URL has matched. Now we check the parameters
3267
-
3268
- if (parameters) {
3269
- const advancedReport = allParameterValuePairsMatchExtreme(extractQueryObjects(request.url), parameters);
3270
- if (advancedReport === true) {
3271
- isInTraffic = true;
3272
- }
3273
- } else {
3274
- isInTraffic = true;
3275
- }
3276
- });
3277
-
3278
- return isInTraffic;
3153
+ async seeTraffic({
3154
+ name, url, parameters, requestPostData, timeout = 10,
3155
+ }) {
3156
+ await seeTraffic.call(this, ...arguments);
3279
3157
  }
3280
3158
 
3281
3159
  /**
3282
- * Returns all URLs of all network requests recorded so far during execution of test scenario.
3283
3160
  *
3284
- * @return {string} List of URLs recorded as a string, separated by new lines after each URL
3285
- * @private
3161
+ * {{> dontSeeTraffic }}
3162
+ *
3286
3163
  */
3287
- _getTrafficDump() {
3288
- let dumpedTraffic = '';
3289
- this.requests.forEach((request) => {
3290
- dumpedTraffic += `${request.method} - ${request.url}\n`;
3291
- });
3292
- return dumpedTraffic;
3164
+ dontSeeTraffic({ name, url }) {
3165
+ dontSeeTraffic.call(this, ...arguments);
3293
3166
  }
3294
3167
 
3295
3168
  /**
3296
- * Starts recording of websocket messages.
3297
- * This also resets recorded websocket messages.
3298
- *
3299
- * ```js
3300
- * await I.startRecordingWebSocketMessages();
3301
- * ```
3302
- *
3169
+ * {{> startRecordingWebSocketMessages }}
3303
3170
  */
3304
3171
  async startRecordingWebSocketMessages() {
3305
3172
  this.flushWebSocketMessages();
@@ -3333,11 +3200,7 @@ class Playwright extends Helper {
3333
3200
  }
3334
3201
 
3335
3202
  /**
3336
- * Stops recording WS messages. Recorded WS messages is not flashed.
3337
- *
3338
- * ```js
3339
- * await I.stopRecordingWebSocketMessages();
3340
- * ```
3203
+ * {{> stopRecordingWebSocketMessages }}
3341
3204
  */
3342
3205
  async stopRecordingWebSocketMessages() {
3343
3206
  await this.cdpSession.send('Network.disable');
@@ -3464,6 +3327,7 @@ function buildLocatorString(locator) {
3464
3327
  async function findElements(matcher, locator) {
3465
3328
  if (locator.react) return findReact(matcher, locator);
3466
3329
  if (locator.vue) return findVue(matcher, locator);
3330
+ if (locator.pw) return findByPlaywrightLocator.call(this, matcher, locator);
3467
3331
  locator = new Locator(locator, 'css');
3468
3332
 
3469
3333
  return matcher.locator(buildLocatorString(locator)).all();
@@ -3471,6 +3335,8 @@ async function findElements(matcher, locator) {
3471
3335
 
3472
3336
  async function findElement(matcher, locator) {
3473
3337
  if (locator.react) return findReact(matcher, locator);
3338
+ if (locator.vue) return findVue(matcher, locator);
3339
+ if (locator.pw) return findByPlaywrightLocator.call(this, matcher, locator);
3474
3340
  locator = new Locator(locator, 'css');
3475
3341
 
3476
3342
  return matcher.locator(buildLocatorString(locator)).first();
@@ -3526,6 +3392,7 @@ async function proceedClick(locator, context = null, options = {}) {
3526
3392
  async function findClickable(matcher, locator) {
3527
3393
  if (locator.react) return findReact(matcher, locator);
3528
3394
  if (locator.vue) return findVue(matcher, locator);
3395
+ if (locator.pw) return findByPlaywrightLocator.call(this, matcher, locator);
3529
3396
 
3530
3397
  locator = new Locator(locator);
3531
3398
  if (!locator.isFuzzy()) return findElements.call(this, matcher, locator);
@@ -3935,134 +3802,3 @@ async function highlightActiveElement(element) {
3935
3802
  });
3936
3803
  }
3937
3804
  }
3938
-
3939
- const createAdvancedTestResults = (url, dataToCheck, requests) => {
3940
- // Creates advanced test results for a network traffic check.
3941
- // Advanced test results only applies when expected parameters are set
3942
- if (!dataToCheck) return '';
3943
-
3944
- let urlFound = false;
3945
- let advancedResults;
3946
- requests.forEach((request) => {
3947
- // url not found in this request. continue with next request
3948
- if (urlFound || !request.url.match(new RegExp(url))) return;
3949
- urlFound = true;
3950
-
3951
- // Url found. Now we create advanced test report for that URL and show which parameters failed
3952
- if (!request.requestPostData) {
3953
- advancedResults = allParameterValuePairsMatchExtreme(extractQueryObjects(request.url), dataToCheck);
3954
- } else if (request.requestPostData) {
3955
- advancedResults = allRequestPostDataValuePairsMatchExtreme(request.requestPostData, dataToCheck);
3956
- }
3957
- });
3958
- return advancedResults;
3959
- };
3960
-
3961
- const extractQueryObjects = (queryString) => {
3962
- // Converts a string of GET parameters into an array of parameter objects. Each parameter object contains the properties "name" and "value".
3963
- if (queryString.indexOf('?') === -1) {
3964
- return [];
3965
- }
3966
- const queryObjects = [];
3967
-
3968
- const queryPart = queryString.split('?')[1];
3969
-
3970
- const queryParameters = queryPart.split('&');
3971
-
3972
- queryParameters.forEach((queryParameter) => {
3973
- const keyValue = queryParameter.split('=');
3974
- const queryObject = {};
3975
- // eslint-disable-next-line prefer-destructuring
3976
- queryObject.name = keyValue[0];
3977
- queryObject.value = decodeURIComponent(keyValue[1]);
3978
- queryObjects.push(queryObject);
3979
- });
3980
-
3981
- return queryObjects;
3982
- };
3983
-
3984
- const allParameterValuePairsMatchExtreme = (queryStringObject, advancedExpectedParameterValuePairs) => {
3985
- // More advanced check if all request parameters match with the expectations
3986
- let littleReport = '\nQuery parameters:\n';
3987
- let success = true;
3988
-
3989
- for (const expectedKey in advancedExpectedParameterValuePairs) {
3990
- if (!Object.prototype.hasOwnProperty.call(advancedExpectedParameterValuePairs, expectedKey)) {
3991
- continue;
3992
- }
3993
- let parameterFound = false;
3994
- const expectedValue = advancedExpectedParameterValuePairs[expectedKey];
3995
-
3996
- for (const queryParameter of queryStringObject) {
3997
- if (queryParameter.name === expectedKey) {
3998
- parameterFound = true;
3999
- if (expectedValue === undefined) {
4000
- littleReport += ` ${expectedKey.padStart(10, ' ')}\n`;
4001
- } else if (typeof expectedValue === 'object' && expectedValue.base64) {
4002
- const decodedActualValue = Buffer.from(queryParameter.value, 'base64').toString('utf8');
4003
- if (decodedActualValue === expectedValue.base64) {
4004
- littleReport += ` ${expectedKey.padStart(10, ' ')} = base64(${expectedValue.base64})\n`;
4005
- } else {
4006
- littleReport += ` ✖ ${expectedKey.padStart(10, ' ')} = base64(${expectedValue.base64}) -> actual value: "base64(${decodedActualValue})"\n`;
4007
- success = false;
4008
- }
4009
- } else if (queryParameter.value === expectedValue) {
4010
- littleReport += ` ${expectedKey.padStart(10, ' ')} = ${expectedValue}\n`;
4011
- } else {
4012
- littleReport += ` ✖ ${expectedKey.padStart(10, ' ')} = ${expectedValue} -> actual value: "${queryParameter.value}"\n`;
4013
- success = false;
4014
- }
4015
- }
4016
- }
4017
-
4018
- if (parameterFound === false) {
4019
- littleReport += ` ✖ ${expectedKey.padStart(10, ' ')}${expectedValue ? ` = ${JSON.stringify(expectedValue)}` : ''} -> parameter not found in request\n`;
4020
- success = false;
4021
- }
4022
- }
4023
-
4024
- return success ? true : littleReport;
4025
- };
4026
-
4027
- const allRequestPostDataValuePairsMatchExtreme = (RequestPostDataObject, advancedExpectedRequestPostValuePairs) => {
4028
- // More advanced check if all request post data match with the expectations
4029
- let littleReport = '\nRequest Post Data:\n';
4030
- let success = true;
4031
-
4032
- for (const expectedKey in advancedExpectedRequestPostValuePairs) {
4033
- if (!Object.prototype.hasOwnProperty.call(advancedExpectedRequestPostValuePairs, expectedKey)) {
4034
- continue;
4035
- }
4036
- let keyFound = false;
4037
- const expectedValue = advancedExpectedRequestPostValuePairs[expectedKey];
4038
-
4039
- for (const [key, value] of Object.entries(RequestPostDataObject)) {
4040
- if (key === expectedKey) {
4041
- keyFound = true;
4042
- if (expectedValue === undefined) {
4043
- littleReport += ` ${expectedKey.padStart(10, ' ')}\n`;
4044
- } else if (typeof expectedValue === 'object' && expectedValue.base64) {
4045
- const decodedActualValue = Buffer.from(value, 'base64').toString('utf8');
4046
- if (decodedActualValue === expectedValue.base64) {
4047
- littleReport += ` ${expectedKey.padStart(10, ' ')} = base64(${expectedValue.base64})\n`;
4048
- } else {
4049
- littleReport += ` ✖ ${expectedKey.padStart(10, ' ')} = base64(${expectedValue.base64}) -> actual value: "base64(${decodedActualValue})"\n`;
4050
- success = false;
4051
- }
4052
- } else if (value === expectedValue) {
4053
- littleReport += ` ${expectedKey.padStart(10, ' ')} = ${expectedValue}\n`;
4054
- } else {
4055
- littleReport += ` ✖ ${expectedKey.padStart(10, ' ')} = ${expectedValue} -> actual value: "${value}"\n`;
4056
- success = false;
4057
- }
4058
- }
4059
- }
4060
-
4061
- if (keyFound === false) {
4062
- littleReport += ` ✖ ${expectedKey.padStart(10, ' ')}${expectedValue ? ` = ${JSON.stringify(expectedValue)}` : ''} -> key not found in request\n`;
4063
- success = false;
4064
- }
4065
- }
4066
-
4067
- return success ? true : littleReport;
4068
- };