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
@@ -72,9 +72,12 @@ class REST extends Helper {
72
72
  this.options.maxBodyLength = maxContentLength;
73
73
  }
74
74
 
75
- this.options = { ...this.options, ...config };
75
+ // override defaults with config
76
+ this._setConfig(config);
77
+
76
78
  this.headers = { ...this.options.defaultHeaders };
77
79
  this.axios = axios.create();
80
+ // @ts-ignore
78
81
  this.axios.defaults.headers = this.options.defaultHeaders;
79
82
  }
80
83
 
@@ -2,10 +2,9 @@ let webdriverio;
2
2
 
3
3
  const assert = require('assert');
4
4
  const path = require('path');
5
- const fs = require('fs');
6
5
 
7
6
  const Helper = require('@codeceptjs/helper');
8
- const crypto = require('crypto');
7
+ const promiseRetry = require('promise-retry');
9
8
  const stringIncludes = require('../assert/include').includes;
10
9
  const { urlEquals, equals } = require('../assert/equal');
11
10
  const { debug } = require('../output');
@@ -29,9 +28,14 @@ const ElementNotFound = require('./errors/ElementNotFound');
29
28
  const ConnectionRefused = require('./errors/ConnectionRefused');
30
29
  const Locator = require('../locator');
31
30
  const { highlightElement } = require('./scripts/highlightElement');
32
- const store = require('../store');
33
31
  const { focusElement } = require('./scripts/focusElement');
34
32
  const { blurElement } = require('./scripts/blurElement');
33
+ const {
34
+ dontSeeElementError, seeElementError, seeElementInDOMError, dontSeeElementInDOMError,
35
+ } = require('./errors/ElementAssertion');
36
+ const {
37
+ dontSeeTraffic, seeTraffic, grabRecordedNetworkTraffics, stopRecordingTraffic, flushNetworkTraffics,
38
+ } = require('./network/actions');
35
39
 
36
40
  const SHADOW = 'shadow';
37
41
  const webRoot = 'body';
