codeceptjs 3.5.4-beta.1 → 3.5.4
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 +0 -2
- package/docs/build/Appium.js +8 -6
- package/docs/build/GraphQL.js +25 -0
- package/docs/build/Nightmare.js +11 -6
- package/docs/build/Playwright.js +425 -193
- package/docs/build/Protractor.js +13 -8
- package/docs/build/Puppeteer.js +20 -14
- package/docs/build/TestCafe.js +17 -10
- package/docs/build/WebDriver.js +41 -37
- package/docs/changelog.md +220 -0
- package/docs/community-helpers.md +8 -4
- package/docs/examples.md +8 -2
- package/docs/helpers/Appium.md +2 -2
- package/docs/helpers/GraphQL.md +21 -0
- package/docs/helpers/Nightmare.md +1258 -0
- package/docs/helpers/Playwright.md +223 -119
- package/docs/helpers/Protractor.md +1709 -0
- package/docs/helpers/Puppeteer.md +3 -3
- package/docs/helpers/TestCafe.md +2 -2
- package/docs/helpers/WebDriver.md +3 -3
- package/docs/playwright.md +24 -1
- package/docs/webapi/dontSeeInField.mustache +1 -1
- package/docs/webapi/seeInField.mustache +1 -1
- package/docs/wiki/Books-&-Posts.md +0 -0
- package/docs/wiki/Community-Helpers-&-Plugins.md +8 -4
- package/docs/wiki/Converting-Playwright-to-Istanbul-Coverage.md +46 -14
- package/docs/wiki/Examples.md +8 -2
- package/docs/wiki/Google-Summer-of-Code-(GSoC)-2020.md +0 -0
- package/docs/wiki/Home.md +0 -0
- package/docs/wiki/Migration-to-Appium-v2---CodeceptJS.md +83 -0
- package/docs/wiki/Release-Process.md +0 -0
- package/docs/wiki/Roadmap.md +0 -0
- package/docs/wiki/Tests.md +0 -0
- package/docs/wiki/Upgrading-to-CodeceptJS-3.md +0 -0
- package/docs/wiki/Videos.md +0 -0
- package/lib/command/definitions.js +2 -7
- package/lib/command/run-multiple/collection.js +17 -5
- package/lib/helper/Appium.js +6 -4
- package/lib/helper/GraphQL.js +25 -0
- package/lib/helper/Nightmare.js +1415 -0
- package/lib/helper/Playwright.js +321 -54
- package/lib/helper/Protractor.js +1837 -0
- package/lib/helper/Puppeteer.js +18 -12
- package/lib/helper/TestCafe.js +15 -8
- package/lib/helper/WebDriver.js +39 -35
- package/lib/helper/clientscripts/nightmare.js +213 -0
- package/lib/helper/errors/ElementNotFound.js +2 -1
- package/lib/helper/scripts/highlightElement.js +1 -1
- package/lib/interfaces/bdd.js +1 -1
- package/lib/mochaFactory.js +2 -1
- package/lib/pause.js +5 -4
- package/lib/plugin/heal.js +2 -3
- package/lib/plugin/selenoid.js +6 -1
- package/lib/step.js +27 -10
- package/lib/utils.js +4 -0
- package/lib/workers.js +3 -1
- package/package.json +13 -13
- package/typings/promiseBasedTypes.d.ts +145 -126
- package/typings/types.d.ts +152 -133
- package/CHANGELOG.md +0 -2519
- package/docs/build/Polly.js +0 -42
- package/docs/build/SeleniumWebdriver.js +0 -76
package/lib/helper/Playwright.js
CHANGED
|
@@ -23,6 +23,7 @@ const {
|
|
|
23
23
|
isModifierKey,
|
|
24
24
|
clearString,
|
|
25
25
|
requireWithFallback,
|
|
26
|
+
normalizeSpacesInString,
|
|
26
27
|
} = require('../utils');
|
|
27
28
|
const {
|
|
28
29
|
isColorProperty,
|
|
@@ -76,7 +77,7 @@ const pathSeparator = path.sep;
|
|
|
76
77
|
* @prop {boolean} [keepBrowserState=false] - keep browser state between tests when `restart` is set to 'session'.
|
|
77
78
|
* @prop {boolean} [keepCookies=false] - keep cookies between tests when `restart` is set to 'session'.
|
|
78
79
|
* @prop {number} [waitForAction] - how long to wait after click, doubleClick or PressKey actions in ms. Default: 100.
|
|
79
|
-
* @prop {'load' | 'domcontentloaded' | '
|
|
80
|
+
* @prop {'load' | 'domcontentloaded' | 'commit'} [waitForNavigation] - When to consider navigation succeeded. Possible options: `load`, `domcontentloaded`, `commit`. Choose one of those options is possible. See [Playwright API](https://playwright.dev/docs/api/class-page#page-wait-for-url).
|
|
80
81
|
* @prop {number} [pressKeyDelay=10] - Delay between key presses in ms. Used when calling Playwrights page.type(...) in fillField/appendField
|
|
81
82
|
* @prop {number} [getPageTimeout] - config option to set maximum navigation time in milliseconds.
|
|
82
83
|
* @prop {number} [waitForTimeout] - default wait* timeout in ms. Default: 1000.
|
|
@@ -93,7 +94,7 @@ const pathSeparator = path.sep;
|
|
|
93
94
|
* @prop {string[]} [ignoreLog] - An array with console message types that are not logged to debug log. Default value is `['warning', 'log']`. E.g. you can set `[]` to log all messages. See all possible [values](https://playwright.dev/docs/api/class-consolemessage#console-message-type).
|
|
94
95
|
* @prop {boolean} [ignoreHTTPSErrors] - Allows access to untrustworthy pages, e.g. to a page with an expired certificate. Default value is `false`
|
|
95
96
|
* @prop {boolean} [bypassCSP] - bypass Content Security Policy or CSP
|
|
96
|
-
* @prop {boolean} [highlightElement] - highlight the interacting elements
|
|
97
|
+
* @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false
|
|
97
98
|
*/
|
|
98
99
|
const config = {};
|
|
99
100
|
|
|
@@ -207,6 +208,7 @@ const config = {};
|
|
|
207
208
|
* url: "http://localhost",
|
|
208
209
|
* show: true // headless mode not supported for extensions
|
|
209
210
|
* chromium: {
|
|
211
|
+
* // Note: due to this would launch persistent context, so to avoid the error when running tests with run-workers a timestamp would be appended to the defined folder name. For instance: playwright-tmp_1692715649511
|
|
210
212
|
* userDataDir: '/tmp/playwright-tmp', // necessary to launch the browser in normal mode instead of incognito,
|
|
211
213
|
* args: [
|
|
212
214
|
* `--disable-extensions-except=${pathToExtension}`,
|
|
@@ -317,6 +319,12 @@ class Playwright extends Helper {
|
|
|
317
319
|
this.recording = false;
|
|
318
320
|
this.recordedAtLeastOnce = false;
|
|
319
321
|
|
|
322
|
+
// for websocket messages
|
|
323
|
+
this.webSocketMessages = [];
|
|
324
|
+
this.recordingWebSocketMessages = false;
|
|
325
|
+
this.recordedWebSocketMessagesAtLeastOnce = false;
|
|
326
|
+
this.cdpSession = null;
|
|
327
|
+
|
|
320
328
|
// override defaults with config
|
|
321
329
|
this._setConfig(config);
|
|
322
330
|
}
|
|
@@ -343,7 +351,8 @@ class Playwright extends Helper {
|
|
|
343
351
|
show: false,
|
|
344
352
|
defaultPopupAction: 'accept',
|
|
345
353
|
use: { actionTimeout: 0 },
|
|
346
|
-
ignoreHTTPSErrors: false, // Adding it here o that context can be set up to ignore the SSL errors
|
|
354
|
+
ignoreHTTPSErrors: false, // Adding it here o that context can be set up to ignore the SSL errors,
|
|
355
|
+
highlightElement: false,
|
|
347
356
|
};
|
|
348
357
|
|
|
349
358
|
config = Object.assign(defaults, config);
|
|
@@ -388,7 +397,7 @@ class Playwright extends Helper {
|
|
|
388
397
|
}
|
|
389
398
|
this.isRemoteBrowser = !!this.playwrightOptions.browserWSEndpoint;
|
|
390
399
|
this.isElectron = this.options.browser === 'electron';
|
|
391
|
-
this.userDataDir = this.playwrightOptions.userDataDir;
|
|
400
|
+
this.userDataDir = this.playwrightOptions.userDataDir ? `${this.playwrightOptions.userDataDir}_${Date.now().toString()}` : undefined;
|
|
392
401
|
this.isCDPConnection = this.playwrightOptions.cdpConnection;
|
|
393
402
|
popupStore.defaultAction = this.options.defaultPopupAction;
|
|
394
403
|
}
|
|
@@ -461,7 +470,7 @@ class Playwright extends Helper {
|
|
|
461
470
|
this.isAuthenticated = false;
|
|
462
471
|
if (this.isElectron) {
|
|
463
472
|
this.browserContext = this.browser.context();
|
|
464
|
-
} else if (this.userDataDir) {
|
|
473
|
+
} else if (this.playwrightOptions.userDataDir) {
|
|
465
474
|
this.browserContext = this.browser;
|
|
466
475
|
} else {
|
|
467
476
|
const contextOptions = {
|
|
@@ -487,8 +496,17 @@ class Playwright extends Helper {
|
|
|
487
496
|
if (this.isElectron) {
|
|
488
497
|
mainPage = await this.browser.firstWindow();
|
|
489
498
|
} else {
|
|
490
|
-
|
|
491
|
-
|
|
499
|
+
try {
|
|
500
|
+
const existingPages = await this.browserContext.pages();
|
|
501
|
+
mainPage = existingPages[0] || await this.browserContext.newPage();
|
|
502
|
+
} catch (e) {
|
|
503
|
+
if (this.playwrightOptions.userDataDir) {
|
|
504
|
+
this.browser = await playwright[this.options.browser].launchPersistentContext(this.userDataDir, this.playwrightOptions);
|
|
505
|
+
this.browserContext = this.browser;
|
|
506
|
+
const existingPages = await this.browserContext.pages();
|
|
507
|
+
mainPage = existingPages[0];
|
|
508
|
+
}
|
|
509
|
+
}
|
|
492
510
|
}
|
|
493
511
|
await targetCreatedHandler.call(this, mainPage);
|
|
494
512
|
|
|
@@ -519,13 +537,15 @@ class Playwright extends Helper {
|
|
|
519
537
|
|
|
520
538
|
// close other sessions
|
|
521
539
|
try {
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
this.
|
|
526
|
-
|
|
540
|
+
if ((await this.browser)._type === 'Browser') {
|
|
541
|
+
const contexts = await this.browser.contexts();
|
|
542
|
+
const currentContext = contexts[0];
|
|
543
|
+
if (currentContext && (this.options.keepCookies || this.options.keepBrowserState)) {
|
|
544
|
+
this.storageState = await currentContext.storageState();
|
|
545
|
+
}
|
|
527
546
|
|
|
528
|
-
|
|
547
|
+
await Promise.all(contexts.map(c => c.close()));
|
|
548
|
+
}
|
|
529
549
|
} catch (e) {
|
|
530
550
|
console.log(e);
|
|
531
551
|
}
|
|
@@ -555,8 +575,16 @@ class Playwright extends Helper {
|
|
|
555
575
|
browserContext = browser.context();
|
|
556
576
|
page = await browser.firstWindow();
|
|
557
577
|
} else {
|
|
558
|
-
|
|
559
|
-
|
|
578
|
+
try {
|
|
579
|
+
browserContext = await this.browser.newContext(Object.assign(this.options, config));
|
|
580
|
+
page = await browserContext.newPage();
|
|
581
|
+
} catch (e) {
|
|
582
|
+
if (this.playwrightOptions.userDataDir) {
|
|
583
|
+
browserContext = await playwright[this.options.browser].launchPersistentContext(`${this.userDataDir}_${this.activeSessionName}`, this.playwrightOptions);
|
|
584
|
+
this.browser = browserContext;
|
|
585
|
+
page = await browserContext.pages()[0];
|
|
586
|
+
}
|
|
587
|
+
}
|
|
560
588
|
}
|
|
561
589
|
|
|
562
590
|
if (this.options.trace) await browserContext.tracing.start({ screenshots: true, snapshots: true });
|
|
@@ -569,10 +597,12 @@ class Playwright extends Helper {
|
|
|
569
597
|
// is closed by _after
|
|
570
598
|
},
|
|
571
599
|
loadVars: async (context) => {
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
600
|
+
if (context) {
|
|
601
|
+
this.browserContext = context;
|
|
602
|
+
const existingPages = await context.pages();
|
|
603
|
+
this.sessionPages[this.activeSessionName] = existingPages[0];
|
|
604
|
+
return this._setPage(this.sessionPages[this.activeSessionName]);
|
|
605
|
+
}
|
|
576
606
|
},
|
|
577
607
|
restoreVars: async (session) => {
|
|
578
608
|
this.withinLocator = null;
|
|
@@ -763,7 +793,7 @@ class Playwright extends Helper {
|
|
|
763
793
|
}
|
|
764
794
|
throw err;
|
|
765
795
|
}
|
|
766
|
-
} else if (this.userDataDir) {
|
|
796
|
+
} else if (this.playwrightOptions.userDataDir) {
|
|
767
797
|
this.browser = await playwright[this.options.browser].launchPersistentContext(this.userDataDir, this.playwrightOptions);
|
|
768
798
|
} else {
|
|
769
799
|
this.browser = await playwright[this.options.browser].launch(this.playwrightOptions);
|
|
@@ -1038,8 +1068,11 @@ class Playwright extends Helper {
|
|
|
1038
1068
|
const body = document.body;
|
|
1039
1069
|
const html = document.documentElement;
|
|
1040
1070
|
window.scrollTo(0, Math.max(
|
|
1041
|
-
body.scrollHeight,
|
|
1042
|
-
|
|
1071
|
+
body.scrollHeight,
|
|
1072
|
+
body.offsetHeight,
|
|
1073
|
+
html.clientHeight,
|
|
1074
|
+
html.scrollHeight,
|
|
1075
|
+
html.offsetHeight,
|
|
1043
1076
|
));
|
|
1044
1077
|
});
|
|
1045
1078
|
}
|
|
@@ -1545,7 +1578,7 @@ class Playwright extends Helper {
|
|
|
1545
1578
|
|
|
1546
1579
|
await el.clear();
|
|
1547
1580
|
|
|
1548
|
-
highlightActiveElement.call(this, el, this.
|
|
1581
|
+
highlightActiveElement.call(this, el, await this._getContext());
|
|
1549
1582
|
|
|
1550
1583
|
await el.type(value.toString(), { delay: this.options.pressKeyDelay });
|
|
1551
1584
|
|
|
@@ -1590,7 +1623,7 @@ class Playwright extends Helper {
|
|
|
1590
1623
|
async appendField(field, value) {
|
|
1591
1624
|
const els = await findFields.call(this, field);
|
|
1592
1625
|
assertElementExists(els, field, 'Field');
|
|
1593
|
-
highlightActiveElement.call(this, els[0], this.
|
|
1626
|
+
highlightActiveElement.call(this, els[0], await this._getContext());
|
|
1594
1627
|
await els[0].press('End');
|
|
1595
1628
|
await els[0].type(value.toString(), { delay: this.options.pressKeyDelay });
|
|
1596
1629
|
return this._waitForAction();
|
|
@@ -1600,14 +1633,16 @@ class Playwright extends Helper {
|
|
|
1600
1633
|
* {{> seeInField }}
|
|
1601
1634
|
*/
|
|
1602
1635
|
async seeInField(field, value) {
|
|
1603
|
-
|
|
1636
|
+
const _value = (typeof value === 'boolean') ? value : value.toString();
|
|
1637
|
+
return proceedSeeInField.call(this, 'assert', field, _value);
|
|
1604
1638
|
}
|
|
1605
1639
|
|
|
1606
1640
|
/**
|
|
1607
1641
|
* {{> dontSeeInField }}
|
|
1608
1642
|
*/
|
|
1609
1643
|
async dontSeeInField(field, value) {
|
|
1610
|
-
|
|
1644
|
+
const _value = (typeof value === 'boolean') ? value : value.toString();
|
|
1645
|
+
return proceedSeeInField.call(this, 'negate', field, _value);
|
|
1611
1646
|
}
|
|
1612
1647
|
|
|
1613
1648
|
/**
|
|
@@ -1634,7 +1669,8 @@ class Playwright extends Helper {
|
|
|
1634
1669
|
assertElementExists(els, select, 'Selectable field');
|
|
1635
1670
|
const el = els[0];
|
|
1636
1671
|
|
|
1637
|
-
highlightActiveElement.call(this, el, this.
|
|
1672
|
+
highlightActiveElement.call(this, el, await this._getContext());
|
|
1673
|
+
|
|
1638
1674
|
if (!Array.isArray(option)) option = [option];
|
|
1639
1675
|
|
|
1640
1676
|
await el.selectOption(option);
|
|
@@ -2553,13 +2589,15 @@ class Playwright extends Helper {
|
|
|
2553
2589
|
}
|
|
2554
2590
|
|
|
2555
2591
|
/**
|
|
2556
|
-
* Waits for navigation to finish. By default takes configured `waitForNavigation` option.
|
|
2592
|
+
* Waits for navigation to finish. By default, it takes configured `waitForNavigation` option.
|
|
2557
2593
|
*
|
|
2558
2594
|
* See [Playwright's reference](https://playwright.dev/docs/api/class-page?_highlight=waitfornavi#pagewaitfornavigationoptions)
|
|
2559
2595
|
*
|
|
2560
2596
|
* @param {*} options
|
|
2561
2597
|
*/
|
|
2562
2598
|
async waitForNavigation(options = {}) {
|
|
2599
|
+
console.log(`waitForNavigation deprecated:
|
|
2600
|
+
* This method is inherently racy, please use 'waitForURL' instead.`);
|
|
2563
2601
|
options = {
|
|
2564
2602
|
timeout: this.options.getPageTimeout,
|
|
2565
2603
|
waitUntil: this.options.waitForNavigation,
|
|
@@ -2568,6 +2606,23 @@ class Playwright extends Helper {
|
|
|
2568
2606
|
return this.page.waitForNavigation(options);
|
|
2569
2607
|
}
|
|
2570
2608
|
|
|
2609
|
+
/**
|
|
2610
|
+
* Waits for page navigates to a new URL or reloads. By default, it takes configured `waitForNavigation` option.
|
|
2611
|
+
*
|
|
2612
|
+
* See [Playwright's reference](https://playwright.dev/docs/api/class-page#page-wait-for-url)
|
|
2613
|
+
*
|
|
2614
|
+
* @param {string|RegExp} url - A glob pattern, regex pattern or predicate receiving URL to match while waiting for the navigation. Note that if the parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly equal to the string.
|
|
2615
|
+
* @param {*} options
|
|
2616
|
+
*/
|
|
2617
|
+
async waitForURL(url, options = {}) {
|
|
2618
|
+
options = {
|
|
2619
|
+
timeout: this.options.getPageTimeout,
|
|
2620
|
+
waitUntil: this.options.waitForNavigation,
|
|
2621
|
+
...options,
|
|
2622
|
+
};
|
|
2623
|
+
return this.page.waitForURL(url, options);
|
|
2624
|
+
}
|
|
2625
|
+
|
|
2571
2626
|
async waitUntilExists(locator, sec) {
|
|
2572
2627
|
console.log(`waitUntilExists deprecated:
|
|
2573
2628
|
* use 'waitForElement' to wait for element to be attached
|
|
@@ -2652,16 +2707,16 @@ class Playwright extends Helper {
|
|
|
2652
2707
|
}
|
|
2653
2708
|
|
|
2654
2709
|
/**
|
|
2655
|
-
* Starts recording
|
|
2710
|
+
* Starts recording the network traffics.
|
|
2656
2711
|
* This also resets recorded network requests.
|
|
2657
2712
|
*
|
|
2658
2713
|
* ```js
|
|
2659
2714
|
* I.startRecordingTraffic();
|
|
2660
2715
|
* ```
|
|
2661
2716
|
*
|
|
2662
|
-
* @return {
|
|
2717
|
+
* @return {void}
|
|
2663
2718
|
*/
|
|
2664
|
-
|
|
2719
|
+
startRecordingTraffic() {
|
|
2665
2720
|
this.flushNetworkTraffics();
|
|
2666
2721
|
this.recording = true;
|
|
2667
2722
|
this.recordedAtLeastOnce = true;
|
|
@@ -2672,31 +2727,62 @@ class Playwright extends Helper {
|
|
|
2672
2727
|
method: request.method(),
|
|
2673
2728
|
requestHeaders: request.headers(),
|
|
2674
2729
|
requestPostData: request.postData(),
|
|
2730
|
+
response: request.response(),
|
|
2675
2731
|
};
|
|
2676
2732
|
|
|
2677
2733
|
this.debugSection('REQUEST: ', JSON.stringify(information));
|
|
2678
2734
|
|
|
2679
|
-
information.requestPostData
|
|
2735
|
+
if (typeof information.requestPostData === 'object') {
|
|
2736
|
+
information.requestPostData = JSON.parse(information.requestPostData);
|
|
2737
|
+
}
|
|
2738
|
+
|
|
2680
2739
|
this.requests.push(information);
|
|
2681
|
-
return this._waitForAction();
|
|
2682
2740
|
});
|
|
2683
2741
|
}
|
|
2684
2742
|
|
|
2685
2743
|
/**
|
|
2686
2744
|
* Grab the recording network traffics
|
|
2687
2745
|
*
|
|
2688
|
-
*
|
|
2746
|
+
* ```js
|
|
2747
|
+
* const traffics = await I.grabRecordedNetworkTraffics();
|
|
2748
|
+
* expect(traffics[0].url).to.equal('https://reqres.in/api/comments/1');
|
|
2749
|
+
* expect(traffics[0].response.status).to.equal(200);
|
|
2750
|
+
* expect(traffics[0].response.body).to.contain({ name: 'this was mocked' });
|
|
2751
|
+
* ```
|
|
2752
|
+
*
|
|
2753
|
+
* @return { Promise<Array<any>> }
|
|
2689
2754
|
*
|
|
2690
2755
|
*/
|
|
2691
|
-
grabRecordedNetworkTraffics() {
|
|
2756
|
+
async grabRecordedNetworkTraffics() {
|
|
2692
2757
|
if (!this.recording || !this.recordedAtLeastOnce) {
|
|
2693
2758
|
throw new Error('Failure in test automation. You use "I.grabRecordedNetworkTraffics", but "I.startRecordingTraffic" was never called before.');
|
|
2694
2759
|
}
|
|
2760
|
+
|
|
2761
|
+
const requests = await this.requests;
|
|
2762
|
+
const promises = requests.map(async (request) => request.response.then(
|
|
2763
|
+
async (response) => {
|
|
2764
|
+
let body;
|
|
2765
|
+
try {
|
|
2766
|
+
// There's no 'body' for some requests (redirect etc...)
|
|
2767
|
+
body = JSON.parse((await response.body()).toString());
|
|
2768
|
+
} catch (e) {
|
|
2769
|
+
// only interested in JSON, not HTML responses.
|
|
2770
|
+
}
|
|
2771
|
+
|
|
2772
|
+
request.response = {
|
|
2773
|
+
status: response.status(),
|
|
2774
|
+
statusText: response.statusText(),
|
|
2775
|
+
body,
|
|
2776
|
+
};
|
|
2777
|
+
},
|
|
2778
|
+
));
|
|
2779
|
+
await Promise.all(promises);
|
|
2780
|
+
|
|
2695
2781
|
return this.requests;
|
|
2696
2782
|
}
|
|
2697
2783
|
|
|
2698
2784
|
/**
|
|
2699
|
-
* Blocks traffic
|
|
2785
|
+
* Blocks traffic of a given URL or a list of URLs.
|
|
2700
2786
|
*
|
|
2701
2787
|
* Examples:
|
|
2702
2788
|
*
|
|
@@ -2707,16 +2793,30 @@ class Playwright extends Helper {
|
|
|
2707
2793
|
* I.blockTraffic(/\.css$/);
|
|
2708
2794
|
* ```
|
|
2709
2795
|
*
|
|
2710
|
-
*
|
|
2796
|
+
* ```js
|
|
2797
|
+
* I.blockTraffic(['http://example.com/css/style.css', 'http://example.com/css/*.css']);
|
|
2798
|
+
* ```
|
|
2799
|
+
*
|
|
2800
|
+
* @param {string|Array|RegExp} urls URL or a list of URLs to block . URL can contain * for wildcards. Example: https://www.example.com** to block all traffic for that domain. Regexp are also supported.
|
|
2711
2801
|
*/
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
.
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2802
|
+
blockTraffic(urls) {
|
|
2803
|
+
if (Array.isArray(urls)) {
|
|
2804
|
+
urls.forEach(url => {
|
|
2805
|
+
this.page.route(url, (route) => {
|
|
2806
|
+
route
|
|
2807
|
+
.abort()
|
|
2808
|
+
// Sometimes it happens that browser has been closed in the meantime. It is ok to ignore error then.
|
|
2809
|
+
.catch((e) => {});
|
|
2810
|
+
});
|
|
2811
|
+
});
|
|
2812
|
+
} else {
|
|
2813
|
+
this.page.route(urls, (route) => {
|
|
2814
|
+
route
|
|
2815
|
+
.abort()
|
|
2816
|
+
// Sometimes it happens that browser has been closed in the meantime. It is ok to ignore error then.
|
|
2817
|
+
.catch((e) => {});
|
|
2818
|
+
});
|
|
2819
|
+
}
|
|
2720
2820
|
}
|
|
2721
2821
|
|
|
2722
2822
|
/**
|
|
@@ -2735,7 +2835,7 @@ class Playwright extends Helper {
|
|
|
2735
2835
|
* @param responseString string The string to return in fake response's body.
|
|
2736
2836
|
* @param contentType Content type of fake response. If not specified default value 'application/json' is used.
|
|
2737
2837
|
*/
|
|
2738
|
-
|
|
2838
|
+
mockTraffic(urls, responseString, contentType = 'application/json') {
|
|
2739
2839
|
// Required to mock cross-domain requests
|
|
2740
2840
|
const headers = { 'access-control-allow-origin': '*' };
|
|
2741
2841
|
|
|
@@ -2757,7 +2857,6 @@ class Playwright extends Helper {
|
|
|
2757
2857
|
});
|
|
2758
2858
|
});
|
|
2759
2859
|
});
|
|
2760
|
-
return this._waitForAction();
|
|
2761
2860
|
}
|
|
2762
2861
|
|
|
2763
2862
|
/**
|
|
@@ -2829,7 +2928,7 @@ class Playwright extends Helper {
|
|
|
2829
2928
|
}
|
|
2830
2929
|
|
|
2831
2930
|
if (!this.recording || !this.recordedAtLeastOnce) {
|
|
2832
|
-
throw new Error('Failure in test automation. You use "I.
|
|
2931
|
+
throw new Error('Failure in test automation. You use "I.seeTraffic", but "I.startRecordingTraffic" was never called before.');
|
|
2833
2932
|
}
|
|
2834
2933
|
|
|
2835
2934
|
for (let i = 0; i <= timeout * 2; i++) {
|
|
@@ -2837,7 +2936,9 @@ class Playwright extends Helper {
|
|
|
2837
2936
|
if (found) {
|
|
2838
2937
|
return true;
|
|
2839
2938
|
}
|
|
2840
|
-
await new Promise((done) =>
|
|
2939
|
+
await new Promise((done) => {
|
|
2940
|
+
setTimeout(done, 1000);
|
|
2941
|
+
});
|
|
2841
2942
|
}
|
|
2842
2943
|
|
|
2843
2944
|
// check request post data
|
|
@@ -2974,6 +3075,163 @@ class Playwright extends Helper {
|
|
|
2974
3075
|
});
|
|
2975
3076
|
return dumpedTraffic;
|
|
2976
3077
|
}
|
|
3078
|
+
|
|
3079
|
+
/**
|
|
3080
|
+
* Starts recording of websocket messages.
|
|
3081
|
+
* This also resets recorded websocket messages.
|
|
3082
|
+
*
|
|
3083
|
+
* ```js
|
|
3084
|
+
* await I.startRecordingWebSocketMessages();
|
|
3085
|
+
* ```
|
|
3086
|
+
*
|
|
3087
|
+
*/
|
|
3088
|
+
async startRecordingWebSocketMessages() {
|
|
3089
|
+
this.flushWebSocketMessages();
|
|
3090
|
+
this.recordingWebSocketMessages = true;
|
|
3091
|
+
this.recordedWebSocketMessagesAtLeastOnce = true;
|
|
3092
|
+
|
|
3093
|
+
this.cdpSession = await this.getNewCDPSession();
|
|
3094
|
+
await this.cdpSession.send('Network.enable');
|
|
3095
|
+
await this.cdpSession.send('Page.enable');
|
|
3096
|
+
|
|
3097
|
+
this.cdpSession.on(
|
|
3098
|
+
'Network.webSocketFrameReceived',
|
|
3099
|
+
(payload) => {
|
|
3100
|
+
this._logWebsocketMessages(this._getWebSocketLog('RECEIVED', payload));
|
|
3101
|
+
},
|
|
3102
|
+
);
|
|
3103
|
+
|
|
3104
|
+
this.cdpSession.on(
|
|
3105
|
+
'Network.webSocketFrameSent',
|
|
3106
|
+
(payload) => {
|
|
3107
|
+
this._logWebsocketMessages(this._getWebSocketLog('SENT', payload));
|
|
3108
|
+
},
|
|
3109
|
+
);
|
|
3110
|
+
|
|
3111
|
+
this.cdpSession.on(
|
|
3112
|
+
'Network.webSocketFrameError',
|
|
3113
|
+
(payload) => {
|
|
3114
|
+
this._logWebsocketMessages(this._getWebSocketLog('ERROR', payload));
|
|
3115
|
+
},
|
|
3116
|
+
);
|
|
3117
|
+
}
|
|
3118
|
+
|
|
3119
|
+
/**
|
|
3120
|
+
* Stops recording WS messages. Recorded WS messages is not flashed.
|
|
3121
|
+
*
|
|
3122
|
+
* ```js
|
|
3123
|
+
* await I.stopRecordingWebSocketMessages();
|
|
3124
|
+
* ```
|
|
3125
|
+
*/
|
|
3126
|
+
async stopRecordingWebSocketMessages() {
|
|
3127
|
+
await this.cdpSession.send('Network.disable');
|
|
3128
|
+
await this.cdpSession.send('Page.disable');
|
|
3129
|
+
this.page.removeAllListeners('Network');
|
|
3130
|
+
this.recordingWebSocketMessages = false;
|
|
3131
|
+
}
|
|
3132
|
+
|
|
3133
|
+
/**
|
|
3134
|
+
* Grab the recording WS messages
|
|
3135
|
+
*
|
|
3136
|
+
* @return { Array<any> }
|
|
3137
|
+
*
|
|
3138
|
+
*/
|
|
3139
|
+
grabWebSocketMessages() {
|
|
3140
|
+
if (!this.recordingWebSocketMessages) {
|
|
3141
|
+
if (!this.recordedWebSocketMessagesAtLeastOnce) {
|
|
3142
|
+
throw new Error('Failure in test automation. You use "I.grabWebSocketMessages", but "I.startRecordingWebSocketMessages" was never called before.');
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
return this.webSocketMessages;
|
|
3146
|
+
}
|
|
3147
|
+
|
|
3148
|
+
/**
|
|
3149
|
+
* Resets all recorded WS messages.
|
|
3150
|
+
*/
|
|
3151
|
+
flushWebSocketMessages() {
|
|
3152
|
+
this.webSocketMessages = [];
|
|
3153
|
+
}
|
|
3154
|
+
|
|
3155
|
+
/**
|
|
3156
|
+
* Return a performance metric from the chrome cdp session.
|
|
3157
|
+
* Note: Chrome-only
|
|
3158
|
+
*
|
|
3159
|
+
* Examples:
|
|
3160
|
+
*
|
|
3161
|
+
* ```js
|
|
3162
|
+
* const metrics = await I.grabMetrics();
|
|
3163
|
+
*
|
|
3164
|
+
* // returned metrics
|
|
3165
|
+
*
|
|
3166
|
+
* [
|
|
3167
|
+
* { name: 'Timestamp', value: 1584904.203473 },
|
|
3168
|
+
* { name: 'AudioHandlers', value: 0 },
|
|
3169
|
+
* { name: 'AudioWorkletProcessors', value: 0 },
|
|
3170
|
+
* { name: 'Documents', value: 22 },
|
|
3171
|
+
* { name: 'Frames', value: 10 },
|
|
3172
|
+
* { name: 'JSEventListeners', value: 366 },
|
|
3173
|
+
* { name: 'LayoutObjects', value: 1240 },
|
|
3174
|
+
* { name: 'MediaKeySessions', value: 0 },
|
|
3175
|
+
* { name: 'MediaKeys', value: 0 },
|
|
3176
|
+
* { name: 'Nodes', value: 4505 },
|
|
3177
|
+
* { name: 'Resources', value: 141 },
|
|
3178
|
+
* { name: 'ContextLifecycleStateObservers', value: 34 },
|
|
3179
|
+
* { name: 'V8PerContextDatas', value: 4 },
|
|
3180
|
+
* { name: 'WorkerGlobalScopes', value: 0 },
|
|
3181
|
+
* { name: 'UACSSResources', value: 0 },
|
|
3182
|
+
* { name: 'RTCPeerConnections', value: 0 },
|
|
3183
|
+
* { name: 'ResourceFetchers', value: 22 },
|
|
3184
|
+
* { name: 'AdSubframes', value: 0 },
|
|
3185
|
+
* { name: 'DetachedScriptStates', value: 2 },
|
|
3186
|
+
* { name: 'ArrayBufferContents', value: 1 },
|
|
3187
|
+
* { name: 'LayoutCount', value: 0 },
|
|
3188
|
+
* { name: 'RecalcStyleCount', value: 0 },
|
|
3189
|
+
* { name: 'LayoutDuration', value: 0 },
|
|
3190
|
+
* { name: 'RecalcStyleDuration', value: 0 },
|
|
3191
|
+
* { name: 'DevToolsCommandDuration', value: 0.000013 },
|
|
3192
|
+
* { name: 'ScriptDuration', value: 0 },
|
|
3193
|
+
* { name: 'V8CompileDuration', value: 0 },
|
|
3194
|
+
* { name: 'TaskDuration', value: 0.000014 },
|
|
3195
|
+
* { name: 'TaskOtherDuration', value: 0.000001 },
|
|
3196
|
+
* { name: 'ThreadTime', value: 0.000046 },
|
|
3197
|
+
* { name: 'ProcessTime', value: 0.616852 },
|
|
3198
|
+
* { name: 'JSHeapUsedSize', value: 19004908 },
|
|
3199
|
+
* { name: 'JSHeapTotalSize', value: 26820608 },
|
|
3200
|
+
* { name: 'FirstMeaningfulPaint', value: 0 },
|
|
3201
|
+
* { name: 'DomContentLoaded', value: 1584903.690491 },
|
|
3202
|
+
* { name: 'NavigationStart', value: 1584902.841845 }
|
|
3203
|
+
* ]
|
|
3204
|
+
*
|
|
3205
|
+
* ```
|
|
3206
|
+
*
|
|
3207
|
+
* @return {Promise<Array<Object>>}
|
|
3208
|
+
*/
|
|
3209
|
+
async grabMetrics() {
|
|
3210
|
+
const client = await this.page.context().newCDPSession(this.page);
|
|
3211
|
+
await client.send('Performance.enable');
|
|
3212
|
+
const perfMetricObject = await client.send('Performance.getMetrics');
|
|
3213
|
+
return perfMetricObject?.metrics;
|
|
3214
|
+
}
|
|
3215
|
+
|
|
3216
|
+
_getWebSocketMessage(payload) {
|
|
3217
|
+
if (payload.errorMessage) {
|
|
3218
|
+
return payload.errorMessage;
|
|
3219
|
+
}
|
|
3220
|
+
|
|
3221
|
+
return payload.response.payloadData;
|
|
3222
|
+
}
|
|
3223
|
+
|
|
3224
|
+
_getWebSocketLog(prefix, payload) {
|
|
3225
|
+
return `${prefix} ID: ${payload.requestId} TIMESTAMP: ${payload.timestamp} (${new Date().toISOString()})\n\n${this._getWebSocketMessage(payload)}\n\n`;
|
|
3226
|
+
}
|
|
3227
|
+
|
|
3228
|
+
async getNewCDPSession() {
|
|
3229
|
+
return this.page.context().newCDPSession(this.page);
|
|
3230
|
+
}
|
|
3231
|
+
|
|
3232
|
+
_logWebsocketMessages(message) {
|
|
3233
|
+
this.webSocketMessages += message;
|
|
3234
|
+
}
|
|
2977
3235
|
}
|
|
2978
3236
|
|
|
2979
3237
|
module.exports = Playwright;
|
|
@@ -3028,7 +3286,7 @@ async function proceedClick(locator, context = null, options = {}) {
|
|
|
3028
3286
|
assertElementExists(els, locator, 'Clickable element');
|
|
3029
3287
|
}
|
|
3030
3288
|
|
|
3031
|
-
highlightActiveElement.call(this, els[0], this.
|
|
3289
|
+
highlightActiveElement.call(this, els[0], await this._getContext());
|
|
3032
3290
|
|
|
3033
3291
|
/*
|
|
3034
3292
|
using the force true options itself but instead dispatching a click
|
|
@@ -3041,7 +3299,7 @@ async function proceedClick(locator, context = null, options = {}) {
|
|
|
3041
3299
|
}
|
|
3042
3300
|
const promises = [];
|
|
3043
3301
|
if (options.waitForNavigation) {
|
|
3044
|
-
promises.push(this.waitForNavigation
|
|
3302
|
+
promises.push(this.waitForURL(/.*/, { waitUntil: options.waitForNavigation }));
|
|
3045
3303
|
}
|
|
3046
3304
|
promises.push(this._waitForAction());
|
|
3047
3305
|
|
|
@@ -3097,7 +3355,7 @@ async function proceedSee(assertType, text, context, strict = false) {
|
|
|
3097
3355
|
if (strict) {
|
|
3098
3356
|
return allText.map(elText => equals(description)[assertType](text, elText));
|
|
3099
3357
|
}
|
|
3100
|
-
return stringIncludes(description)[assertType](text, allText.join(' | '));
|
|
3358
|
+
return stringIncludes(description)[assertType](normalizeSpacesInString(text), normalizeSpacesInString(allText.join(' | ')));
|
|
3101
3359
|
}
|
|
3102
3360
|
|
|
3103
3361
|
async function findCheckable(locator, context) {
|
|
@@ -3206,7 +3464,16 @@ async function proceedSeeInField(assertType, field, value) {
|
|
|
3206
3464
|
return proceedMultiple(els[0]);
|
|
3207
3465
|
}
|
|
3208
3466
|
|
|
3209
|
-
|
|
3467
|
+
let fieldVal;
|
|
3468
|
+
|
|
3469
|
+
try {
|
|
3470
|
+
fieldVal = await el.inputValue();
|
|
3471
|
+
} catch (e) {
|
|
3472
|
+
if (e.message.includes('Error: Node is not an <input>, <textarea> or <select> element')) {
|
|
3473
|
+
fieldVal = await el.innerText();
|
|
3474
|
+
}
|
|
3475
|
+
}
|
|
3476
|
+
|
|
3210
3477
|
return stringIncludes(`fields by ${field}`)[assertType](value, fieldVal);
|
|
3211
3478
|
}
|
|
3212
3479
|
|
|
@@ -3443,7 +3710,7 @@ async function saveTraceForContext(context, name) {
|
|
|
3443
3710
|
}
|
|
3444
3711
|
|
|
3445
3712
|
function highlightActiveElement(element, context) {
|
|
3446
|
-
if (!this.options.
|
|
3713
|
+
if (!this.options.highlightElement && !store.debugMode) return;
|
|
3447
3714
|
|
|
3448
3715
|
highlightElement(element, context);
|
|
3449
3716
|
}
|