codeceptjs 3.5.14 → 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 (137) 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/generate.js +34 -0
  103. package/lib/command/run-workers.js +3 -0
  104. package/lib/command/run.js +3 -0
  105. package/lib/container.js +2 -0
  106. package/lib/heal.js +172 -0
  107. package/lib/helper/AI.js +124 -0
  108. package/lib/helper/Appium.js +12 -36
  109. package/lib/helper/Expect.js +7 -10
  110. package/lib/helper/JSONResponse.js +8 -8
  111. package/lib/helper/Playwright.js +240 -100
  112. package/lib/helper/Puppeteer.js +9 -61
  113. package/lib/helper/REST.js +1 -4
  114. package/lib/helper/WebDriver.js +10 -324
  115. package/lib/index.js +3 -0
  116. package/lib/listener/steps.js +0 -2
  117. package/lib/locator.js +4 -13
  118. package/lib/plugin/heal.js +26 -117
  119. package/lib/recorder.js +11 -5
  120. package/lib/step.js +1 -3
  121. package/lib/store.js +2 -0
  122. package/lib/template/heal.js +39 -0
  123. package/package.json +23 -27
  124. package/typings/index.d.ts +0 -16
  125. package/typings/promiseBasedTypes.d.ts +55 -338
  126. package/typings/types.d.ts +58 -353
  127. package/docs/webapi/dontSeeTraffic.mustache +0 -13
  128. package/docs/webapi/flushNetworkTraffics.mustache +0 -5
  129. package/docs/webapi/grabRecordedNetworkTraffics.mustache +0 -10
  130. package/docs/webapi/seeTraffic.mustache +0 -36
  131. package/docs/webapi/startRecordingTraffic.mustache +0 -8
  132. package/docs/webapi/stopRecordingTraffic.mustache +0 -5
  133. package/docs/webapi/waitForCookie.mustache +0 -9
  134. package/lib/helper/MockServer.js +0 -221
  135. package/lib/helper/errors/ElementAssertion.js +0 -38
  136. package/lib/helper/networkTraffics/utils.js +0 -137
  137. /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
+ };
@@ -5,7 +5,6 @@ const path = require('path');
5
5
 
6
6
  const Helper = require('@codeceptjs/helper');
7
7
  const { v4: uuidv4 } = require('uuid');
8
- const promiseRetry = require('promise-retry');
9
8
  const Locator = require('../locator');
10
9
  const recorder = require('../recorder');
11
10
  const store = require('../store');
@@ -40,9 +39,6 @@ const findReact = require('./extras/React');
40
39
  const { highlightElement } = require('./scripts/highlightElement');
41
40
  const { blurElement } = require('./scripts/blurElement');
42
41
  const { focusElement } = require('./scripts/focusElement');
43
- const {
44
- dontSeeElementError, seeElementError, dontSeeElementInDOMError, seeElementInDOMError,
45
- } = require('./errors/ElementAssertion');
46
42
 
47
43
  let puppeteer;
48
44
  let perfTiming;
