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

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 (139) hide show
  1. package/README.md +2 -2
  2. package/bin/codecept.js +66 -30
  3. package/docs/advanced.md +351 -0
  4. package/docs/ai.md +365 -0
  5. package/docs/api.md +323 -0
  6. package/docs/basics.md +979 -0
  7. package/docs/bdd.md +539 -0
  8. package/docs/best.md +237 -0
  9. package/docs/books.md +37 -0
  10. package/docs/bootstrap.md +135 -0
  11. package/docs/build/AI.js +124 -0
  12. package/docs/build/ApiDataFactory.js +410 -0
  13. package/docs/build/Appium.js +2027 -0
  14. package/docs/build/Expect.js +422 -0
  15. package/docs/build/FileSystem.js +228 -0
  16. package/docs/build/GraphQL.js +229 -0
  17. package/docs/build/GraphQLDataFactory.js +309 -0
  18. package/docs/build/JSONResponse.js +338 -0
  19. package/docs/build/Mochawesome.js +71 -0
  20. package/docs/build/Nightmare.js +2152 -0
  21. package/docs/build/Playwright.js +5110 -0
  22. package/docs/build/Protractor.js +2706 -0
  23. package/docs/build/Puppeteer.js +3905 -0
  24. package/docs/build/REST.js +344 -0
  25. package/docs/build/TestCafe.js +2125 -0
  26. package/docs/build/WebDriver.js +4240 -0
  27. package/docs/changelog.md +2572 -0
  28. package/docs/commands.md +266 -0
  29. package/docs/community-helpers.md +58 -0
  30. package/docs/configuration.md +157 -0
  31. package/docs/continuous-integration.md +22 -0
  32. package/docs/custom-helpers.md +306 -0
  33. package/docs/data.md +379 -0
  34. package/docs/detox.md +235 -0
  35. package/docs/docker.md +136 -0
  36. package/docs/email.md +183 -0
  37. package/docs/examples.md +149 -0
  38. package/docs/heal.md +186 -0
  39. package/docs/helpers/ApiDataFactory.md +266 -0
  40. package/docs/helpers/Appium.md +1374 -0
  41. package/docs/helpers/Detox.md +586 -0
  42. package/docs/helpers/Expect.md +275 -0
  43. package/docs/helpers/FileSystem.md +152 -0
  44. package/docs/helpers/GraphQL.md +151 -0
  45. package/docs/helpers/GraphQLDataFactory.md +226 -0
  46. package/docs/helpers/JSONResponse.md +254 -0
  47. package/docs/helpers/Mochawesome.md +8 -0
  48. package/docs/helpers/MockRequest.md +377 -0
  49. package/docs/helpers/Nightmare.md +1305 -0
  50. package/docs/helpers/OpenAI.md +70 -0
  51. package/docs/helpers/Playwright.md +2759 -0
  52. package/docs/helpers/Polly.md +44 -0
  53. package/docs/helpers/Protractor.md +1769 -0
  54. package/docs/helpers/Puppeteer-firefox.md +86 -0
  55. package/docs/helpers/Puppeteer.md +2317 -0
  56. package/docs/helpers/REST.md +218 -0
  57. package/docs/helpers/TestCafe.md +1321 -0
  58. package/docs/helpers/WebDriver.md +2547 -0
  59. package/docs/hooks.md +340 -0
  60. package/docs/index.md +111 -0
  61. package/docs/installation.md +75 -0
  62. package/docs/internal-api.md +266 -0
  63. package/docs/locators.md +339 -0
  64. package/docs/mobile-react-native-locators.md +67 -0
  65. package/docs/mobile.md +338 -0
  66. package/docs/pageobjects.md +291 -0
  67. package/docs/parallel.md +400 -0
  68. package/docs/playwright.md +632 -0
  69. package/docs/plugins.md +1247 -0
  70. package/docs/puppeteer.md +316 -0
  71. package/docs/quickstart.md +162 -0
  72. package/docs/react.md +70 -0
  73. package/docs/reports.md +392 -0
  74. package/docs/secrets.md +36 -0
  75. package/docs/shadow.md +68 -0
  76. package/docs/shared/keys.mustache +31 -0
  77. package/docs/shared/react.mustache +1 -0
  78. package/docs/testcafe.md +174 -0
  79. package/docs/translation.md +247 -0
  80. package/docs/tutorial.md +271 -0
  81. package/docs/typescript.md +180 -0
  82. package/docs/ui.md +59 -0
  83. package/docs/videos.md +28 -0
  84. package/docs/visual.md +202 -0
  85. package/docs/vue.md +143 -0
  86. package/docs/webdriver.md +701 -0
  87. package/docs/wiki/Books-&-Posts.md +27 -0
  88. package/docs/wiki/Community-Helpers-&-Plugins.md +53 -0
  89. package/docs/wiki/Converting-Playwright-to-Istanbul-Coverage.md +61 -0
  90. package/docs/wiki/Examples.md +145 -0
  91. package/docs/wiki/Google-Summer-of-Code-(GSoC)-2020.md +68 -0
  92. package/docs/wiki/Home.md +16 -0
  93. package/docs/wiki/Migration-to-Appium-v2---CodeceptJS.md +83 -0
  94. package/docs/wiki/Release-Process.md +24 -0
  95. package/docs/wiki/Roadmap.md +23 -0
  96. package/docs/wiki/Tests.md +1393 -0
  97. package/docs/wiki/Upgrading-to-CodeceptJS-3.md +153 -0
  98. package/docs/wiki/Videos.md +19 -0
  99. package/lib/actor.js +3 -6
  100. package/lib/ai.js +152 -80
  101. package/lib/cli.js +1 -0
  102. package/lib/command/dryRun.js +13 -44
  103. package/lib/command/generate.js +34 -0
  104. package/lib/command/run-workers.js +3 -0
  105. package/lib/command/run.js +3 -0
  106. package/lib/container.js +2 -0
  107. package/lib/heal.js +172 -0
  108. package/lib/helper/AI.js +124 -0
  109. package/lib/helper/Appium.js +12 -36
  110. package/lib/helper/Expect.js +8 -11
  111. package/lib/helper/JSONResponse.js +8 -8
  112. package/lib/helper/Playwright.js +240 -100
  113. package/lib/helper/Puppeteer.js +68 -182
  114. package/lib/helper/REST.js +1 -4
  115. package/lib/helper/WebDriver.js +10 -324
  116. package/lib/index.js +3 -0
  117. package/lib/listener/steps.js +0 -2
  118. package/lib/locator.js +4 -13
  119. package/lib/plugin/coverage.js +99 -112
  120. package/lib/plugin/heal.js +26 -117
  121. package/lib/recorder.js +11 -5
  122. package/lib/step.js +1 -3
  123. package/lib/store.js +2 -0
  124. package/lib/template/heal.js +39 -0
  125. package/package.json +35 -47
  126. package/typings/index.d.ts +0 -17
  127. package/typings/promiseBasedTypes.d.ts +57 -340
  128. package/typings/types.d.ts +73 -433
  129. package/docs/webapi/dontSeeTraffic.mustache +0 -13
  130. package/docs/webapi/flushNetworkTraffics.mustache +0 -5
  131. package/docs/webapi/grabRecordedNetworkTraffics.mustache +0 -10
  132. package/docs/webapi/seeTraffic.mustache +0 -36
  133. package/docs/webapi/startRecordingTraffic.mustache +0 -8
  134. package/docs/webapi/stopRecordingTraffic.mustache +0 -5
  135. package/docs/webapi/waitForCookie.mustache +0 -9
  136. package/lib/helper/MockServer.js +0 -221
  137. package/lib/helper/errors/ElementAssertion.js +0 -38
  138. package/lib/helper/networkTraffics/utils.js +0 -137
  139. /package/{lib/helper → docs/build}/OpenAI.js +0 -0
