codeceptjs 3.0.3 → 3.0.7

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 (64) hide show
  1. package/CHANGELOG.md +114 -18
  2. package/bin/codecept.js +1 -0
  3. package/docs/basics.md +2 -2
  4. package/docs/bdd.md +12 -1
  5. package/docs/build/Appium.js +2 -1
  6. package/docs/build/GraphQL.js +9 -10
  7. package/docs/build/Nightmare.js +4 -5
  8. package/docs/build/Playwright.js +164 -37
  9. package/docs/build/Protractor.js +1 -1
  10. package/docs/build/Puppeteer.js +1 -1
  11. package/docs/build/REST.js +24 -4
  12. package/docs/build/TestCafe.js +1 -1
  13. package/docs/build/WebDriver.js +85 -17
  14. package/docs/changelog.md +114 -18
  15. package/docs/data.md +5 -5
  16. package/docs/detox.md +2 -2
  17. package/docs/docker.md +11 -11
  18. package/docs/email.md +8 -8
  19. package/docs/helpers/Appium.md +1 -1
  20. package/docs/helpers/Nightmare.md +4 -5
  21. package/docs/helpers/Playwright.md +94 -64
  22. package/docs/helpers/Protractor.md +1 -1
  23. package/docs/helpers/Puppeteer.md +1 -1
  24. package/docs/helpers/REST.md +9 -0
  25. package/docs/helpers/TestCafe.md +1 -1
  26. package/docs/helpers/WebDriver.md +2 -1
  27. package/docs/locators.md +29 -2
  28. package/docs/mobile-react-native-locators.md +2 -2
  29. package/docs/mobile.md +3 -3
  30. package/docs/nightmare.md +0 -5
  31. package/docs/pageobjects.md +3 -1
  32. package/docs/parallel.md +35 -10
  33. package/docs/playwright.md +55 -8
  34. package/docs/plugins.md +73 -29
  35. package/docs/reports.md +8 -7
  36. package/docs/typescript.md +47 -5
  37. package/docs/webapi/fillField.mustache +1 -1
  38. package/lib/cli.js +25 -10
  39. package/lib/codecept.js +9 -1
  40. package/lib/command/interactive.js +10 -9
  41. package/lib/command/run.js +1 -1
  42. package/lib/command/workers/runTests.js +11 -6
  43. package/lib/config.js +8 -3
  44. package/lib/event.js +2 -0
  45. package/lib/helper/Appium.js +1 -0
  46. package/lib/helper/GraphQL.js +9 -10
  47. package/lib/helper/Nightmare.js +1 -1
  48. package/lib/helper/Playwright.js +131 -38
  49. package/lib/helper/REST.js +24 -4
  50. package/lib/helper/WebDriver.js +84 -16
  51. package/lib/interfaces/gherkin.js +11 -4
  52. package/lib/output.js +7 -4
  53. package/lib/plugin/allure.js +3 -7
  54. package/lib/plugin/fakerTransform.js +51 -0
  55. package/lib/plugin/screenshotOnFail.js +6 -2
  56. package/lib/recorder.js +9 -0
  57. package/lib/step.js +2 -1
  58. package/lib/transform.js +26 -0
  59. package/lib/ui.js +6 -2
  60. package/lib/within.js +1 -1
  61. package/lib/workers.js +39 -25
  62. package/package.json +14 -9
  63. package/typings/index.d.ts +49 -21
  64. package/typings/types.d.ts +72 -26
@@ -35,9 +35,10 @@ let defaultSelectorEnginesInitialized = false;
35
35
 
36
36
  const popupStore = new Popup();
37
37
  const consoleLogStore = new Console();
38
- const availableBrowsers = ['chromium', 'webkit', 'firefox'];
38
+ const availableBrowsers = ['chromium', 'webkit', 'firefox', 'electron'];
39
39
 
40
40
  const { createValueEngine, createDisabledEngine } = require('./extras/PlaywrightPropEngine');
