codeceptjs 3.6.0-beta.1.ai-healers → 3.6.0

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 (129) 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/plugin/coverage.js +112 -99
  28. package/lib/step.js +3 -1
  29. package/package.json +48 -37
  30. package/typings/index.d.ts +19 -2
  31. package/typings/promiseBasedTypes.d.ts +505 -41
  32. package/typings/types.d.ts +609 -56
  33. package/docs/advanced.md +0 -351
  34. package/docs/ai.md +0 -365
  35. package/docs/api.md +0 -323
  36. package/docs/basics.md +0 -979
  37. package/docs/bdd.md +0 -539
  38. package/docs/best.md +0 -237
  39. package/docs/books.md +0 -37
  40. package/docs/bootstrap.md +0 -135
  41. package/docs/build/AI.js +0 -124
  42. package/docs/build/ApiDataFactory.js +0 -410
  43. package/docs/build/Appium.js +0 -2027
  44. package/docs/build/Expect.js +0 -422
  45. package/docs/build/FileSystem.js +0 -228
  46. package/docs/build/GraphQL.js +0 -229
  47. package/docs/build/GraphQLDataFactory.js +0 -309
  48. package/docs/build/JSONResponse.js +0 -338
  49. package/docs/build/Mochawesome.js +0 -71
  50. package/docs/build/Nightmare.js +0 -2152
  51. package/docs/build/OpenAI.js +0 -126
  52. package/docs/build/Playwright.js +0 -5110
  53. package/docs/build/Protractor.js +0 -2706
  54. package/docs/build/Puppeteer.js +0 -3905
  55. package/docs/build/REST.js +0 -344
  56. package/docs/build/TestCafe.js +0 -2125
  57. package/docs/build/WebDriver.js +0 -4240
  58. package/docs/changelog.md +0 -2572
  59. package/docs/commands.md +0 -266
  60. package/docs/community-helpers.md +0 -58
  61. package/docs/configuration.md +0 -157
  62. package/docs/continuous-integration.md +0 -22
  63. package/docs/custom-helpers.md +0 -306
  64. package/docs/data.md +0 -379
  65. package/docs/detox.md +0 -235
  66. package/docs/docker.md +0 -136
  67. package/docs/email.md +0 -183
  68. package/docs/examples.md +0 -149
  69. package/docs/heal.md +0 -186
  70. package/docs/helpers/ApiDataFactory.md +0 -266
  71. package/docs/helpers/Appium.md +0 -1374
  72. package/docs/helpers/Detox.md +0 -586
  73. package/docs/helpers/Expect.md +0 -275
  74. package/docs/helpers/FileSystem.md +0 -152
  75. package/docs/helpers/GraphQL.md +0 -151
  76. package/docs/helpers/GraphQLDataFactory.md +0 -226
  77. package/docs/helpers/JSONResponse.md +0 -254
  78. package/docs/helpers/Mochawesome.md +0 -8
  79. package/docs/helpers/MockRequest.md +0 -377
  80. package/docs/helpers/Nightmare.md +0 -1305
  81. package/docs/helpers/OpenAI.md +0 -70
  82. package/docs/helpers/Playwright.md +0 -2759
  83. package/docs/helpers/Polly.md +0 -44
  84. package/docs/helpers/Protractor.md +0 -1769
  85. package/docs/helpers/Puppeteer-firefox.md +0 -86
  86. package/docs/helpers/Puppeteer.md +0 -2317
  87. package/docs/helpers/REST.md +0 -218
  88. package/docs/helpers/TestCafe.md +0 -1321
  89. package/docs/helpers/WebDriver.md +0 -2547
  90. package/docs/hooks.md +0 -340
  91. package/docs/index.md +0 -111
  92. package/docs/installation.md +0 -75
  93. package/docs/internal-api.md +0 -266
  94. package/docs/locators.md +0 -339
  95. package/docs/mobile-react-native-locators.md +0 -67
  96. package/docs/mobile.md +0 -338
  97. package/docs/pageobjects.md +0 -291
  98. package/docs/parallel.md +0 -400
  99. package/docs/playwright.md +0 -632
  100. package/docs/plugins.md +0 -1247
  101. package/docs/puppeteer.md +0 -316
  102. package/docs/quickstart.md +0 -162
  103. package/docs/react.md +0 -70
  104. package/docs/reports.md +0 -392
  105. package/docs/secrets.md +0 -36
  106. package/docs/shadow.md +0 -68
  107. package/docs/shared/keys.mustache +0 -31
  108. package/docs/shared/react.mustache +0 -1
  109. package/docs/testcafe.md +0 -174
  110. package/docs/translation.md +0 -247
  111. package/docs/tutorial.md +0 -271
  112. package/docs/typescript.md +0 -180
  113. package/docs/ui.md +0 -59
  114. package/docs/videos.md +0 -28
  115. package/docs/visual.md +0 -202
  116. package/docs/vue.md +0 -143
  117. package/docs/webdriver.md +0 -701
  118. package/docs/wiki/Books-&-Posts.md +0 -27
  119. package/docs/wiki/Community-Helpers-&-Plugins.md +0 -53
  120. package/docs/wiki/Converting-Playwright-to-Istanbul-Coverage.md +0 -61
  121. package/docs/wiki/Examples.md +0 -145
  122. package/docs/wiki/Google-Summer-of-Code-(GSoC)-2020.md +0 -68
  123. package/docs/wiki/Home.md +0 -16
  124. package/docs/wiki/Migration-to-Appium-v2---CodeceptJS.md +0 -83
  125. package/docs/wiki/Release-Process.md +0 -24
  126. package/docs/wiki/Roadmap.md +0 -23
  127. package/docs/wiki/Tests.md +0 -1393
  128. package/docs/wiki/Upgrading-to-CodeceptJS-3.md +0 -153
  129. 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
+ };