codeceptjs 3.2.0 → 3.3.0-beta.1
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 +78 -2
- package/docs/advanced.md +3 -3
- package/docs/api.md +227 -188
- package/docs/basics.md +26 -1
- package/docs/bdd.md +2 -2
- package/docs/build/ApiDataFactory.js +13 -6
- package/docs/build/Appium.js +98 -36
- package/docs/build/FileSystem.js +11 -1
- package/docs/build/GraphQL.js +11 -0
- package/docs/build/JSONResponse.js +297 -0
- package/docs/build/Nightmare.js +48 -48
- package/docs/build/Playwright.js +277 -151
- package/docs/build/Puppeteer.js +77 -68
- package/docs/build/REST.js +36 -0
- package/docs/build/TestCafe.js +44 -44
- package/docs/build/WebDriver.js +70 -70
- package/docs/changelog.md +28 -2
- package/docs/configuration.md +8 -8
- package/docs/custom-helpers.md +1 -1
- package/docs/data.md +9 -9
- package/docs/helpers/ApiDataFactory.md +7 -0
- package/docs/helpers/Appium.md +240 -198
- package/docs/helpers/FileSystem.md +11 -1
- package/docs/helpers/JSONResponse.md +230 -0
- package/docs/helpers/Playwright.md +283 -216
- package/docs/helpers/Puppeteer.md +9 -1
- package/docs/helpers/REST.md +30 -9
- package/docs/installation.md +3 -1
- package/docs/internal-api.md +265 -0
- package/docs/mobile.md +11 -11
- package/docs/nightmare.md +3 -3
- package/docs/pageobjects.md +2 -0
- package/docs/playwright.md +73 -18
- package/docs/plugins.md +140 -40
- package/docs/puppeteer.md +28 -12
- package/docs/quickstart.md +2 -3
- package/docs/reports.md +44 -3
- package/docs/testcafe.md +1 -1
- package/docs/translation.md +2 -2
- package/docs/videos.md +2 -2
- package/docs/visual.md +2 -2
- package/docs/vue.md +1 -1
- package/docs/webdriver.md +92 -4
- package/lib/actor.js +2 -2
- package/lib/cli.js +25 -20
- package/lib/command/init.js +5 -15
- package/lib/command/workers/runTests.js +25 -7
- package/lib/config.js +17 -13
- package/lib/helper/ApiDataFactory.js +13 -6
- package/lib/helper/Appium.js +65 -3
- package/lib/helper/FileSystem.js +11 -1
- package/lib/helper/GraphQL.js +11 -0
- package/lib/helper/JSONResponse.js +297 -0
- package/lib/helper/Playwright.js +215 -89
- package/lib/helper/Puppeteer.js +13 -4
- package/lib/helper/REST.js +36 -0
- package/lib/helper/WebDriver.js +1 -1
- package/lib/helper/extras/Console.js +8 -0
- package/lib/helper/extras/PlaywrightRestartOpts.js +35 -0
- package/lib/interfaces/bdd.js +3 -1
- package/lib/listener/timeout.js +4 -3
- package/lib/plugin/allure.js +12 -0
- package/lib/plugin/autoLogin.js +1 -1
- package/lib/plugin/eachElement.js +127 -0
- package/lib/plugin/retryFailedStep.js +4 -3
- package/lib/plugin/stepTimeout.js +5 -4
- package/lib/plugin/tryTo.js +6 -0
- package/lib/recorder.js +2 -1
- package/lib/step.js +57 -2
- package/lib/utils.js +20 -0
- package/package.json +6 -4
- package/translations/pt-BR.js +8 -0
- package/typings/index.d.ts +4 -0
- package/typings/types.d.ts +345 -110
package/lib/helper/Playwright.js
CHANGED
|
@@ -14,12 +14,12 @@ const {
|
|
|
14
14
|
ucfirst,
|
|
15
15
|
fileExists,
|
|
16
16
|
chunkArray,
|
|
17
|
-
toCamelCase,
|
|
18
17
|
convertCssPropertiesToCamelCase,
|
|
19
18
|
screenshotOutputFolder,
|
|
20
19
|
getNormalizedKeyAttributeValue,
|
|
21
20
|
isModifierKey,
|
|
22
21
|
clearString,
|
|
22
|
+
requireWithFallback,
|
|
23
23
|
} = require('../utils');
|
|
24
24
|
const {
|
|
25
25
|
isColorProperty,
|
|
@@ -39,6 +39,9 @@ const popupStore = new Popup();
|
|
|
39
39
|
const consoleLogStore = new Console();
|
|
40
40
|
const availableBrowsers = ['chromium', 'webkit', 'firefox', 'electron'];
|
|
41
41
|
|
|
42
|
+
const {
|
|
43
|
+
setRestartStrategy, restartsSession, restartsContext, restartsBrowser,
|
|
44
|
+
} = require('./extras/PlaywrightRestartOpts');
|
|
42
45
|
const { createValueEngine, createDisabledEngine } = require('./extras/PlaywrightPropEngine');
|
|
43
46
|
|
|
44
47
|
/**
|
|
@@ -50,12 +53,18 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
|
|
|
50
53
|
*
|
|
51
54
|
* This helper works with a browser out of the box with no additional tools required to install.
|
|
52
55
|
*
|
|
53
|
-
* Requires `playwright` package version ^1 to be installed:
|
|
56
|
+
* Requires `playwright` or `playwright-core` package version ^1 to be installed:
|
|
54
57
|
*
|
|
55
58
|
* ```
|
|
56
|
-
* npm i playwright@^1 --save
|
|
59
|
+
* npm i playwright@^1.18 --save
|
|
60
|
+
* ```
|
|
61
|
+
* or
|
|
62
|
+
* ```
|
|
63
|
+
* npm i playwright-core@^1.18 --save
|
|
57
64
|
* ```
|
|
58
65
|
*
|
|
66
|
+
* Using playwright-core package, will prevent the download of browser binaries and allow connecting to an existing browser installation or for connecting to a remote one.
|
|
67
|
+
*
|
|
59
68
|
* ## Configuration
|
|
60
69
|
*
|
|
61
70
|
* This helper should be configured in codecept.json or codecept.conf.js
|
|
@@ -63,17 +72,21 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
|
|
|
63
72
|
* * `url`: base url of website to be tested
|
|
64
73
|
* * `browser`: a browser to test on, either: `chromium`, `firefox`, `webkit`, `electron`. Default: chromium.
|
|
65
74
|
* * `show`: (optional, default: false) - show browser window.
|
|
66
|
-
* * `restart`: (optional, default:
|
|
75
|
+
* * `restart`: (optional, default: false) - restart strategy between tests. Possible values:
|
|
76
|
+
* * 'context' or **false** - restarts [browser context](https://playwright.dev/docs/api/class-browsercontext) but keeps running browser. Recommended by Playwright team to keep tests isolated.
|
|
77
|
+
* * 'browser' or **true** - closes browser and opens it again between tests.
|
|
78
|
+
* * 'session' or 'keep' - keeps browser context and session, but cleans up cookies and localStorage between tests. The fastest option when running tests in windowed mode. Works with `keepCookies` and `keepBrowserState` options. This behavior was default before CodeceptJS 3.1
|
|
79
|
+
* * `timeout`: (optional, default: 1000) - [timeout](https://playwright.dev/docs/api/class-page#page-set-default-timeout) in ms of all Playwright actions .
|
|
67
80
|
* * `disableScreenshots`: (optional, default: false) - don't save screenshot on failure.
|
|
68
81
|
* * `emulate`: (optional, default: {}) launch browser in device emulation mode.
|
|
69
82
|
* * `video`: (optional, default: false) enables video recording for failed tests; videos are saved into `output/videos` folder
|
|
70
83
|
* * `trace`: (optional, default: false) record [tracing information](https://playwright.dev/docs/trace-viewer) with screenshots and snapshots.
|
|
71
84
|
* * `fullPageScreenshots` (optional, default: false) - make full page screenshots on failure.
|
|
72
85
|
* * `uniqueScreenshotNames`: (optional, default: false) - option to prevent screenshot override if you have scenarios with the same name in different suites.
|
|
73
|
-
* * `keepBrowserState`: (optional, default: false) - keep browser state between tests when `restart` is set to
|
|
74
|
-
* * `keepCookies`: (optional, default: false) - keep cookies between tests when `restart` is set to
|
|
86
|
+
* * `keepBrowserState`: (optional, default: false) - keep browser state between tests when `restart` is set to 'session'.
|
|
87
|
+
* * `keepCookies`: (optional, default: false) - keep cookies between tests when `restart` is set to 'session'.
|
|
75
88
|
* * `waitForAction`: (optional) how long to wait after click, doubleClick or PressKey actions in ms. Default: 100.
|
|
76
|
-
* * `waitForNavigation`: (optional, default: 'load'). When to consider navigation succeeded. Possible options: `load`, `domcontentloaded`, `networkidle`. Choose one of those options is possible. See [Playwright API](https://github.com/microsoft/playwright/blob/
|
|
89
|
+
* * `waitForNavigation`: (optional, default: 'load'). When to consider navigation succeeded. Possible options: `load`, `domcontentloaded`, `networkidle`. Choose one of those options is possible. See [Playwright API](https://github.com/microsoft/playwright/blob/main/docs/api.md#pagewaitfornavigationoptions).
|
|
77
90
|
* * `pressKeyDelay`: (optional, default: '10'). Delay between key presses in ms. Used when calling Playwrights page.type(...) in fillField/appendField
|
|
78
91
|
* * `getPageTimeout` (optional, default: '0') config option to set maximum navigation time in milliseconds.
|
|
79
92
|
* * `waitForTimeout`: (optional) default wait* timeout in ms. Default: 1000.
|
|
@@ -84,6 +97,7 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
|
|
|
84
97
|
* * `manualStart`: (optional, default: false) - do not start browser before a test, start it manually inside a helper with `this.helpers["Playwright"]._startBrowser()`.
|
|
85
98
|
* * `chromium`: (optional) pass additional chromium options
|
|
86
99
|
* * `electron`: (optional) pass additional electron options
|
|
100
|
+
* * `channel`: (optional) While Playwright can operate against the stock Google Chrome and Microsoft Edge browsers available on the machine. In particular, current Playwright version will support Stable and Beta channels of these browsers. See [Google Chrome & Microsoft Edge](https://playwright.dev/docs/browsers/#google-chrome--microsoft-edge).
|
|
87
101
|
*
|
|
88
102
|
* #### Video Recording Customization
|
|
89
103
|
*
|
|
@@ -235,7 +249,7 @@ class Playwright extends Helper {
|
|
|
235
249
|
constructor(config) {
|
|
236
250
|
super(config);
|
|
237
251
|
|
|
238
|
-
playwright =
|
|
252
|
+
playwright = requireWithFallback('playwright', 'playwright-core');
|
|
239
253
|
|
|
240
254
|
// set defaults
|
|
241
255
|
this.isRemoteBrowser = false;
|
|
@@ -259,8 +273,10 @@ class Playwright extends Helper {
|
|
|
259
273
|
waitForAction: 100,
|
|
260
274
|
waitForTimeout: 1000,
|
|
261
275
|
pressKeyDelay: 10,
|
|
276
|
+
timeout: 1000,
|
|
262
277
|
fullPageScreenshots: false,
|
|
263
278
|
disableScreenshots: false,
|
|
279
|
+
ignoreLog: ['warning', 'log'],
|
|
264
280
|
uniqueScreenshotNames: false,
|
|
265
281
|
manualStart: false,
|
|
266
282
|
getPageTimeout: 0,
|
|
@@ -270,6 +286,7 @@ class Playwright extends Helper {
|
|
|
270
286
|
keepBrowserState: false,
|
|
271
287
|
show: false,
|
|
272
288
|
defaultPopupAction: 'accept',
|
|
289
|
+
use: { actionTimeout: 0 },
|
|
273
290
|
ignoreHTTPSErrors: false, // Adding it here o that context can be set up to ignore the SSL errors
|
|
274
291
|
};
|
|
275
292
|
|
|
@@ -297,10 +314,16 @@ class Playwright extends Helper {
|
|
|
297
314
|
|
|
298
315
|
_setConfig(config) {
|
|
299
316
|
this.options = this._validateConfig(config);
|
|
317
|
+
setRestartStrategy(this.options);
|
|
300
318
|
this.playwrightOptions = {
|
|
301
319
|
headless: !this.options.show,
|
|
302
320
|
...this._getOptionsForBrowser(config),
|
|
303
321
|
};
|
|
322
|
+
|
|
323
|
+
if (this.options.channel && this.options.browser === 'chromium') {
|
|
324
|
+
this.playwrightOptions.channel = this.options.channel;
|
|
325
|
+
}
|
|
326
|
+
|
|
304
327
|
if (this.options.video) {
|
|
305
328
|
this.options.recordVideo = { size: parseWindowSize(this.options.windowSize) };
|
|
306
329
|
}
|
|
@@ -329,9 +352,9 @@ class Playwright extends Helper {
|
|
|
329
352
|
|
|
330
353
|
static _checkRequirements() {
|
|
331
354
|
try {
|
|
332
|
-
|
|
355
|
+
requireWithFallback('playwright', 'playwright-core');
|
|
333
356
|
} catch (e) {
|
|
334
|
-
return ['playwright@^1'];
|
|
357
|
+
return ['playwright@^1.18'];
|
|
335
358
|
}
|
|
336
359
|
}
|
|
337
360
|
|
|
@@ -348,7 +371,7 @@ class Playwright extends Helper {
|
|
|
348
371
|
}
|
|
349
372
|
|
|
350
373
|
_beforeSuite() {
|
|
351
|
-
if (
|
|
374
|
+
if ((restartsSession() || restartsContext()) && !this.options.manualStart && !this.isRunning) {
|
|
352
375
|
this.debugSection('Session', 'Starting singleton browser session');
|
|
353
376
|
return this._startBrowser();
|
|
354
377
|
}
|
|
@@ -365,7 +388,8 @@ class Playwright extends Helper {
|
|
|
365
388
|
return err.message.includes('context');
|
|
366
389
|
},
|
|
367
390
|
});
|
|
368
|
-
|
|
391
|
+
|
|
392
|
+
if (restartsBrowser() && !this.options.manualStart) await this._startBrowser();
|
|
369
393
|
if (!this.isRunning && !this.options.manualStart) await this._startBrowser();
|
|
370
394
|
|
|
371
395
|
this.isAuthenticated = false;
|
|
@@ -387,7 +411,9 @@ class Playwright extends Helper {
|
|
|
387
411
|
if (this.storageState) contextOptions.storageState = this.storageState;
|
|
388
412
|
if (this.options.userAgent) contextOptions.userAgent = this.options.userAgent;
|
|
389
413
|
if (this.options.locale) contextOptions.locale = this.options.locale;
|
|
390
|
-
this.browserContext
|
|
414
|
+
if (!this.browserContext || !restartsSession()) {
|
|
415
|
+
this.browserContext = await this.browser.newContext(contextOptions); // Adding the HTTPSError ignore in the context so that we can ignore those errors
|
|
416
|
+
}
|
|
391
417
|
}
|
|
392
418
|
|
|
393
419
|
let mainPage;
|
|
@@ -415,7 +441,11 @@ class Playwright extends Helper {
|
|
|
415
441
|
return;
|
|
416
442
|
}
|
|
417
443
|
|
|
418
|
-
if (
|
|
444
|
+
if (restartsSession()) {
|
|
445
|
+
return refreshContextSession.bind(this)();
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (restartsBrowser()) {
|
|
419
449
|
this.isRunning = false;
|
|
420
450
|
return this._stopBrowser();
|
|
421
451
|
}
|
|
@@ -437,11 +467,10 @@ class Playwright extends Helper {
|
|
|
437
467
|
return this.browser;
|
|
438
468
|
}
|
|
439
469
|
|
|
440
|
-
_afterSuite() {
|
|
441
|
-
}
|
|
470
|
+
_afterSuite() {}
|
|
442
471
|
|
|
443
|
-
_finishTest() {
|
|
444
|
-
if (
|
|
472
|
+
async _finishTest() {
|
|
473
|
+
if ((restartsSession() || restartsContext()) && this.isRunning) return this._stopBrowser();
|
|
445
474
|
}
|
|
446
475
|
|
|
447
476
|
_session() {
|
|
@@ -500,16 +529,16 @@ class Playwright extends Helper {
|
|
|
500
529
|
* First argument is a description of an action.
|
|
501
530
|
* Second argument is async function that gets this helper as parameter.
|
|
502
531
|
*
|
|
503
|
-
* { [`page`](https://github.com/microsoft/playwright/blob/
|
|
532
|
+
* { [`page`](https://github.com/microsoft/playwright/blob/main/docs/src/api/class-page.md), [`browserContext`](https://github.com/microsoft/playwright/blob/main/docs/src/api/class-browsercontext.md) [`browser`](https://github.com/microsoft/playwright/blob/main/docs/src/api/class-browser.md) } objects from Playwright API are available.
|
|
504
533
|
*
|
|
505
534
|
* ```js
|
|
506
|
-
* I.usePlaywrightTo('emulate offline mode', async ({
|
|
507
|
-
* await
|
|
535
|
+
* I.usePlaywrightTo('emulate offline mode', async ({ browserContext }) => {
|
|
536
|
+
* await browserContext.setOffline(true);
|
|
508
537
|
* });
|
|
509
538
|
* ```
|
|
510
539
|
*
|
|
511
540
|
* @param {string} description used to show in logs.
|
|
512
|
-
* @param {function} fn async
|
|
541
|
+
* @param {function} fn async function that executed with Playwright helper as argument
|
|
513
542
|
*/
|
|
514
543
|
usePlaywrightTo(description, fn) {
|
|
515
544
|
return this._useTo(...arguments);
|
|
@@ -577,7 +606,14 @@ class Playwright extends Helper {
|
|
|
577
606
|
this._addPopupListener(page);
|
|
578
607
|
this.page = page;
|
|
579
608
|
if (!page) return;
|
|
609
|
+
this.browserContext.setDefaultTimeout(0);
|
|
580
610
|
page.setDefaultNavigationTimeout(this.options.getPageTimeout);
|
|
611
|
+
page.setDefaultTimeout(this.options.timeout);
|
|
612
|
+
|
|
613
|
+
page.on('crash', async () => {
|
|
614
|
+
console.log('ERROR: Page has crashed, closing page!');
|
|
615
|
+
await page.close();
|
|
616
|
+
});
|
|
581
617
|
this.context = await this.page;
|
|
582
618
|
this.contextLocator = null;
|
|
583
619
|
if (this.options.browser === 'chrome') {
|
|
@@ -673,7 +709,6 @@ class Playwright extends Helper {
|
|
|
673
709
|
this._setPage(null);
|
|
674
710
|
this.context = null;
|
|
675
711
|
popupStore.clear();
|
|
676
|
-
|
|
677
712
|
await this.browser.close();
|
|
678
713
|
}
|
|
679
714
|
|
|
@@ -815,9 +850,21 @@ class Playwright extends Helper {
|
|
|
815
850
|
|
|
816
851
|
/**
|
|
817
852
|
* {{> dragAndDrop }}
|
|
853
|
+
*
|
|
854
|
+
* [Additional options](https://playwright.dev/docs/api/class-page#page-drag-and-drop) can be passed as 3rd argument.
|
|
855
|
+
*
|
|
856
|
+
* ```js
|
|
857
|
+
* // specify coordinates for source position
|
|
858
|
+
* I.dragAndDrop('img.src', 'img.dst', { sourcePosition: {x: 10, y: 10} })
|
|
859
|
+
* ```
|
|
860
|
+
*
|
|
861
|
+
* > By default option `force: true` is set
|
|
818
862
|
*/
|
|
819
|
-
async dragAndDrop(srcElement, destElement) {
|
|
820
|
-
|
|
863
|
+
async dragAndDrop(srcElement, destElement, options = { force: true }) {
|
|
864
|
+
const src = new Locator(srcElement, 'css');
|
|
865
|
+
const dst = new Locator(destElement, 'css');
|
|
866
|
+
|
|
867
|
+
return this.page.dragAndDrop(buildLocatorString(src), buildLocatorString(dst), options);
|
|
821
868
|
}
|
|
822
869
|
|
|
823
870
|
/**
|
|
@@ -1065,7 +1112,7 @@ class Playwright extends Helper {
|
|
|
1065
1112
|
* I.openNewTab();
|
|
1066
1113
|
* ```
|
|
1067
1114
|
*
|
|
1068
|
-
* You can pass in [page options](https://github.com/microsoft/playwright/blob/
|
|
1115
|
+
* You can pass in [page options](https://github.com/microsoft/playwright/blob/main/docs/api.md#browsernewpageoptions) to emulate device on this page
|
|
1069
1116
|
*
|
|
1070
1117
|
* ```js
|
|
1071
1118
|
* // enable mobile
|
|
@@ -1157,10 +1204,21 @@ class Playwright extends Helper {
|
|
|
1157
1204
|
/**
|
|
1158
1205
|
* {{> click }}
|
|
1159
1206
|
*
|
|
1207
|
+
* [Additional options](https://playwright.dev/docs/api/class-page#page-click) for click available as 3rd argument.
|
|
1208
|
+
*
|
|
1209
|
+
* Examples:
|
|
1210
|
+
*
|
|
1211
|
+
* ```js
|
|
1212
|
+
* // click on element at position
|
|
1213
|
+
* I.click('canvas', '.model', { position: { x: 20, y: 40 } })
|
|
1214
|
+
*
|
|
1215
|
+
* // make ctrl-click
|
|
1216
|
+
* I.click('.edit', null, { modifiers: ['Ctrl'] } )
|
|
1217
|
+
* ```
|
|
1160
1218
|
*
|
|
1161
1219
|
*/
|
|
1162
|
-
async click(locator, context = null) {
|
|
1163
|
-
return proceedClick.call(this, locator, context);
|
|
1220
|
+
async click(locator, context = null, opts = {}) {
|
|
1221
|
+
return proceedClick.call(this, locator, context, opts);
|
|
1164
1222
|
}
|
|
1165
1223
|
|
|
1166
1224
|
/**
|
|
@@ -1199,30 +1257,40 @@ class Playwright extends Helper {
|
|
|
1199
1257
|
|
|
1200
1258
|
/**
|
|
1201
1259
|
* {{> checkOption }}
|
|
1260
|
+
*
|
|
1261
|
+
* [Additional options](https://playwright.dev/docs/api/class-elementhandle#element-handle-check) for check available as 3rd argument.
|
|
1262
|
+
*
|
|
1263
|
+
* Examples:
|
|
1264
|
+
*
|
|
1265
|
+
* ```js
|
|
1266
|
+
* // click on element at position
|
|
1267
|
+
* I.checkOption('Agree', '.signup', { position: { x: 5, y: 5 } })
|
|
1268
|
+
* ```
|
|
1269
|
+
* > ⚠️ To avoid flakiness, option `force: true` is set by default
|
|
1202
1270
|
*/
|
|
1203
|
-
async checkOption(field, context = null) {
|
|
1271
|
+
async checkOption(field, context = null, options = { force: true }) {
|
|
1204
1272
|
const elm = await this._locateCheckable(field, context);
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
// Only check if NOT currently checked
|
|
1208
|
-
if (!curentlyChecked) {
|
|
1209
|
-
await elm.click();
|
|
1210
|
-
return this._waitForAction();
|
|
1211
|
-
}
|
|
1273
|
+
await elm.check(options);
|
|
1274
|
+
return this._waitForAction();
|
|
1212
1275
|
}
|
|
1213
1276
|
|
|
1214
1277
|
/**
|
|
1215
1278
|
* {{> uncheckOption }}
|
|
1279
|
+
*
|
|
1280
|
+
* [Additional options](https://playwright.dev/docs/api/class-elementhandle#element-handle-uncheck) for uncheck available as 3rd argument.
|
|
1281
|
+
*
|
|
1282
|
+
* Examples:
|
|
1283
|
+
*
|
|
1284
|
+
* ```js
|
|
1285
|
+
* // click on element at position
|
|
1286
|
+
* I.uncheckOption('Agree', '.signup', { position: { x: 5, y: 5 } })
|
|
1287
|
+
* ```
|
|
1288
|
+
* > ⚠️ To avoid flakiness, option `force: true` is set by default
|
|
1216
1289
|
*/
|
|
1217
|
-
async uncheckOption(field, context = null) {
|
|
1290
|
+
async uncheckOption(field, context = null, options = { force: true }) {
|
|
1218
1291
|
const elm = await this._locateCheckable(field, context);
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
// Only uncheck if currently checked
|
|
1222
|
-
if (curentlyChecked) {
|
|
1223
|
-
await elm.click();
|
|
1224
|
-
return this._waitForAction();
|
|
1225
|
-
}
|
|
1292
|
+
await elm.uncheck(options);
|
|
1293
|
+
return this._waitForAction();
|
|
1226
1294
|
}
|
|
1227
1295
|
|
|
1228
1296
|
/**
|
|
@@ -1574,7 +1642,7 @@ class Playwright extends Helper {
|
|
|
1574
1642
|
*/
|
|
1575
1643
|
async clearCookie() {
|
|
1576
1644
|
// Playwright currently doesn't support to delete a certain cookie
|
|
1577
|
-
// https://github.com/microsoft/playwright/blob/
|
|
1645
|
+
// https://github.com/microsoft/playwright/blob/main/docs/api.md#class-browsercontext
|
|
1578
1646
|
if (!this.browserContext) return;
|
|
1579
1647
|
return this.browserContext.clearCookies();
|
|
1580
1648
|
}
|
|
@@ -1870,6 +1938,53 @@ class Playwright extends Helper {
|
|
|
1870
1938
|
return this.page.screenshot({ path: outputFile, fullPage: fullPageOption, type: 'png' });
|
|
1871
1939
|
}
|
|
1872
1940
|
|
|
1941
|
+
/**
|
|
1942
|
+
* Performs [api request](https://playwright.dev/docs/api/class-apirequestcontext#api-request-context-get) using
|
|
1943
|
+
* the cookies from the current browser session.
|
|
1944
|
+
*
|
|
1945
|
+
* ```js
|
|
1946
|
+
* const users = await I.makeApiRequest('GET', '/api/users', { params: { page: 1 }});
|
|
1947
|
+
* users[0]
|
|
1948
|
+
* I.makeApiRequest('PATCH', )
|
|
1949
|
+
* ```
|
|
1950
|
+
*
|
|
1951
|
+
* > This is Playwright's built-in alternative to using REST helper's sendGet, sendPost, etc methods.
|
|
1952
|
+
*
|
|
1953
|
+
* @param {string} method HTTP method
|
|
1954
|
+
* @param {string} url endpoint
|
|
1955
|
+
* @param {object} options request options depending on method used
|
|
1956
|
+
* @returns {Promise<object>} response
|
|
1957
|
+
*/
|
|
1958
|
+
async makeApiRequest(method, url, options) {
|
|
1959
|
+
method = method.toLowerCase();
|
|
1960
|
+
const allowedMethods = ['get', 'post', 'patch', 'head', 'fetch', 'delete'];
|
|
1961
|
+
if (!allowedMethods.includes(method)) {
|
|
1962
|
+
throw new Error(`Method ${method} is not allowed, use the one from a list ${allowedMethods} or switch to using REST helper`);
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
if (url.startsWith('/')) { // local url
|
|
1966
|
+
url = this.options.url + url;
|
|
1967
|
+
this.debugSection('URL', url);
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
const response = await this.page.request[method](url, options);
|
|
1971
|
+
this.debugSection('Status', response.status());
|
|
1972
|
+
this.debugSection('Response', await response.text());
|
|
1973
|
+
|
|
1974
|
+
// hook to allow JSON response handle this
|
|
1975
|
+
if (this.config.onResponse) {
|
|
1976
|
+
const axiosResponse = {
|
|
1977
|
+
data: await response.json(),
|
|
1978
|
+
status: response.status(),
|
|
1979
|
+
statusText: response.statusText(),
|
|
1980
|
+
headers: response.headers(),
|
|
1981
|
+
};
|
|
1982
|
+
this.config.onResponse(axiosResponse);
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
return response;
|
|
1986
|
+
}
|
|
1987
|
+
|
|
1873
1988
|
async _failed(test) {
|
|
1874
1989
|
await this._withinEnd();
|
|
1875
1990
|
|
|
@@ -1882,7 +1997,7 @@ class Playwright extends Helper {
|
|
|
1882
1997
|
}
|
|
1883
1998
|
|
|
1884
1999
|
if (this.options.trace) {
|
|
1885
|
-
const path = `${global.output_dir}/trace/${clearString(test.title)
|
|
2000
|
+
const path = `${`${global.output_dir}/trace/${clearString(test.title)}`.slice(0, 251)}.zip`;
|
|
1886
2001
|
await this.browserContext.tracing.stop({ path });
|
|
1887
2002
|
test.artifacts.trace = path;
|
|
1888
2003
|
}
|
|
@@ -2478,8 +2593,7 @@ async function findCheckable(locator, context) {
|
|
|
2478
2593
|
async function proceedIsChecked(assertType, option) {
|
|
2479
2594
|
let els = await findCheckable.call(this, option);
|
|
2480
2595
|
assertElementExists(els, option, 'Checkable');
|
|
2481
|
-
els = await Promise.all(els.map(el => el.
|
|
2482
|
-
els = await Promise.all(els.map(el => el.jsonValue()));
|
|
2596
|
+
els = await Promise.all(els.map(el => el.isChecked()));
|
|
2483
2597
|
const selected = els.reduce((prev, cur) => prev || cur);
|
|
2484
2598
|
return truth(`checkable ${option}`, 'to be checked')[assertType](selected);
|
|
2485
2599
|
}
|
|
@@ -2507,36 +2621,6 @@ async function findFields(locator) {
|
|
|
2507
2621
|
return this._locate({ css: locator });
|
|
2508
2622
|
}
|
|
2509
2623
|
|
|
2510
|
-
async function proceedDragAndDrop(sourceLocator, destinationLocator) {
|
|
2511
|
-
// modern drag and drop in Playwright
|
|
2512
|
-
if (this.page.dragAndDrop) {
|
|
2513
|
-
const source = new Locator(sourceLocator);
|
|
2514
|
-
const dest = new Locator(destinationLocator);
|
|
2515
|
-
if (source.isBasic() && dest.isBasic()) {
|
|
2516
|
-
return this.page.dragAndDrop(source.simplify(), dest.simplify());
|
|
2517
|
-
}
|
|
2518
|
-
}
|
|
2519
|
-
|
|
2520
|
-
const src = await this._locate(sourceLocator);
|
|
2521
|
-
assertElementExists(src, sourceLocator, 'Source Element');
|
|
2522
|
-
|
|
2523
|
-
const dst = await this._locate(destinationLocator);
|
|
2524
|
-
assertElementExists(dst, destinationLocator, 'Destination Element');
|
|
2525
|
-
|
|
2526
|
-
// Note: Using clickablePoint private api becaues the .BoundingBox does not take into account iframe offsets!
|
|
2527
|
-
const dragSource = await clickablePoint(src[0]);
|
|
2528
|
-
const dragDestination = await clickablePoint(dst[0]);
|
|
2529
|
-
|
|
2530
|
-
// Drag start point
|
|
2531
|
-
await this.page.mouse.move(dragSource.x, dragSource.y, { steps: 5 });
|
|
2532
|
-
await this.page.mouse.down();
|
|
2533
|
-
|
|
2534
|
-
// Drag destination
|
|
2535
|
-
await this.page.mouse.move(dragDestination.x, dragDestination.y, { steps: 5 });
|
|
2536
|
-
await this.page.mouse.up();
|
|
2537
|
-
await this._waitForAction();
|
|
2538
|
-
}
|
|
2539
|
-
|
|
2540
2624
|
async function proceedSeeInField(assertType, field, value) {
|
|
2541
2625
|
const els = await findFields.call(this, field);
|
|
2542
2626
|
assertElementExists(els, field, 'Field');
|
|
@@ -2563,14 +2647,15 @@ async function proceedSeeInField(assertType, field, value) {
|
|
|
2563
2647
|
};
|
|
2564
2648
|
|
|
2565
2649
|
if (tag === 'SELECT') {
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2650
|
+
if (await el.getProperty('multiple')) {
|
|
2651
|
+
const selectedOptions = await el.$$('option:checked');
|
|
2652
|
+
if (!selectedOptions.length) return null;
|
|
2653
|
+
|
|
2654
|
+
const options = await filterFieldsByValue(selectedOptions, value, true);
|
|
2655
|
+
return proceedMultiple(options);
|
|
2570
2656
|
}
|
|
2571
2657
|
|
|
2572
|
-
|
|
2573
|
-
return proceedMultiple(options);
|
|
2658
|
+
return el.inputValue();
|
|
2574
2659
|
}
|
|
2575
2660
|
|
|
2576
2661
|
if (tag === 'INPUT') {
|
|
@@ -2586,7 +2671,8 @@ async function proceedSeeInField(assertType, field, value) {
|
|
|
2586
2671
|
}
|
|
2587
2672
|
return proceedMultiple(els[0]);
|
|
2588
2673
|
}
|
|
2589
|
-
|
|
2674
|
+
|
|
2675
|
+
const fieldVal = await el.inputValue();
|
|
2590
2676
|
return stringIncludes(`fields by ${field}`)[assertType](value, fieldVal);
|
|
2591
2677
|
}
|
|
2592
2678
|
|
|
@@ -2617,10 +2703,10 @@ async function filterFieldsBySelectionState(elements, state) {
|
|
|
2617
2703
|
}
|
|
2618
2704
|
|
|
2619
2705
|
async function elementSelected(element) {
|
|
2620
|
-
const type = await element.getProperty('type').then(el => el.jsonValue());
|
|
2706
|
+
const type = await element.getProperty('type').then(el => !!el && el.jsonValue());
|
|
2621
2707
|
|
|
2622
2708
|
if (type === 'checkbox' || type === 'radio') {
|
|
2623
|
-
return element.
|
|
2709
|
+
return element.isChecked();
|
|
2624
2710
|
}
|
|
2625
2711
|
return element.getProperty('selected').then(el => el.jsonValue());
|
|
2626
2712
|
}
|
|
@@ -2668,12 +2754,18 @@ async function targetCreatedHandler(page) {
|
|
|
2668
2754
|
});
|
|
2669
2755
|
});
|
|
2670
2756
|
page.on('console', (msg) => {
|
|
2671
|
-
|
|
2757
|
+
if (!consoleLogStore.includes(msg) && this.options.ignoreLog && !this.options.ignoreLog.includes(msg.type())) {
|
|
2758
|
+
this.debugSection(`Browser:${ucfirst(msg.type())}`, (msg.text && msg.text() || msg._text || '') + msg.args().join(' '));
|
|
2759
|
+
}
|
|
2672
2760
|
consoleLogStore.add(msg);
|
|
2673
2761
|
});
|
|
2674
2762
|
|
|
2675
2763
|
if (this.options.windowSize && this.options.windowSize.indexOf('x') > 0 && this._getType() === 'Browser') {
|
|
2676
|
-
|
|
2764
|
+
try {
|
|
2765
|
+
await page.setViewportSize(parseWindowSize(this.options.windowSize));
|
|
2766
|
+
} catch (err) {
|
|
2767
|
+
// target can be already closed, ignoring...
|
|
2768
|
+
}
|
|
2677
2769
|
}
|
|
2678
2770
|
}
|
|
2679
2771
|
|
|
@@ -2763,3 +2855,37 @@ async function clickablePoint(el) {
|
|
|
2763
2855
|
} = rect;
|
|
2764
2856
|
return { x: x + width / 2, y: y + height / 2 };
|
|
2765
2857
|
}
|
|
2858
|
+
|
|
2859
|
+
async function refreshContextSession() {
|
|
2860
|
+
// close other sessions
|
|
2861
|
+
try {
|
|
2862
|
+
const contexts = await this.browser.contexts();
|
|
2863
|
+
contexts.shift();
|
|
2864
|
+
|
|
2865
|
+
await Promise.all(contexts.map(c => c.close()));
|
|
2866
|
+
} catch (e) {
|
|
2867
|
+
console.log(e);
|
|
2868
|
+
}
|
|
2869
|
+
|
|
2870
|
+
if (this.page) {
|
|
2871
|
+
const existingPages = await this.browserContext.pages();
|
|
2872
|
+
await this._setPage(existingPages[0]);
|
|
2873
|
+
}
|
|
2874
|
+
|
|
2875
|
+
if (this.options.keepBrowserState) return;
|
|
2876
|
+
|
|
2877
|
+
if (!this.options.keepCookies) {
|
|
2878
|
+
this.debugSection('Session', 'cleaning cookies and localStorage');
|
|
2879
|
+
await this.clearCookie();
|
|
2880
|
+
}
|
|
2881
|
+
const currentUrl = await this.grabCurrentUrl();
|
|
2882
|
+
|
|
2883
|
+
if (currentUrl.startsWith('http')) {
|
|
2884
|
+
await this.executeScript('localStorage.clear();').catch((err) => {
|
|
2885
|
+
if (!(err.message.indexOf("Storage is disabled inside 'data:' URLs.") > -1)) throw err;
|
|
2886
|
+
});
|
|
2887
|
+
await this.executeScript('sessionStorage.clear();').catch((err) => {
|
|
2888
|
+
if (!(err.message.indexOf("Storage is disabled inside 'data:' URLs.") > -1)) throw err;
|
|
2889
|
+
});
|
|
2890
|
+
}
|
|
2891
|
+
}
|
package/lib/helper/Puppeteer.js
CHANGED
|
@@ -22,6 +22,7 @@ const {
|
|
|
22
22
|
screenshotOutputFolder,
|
|
23
23
|
getNormalizedKeyAttributeValue,
|
|
24
24
|
isModifierKey,
|
|
25
|
+
requireWithFallback,
|
|
25
26
|
} = require('../utils');
|
|
26
27
|
const {
|
|
27
28
|
isColorProperty,
|
|
@@ -43,7 +44,15 @@ const consoleLogStore = new Console();
|
|
|
43
44
|
* Browser control is executed via DevTools Protocol (instead of Selenium).
|
|
44
45
|
* This helper works with a browser out of the box with no additional tools required to install.
|
|
45
46
|
*
|
|
46
|
-
* Requires `puppeteer` package to be installed.
|
|
47
|
+
* Requires `puppeteer` or `puppeteer-core` package to be installed.
|
|
48
|
+
* ```
|
|
49
|
+
* npm i puppeteer --save
|
|
50
|
+
* ```
|
|
51
|
+
* or
|
|
52
|
+
* ```
|
|
53
|
+
* npm i puppeteer-core --save
|
|
54
|
+
* ```
|
|
55
|
+
* Using `puppeteer-core` package, will prevent the download of browser binaries and allow connecting to an existing browser installation or for connecting to a remote one.
|
|
47
56
|
*
|
|
48
57
|
* > Experimental Firefox support [can be activated](https://codecept.io/helpers/Puppeteer-firefox).
|
|
49
58
|
*
|
|
@@ -182,7 +191,7 @@ class Puppeteer extends Helper {
|
|
|
182
191
|
constructor(config) {
|
|
183
192
|
super(config);
|
|
184
193
|
|
|
185
|
-
puppeteer =
|
|
194
|
+
puppeteer = requireWithFallback('puppeteer', 'puppeteer-core');
|
|
186
195
|
|
|
187
196
|
// set defaults
|
|
188
197
|
this.isRemoteBrowser = false;
|
|
@@ -245,7 +254,7 @@ class Puppeteer extends Helper {
|
|
|
245
254
|
|
|
246
255
|
static _checkRequirements() {
|
|
247
256
|
try {
|
|
248
|
-
|
|
257
|
+
requireWithFallback('puppeteer', 'puppeteer-core');
|
|
249
258
|
} catch (e) {
|
|
250
259
|
return ['puppeteer'];
|
|
251
260
|
}
|
|
@@ -264,7 +273,7 @@ class Puppeteer extends Helper {
|
|
|
264
273
|
async _before() {
|
|
265
274
|
this.sessionPages = {};
|
|
266
275
|
recorder.retry({
|
|
267
|
-
retries:
|
|
276
|
+
retries: 3,
|
|
268
277
|
when: err => {
|
|
269
278
|
if (!err || typeof (err.message) !== 'string') {
|
|
270
279
|
return false;
|
package/lib/helper/REST.js
CHANGED
|
@@ -63,6 +63,12 @@ class REST extends Helper {
|
|
|
63
63
|
this.axios.defaults.headers = this.options.defaultHeaders;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
static _config() {
|
|
67
|
+
return [
|
|
68
|
+
{ name: 'endpoint', message: 'Endpoint of API you are going to test', default: 'http://localhost:3000/api' },
|
|
69
|
+
];
|
|
70
|
+
}
|
|
71
|
+
|
|
66
72
|
static _checkRequirements() {
|
|
67
73
|
try {
|
|
68
74
|
require('axios');
|
|
@@ -71,6 +77,33 @@ class REST extends Helper {
|
|
|
71
77
|
}
|
|
72
78
|
}
|
|
73
79
|
|
|
80
|
+
_before() {
|
|
81
|
+
this.headers = { ...this.options.defaultHeaders };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Sets request headers for all requests of this test
|
|
86
|
+
*
|
|
87
|
+
* @param {object} headers headers list
|
|
88
|
+
*/
|
|
89
|
+
haveRequestHeaders(headers) {
|
|
90
|
+
this.headers = { ...headers, ...this.headers };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Adds a header for Bearer authentication
|
|
95
|
+
*
|
|
96
|
+
* ```js
|
|
97
|
+
* // we use secret function to hide token from logs
|
|
98
|
+
* I.amBearerAuthenticated(secret('heregoestoken'))
|
|
99
|
+
* ```
|
|
100
|
+
*
|
|
101
|
+
* @param {string} accessToken Bearer access token
|
|
102
|
+
*/
|
|
103
|
+
amBearerAuthenticated(accessToken) {
|
|
104
|
+
this.haveRequestHeaders({ Authorization: `Bearer ${accessToken}` });
|
|
105
|
+
}
|
|
106
|
+
|
|
74
107
|
/**
|
|
75
108
|
* Executes axios request
|
|
76
109
|
*
|
|
@@ -111,6 +144,9 @@ class REST extends Helper {
|
|
|
111
144
|
this.debugSection('Response', `Response error. Status code: ${err.response.status}`);
|
|
112
145
|
response = err.response;
|
|
113
146
|
}
|
|
147
|
+
if (this.config.onResponse) {
|
|
148
|
+
await this.config.onResponse(response);
|
|
149
|
+
}
|
|
114
150
|
this.debugSection('Response', JSON.stringify(response.data));
|
|
115
151
|
return response;
|
|
116
152
|
}
|
package/lib/helper/WebDriver.js
CHANGED
|
@@ -582,7 +582,7 @@ class WebDriver extends Helper {
|
|
|
582
582
|
this.context = this.root;
|
|
583
583
|
if (this.options.restart && !this.options.manualStart) return this._startBrowser();
|
|
584
584
|
if (!this.isRunning && !this.options.manualStart) return this._startBrowser();
|
|
585
|
-
this.$$ = this.browser.$$.bind(this.browser);
|
|
585
|
+
if (this.browser) this.$$ = this.browser.$$.bind(this.browser);
|
|
586
586
|
return this.browser;
|
|
587
587
|
}
|
|
588
588
|
|