@@ -448,6 +452,11 @@ class WebDriver extends Helper {
448
452
  this.activeSessionName = '';
449
453
  this.customLocatorStrategies = config.customLocatorStrategies;
450
454
 
455
+ // for network stuff
456
+ this.requests = [];
457
+ this.recording = false;
458
+ this.recordedAtLeastOnce = false;
459
+
451
460
  this._setConfig(config);
452
461
 
453
462
  Locator.addFilter((locator, result) => {
@@ -487,8 +496,9 @@ class WebDriver extends Helper {
487
496
  if (config.host) {
488
497
  // webdriverio spec
489
498
  config.hostname = config.host;
490
- config.path = '/wd/hub';
499
+ config.path = config.path ? config.path : '/wd/hub';
491
500
  }
501
+
492
502
  config.baseUrl = config.url || config.baseUrl;
493
503
  if (config.desiredCapabilities && Object.keys(config.desiredCapabilities).length) {
494
504
  config.capabilities = config.desiredCapabilities;
@@ -629,6 +639,11 @@ class WebDriver extends Helper {
629
639
  if (this.browser.capabilities && this.browser.capabilities.platformName) {
630
640
  this.browser.capabilities.platformName = this.browser.capabilities.platformName.toLowerCase();
631
641
  }
642
+
643
+ if (this.options.automationProtocol) {
644
+ this.puppeteerBrowser = await this.browser.getPuppeteer();
645
+ }
646
+
632
647
  return this.browser;
633
648
  }
634
649
 
@@ -961,12 +976,12 @@ class WebDriver extends Helper {
961
976
  */
962
977
  amOnPage(url) {
963
978
  let split_url;
964
- if (this.config.basicAuth) {
979
+ if (this.options.basicAuth) {
965
980
  if (url.startsWith('/')) {
966
- url = this.config.url + url;
981
+ url = this.options.url + url;
967
982
  }
968
983
  split_url = url.split('//');
969
- url = `${split_url[0]}//${this.config.basicAuth.username}:${this.config.basicAuth.password}@${split_url[1]}`;
984
+ url = `${split_url[0]}//${this.options.basicAuth.username}:${this.options.basicAuth.password}@${split_url[1]}`;
970
985
  }
971
986
  return this.browser.url(url);
972
987
  }
@@ -1461,7 +1476,11 @@ class WebDriver extends Helper {
1461
1476
  const res = await this._locate(locator, true);
1462
1477
  assertElementExists(res, locator);
1463
1478
  const selected = await forEachAsync(res, async el => el.isDisplayed());
1464
- return truth(`elements of ${(new Locator(locator))}`, 'to be seen').assert(selected);
1479
+ try {
1480
+ return truth(`elements of ${(new Locator(locator))}`, 'to be seen').assert(selected);
1481
+ } catch (e) {
1482
+ dontSeeElementError(locator);
1483
+ }
1465
1484
  }
1466
1485
 
1467
1486
  /**
@@ -1474,7 +1493,11 @@ class WebDriver extends Helper {
1474
1493
  return truth(`elements of ${(new Locator(locator))}`, 'to be seen').negate(false);
1475
1494
  }
1476
1495
  const selected = await forEachAsync(res, async el => el.isDisplayed());
1477
- return truth(`elements of ${(new Locator(locator))}`, 'to be seen').negate(selected);
1496
+ try {
1497
+ return truth(`elements of ${(new Locator(locator))}`, 'to be seen').negate(selected);
1498
+ } catch (e) {
1499
+ seeElementError(locator);
1500
+ }
1478
1501
  }
1479
1502
 
1480
1503
  /**
@@ -1483,7 +1506,11 @@ class WebDriver extends Helper {
1483
1506
  */
1484
1507
  async seeElementInDOM(locator) {
1485
1508
  const res = await this._res(locator);
1486
- return empty('elements').negate(res);
1509
+ try {
1510
+ return empty('elements').negate(res);
1511
+ } catch (e) {
1512
+ dontSeeElementInDOMError(locator);
1513
+ }
1487
1514
  }
1488
1515
 
1489
1516
  /**
@@ -1492,7 +1519,11 @@ class WebDriver extends Helper {
1492
1519
  */
1493
1520
  async dontSeeElementInDOM(locator) {
1494
1521
  const res = await this._res(locator);
1495
- return empty('elements').assert(res);
1522
+ try {
1523
+ return empty('elements').assert(res);
1524
+ } catch (e) {
1525
+ seeElementInDOMError(locator);
1526
+ }
1496
1527
  }
1497
1528
 
1498
1529
  /**
@@ -1612,6 +1643,8 @@ class WebDriver extends Helper {
1612
1643
  for (let i = 0; i < val.length; ++i) {
1613
1644
  const _actual = Number.isNaN(val[i]) || (typeof values[i]) === 'string' ? val[i] : Number.parseInt(val[i], 10);
1614
1645
  const _expected = Number.isNaN(values[i]) || (typeof values[i]) === 'string' ? values[i] : Number.parseInt(values[i], 10);
1646
+ // the attribute could be a boolean
1647
+ if (typeof _actual === 'boolean') return _actual === _expected;
1615
1648
  if (_actual !== _expected) return false;
1616
1649
  }
1617
1650
  return true;
@@ -1839,6 +1872,37 @@ class WebDriver extends Helper {
1839
1872
  return cookie[0];
1840
1873
  }
1841
1874
 
1875
+ /**
1876
+ * {{> waitForCookie }}
1877
+ */
1878
+ async waitForCookie(name, sec) {
1879
+ // by default, we will retry 3 times
1880
+ let retries = 3;
1881
+ const waitTimeout = sec || this.options.waitForTimeoutInSeconds;
1882
+
1883
+ if (sec) {
1884
+ retries = sec;
1885
+ } else {
1886
+ retries = waitTimeout - 1;
1887
+ }
1888
+
1889
+ return promiseRetry(async (retry, number) => {
1890
+ const _grabCookie = async (name) => {
1891
+ const cookie = await this.browser.getCookies([name]);
1892
+ if (cookie.length === 0) throw Error(`Cookie ${name} is not found after ${retries}s`);
1893
+ };
1894
+
1895
+ this.debugSection('Wait for cookie: ', name);
1896
+ if (number > 1) this.debugSection('Retrying... Attempt #', number);
1897
+
1898
+ try {
1899
+ await _grabCookie(name);
1900
+ } catch (e) {
1901
+ retry(e);
1902
+ }
1903
+ }, { retries, maxTimeout: 1000 });
1904
+ }
1905
+
1842
1906
  /**
1843
1907
  * Accepts the active JavaScript native popup window, as created by window.alert|window.confirm|window.prompt.
1844
1908
  * Don't confuse popups with modal windows, as created by [various
@@ -2550,9 +2614,9 @@ class WebDriver extends Helper {
2550
2614
  return;
2551
2615
  }
2552
2616
  this.geoLocation = { latitude, longitude };
2553
- const puppeteerBrowser = await this.browser.getPuppeteer();
2617
+
2554
2618
  await this.browser.call(async () => {
2555
- const pages = await puppeteerBrowser.pages();
2619
+ const pages = await this.puppeteerBrowser.pages();
2556
2620
  await pages[0].setGeolocation({ latitude, longitude });
2557
2621
  });
2558
2622
  }
@@ -2614,6 +2678,118 @@ class WebDriver extends Helper {
2614
2678
  runInWeb(fn) {
2615
2679
  return fn();
2616
2680
  }
2681
+
2682
+ /**
2683
+ *
2684
+ * _Note:_ Only works when devtoolsProtocol is enabled.
2685
+ *
2686
+ * {{> flushNetworkTraffics }}
2687
+ */
2688
+ flushNetworkTraffics() {
2689
+ if (!this.options.automationProtocol) {
2690
+ console.log('* Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration');
2691
+ return;
2692
+ }
2693
+ this.requests = [];
2694
+ }
2695
+
2696
+ /**
2697
+ *
2698
+ * _Note:_ Only works when devtoolsProtocol is enabled.
2699
+ *
2700
+ * {{> stopRecordingTraffic }}
2701
+ */
2702
+ stopRecordingTraffic() {
2703
+ if (!this.options.automationProtocol) {
2704
+ console.log('* Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration');
2705
+ return;
2706
+ }
2707
+ this.page.removeAllListeners('request');
2708
+ this.recording = false;
2709
+ }
2710
+
2711
+ /**
2712
+ *
2713
+ * _Note:_ Only works when devtoolsProtocol is enabled.
2714
+ *
2715
+ * {{> startRecordingTraffic }}
2716
+ *
2717
+ */
2718
+ async startRecordingTraffic() {
2719
+ if (!this.options.automationProtocol) {
2720
+ console.log('* Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration');
2721
+ return;
2722
+ }
2723
+ this.flushNetworkTraffics();
2724
+ this.recording = true;
2725
+ this.recordedAtLeastOnce = true;
2726
+
2727
+ this.page = (await this.puppeteerBrowser.pages())[0];
2728
+ await this.page.setRequestInterception(true);
2729
+
2730
+ this.page.on('request', (request) => {
2731
+ const information = {
2732
+ url: request.url(),
2733
+ method: request.method(),
2734
+ requestHeaders: request.headers(),
2735
+ requestPostData: request.postData(),
2736
+ response: request.response(),
2737
+ };
2738
+
2739
+ this.debugSection('REQUEST: ', JSON.stringify(information));
2740
+
2741
+ if (typeof information.requestPostData === 'object') {
2742
+ information.requestPostData = JSON.parse(information.requestPostData);
2743
+ }
2744
+ request.continue();
2745
+ this.requests.push(information);
2746
+ });
2747
+ }
2748
+
2749
+ /**
2750
+ *
2751
+ * _Note:_ Only works when devtoolsProtocol is enabled.
2752
+ *
2753
+ * {{> grabRecordedNetworkTraffics }}
2754
+ */
2755
+ async grabRecordedNetworkTraffics() {
2756
+ if (!this.options.automationProtocol) {
2757
+ console.log('* Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration');
2758
+ return;
2759
+ }
2760
+ return grabRecordedNetworkTraffics.call(this);
2761
+ }
2762
+
2763
+ /**
2764
+ *
2765
+ * _Note:_ Only works when devtoolsProtocol is enabled.
2766
+ *
2767
+ * {{> seeTraffic }}
2768
+ */
2769
+ async seeTraffic({
2770
+ name, url, parameters, requestPostData, timeout = 10,
2771
+ }) {
2772
+ if (!this.options.automationProtocol) {
2773
+ console.log('* Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration');
2774
+ return;
2775
+ }
2776
+ await seeTraffic.call(this, ...arguments);
2777
+ }
2778
+
2779
+ /**
2780
+ *
2781
+ * _Note:_ Only works when devtoolsProtocol is enabled.
2782
+ *
2783
+ * {{> dontSeeTraffic }}
2784
+ *
2785
+ */
2786
+ dontSeeTraffic({ name, url }) {
2787
+ if (!this.options.automationProtocol) {
2788
+ console.log('* Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration');
2789
+ return;
2790
+ }
2791
+ dontSeeTraffic.call(this, ...arguments);
2792
+ }
2617
2793
  }
2618
2794
 
2619
2795
  async function proceedSee(assertType, text, context, strict = false) {
@@ -0,0 +1,38 @@
1
+ const Locator = require('../../locator');
2
+
3
+ const prefixMessage = 'Element';
4
+
5
+ function seeElementError(locator) {
6
+ if (typeof locator === 'object') {
7
+ locator = JSON.stringify(locator);
8
+ }
9
+ throw new Error(`${prefixMessage} "${(new Locator(locator))}" is still visible on page.`);
10
+ }
11
+
12
+ function seeElementInDOMError(locator) {
13
+ if (typeof locator === 'object') {
14
+ locator = JSON.stringify(locator);
15
+ }
16
+ throw new Error(`${prefixMessage} "${(new Locator(locator))}" is still seen in DOM.`);
17
+ }
18
+
19
+ function dontSeeElementError(locator) {
20
+ if (typeof locator === 'object') {
21
+ locator = JSON.stringify(locator);
22
+ }
23
+ throw new Error(`${prefixMessage} "${(new Locator(locator))}" is not visible on page.`);
24
+ }
25
+
26
+ function dontSeeElementInDOMError(locator) {
27
+ if (typeof locator === 'object') {
28
+ locator = JSON.stringify(locator);
29
+ }
30
+ throw new Error(`${prefixMessage} "${(new Locator(locator))}" is not seen in DOM.`);
31
+ }
32
+
33
+ module.exports = {
34
+ seeElementError,
35
+ dontSeeElementError,
36
+ seeElementInDOMError,
37
+ dontSeeElementInDOMError,
38
+ };
@@ -20,6 +20,11 @@ async function findVue(matcher, locator) {
20
20
  return matcher.locator(_locator).all();
21
21
  }
22
22
 
23
+ async function findByPlaywrightLocator(matcher, locator) {
24
+ if (locator && locator.toString().includes(process.env.testIdAttribute)) return matcher.getByTestId(locator.pw.value.split('=')[1]);
25
+ return matcher.locator(locator.pw).all();
26
+ }
27
+
23
28
  function propBuilder(props) {
24
29
  let _props = '';
25
30
 
@@ -35,4 +40,4 @@ function propBuilder(props) {
35
40
  return _props;
36
41
  }
37
42
 
38
- module.exports = { findReact, findVue };
43
+ module.exports = { findReact, findVue, findByPlaywrightLocator };
@@ -0,0 +1,123 @@
1
+ const assert = require('assert');
2
+ const { isInTraffic, createAdvancedTestResults, getTrafficDump } = require('./utils');
3
+
4
+ function dontSeeTraffic({ name, url }) {
5
+ if (!this.recordedAtLeastOnce) {
6
+ throw new Error('Failure in test automation. You use "I.dontSeeTraffic", but "I.startRecordingTraffic" was never called before.');
7
+ }
8
+
9
+ if (!name) {
10
+ throw new Error('Missing required key "name" in object given to "I.dontSeeTraffic".');
11
+ }
12
+
13
+ if (!url) {
14
+ throw new Error('Missing required key "url" in object given to "I.dontSeeTraffic".');
15
+ }
16
+
17
+ if (isInTraffic.call(this, url)) {
18
+ assert.fail(`Traffic with name "${name}" (URL: "${url}') found, but was not expected to be found.`);
19
+ }
20
+ }
21
+
22
+ async function seeTraffic({
23
+ name, url, parameters, requestPostData, timeout = 10,
24
+ }) {
25
+ if (!name) {
26
+ throw new Error('Missing required key "name" in object given to "I.seeTraffic".');
27
+ }
28
+
29
+ if (!url) {
30
+ throw new Error('Missing required key "url" in object given to "I.seeTraffic".');
31
+ }
32
+
33
+ if (!this.recording || !this.recordedAtLeastOnce) {
34
+ throw new Error('Failure in test automation. You use "I.seeTraffic", but "I.startRecordingTraffic" was never called before.');
35
+ }
36
+
37
+ for (let i = 0; i <= timeout * 2; i++) {
38
+ const found = isInTraffic.call(this, url, parameters);
39
+ if (found) {
40
+ return true;
41
+ }
42
+ await new Promise((done) => {
43
+ setTimeout(done, 1000);
44
+ });
45
+ }
46
+
47
+ // check request post data
48
+ if (requestPostData && isInTraffic.call(this, url)) {
49
+ const advancedTestResults = createAdvancedTestResults(url, requestPostData, this.requests);
50
+
51
+ assert.equal(advancedTestResults, true, `Traffic named "${name}" found correct URL ${url}, BUT the post data did not match:\n ${advancedTestResults}`);
52
+ } else if (parameters && isInTraffic.call(this, url)) {
53
+ const advancedTestResults = createAdvancedTestResults(url, parameters, this.requests);
54
+
55
+ assert.fail(
56
+ `Traffic named "${name}" found correct URL ${url}, BUT the query parameters did not match:\n`
57
+ + `${advancedTestResults}`,
58
+ );
59
+ } else {
60
+ assert.fail(
61
+ `Traffic named "${name}" not found in recorded traffic within ${timeout} seconds.\n`
62
+ + `Expected url: ${url}.\n`
63
+ + `Recorded traffic:\n${getTrafficDump.call(this)}`,
64
+ );
65
+ }
66
+ }
67
+
68
+ async function grabRecordedNetworkTraffics() {
69
+ if (!this.recording || !this.recordedAtLeastOnce) {
70
+ throw new Error('Failure in test automation. You use "I.grabRecordedNetworkTraffics", but "I.startRecordingTraffic" was never called before.');
71
+ }
72
+
73
+ const promises = this.requests.map(async (request) => {
74
+ const resp = await request.response;
75
+
76
+ if (!resp) {
77
+ return {
78
+ url: '',
79
+ response: {
80
+ status: '',
81
+ statusText: '',
82
+ body: '',
83
+ },
84
+ };
85
+ }
86
+
87
+ let body;
88
+ try {
89
+ // There's no 'body' for some requests (redirect etc...)
90
+ body = JSON.parse((await resp.body()).toString());
91
+ } catch (e) {
92
+ // only interested in JSON, not HTML responses.
93
+ }
94
+
95
+ return {
96
+ url: resp.url(),
97
+ response: {
98
+ status: resp.status(),
99
+ statusText: resp.statusText(),
100
+ body,
101
+ },
102
+ };
103
+ });
104
+ return Promise.all(promises);
105
+ }
106
+
107
+ function stopRecordingTraffic() {
108
+ // @ts-ignore
109
+ this.page.removeAllListeners('request');
110
+ this.recording = false;
111
+ }
112
+
113
+ function flushNetworkTraffics() {
114
+ this.requests = [];
115
+ }
116
+
117
+ module.exports = {
118
+ dontSeeTraffic,
119
+ seeTraffic,
120
+ grabRecordedNetworkTraffics,
121
+ stopRecordingTraffic,
122
+ flushNetworkTraffics,
123
+ };
@@ -0,0 +1,187 @@
1
+ const createAdvancedTestResults = (url, dataToCheck, requests) => {
2
+ // Creates advanced test results for a network traffic check.
3
+ // Advanced test results only applies when expected parameters are set
4
+ if (!dataToCheck) return '';
5
+
6
+ let urlFound = false;
7
+ let advancedResults;
8
+ requests.forEach((request) => {
9
+ // url not found in this request. continue with next request
10
+ if (urlFound || !request.url.match(new RegExp(url))) return;
11
+ urlFound = true;
12
+
13
+ // Url found. Now we create advanced test report for that URL and show which parameters failed
14
+ if (!request.requestPostData) {
15
+ advancedResults = allParameterValuePairsMatchExtreme(extractQueryObjects(request.url), dataToCheck);
16
+ } else if (request.requestPostData) {
17
+ advancedResults = allRequestPostDataValuePairsMatchExtreme(request.requestPostData, dataToCheck);
18
+ }
19
+ });
20
+ return advancedResults;
21
+ };
22
+
23
+ const extractQueryObjects = (queryString) => {
24
+ // Converts a string of GET parameters into an array of parameter objects. Each parameter object contains the properties "name" and "value".
25
+ if (queryString.indexOf('?') === -1) {
26
+ return [];
27
+ }
28
+ const queryObjects = [];
29
+
30
+ const queryPart = queryString.split('?')[1];
31
+
32
+ const queryParameters = queryPart.split('&');
33
+
34
+ queryParameters.forEach((queryParameter) => {
35
+ const keyValue = queryParameter.split('=');
36
+ const queryObject = {};
37
+ // eslint-disable-next-line prefer-destructuring
38
+ queryObject.name = keyValue[0];
39
+ queryObject.value = decodeURIComponent(keyValue[1]);
40
+ queryObjects.push(queryObject);
41
+ });
42
+
43
+ return queryObjects;
44
+ };
45
+
46
+ const allParameterValuePairsMatchExtreme = (queryStringObject, advancedExpectedParameterValuePairs) => {
47
+ // More advanced check if all request parameters match with the expectations
48
+ let littleReport = '\nQuery parameters:\n';
49
+ let success = true;
50
+
51
+ for (const expectedKey in advancedExpectedParameterValuePairs) {
52
+ if (!Object.prototype.hasOwnProperty.call(advancedExpectedParameterValuePairs, expectedKey)) {
53
+ continue;
54
+ }
55
+ let parameterFound = false;
56
+ const expectedValue = advancedExpectedParameterValuePairs[expectedKey];
57
+
58
+ for (const queryParameter of queryStringObject) {
59
+ if (queryParameter.name === expectedKey) {
60
+ parameterFound = true;
61
+ if (expectedValue === undefined) {
62
+ littleReport += ` ${expectedKey.padStart(10, ' ')}\n`;
63
+ } else if (typeof expectedValue === 'object' && expectedValue.base64) {
64
+ const decodedActualValue = Buffer.from(queryParameter.value, 'base64').toString('utf8');
65
+ if (decodedActualValue === expectedValue.base64) {
66
+ littleReport += ` ${expectedKey.padStart(10, ' ')} = base64(${expectedValue.base64})\n`;
67
+ } else {
68
+ littleReport += ` ✖ ${expectedKey.padStart(10, ' ')} = base64(${expectedValue.base64}) -> actual value: "base64(${decodedActualValue})"\n`;
69
+ success = false;
70
+ }
71
+ } else if (queryParameter.value === expectedValue) {
72
+ littleReport += ` ${expectedKey.padStart(10, ' ')} = ${expectedValue}\n`;
73
+ } else {
74
+ littleReport += ` ✖ ${expectedKey.padStart(10, ' ')} = ${expectedValue} -> actual value: "${queryParameter.value}"\n`;
75
+ success = false;
76
+ }
77
+ }
78
+ }
79
+
80
+ if (parameterFound === false) {
81
+ littleReport += ` ✖ ${expectedKey.padStart(10, ' ')}${expectedValue ? ` = ${JSON.stringify(expectedValue)}` : ''} -> parameter not found in request\n`;
82
+ success = false;
83
+ }
84
+ }
85
+
86
+ return success ? true : littleReport;
87
+ };
88
+
89
+ const allRequestPostDataValuePairsMatchExtreme = (RequestPostDataObject, advancedExpectedRequestPostValuePairs) => {
90
+ // More advanced check if all request post data match with the expectations
91
+ let littleReport = '\nRequest Post Data:\n';
92
+ let success = true;
93
+
94
+ for (const expectedKey in advancedExpectedRequestPostValuePairs) {
95
+ if (!Object.prototype.hasOwnProperty.call(advancedExpectedRequestPostValuePairs, expectedKey)) {
96
+ continue;
97
+ }
98
+ let keyFound = false;
99
+ const expectedValue = advancedExpectedRequestPostValuePairs[expectedKey];
100
+
101
+ for (const [key, value] of Object.entries(RequestPostDataObject)) {
102
+ if (key === expectedKey) {
103
+ keyFound = true;
104
+ if (expectedValue === undefined) {
105
+ littleReport += ` ${expectedKey.padStart(10, ' ')}\n`;
106
+ } else if (typeof expectedValue === 'object' && expectedValue.base64) {
107
+ const decodedActualValue = Buffer.from(value, 'base64').toString('utf8');
108
+ if (decodedActualValue === expectedValue.base64) {
109
+ littleReport += ` ${expectedKey.padStart(10, ' ')} = base64(${expectedValue.base64})\n`;
110
+ } else {
111
+ littleReport += ` ✖ ${expectedKey.padStart(10, ' ')} = base64(${expectedValue.base64}) -> actual value: "base64(${decodedActualValue})"\n`;
112
+ success = false;
113
+ }
114
+ } else if (value === expectedValue) {
115
+ littleReport += ` ${expectedKey.padStart(10, ' ')} = ${expectedValue}\n`;
116
+ } else {
117
+ littleReport += ` ✖ ${expectedKey.padStart(10, ' ')} = ${expectedValue} -> actual value: "${value}"\n`;
118
+ success = false;
119
+ }
120
+ }
121
+ }
122
+
123
+ if (keyFound === false) {
124
+ littleReport += ` ✖ ${expectedKey.padStart(10, ' ')}${expectedValue ? ` = ${JSON.stringify(expectedValue)}` : ''} -> key not found in request\n`;
125
+ success = false;
126
+ }
127
+ }
128
+
129
+ return success ? true : littleReport;
130
+ };
131
+
132
+ /**
133
+ * Returns all URLs of all network requests recorded so far during execution of test scenario.
134
+ *
135
+ * @return {string} List of URLs recorded as a string, separated by new lines after each URL
136
+ * @private
137
+ */
138
+ function getTrafficDump() {
139
+ let dumpedTraffic = '';
140
+ this.requests.forEach((request) => {
141
+ dumpedTraffic += `${request.method} - ${request.url}\n`;
142
+ });
143
+ return dumpedTraffic;
144
+ }
145
+
146
+ /**
147
+ * Checks if URL with parameters is part of network traffic. Returns true or false. Internal method for this helper.
148
+ *
149
+ * @param url URL to look for.
150
+ * @param [parameters] Parameters that this URL needs to contain
151
+ * @return {boolean} Whether or not URL with parameters is part of network traffic.
152
+ * @private
153
+ */
154
+ function isInTraffic(url, parameters) {
155
+ let isInTraffic = false;
156
+ this.requests.forEach((request) => {
157
+ if (isInTraffic) {
158
+ return; // We already found traffic. Continue with next request
159
+ }
160
+
161
+ if (!request.url.match(new RegExp(url))) {
162
+ return; // url not found in this request. continue with next request
163
+ }
164
+
165
+ // URL has matched. Now we check the parameters
166
+
167
+ if (parameters) {
168
+ const advancedReport = allParameterValuePairsMatchExtreme(extractQueryObjects(request.url), parameters);
169
+ if (advancedReport === true) {
170
+ isInTraffic = true;
171
+ }
172
+ } else {
173
+ isInTraffic = true;
174
+ }
175
+ });
176
+
177
+ return isInTraffic;
178
+ }
179
+
180
+ module.exports = {
181
+ createAdvancedTestResults,
182
+ extractQueryObjects,
183
+ allParameterValuePairsMatchExtreme,
184
+ allRequestPostDataValuePairsMatchExtreme,
185
+ getTrafficDump,
186
+ isInTraffic,
187
+ };