@@ -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');
8
7
  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');
@@ -47,11 +47,6 @@ 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 { createAdvancedTestResults, allParameterValuePairsMatchExtreme, extractQueryObjects } = require('./networkTraffics/utils');
54
- const { log } = require('../output');
55
50
 
56
51
  const pathSeparator = path.sep;
57
52
 
@@ -750,7 +745,9 @@ class Playwright extends Helper {
750
745
  });
751
746
  this.context = await this.page;
752
747
  this.contextLocator = null;
753
- await page.bringToFront();
748
+ if (this.options.browser === 'chrome') {
749
+ await page.bringToFront();
750
+ }
754
751
  }
755
752
 
756
753
  /**
@@ -1454,11 +1451,7 @@ class Playwright extends Helper {
1454
1451
  async seeElement(locator) {
1455
1452
  let els = await this._locate(locator);
1456
1453
  els = await Promise.all(els.map(el => el.isVisible()));
1457
- try {
1458
- return empty('visible elements').negate(els.filter(v => v).fill('ELEMENT'));
1459
- } catch (e) {
1460
- dontSeeElementError(locator);
1461
- }
1454
+ return empty('visible elements').negate(els.filter(v => v).fill('ELEMENT'));
1462
1455
  }
1463
1456
 
1464
1457
  /**
@@ -1468,11 +1461,7 @@ class Playwright extends Helper {
1468
1461
  async dontSeeElement(locator) {
1469
1462
  let els = await this._locate(locator);
1470
1463
  els = await Promise.all(els.map(el => el.isVisible()));
1471
- try {
1472
- return empty('visible elements').assert(els.filter(v => v).fill('ELEMENT'));
1473
- } catch (e) {
1474
- seeElementError(locator);
1475
- }
1464
+ return empty('visible elements').assert(els.filter(v => v).fill('ELEMENT'));
1476
1465
  }
1477
1466
 
1478
1467
  /**
@@ -1480,11 +1469,7 @@ class Playwright extends Helper {
1480
1469
  */