@@ -486,7 +482,7 @@ class Puppeteer extends Helper {
486
482
  if (!page) return;
487
483
  page.setDefaultNavigationTimeout(this.options.getPageTimeout);
488
484
  this.context = await this.page.$('body');
489
- if (this.options.browser === 'chrome') {
485
+ if (this.config.browser === 'chrome') {
490
486
  await page.bringToFront();
491
487
  }
492
488
  }
@@ -658,9 +654,9 @@ class Puppeteer extends Helper {
658
654
  url = this.options.url + url;
659
655
  }
660
656
 
661
- if (this.options.basicAuth && (this.isAuthenticated !== true)) {
657
+ if (this.config.basicAuth && (this.isAuthenticated !== true)) {
662
658
  if (url.includes(this.options.url)) {
663
- await this.page.authenticate(this.options.basicAuth);
659
+ await this.page.authenticate(this.config.basicAuth);
664
660
  this.isAuthenticated = true;
665
661
  }
666
662
  }
@@ -1042,11 +1038,8 @@ class Puppeteer extends Helper {
1042
1038
  els = (await Promise.all(els.map(el => el.boundingBox() && el))).filter(v => v);
1043
1039
  // Puppeteer visibility was ignored? | Remove when Puppeteer is fixed
1044
1040
  els = await Promise.all(els.map(async el => (await el.evaluate(node => window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none')) && el));
1045
- try {
1046
- return empty('visible elements').negate(els.filter(v => v).fill('ELEMENT'));
1047
- } catch (e) {
1048
- dontSeeElementError(locator);
1049
- }
1041
+
1042
+ return empty('visible elements').negate(els.filter(v => v).fill('ELEMENT'));
1050
1043
  }
1051
1044
 
1052
1045
  /**
@@ -1058,11 +1051,8 @@ class Puppeteer extends Helper {
1058
1051
  els = (await Promise.all(els.map(el => el.boundingBox() && el))).filter(v => v);
1059
1052
  // Puppeteer visibility was ignored? | Remove when Puppeteer is fixed
1060
1053
  els = await Promise.all(els.map(async el => (await el.evaluate(node => window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none')) && el));
1061
- try {
1062
- return empty('visible elements').assert(els.filter(v => v).fill('ELEMENT'));
1063
- } catch (e) {
1064
- seeElementError(locator);
1065
- }
1054
+
1055
+ return empty('visible elements').assert(els.filter(v => v).fill('ELEMENT'));
1066
1056
  }
1067
1057
 
1068
1058
  /**
@@ -1070,11 +1060,7 @@ class Puppeteer extends Helper {
1070
1060
  */
1071
1061
  async seeElementInDOM(locator) {
1072
1062
  const els = await this._locate(locator);
1073
- try {
1074
- return empty('elements on page').negate(els.filter(v => v).fill('ELEMENT'));
1075
- } catch (e) {
1076
- dontSeeElementInDOMError(locator);
1077
- }
1063
+ return empty('elements on page').negate(els.filter(v => v).fill('ELEMENT'));
1078
1064
  }
1079
1065
 
1080
1066
  /**
@@ -1082,11 +1068,7 @@ class Puppeteer extends Helper {
1082
1068
  */
1083
1069
  async dontSeeElementInDOM(locator) {
1084
1070
  const els = await this._locate(locator);
1085
- try {
1086
- return empty('elements on a page').assert(els.filter(v => v).fill('ELEMENT'));
1087
- } catch (e) {
1088
- seeElementInDOMError(locator);
1089
- }
1071
+ return empty('elements on a page').assert(els.filter(v => v).fill('ELEMENT'));
1090
1072
  }
1091
1073
 
1092
1074
  /**
@@ -1636,38 +1618,6 @@ class Puppeteer extends Helper {
1636
1618
  if (cookie[0]) return cookie[0];
1637
1619
  }
1638
1620
 
1639
- /**
1640
- * {{> waitForCookie }}
1641
- */
1642
- async waitForCookie(name, sec) {
1643
- // by default, we will retry 3 times
1644
- let retries = 3;
1645
- const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
1646
-
1647
- if (sec) {
1648
- retries = sec;
1649
- } else {
1650
- retries = Math.ceil(waitTimeout / 1000) - 1;
1651
- }
1652
-
1653
- return promiseRetry(async (retry, number) => {
1654
- const _grabCookie = async (name) => {
1655
- const cookies = await this.page.cookies();
1656
- const cookie = cookies.filter(c => c.name === name);
1657
- if (cookie.length === 0) throw Error(`Cookie ${name} is not found after ${retries}s`);
1658
- };
1659
-
1660
- this.debugSection('Wait for cookie: ', name);
1661
- if (number > 1) this.debugSection('Retrying... Attempt #', number);
1662
-
1663
- try {
1664
- await _grabCookie(name);
1665
- } catch (e) {
1666
- retry(e);
1667
- }
1668
- }, { retries, maxTimeout: 1000 });
1669
- }
1670
-
1671
1621
  /**
1672
1622
  * {{> clearCookie }}
1673
1623
  */
@@ -1875,8 +1825,6 @@ class Puppeteer extends Helper {
1875
1825
  for (let i = 0; i < val.length; ++i) {
1876
1826
  const _actual = Number.isNaN(val[i]) || (typeof values[i]) === 'string' ? val[i] : Number.parseInt(values[i], 10);
1877
1827
  const _expected = Number.isNaN(values[i]) || (typeof values[i]) === 'string' ? values[i] : Number.parseInt(values[i], 10);
1878
- // the attribute could be a boolean
1879
- if (typeof _actual === 'boolean') return _actual === _expected;
1880
1828
  // if the attribute doesn't exist, returns false as well
1881
1829
  if (!_actual || !_actual.includes(_expected)) return false;
1882
1830
  }
@@ -72,12 +72,9 @@ class REST extends Helper {
72
72
  this.options.maxBodyLength = maxContentLength;
73
73
  }
74
74
 
75
- // override defaults with config
76
- this._setConfig(config);
77
-
75
+ this.options = { ...this.options, ...config };
78
76
  this.headers = { ...this.options.defaultHeaders };
79
77
  this.axios = axios.create();
80
- // @ts-ignore
81
78
  this.axios.defaults.headers = this.options.defaultHeaders;
82
79
  }
83
80