codeceptjs 3.2.1 → 3.3.0-beta.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.
Files changed (73) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/docs/advanced.md +3 -3
  3. package/docs/api.md +227 -188
  4. package/docs/basics.md +26 -1
  5. package/docs/bdd.md +2 -2
  6. package/docs/build/ApiDataFactory.js +13 -6
  7. package/docs/build/Appium.js +98 -36
  8. package/docs/build/FileSystem.js +11 -1
  9. package/docs/build/GraphQL.js +11 -0
  10. package/docs/build/JSONResponse.js +297 -0
  11. package/docs/build/Nightmare.js +48 -48
  12. package/docs/build/Playwright.js +282 -151
  13. package/docs/build/Puppeteer.js +76 -67
  14. package/docs/build/REST.js +36 -0
  15. package/docs/build/TestCafe.js +44 -44
  16. package/docs/build/WebDriver.js +70 -70
  17. package/docs/changelog.md +17 -0
  18. package/docs/configuration.md +8 -8
  19. package/docs/custom-helpers.md +1 -1
  20. package/docs/data.md +9 -9
  21. package/docs/helpers/ApiDataFactory.md +7 -0
  22. package/docs/helpers/Appium.md +240 -198
  23. package/docs/helpers/FileSystem.md +11 -1
  24. package/docs/helpers/JSONResponse.md +230 -0
  25. package/docs/helpers/Playwright.md +283 -216
  26. package/docs/helpers/Puppeteer.md +9 -1
  27. package/docs/helpers/REST.md +30 -9
  28. package/docs/installation.md +3 -1
  29. package/docs/internal-api.md +265 -0
  30. package/docs/mobile.md +11 -11
  31. package/docs/nightmare.md +3 -3
  32. package/docs/pageobjects.md +2 -0
  33. package/docs/playwright.md +73 -18
  34. package/docs/plugins.md +138 -38
  35. package/docs/puppeteer.md +28 -12
  36. package/docs/quickstart.md +2 -3
  37. package/docs/reports.md +44 -3
  38. package/docs/testcafe.md +1 -1
  39. package/docs/translation.md +2 -2
  40. package/docs/videos.md +2 -2
  41. package/docs/visual.md +2 -2
  42. package/docs/vue.md +1 -1
  43. package/docs/webdriver.md +92 -4
  44. package/lib/actor.js +2 -2
  45. package/lib/cli.js +25 -20
  46. package/lib/command/init.js +5 -15
  47. package/lib/command/workers/runTests.js +25 -7
  48. package/lib/config.js +17 -13
  49. package/lib/helper/ApiDataFactory.js +13 -6
  50. package/lib/helper/Appium.js +65 -3
  51. package/lib/helper/FileSystem.js +11 -1
  52. package/lib/helper/GraphQL.js +11 -0
  53. package/lib/helper/JSONResponse.js +297 -0
  54. package/lib/helper/Playwright.js +220 -89
  55. package/lib/helper/Puppeteer.js +12 -3
  56. package/lib/helper/REST.js +36 -0
  57. package/lib/helper/WebDriver.js +1 -1
  58. package/lib/helper/extras/Console.js +8 -0
  59. package/lib/helper/extras/PlaywrightRestartOpts.js +35 -0
  60. package/lib/interfaces/bdd.js +3 -1
  61. package/lib/listener/timeout.js +4 -3
  62. package/lib/plugin/allure.js +12 -0
  63. package/lib/plugin/autoLogin.js +1 -1
  64. package/lib/plugin/eachElement.js +127 -0
  65. package/lib/plugin/stepTimeout.js +5 -4
  66. package/lib/plugin/tryTo.js +6 -0
  67. package/lib/recorder.js +2 -1
  68. package/lib/step.js +57 -2
  69. package/lib/utils.js +20 -0
  70. package/package.json +24 -22
  71. package/translations/pt-BR.js +8 -0
  72. package/typings/index.d.ts +4 -0
  73. package/typings/types.d.ts +345 -110