1481
1470
  async seeElementInDOM(locator) {
1482
1471
  const els = await this._locate(locator);
1483
- try {
1484
- return empty('elements on page').negate(els.filter(v => v).fill('ELEMENT'));
1485
- } catch (e) {
1486
- dontSeeElementInDOMError(locator);
1487
- }
1472
+ return empty('elements on page').negate(els.filter(v => v).fill('ELEMENT'));
1488
1473
  }
1489
1474
 
1490
1475
  /**
@@ -1492,11 +1477,7 @@ class Playwright extends Helper {
1492
1477
  */
1493
1478
  async dontSeeElementInDOM(locator) {
1494
1479
  const els = await this._locate(locator);
1495
- try {
1496
- return empty('elements on a page').assert(els.filter(v => v).fill('ELEMENT'));
1497
- } catch (e) {
1498
- seeElementInDOMError(locator);
1499
- }
1480
+ return empty('elements on a page').assert(els.filter(v => v).fill('ELEMENT'));
1500
1481
  }
1501
1482
 
1502
1483
  /**
@@ -1805,7 +1786,7 @@ class Playwright extends Helper {
1805
1786
  let optionToSelect = '';
1806
1787
 
1807
1788
  try {
1808
- optionToSelect = (await el.locator('option', { hasText: option }).textContent()).trim();
1789
+ optionToSelect = await el.locator('option', { hasText: option }).textContent();
1809
1790
  } catch (e) {
1810
1791
  optionToSelect = option;
1811
1792
  }
@@ -2188,8 +2169,6 @@ class Playwright extends Helper {
2188
2169
  let chunked = chunkArray(attrs, values.length);
2189
2170
  chunked = chunked.filter((val) => {
2190
2171
  for (let i = 0; i < val.length; ++i) {
2191
- // the attribute could be a boolean
2192
- if (typeof val[i] === 'boolean') return val[i] === values[i];
2193
2172
  // if the attribute doesn't exist, returns false as well
2194
2173
  if (!val[i] || !val[i].includes(values[i])) return false;
2195
2174
  }
@@ -2678,7 +2657,6 @@ class Playwright extends Helper {
2678
2657
  */
