codeceptjs 3.0.3 → 3.0.7
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/CHANGELOG.md +114 -18
- package/bin/codecept.js +1 -0
- package/docs/basics.md +2 -2
- package/docs/bdd.md +12 -1
- package/docs/build/Appium.js +2 -1
- package/docs/build/GraphQL.js +9 -10
- package/docs/build/Nightmare.js +4 -5
- package/docs/build/Playwright.js +164 -37
- package/docs/build/Protractor.js +1 -1
- package/docs/build/Puppeteer.js +1 -1
- package/docs/build/REST.js +24 -4
- package/docs/build/TestCafe.js +1 -1
- package/docs/build/WebDriver.js +85 -17
- package/docs/changelog.md +114 -18
- package/docs/data.md +5 -5
- package/docs/detox.md +2 -2
- package/docs/docker.md +11 -11
- package/docs/email.md +8 -8
- package/docs/helpers/Appium.md +1 -1
- package/docs/helpers/Nightmare.md +4 -5
- package/docs/helpers/Playwright.md +94 -64
- package/docs/helpers/Protractor.md +1 -1
- package/docs/helpers/Puppeteer.md +1 -1
- package/docs/helpers/REST.md +9 -0
- package/docs/helpers/TestCafe.md +1 -1
- package/docs/helpers/WebDriver.md +2 -1
- package/docs/locators.md +29 -2
- package/docs/mobile-react-native-locators.md +2 -2
- package/docs/mobile.md +3 -3
- package/docs/nightmare.md +0 -5
- package/docs/pageobjects.md +3 -1
- package/docs/parallel.md +35 -10
- package/docs/playwright.md +55 -8
- package/docs/plugins.md +73 -29
- package/docs/reports.md +8 -7
- package/docs/typescript.md +47 -5
- package/docs/webapi/fillField.mustache +1 -1
- package/lib/cli.js +25 -10
- package/lib/codecept.js +9 -1
- package/lib/command/interactive.js +10 -9
- package/lib/command/run.js +1 -1
- package/lib/command/workers/runTests.js +11 -6
- package/lib/config.js +8 -3
- package/lib/event.js +2 -0
- package/lib/helper/Appium.js +1 -0
- package/lib/helper/GraphQL.js +9 -10
- package/lib/helper/Nightmare.js +1 -1
- package/lib/helper/Playwright.js +131 -38
- package/lib/helper/REST.js +24 -4
- package/lib/helper/WebDriver.js +84 -16
- package/lib/interfaces/gherkin.js +11 -4
- package/lib/output.js +7 -4
- package/lib/plugin/allure.js +3 -7
- package/lib/plugin/fakerTransform.js +51 -0
- package/lib/plugin/screenshotOnFail.js +6 -2
- package/lib/recorder.js +9 -0
- package/lib/step.js +2 -1
- package/lib/transform.js +26 -0
- package/lib/ui.js +6 -2
- package/lib/within.js +1 -1
- package/lib/workers.js +39 -25
- package/package.json +14 -9
- package/typings/index.d.ts +49 -21
- package/typings/types.d.ts +72 -26
package/docs/build/Playwright.js
CHANGED
|
@@ -35,9 +35,10 @@ let defaultSelectorEnginesInitialized = false;
|
|
|
35
35
|
|
|
36
36
|
const popupStore = new Popup();
|
|
37
37
|
const consoleLogStore = new Console();
|
|
38
|
-
const availableBrowsers = ['chromium', 'webkit', 'firefox'];
|
|
38
|
+
const availableBrowsers = ['chromium', 'webkit', 'firefox', 'electron'];
|
|
39
39
|
|
|
40
40
|
const { createValueEngine, createDisabledEngine } = require('./extras/PlaywrightPropEngine');
|
|
41
|
+
|
|
41
42
|
/**
|
|
42
43
|
* Uses [Playwright](https://github.com/microsoft/playwright) library to run tests inside:
|
|
43
44
|
*
|
|
@@ -58,7 +59,7 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
|
|
|
58
59
|
* This helper should be configured in codecept.json or codecept.conf.js
|
|
59
60
|
*
|
|
60
61
|
* * `url`: base url of website to be tested
|
|
61
|
-
* * `browser`: a browser to test on, either: `chromium`, `firefox`, `webkit`. Default: chromium.
|
|
62
|
+
* * `browser`: a browser to test on, either: `chromium`, `firefox`, `webkit`, `electron`. Default: chromium.
|
|
62
63
|
* * `show`: (optional, default: false) - show browser window.
|
|
63
64
|
* * `restart`: (optional, default: true) - restart browser between tests.
|
|
64
65
|
* * `disableScreenshots`: (optional, default: false) - don't save screenshot on failure.
|
|
@@ -77,6 +78,7 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
|
|
|
77
78
|
* * `userAgent`: (optional) user-agent string.
|
|
78
79
|
* * `manualStart`: (optional, default: false) - do not start browser before a test, start it manually inside a helper with `this.helpers["Playwright"]._startBrowser()`.
|
|
79
80
|
* * `chromium`: (optional) pass additional chromium options
|
|
81
|
+
* * `electron`: (optional) pass additional electron options
|
|
80
82
|
*
|
|
81
83
|
* #### Example #1: Wait for 0 network connections.
|
|
82
84
|
*
|
|
@@ -121,7 +123,7 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
|
|
|
121
123
|
* }
|
|
122
124
|
* ```
|
|
123
125
|
*
|
|
124
|
-
* #### Example #4: Connect to remote browser by specifying [websocket endpoint](https://
|
|
126
|
+
* #### Example #4: Connect to remote browser by specifying [websocket endpoint](https://playwright.dev/docs/api/class-browsertype#browsertypeconnectparams)
|
|
125
127
|
*
|
|
126
128
|
* ```js
|
|
127
129
|
* {
|
|
@@ -129,7 +131,7 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
|
|
|
129
131
|
* Playwright: {
|
|
130
132
|
* url: "http://localhost",
|
|
131
133
|
* chromium: {
|
|
132
|
-
* browserWSEndpoint:
|
|
134
|
+
* browserWSEndpoint: { wsEndpoint: 'ws://localhost:9222/devtools/browser/c5aa6160-b5bc-4d53-bb49-6ecb36cd2e0a' }
|
|
133
135
|
* }
|
|
134
136
|
* }
|
|
135
137
|
* }
|
|
@@ -147,6 +149,7 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
|
|
|
147
149
|
* url: "http://localhost",
|
|
148
150
|
* show: true // headless mode not supported for extensions
|
|
149
151
|
* chromium: {
|
|
152
|
+
* userDataDir: '/tmp/playwright-tmp', // necessary to launch the browser in normal mode instead of incognito,
|
|
150
153
|
* args: [
|
|
151
154
|
* `--disable-extensions-except=${pathToExtension}`,
|
|
152
155
|
* `--load-extension=${pathToExtension}`
|
|
@@ -206,6 +209,8 @@ class Playwright extends Helper {
|
|
|
206
209
|
this.isAuthenticated = false;
|
|
207
210
|
this.sessionPages = {};
|
|
208
211
|
this.activeSessionName = '';
|
|
212
|
+
this.isElectron = false;
|
|
213
|
+
this.electronSessions = [];
|
|
209
214
|
|
|
210
215
|
// override defaults with config
|
|
211
216
|
this._setConfig(config);
|
|
@@ -257,6 +262,8 @@ class Playwright extends Helper {
|
|
|
257
262
|
...this._getOptionsForBrowser(config),
|
|
258
263
|
};
|
|
259
264
|
this.isRemoteBrowser = !!this.playwrightOptions.browserWSEndpoint;
|
|
265
|
+
this.isElectron = this.options.browser === 'electron';
|
|
266
|
+
this.userDataDir = this.playwrightOptions.userDataDir;
|
|
260
267
|
popupStore.defaultAction = this.options.defaultPopupAction;
|
|
261
268
|
}
|
|
262
269
|
|
|
@@ -268,7 +275,7 @@ class Playwright extends Helper {
|
|
|
268
275
|
},
|
|
269
276
|
{
|
|
270
277
|
name: 'browser',
|
|
271
|
-
message: 'Browser in which testing will be performed. Possible options: chromium, firefox or
|
|
278
|
+
message: 'Browser in which testing will be performed. Possible options: chromium, firefox, webkit or electron',
|
|
272
279
|
default: 'chromium',
|
|
273
280
|
},
|
|
274
281
|
];
|
|
@@ -320,11 +327,21 @@ class Playwright extends Helper {
|
|
|
320
327
|
async _after() {
|
|
321
328
|
if (!this.isRunning) return;
|
|
322
329
|
|
|
330
|
+
if (this.isElectron) {
|
|
331
|
+
this.browser.close();
|
|
332
|
+
this.electronSessions.forEach(session => session.close());
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
323
336
|
// close other sessions
|
|
324
|
-
|
|
325
|
-
|
|
337
|
+
try {
|
|
338
|
+
const contexts = await this.browser.contexts();
|
|
339
|
+
contexts.shift();
|
|
326
340
|
|
|
327
|
-
|
|
341
|
+
await Promise.all(contexts.map(c => c.close()));
|
|
342
|
+
} catch (e) {
|
|
343
|
+
console.log(e);
|
|
344
|
+
}
|
|
328
345
|
|
|
329
346
|
if (this.options.restart) {
|
|
330
347
|
this.isRunning = false;
|
|
@@ -371,12 +388,22 @@ class Playwright extends Helper {
|
|
|
371
388
|
this.debugSection('New Context', config ? JSON.stringify(config) : 'opened');
|
|
372
389
|
this.activeSessionName = sessionName;
|
|
373
390
|
|
|
374
|
-
|
|
375
|
-
|
|
391
|
+
let browserContext;
|
|
392
|
+
let page;
|
|
393
|
+
if (this.isElectron) {
|
|
394
|
+
const browser = await playwright._electron.launch(this.playwrightOptions);
|
|
395
|
+
this.electronSessions.push(browser);
|
|
396
|
+
browserContext = browser.context();
|
|
397
|
+
page = await browser.firstWindow();
|
|
398
|
+
} else {
|
|
399
|
+
browserContext = await this.browser.newContext(config);
|
|
400
|
+
page = await browserContext.newPage();
|
|
401
|
+
}
|
|
402
|
+
|
|
376
403
|
targetCreatedHandler.call(this, page);
|
|
377
404
|
this._setPage(page);
|
|
378
405
|
// Create a new page inside context.
|
|
379
|
-
return
|
|
406
|
+
return browserContext;
|
|
380
407
|
},
|
|
381
408
|
stop: async () => {
|
|
382
409
|
// is closed by _after
|
|
@@ -496,6 +523,7 @@ class Playwright extends Helper {
|
|
|
496
523
|
if (!page) return;
|
|
497
524
|
page.setDefaultNavigationTimeout(this.options.getPageTimeout);
|
|
498
525
|
this.context = await this.page;
|
|
526
|
+
this.contextLocator = null;
|
|
499
527
|
if (this.config.browser === 'chrome') {
|
|
500
528
|
await page.bringToFront();
|
|
501
529
|
}
|
|
@@ -554,15 +582,19 @@ class Playwright extends Helper {
|
|
|
554
582
|
}
|
|
555
583
|
|
|
556
584
|
async _startBrowser() {
|
|
557
|
-
if (this.
|
|
585
|
+
if (this.isElectron) {
|
|
586
|
+
this.browser = await playwright._electron.launch(this.playwrightOptions);
|
|
587
|
+
} else if (this.isRemoteBrowser) {
|
|
558
588
|
try {
|
|
559
|
-
this.browser = await playwright[this.options.browser].connect(this.playwrightOptions);
|
|
589
|
+
this.browser = await playwright[this.options.browser].connect(this.playwrightOptions.browserWSEndpoint);
|
|
560
590
|
} catch (err) {
|
|
561
591
|
if (err.toString().indexOf('ECONNREFUSED')) {
|
|
562
592
|
throw new RemoteBrowserConnectionRefused(err);
|
|
563
593
|
}
|
|
564
594
|
throw err;
|
|
565
595
|
}
|
|
596
|
+
} else if (this.userDataDir) {
|
|
597
|
+
this.browser = await playwright[this.options.browser].launchPersistentContext(this.userDataDir, this.playwrightOptions);
|
|
566
598
|
} else {
|
|
567
599
|
this.browser = await playwright[this.options.browser].launch(this.playwrightOptions);
|
|
568
600
|
}
|
|
@@ -571,11 +603,22 @@ class Playwright extends Helper {
|
|
|
571
603
|
this.browser.on('targetchanged', (target) => {
|
|
572
604
|
this.debugSection('Url', target.url());
|
|
573
605
|
});
|
|
574
|
-
this.browserContext = await this.browser.newContext({ ignoreHTTPSErrors: this.options.ignoreHTTPSErrors, acceptDownloads: true, ...this.options.emulate });// Adding the HTTPSError ignore in the context so that we can ignore those errors
|
|
575
606
|
|
|
576
|
-
|
|
607
|
+
if (this.isElectron) {
|
|
608
|
+
this.browserContext = this.browser.context();
|
|
609
|
+
} else if (this.userDataDir) {
|
|
610
|
+
this.browserContext = this.browser;
|
|
611
|
+
} else {
|
|
612
|
+
this.browserContext = await this.browser.newContext({ ignoreHTTPSErrors: this.options.ignoreHTTPSErrors, acceptDownloads: true, ...this.options.emulate });// Adding the HTTPSError ignore in the context so that we can ignore those errors
|
|
613
|
+
}
|
|
577
614
|
|
|
578
|
-
|
|
615
|
+
let mainPage;
|
|
616
|
+
if (this.isElectron) {
|
|
617
|
+
mainPage = await this.browser.firstWindow();
|
|
618
|
+
} else {
|
|
619
|
+
const existingPages = await this.browserContext.pages();
|
|
620
|
+
mainPage = existingPages[0] || await this.browserContext.newPage();
|
|
621
|
+
}
|
|
579
622
|
targetCreatedHandler.call(this, mainPage);
|
|
580
623
|
|
|
581
624
|
await this._setPage(mainPage);
|
|
@@ -584,6 +627,10 @@ class Playwright extends Helper {
|
|
|
584
627
|
this.isRunning = true;
|
|
585
628
|
}
|
|
586
629
|
|
|
630
|
+
_getType() {
|
|
631
|
+
return this.browser._type;
|
|
632
|
+
}
|
|
633
|
+
|
|
587
634
|
async _stopBrowser() {
|
|
588
635
|
this.withinLocator = null;
|
|
589
636
|
this._setPage(null);
|
|
@@ -618,6 +665,7 @@ class Playwright extends Helper {
|
|
|
618
665
|
const els = await this._locate(locator);
|
|
619
666
|
assertElementExists(els, locator);
|
|
620
667
|
this.context = els[0];
|
|
668
|
+
this.contextLocator = locator;
|
|
621
669
|
|
|
622
670
|
this.withinLocator = new Locator(locator);
|
|
623
671
|
}
|
|
@@ -625,6 +673,7 @@ class Playwright extends Helper {
|
|
|
625
673
|
async _withinEnd() {
|
|
626
674
|
this.withinLocator = null;
|
|
627
675
|
this.context = await this.page;
|
|
676
|
+
this.contextLocator = null;
|
|
628
677
|
}
|
|
629
678
|
|
|
630
679
|
_extractDataFromPerformanceTiming(timing, ...dataNames) {
|
|
@@ -651,6 +700,9 @@ class Playwright extends Helper {
|
|
|
651
700
|
* @param {string} url url path or global url.
|
|
652
701
|
*/
|
|
653
702
|
async amOnPage(url) {
|
|
703
|
+
if (this.isElectron) {
|
|
704
|
+
throw new Error('Cannot open pages inside an Electron container');
|
|
705
|
+
}
|
|
654
706
|
if (!(/^\w+\:\/\//.test(url))) {
|
|
655
707
|
url = this.options.url + url;
|
|
656
708
|
}
|
|
@@ -878,11 +930,14 @@ class Playwright extends Helper {
|
|
|
878
930
|
}
|
|
879
931
|
|
|
880
932
|
/**
|
|
881
|
-
*
|
|
882
|
-
*
|
|
883
|
-
*
|
|
884
|
-
*
|
|
885
|
-
*
|
|
933
|
+
* Checks that title is equal to provided one.
|
|
934
|
+
*
|
|
935
|
+
* ```js
|
|
936
|
+
* I.seeTitleEquals('Test title.');
|
|
937
|
+
* ```
|
|
938
|
+
*
|
|
939
|
+
* @param {string} text value to check.
|
|
940
|
+
*
|
|
886
941
|
*/
|
|
887
942
|
async seeTitleEquals(text) {
|
|
888
943
|
const title = await this.page.title();
|
|
@@ -981,6 +1036,9 @@ class Playwright extends Helper {
|
|
|
981
1036
|
* @param {number} [num=1]
|
|
982
1037
|
*/
|
|
983
1038
|
async switchToNextTab(num = 1) {
|
|
1039
|
+
if (this.isElectron) {
|
|
1040
|
+
throw new Error('Cannot switch tabs inside an Electron container');
|
|
1041
|
+
}
|
|
984
1042
|
const pages = await this.browserContext.pages();
|
|
985
1043
|
|
|
986
1044
|
const index = pages.indexOf(this.page);
|
|
@@ -1004,6 +1062,9 @@ class Playwright extends Helper {
|
|
|
1004
1062
|
* @param {number} [num=1]
|
|
1005
1063
|
*/
|
|
1006
1064
|
async switchToPreviousTab(num = 1) {
|
|
1065
|
+
if (this.isElectron) {
|
|
1066
|
+
throw new Error('Cannot switch tabs inside an Electron container');
|
|
1067
|
+
}
|
|
1007
1068
|
const pages = await this.browserContext.pages();
|
|
1008
1069
|
const index = pages.indexOf(this.page);
|
|
1009
1070
|
this.withinLocator = null;
|
|
@@ -1025,6 +1086,9 @@ class Playwright extends Helper {
|
|
|
1025
1086
|
* ```
|
|
1026
1087
|
*/
|
|
1027
1088
|
async closeCurrentTab() {
|
|
1089
|
+
if (this.isElectron) {
|
|
1090
|
+
throw new Error('Cannot close current tab inside an Electron container');
|
|
1091
|
+
}
|
|
1028
1092
|
const oldPage = this.page;
|
|
1029
1093
|
await this.switchToPreviousTab();
|
|
1030
1094
|
await oldPage.close();
|
|
@@ -1063,6 +1127,9 @@ class Playwright extends Helper {
|
|
|
1063
1127
|
* ```
|
|
1064
1128
|
*/
|
|
1065
1129
|
async openNewTab(options) {
|
|
1130
|
+
if (this.isElectron) {
|
|
1131
|
+
throw new Error('Cannot open new tabs inside an Electron container');
|
|
1132
|
+
}
|
|
1066
1133
|
await this._setPage(await this.browserContext.newPage(options));
|
|
1067
1134
|
return this._waitForAction();
|
|
1068
1135
|
}
|
|
@@ -1217,14 +1284,34 @@ class Playwright extends Helper {
|
|
|
1217
1284
|
}
|
|
1218
1285
|
|
|
1219
1286
|
/**
|
|
1220
|
-
*
|
|
1221
|
-
*
|
|
1222
|
-
*
|
|
1287
|
+
* Perform an emulated click on a link or a button, given by a locator.
|
|
1288
|
+
* Unlike normal click instead of sending native event, emulates a click with JavaScript.
|
|
1289
|
+
* This works on hidden, animated or inactive elements as well.
|
|
1290
|
+
*
|
|
1291
|
+
* If a fuzzy locator is given, the page will be searched for a button, link, or image matching the locator string.
|
|
1292
|
+
* For buttons, the "value" attribute, "name" attribute, and inner text are searched. For links, the link text is searched.
|
|
1293
|
+
* For images, the "alt" attribute and inner text of any parent links are searched.
|
|
1294
|
+
*
|
|
1295
|
+
* The second parameter is a context (CSS or XPath locator) to narrow the search.
|
|
1296
|
+
*
|
|
1223
1297
|
* ```js
|
|
1224
|
-
*
|
|
1225
|
-
* I.forceClick('
|
|
1298
|
+
* // simple link
|
|
1299
|
+
* I.forceClick('Logout');
|
|
1300
|
+
* // button of form
|
|
1301
|
+
* I.forceClick('Submit');
|
|
1302
|
+
* // CSS button
|
|
1303
|
+
* I.forceClick('#form input[type=submit]');
|
|
1304
|
+
* // XPath
|
|
1305
|
+
* I.forceClick('//form/*[@type=submit]');
|
|
1306
|
+
* // link in context
|
|
1307
|
+
* I.forceClick('Logout', '#nav');
|
|
1308
|
+
* // using strict locator
|
|
1309
|
+
* I.forceClick({css: 'nav a.login'});
|
|
1226
1310
|
* ```
|
|
1227
|
-
*
|
|
1311
|
+
*
|
|
1312
|
+
* @param {CodeceptJS.LocatorOrString} locator clickable link or button located by text, or any element located by CSS|XPath|strict locator.
|
|
1313
|
+
* @param {?CodeceptJS.LocatorOrString} [context=null] (optional, `null` by default) element to search in CSS|XPath|Strict locator.
|
|
1314
|
+
*
|
|
1228
1315
|
*/
|
|
1229
1316
|
async forceClick(locator, context = null) {
|
|
1230
1317
|
return proceedClick.call(this, locator, context, { force: true });
|
|
@@ -1530,7 +1617,7 @@ class Playwright extends Helper {
|
|
|
1530
1617
|
* I.fillField({css: 'form#login input[name=username]'}, 'John');
|
|
1531
1618
|
* ```
|
|
1532
1619
|
* @param {CodeceptJS.LocatorOrString} field located by label|name|CSS|XPath|strict locator.
|
|
1533
|
-
* @param {
|
|
1620
|
+
* @param {CodeceptJS.StringOrSecret} value text value to fill.
|
|
1534
1621
|
*
|
|
1535
1622
|
*
|
|
1536
1623
|
*/
|
|
@@ -2043,6 +2130,10 @@ class Playwright extends Helper {
|
|
|
2043
2130
|
* I.executeScript(([x, y]) => x + y, [x, y]);
|
|
2044
2131
|
* ```
|
|
2045
2132
|
* If a function returns a Promise it will wait for its resolution.
|
|
2133
|
+
*
|
|
2134
|
+
* @param {string|function} fn function to be executed in browser context.
|
|
2135
|
+
* @param {any} [arg] optional argument to pass to the function
|
|
2136
|
+
* @return {Promise<any>}
|
|
2046
2137
|
*/
|
|
2047
2138
|
async executeScript(fn, arg) {
|
|
2048
2139
|
let context = this.page;
|
|
@@ -2052,6 +2143,22 @@ class Playwright extends Helper {
|
|
|
2052
2143
|
return context.evaluate.apply(context, [fn, arg]);
|
|
2053
2144
|
}
|
|
2054
2145
|
|
|
2146
|
+
/**
|
|
2147
|
+
* Grab Locator if called within Context
|
|
2148
|
+
*
|
|
2149
|
+
* @param {*} locator
|
|
2150
|
+
*/
|
|
2151
|
+
_contextLocator(locator) {
|
|
2152
|
+
locator = buildLocatorString(new Locator(locator, 'css'));
|
|
2153
|
+
|
|
2154
|
+
if (this.contextLocator) {
|
|
2155
|
+
const contextLocator = buildLocatorString(new Locator(this.contextLocator, 'css'));
|
|
2156
|
+
locator = `${contextLocator} >> ${locator}`;
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
return locator;
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2055
2162
|
/**
|
|
2056
2163
|
* Retrieves a text from an element located by CSS or XPath and returns it to test.
|
|
2057
2164
|
* Resumes test execution, so **should be used inside async with `await`** operator.
|
|
@@ -2067,10 +2174,11 @@ class Playwright extends Helper {
|
|
|
2067
2174
|
*
|
|
2068
2175
|
*/
|
|
2069
2176
|
async grabTextFrom(locator) {
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2177
|
+
locator = this._contextLocator(locator);
|
|
2178
|
+
const text = await this.page.textContent(locator);
|
|
2179
|
+
assertElementExists(text, locator);
|
|
2180
|
+
this.debugSection('Text', text);
|
|
2181
|
+
return text;
|
|
2074
2182
|
}
|
|
2075
2183
|
|
|
2076
2184
|
/**
|
|
@@ -2209,8 +2317,7 @@ class Playwright extends Helper {
|
|
|
2209
2317
|
async grabCssPropertyFromAll(locator, cssProperty) {
|
|
2210
2318
|
const els = await this._locate(locator);
|
|
2211
2319
|
this.debug(`Matched ${els.length} elements`);
|
|
2212
|
-
const
|
|
2213
|
-
const cssValues = res.map(props => props[toCamelCase(cssProperty)]);
|
|
2320
|
+
const cssValues = await Promise.all(els.map(el => el.$eval('xpath=.', (el, cssProperty) => getComputedStyle(el).getPropertyValue(cssProperty), cssProperty)));
|
|
2214
2321
|
|
|
2215
2322
|
return cssValues;
|
|
2216
2323
|
}
|
|
@@ -2824,6 +2931,7 @@ class Playwright extends Helper {
|
|
|
2824
2931
|
|
|
2825
2932
|
if (locator >= 0 && locator < childFrames.length) {
|
|
2826
2933
|
this.context = childFrames[locator];
|
|
2934
|
+
this.contextLocator = locator;
|
|
2827
2935
|
} else {
|
|
2828
2936
|
throw new Error('Element #invalidIframeSelector was not found by text|CSS|XPath');
|
|
2829
2937
|
}
|
|
@@ -2831,6 +2939,7 @@ class Playwright extends Helper {
|
|
|
2831
2939
|
}
|
|
2832
2940
|
if (!locator) {
|
|
2833
2941
|
this.context = this.page;
|
|
2942
|
+
this.contextLocator = null;
|
|
2834
2943
|
return;
|
|
2835
2944
|
}
|
|
2836
2945
|
|
|
@@ -2841,8 +2950,10 @@ class Playwright extends Helper {
|
|
|
2841
2950
|
|
|
2842
2951
|
if (contentFrame) {
|
|
2843
2952
|
this.context = contentFrame;
|
|
2953
|
+
this.contextLocator = null;
|
|
2844
2954
|
} else {
|
|
2845
2955
|
this.context = els[0];
|
|
2956
|
+
this.contextLocator = locator;
|
|
2846
2957
|
}
|
|
2847
2958
|
}
|
|
2848
2959
|
|
|
@@ -2882,7 +2993,7 @@ class Playwright extends Helper {
|
|
|
2882
2993
|
/**
|
|
2883
2994
|
* Waits for navigation to finish. By default takes configured `waitForNavigation` option.
|
|
2884
2995
|
*
|
|
2885
|
-
* See [
|
|
2996
|
+
* See [Playwright's reference](https://playwright.dev/docs/api/class-page?_highlight=waitfornavi#pagewaitfornavigationoptions)
|
|
2886
2997
|
*
|
|
2887
2998
|
* @param {*} opts
|
|
2888
2999
|
*/
|
|
@@ -3031,6 +3142,19 @@ async function findElements(matcher, locator) {
|
|
|
3031
3142
|
return matcher.$$(buildLocatorString(locator));
|
|
3032
3143
|
}
|
|
3033
3144
|
|
|
3145
|
+
async function getVisibleElements(elements) {
|
|
3146
|
+
const visibleElements = [];
|
|
3147
|
+
for (const element of elements) {
|
|
3148
|
+
if (await element.isVisible()) {
|
|
3149
|
+
visibleElements.push(element);
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
if (visibleElements.length === 0) {
|
|
3153
|
+
return elements;
|
|
3154
|
+
}
|
|
3155
|
+
return visibleElements;
|
|
3156
|
+
}
|
|
3157
|
+
|
|
3034
3158
|
async function proceedClick(locator, context = null, options = {}) {
|
|
3035
3159
|
let matcher = await this._getContext();
|
|
3036
3160
|
if (context) {
|
|
@@ -3050,7 +3174,8 @@ async function proceedClick(locator, context = null, options = {}) {
|
|
|
3050
3174
|
if (options.force) {
|
|
3051
3175
|
await els[0].dispatchEvent('click');
|
|
3052
3176
|
} else {
|
|
3053
|
-
await els[0]
|
|
3177
|
+
const element = els.length > 1 ? (await getVisibleElements(els))[0] : els[0];
|
|
3178
|
+
await element.click(options);
|
|
3054
3179
|
}
|
|
3055
3180
|
const promises = [];
|
|
3056
3181
|
if (options.waitForNavigation) {
|
|
@@ -3308,11 +3433,13 @@ async function targetCreatedHandler(page) {
|
|
|
3308
3433
|
// we are inside iframe?
|
|
3309
3434
|
const frameEl = await this.context.frameElement();
|
|
3310
3435
|
this.context = await frameEl.contentFrame();
|
|
3436
|
+
this.contextLocator = null;
|
|
3311
3437
|
return;
|
|
3312
3438
|
}
|
|
3313
3439
|
// if context element was in iframe - keep it
|
|
3314
3440
|
// if (await this.context.ownerFrame()) return;
|
|
3315
3441
|
this.context = page;
|
|
3442
|
+
this.contextLocator = null;
|
|
3316
3443
|
});
|
|
3317
3444
|
});
|
|
3318
3445
|
page.on('console', (msg) => {
|
|
@@ -3323,7 +3450,7 @@ async function targetCreatedHandler(page) {
|
|
|
3323
3450
|
if (this.options.userAgent) {
|
|
3324
3451
|
await page.setUserAgent(this.options.userAgent);
|
|
3325
3452
|
}
|
|
3326
|
-
if (this.options.windowSize && this.options.windowSize.indexOf('x') > 0) {
|
|
3453
|
+
if (this.options.windowSize && this.options.windowSize.indexOf('x') > 0 && this._getType() === 'Browser') {
|
|
3327
3454
|
const dimensions = this.options.windowSize.split('x');
|
|
3328
3455
|
const width = parseInt(dimensions[0], 10);
|
|
3329
3456
|
const height = parseInt(dimensions[1], 10);
|
package/docs/build/Protractor.js
CHANGED
|
@@ -723,7 +723,7 @@ class Protractor extends Helper {
|
|
|
723
723
|
* I.fillField({css: 'form#login input[name=username]'}, 'John');
|
|
724
724
|
* ```
|
|
725
725
|
* @param {CodeceptJS.LocatorOrString} field located by label|name|CSS|XPath|strict locator.
|
|
726
|
-
* @param {
|
|
726
|
+
* @param {CodeceptJS.StringOrSecret} value text value to fill.
|
|
727
727
|
*
|
|
728
728
|
*/
|
|
729
729
|
async fillField(field, value) {
|
package/docs/build/Puppeteer.js
CHANGED
|
@@ -1593,7 +1593,7 @@ class Puppeteer extends Helper {
|
|
|
1593
1593
|
* I.fillField({css: 'form#login input[name=username]'}, 'John');
|
|
1594
1594
|
* ```
|
|
1595
1595
|
* @param {CodeceptJS.LocatorOrString} field located by label|name|CSS|XPath|strict locator.
|
|
1596
|
-
* @param {
|
|
1596
|
+
* @param {CodeceptJS.StringOrSecret} value text value to fill.
|
|
1597
1597
|
*
|
|
1598
1598
|
* {{ react }}
|
|
1599
1599
|
*/
|
package/docs/build/REST.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const axios = require('axios').default;
|
|
2
|
+
const Secret = require('../secret');
|
|
2
3
|
|
|
3
4
|
const Helper = require('../helper');
|
|
4
5
|
|
|
@@ -58,7 +59,8 @@ class REST extends Helper {
|
|
|
58
59
|
|
|
59
60
|
this.options = { ...this.options, ...config };
|
|
60
61
|
this.headers = { ...this.options.defaultHeaders };
|
|
61
|
-
axios
|
|
62
|
+
this.axios = axios.create();
|
|
63
|
+
this.axios.defaults.headers = this.options.defaultHeaders;
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
static _checkRequirements() {
|
|
@@ -75,12 +77,18 @@ class REST extends Helper {
|
|
|
75
77
|
* @param {*} request
|
|
76
78
|
*/
|
|
77
79
|
async _executeRequest(request) {
|
|
78
|
-
|
|
80
|
+
const _debugRequest = { ...request };
|
|
81
|
+
this.axios.defaults.timeout = request.timeout || this.options.timeout;
|
|
79
82
|
|
|
80
83
|
if (this.headers && this.headers.auth) {
|
|
81
84
|
request.auth = this.headers.auth;
|
|
82
85
|
}
|
|
83
86
|
|
|
87
|
+
if (request.data instanceof Secret) {
|
|
88
|
+
_debugRequest.data = '*****';
|
|
89
|
+
request.data = typeof request.data === 'object' ? { ...request.data.toString() } : request.data.toString();
|
|
90
|
+
}
|
|
91
|
+
|
|
84
92
|
if ((typeof request.data) === 'string') {
|
|
85
93
|
if (!request.headers || !request.headers['Content-Type']) {
|
|
86
94
|
request.headers = { ...request.headers, ...{ 'Content-Type': 'application/x-www-form-urlencoded' } };
|
|
@@ -91,11 +99,11 @@ class REST extends Helper {
|
|
|
91
99
|
await this.config.onRequest(request);
|
|
92
100
|
}
|
|
93
101
|
|
|
94
|
-
this.debugSection('Request', JSON.stringify(
|
|
102
|
+
this.debugSection('Request', JSON.stringify(_debugRequest));
|
|
95
103
|
|
|
96
104
|
let response;
|
|
97
105
|
try {
|
|
98
|
-
response = await axios(request);
|
|
106
|
+
response = await this.axios(request);
|
|
99
107
|
} catch (err) {
|
|
100
108
|
if (!err.response) throw err;
|
|
101
109
|
this.debugSection('Response', `Response error. Status code: ${err.response.status}`);
|
|
@@ -149,6 +157,10 @@ class REST extends Helper {
|
|
|
149
157
|
*
|
|
150
158
|
* ```js
|
|
151
159
|
* I.sendPostRequest('/api/users.json', { "email": "user@user.com" });
|
|
160
|
+
*
|
|
161
|
+
* // To mask the payload in logs
|
|
162
|
+
* I.sendPostRequest('/api/users.json', secret({ "email": "user@user.com" }));
|
|
163
|
+
*
|
|
152
164
|
* ```
|
|
153
165
|
*
|
|
154
166
|
* @param {*} url
|
|
@@ -176,6 +188,10 @@ class REST extends Helper {
|
|
|
176
188
|
*
|
|
177
189
|
* ```js
|
|
178
190
|
* I.sendPatchRequest('/api/users.json', { "email": "user@user.com" });
|
|
191
|
+
*
|
|
192
|
+
* // To mask the payload in logs
|
|
193
|
+
* I.sendPatchRequest('/api/users.json', secret({ "email": "user@user.com" }));
|
|
194
|
+
*
|
|
179
195
|
* ```
|
|
180
196
|
*
|
|
181
197
|
* @param {string} url
|
|
@@ -203,6 +219,10 @@ class REST extends Helper {
|
|
|
203
219
|
*
|
|
204
220
|
* ```js
|
|
205
221
|
* I.sendPutRequest('/api/users.json', { "email": "user@user.com" });
|
|
222
|
+
*
|
|
223
|
+
* // To mask the payload in logs
|
|
224
|
+
* I.sendPutRequest('/api/users.json', secret({ "email": "user@user.com" }));
|
|
225
|
+
*
|
|
206
226
|
* ```
|
|
207
227
|
*
|
|
208
228
|
* @param {string} url
|
package/docs/build/TestCafe.js
CHANGED
|
@@ -440,7 +440,7 @@ class TestCafe extends Helper {
|
|
|
440
440
|
* I.fillField({css: 'form#login input[name=username]'}, 'John');
|
|
441
441
|
* ```
|
|
442
442
|
* @param {CodeceptJS.LocatorOrString} field located by label|name|CSS|XPath|strict locator.
|
|
443
|
-
* @param {
|
|
443
|
+
* @param {CodeceptJS.StringOrSecret} value text value to fill.
|
|
444
444
|
*
|
|
445
445
|
*/
|
|
446
446
|
async fillField(field, value) {
|