@@ -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: true) - restart browser between tests.
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 false.
74
- * * `keepCookies`: (optional, default: false) - keep cookies between tests when `restart` is set to false.
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/master/docs/api.md#pagewaitfornavigationoptions).
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 = require('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
- require('playwright');
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,14 +371,25 @@ class Playwright extends Helper {
348
371
  }
349
372
 
350
373
  _beforeSuite() {
351
- if (!this.options.restart && !this.options.manualStart && !this.isRunning) {
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
  }
355
378
  }
356
379
 
357
380
  async _before() {
358
- if (this.options.restart && !this.options.manualStart) await this._startBrowser();
381
+ recorder.retry({
382
+ retries: 5,
383
+ when: err => {
384
+ if (!err || typeof (err.message) !== 'string') {
385
+ return false;
386
+ }
387
+ // ignore context errors
388
+ return err.message.includes('context');
389
+ },
390
+ });
391
+
392
+ if (restartsBrowser() && !this.options.manualStart) await this._startBrowser();
359
393
  if (!this.isRunning && !this.options.manualStart) await this._startBrowser();
360
394
 
361
395
  this.isAuthenticated = false;
@@ -377,7 +411,9 @@ class Playwright extends Helper {
377
411
  if (this.storageState) contextOptions.storageState = this.storageState;
378
412
  if (this.options.userAgent) contextOptions.userAgent = this.options.userAgent;
379
413
  if (this.options.locale) contextOptions.locale = this.options.locale;
380
- this.browserContext = await this.browser.newContext(contextOptions); // Adding the HTTPSError ignore in the context so that we can ignore those errors
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
+ }
381
417
  }
382
418
 
383
419
  let mainPage;
@@ -405,7 +441,11 @@ class Playwright extends Helper {
405
441
  return;
406
442
  }
407
443
 
408
- if (this.options.restart) {
444
+ if (restartsSession()) {
445
+ return refreshContextSession.bind(this)();
446
+ }
447
+
448
+ if (restartsBrowser()) {
409
449
  this.isRunning = false;
410
450
  return this._stopBrowser();
411
451
  }
@@ -427,11 +467,10 @@ class Playwright extends Helper {
427
467
  return this.browser;
428
468
  }
429
469
 
430
- _afterSuite() {
431
- }
470
+ _afterSuite() {}
432
471
 
433
- _finishTest() {
434
- if (!this.options.restart && this.isRunning) return this._stopBrowser();
472
+ async _finishTest() {
473
+ if ((restartsSession() || restartsContext()) && this.isRunning) return this._stopBrowser();
435
474
  }
436
475
 
437
476
  _session() {
@@ -490,16 +529,16 @@ class Playwright extends Helper {
490
529
  * First argument is a description of an action.
491
530
  * Second argument is async function that gets this helper as parameter.
492
531
  *
493
- * { [`page`](https://github.com/microsoft/playwright/blob/master/docs/api.md#class-page), [`context`](https://github.com/microsoft/playwright/blob/master/docs/api.md#class-context) [`browser`](https://github.com/microsoft/playwright/blob/master/docs/api.md#class-browser) } objects from Playwright API are available.
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.
494
533
  *
495
534
  * ```js
496
- * I.usePlaywrightTo('emulate offline mode', async ({ context }) {
497
- * await context.setOffline(true);
535
+ * I.usePlaywrightTo('emulate offline mode', async ({ browserContext }) => {
536
+ * await browserContext.setOffline(true);
498
537
  * });
499
538
  * ```
500
539
  *
501
540
  * @param {string} description used to show in logs.
502
- * @param {function} fn async functuion that executed with Playwright helper as argument
541
+ * @param {function} fn async function that executed with Playwright helper as argument
503
542
  */
504
543
  usePlaywrightTo(description, fn) {
505
544
  return this._useTo(...arguments);
@@ -567,7 +606,9 @@ class Playwright extends Helper {
567
606
  this._addPopupListener(page);
568
607
  this.page = page;
569
608
  if (!page) return;
609
+ this.browserContext.setDefaultTimeout(0);
570
610
  page.setDefaultNavigationTimeout(this.options.getPageTimeout);
611
+ page.setDefaultTimeout(this.options.timeout);
571
612
 
572
613
  page.on('crash', async () => {
573
614
  console.log('ERROR: Page has crashed, closing page!');
@@ -668,7 +709,6 @@ class Playwright extends Helper {
668
709
  this._setPage(null);
669
710
  this.context = null;
670
711
  popupStore.clear();
671
-
672
712
  await this.browser.close();
673
713
  }
674
714
 
@@ -810,9 +850,21 @@ class Playwright extends Helper {
810
850
 
811
851
  /**
812
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
813
862
  */
814
- async dragAndDrop(srcElement, destElement) {
815
- return proceedDragAndDrop.call(this, srcElement, destElement);
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);
816
868
  }
817
869
 
818
870
  /**
@@ -1060,7 +1112,7 @@ class Playwright extends Helper {
1060
1112
  * I.openNewTab();
1061
1113
  * ```
1062
1114
  *
1063
- * You can pass in [page options](https://github.com/microsoft/playwright/blob/master/docs/api.md#browsernewpageoptions) to emulate device on this page
1115
+ * You can pass in [page options](https://github.com/microsoft/playwright/blob/main/docs/api.md#browsernewpageoptions) to emulate device on this page
1064
1116
  *
1065
1117
  * ```js
1066
1118
  * // enable mobile
@@ -1152,10 +1204,21 @@ class Playwright extends Helper {
1152
1204
  /**
1153
1205
  * {{> click }}
1154
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
+ * ```
1155
1218
  *
1156
1219
  */
1157
- async click(locator, context = null) {
1158
- return proceedClick.call(this, locator, context);
1220
+ async click(locator, context = null, opts = {}) {
1221
+ return proceedClick.call(this, locator, context, opts);
1159
1222
  }
1160
1223
 
1161
1224
  /**
@@ -1194,30 +1257,40 @@ class Playwright extends Helper {
1194
1257
 
1195
1258
  /**
1196
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
1197
1270
  */
1198
- async checkOption(field, context = null) {
1271
+ async checkOption(field, context = null, options = { force: true }) {
1199
1272
  const elm = await this._locateCheckable(field, context);
1200
- const curentlyChecked = await elm.getProperty('checked')
1201
- .then(checkedProperty => checkedProperty.jsonValue());
1202
- // Only check if NOT currently checked
1203
- if (!curentlyChecked) {
1204
- await elm.click();
1205
- return this._waitForAction();
1206
- }
1273
+ await elm.check(options);
1274
+ return this._waitForAction();
1207
1275
  }
1208
1276
 
1209
1277
  /**
1210
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
1211
1289
  */
1212
- async uncheckOption(field, context = null) {
1290
+ async uncheckOption(field, context = null, options = { force: true }) {
1213
1291
  const elm = await this._locateCheckable(field, context);
1214
- const curentlyChecked = await elm.getProperty('checked')
1215
- .then(checkedProperty => checkedProperty.jsonValue());
1216
- // Only uncheck if currently checked
1217
- if (curentlyChecked) {
1218
- await elm.click();
1219
- return this._waitForAction();
1220
- }
1292
+ await elm.uncheck(options);
1293
+ return this._waitForAction();
1221
1294
  }
1222
1295
 
1223
1296
  /**
@@ -1569,7 +1642,7 @@ class Playwright extends Helper {
1569
1642
  */
1570
1643
  async clearCookie() {
1571
1644
  // Playwright currently doesn't support to delete a certain cookie
1572
- // https://github.com/microsoft/playwright/blob/master/docs/api.md#class-browsercontext
1645
+ // https://github.com/microsoft/playwright/blob/main/docs/api.md#class-browsercontext
1573
1646
  if (!this.browserContext) return;
1574
1647
  return this.browserContext.clearCookies();
1575
1648
  }
@@ -1865,6 +1938,53 @@ class Playwright extends Helper {
1865
1938
  return this.page.screenshot({ path: outputFile, fullPage: fullPageOption, type: 'png' });
1866
1939
  }
1867
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
+
1868
1988
  async _failed(test) {
1869
1989
  await this._withinEnd();
1870
1990
 
@@ -1877,7 +1997,7 @@ class Playwright extends Helper {
1877
1997
  }
1878
1998
 
1879
1999
  if (this.options.trace) {
1880
- const path = `${global.output_dir}/trace/${clearString(test.title).slice(0, 255)}.zip`;
2000
+ const path = `${`${global.output_dir}/trace/${clearString(test.title)}`.slice(0, 251)}.zip`;
1881
2001
  await this.browserContext.tracing.stop({ path });
1882
2002
  test.artifacts.trace = path;
1883
2003
  }
@@ -2473,8 +2593,7 @@ async function findCheckable(locator, context) {
2473
2593
  async function proceedIsChecked(assertType, option) {
2474
2594
  let els = await findCheckable.call(this, option);
2475
2595
  assertElementExists(els, option, 'Checkable');
2476
- els = await Promise.all(els.map(el => el.getProperty('checked')));
2477
- els = await Promise.all(els.map(el => el.jsonValue()));
2596
+ els = await Promise.all(els.map(el => el.isChecked()));
2478
2597
  const selected = els.reduce((prev, cur) => prev || cur);
2479
2598
  return truth(`checkable ${option}`, 'to be checked')[assertType](selected);
2480
2599
  }
@@ -2502,36 +2621,6 @@ async function findFields(locator) {
2502
2621
  return this._locate({ css: locator });
2503
2622
  }
2504
2623
 
2505
- async function proceedDragAndDrop(sourceLocator, destinationLocator) {
2506
- // modern drag and drop in Playwright
2507
- if (this.page.dragAndDrop) {
2508
- const source = new Locator(sourceLocator);
2509
- const dest = new Locator(destinationLocator);
2510
- if (source.isBasic() && dest.isBasic()) {
2511
- return this.page.dragAndDrop(source.simplify(), dest.simplify());
2512
- }
2513
- }
2514
-
2515
- const src = await this._locate(sourceLocator);
2516
- assertElementExists(src, sourceLocator, 'Source Element');
2517
-
2518
- const dst = await this._locate(destinationLocator);
2519
- assertElementExists(dst, destinationLocator, 'Destination Element');
2520
-
2521
- // Note: Using clickablePoint private api becaues the .BoundingBox does not take into account iframe offsets!
2522
- const dragSource = await clickablePoint(src[0]);
2523
- const dragDestination = await clickablePoint(dst[0]);
2524
-
2525
- // Drag start point
2526
- await this.page.mouse.move(dragSource.x, dragSource.y, { steps: 5 });
2527
- await this.page.mouse.down();
2528
-
2529
- // Drag destination
2530
- await this.page.mouse.move(dragDestination.x, dragDestination.y, { steps: 5 });
2531
- await this.page.mouse.up();
2532
- await this._waitForAction();
2533
- }
2534
-
2535
2624
  async function proceedSeeInField(assertType, field, value) {
2536
2625
  const els = await findFields.call(this, field);
2537
2626
  assertElementExists(els, field, 'Field');
@@ -2558,14 +2647,15 @@ async function proceedSeeInField(assertType, field, value) {
2558
2647
  };
2559
2648
 
2560
2649
  if (tag === 'SELECT') {
2561
- const selectedOptions = await el.$$('option:checked');
2562
- // locate option by values and check them
2563
- if (value === '') {
2564
- return proceedMultiple(selectedOptions);
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);
2565
2656
  }
2566
2657
 
2567
- const options = await filterFieldsByValue(selectedOptions, value, true);
2568
- return proceedMultiple(options);
2658
+ return el.inputValue();
2569
2659
  }
2570
2660
 
2571
2661
  if (tag === 'INPUT') {
@@ -2581,7 +2671,8 @@ async function proceedSeeInField(assertType, field, value) {
2581
2671
  }
2582
2672
  return proceedMultiple(els[0]);
2583
2673
  }
2584
- const fieldVal = await el.getProperty('value').then(el => el.jsonValue());
2674
+
2675
+ const fieldVal = await el.inputValue();
2585
2676
  return stringIncludes(`fields by ${field}`)[assertType](value, fieldVal);
2586
2677
  }
2587
2678
 
@@ -2612,10 +2703,10 @@ async function filterFieldsBySelectionState(elements, state) {
2612
2703
  }
2613
2704
 
2614
2705
  async function elementSelected(element) {
2615
- const type = await element.getProperty('type').then(el => el.jsonValue());
2706
+ const type = await element.getProperty('type').then(el => !!el && el.jsonValue());
2616
2707
 
2617
2708
  if (type === 'checkbox' || type === 'radio') {
2618
- return element.getProperty('checked').then(el => el.jsonValue());
2709
+ return element.isChecked();
2619
2710
  }
2620
2711
  return element.getProperty('selected').then(el => el.jsonValue());
2621
2712
  }
@@ -2663,12 +2754,18 @@ async function targetCreatedHandler(page) {
2663
2754
  });
2664
2755
  });
2665
2756
  page.on('console', (msg) => {
2666
- this.debugSection(`Browser:${ucfirst(msg.type())}`, (msg.text && msg.text() || msg._text || '') + msg.args().join(' '));
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
+ }
2667
2760
  consoleLogStore.add(msg);
2668
2761
  });
2669
2762
 
2670
2763
  if (this.options.windowSize && this.options.windowSize.indexOf('x') > 0 && this._getType() === 'Browser') {
2671
- await page.setViewportSize(parseWindowSize(this.options.windowSize));
2764
+ try {
2765
+ await page.setViewportSize(parseWindowSize(this.options.windowSize));
2766
+ } catch (err) {
2767
+ // target can be already closed, ignoring...
2768
+ }
2672
2769
  }
2673
2770
  }
2674
2771
 
@@ -2758,3 +2855,37 @@ async function clickablePoint(el) {
2758
2855
  } = rect;
2759
2856
  return { x: x + width / 2, y: y + height / 2 };
2760
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
+ }
@@ -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 = require('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
- require('puppeteer');
257
+ requireWithFallback('puppeteer', 'puppeteer-core');
249
258
  } catch (e) {
250
259
  return ['puppeteer'];
251
260
  }
@@ -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
  }
@@ -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