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/lib/helper/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;
|
|
@@ -38,6 +40,7 @@ const consoleLogStore = new Console();
|
|
|
38
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
|
*
|
|
@@ -63,6 +66,8 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
|
|
|
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.
|
|
@@ -79,6 +84,22 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
|
|
|
79
84
|
* * `chromium`: (optional) pass additional chromium options
|
|
80
85
|
* * `electron`: (optional) pass additional electron options
|
|
81
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
|
+
*
|
|
82
103
|
* #### Example #1: Wait for 0 network connections.
|
|
83
104
|
*
|
|
84
105
|
* ```js
|
|
@@ -130,7 +151,7 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
|
|
|
130
151
|
* Playwright: {
|
|
131
152
|
* url: "http://localhost",
|
|
132
153
|
* chromium: {
|
|
133
|
-
* browserWSEndpoint:
|
|
154
|
+
* browserWSEndpoint: 'ws://localhost:9222/devtools/browser/c5aa6160-b5bc-4d53-bb49-6ecb36cd2e0a'
|
|
134
155
|
* }
|
|
135
156
|
* }
|
|
136
157
|
* }
|
|
@@ -210,6 +231,7 @@ class Playwright extends Helper {
|
|
|
210
231
|
this.activeSessionName = '';
|
|
211
232
|
this.isElectron = false;
|
|
212
233
|
this.electronSessions = [];
|
|
234
|
+
this.storageState = null;
|
|
213
235
|
|
|
214
236
|
// override defaults with config
|
|
215
237
|
this._setConfig(config);
|
|
@@ -219,7 +241,6 @@ class Playwright extends Helper {
|
|
|
219
241
|
const defaults = {
|
|
220
242
|
// options to emulate context
|
|
221
243
|
emulate: {},
|
|
222
|
-
|
|
223
244
|
browser: 'chromium',
|
|
224
245
|
waitForAction: 100,
|
|
225
246
|
waitForTimeout: 1000,
|
|
@@ -248,10 +269,16 @@ class Playwright extends Helper {
|
|
|
248
269
|
}
|
|
249
270
|
|
|
250
271
|
_getOptionsForBrowser(config) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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 {};
|
|
255
282
|
}
|
|
256
283
|
|
|
257
284
|
_setConfig(config) {
|
|
@@ -260,6 +287,12 @@ class Playwright extends Helper {
|
|
|
260
287
|
headless: !this.options.show,
|
|
261
288
|
...this._getOptionsForBrowser(config),
|
|
262
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
|
+
}
|
|
263
296
|
this.isRemoteBrowser = !!this.playwrightOptions.browserWSEndpoint;
|
|
264
297
|
this.isElectron = this.options.browser === 'electron';
|
|
265
298
|
this.userDataDir = this.playwrightOptions.userDataDir;
|
|
@@ -318,8 +351,37 @@ class Playwright extends Helper {
|
|
|
318
351
|
return err.message.includes('context');
|
|
319
352
|
},
|
|
320
353
|
});
|
|
321
|
-
if (this.options.restart && !this.options.manualStart)
|
|
322
|
-
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
|
+
|
|
323
385
|
return this.browser;
|
|
324
386
|
}
|
|
325
387
|
|
|
@@ -332,43 +394,24 @@ class Playwright extends Helper {
|
|
|
332
394
|
return;
|
|
333
395
|
}
|
|
334
396
|
|
|
397
|
+
if (this.options.restart) {
|
|
398
|
+
this.isRunning = false;
|
|
399
|
+
return this._stopBrowser();
|
|
400
|
+
}
|
|
401
|
+
|
|
335
402
|
// close other sessions
|
|
336
403
|
try {
|
|
337
404
|
const contexts = await this.browser.contexts();
|
|
338
|
-
contexts
|
|
405
|
+
const currentContext = contexts[0];
|
|
406
|
+
if (currentContext && (this.options.keepCookies || this.options.keepBrowserState)) {
|
|
407
|
+
this.storageState = await currentContext.storageState();
|
|
408
|
+
}
|
|
339
409
|
|
|
340
410
|
await Promise.all(contexts.map(c => c.close()));
|
|
341
411
|
} catch (e) {
|
|
342
412
|
console.log(e);
|
|
343
413
|
}
|
|
344
414
|
|
|
345
|
-
if (this.options.restart) {
|
|
346
|
-
this.isRunning = false;
|
|
347
|
-
return this._stopBrowser();
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// ensure current page is in default context
|
|
351
|
-
if (this.page) {
|
|
352
|
-
const existingPages = await this.browserContext.pages();
|
|
353
|
-
await this._setPage(existingPages[0]);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
if (this.options.keepBrowserState) return;
|
|
357
|
-
|
|
358
|
-
if (!this.options.keepCookies) {
|
|
359
|
-
this.debugSection('Session', 'cleaning cookies and localStorage');
|
|
360
|
-
await this.clearCookie();
|
|
361
|
-
}
|
|
362
|
-
const currentUrl = await this.grabCurrentUrl();
|
|
363
|
-
|
|
364
|
-
if (currentUrl.startsWith('http')) {
|
|
365
|
-
await this.executeScript('localStorage.clear();').catch((err) => {
|
|
366
|
-
if (!(err.message.indexOf("Storage is disabled inside 'data:' URLs.") > -1)) throw err;
|
|
367
|
-
});
|
|
368
|
-
await this.executeScript('sessionStorage.clear();').catch((err) => {
|
|
369
|
-
if (!(err.message.indexOf("Storage is disabled inside 'data:' URLs.") > -1)) throw err;
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
415
|
// await this.closeOtherTabs();
|
|
373
416
|
return this.browser;
|
|
374
417
|
}
|
|
@@ -515,6 +558,7 @@ class Playwright extends Helper {
|
|
|
515
558
|
if (!page) return;
|
|
516
559
|
page.setDefaultNavigationTimeout(this.options.getPageTimeout);
|
|
517
560
|
this.context = await this.page;
|
|
561
|
+
this.contextLocator = null;
|
|
518
562
|
if (this.config.browser === 'chrome') {
|
|
519
563
|
await page.bringToFront();
|
|
520
564
|
}
|
|
@@ -531,6 +575,7 @@ class Playwright extends Helper {
|
|
|
531
575
|
if (!page) {
|
|
532
576
|
return;
|
|
533
577
|
}
|
|
578
|
+
page.removeAllListeners('dialog');
|
|
534
579
|
page.on('dialog', async (dialog) => {
|
|
535
580
|
popupStore.popup = dialog;
|
|
536
581
|
const action = popupStore.actionType || this.options.defaultPopupAction;
|
|
@@ -577,7 +622,7 @@ class Playwright extends Helper {
|
|
|
577
622
|
this.browser = await playwright._electron.launch(this.playwrightOptions);
|
|
578
623
|
} else if (this.isRemoteBrowser) {
|
|
579
624
|
try {
|
|
580
|
-
this.browser = await playwright[this.options.browser].connect(this.playwrightOptions
|
|
625
|
+
this.browser = await playwright[this.options.browser].connect(this.playwrightOptions);
|
|
581
626
|
} catch (err) {
|
|
582
627
|
if (err.toString().indexOf('ECONNREFUSED')) {
|
|
583
628
|
throw new RemoteBrowserConnectionRefused(err);
|
|
@@ -595,26 +640,6 @@ class Playwright extends Helper {
|
|
|
595
640
|
this.debugSection('Url', target.url());
|
|
596
641
|
});
|
|
597
642
|
|
|
598
|
-
if (this.isElectron) {
|
|
599
|
-
this.browserContext = this.browser.context();
|
|
600
|
-
} else if (this.userDataDir) {
|
|
601
|
-
this.browserContext = this.browser;
|
|
602
|
-
} else {
|
|
603
|
-
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
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
let mainPage;
|
|
607
|
-
if (this.isElectron) {
|
|
608
|
-
mainPage = await this.browser.firstWindow();
|
|
609
|
-
} else {
|
|
610
|
-
const existingPages = await this.browserContext.pages();
|
|
611
|
-
mainPage = existingPages[0] || await this.browserContext.newPage();
|
|
612
|
-
}
|
|
613
|
-
targetCreatedHandler.call(this, mainPage);
|
|
614
|
-
|
|
615
|
-
await this._setPage(mainPage);
|
|
616
|
-
await this.closeOtherTabs();
|
|
617
|
-
|
|
618
643
|
this.isRunning = true;
|
|
619
644
|
}
|
|
620
645
|
|
|
@@ -656,6 +681,7 @@ class Playwright extends Helper {
|
|
|
656
681
|
const els = await this._locate(locator);
|
|
657
682
|
assertElementExists(els, locator);
|
|
658
683
|
this.context = els[0];
|
|
684
|
+
this.contextLocator = locator;
|
|
659
685
|
|
|
660
686
|
this.withinLocator = new Locator(locator);
|
|
661
687
|
}
|
|
@@ -663,6 +689,7 @@ class Playwright extends Helper {
|
|
|
663
689
|
async _withinEnd() {
|
|
664
690
|
this.withinLocator = null;
|
|
665
691
|
this.context = await this.page;
|
|
692
|
+
this.contextLocator = null;
|
|
666
693
|
}
|
|
667
694
|
|
|
668
695
|
_extractDataFromPerformanceTiming(timing, ...dataNames) {
|
|
@@ -1046,7 +1073,7 @@ class Playwright extends Helper {
|
|
|
1046
1073
|
*/
|
|
1047
1074
|
async seeElement(locator) {
|
|
1048
1075
|
let els = await this._locate(locator);
|
|
1049
|
-
els = await Promise.all(els.map(el => el.
|
|
1076
|
+
els = await Promise.all(els.map(el => el.isVisible()));
|
|
1050
1077
|
return empty('visible elements').negate(els.filter(v => v).fill('ELEMENT'));
|
|
1051
1078
|
}
|
|
1052
1079
|
|
|
@@ -1056,7 +1083,7 @@ class Playwright extends Helper {
|
|
|
1056
1083
|
*/
|
|
1057
1084
|
async dontSeeElement(locator) {
|
|
1058
1085
|
let els = await this._locate(locator);
|
|
1059
|
-
els = await Promise.all(els.map(el => el.
|
|
1086
|
+
els = await Promise.all(els.map(el => el.isVisible()));
|
|
1060
1087
|
return empty('visible elements').assert(els.filter(v => v).fill('ELEMENT'));
|
|
1061
1088
|
}
|
|
1062
1089
|
|
|
@@ -1360,7 +1387,7 @@ class Playwright extends Helper {
|
|
|
1360
1387
|
*/
|
|
1361
1388
|
async grabNumberOfVisibleElements(locator) {
|
|
1362
1389
|
let els = await this._locate(locator);
|
|
1363
|
-
els = await Promise.all(els.map(el => el.
|
|
1390
|
+
els = await Promise.all(els.map(el => el.isVisible()));
|
|
1364
1391
|
return els.filter(v => v).length;
|
|
1365
1392
|
}
|
|
1366
1393
|
|
|
@@ -1527,6 +1554,7 @@ class Playwright extends Helper {
|
|
|
1527
1554
|
async clearCookie() {
|
|
1528
1555
|
// Playwright currently doesn't support to delete a certain cookie
|
|
1529
1556
|
// https://github.com/microsoft/playwright/blob/master/docs/api.md#class-browsercontext
|
|
1557
|
+
if (!this.browserContext) return;
|
|
1530
1558
|
return this.browserContext.clearCookies();
|
|
1531
1559
|
}
|
|
1532
1560
|
|
|
@@ -1562,15 +1590,32 @@ class Playwright extends Helper {
|
|
|
1562
1590
|
return context.evaluate.apply(context, [fn, arg]);
|
|
1563
1591
|
}
|
|
1564
1592
|
|
|
1593
|
+
/**
|
|
1594
|
+
* Grab Locator if called within Context
|
|
1595
|
+
*
|
|
1596
|
+
* @param {*} locator
|
|
1597
|
+
*/
|
|
1598
|
+
_contextLocator(locator) {
|
|
1599
|
+
locator = buildLocatorString(new Locator(locator, 'css'));
|
|
1600
|
+
|
|
1601
|
+
if (this.contextLocator) {
|
|
1602
|
+
const contextLocator = buildLocatorString(new Locator(this.contextLocator, 'css'));
|
|
1603
|
+
locator = `${contextLocator} >> ${locator}`;
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
return locator;
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1565
1609
|
/**
|
|
1566
1610
|
* {{> grabTextFrom }}
|
|
1567
1611
|
*
|
|
1568
1612
|
*/
|
|
1569
1613
|
async grabTextFrom(locator) {
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1614
|
+
locator = this._contextLocator(locator);
|
|
1615
|
+
const text = await this.page.textContent(locator);
|
|
1616
|
+
assertElementExists(text, locator);
|
|
1617
|
+
this.debugSection('Text', text);
|
|
1618
|
+
return text;
|
|
1574
1619
|
}
|
|
1575
1620
|
|
|
1576
1621
|
/**
|
|
@@ -1804,8 +1849,37 @@ class Playwright extends Helper {
|
|
|
1804
1849
|
return this.page.screenshot({ path: outputFile, fullPage: fullPageOption, type: 'png' });
|
|
1805
1850
|
}
|
|
1806
1851
|
|
|
1807
|
-
async _failed() {
|
|
1852
|
+
async _failed(test) {
|
|
1808
1853
|
await this._withinEnd();
|
|
1854
|
+
if (this.options.recordVideo && this.page.video()) {
|
|
1855
|
+
test.artifacts.video = await this.page.video().path();
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
if (this.options.trace) {
|
|
1859
|
+
const path = `${global.output_dir}/trace/${clearString(test.title).slice(0, 255)}.zip`;
|
|
1860
|
+
await this.browserContext.tracing.stop({ path });
|
|
1861
|
+
test.artifacts.trace = path;
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
async _passed(test) {
|
|
1866
|
+
if (this.options.recordVideo && this.page.video()) {
|
|
1867
|
+
if (this.options.keepVideoForPassedTests) {
|
|
1868
|
+
test.artifacts.video = await this.page.video().path();
|
|
1869
|
+
} else {
|
|
1870
|
+
this.page.video().delete().catch(e => {});
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
if (this.options.trace) {
|
|
1875
|
+
if (this.options.keepTraceForPassedTests) {
|
|
1876
|
+
const path = `${global.output_dir}/trace/${clearString(test.title)}.zip`;
|
|
1877
|
+
await this.browserContext.tracing.stop({ path });
|
|
1878
|
+
test.artifacts.trace = path;
|
|
1879
|
+
} else {
|
|
1880
|
+
await this.browserContext.tracing.stop();
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1809
1883
|
}
|
|
1810
1884
|
|
|
1811
1885
|
/**
|
|
@@ -2093,6 +2167,7 @@ class Playwright extends Helper {
|
|
|
2093
2167
|
|
|
2094
2168
|
if (locator >= 0 && locator < childFrames.length) {
|
|
2095
2169
|
this.context = childFrames[locator];
|
|
2170
|
+
this.contextLocator = locator;
|
|
2096
2171
|
} else {
|
|
2097
2172
|
throw new Error('Element #invalidIframeSelector was not found by text|CSS|XPath');
|
|
2098
2173
|
}
|
|
@@ -2100,6 +2175,7 @@ class Playwright extends Helper {
|
|
|
2100
2175
|
}
|
|
2101
2176
|
if (!locator) {
|
|
2102
2177
|
this.context = this.page;
|
|
2178
|
+
this.contextLocator = null;
|
|
2103
2179
|
return;
|
|
2104
2180
|
}
|
|
2105
2181
|
|
|
@@ -2110,8 +2186,10 @@ class Playwright extends Helper {
|
|
|
2110
2186
|
|
|
2111
2187
|
if (contentFrame) {
|
|
2112
2188
|
this.context = contentFrame;
|
|
2189
|
+
this.contextLocator = null;
|
|
2113
2190
|
} else {
|
|
2114
2191
|
this.context = els[0];
|
|
2192
|
+
this.contextLocator = locator;
|
|
2115
2193
|
}
|
|
2116
2194
|
}
|
|
2117
2195
|
|
|
@@ -2209,6 +2287,39 @@ class Playwright extends Helper {
|
|
|
2209
2287
|
if (prop) return rect[prop];
|
|
2210
2288
|
return rect;
|
|
2211
2289
|
}
|
|
2290
|
+
|
|
2291
|
+
/**
|
|
2292
|
+
* Mocks network request using [`browserContext.route`](https://playwright.dev/docs/api/class-browsercontext#browser-context-route) of Playwright
|
|
2293
|
+
*
|
|
2294
|
+
* ```js
|
|
2295
|
+
* I.mockRoute(/(\.png$)|(\.jpg$)/, route => route.abort());
|
|
2296
|
+
* ```
|
|
2297
|
+
* This method allows intercepting and mocking requests & responses. [Learn more about it](https://playwright.dev/docs/network#handle-requests)
|
|
2298
|
+
*
|
|
2299
|
+
* @param {string} [url] URL, regex or pattern for to match URL
|
|
2300
|
+
* @param {function} [handler] a function to process request
|
|
2301
|
+
*
|
|
2302
|
+
*/
|
|
2303
|
+
async mockRoute(url, handler) {
|
|
2304
|
+
return this.browserContext.route(...arguments);
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2307
|
+
/**
|
|
2308
|
+
* Stops network mocking created by `mockRoute`.
|
|
2309
|
+
*
|
|
2310
|
+
* ```js
|
|
2311
|
+
* I.stopMockingRoute(/(\.png$)|(\.jpg$)/);
|
|
2312
|
+
* I.stopMockingRoute(/(\.png$)|(\.jpg$)/, previouslySetHandler);
|
|
2313
|
+
* ```
|
|
2314
|
+
* If no handler is passed, all mock requests for the rote are disabled.
|
|
2315
|
+
*
|
|
2316
|
+
* @param {string} [url] URL, regex or pattern for to match URL
|
|
2317
|
+
* @param {function} [handler] a function to process request
|
|
2318
|
+
*
|
|
2319
|
+
*/
|
|
2320
|
+
async stopMockingRoute(url, handler) {
|
|
2321
|
+
return this.browserContext.unroute(...arguments);
|
|
2322
|
+
}
|
|
2212
2323
|
}
|
|
2213
2324
|
|
|
2214
2325
|
module.exports = Playwright;
|
|
@@ -2224,10 +2335,24 @@ function buildLocatorString(locator) {
|
|
|
2224
2335
|
}
|
|
2225
2336
|
|
|
2226
2337
|
async function findElements(matcher, locator) {
|
|
2338
|
+
if (locator.react) return findReact(matcher, locator);
|
|
2227
2339
|
locator = new Locator(locator, 'css');
|
|
2228
2340
|
return matcher.$$(buildLocatorString(locator));
|
|
2229
2341
|
}
|
|
2230
2342
|
|
|
2343
|
+
async function getVisibleElements(elements) {
|
|
2344
|
+
const visibleElements = [];
|
|
2345
|
+
for (const element of elements) {
|
|
2346
|
+
if (await element.isVisible()) {
|
|
2347
|
+
visibleElements.push(element);
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
if (visibleElements.length === 0) {
|
|
2351
|
+
return elements;
|
|
2352
|
+
}
|
|
2353
|
+
return visibleElements;
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2231
2356
|
async function proceedClick(locator, context = null, options = {}) {
|
|
2232
2357
|
let matcher = await this._getContext();
|
|
2233
2358
|
if (context) {
|
|
@@ -2247,7 +2372,8 @@ async function proceedClick(locator, context = null, options = {}) {
|
|
|
2247
2372
|
if (options.force) {
|
|
2248
2373
|
await els[0].dispatchEvent('click');
|
|
2249
2374
|
} else {
|
|
2250
|
-
await els[0]
|
|
2375
|
+
const element = els.length > 1 ? (await getVisibleElements(els))[0] : els[0];
|
|
2376
|
+
await element.click(options);
|
|
2251
2377
|
}
|
|
2252
2378
|
const promises = [];
|
|
2253
2379
|
if (options.waitForNavigation) {
|
|
@@ -2258,6 +2384,8 @@ async function proceedClick(locator, context = null, options = {}) {
|
|
|
2258
2384
|
}
|
|
2259
2385
|
|
|
2260
2386
|
async function findClickable(matcher, locator) {
|
|
2387
|
+
if (locator.react) return findReact(matcher, locator);
|
|
2388
|
+
|
|
2261
2389
|
locator = new Locator(locator);
|
|
2262
2390
|
if (!locator.isFuzzy()) return findElements.call(this, matcher, locator);
|
|
2263
2391
|
|
|
@@ -2364,6 +2492,15 @@ async function findFields(locator) {
|
|
|
2364
2492
|
}
|
|
2365
2493
|
|
|
2366
2494
|
async function proceedDragAndDrop(sourceLocator, destinationLocator) {
|
|
2495
|
+
// modern drag and drop in Playwright
|
|
2496
|
+
if (this.page.dragAndDrop) {
|
|
2497
|
+
const source = new Locator(sourceLocator);
|
|
2498
|
+
const dest = new Locator(destinationLocator);
|
|
2499
|
+
if (source.isBasic() && dest.isBasic()) {
|
|
2500
|
+
return this.page.dragAndDrop(source.simplify(), dest.simplify());
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
|
|
2367
2504
|
const src = await this._locate(sourceLocator);
|
|
2368
2505
|
assertElementExists(src, sourceLocator, 'Source Element');
|
|
2369
2506
|
|
|
@@ -2505,11 +2642,13 @@ async function targetCreatedHandler(page) {
|
|
|
2505
2642
|
// we are inside iframe?
|
|
2506
2643
|
const frameEl = await this.context.frameElement();
|
|
2507
2644
|
this.context = await frameEl.contentFrame();
|
|
2645
|
+
this.contextLocator = null;
|
|
2508
2646
|
return;
|
|
2509
2647
|
}
|
|
2510
2648
|
// if context element was in iframe - keep it
|
|
2511
2649
|
// if (await this.context.ownerFrame()) return;
|
|
2512
2650
|
this.context = page;
|
|
2651
|
+
this.contextLocator = null;
|
|
2513
2652
|
});
|
|
2514
2653
|
});
|
|
2515
2654
|
page.on('console', (msg) => {
|
|
@@ -2521,11 +2660,20 @@ async function targetCreatedHandler(page) {
|
|
|
2521
2660
|
await page.setUserAgent(this.options.userAgent);
|
|
2522
2661
|
}
|
|
2523
2662
|
if (this.options.windowSize && this.options.windowSize.indexOf('x') > 0 && this._getType() === 'Browser') {
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2663
|
+
await page.setViewportSize(parseWindowSize(this.options.windowSize));
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2667
|
+
function parseWindowSize(windowSize) {
|
|
2668
|
+
if (!windowSize) return { width: 800, height: 600 };
|
|
2669
|
+
const dimensions = windowSize.split('x');
|
|
2670
|
+
if (dimensions.length < 2 || windowSize === 'maximize') {
|
|
2671
|
+
console.log('Invalid window size, setting window to default values');
|
|
2672
|
+
return { width: 800, height: 600 }; // invalid size
|
|
2528
2673
|
}
|
|
2674
|
+
const width = parseInt(dimensions[0], 10);
|
|
2675
|
+
const height = parseInt(dimensions[1], 10);
|
|
2676
|
+
return { width, height };
|
|
2529
2677
|
}
|
|
2530
2678
|
|
|
2531
2679
|
// List of key values to key definitions
|
package/lib/helper/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) {
|