41
+
41
42
  /**
42
43
  * Uses [Playwright](https://github.com/microsoft/playwright) library to run tests inside:
43
44
  *
@@ -58,7 +59,7 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
58
59
  * This helper should be configured in codecept.json or codecept.conf.js
59
60
  *
60
61
  * * `url`: base url of website to be tested
61
- * * `browser`: a browser to test on, either: `chromium`, `firefox`, `webkit`. Default: chromium.
62
+ * * `browser`: a browser to test on, either: `chromium`, `firefox`, `webkit`, `electron`. Default: chromium.
62
63
  * * `show`: (optional, default: false) - show browser window.
63
64
  * * `restart`: (optional, default: true) - restart browser between tests.
64
65
  * * `disableScreenshots`: (optional, default: false) - don't save screenshot on failure.
@@ -77,6 +78,7 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
77
78
  * * `userAgent`: (optional) user-agent string.
78
79
  * * `manualStart`: (optional, default: false) - do not start browser before a test, start it manually inside a helper with `this.helpers["Playwright"]._startBrowser()`.
79
80
  * * `chromium`: (optional) pass additional chromium options
81
+ * * `electron`: (optional) pass additional electron options
80
82
  *
81
83
  * #### Example #1: Wait for 0 network connections.
82
84
  *
@@ -121,7 +123,7 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
121
123
  * }
122
124
  * ```
123
125
  *
124
- * #### Example #4: Connect to remote browser by specifying [websocket endpoint](https://chromedevtools.github.io/devtools-protocol/#how-do-i-access-the-browser-target)
126
+ * #### Example #4: Connect to remote browser by specifying [websocket endpoint](https://playwright.dev/docs/api/class-browsertype#browsertypeconnectparams)
125
127
  *
126
128
  * ```js
127
129
  * {
@@ -129,7 +131,7 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
129
131
  * Playwright: {
130
132
  * url: "http://localhost",
131
133
  * chromium: {
132
- * browserWSEndpoint: "ws://localhost:9222/devtools/browser/c5aa6160-b5bc-4d53-bb49-6ecb36cd2e0a"
134
+ * browserWSEndpoint: { wsEndpoint: 'ws://localhost:9222/devtools/browser/c5aa6160-b5bc-4d53-bb49-6ecb36cd2e0a' }
133
135
  * }
134
136
  * }
135
137
  * }
@@ -147,6 +149,7 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
147
149
  * url: "http://localhost",
148
150
  * show: true // headless mode not supported for extensions
149
151
  * chromium: {
152
+ * userDataDir: '/tmp/playwright-tmp', // necessary to launch the browser in normal mode instead of incognito,
150
153
  * args: [
151
154
  * `--disable-extensions-except=${pathToExtension}`,
152
155
  * `--load-extension=${pathToExtension}`
@@ -206,6 +209,8 @@ class Playwright extends Helper {
206
209
  this.isAuthenticated = false;
207
210
  this.sessionPages = {};
208
211
  this.activeSessionName = '';
212
+ this.isElectron = false;
213
+ this.electronSessions = [];
209
214
 
210
215
  // override defaults with config
211
216
  this._setConfig(config);
@@ -257,6 +262,8 @@ class Playwright extends Helper {
257
262
  ...this._getOptionsForBrowser(config),
258
263
  };
259
264
  this.isRemoteBrowser = !!this.playwrightOptions.browserWSEndpoint;
265
+ this.isElectron = this.options.browser === 'electron';
266
+ this.userDataDir = this.playwrightOptions.userDataDir;
260
267
  popupStore.defaultAction = this.options.defaultPopupAction;
261
268
  }
262
269
 
@@ -268,7 +275,7 @@ class Playwright extends Helper {
268
275
  },
269
276
  {
270
277
  name: 'browser',
271
- message: 'Browser in which testing will be performed. Possible options: chromium, firefox or webkit',
278
+ message: 'Browser in which testing will be performed. Possible options: chromium, firefox, webkit or electron',
272
279
  default: 'chromium',
273
280
  },
274
281
  ];
@@ -320,11 +327,21 @@ class Playwright extends Helper {
320
327
  async _after() {
321
328
  if (!this.isRunning) return;
322
329
 
330
+ if (this.isElectron) {
331
+ this.browser.close();
332
+ this.electronSessions.forEach(session => session.close());
333
+ return;
334
+ }
335
+
323
336
  // close other sessions
324
- const contexts = await this.browser.contexts();
325
- contexts.shift();
337
+ try {
338
+ const contexts = await this.browser.contexts();
339
+ contexts.shift();
326
340
 
327
- await Promise.all(contexts.map(c => c.close()));
341
+ await Promise.all(contexts.map(c => c.close()));
342
+ } catch (e) {
343
+ console.log(e);
344
+ }
328
345
 
329
346
  if (this.options.restart) {
330
347
  this.isRunning = false;
@@ -371,12 +388,22 @@ class Playwright extends Helper {
371
388
  this.debugSection('New Context', config ? JSON.stringify(config) : 'opened');
372
389
  this.activeSessionName = sessionName;
373
390
 
374
- const bc = await this.browser.newContext(config);
375
- const page = await bc.newPage();
391
+ let browserContext;
392
+ let page;
393
+ if (this.isElectron) {
394
+ const browser = await playwright._electron.launch(this.playwrightOptions);
395
+ this.electronSessions.push(browser);
396
+ browserContext = browser.context();
397
+ page = await browser.firstWindow();
398
+ } else {
399
+ browserContext = await this.browser.newContext(config);
400
+ page = await browserContext.newPage();
401
+ }
402
+
376
403
  targetCreatedHandler.call(this, page);
377
404
  this._setPage(page);
378
405
  // Create a new page inside context.
379
- return bc;
406
+ return browserContext;
380
407
  },
381
408
  stop: async () => {
382
409
  // is closed by _after
@@ -496,6 +523,7 @@ class Playwright extends Helper {
496
523
  if (!page) return;
497
524
  page.setDefaultNavigationTimeout(this.options.getPageTimeout);
498
525
  this.context = await this.page;
526
+ this.contextLocator = null;
499
527
  if (this.config.browser === 'chrome') {
500
528
  await page.bringToFront();
501
529
  }
@@ -554,15 +582,19 @@ class Playwright extends Helper {
554
582
  }
555
583
 
556
584
  async _startBrowser() {
557
- if (this.isRemoteBrowser) {
585
+ if (this.isElectron) {
586
+ this.browser = await playwright._electron.launch(this.playwrightOptions);
587
+ } else if (this.isRemoteBrowser) {
558
588
  try {
559
- this.browser = await playwright[this.options.browser].connect(this.playwrightOptions);
589
+ this.browser = await playwright[this.options.browser].connect(this.playwrightOptions.browserWSEndpoint);
560
590
  } catch (err) {
561
591
  if (err.toString().indexOf('ECONNREFUSED')) {
562
592
  throw new RemoteBrowserConnectionRefused(err);
563
593
  }
564
594
  throw err;
565
595
  }
596
+ } else if (this.userDataDir) {
597
+ this.browser = await playwright[this.options.browser].launchPersistentContext(this.userDataDir, this.playwrightOptions);
566
598
  } else {
567
599
  this.browser = await playwright[this.options.browser].launch(this.playwrightOptions);
568
600
  }
@@ -571,11 +603,22 @@ class Playwright extends Helper {
571
603
  this.browser.on('targetchanged', (target) => {
572
604
  this.debugSection('Url', target.url());
573
605
  });
574
- this.browserContext = await this.browser.newContext({ ignoreHTTPSErrors: this.options.ignoreHTTPSErrors, acceptDownloads: true, ...this.options.emulate });// Adding the HTTPSError ignore in the context so that we can ignore those errors
575
606
 
576
- const existingPages = await this.browserContext.pages();
607
+ if (this.isElectron) {
608
+ this.browserContext = this.browser.context();
609
+ } else if (this.userDataDir) {
610
+ this.browserContext = this.browser;
611
+ } else {
612
+ this.browserContext = await this.browser.newContext({ ignoreHTTPSErrors: this.options.ignoreHTTPSErrors, acceptDownloads: true, ...this.options.emulate });// Adding the HTTPSError ignore in the context so that we can ignore those errors
613
+ }
577
614
 
578
- const mainPage = existingPages[0] || await this.browserContext.newPage();
615
+ let mainPage;
616
+ if (this.isElectron) {
617
+ mainPage = await this.browser.firstWindow();
618
+ } else {
619
+ const existingPages = await this.browserContext.pages();
620
+ mainPage = existingPages[0] || await this.browserContext.newPage();
621
+ }
579
622
  targetCreatedHandler.call(this, mainPage);
580
623
 
581
624
  await this._setPage(mainPage);
@@ -584,6 +627,10 @@ class Playwright extends Helper {
584
627
  this.isRunning = true;
585
628
  }
586
629
 
630
+ _getType() {
631
+ return this.browser._type;
632
+ }
633
+
587
634
  async _stopBrowser() {
588
635
  this.withinLocator = null;
589
636
  this._setPage(null);
@@ -618,6 +665,7 @@ class Playwright extends Helper {
618
665
  const els = await this._locate(locator);
619
666
  assertElementExists(els, locator);
620
667
  this.context = els[0];
668
+ this.contextLocator = locator;
621
669
 
622
670
  this.withinLocator = new Locator(locator);
623
671
  }
@@ -625,6 +673,7 @@ class Playwright extends Helper {
625
673
  async _withinEnd() {
626
674
  this.withinLocator = null;
627
675
  this.context = await this.page;
676
+ this.contextLocator = null;
628
677
  }
629
678
 
630
679
  _extractDataFromPerformanceTiming(timing, ...dataNames) {
@@ -651,6 +700,9 @@ class Playwright extends Helper {
651
700
  * @param {string} url url path or global url.
652
701
  */