2679
2658
  async waitForText(text, sec = null, context = null) {
2680
2659
  const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
2681
- const errorMessage = `Text "${text}" was not found on page after ${waitTimeout / 1000} sec.`;
2682
2660
  let waiter;
2683
2661
 
2684
2662
  const contextObject = await this._getContext();
@@ -2689,21 +2667,18 @@ class Playwright extends Helper {
2689
2667
  try {
2690
2668
  await contextObject.locator(`${locator.isCustom() ? `${locator.type}=${locator.value}` : locator.simplify()} >> text=${text}`).first().waitFor({ timeout: waitTimeout, state: 'visible' });
2691
2669
  } catch (e) {
2692
- throw new Error(`${errorMessage}\n${e.message}`);
2670
+ console.log(e);
2671
+ throw new Error(`Text "${text}" was not found on page after ${waitTimeout / 1000} sec\n${e.message}`);
2693
2672
  }
2694
2673
  }
2695
2674
 
2696
2675
  if (locator.isXPath()) {
2697
- try {
2698
- await contextObject.waitForFunction(([locator, text, $XPath]) => {
2699
- eval($XPath); // eslint-disable-line no-eval
2700
- const el = $XPath(null, locator);
2701
- if (!el.length) return false;
2702
- return el[0].innerText.indexOf(text) > -1;
2703
- }, [locator.value, text, $XPath.toString()], { timeout: waitTimeout });
2704
- } catch (e) {
2705
- throw new Error(`${errorMessage}\n${e.message}`);
2706
- }
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 });
2707
2682
  }
2708
2683
  } else {
2709
2684
  // we have this as https://github.com/microsoft/playwright/issues/26829 is not yet implemented
@@ -2717,7 +2692,7 @@ class Playwright extends Helper {
2717
2692
  count += 1000;
2718
2693
  } while (count <= waitTimeout);
2719
2694
 
2720
- if (!waiter) throw new Error(`${errorMessage}`);
2695
+ if (!waiter) throw new Error(`Text "${text}" was not found on page after ${waitTimeout / 1000} sec`);
2721
2696
  }
2722
2697
  }
2723
2698
 
@@ -2894,38 +2869,6 @@ class Playwright extends Helper {
2894
2869
  }
2895
2870
  }
2896
2871
 
2897
- /**
2898
- * {{> waitForCookie }}
2899
- */
2900
- async waitForCookie(name, sec) {
2901
- // by default, we will retry 3 times
2902
- let retries = 3;
2903
- const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
2904
-
2905
- if (sec) {
2906
- retries = sec;
2907
- } else {
2908
- retries = Math.ceil(waitTimeout / 1000) - 1;
2909
- }
2910
-
2911
- return promiseRetry(async (retry, number) => {
2912
- const _grabCookie = async (name) => {
2913
- const cookies = await this.browserContext.cookies();
2914
- const cookie = cookies.filter(c => c.name === name);
2915
- if (cookie.length === 0) throw Error(`Cookie ${name} is not found after ${retries}s`);
2916
- };
2917
-
2918
- this.debugSection('Wait for cookie: ', name);
2919
- if (number > 1) this.debugSection('Retrying... Attempt #', number);
2920
-
2921
- try {
2922
- await _grabCookie(name);
2923
- } catch (e) {
2924
- retry(e);
2925
- }
2926
- }, { retries, maxTimeout: 1000 });
2927
- }
2928
-
2929
2872
  async _waitForAction() {
2930
2873
  return this.wait(this.options.waitForAction / 1000);
2931
2874
  }
