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.
- package/README.md +2 -2
- package/bin/codecept.js +66 -30
- package/docs/advanced.md +351 -0
- package/docs/ai.md +365 -0
- package/docs/api.md +323 -0
- package/docs/basics.md +979 -0
- package/docs/bdd.md +539 -0
- package/docs/best.md +237 -0
- package/docs/books.md +37 -0
- package/docs/bootstrap.md +135 -0
- package/docs/build/AI.js +124 -0
- package/docs/build/ApiDataFactory.js +410 -0
- package/docs/build/Appium.js +2027 -0
- package/docs/build/Expect.js +422 -0
- package/docs/build/FileSystem.js +228 -0
- package/docs/build/GraphQL.js +229 -0
- package/docs/build/GraphQLDataFactory.js +309 -0
- package/docs/build/JSONResponse.js +338 -0
- package/docs/build/Mochawesome.js +71 -0
- package/docs/build/Nightmare.js +2152 -0
- package/docs/build/Playwright.js +5110 -0
- package/docs/build/Protractor.js +2706 -0
- package/docs/build/Puppeteer.js +3905 -0
- package/docs/build/REST.js +344 -0
- package/docs/build/TestCafe.js +2125 -0
- package/docs/build/WebDriver.js +4240 -0
- package/docs/changelog.md +2572 -0
- package/docs/commands.md +266 -0
- package/docs/community-helpers.md +58 -0
- package/docs/configuration.md +157 -0
- package/docs/continuous-integration.md +22 -0
- package/docs/custom-helpers.md +306 -0
- package/docs/data.md +379 -0
- package/docs/detox.md +235 -0
- package/docs/docker.md +136 -0
- package/docs/email.md +183 -0
- package/docs/examples.md +149 -0
- package/docs/heal.md +186 -0
- package/docs/helpers/ApiDataFactory.md +266 -0
- package/docs/helpers/Appium.md +1374 -0
- package/docs/helpers/Detox.md +586 -0
- package/docs/helpers/Expect.md +275 -0
- package/docs/helpers/FileSystem.md +152 -0
- package/docs/helpers/GraphQL.md +151 -0
- package/docs/helpers/GraphQLDataFactory.md +226 -0
- package/docs/helpers/JSONResponse.md +254 -0
- package/docs/helpers/Mochawesome.md +8 -0
- package/docs/helpers/MockRequest.md +377 -0
- package/docs/helpers/Nightmare.md +1305 -0
- package/docs/helpers/OpenAI.md +70 -0
- package/docs/helpers/Playwright.md +2759 -0
- package/docs/helpers/Polly.md +44 -0
- package/docs/helpers/Protractor.md +1769 -0
- package/docs/helpers/Puppeteer-firefox.md +86 -0
- package/docs/helpers/Puppeteer.md +2317 -0
- package/docs/helpers/REST.md +218 -0
- package/docs/helpers/TestCafe.md +1321 -0
- package/docs/helpers/WebDriver.md +2547 -0
- package/docs/hooks.md +340 -0
- package/docs/index.md +111 -0
- package/docs/installation.md +75 -0
- package/docs/internal-api.md +266 -0
- package/docs/locators.md +339 -0
- package/docs/mobile-react-native-locators.md +67 -0
- package/docs/mobile.md +338 -0
- package/docs/pageobjects.md +291 -0
- package/docs/parallel.md +400 -0
- package/docs/playwright.md +632 -0
- package/docs/plugins.md +1247 -0
- package/docs/puppeteer.md +316 -0
- package/docs/quickstart.md +162 -0
- package/docs/react.md +70 -0
- package/docs/reports.md +392 -0
- package/docs/secrets.md +36 -0
- package/docs/shadow.md +68 -0
- package/docs/shared/keys.mustache +31 -0
- package/docs/shared/react.mustache +1 -0
- package/docs/testcafe.md +174 -0
- package/docs/translation.md +247 -0
- package/docs/tutorial.md +271 -0
- package/docs/typescript.md +180 -0
- package/docs/ui.md +59 -0
- package/docs/videos.md +28 -0
- package/docs/visual.md +202 -0
- package/docs/vue.md +143 -0
- package/docs/webdriver.md +701 -0
- package/docs/wiki/Books-&-Posts.md +27 -0
- package/docs/wiki/Community-Helpers-&-Plugins.md +53 -0
- package/docs/wiki/Converting-Playwright-to-Istanbul-Coverage.md +61 -0
- package/docs/wiki/Examples.md +145 -0
- package/docs/wiki/Google-Summer-of-Code-(GSoC)-2020.md +68 -0
- package/docs/wiki/Home.md +16 -0
- package/docs/wiki/Migration-to-Appium-v2---CodeceptJS.md +83 -0
- package/docs/wiki/Release-Process.md +24 -0
- package/docs/wiki/Roadmap.md +23 -0
- package/docs/wiki/Tests.md +1393 -0
- package/docs/wiki/Upgrading-to-CodeceptJS-3.md +153 -0
- package/docs/wiki/Videos.md +19 -0
- package/lib/actor.js +3 -6
- package/lib/ai.js +152 -80
- package/lib/cli.js +1 -0
- package/lib/command/dryRun.js +13 -44
- package/lib/command/generate.js +34 -0
- package/lib/command/run-workers.js +3 -0
- package/lib/command/run.js +3 -0
- package/lib/container.js +2 -0
- package/lib/heal.js +172 -0
- package/lib/helper/AI.js +124 -0
- package/lib/helper/Appium.js +12 -36
- package/lib/helper/Expect.js +8 -11
- package/lib/helper/JSONResponse.js +8 -8
- package/lib/helper/Playwright.js +240 -100
- package/lib/helper/Puppeteer.js +68 -182
- package/lib/helper/REST.js +1 -4
- package/lib/helper/WebDriver.js +10 -324
- package/lib/index.js +3 -0
- package/lib/listener/steps.js +0 -2
- package/lib/locator.js +4 -13
- package/lib/plugin/coverage.js +99 -112
- package/lib/plugin/heal.js +26 -117
- package/lib/recorder.js +11 -5
- package/lib/step.js +1 -3
- package/lib/store.js +2 -0
- package/lib/template/heal.js +39 -0
- package/package.json +35 -47
- package/typings/index.d.ts +0 -17
- package/typings/promiseBasedTypes.d.ts +57 -340
- package/typings/types.d.ts +73 -433
- package/docs/webapi/dontSeeTraffic.mustache +0 -13
- package/docs/webapi/flushNetworkTraffics.mustache +0 -5
- package/docs/webapi/grabRecordedNetworkTraffics.mustache +0 -10
- package/docs/webapi/seeTraffic.mustache +0 -36
- package/docs/webapi/startRecordingTraffic.mustache +0 -8
- package/docs/webapi/stopRecordingTraffic.mustache +0 -5
- package/docs/webapi/waitForCookie.mustache +0 -9
- package/lib/helper/MockServer.js +0 -221
- package/lib/helper/errors/ElementAssertion.js +0 -38
- package/lib/helper/networkTraffics/utils.js +0 -137
- /package/{lib/helper → docs/build}/OpenAI.js +0 -0
package/lib/helper/Playwright.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
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(
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
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
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
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
|
|
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
|
-
*
|
|
3079
|
+
* Resets all recorded network requests.
|
|
3121
3080
|
*/
|
|
3122
3081
|
flushNetworkTraffics() {
|
|
3123
3082
|
this.requests = [];
|
|
3124
3083
|
}
|
|
3125
3084
|
|
|
3126
3085
|
/**
|
|
3127
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
+
};
|