653
702
  async amOnPage(url) {
703
+ if (this.isElectron) {
704
+ throw new Error('Cannot open pages inside an Electron container');
705
+ }
654
706
  if (!(/^\w+\:\/\//.test(url))) {
655
707
  url = this.options.url + url;
656
708
  }
@@ -878,11 +930,14 @@ class Playwright extends Helper {
878
930
  }
879
931
 
880
932
  /**
881
- * Checks that title is equal to provided one.
882
- *
883
- * ```js
884
- * I.seeTitleEquals('Test title.');
885
- * ```
933
+ * Checks that title is equal to provided one.
934
+ *
935
+ * ```js
936
+ * I.seeTitleEquals('Test title.');
937
+ * ```
938
+ *
939
+ * @param {string} text value to check.
940
+ *
886
941
  */
887
942
  async seeTitleEquals(text) {
888
943
  const title = await this.page.title();
@@ -981,6 +1036,9 @@ class Playwright extends Helper {
981
1036
  * @param {number} [num=1]
982
1037
  */
983
1038
  async switchToNextTab(num = 1) {
1039
+ if (this.isElectron) {
1040
+ throw new Error('Cannot switch tabs inside an Electron container');
1041
+ }
984
1042
  const pages = await this.browserContext.pages();
985
1043
 
986
1044
  const index = pages.indexOf(this.page);
@@ -1004,6 +1062,9 @@ class Playwright extends Helper {
1004
1062
  * @param {number} [num=1]
1005
1063
  */
1006
1064
  async switchToPreviousTab(num = 1) {
1065
+ if (this.isElectron) {
1066
+ throw new Error('Cannot switch tabs inside an Electron container');
1067
+ }
1007
1068
  const pages = await this.browserContext.pages();
1008
1069
  const index = pages.indexOf(this.page);
1009
1070
  this.withinLocator = null;
@@ -1025,6 +1086,9 @@ class Playwright extends Helper {
1025
1086
  * ```
1026
1087
  */
1027
1088
  async closeCurrentTab() {
1089
+ if (this.isElectron) {
1090
+ throw new Error('Cannot close current tab inside an Electron container');
1091
+ }
1028
1092
  const oldPage = this.page;
1029
1093
  await this.switchToPreviousTab();
1030
1094
  await oldPage.close();
@@ -1063,6 +1127,9 @@ class Playwright extends Helper {
1063
1127
  * ```
1064
1128
  */
1065
1129
  async openNewTab(options) {
1130
+ if (this.isElectron) {
1131
+ throw new Error('Cannot open new tabs inside an Electron container');
1132
+ }
1066
1133
  await this._setPage(await this.browserContext.newPage(options));
1067
1134
  return this._waitForAction();
1068
1135
  }
@@ -1217,14 +1284,34 @@ class Playwright extends Helper {
1217
1284
  }
1218
1285
 
1219
1286
  /**
1220
- *
1221
- * Force clicks an element without waiting for it to become visible and not animating.
1222
- *
1287
+ * Perform an emulated click on a link or a button, given by a locator.
1288
+ * Unlike normal click instead of sending native event, emulates a click with JavaScript.
1289
+ * This works on hidden, animated or inactive elements as well.
1290
+ *
1291
+ * If a fuzzy locator is given, the page will be searched for a button, link, or image matching the locator string.
1292
+ * For buttons, the "value" attribute, "name" attribute, and inner text are searched. For links, the link text is searched.
1293
+ * For images, the "alt" attribute and inner text of any parent links are searched.
1294
+ *
1295
+ * The second parameter is a context (CSS or XPath locator) to narrow the search.
1296
+ *
1223
1297
  * ```js
1224
- * I.forceClick('#hiddenButton');
1225
- * I.forceClick('Click me', '#hidden');
1298
+ * // simple link
1299
+ * I.forceClick('Logout');
1300
+ * // button of form
1301
+ * I.forceClick('Submit');
1302
+ * // CSS button
1303
+ * I.forceClick('#form input[type=submit]');
1304
+ * // XPath
1305
+ * I.forceClick('//form/*[@type=submit]');
1306
+ * // link in context
1307
+ * I.forceClick('Logout', '#nav');
1308
+ * // using strict locator
1309
+ * I.forceClick({css: 'nav a.login'});
1226
1310
  * ```
1227
- *
1311
+ *
1312
+ * @param {CodeceptJS.LocatorOrString} locator clickable link or button located by text, or any element located by CSS|XPath|strict locator.
1313
+ * @param {?CodeceptJS.LocatorOrString} [context=null] (optional, `null` by default) element to search in CSS|XPath|Strict locator.
1314
+ *
1228
1315
  */
1229
1316
  async forceClick(locator, context = null) {
1230
1317
  return proceedClick.call(this, locator, context, { force: true });
@@ -1530,7 +1617,7 @@ class Playwright extends Helper {
1530
1617
  * I.fillField({css: 'form#login input[name=username]'}, 'John');
1531
1618
  * ```
1532
1619
  * @param {CodeceptJS.LocatorOrString} field located by label|name|CSS|XPath|strict locator.
1533
- * @param {string} value text value to fill.
1620
+ * @param {CodeceptJS.StringOrSecret} value text value to fill.
1534
1621
  *
1535
1622
  *
1536
1623
  */
@@ -2043,6 +2130,10 @@ class Playwright extends Helper {
2043
2130
  * I.executeScript(([x, y]) => x + y, [x, y]);
2044
2131
  * ```
2045
2132
  * If a function returns a Promise it will wait for its resolution.
2133
+ *
2134
+ * @param {string|function} fn function to be executed in browser context.
2135
+ * @param {any} [arg] optional argument to pass to the function
2136
+ * @return {Promise<any>}
2046
2137
  */
2047
2138
  async executeScript(fn, arg) {
2048
2139
  let context = this.page;
@@ -2052,6 +2143,22 @@ class Playwright extends Helper {
2052
2143
  return context.evaluate.apply(context, [fn, arg]);
2053
2144
  }
2054
2145
 
2146
+ /**
2147
+ * Grab Locator if called within Context
2148
+ *
2149
+ * @param {*} locator
2150
+ */
2151
+ _contextLocator(locator) {
2152
+ locator = buildLocatorString(new Locator(locator, 'css'));
2153
+
2154
+ if (this.contextLocator) {
2155
+ const contextLocator = buildLocatorString(new Locator(this.contextLocator, 'css'));
2156
+ locator = `${contextLocator} >> ${locator}`;
2157
+ }
2158
+
2159
+ return locator;
2160
+ }
2161
+
2055
2162
  /**
2056
2163
  * Retrieves a text from an element located by CSS or XPath and returns it to test.
2057
2164
  * Resumes test execution, so **should be used inside async with `await`** operator.
@@ -2067,10 +2174,11 @@ class Playwright extends Helper {
2067
2174
  *
2068
2175
  */
2069
2176
  async grabTextFrom(locator) {
2070
- const texts = await this.grabTextFromAll(locator);
2071
- assertElementExists(texts, locator);
2072
- this.debugSection('Text', texts[0]);
2073
- return texts[0];
2177
+ locator = this._contextLocator(locator);
2178
+ const text = await this.page.textContent(locator);
2179
+ assertElementExists(text, locator);
2180
+ this.debugSection('Text', text);
2181
+ return text;
2074
2182
  }
2075
2183
 
2076
2184
  /**
@@ -2209,8 +2317,7 @@ class Playwright extends Helper {
2209
2317
  async grabCssPropertyFromAll(locator, cssProperty) {
2210
2318
  const els = await this._locate(locator);
2211
2319
  this.debug(`Matched ${els.length} elements`);
2212
- const res = await Promise.all(els.map(el => el.$eval('xpath=.', el => JSON.parse(JSON.stringify(getComputedStyle(el))), el)));
2213
- const cssValues = res.map(props => props[toCamelCase(cssProperty)]);
2320
+ const cssValues = await Promise.all(els.map(el => el.$eval('xpath=.', (el, cssProperty) => getComputedStyle(el).getPropertyValue(cssProperty), cssProperty)));
2214
2321
 
2215
2322
  return cssValues;
2216
2323
  }
@@ -2824,6 +2931,7 @@ class Playwright extends Helper {
2824
2931
 
2825
2932
  if (locator >= 0 && locator < childFrames.length) {
2826
2933
  this.context = childFrames[locator];
2934
+ this.contextLocator = locator;
2827
2935
  } else {
2828
2936
  throw new Error('Element #invalidIframeSelector was not found by text|CSS|XPath');
2829
2937
  }
@@ -2831,6 +2939,7 @@ class Playwright extends Helper {
2831
2939
  }
2832
2940
  if (!locator) {
2833
2941
  this.context = this.page;
2942
+ this.contextLocator = null;
2834
2943
  return;
2835
2944
  }
2836
2945
 
@@ -2841,8 +2950,10 @@ class Playwright extends Helper {
2841
2950
 
2842
2951
  if (contentFrame) {
2843
2952
  this.context = contentFrame;
2953
+ this.contextLocator = null;
2844
2954
  } else {
2845
2955
  this.context = els[0];
2956
+ this.contextLocator = locator;
2846
2957
  }
2847
2958
  }
2848
2959
 
@@ -2882,7 +2993,7 @@ class Playwright extends Helper {
2882
2993
  /**
2883
2994
  * Waits for navigation to finish. By default takes configured `waitForNavigation` option.
2884
2995
  *
2885
- * See [Pupeteer's reference](https://github.com/microsoft/Playwright/blob/master/docs/api.md#pagewaitfornavigationoptions)
2996
+ * See [Playwright's reference](https://playwright.dev/docs/api/class-page?_highlight=waitfornavi#pagewaitfornavigationoptions)
2886
2997
  *
2887
2998
  * @param {*} opts
2888
2999
  */
@@ -3031,6 +3142,19 @@ async function findElements(matcher, locator) {
3031
3142
  return matcher.$$(buildLocatorString(locator));
3032
3143
  }
3033
3144
 
3145
+ async function getVisibleElements(elements) {
3146
+ const visibleElements = [];
3147
+ for (const element of elements) {
3148
+ if (await element.isVisible()) {
3149
+ visibleElements.push(element);
3150
+ }
3151
+ }
3152
+ if (visibleElements.length === 0) {
3153
+ return elements;
3154
+ }
3155
+ return visibleElements;
3156
+ }
3157
+
3034
3158
  async function proceedClick(locator, context = null, options = {}) {
3035
3159
  let matcher = await this._getContext();
3036
3160
  if (context) {
@@ -3050,7 +3174,8 @@ async function proceedClick(locator, context = null, options = {}) {
3050
3174
  if (options.force) {
3051
3175
  await els[0].dispatchEvent('click');
3052
3176
  } else {
3053
- await els[0].click(options);
3177
+ const element = els.length > 1 ? (await getVisibleElements(els))[0] : els[0];
3178
+ await element.click(options);
3054
3179
  }
3055
3180
  const promises = [];
3056
3181
  if (options.waitForNavigation) {
@@ -3308,11 +3433,13 @@ async function targetCreatedHandler(page) {
3308
3433
  // we are inside iframe?
3309
3434
  const frameEl = await this.context.frameElement();
3310
3435
  this.context = await frameEl.contentFrame();
3436
+ this.contextLocator = null;
3311
3437
  return;
3312
3438
  }
3313
3439
  // if context element was in iframe - keep it
3314
3440
  // if (await this.context.ownerFrame()) return;
3315
3441
  this.context = page;
3442
+ this.contextLocator = null;
3316
3443
  });
3317
3444
  });
3318
3445
  page.on('console', (msg) => {
@@ -3323,7 +3450,7 @@ async function targetCreatedHandler(page) {
3323
3450
  if (this.options.userAgent) {
3324
3451
  await page.setUserAgent(this.options.userAgent);
3325
3452
  }
3326
- if (this.options.windowSize && this.options.windowSize.indexOf('x') > 0) {
3453
+ if (this.options.windowSize && this.options.windowSize.indexOf('x') > 0 && this._getType() === 'Browser') {
3327
3454
  const dimensions = this.options.windowSize.split('x');
3328
3455
  const width = parseInt(dimensions[0], 10);
3329
3456
  const height = parseInt(dimensions[1], 10);
@@ -723,7 +723,7 @@ class Protractor extends Helper {
723
723
  * I.fillField({css: 'form#login input[name=username]'}, 'John');
724
724
  * ```
725
725
  * @param {CodeceptJS.LocatorOrString} field located by label|name|CSS|XPath|strict locator.
726
- * @param {string} value text value to fill.
726
+ * @param {CodeceptJS.StringOrSecret} value text value to fill.
727
727
  *
728
728
  */
729
729
  async fillField(field, value) {
@@ -1593,7 +1593,7 @@ class Puppeteer extends Helper {
1593
1593
  * I.fillField({css: 'form#login input[name=username]'}, 'John');
1594
1594
  * ```
1595
1595
  * @param {CodeceptJS.LocatorOrString} field located by label|name|CSS|XPath|strict locator.
1596
- * @param {string} value text value to fill.
1596
+ * @param {CodeceptJS.StringOrSecret} value text value to fill.
1597
1597
  *
1598
1598
  * {{ react }}
1599
1599
  */
@@ -1,4 +1,5 @@
1
1
  const axios = require('axios').default;
2
+ const Secret = require('../secret');
2
3
 
3
4
  const Helper = require('../helper');
4
5
 
@@ -58,7 +59,8 @@ class REST extends Helper {
58
59
 
59
60
  this.options = { ...this.options, ...config };
60
61
  this.headers = { ...this.options.defaultHeaders };
61
- axios.defaults.headers = this.options.defaultHeaders;
62
+ this.axios = axios.create();
63
+ this.axios.defaults.headers = this.options.defaultHeaders;
62
64
  }
63
65
 
64
66
  static _checkRequirements() {
@@ -75,12 +77,18 @@ class REST extends Helper {
75
77
  * @param {*} request
76
78
  */
77
79
  async _executeRequest(request) {
78
- axios.defaults.timeout = request.timeout || this.options.timeout;
80
+ const _debugRequest = { ...request };
81
+ this.axios.defaults.timeout = request.timeout || this.options.timeout;
79
82
 
80
83
  if (this.headers && this.headers.auth) {
81
84
  request.auth = this.headers.auth;
82
85
  }
83
86
 
87
+ if (request.data instanceof Secret) {
88
+ _debugRequest.data = '*****';
89
+ request.data = typeof request.data === 'object' ? { ...request.data.toString() } : request.data.toString();
90
+ }
91
+
84
92
  if ((typeof request.data) === 'string') {
85
93
  if (!request.headers || !request.headers['Content-Type']) {
86
94
  request.headers = { ...request.headers, ...{ 'Content-Type': 'application/x-www-form-urlencoded' } };
@@ -91,11 +99,11 @@ class REST extends Helper {
91
99
  await this.config.onRequest(request);
92
100
  }
93
101
 
94
- this.debugSection('Request', JSON.stringify(request));
102
+ this.debugSection('Request', JSON.stringify(_debugRequest));
95
103
 
96
104
  let response;
97
105
  try {
98
- response = await axios(request);
106
+ response = await this.axios(request);
99
107
  } catch (err) {
100
108
  if (!err.response) throw err;
101
109
  this.debugSection('Response', `Response error. Status code: ${err.response.status}`);
@@ -149,6 +157,10 @@ class REST extends Helper {
149
157
  *
150
158
  * ```js
151
159
  * I.sendPostRequest('/api/users.json', { "email": "user@user.com" });
160
+ *
161
+ * // To mask the payload in logs
162
+ * I.sendPostRequest('/api/users.json', secret({ "email": "user@user.com" }));
163
+ *
152
164
  * ```
153
165
  *
154
166
  * @param {*} url
@@ -176,6 +188,10 @@ class REST extends Helper {
176
188
  *
177
189
  * ```js
178
190
  * I.sendPatchRequest('/api/users.json', { "email": "user@user.com" });
191
+ *
192
+ * // To mask the payload in logs
193
+ * I.sendPatchRequest('/api/users.json', secret({ "email": "user@user.com" }));
194
+ *
179
195
  * ```
180
196
  *
181
197
  * @param {string} url
@@ -203,6 +219,10 @@ class REST extends Helper {
203
219
  *
204
220
  * ```js
205
221
  * I.sendPutRequest('/api/users.json', { "email": "user@user.com" });
222
+ *
223
+ * // To mask the payload in logs
224
+ * I.sendPutRequest('/api/users.json', secret({ "email": "user@user.com" }));
225
+ *
206
226
  * ```
207
227
  *
208
228
  * @param {string} url
@@ -440,7 +440,7 @@ class TestCafe extends Helper {
440
440
  * I.fillField({css: 'form#login input[name=username]'}, 'John');
441
441
  * ```
442
442
  * @param {CodeceptJS.LocatorOrString} field located by label|name|CSS|XPath|strict locator.
443
- * @param {string} value text value to fill.
443
+ * @param {CodeceptJS.StringOrSecret} value text value to fill.
444
444
  *
445
445
  */
446
446
  async fillField(field, value) {