codeceptjs 3.0.7 → 3.1.3
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 +96 -2
- package/README.md +9 -1
- package/bin/codecept.js +27 -17
- package/docs/bdd.md +55 -1
- package/docs/build/Appium.js +76 -4
- package/docs/build/Playwright.js +186 -69
- package/docs/build/Protractor.js +2 -0
- package/docs/build/Puppeteer.js +56 -18
- package/docs/build/REST.js +12 -0
- package/docs/build/WebDriver.js +1 -3
- package/docs/changelog.md +96 -2
- package/docs/commands.md +21 -7
- package/docs/configuration.md +15 -2
- package/docs/helpers/Appium.md +96 -94
- package/docs/helpers/Playwright.md +259 -202
- package/docs/helpers/Puppeteer.md +17 -1
- package/docs/helpers/REST.md +23 -9
- package/docs/helpers/WebDriver.md +2 -2
- package/docs/mobile.md +2 -1
- package/docs/playwright.md +156 -6
- package/docs/plugins.md +61 -69
- package/docs/react.md +1 -1
- package/docs/reports.md +21 -3
- package/lib/actor.js +2 -3
- package/lib/codecept.js +13 -2
- package/lib/command/definitions.js +8 -1
- package/lib/command/run-multiple/collection.js +4 -0
- package/lib/config.js +1 -1
- package/lib/container.js +3 -3
- package/lib/data/dataTableArgument.js +35 -0
- package/lib/helper/Appium.js +49 -4
- package/lib/helper/Playwright.js +186 -69
- package/lib/helper/Protractor.js +2 -0
- package/lib/helper/Puppeteer.js +56 -18
- package/lib/helper/REST.js +12 -0
- package/lib/helper/WebDriver.js +1 -3
- package/lib/helper/errors/ConnectionRefused.js +1 -1
- package/lib/helper/extras/Popup.js +1 -1
- package/lib/helper/extras/React.js +44 -32
- package/lib/index.js +2 -0
- package/lib/interfaces/gherkin.js +8 -1
- package/lib/listener/exit.js +2 -4
- package/lib/listener/helpers.js +4 -4
- package/lib/locator.js +7 -0
- package/lib/mochaFactory.js +13 -9
- package/lib/output.js +2 -2
- package/lib/plugin/allure.js +7 -18
- package/lib/plugin/commentStep.js +1 -1
- package/lib/plugin/{puppeteerCoverage.js → coverage.js} +10 -22
- package/lib/plugin/customLocator.js +2 -2
- package/lib/plugin/subtitles.js +88 -0
- package/lib/plugin/tryTo.js +1 -1
- package/lib/recorder.js +5 -3
- package/lib/step.js +4 -2
- package/package.json +4 -3
- package/typings/index.d.ts +2 -0
- package/typings/types.d.ts +158 -18
package/docs/build/Playwright.js
CHANGED
|
@@ -19,6 +19,7 @@ const {
|
|
|
19
19
|
screenshotOutputFolder,
|
|
20
20
|
getNormalizedKeyAttributeValue,
|
|
21
21
|
isModifierKey,
|
|
22
|
+
clearString,
|
|
22
23
|
} = require('../utils');
|
|
23
24
|
const {
|
|
24
25
|
isColorProperty,
|
|
@@ -28,6 +29,7 @@ const ElementNotFound = require('./errors/ElementNotFound');
|
|
|
28
29
|
const RemoteBrowserConnectionRefused = require('./errors/RemoteBrowserConnectionRefused');
|
|
29
30
|
const Popup = require('./extras/Popup');
|
|
30
31
|
const Console = require('./extras/Console');
|
|
32
|
+
const findReact = require('./extras/React');
|
|
31
33
|
|
|
32
34
|
let playwright;
|
|
33
35
|
let perfTiming;
|
|
@@ -64,6 +66,8 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
|
|
|
64
66
|
* * `restart`: (optional, default: true) - restart browser between tests.
|
|
65
67
|
* * `disableScreenshots`: (optional, default: false) - don't save screenshot on failure.
|
|
66
68
|
* * `emulate`: (optional, default: {}) launch browser in device emulation mode.
|
|
69
|
+
* * `video`: (optional, default: false) enables video recording for failed tests; videos are saved into `output/videos` folder
|
|
70
|
+
* * `trace`: (optional, default: false) record [tracing information](https://playwright.dev/docs/trace-viewer) with screenshots and snapshots.
|
|
67
71
|
* * `fullPageScreenshots` (optional, default: false) - make full page screenshots on failure.
|
|
68
72
|
* * `uniqueScreenshotNames`: (optional, default: false) - option to prevent screenshot override if you have scenarios with the same name in different suites.
|
|
69
73
|
* * `keepBrowserState`: (optional, default: false) - keep browser state between tests when `restart` is set to false.
|
|
@@ -80,6 +84,22 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
|
|
|
80
84
|
* * `chromium`: (optional) pass additional chromium options
|
|
81
85
|
* * `electron`: (optional) pass additional electron options
|
|
82
86
|
*
|
|
87
|
+
* #### Video Recording Customization
|
|
88
|
+
*
|
|
89
|
+
* By default, video is saved to `output/video` dir. You can customize this path by passing `dir` option to `recordVideo` option.
|
|
90
|
+
*
|
|
91
|
+
* * `video`: enables video recording for failed tests; videos are saved into `output/videos` folder
|
|
92
|
+
* * `keepVideoForPassedTests`: - save videos for passed tests
|
|
93
|
+
* * `recordVideo`: [additional options for videos customization](https://playwright.dev/docs/next/api/class-browser#browser-new-context)
|
|
94
|
+
*
|
|
95
|
+
* #### Trace Recording Customization
|
|
96
|
+
*
|
|
97
|
+
* Trace recording provides a complete information on test execution and includes DOM snapshots, screenshots, and network requests logged during run.
|
|
98
|
+
* Traces will be saved to `output/trace`
|
|
99
|
+
*
|
|
100
|
+
* * `trace`: enables trace recording for failed tests; trace are saved into `output/trace` folder
|
|
101
|
+
* * `keepTraceForPassedTests`: - save trace for passed tests
|
|
102
|
+
*
|
|
83
103
|
* #### Example #1: Wait for 0 network connections.
|
|
84
104
|
*
|
|
85
105
|
* ```js
|
|
@@ -131,7 +151,7 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
|
|
|
131
151
|
* Playwright: {
|
|
132
152
|
* url: "http://localhost",
|
|
133
153
|
* chromium: {
|
|
134
|
-
* browserWSEndpoint:
|
|
154
|
+
* browserWSEndpoint: 'ws://localhost:9222/devtools/browser/c5aa6160-b5bc-4d53-bb49-6ecb36cd2e0a'
|
|
135
155
|
* }
|
|
136
156
|
* }
|
|
137
157
|
* }
|
|
@@ -211,6 +231,7 @@ class Playwright extends Helper {
|
|
|
211
231
|
this.activeSessionName = '';
|
|
212
232
|
this.isElectron = false;
|
|
213
233
|
this.electronSessions = [];
|
|
234
|
+
this.storageState = null;
|
|
214
235
|
|
|
215
236
|
// override defaults with config
|
|
216
237
|
this._setConfig(config);
|
|
@@ -220,7 +241,6 @@ class Playwright extends Helper {
|
|
|
220
241
|
const defaults = {
|
|
221
242
|
// options to emulate context
|
|
222
243
|
emulate: {},
|
|
223
|
-
|
|
224
244
|
browser: 'chromium',
|
|
225
245
|
waitForAction: 100,
|
|
226
246
|
waitForTimeout: 1000,
|
|
@@ -249,10 +269,16 @@ class Playwright extends Helper {
|
|
|
249
269
|
}
|
|
250
270
|
|
|
251
271
|
_getOptionsForBrowser(config) {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
272
|
+
if (config[config.browser]) {
|
|
273
|
+
if (config[config.browser].browserWSEndpoint && config[config.browser].browserWSEndpoint.wsEndpoint) {
|
|
274
|
+
config[config.browser].browserWSEndpoint = config[config.browser].browserWSEndpoint.wsEndpoint;
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
...config[config.browser],
|
|
278
|
+
wsEndpoint: config[config.browser].browserWSEndpoint,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
return {};
|
|
256
282
|
}
|
|
257
283
|
|
|
258
284
|
_setConfig(config) {
|
|
@@ -261,6 +287,12 @@ class Playwright extends Helper {
|
|
|
261
287
|
headless: !this.options.show,
|
|
262
288
|
...this._getOptionsForBrowser(config),
|
|
263
289
|
};
|
|
290
|
+
if (this.options.video) {
|
|
291
|
+
this.options.recordVideo = { size: parseWindowSize(this.options.windowSize) };
|
|
292
|
+
}
|
|
293
|
+
if (this.options.recordVideo && !this.options.recordVideo.dir) {
|
|
294
|
+
this.options.recordVideo.dir = `${global.output_dir}/videos/`;
|
|
295
|
+
}
|
|
264
296
|
this.isRemoteBrowser = !!this.playwrightOptions.browserWSEndpoint;
|
|
265
297
|
this.isElectron = this.options.browser === 'electron';
|
|
266
298
|
this.userDataDir = this.playwrightOptions.userDataDir;
|
|
@@ -319,8 +351,42 @@ class Playwright extends Helper {
|
|
|
319
351
|
return err.message.includes('context');
|
|
320
352
|
},
|
|
321
353
|
});
|
|
322
|
-
if (this.options.restart && !this.options.manualStart)
|
|
323
|
-
if (!this.isRunning && !this.options.manualStart)
|
|
354
|
+
if (this.options.restart && !this.options.manualStart) await this._startBrowser();
|
|
355
|
+
if (!this.isRunning && !this.options.manualStart) await this._startBrowser();
|
|
356
|
+
|
|
357
|
+
this.isAuthenticated = false;
|
|
358
|
+
if (this.isElectron) {
|
|
359
|
+
this.browserContext = this.browser.context();
|
|
360
|
+
} else if (this.userDataDir) {
|
|
361
|
+
this.browserContext = this.browser;
|
|
362
|
+
} else {
|
|
363
|
+
const contextOptions = {
|
|
364
|
+
ignoreHTTPSErrors: this.options.ignoreHTTPSErrors,
|
|
365
|
+
acceptDownloads: true,
|
|
366
|
+
...this.options.emulate,
|
|
367
|
+
};
|
|
368
|
+
if (this.options.basicAuth) {
|
|
369
|
+
contextOptions.httpCredentials = this.options.basicAuth;
|
|
370
|
+
this.isAuthenticated = true;
|
|
371
|
+
}
|
|
372
|
+
if (this.options.recordVideo) contextOptions.recordVideo = this.options.recordVideo;
|
|
373
|
+
if (this.storageState) contextOptions.storageState = this.storageState;
|
|
374
|
+
this.browserContext = await this.browser.newContext(contextOptions); // Adding the HTTPSError ignore in the context so that we can ignore those errors
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
let mainPage;
|
|
378
|
+
if (this.isElectron) {
|
|
379
|
+
mainPage = await this.browser.firstWindow();
|
|
380
|
+
} else {
|
|
381
|
+
const existingPages = await this.browserContext.pages();
|
|
382
|
+
mainPage = existingPages[0] || await this.browserContext.newPage();
|
|
383
|
+
}
|
|
384
|
+
targetCreatedHandler.call(this, mainPage);
|
|
385
|
+
|
|
386
|
+
await this._setPage(mainPage);
|
|
387
|
+
|
|
388
|
+
if (this.options.trace) await this.browserContext.tracing.start({ screenshots: true, snapshots: true });
|
|
389
|
+
|
|
324
390
|
return this.browser;
|
|
325
391
|
}
|
|
326
392
|
|
|
@@ -333,43 +399,24 @@ class Playwright extends Helper {
|
|
|
333
399
|
return;
|
|
334
400
|
}
|
|
335
401
|
|
|
402
|
+
if (this.options.restart) {
|
|
403
|
+
this.isRunning = false;
|
|
404
|
+
return this._stopBrowser();
|
|
405
|
+
}
|
|
406
|
+
|
|
336
407
|
// close other sessions
|
|
337
408
|
try {
|
|
338
409
|
const contexts = await this.browser.contexts();
|
|
339
|
-
contexts
|
|
410
|
+
const currentContext = contexts[0];
|
|
411
|
+
if (currentContext && (this.options.keepCookies || this.options.keepBrowserState)) {
|
|
412
|
+
this.storageState = await currentContext.storageState();
|
|
413
|
+
}
|
|
340
414
|
|
|
341
415
|
await Promise.all(contexts.map(c => c.close()));
|
|
342
416
|
} catch (e) {
|
|
343
417
|
console.log(e);
|
|
344
418
|
}
|
|
345
419
|
|
|
346
|
-
if (this.options.restart) {
|
|
347
|
-
this.isRunning = false;
|
|
348
|
-
return this._stopBrowser();
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// ensure current page is in default context
|
|
352
|
-
if (this.page) {
|
|
353
|
-
const existingPages = await this.browserContext.pages();
|
|
354
|
-
await this._setPage(existingPages[0]);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
if (this.options.keepBrowserState) return;
|
|
358
|
-
|
|
359
|
-
if (!this.options.keepCookies) {
|
|
360
|
-
this.debugSection('Session', 'cleaning cookies and localStorage');
|
|
361
|
-
await this.clearCookie();
|
|
362
|
-
}
|
|
363
|
-
const currentUrl = await this.grabCurrentUrl();
|
|
364
|
-
|
|
365
|
-
if (currentUrl.startsWith('http')) {
|
|
366
|
-
await this.executeScript('localStorage.clear();').catch((err) => {
|
|
367
|
-
if (!(err.message.indexOf("Storage is disabled inside 'data:' URLs.") > -1)) throw err;
|
|
368
|
-
});
|
|
369
|
-
await this.executeScript('sessionStorage.clear();').catch((err) => {
|
|
370
|
-
if (!(err.message.indexOf("Storage is disabled inside 'data:' URLs.") > -1)) throw err;
|
|
371
|
-
});
|
|
372
|
-
}
|
|
373
420
|
// await this.closeOtherTabs();
|
|
374
421
|
return this.browser;
|
|
375
422
|
}
|
|
@@ -524,7 +571,7 @@ class Playwright extends Helper {
|
|
|
524
571
|
page.setDefaultNavigationTimeout(this.options.getPageTimeout);
|
|
525
572
|
this.context = await this.page;
|
|
526
573
|
this.contextLocator = null;
|
|
527
|
-
if (this.
|
|
574
|
+
if (this.options.browser === 'chrome') {
|
|
528
575
|
await page.bringToFront();
|
|
529
576
|
}
|
|
530
577
|
}
|
|
@@ -540,6 +587,7 @@ class Playwright extends Helper {
|
|
|
540
587
|
if (!page) {
|
|
541
588
|
return;
|
|
542
589
|
}
|
|
590
|
+
page.removeAllListeners('dialog');
|
|
543
591
|
page.on('dialog', async (dialog) => {
|
|
544
592
|
popupStore.popup = dialog;
|
|
545
593
|
const action = popupStore.actionType || this.options.defaultPopupAction;
|
|
@@ -586,7 +634,7 @@ class Playwright extends Helper {
|
|
|
586
634
|
this.browser = await playwright._electron.launch(this.playwrightOptions);
|
|
587
635
|
} else if (this.isRemoteBrowser) {
|
|
588
636
|
try {
|
|
589
|
-
this.browser = await playwright[this.options.browser].connect(this.playwrightOptions
|
|
637
|
+
this.browser = await playwright[this.options.browser].connect(this.playwrightOptions);
|
|
590
638
|
} catch (err) {
|
|
591
639
|
if (err.toString().indexOf('ECONNREFUSED')) {
|
|
592
640
|
throw new RemoteBrowserConnectionRefused(err);
|
|
@@ -604,26 +652,6 @@ class Playwright extends Helper {
|
|
|
604
652
|
this.debugSection('Url', target.url());
|
|
605
653
|
});
|
|
606
654
|
|
|
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
|
-
}
|
|
614
|
-
|
|
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
|
-
}
|
|
622
|
-
targetCreatedHandler.call(this, mainPage);
|
|
623
|
-
|
|
624
|
-
await this._setPage(mainPage);
|
|
625
|
-
await this.closeOtherTabs();
|
|
626
|
-
|
|
627
655
|
this.isRunning = true;
|
|
628
656
|
}
|
|
629
657
|
|
|
@@ -707,9 +735,9 @@ class Playwright extends Helper {
|
|
|
707
735
|
url = this.options.url + url;
|
|
708
736
|
}
|
|
709
737
|
|
|
710
|
-
if (this.
|
|
738
|
+
if (this.options.basicAuth && (this.isAuthenticated !== true)) {
|
|
711
739
|
if (url.includes(this.options.url)) {
|
|
712
|
-
await this.browserContext.setHTTPCredentials(this.
|
|
740
|
+
await this.browserContext.setHTTPCredentials(this.options.basicAuth);
|
|
713
741
|
this.isAuthenticated = true;
|
|
714
742
|
}
|
|
715
743
|
}
|
|
@@ -772,7 +800,7 @@ class Playwright extends Helper {
|
|
|
772
800
|
if (!customHeaders) {
|
|
773
801
|
throw new Error('Cannot send empty headers.');
|
|
774
802
|
}
|
|
775
|
-
return this.
|
|
803
|
+
return this.browserContext.setExtraHTTPHeaders(customHeaders);
|
|
776
804
|
}
|
|
777
805
|
|
|
778
806
|
/**
|
|
@@ -1162,7 +1190,7 @@ class Playwright extends Helper {
|
|
|
1162
1190
|
*/
|
|
1163
1191
|
async seeElement(locator) {
|
|
1164
1192
|
let els = await this._locate(locator);
|
|
1165
|
-
els = await Promise.all(els.map(el => el.
|
|
1193
|
+
els = await Promise.all(els.map(el => el.isVisible()));
|
|
1166
1194
|
return empty('visible elements').negate(els.filter(v => v).fill('ELEMENT'));
|
|
1167
1195
|
}
|
|
1168
1196
|
|
|
@@ -1178,7 +1206,7 @@ class Playwright extends Helper {
|
|
|
1178
1206
|
*/
|
|
1179
1207
|
async dontSeeElement(locator) {
|
|
1180
1208
|
let els = await this._locate(locator);
|
|
1181
|
-
els = await Promise.all(els.map(el => el.
|
|
1209
|
+
els = await Promise.all(els.map(el => el.isVisible()));
|
|
1182
1210
|
return empty('visible elements').assert(els.filter(v => v).fill('ELEMENT'));
|
|
1183
1211
|
}
|
|
1184
1212
|
|
|
@@ -1797,7 +1825,7 @@ class Playwright extends Helper {
|
|
|
1797
1825
|
*/
|
|
1798
1826
|
async grabNumberOfVisibleElements(locator) {
|
|
1799
1827
|
let els = await this._locate(locator);
|
|
1800
|
-
els = await Promise.all(els.map(el => el.
|
|
1828
|
+
els = await Promise.all(els.map(el => el.isVisible()));
|
|
1801
1829
|
return els.filter(v => v).length;
|
|
1802
1830
|
}
|
|
1803
1831
|
|
|
@@ -2108,6 +2136,7 @@ class Playwright extends Helper {
|
|
|
2108
2136
|
async clearCookie() {
|
|
2109
2137
|
// Playwright currently doesn't support to delete a certain cookie
|
|
2110
2138
|
// https://github.com/microsoft/playwright/blob/master/docs/api.md#class-browsercontext
|
|
2139
|
+
if (!this.browserContext) return;
|
|
2111
2140
|
return this.browserContext.clearCookies();
|
|
2112
2141
|
}
|
|
2113
2142
|
|
|
@@ -2539,8 +2568,42 @@ class Playwright extends Helper {
|
|
|
2539
2568
|
return this.page.screenshot({ path: outputFile, fullPage: fullPageOption, type: 'png' });
|
|
2540
2569
|
}
|
|
2541
2570
|
|
|
2542
|
-
async _failed() {
|
|
2571
|
+
async _failed(test) {
|
|
2543
2572
|
await this._withinEnd();
|
|
2573
|
+
|
|
2574
|
+
if (!test.artifacts) {
|
|
2575
|
+
test.artifacts = {};
|
|
2576
|
+
}
|
|
2577
|
+
|
|
2578
|
+
if (this.options.recordVideo && this.page.video()) {
|
|
2579
|
+
test.artifacts.video = await this.page.video().path();
|
|
2580
|
+
}
|
|
2581
|
+
|
|
2582
|
+
if (this.options.trace) {
|
|
2583
|
+
const path = `${global.output_dir}/trace/${clearString(test.title).slice(0, 255)}.zip`;
|
|
2584
|
+
await this.browserContext.tracing.stop({ path });
|
|
2585
|
+
test.artifacts.trace = path;
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
|
|
2589
|
+
async _passed(test) {
|
|
2590
|
+
if (this.options.recordVideo && this.page.video()) {
|
|
2591
|
+
if (this.options.keepVideoForPassedTests) {
|
|
2592
|
+
test.artifacts.video = await this.page.video().path();
|
|
2593
|
+
} else {
|
|
2594
|
+
this.page.video().delete().catch(e => {});
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
if (this.options.trace) {
|
|
2599
|
+
if (this.options.keepTraceForPassedTests) {
|
|
2600
|
+
const path = `${global.output_dir}/trace/${clearString(test.title)}.zip`;
|
|
2601
|
+
await this.browserContext.tracing.stop({ path });
|
|
2602
|
+
test.artifacts.trace = path;
|
|
2603
|
+
} else {
|
|
2604
|
+
await this.browserContext.tracing.stop();
|
|
2605
|
+
}
|
|
2606
|
+
}
|
|
2544
2607
|
}
|
|
2545
2608
|
|
|
2546
2609
|
/**
|
|
@@ -3123,6 +3186,39 @@ class Playwright extends Helper {
|
|
|
3123
3186
|
if (prop) return rect[prop];
|
|
3124
3187
|
return rect;
|
|
3125
3188
|
}
|
|
3189
|
+
|
|
3190
|
+
/**
|
|
3191
|
+
* Mocks network request using [`browserContext.route`](https://playwright.dev/docs/api/class-browsercontext#browser-context-route) of Playwright
|
|
3192
|
+
*
|
|
3193
|
+
* ```js
|
|
3194
|
+
* I.mockRoute(/(\.png$)|(\.jpg$)/, route => route.abort());
|
|
3195
|
+
* ```
|
|
3196
|
+
* This method allows intercepting and mocking requests & responses. [Learn more about it](https://playwright.dev/docs/network#handle-requests)
|
|
3197
|
+
*
|
|
3198
|
+
* @param {string} [url] URL, regex or pattern for to match URL
|
|
3199
|
+
* @param {function} [handler] a function to process request
|
|
3200
|
+
*
|
|
3201
|
+
*/
|
|
3202
|
+
async mockRoute(url, handler) {
|
|
3203
|
+
return this.browserContext.route(...arguments);
|
|
3204
|
+
}
|
|
3205
|
+
|
|
3206
|
+
/**
|
|
3207
|
+
* Stops network mocking created by `mockRoute`.
|
|
3208
|
+
*
|
|
3209
|
+
* ```js
|
|
3210
|
+
* I.stopMockingRoute(/(\.png$)|(\.jpg$)/);
|
|
3211
|
+
* I.stopMockingRoute(/(\.png$)|(\.jpg$)/, previouslySetHandler);
|
|
3212
|
+
* ```
|
|
3213
|
+
* If no handler is passed, all mock requests for the rote are disabled.
|
|
3214
|
+
*
|
|
3215
|
+
* @param {string} [url] URL, regex or pattern for to match URL
|
|
3216
|
+
* @param {function} [handler] a function to process request
|
|
3217
|
+
*
|
|
3218
|
+
*/
|
|
3219
|
+
async stopMockingRoute(url, handler) {
|
|
3220
|
+
return this.browserContext.unroute(...arguments);
|
|
3221
|
+
}
|
|
3126
3222
|
}
|
|
3127
3223
|
|
|
3128
3224
|
module.exports = Playwright;
|
|
@@ -3138,6 +3234,7 @@ function buildLocatorString(locator) {
|
|
|
3138
3234
|
}
|
|
3139
3235
|
|
|
3140
3236
|
async function findElements(matcher, locator) {
|
|
3237
|
+
if (locator.react) return findReact(matcher, locator);
|
|
3141
3238
|
locator = new Locator(locator, 'css');
|
|
3142
3239
|
return matcher.$$(buildLocatorString(locator));
|
|
3143
3240
|
}
|
|
@@ -3186,6 +3283,8 @@ async function proceedClick(locator, context = null, options = {}) {
|
|
|
3186
3283
|
}
|
|
3187
3284
|
|
|
3188
3285
|
async function findClickable(matcher, locator) {
|
|
3286
|
+
if (locator.react) return findReact(matcher, locator);
|
|
3287
|
+
|
|
3189
3288
|
locator = new Locator(locator);
|
|
3190
3289
|
if (!locator.isFuzzy()) return findElements.call(this, matcher, locator);
|
|
3191
3290
|
|
|
@@ -3292,6 +3391,15 @@ async function findFields(locator) {
|
|
|
3292
3391
|
}
|
|
3293
3392
|
|
|
3294
3393
|
async function proceedDragAndDrop(sourceLocator, destinationLocator) {
|
|
3394
|
+
// modern drag and drop in Playwright
|
|
3395
|
+
if (this.page.dragAndDrop) {
|
|
3396
|
+
const source = new Locator(sourceLocator);
|
|
3397
|
+
const dest = new Locator(destinationLocator);
|
|
3398
|
+
if (source.isBasic() && dest.isBasic()) {
|
|
3399
|
+
return this.page.dragAndDrop(source.simplify(), dest.simplify());
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
|
|
3295
3403
|
const src = await this._locate(sourceLocator);
|
|
3296
3404
|
assertElementExists(src, sourceLocator, 'Source Element');
|
|
3297
3405
|
|
|
@@ -3451,11 +3559,20 @@ async function targetCreatedHandler(page) {
|
|
|
3451
3559
|
await page.setUserAgent(this.options.userAgent);
|
|
3452
3560
|
}
|
|
3453
3561
|
if (this.options.windowSize && this.options.windowSize.indexOf('x') > 0 && this._getType() === 'Browser') {
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3562
|
+
await page.setViewportSize(parseWindowSize(this.options.windowSize));
|
|
3563
|
+
}
|
|
3564
|
+
}
|
|
3565
|
+
|
|
3566
|
+
function parseWindowSize(windowSize) {
|
|
3567
|
+
if (!windowSize) return { width: 800, height: 600 };
|
|
3568
|
+
const dimensions = windowSize.split('x');
|
|
3569
|
+
if (dimensions.length < 2 || windowSize === 'maximize') {
|
|
3570
|
+
console.log('Invalid window size, setting window to default values');
|
|
3571
|
+
return { width: 800, height: 600 }; // invalid size
|
|
3458
3572
|
}
|
|
3573
|
+
const width = parseInt(dimensions[0], 10);
|
|
3574
|
+
const height = parseInt(dimensions[1], 10);
|
|
3575
|
+
return { width, height };
|
|
3459
3576
|
}
|
|
3460
3577
|
|
|
3461
3578
|
// List of key values to key definitions
|
package/docs/build/Protractor.js
CHANGED
|
@@ -124,6 +124,8 @@ class Protractor extends Helper {
|
|
|
124
124
|
|
|
125
125
|
this.isRunning = false;
|
|
126
126
|
this._setConfig(config);
|
|
127
|
+
|
|
128
|
+
console.log('Protractor helper is deprecated as well as Protractor itself.\nThis helper will be removed in next major release');
|
|
127
129
|
}
|
|
128
130
|
|
|
129
131
|
_validateConfig(config) {
|
package/docs/build/Puppeteer.js
CHANGED
|
@@ -129,8 +129,9 @@ const consoleLogStore = new Console();
|
|
|
129
129
|
* }
|
|
130
130
|
* }
|
|
131
131
|
* ```
|
|
132
|
+
* > Note: When connecting to remote browser `show` and specific `chrome` options (e.g. `headless` or `devtools`) are ignored.
|
|
132
133
|
*
|
|
133
|
-
* #### Example #5: Target URL with provided basic authentication
|
|
134
|
+
* #### Example #5: Target URL with provided basic authentication
|
|
134
135
|
*
|
|
135
136
|
* ```js
|
|
136
137
|
* {
|
|
@@ -143,10 +144,25 @@ const consoleLogStore = new Console();
|
|
|
143
144
|
* }
|
|
144
145
|
* }
|
|
145
146
|
* ```
|
|
147
|
+
* #### Troubleshooting
|
|
146
148
|
*
|
|
149
|
+
* Error Message: `No usable sandbox!`
|
|
150
|
+
*
|
|
151
|
+
* When running Puppeteer on CI try to disable sandbox if you see that message
|
|
152
|
+
*
|
|
153
|
+
* ```
|
|
154
|
+
* helpers: {
|
|
155
|
+
* Puppeteer: {
|
|
156
|
+
* url: 'http://localhost',
|
|
157
|
+
* show: false,
|
|
158
|
+
* chrome: {
|
|
159
|
+
* args: ['--no-sandbox', '--disable-setuid-sandbox']
|
|
160
|
+
* }
|
|
161
|
+
* },
|
|
162
|
+
* }
|
|
163
|
+
* ```
|
|
147
164
|
*
|
|
148
165
|
*
|
|
149
|
-
* Note: When connecting to remote browser `show` and specific `chrome` options (e.g. `headless` or `devtools`) are ignored.
|
|
150
166
|
*
|
|
151
167
|
* ## Access From Helpers
|
|
152
168
|
*
|
|
@@ -701,7 +717,7 @@ class Puppeteer extends Helper {
|
|
|
701
717
|
assertElementExists(els);
|
|
702
718
|
|
|
703
719
|
// Use manual mouse.move instead of .hover() so the offset can be added to the coordinates
|
|
704
|
-
const { x, y } = await els[0]
|
|
720
|
+
const { x, y } = await getClickablePoint(els[0]);
|
|
705
721
|
await this.page.mouse.move(x + offsetX, y + offsetY);
|
|
706
722
|
return this._waitForAction();
|
|
707
723
|
}
|
|
@@ -790,7 +806,7 @@ class Puppeteer extends Helper {
|
|
|
790
806
|
const els = await this._locate(locator);
|
|
791
807
|
assertElementExists(els, locator, 'Element');
|
|
792
808
|
await els[0]._scrollIntoViewIfNeeded();
|
|
793
|
-
const elementCoordinates = await els[0]
|
|
809
|
+
const elementCoordinates = await getClickablePoint(els[0]);
|
|
794
810
|
await this.executeScript((x, y) => window.scrollBy(x, y), elementCoordinates.x + offsetX, elementCoordinates.y + offsetY);
|
|
795
811
|
} else {
|
|
796
812
|
await this.executeScript((x, y) => window.scrollTo(x, y), offsetX, offsetY);
|
|
@@ -1047,7 +1063,10 @@ class Puppeteer extends Helper {
|
|
|
1047
1063
|
*/
|
|
1048
1064
|
async seeElement(locator) {
|
|
1049
1065
|
let els = await this._locate(locator);
|
|
1050
|
-
els = await Promise.all(els.map(el => el.boundingBox()));
|
|
1066
|
+
els = (await Promise.all(els.map(el => el.boundingBox() && el))).filter(v => v);
|
|
1067
|
+
// Puppeteer visibility was ignored? | Remove when Puppeteer is fixed
|
|
1068
|
+
els = await Promise.all(els.map(async el => (await el.evaluate(node => window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none')) && el));
|
|
1069
|
+
|
|
1051
1070
|
return empty('visible elements').negate(els.filter(v => v).fill('ELEMENT'));
|
|
1052
1071
|
}
|
|
1053
1072
|
|
|
@@ -1063,7 +1082,10 @@ class Puppeteer extends Helper {
|
|
|
1063
1082
|
*/
|
|
1064
1083
|
async dontSeeElement(locator) {
|
|
1065
1084
|
let els = await this._locate(locator);
|
|
1066
|
-
els = await Promise.all(els.map(el => el.boundingBox()));
|
|
1085
|
+
els = (await Promise.all(els.map(el => el.boundingBox() && el))).filter(v => v);
|
|
1086
|
+
// Puppeteer visibility was ignored? | Remove when Puppeteer is fixed
|
|
1087
|
+
els = await Promise.all(els.map(async el => (await el.evaluate(node => window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none')) && el));
|
|
1088
|
+
|
|
1067
1089
|
return empty('visible elements').assert(els.filter(v => v).fill('ELEMENT'));
|
|
1068
1090
|
}
|
|
1069
1091
|
|
|
@@ -1598,7 +1620,7 @@ class Puppeteer extends Helper {
|
|
|
1598
1620
|
* {{ react }}
|
|
1599
1621
|
*/
|
|
1600
1622
|
async fillField(field, value) {
|
|
1601
|
-
const els = await
|
|
1623
|
+
const els = await findVisibleFields.call(this, field);
|
|
1602
1624
|
assertElementExists(els, field, 'Field');
|
|
1603
1625
|
const el = els[0];
|
|
1604
1626
|
const tag = await el.getProperty('tagName').then(el => el.jsonValue());
|
|
@@ -1640,7 +1662,7 @@ class Puppeteer extends Helper {
|
|
|
1640
1662
|
* {{ react }}
|
|
1641
1663
|
*/
|
|
1642
1664
|
async appendField(field, value) {
|
|
1643
|
-
const els = await
|
|
1665
|
+
const els = await findVisibleFields.call(this, field);
|
|
1644
1666
|
assertElementExists(els, field, 'Field');
|
|
1645
1667
|
await els[0].press('End');
|
|
1646
1668
|
await els[0].type(value, { delay: this.options.pressKeyDelay });
|
|
@@ -1732,7 +1754,7 @@ class Puppeteer extends Helper {
|
|
|
1732
1754
|
*
|
|
1733
1755
|
*/
|
|
1734
1756
|
async selectOption(select, option) {
|
|
1735
|
-
const els = await
|
|
1757
|
+
const els = await findVisibleFields.call(this, select);
|
|
1736
1758
|
assertElementExists(els, select, 'Selectable field');
|
|
1737
1759
|
const el = els[0];
|
|
1738
1760
|
if (await el.getProperty('tagName').then(t => t.jsonValue()) !== 'SELECT') {
|
|
@@ -1774,7 +1796,10 @@ class Puppeteer extends Helper {
|
|
|
1774
1796
|
*/
|
|
1775
1797
|
async grabNumberOfVisibleElements(locator) {
|
|
1776
1798
|
let els = await this._locate(locator);
|
|
1777
|
-
els = await Promise.all(els.map(el => el.boundingBox()));
|
|
1799
|
+
els = (await Promise.all(els.map(el => el.boundingBox() && el))).filter(v => v);
|
|
1800
|
+
// Puppeteer visibility was ignored? | Remove when Puppeteer is fixed
|
|
1801
|
+
els = await Promise.all(els.map(async el => (await el.evaluate(node => window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none')) && el));
|
|
1802
|
+
|
|
1778
1803
|
return els.filter(v => v).length;
|
|
1779
1804
|
}
|
|
1780
1805
|
|
|
@@ -2452,8 +2477,8 @@ class Puppeteer extends Helper {
|
|
|
2452
2477
|
const src = await this._locate(locator);
|
|
2453
2478
|
assertElementExists(src, locator, 'Slider Element');
|
|
2454
2479
|
|
|
2455
|
-
// Note: Using
|
|
2456
|
-
const sliderSource = await src[0]
|
|
2480
|
+
// Note: Using public api .getClickablePoint because the .BoundingBox does not take into account iframe offsets
|
|
2481
|
+
const sliderSource = await getClickablePoint(src[0]);
|
|
2457
2482
|
|
|
2458
2483
|
// Drag start point
|
|
2459
2484
|
await this.page.mouse.move(sliderSource.x, sliderSource.y, { steps: 5 });
|
|
@@ -3202,7 +3227,7 @@ class Puppeteer extends Helper {
|
|
|
3202
3227
|
module.exports = Puppeteer;
|
|
3203
3228
|
|
|
3204
3229
|
async function findElements(matcher, locator) {
|
|
3205
|
-
if (locator.react) return findReact(matcher, locator);
|
|
3230
|
+
if (locator.react) return findReact(matcher.executionContext(), locator);
|
|
3206
3231
|
locator = new Locator(locator, 'css');
|
|
3207
3232
|
if (!locator.isXPath()) return matcher.$$(locator.simplify());
|
|
3208
3233
|
return matcher.$x(locator.value);
|
|
@@ -3231,7 +3256,7 @@ async function proceedClick(locator, context = null, options = {}) {
|
|
|
3231
3256
|
}
|
|
3232
3257
|
|
|
3233
3258
|
async function findClickable(matcher, locator) {
|
|
3234
|
-
if (locator.react) return findReact(matcher, locator);
|
|
3259
|
+
if (locator.react) return findReact(matcher.executionContext(), locator);
|
|
3235
3260
|
locator = new Locator(locator);
|
|
3236
3261
|
if (!locator.isFuzzy()) return findElements.call(this, matcher, locator);
|
|
3237
3262
|
|
|
@@ -3314,6 +3339,12 @@ async function proceedIsChecked(assertType, option) {
|
|
|
3314
3339
|
return truth(`checkable ${option}`, 'to be checked')[assertType](selected);
|
|
3315
3340
|
}
|
|
3316
3341
|
|
|
3342
|
+
async function findVisibleFields(locator) {
|
|
3343
|
+
const els = await findFields.call(this, locator);
|
|
3344
|
+
const visible = await Promise.all(els.map(el => el.boundingBox()));
|
|
3345
|
+
return els.filter((el, index) => visible[index]);
|
|
3346
|
+
}
|
|
3347
|
+
|
|
3317
3348
|
async function findFields(locator) {
|
|
3318
3349
|
const matchedLocator = new Locator(locator);
|
|
3319
3350
|
if (!matchedLocator.isFuzzy()) {
|
|
@@ -3344,9 +3375,9 @@ async function proceedDragAndDrop(sourceLocator, destinationLocator) {
|
|
|
3344
3375
|
const dst = await this._locate(destinationLocator);
|
|
3345
3376
|
assertElementExists(dst, destinationLocator, 'Destination Element');
|
|
3346
3377
|
|
|
3347
|
-
// Note: Using
|
|
3348
|
-
const dragSource = await src[0]
|
|
3349
|
-
const dragDestination = await dst[0]
|
|
3378
|
+
// Note: Using public api .getClickablePoint becaues the .BoundingBox does not take into account iframe offsets
|
|
3379
|
+
const dragSource = await getClickablePoint(src[0]);
|
|
3380
|
+
const dragDestination = await getClickablePoint(dst[0]);
|
|
3350
3381
|
|
|
3351
3382
|
// Drag start point
|
|
3352
3383
|
await this.page.mouse.move(dragSource.x, dragSource.y, { steps: 5 });
|
|
@@ -3360,7 +3391,7 @@ async function proceedDragAndDrop(sourceLocator, destinationLocator) {
|
|
|
3360
3391
|
}
|
|
3361
3392
|
|
|
3362
3393
|
async function proceedSeeInField(assertType, field, value) {
|
|
3363
|
-
const els = await
|
|
3394
|
+
const els = await findVisibleFields.call(this, field);
|
|
3364
3395
|
assertElementExists(els, field, 'Field');
|
|
3365
3396
|
const el = els[0];
|
|
3366
3397
|
const tag = await el.getProperty('tagName').then(el => el.jsonValue());
|
|
@@ -3493,6 +3524,13 @@ async function targetCreatedHandler(page) {
|
|
|
3493
3524
|
}
|
|
3494
3525
|
}
|
|
3495
3526
|
|
|
3527
|
+
// BC compatibility for Puppeteer < 10
|
|
3528
|
+
async function getClickablePoint(el) {
|
|
3529
|
+
if (el.clickablePoint) return el.clickablePoint();
|
|
3530
|
+
if (el._clickablePoint) return el._clickablePoint();
|
|
3531
|
+
return null;
|
|
3532
|
+
}
|
|
3533
|
+
|
|
3496
3534
|
// List of key values to key definitions
|
|
3497
3535
|
// https://github.com/GoogleChrome/puppeteer/blob/v1.20.0/lib/USKeyboardLayout.js
|
|
3498
3536
|
const keyDefinitionMap = {
|