@@ -2980,8 +2923,14 @@ class Playwright extends Helper {
2980
2923
  }
2981
2924
 
2982
2925
  /**
2983
- * {{> flushNetworkTraffics }}
2926
+ * Starts recording the network traffics.
2927
+ * This also resets recorded network requests.
2984
2928
  *
2929
+ * ```js
2930
+ * I.startRecordingTraffic();
2931
+ * ```
2932
+ *
2933
+ * @return {void}
2985
2934
  */
2986
2935
  startRecordingTraffic() {
2987
2936
  this.flushNetworkTraffics();
@@ -3008,34 +2957,44 @@ class Playwright extends Helper {
3008
2957
  }
3009
2958
 
3010
2959
  /**
3011
- * {{> grabRecordedNetworkTraffics }}
3012
- */
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
+ */
3013
2972
  async grabRecordedNetworkTraffics() {
3014
2973
  if (!this.recording || !this.recordedAtLeastOnce) {
3015
2974
  throw new Error('Failure in test automation. You use "I.grabRecordedNetworkTraffics", but "I.startRecordingTraffic" was never called before.');
3016
2975
  }
3017
2976
 
3018
- const promises = this.requests.map(async (request) => {
3019
- const resp = await request.response;
3020
- let body;
3021
- try {
3022
- // There's no 'body' for some requests (redirect etc...)
3023
- body = JSON.parse((await resp.body()).toString());
3024
- } catch (e) {
3025
- // only interested in JSON, not HTML responses.
3026
- }
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
+ }
3027
2987
 
3028
- return {
3029
- url: resp.url(),
3030
- response: {
3031
- status: resp.status(),
3032
- statusText: resp.statusText(),
2988
+ request.response = {
2989
+ status: response.status(),
2990
+ statusText: response.statusText(),
3033
2991
  body,
3034
- },
3035
- };
3036
- });
2992
+ };
2993
+ },
2994
+ ));
2995
+ await Promise.all(promises);
3037
2996
 
3038
- return Promise.all(promises);
2997
+ return this.requests;
3039
2998
  }
3040
2999
 
3041
3000
  /**
@@ -3117,14 +3076,18 @@ class Playwright extends Helper {
3117
3076
  }
3118
3077
 
3119
3078
  /**
3120
- * {{> flushNetworkTraffics }}
3079
+ * Resets all recorded network requests.
3121
3080
  */
3122
3081
  flushNetworkTraffics() {
3123
3082
  this.requests = [];
3124
3083
  }
3125
3084
 
3126
3085
  /**
3127
- * {{> stopRecordingTraffic }}
3086
+ * Stops recording of network traffic. Recorded traffic is not flashed.
3087
+ *
3088
+ * ```js
3089
+ * I.stopRecordingTraffic();
3090
+ * ```
3128
3091
  */
3129
3092
  stopRecordingTraffic() {
3130
3093
  this.page.removeAllListeners('request');
@@ -3132,7 +3095,42 @@ class Playwright extends Helper {
3132
3095
  }
3133
3096
 
3134
3097
  /**
3135
- * {{> seeTraffic }}
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<*> }
3136
3134
  */
3137
3135
  async seeTraffic({
3138
3136
  name, url, parameters, requestPostData, timeout = 10,
@@ -3214,7 +3212,18 @@ class Playwright extends Helper {
3214
3212
  }
3215
3213
 
3216
3214
  /**
3217
- * {{> dontSeeTraffic }}
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.
3218
3227
  *
3219
3228
  */
3220
3229
  dontSeeTraffic({ name, url }) {
@@ -3926,3 +3935,134 @@ async function highlightActiveElement(element) {
3926
3935
  });
3927
3936
  }
3928
3937
  }
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
+ };