codeceptjs 3.0.6 → 3.1.2
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 +92 -8
- package/README.md +9 -1
- package/bin/codecept.js +28 -17
- package/docs/build/Appium.js +69 -0
- package/docs/build/GraphQL.js +9 -10
- package/docs/build/Playwright.js +271 -63
- package/docs/build/Protractor.js +2 -0
- package/docs/build/Puppeteer.js +56 -18
- package/docs/build/REST.js +16 -3
- package/docs/build/WebDriver.js +82 -16
- package/docs/changelog.md +93 -9
- package/docs/configuration.md +15 -2
- package/docs/email.md +8 -8
- package/docs/examples.md +3 -3
- package/docs/helpers/Appium.md +66 -68
- package/docs/helpers/MockRequest.md +3 -3
- package/docs/helpers/Playwright.md +269 -203
- package/docs/helpers/Puppeteer.md +17 -1
- package/docs/helpers/REST.md +23 -9
- package/docs/helpers/WebDriver.md +3 -2
- package/docs/locators.md +27 -0
- package/docs/mobile.md +2 -1
- package/docs/nightmare.md +0 -5
- package/docs/parallel.md +14 -7
- package/docs/playwright.md +178 -11
- package/docs/plugins.md +61 -69
- package/docs/react.md +1 -1
- package/docs/reports.md +5 -4
- package/lib/actor.js +1 -2
- package/lib/codecept.js +13 -2
- package/lib/command/definitions.js +8 -1
- package/lib/command/interactive.js +4 -2
- package/lib/command/run-multiple/collection.js +4 -0
- package/lib/container.js +3 -3
- package/lib/helper/Appium.js +41 -0
- package/lib/helper/GraphQL.js +9 -10
- package/lib/helper/Playwright.js +218 -70
- 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 +82 -16
- 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/interfaces/gherkin.js +1 -0
- package/lib/listener/exit.js +2 -4
- package/lib/listener/helpers.js +3 -4
- package/lib/locator.js +7 -0
- package/lib/mochaFactory.js +11 -6
- package/lib/output.js +5 -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/screenshotOnFail.js +5 -0
- package/lib/plugin/subtitles.js +88 -0
- package/lib/plugin/tryTo.js +1 -1
- package/lib/step.js +4 -2
- package/lib/ui.js +6 -2
- package/package.json +5 -4
- package/typings/index.d.ts +44 -21
- package/typings/types.d.ts +137 -16
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;
|
|
@@ -35,9 +37,10 @@ let defaultSelectorEnginesInitialized = false;
|
|
|
35
37
|
|
|
36
38
|
const popupStore = new Popup();
|
|
37
39
|
const consoleLogStore = new Console();
|
|
38
|
-
const availableBrowsers = ['chromium', 'webkit', 'firefox'];
|
|
40
|
+
const availableBrowsers = ['chromium', 'webkit', 'firefox', 'electron'];
|
|
39
41
|
|
|
40
42
|
const { createValueEngine, createDisabledEngine } = require('./extras/PlaywrightPropEngine');
|
|
43
|
+
|
|
41
44
|
/**
|
|
42
45
|
* Uses [Playwright](https://github.com/microsoft/playwright) library to run tests inside:
|
|
43
46
|
*
|
|
@@ -58,11 +61,13 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
|
|
|
58
61
|
* This helper should be configured in codecept.json or codecept.conf.js
|
|
59
62
|
*
|
|
60
63
|
* * `url`: base url of website to be tested
|
|
61
|
-
* * `browser`: a browser to test on, either: `chromium`, `firefox`, `webkit`. Default: chromium.
|
|
64
|
+
* * `browser`: a browser to test on, either: `chromium`, `firefox`, `webkit`, `electron`. Default: chromium.
|
|
62
65
|
* * `show`: (optional, default: false) - show browser window.
|
|
63
66
|
* * `restart`: (optional, default: true) - restart browser between tests.
|
|
64
67
|
* * `disableScreenshots`: (optional, default: false) - don't save screenshot on failure.
|
|
65
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.
|
|
66
71
|
* * `fullPageScreenshots` (optional, default: false) - make full page screenshots on failure.
|
|
67
72
|
* * `uniqueScreenshotNames`: (optional, default: false) - option to prevent screenshot override if you have scenarios with the same name in different suites.
|
|
68
73
|
* * `keepBrowserState`: (optional, default: false) - keep browser state between tests when `restart` is set to false.
|
|
@@ -77,6 +82,23 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
|
|
|
77
82
|
* * `userAgent`: (optional) user-agent string.
|
|
78
83
|
* * `manualStart`: (optional, default: false) - do not start browser before a test, start it manually inside a helper with `this.helpers["Playwright"]._startBrowser()`.
|
|
79
84
|
* * `chromium`: (optional) pass additional chromium options
|
|
85
|
+
* * `electron`: (optional) pass additional electron options
|
|
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
|
|
80
102
|
*
|
|
81
103
|
* #### Example #1: Wait for 0 network connections.
|
|
82
104
|
*
|
|
@@ -121,7 +143,7 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
|
|
|
121
143
|
* }
|
|
122
144
|
* ```
|
|
123
145
|
*
|
|
124
|
-
* #### Example #4: Connect to remote browser by specifying [websocket endpoint](https://
|
|
146
|
+
* #### Example #4: Connect to remote browser by specifying [websocket endpoint](https://playwright.dev/docs/api/class-browsertype#browsertypeconnectparams)
|
|
125
147
|
*
|
|
126
148
|
* ```js
|
|
127
149
|
* {
|
|
@@ -129,7 +151,7 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
|
|
|
129
151
|
* Playwright: {
|
|
130
152
|
* url: "http://localhost",
|
|
131
153
|
* chromium: {
|
|
132
|
-
* browserWSEndpoint:
|
|
154
|
+
* browserWSEndpoint: 'ws://localhost:9222/devtools/browser/c5aa6160-b5bc-4d53-bb49-6ecb36cd2e0a'
|
|
133
155
|
* }
|
|
134
156
|
* }
|
|
135
157
|
* }
|
|
@@ -147,6 +169,7 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
|
|
|
147
169
|
* url: "http://localhost",
|
|
148
170
|
* show: true // headless mode not supported for extensions
|
|
149
171
|
* chromium: {
|
|
172
|
+
* userDataDir: '/tmp/playwright-tmp', // necessary to launch the browser in normal mode instead of incognito,
|
|
150
173
|
* args: [
|
|
151
174
|
* `--disable-extensions-except=${pathToExtension}`,
|
|
152
175
|
* `--load-extension=${pathToExtension}`
|
|
@@ -206,6 +229,9 @@ class Playwright extends Helper {
|
|
|
206
229
|
this.isAuthenticated = false;
|
|
207
230
|
this.sessionPages = {};
|
|
208
231
|
this.activeSessionName = '';
|
|
232
|
+
this.isElectron = false;
|
|
233
|
+
this.electronSessions = [];
|
|
234
|
+
this.storageState = null;
|
|
209
235
|
|
|
210
236
|
// override defaults with config
|
|
211
237
|
this._setConfig(config);
|
|
@@ -215,7 +241,6 @@ class Playwright extends Helper {
|
|
|
215
241
|
const defaults = {
|
|
216
242
|
// options to emulate context
|
|
217
243
|
emulate: {},
|
|
218
|
-
|
|
219
244
|
browser: 'chromium',
|
|
220
245
|
waitForAction: 100,
|
|
221
246
|
waitForTimeout: 1000,
|
|
@@ -244,10 +269,16 @@ class Playwright extends Helper {
|
|
|
244
269
|
}
|
|
245
270
|
|
|
246
271
|
_getOptionsForBrowser(config) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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 {};
|
|
251
282
|
}
|
|
252
283
|
|
|
253
284
|
_setConfig(config) {
|
|
@@ -256,7 +287,15 @@ class Playwright extends Helper {
|
|
|
256
287
|
headless: !this.options.show,
|
|
257
288
|
...this._getOptionsForBrowser(config),
|
|
258
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
|
+
}
|
|
259
296
|
this.isRemoteBrowser = !!this.playwrightOptions.browserWSEndpoint;
|
|
297
|
+
this.isElectron = this.options.browser === 'electron';
|
|
298
|
+
this.userDataDir = this.playwrightOptions.userDataDir;
|
|
260
299
|
popupStore.defaultAction = this.options.defaultPopupAction;
|
|
261
300
|
}
|
|
262
301
|
|
|
@@ -268,7 +307,7 @@ class Playwright extends Helper {
|
|
|
268
307
|
},
|
|
269
308
|
{
|
|
270
309
|
name: 'browser',
|
|
271
|
-
message: 'Browser in which testing will be performed. Possible options: chromium, firefox or
|
|
310
|
+
message: 'Browser in which testing will be performed. Possible options: chromium, firefox, webkit or electron',
|
|
272
311
|
default: 'chromium',
|
|
273
312
|
},
|
|
274
313
|
];
|
|
@@ -312,47 +351,67 @@ class Playwright extends Helper {
|
|
|
312
351
|
return err.message.includes('context');
|
|
313
352
|
},
|
|
314
353
|
});
|
|
315
|
-
if (this.options.restart && !this.options.manualStart)
|
|
316
|
-
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
|
+
if (this.isElectron) {
|
|
358
|
+
this.browserContext = this.browser.context();
|
|
359
|
+
} else if (this.userDataDir) {
|
|
360
|
+
this.browserContext = this.browser;
|
|
361
|
+
} else {
|
|
362
|
+
const contextOptions = {
|
|
363
|
+
ignoreHTTPSErrors: this.options.ignoreHTTPSErrors,
|
|
364
|
+
acceptDownloads: true,
|
|
365
|
+
...this.options.emulate,
|
|
366
|
+
};
|
|
367
|
+
if (this.options.recordVideo) contextOptions.recordVideo = this.options.recordVideo;
|
|
368
|
+
if (this.storageState) contextOptions.storageState = this.storageState;
|
|
369
|
+
this.browserContext = await this.browser.newContext(contextOptions); // Adding the HTTPSError ignore in the context so that we can ignore those errors
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
let mainPage;
|
|
373
|
+
if (this.isElectron) {
|
|
374
|
+
mainPage = await this.browser.firstWindow();
|
|
375
|
+
} else {
|
|
376
|
+
const existingPages = await this.browserContext.pages();
|
|
377
|
+
mainPage = existingPages[0] || await this.browserContext.newPage();
|
|
378
|
+
}
|
|
379
|
+
targetCreatedHandler.call(this, mainPage);
|
|
380
|
+
|
|
381
|
+
await this._setPage(mainPage);
|
|
382
|
+
|
|
383
|
+
if (this.options.trace) await this.browserContext.tracing.start({ screenshots: true, snapshots: true });
|
|
384
|
+
|
|
317
385
|
return this.browser;
|
|
318
386
|
}
|
|
319
387
|
|
|
320
388
|
async _after() {
|
|
321
389
|
if (!this.isRunning) return;
|
|
322
390
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
391
|
+
if (this.isElectron) {
|
|
392
|
+
this.browser.close();
|
|
393
|
+
this.electronSessions.forEach(session => session.close());
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
328
396
|
|
|
329
397
|
if (this.options.restart) {
|
|
330
398
|
this.isRunning = false;
|
|
331
399
|
return this._stopBrowser();
|
|
332
400
|
}
|
|
333
401
|
|
|
334
|
-
//
|
|
335
|
-
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
402
|
+
// close other sessions
|
|
403
|
+
try {
|
|
404
|
+
const contexts = await this.browser.contexts();
|
|
405
|
+
const currentContext = contexts[0];
|
|
406
|
+
if (currentContext && (this.options.keepCookies || this.options.keepBrowserState)) {
|
|
407
|
+
this.storageState = await currentContext.storageState();
|
|
408
|
+
}
|
|
341
409
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
410
|
+
await Promise.all(contexts.map(c => c.close()));
|
|
411
|
+
} catch (e) {
|
|
412
|
+
console.log(e);
|
|
345
413
|
}
|
|
346
|
-
const currentUrl = await this.grabCurrentUrl();
|
|
347
414
|
|
|
348
|
-
if (currentUrl.startsWith('http')) {
|
|
349
|
-
await this.executeScript('localStorage.clear();').catch((err) => {
|
|
350
|
-
if (!(err.message.indexOf("Storage is disabled inside 'data:' URLs.") > -1)) throw err;
|
|
351
|
-
});
|
|
352
|
-
await this.executeScript('sessionStorage.clear();').catch((err) => {
|
|
353
|
-
if (!(err.message.indexOf("Storage is disabled inside 'data:' URLs.") > -1)) throw err;
|
|
354
|
-
});
|
|
355
|
-
}
|
|
356
415
|
// await this.closeOtherTabs();
|
|
357
416
|
return this.browser;
|
|
358
417
|
}
|
|
@@ -371,12 +430,22 @@ class Playwright extends Helper {
|
|
|
371
430
|
this.debugSection('New Context', config ? JSON.stringify(config) : 'opened');
|
|
372
431
|
this.activeSessionName = sessionName;
|
|
373
432
|
|
|
374
|
-
|
|
375
|
-
|
|
433
|
+
let browserContext;
|
|
434
|
+
let page;
|
|
435
|
+
if (this.isElectron) {
|
|
436
|
+
const browser = await playwright._electron.launch(this.playwrightOptions);
|
|
437
|
+
this.electronSessions.push(browser);
|
|
438
|
+
browserContext = browser.context();
|
|
439
|
+
page = await browser.firstWindow();
|
|
440
|
+
} else {
|
|
441
|
+
browserContext = await this.browser.newContext(config);
|
|
442
|
+
page = await browserContext.newPage();
|
|
443
|
+
}
|
|
444
|
+
|
|
376
445
|
targetCreatedHandler.call(this, page);
|
|
377
446
|
this._setPage(page);
|
|
378
447
|
// Create a new page inside context.
|
|
379
|
-
return
|
|
448
|
+
return browserContext;
|
|
380
449
|
},
|
|
381
450
|
stop: async () => {
|
|
382
451
|
// is closed by _after
|
|
@@ -496,6 +565,7 @@ class Playwright extends Helper {
|
|
|
496
565
|
if (!page) return;
|
|
497
566
|
page.setDefaultNavigationTimeout(this.options.getPageTimeout);
|
|
498
567
|
this.context = await this.page;
|
|
568
|
+
this.contextLocator = null;
|
|
499
569
|
if (this.config.browser === 'chrome') {
|
|
500
570
|
await page.bringToFront();
|
|
501
571
|
}
|
|
@@ -512,6 +582,7 @@ class Playwright extends Helper {
|
|
|
512
582
|
if (!page) {
|
|
513
583
|
return;
|
|
514
584
|
}
|
|
585
|
+
page.removeAllListeners('dialog');
|
|
515
586
|
page.on('dialog', async (dialog) => {
|
|
516
587
|
popupStore.popup = dialog;
|
|
517
588
|
const action = popupStore.actionType || this.options.defaultPopupAction;
|
|
@@ -554,7 +625,9 @@ class Playwright extends Helper {
|
|
|
554
625
|
}
|
|
555
626
|
|
|
556
627
|
async _startBrowser() {
|
|
557
|
-
if (this.
|
|
628
|
+
if (this.isElectron) {
|
|
629
|
+
this.browser = await playwright._electron.launch(this.playwrightOptions);
|
|
630
|
+
} else if (this.isRemoteBrowser) {
|
|
558
631
|
try {
|
|
559
632
|
this.browser = await playwright[this.options.browser].connect(this.playwrightOptions);
|
|
560
633
|
} catch (err) {
|
|
@@ -563,6 +636,8 @@ class Playwright extends Helper {
|
|
|
563
636
|
}
|
|
564
637
|
throw err;
|
|
565
638
|
}
|
|
639
|
+
} else if (this.userDataDir) {
|
|
640
|
+
this.browser = await playwright[this.options.browser].launchPersistentContext(this.userDataDir, this.playwrightOptions);
|
|
566
641
|
} else {
|
|
567
642
|
this.browser = await playwright[this.options.browser].launch(this.playwrightOptions);
|
|
568
643
|
}
|
|
@@ -571,19 +646,14 @@ class Playwright extends Helper {
|
|
|
571
646
|
this.browser.on('targetchanged', (target) => {
|
|
572
647
|
this.debugSection('Url', target.url());
|
|
573
648
|
});
|
|
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
|
-
|
|
576
|
-
const existingPages = await this.browserContext.pages();
|
|
577
|
-
|
|
578
|
-
const mainPage = existingPages[0] || await this.browserContext.newPage();
|
|
579
|
-
targetCreatedHandler.call(this, mainPage);
|
|
580
|
-
|
|
581
|
-
await this._setPage(mainPage);
|
|
582
|
-
await this.closeOtherTabs();
|
|
583
649
|
|
|
584
650
|
this.isRunning = true;
|
|
585
651
|
}
|
|
586
652
|
|
|
653
|
+
_getType() {
|
|
654
|
+
return this.browser._type;
|
|
655
|
+
}
|
|
656
|
+
|
|
587
657
|
async _stopBrowser() {
|
|
588
658
|
this.withinLocator = null;
|
|
589
659
|
this._setPage(null);
|
|
@@ -618,6 +688,7 @@ class Playwright extends Helper {
|
|
|
618
688
|
const els = await this._locate(locator);
|
|
619
689
|
assertElementExists(els, locator);
|
|
620
690
|
this.context = els[0];
|
|
691
|
+
this.contextLocator = locator;
|
|
621
692
|
|
|
622
693
|
this.withinLocator = new Locator(locator);
|
|
623
694
|
}
|
|
@@ -625,6 +696,7 @@ class Playwright extends Helper {
|
|
|
625
696
|
async _withinEnd() {
|
|
626
697
|
this.withinLocator = null;
|
|
627
698
|
this.context = await this.page;
|
|
699
|
+
this.contextLocator = null;
|
|
628
700
|
}
|
|
629
701
|
|
|
630
702
|
_extractDataFromPerformanceTiming(timing, ...dataNames) {
|
|
@@ -651,6 +723,9 @@ class Playwright extends Helper {
|
|
|
651
723
|
* @param {string} url url path or global url.
|
|
652
724
|
*/
|
|
653
725
|
async amOnPage(url) {
|
|
726
|
+
if (this.isElectron) {
|
|
727
|
+
throw new Error('Cannot open pages inside an Electron container');
|
|
728
|
+
}
|
|
654
729
|
if (!(/^\w+\:\/\//.test(url))) {
|
|
655
730
|
url = this.options.url + url;
|
|
656
731
|
}
|
|
@@ -984,6 +1059,9 @@ class Playwright extends Helper {
|
|
|
984
1059
|
* @param {number} [num=1]
|
|
985
1060
|
*/
|
|
986
1061
|
async switchToNextTab(num = 1) {
|
|
1062
|
+
if (this.isElectron) {
|
|
1063
|
+
throw new Error('Cannot switch tabs inside an Electron container');
|
|
1064
|
+
}
|
|
987
1065
|
const pages = await this.browserContext.pages();
|
|
988
1066
|
|
|
989
1067
|
const index = pages.indexOf(this.page);
|
|
@@ -1007,6 +1085,9 @@ class Playwright extends Helper {
|
|
|
1007
1085
|
* @param {number} [num=1]
|
|
1008
1086
|
*/
|
|
1009
1087
|
async switchToPreviousTab(num = 1) {
|
|
1088
|
+
if (this.isElectron) {
|
|
1089
|
+
throw new Error('Cannot switch tabs inside an Electron container');
|
|
1090
|
+
}
|
|
1010
1091
|
const pages = await this.browserContext.pages();
|
|
1011
1092
|
const index = pages.indexOf(this.page);
|
|
1012
1093
|
this.withinLocator = null;
|
|
@@ -1028,6 +1109,9 @@ class Playwright extends Helper {
|
|
|
1028
1109
|
* ```
|
|
1029
1110
|
*/
|
|
1030
1111
|
async closeCurrentTab() {
|
|
1112
|
+
if (this.isElectron) {
|
|
1113
|
+
throw new Error('Cannot close current tab inside an Electron container');
|
|
1114
|
+
}
|
|
1031
1115
|
const oldPage = this.page;
|
|
1032
1116
|
await this.switchToPreviousTab();
|
|
1033
1117
|
await oldPage.close();
|
|
@@ -1066,6 +1150,9 @@ class Playwright extends Helper {
|
|
|
1066
1150
|
* ```
|
|
1067
1151
|
*/
|
|
1068
1152
|
async openNewTab(options) {
|
|
1153
|
+
if (this.isElectron) {
|
|
1154
|
+
throw new Error('Cannot open new tabs inside an Electron container');
|
|
1155
|
+
}
|
|
1069
1156
|
await this._setPage(await this.browserContext.newPage(options));
|
|
1070
1157
|
return this._waitForAction();
|
|
1071
1158
|
}
|
|
@@ -1098,7 +1185,7 @@ class Playwright extends Helper {
|
|
|
1098
1185
|
*/
|
|
1099
1186
|
async seeElement(locator) {
|
|
1100
1187
|
let els = await this._locate(locator);
|
|
1101
|
-
els = await Promise.all(els.map(el => el.
|
|
1188
|
+
els = await Promise.all(els.map(el => el.isVisible()));
|
|
1102
1189
|
return empty('visible elements').negate(els.filter(v => v).fill('ELEMENT'));
|
|
1103
1190
|
}
|
|
1104
1191
|
|
|
@@ -1114,7 +1201,7 @@ class Playwright extends Helper {
|
|
|
1114
1201
|
*/
|
|
1115
1202
|
async dontSeeElement(locator) {
|
|
1116
1203
|
let els = await this._locate(locator);
|
|
1117
|
-
els = await Promise.all(els.map(el => el.
|
|
1204
|
+
els = await Promise.all(els.map(el => el.isVisible()));
|
|
1118
1205
|
return empty('visible elements').assert(els.filter(v => v).fill('ELEMENT'));
|
|
1119
1206
|
}
|
|
1120
1207
|
|
|
@@ -1733,7 +1820,7 @@ class Playwright extends Helper {
|
|
|
1733
1820
|
*/
|
|
1734
1821
|
async grabNumberOfVisibleElements(locator) {
|
|
1735
1822
|
let els = await this._locate(locator);
|
|
1736
|
-
els = await Promise.all(els.map(el => el.
|
|
1823
|
+
els = await Promise.all(els.map(el => el.isVisible()));
|
|
1737
1824
|
return els.filter(v => v).length;
|
|
1738
1825
|
}
|
|
1739
1826
|
|
|
@@ -2044,6 +2131,7 @@ class Playwright extends Helper {
|
|
|
2044
2131
|
async clearCookie() {
|
|
2045
2132
|
// Playwright currently doesn't support to delete a certain cookie
|
|
2046
2133
|
// https://github.com/microsoft/playwright/blob/master/docs/api.md#class-browsercontext
|
|
2134
|
+
if (!this.browserContext) return;
|
|
2047
2135
|
return this.browserContext.clearCookies();
|
|
2048
2136
|
}
|
|
2049
2137
|
|
|
@@ -2079,6 +2167,22 @@ class Playwright extends Helper {
|
|
|
2079
2167
|
return context.evaluate.apply(context, [fn, arg]);
|
|
2080
2168
|
}
|
|
2081
2169
|
|
|
2170
|
+
/**
|
|
2171
|
+
* Grab Locator if called within Context
|
|
2172
|
+
*
|
|
2173
|
+
* @param {*} locator
|
|
2174
|
+
*/
|
|
2175
|
+
_contextLocator(locator) {
|
|
2176
|
+
locator = buildLocatorString(new Locator(locator, 'css'));
|
|
2177
|
+
|
|
2178
|
+
if (this.contextLocator) {
|
|
2179
|
+
const contextLocator = buildLocatorString(new Locator(this.contextLocator, 'css'));
|
|
2180
|
+
locator = `${contextLocator} >> ${locator}`;
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2183
|
+
return locator;
|
|
2184
|
+
}
|
|
2185
|
+
|
|
2082
2186
|
/**
|
|
2083
2187
|
* Retrieves a text from an element located by CSS or XPath and returns it to test.
|
|
2084
2188
|
* Resumes test execution, so **should be used inside async with `await`** operator.
|
|
@@ -2094,10 +2198,11 @@ class Playwright extends Helper {
|
|
|
2094
2198
|
*
|
|
2095
2199
|
*/
|
|
2096
2200
|
async grabTextFrom(locator) {
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2201
|
+
locator = this._contextLocator(locator);
|
|
2202
|
+
const text = await this.page.textContent(locator);
|
|
2203
|
+
assertElementExists(text, locator);
|
|
2204
|
+
this.debugSection('Text', text);
|
|
2205
|
+
return text;
|
|
2101
2206
|
}
|
|
2102
2207
|
|
|
2103
2208
|
/**
|
|
@@ -2458,8 +2563,37 @@ class Playwright extends Helper {
|
|
|
2458
2563
|
return this.page.screenshot({ path: outputFile, fullPage: fullPageOption, type: 'png' });
|
|
2459
2564
|
}
|
|
2460
2565
|
|
|
2461
|
-
async _failed() {
|
|
2566
|
+
async _failed(test) {
|
|
2462
2567
|
await this._withinEnd();
|
|
2568
|
+
if (this.options.recordVideo && this.page.video()) {
|
|
2569
|
+
test.artifacts.video = await this.page.video().path();
|
|
2570
|
+
}
|
|
2571
|
+
|
|
2572
|
+
if (this.options.trace) {
|
|
2573
|
+
const path = `${global.output_dir}/trace/${clearString(test.title).slice(0, 255)}.zip`;
|
|
2574
|
+
await this.browserContext.tracing.stop({ path });
|
|
2575
|
+
test.artifacts.trace = path;
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
async _passed(test) {
|
|
2580
|
+
if (this.options.recordVideo && this.page.video()) {
|
|
2581
|
+
if (this.options.keepVideoForPassedTests) {
|
|
2582
|
+
test.artifacts.video = await this.page.video().path();
|
|
2583
|
+
} else {
|
|
2584
|
+
this.page.video().delete().catch(e => {});
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2588
|
+
if (this.options.trace) {
|
|
2589
|
+
if (this.options.keepTraceForPassedTests) {
|
|
2590
|
+
const path = `${global.output_dir}/trace/${clearString(test.title)}.zip`;
|
|
2591
|
+
await this.browserContext.tracing.stop({ path });
|
|
2592
|
+
test.artifacts.trace = path;
|
|
2593
|
+
} else {
|
|
2594
|
+
await this.browserContext.tracing.stop();
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2463
2597
|
}
|
|
2464
2598
|
|
|
2465
2599
|
/**
|
|
@@ -2850,6 +2984,7 @@ class Playwright extends Helper {
|
|
|
2850
2984
|
|
|
2851
2985
|
if (locator >= 0 && locator < childFrames.length) {
|
|
2852
2986
|
this.context = childFrames[locator];
|
|
2987
|
+
this.contextLocator = locator;
|
|
2853
2988
|
} else {
|
|
2854
2989
|
throw new Error('Element #invalidIframeSelector was not found by text|CSS|XPath');
|
|
2855
2990
|
}
|
|
@@ -2857,6 +2992,7 @@ class Playwright extends Helper {
|
|
|
2857
2992
|
}
|
|
2858
2993
|
if (!locator) {
|
|
2859
2994
|
this.context = this.page;
|
|
2995
|
+
this.contextLocator = null;
|
|
2860
2996
|
return;
|
|
2861
2997
|
}
|
|
2862
2998
|
|
|
@@ -2867,8 +3003,10 @@ class Playwright extends Helper {
|
|
|
2867
3003
|
|
|
2868
3004
|
if (contentFrame) {
|
|
2869
3005
|
this.context = contentFrame;
|
|
3006
|
+
this.contextLocator = null;
|
|
2870
3007
|
} else {
|
|
2871
3008
|
this.context = els[0];
|
|
3009
|
+
this.contextLocator = locator;
|
|
2872
3010
|
}
|
|
2873
3011
|
}
|
|
2874
3012
|
|
|
@@ -3038,6 +3176,39 @@ class Playwright extends Helper {
|
|
|
3038
3176
|
if (prop) return rect[prop];
|
|
3039
3177
|
return rect;
|
|
3040
3178
|
}
|
|
3179
|
+
|
|
3180
|
+
/**
|
|
3181
|
+
* Mocks network request using [`browserContext.route`](https://playwright.dev/docs/api/class-browsercontext#browser-context-route) of Playwright
|
|
3182
|
+
*
|
|
3183
|
+
* ```js
|
|
3184
|
+
* I.mockRoute(/(\.png$)|(\.jpg$)/, route => route.abort());
|
|
3185
|
+
* ```
|
|
3186
|
+
* This method allows intercepting and mocking requests & responses. [Learn more about it](https://playwright.dev/docs/network#handle-requests)
|
|
3187
|
+
*
|
|
3188
|
+
* @param {string} [url] URL, regex or pattern for to match URL
|
|
3189
|
+
* @param {function} [handler] a function to process request
|
|
3190
|
+
*
|
|
3191
|
+
*/
|
|
3192
|
+
async mockRoute(url, handler) {
|
|
3193
|
+
return this.browserContext.route(...arguments);
|
|
3194
|
+
}
|
|
3195
|
+
|
|
3196
|
+
/**
|
|
3197
|
+
* Stops network mocking created by `mockRoute`.
|
|
3198
|
+
*
|
|
3199
|
+
* ```js
|
|
3200
|
+
* I.stopMockingRoute(/(\.png$)|(\.jpg$)/);
|
|
3201
|
+
* I.stopMockingRoute(/(\.png$)|(\.jpg$)/, previouslySetHandler);
|
|
3202
|
+
* ```
|
|
3203
|
+
* If no handler is passed, all mock requests for the rote are disabled.
|
|
3204
|
+
*
|
|
3205
|
+
* @param {string} [url] URL, regex or pattern for to match URL
|
|
3206
|
+
* @param {function} [handler] a function to process request
|
|
3207
|
+
*
|
|
3208
|
+
*/
|
|
3209
|
+
async stopMockingRoute(url, handler) {
|
|
3210
|
+
return this.browserContext.unroute(...arguments);
|
|
3211
|
+
}
|
|
3041
3212
|
}
|
|
3042
3213
|
|
|
3043
3214
|
module.exports = Playwright;
|
|
@@ -3053,10 +3224,24 @@ function buildLocatorString(locator) {
|
|
|
3053
3224
|
}
|
|
3054
3225
|
|
|
3055
3226
|
async function findElements(matcher, locator) {
|
|
3227
|
+
if (locator.react) return findReact(matcher, locator);
|
|
3056
3228
|
locator = new Locator(locator, 'css');
|
|
3057
3229
|
return matcher.$$(buildLocatorString(locator));
|
|
3058
3230
|
}
|
|
3059
3231
|
|
|
3232
|
+
async function getVisibleElements(elements) {
|
|
3233
|
+
const visibleElements = [];
|
|
3234
|
+
for (const element of elements) {
|
|
3235
|
+
if (await element.isVisible()) {
|
|
3236
|
+
visibleElements.push(element);
|
|
3237
|
+
}
|
|
3238
|
+
}
|
|
3239
|
+
if (visibleElements.length === 0) {
|
|
3240
|
+
return elements;
|
|
3241
|
+
}
|
|
3242
|
+
return visibleElements;
|
|
3243
|
+
}
|
|
3244
|
+
|
|
3060
3245
|
async function proceedClick(locator, context = null, options = {}) {
|
|
3061
3246
|
let matcher = await this._getContext();
|
|
3062
3247
|
if (context) {
|
|
@@ -3076,7 +3261,8 @@ async function proceedClick(locator, context = null, options = {}) {
|
|
|
3076
3261
|
if (options.force) {
|
|
3077
3262
|
await els[0].dispatchEvent('click');
|
|
3078
3263
|
} else {
|
|
3079
|
-
await els[0]
|
|
3264
|
+
const element = els.length > 1 ? (await getVisibleElements(els))[0] : els[0];
|
|
3265
|
+
await element.click(options);
|
|
3080
3266
|
}
|
|
3081
3267
|
const promises = [];
|
|
3082
3268
|
if (options.waitForNavigation) {
|
|
@@ -3087,6 +3273,8 @@ async function proceedClick(locator, context = null, options = {}) {
|
|
|
3087
3273
|
}
|
|
3088
3274
|
|
|
3089
3275
|
async function findClickable(matcher, locator) {
|
|
3276
|
+
if (locator.react) return findReact(matcher, locator);
|
|
3277
|
+
|
|
3090
3278
|
locator = new Locator(locator);
|
|
3091
3279
|
if (!locator.isFuzzy()) return findElements.call(this, matcher, locator);
|
|
3092
3280
|
|
|
@@ -3193,6 +3381,15 @@ async function findFields(locator) {
|
|
|
3193
3381
|
}
|
|
3194
3382
|
|
|
3195
3383
|
async function proceedDragAndDrop(sourceLocator, destinationLocator) {
|
|
3384
|
+
// modern drag and drop in Playwright
|
|
3385
|
+
if (this.page.dragAndDrop) {
|
|
3386
|
+
const source = new Locator(sourceLocator);
|
|
3387
|
+
const dest = new Locator(destinationLocator);
|
|
3388
|
+
if (source.isBasic() && dest.isBasic()) {
|
|
3389
|
+
return this.page.dragAndDrop(source.simplify(), dest.simplify());
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3392
|
+
|
|
3196
3393
|
const src = await this._locate(sourceLocator);
|
|
3197
3394
|
assertElementExists(src, sourceLocator, 'Source Element');
|
|
3198
3395
|
|
|
@@ -3334,11 +3531,13 @@ async function targetCreatedHandler(page) {
|
|
|
3334
3531
|
// we are inside iframe?
|
|
3335
3532
|
const frameEl = await this.context.frameElement();
|
|
3336
3533
|
this.context = await frameEl.contentFrame();
|
|
3534
|
+
this.contextLocator = null;
|
|
3337
3535
|
return;
|
|
3338
3536
|
}
|
|
3339
3537
|
// if context element was in iframe - keep it
|
|
3340
3538
|
// if (await this.context.ownerFrame()) return;
|
|
3341
3539
|
this.context = page;
|
|
3540
|
+
this.contextLocator = null;
|
|
3342
3541
|
});
|
|
3343
3542
|
});
|
|
3344
3543
|
page.on('console', (msg) => {
|
|
@@ -3349,12 +3548,21 @@ async function targetCreatedHandler(page) {
|
|
|
3349
3548
|
if (this.options.userAgent) {
|
|
3350
3549
|
await page.setUserAgent(this.options.userAgent);
|
|
3351
3550
|
}
|
|
3352
|
-
if (this.options.windowSize && this.options.windowSize.indexOf('x') > 0) {
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3551
|
+
if (this.options.windowSize && this.options.windowSize.indexOf('x') > 0 && this._getType() === 'Browser') {
|
|
3552
|
+
await page.setViewportSize(parseWindowSize(this.options.windowSize));
|
|
3553
|
+
}
|
|
3554
|
+
}
|
|
3555
|
+
|
|
3556
|
+
function parseWindowSize(windowSize) {
|
|
3557
|
+
if (!windowSize) return { width: 800, height: 600 };
|
|
3558
|
+
const dimensions = windowSize.split('x');
|
|
3559
|
+
if (dimensions.length < 2 || windowSize === 'maximize') {
|
|
3560
|
+
console.log('Invalid window size, setting window to default values');
|
|
3561
|
+
return { width: 800, height: 600 }; // invalid size
|
|
3357
3562
|
}
|
|
3563
|
+
const width = parseInt(dimensions[0], 10);
|
|
3564
|
+
const height = parseInt(dimensions[1], 10);
|
|
3565
|
+
return { width, height };
|
|
3358
3566
|
}
|
|
3359
3567
|
|
|
3360
3568
|
// 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) {
|