codeceptjs 2.4.3 → 2.6.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 (76) hide show
  1. package/CHANGELOG.md +117 -0
  2. package/README.md +32 -7
  3. package/bin/codecept.js +3 -0
  4. package/docs/basics.md +11 -5
  5. package/docs/bdd.md +4 -4
  6. package/docs/build/MockRequest.js +3 -0
  7. package/docs/build/Nightmare.js +10 -2
  8. package/docs/build/Playwright.js +3187 -0
  9. package/docs/build/Protractor.js +16 -2
  10. package/docs/build/Puppeteer.js +126 -19
  11. package/docs/build/REST.js +3 -1
  12. package/docs/build/TestCafe.js +11 -3
  13. package/docs/build/WebDriver.js +361 -28
  14. package/docs/changelog.md +116 -0
  15. package/docs/configuration.md +2 -2
  16. package/docs/custom-helpers.md +55 -10
  17. package/docs/helpers/Appium.md +81 -1
  18. package/docs/helpers/MockRequest.md +281 -38
  19. package/docs/helpers/Nightmare.md +10 -2
  20. package/docs/helpers/Playwright.md +1770 -0
  21. package/docs/helpers/Protractor.md +15 -3
  22. package/docs/helpers/Puppeteer-firefox.md +32 -1
  23. package/docs/helpers/Puppeteer.md +126 -76
  24. package/docs/helpers/TestCafe.md +10 -2
  25. package/docs/helpers/WebDriver.md +208 -118
  26. package/docs/locators.md +2 -0
  27. package/docs/playwright.md +306 -0
  28. package/docs/plugins.md +103 -0
  29. package/docs/reports.md +12 -0
  30. package/docs/shadow.md +68 -0
  31. package/docs/visual.md +0 -73
  32. package/docs/webapi/forceClick.mustache +27 -0
  33. package/docs/webapi/seeInPopup.mustache +7 -0
  34. package/docs/webapi/setCookie.mustache +10 -2
  35. package/docs/webapi/type.mustache +12 -0
  36. package/docs/webdriver.md +7 -3
  37. package/lib/codecept.js +1 -1
  38. package/lib/command/definitions.js +2 -2
  39. package/lib/command/generate.js +4 -4
  40. package/lib/command/gherkin/snippets.js +4 -4
  41. package/lib/command/init.js +1 -1
  42. package/lib/command/interactive.js +3 -0
  43. package/lib/command/run-multiple.js +2 -2
  44. package/lib/command/run-rerun.js +2 -0
  45. package/lib/command/run-workers.js +22 -8
  46. package/lib/command/run.js +2 -0
  47. package/lib/command/workers/runTests.js +1 -0
  48. package/lib/container.js +1 -1
  49. package/lib/event.js +2 -0
  50. package/lib/helper/MockRequest.js +3 -0
  51. package/lib/helper/Playwright.js +2422 -0
  52. package/lib/helper/Protractor.js +1 -2
  53. package/lib/helper/Puppeteer.js +84 -19
  54. package/lib/helper/REST.js +3 -1
  55. package/lib/helper/TestCafe.js +1 -1
  56. package/lib/helper/WebDriver.js +313 -26
  57. package/lib/helper/extras/PlaywrightPropEngine.js +53 -0
  58. package/lib/helper/scripts/isElementClickable.js +54 -14
  59. package/lib/interfaces/gherkin.js +1 -1
  60. package/lib/listener/helpers.js +3 -0
  61. package/lib/locator.js +5 -0
  62. package/lib/mochaFactory.js +12 -10
  63. package/lib/plugin/allure.js +8 -1
  64. package/lib/plugin/autoDelay.js +1 -8
  65. package/lib/plugin/commentStep.js +133 -0
  66. package/lib/plugin/screenshotOnFail.js +3 -10
  67. package/lib/plugin/selenoid.js +2 -2
  68. package/lib/plugin/standardActingHelpers.js +13 -0
  69. package/lib/plugin/stepByStepReport.js +1 -8
  70. package/lib/plugin/wdio.js +10 -1
  71. package/lib/reporter/cli.js +30 -1
  72. package/lib/session.js +7 -4
  73. package/package.json +13 -10
  74. package/typings/Mocha.d.ts +567 -16
  75. package/typings/index.d.ts +9 -5
  76. package/typings/types.d.ts +1634 -74
@@ -1557,6 +1557,12 @@ class Protractor extends Helper {
1557
1557
  /**
1558
1558
  * Checks that the active JavaScript popup, as created by `window.alert|window.confirm|window.prompt`, contains the
1559
1559
  * given string.
1560
+ *
1561
+ * ```js
1562
+ * I.seeInPopup('Popup text');
1563
+ * ```
1564
+ * @param {string} text value to check.
1565
+ *
1560
1566
  */
1561
1567
  async seeInPopup(text) {
1562
1568
  const popupAlert = await this.browser.switchTo().alert();
@@ -2256,13 +2262,21 @@ class Protractor extends Helper {
2256
2262
  }
2257
2263
 
2258
2264
  /**
2259
- * Sets a cookie.
2265
+ * Sets cookie(s).
2266
+ *
2267
+ * Can be a single cookie object or an array of cookies:
2260
2268
  *
2261
2269
  * ```js
2262
2270
  * I.setCookie({name: 'auth', value: true});
2271
+ *
2272
+ * // as array
2273
+ * I.setCookie([
2274
+ * {name: 'auth', value: true},
2275
+ * {name: 'agree', value: true}
2276
+ * ]);
2263
2277
  * ```
2264
2278
  *
2265
- * @param {object} cookie a cookie object.
2279
+ * @param {object|array} cookie a cookie object or array of cookie objects.
2266
2280
  */
2267
2281
  setCookie(cookie) {
2268
2282
  return this.browser.manage().addCookie(cookie);
@@ -36,7 +36,6 @@ const findReact = require('./extras/React');
36
36
 
37
37
  let puppeteer;
38
38
  let perfTiming;
39
- let isAuthenticated = false;
40
39
  const popupStore = new Popup();
41
40
  const consoleLogStore = new Console();
42
41
 
@@ -168,11 +167,14 @@ class Puppeteer extends Helper {
168
167
  constructor(config) {
169
168
  super(config);
170
169
 
171
- puppeteer = requireg(config.browser === 'firefox' ? 'puppeteer-firefox' : 'puppeteer');
170
+ puppeteer = requireg('puppeteer');
172
171
 
173
172
  // set defaults
174
173
  this.isRemoteBrowser = false;
175
174
  this.isRunning = false;
175
+ this.isAuthenticated = false;
176
+ this.sessionPages = {};
177
+ this.activeSessionName = '';
176
178
 
177
179
  // override defaults with config
178
180
  this._setConfig(config);
@@ -201,7 +203,7 @@ class Puppeteer extends Helper {
201
203
  }
202
204
 
203
205
  _getOptions(config) {
204
- return config.browser === 'firefox' ? this.options.firefox : this.options.chrome;
206
+ return config.browser === 'firefox' ? Object.assign(this.options.firefox, { product: 'firefox' }) : this.options.chrome;
205
207
  }
206
208
 
207
209
  _setConfig(config) {
@@ -220,6 +222,9 @@ class Puppeteer extends Helper {
220
222
  {
221
223
  name: 'show', message: 'Show browser window', default: true, type: 'confirm',
222
224
  },
225
+ {
226
+ name: 'windowSize', message: 'Browser viewport size', default: '1200x900',
227
+ },
223
228
  ];
224
229
  }
225
230
 
@@ -227,7 +232,7 @@ class Puppeteer extends Helper {
227
232
  try {
228
233
  requireg('puppeteer');
229
234
  } catch (e) {
230
- return ['puppeteer@^1.6.0'];
235
+ return ['puppeteer@^3.0.1'];
231
236
  }
232
237
  }
233
238
 
@@ -243,6 +248,7 @@ class Puppeteer extends Helper {
243
248
 
244
249
 
245
250
  async _before() {
251
+ this.sessionPages = {};
246
252
  recorder.retry({
247
253
  retries: 5,
248
254
  when: err => err.message.indexOf('context') > -1, // ignore context errors
@@ -257,7 +263,8 @@ class Puppeteer extends Helper {
257
263
 
258
264
  // close other sessions
259
265
  const contexts = this.browser.browserContexts();
260
- contexts.shift();
266
+ const defaultCtx = contexts.shift();
267
+
261
268
  await Promise.all(contexts.map(c => c.close()));
262
269
 
263
270
  if (this.options.restart) {
@@ -265,6 +272,12 @@ class Puppeteer extends Helper {
265
272
  return this._stopBrowser();
266
273
  }
267
274
 
275
+ // ensure this.page is from default context
276
+ if (this.page) {
277
+ const existingPages = defaultCtx.targets().filter(t => t.type() === 'page');
278
+ await this._setPage(await existingPages[0].page());
279
+ }
280
+
268
281
  if (this.options.keepBrowserState) return;
269
282
 
270
283
  if (!this.options.keepCookies) {
@@ -291,11 +304,13 @@ class Puppeteer extends Helper {
291
304
 
292
305
  _session() {
293
306
  return {
294
- start: async () => {
307
+ start: async (name = '', config) => {
295
308
  this.debugSection('Incognito Tab', 'opened');
309
+ this.activeSessionName = name;
296
310
 
297
311
  const bc = await this.browser.createIncognitoBrowserContext();
298
- await bc.newPage();
312
+ const page = await bc.newPage();
313
+
299
314
  // Create a new page inside context.
300
315
  return bc;
301
316
  },
@@ -304,12 +319,21 @@ class Puppeteer extends Helper {
304
319
  },
305
320
  loadVars: async (context) => {
306
321
  const existingPages = context.targets().filter(t => t.type() === 'page');
307
- return this._setPage(await existingPages[0].page());
322
+ this.sessionPages[this.activeSessionName] = await existingPages[0].page();
323
+ return this._setPage(this.sessionPages[this.activeSessionName]);
308
324
  },
309
- restoreVars: async () => {
325
+ restoreVars: async (session) => {
310
326
  this.withinLocator = null;
311
- const existingPages = await this.browser.targets().filter(t => t.type() === 'page');
327
+
328
+ if (!session) {
329
+ this.activeSessionName = '';
330
+ } else {
331
+ this.activeSessionName = session;
332
+ }
333
+ const defaultCtx = this.browser.defaultBrowserContext();
334
+ const existingPages = defaultCtx.targets().filter(t => t.type() === 'page');
312
335
  await this._setPage(await existingPages[0].page());
336
+
313
337
  return this._waitForAction();
314
338
  },
315
339
  };
@@ -363,6 +387,12 @@ class Puppeteer extends Helper {
363
387
  /**
364
388
  * Checks that the active JavaScript popup, as created by `window.alert|window.confirm|window.prompt`, contains the
365
389
  * given string.
390
+ *
391
+ * ```js
392
+ * I.seeInPopup('Popup text');
393
+ * ```
394
+ * @param {string} text value to check.
395
+ *
366
396
  */
367
397
  async seeInPopup(text) {
368
398
  popupStore.assertPopupVisible();
@@ -475,6 +505,7 @@ class Puppeteer extends Helper {
475
505
  this._setPage(null);
476
506
  this.context = null;
477
507
  popupStore.clear();
508
+ this.isAuthenticated = false;
478
509
  if (this.isRemoteBrowser) {
479
510
  await this.browser.disconnect();
480
511
  } else {
@@ -552,10 +583,10 @@ class Puppeteer extends Helper {
552
583
  url = this.options.url + url;
553
584
  }
554
585
 
555
- if (this.config.basicAuth && (isAuthenticated !== true)) {
586
+ if (this.config.basicAuth && (this.isAuthenticated !== true)) {
556
587
  if (url.includes(this.options.url)) {
557
- this.page.authenticate(this.config.basicAuth);
558
- isAuthenticated = true;
588
+ await this.page.authenticate(this.config.basicAuth);
589
+ this.isAuthenticated = true;
559
590
  }
560
591
  }
561
592
 
@@ -1056,6 +1087,63 @@ class Puppeteer extends Helper {
1056
1087
  return proceedClick.call(this, locator, context);
1057
1088
  }
1058
1089
 
1090
+ /**
1091
+ * Perform an emulated click on a link or a button, given by a locator.
1092
+ * Unlike normal click instead of sending native event, emulates a click with JavaScript.
1093
+ * This works on hidden, animated or inactive elements as well.
1094
+ *
1095
+ * If a fuzzy locator is given, the page will be searched for a button, link, or image matching the locator string.
1096
+ * For buttons, the "value" attribute, "name" attribute, and inner text are searched. For links, the link text is searched.
1097
+ * For images, the "alt" attribute and inner text of any parent links are searched.
1098
+ *
1099
+ * The second parameter is a context (CSS or XPath locator) to narrow the search.
1100
+ *
1101
+ * ```js
1102
+ * // simple link
1103
+ * I.forceClick('Logout');
1104
+ * // button of form
1105
+ * I.forceClick('Submit');
1106
+ * // CSS button
1107
+ * I.forceClick('#form input[type=submit]');
1108
+ * // XPath
1109
+ * I.forceClick('//form/*[@type=submit]');
1110
+ * // link in context
1111
+ * I.forceClick('Logout', '#nav');
1112
+ * // using strict locator
1113
+ * I.forceClick({css: 'nav a.login'});
1114
+ * ```
1115
+ *
1116
+ * @param {CodeceptJS.LocatorOrString} locator clickable link or button located by text, or any element located by CSS|XPath|strict locator.
1117
+ * @param {?CodeceptJS.LocatorOrString} [context=null] (optional, `null` by default) element to search in CSS|XPath|Strict locator.
1118
+ *
1119
+ *
1120
+ * {{ react }}
1121
+ */
1122
+ async forceClick(locator, context = null) {
1123
+ let matcher = await this.context;
1124
+ if (context) {
1125
+ const els = await this._locate(context);
1126
+ assertElementExists(els, context);
1127
+ matcher = els[0];
1128
+ }
1129
+
1130
+ const els = await findClickable.call(this, matcher, locator);
1131
+ if (context) {
1132
+ assertElementExists(els, locator, 'Clickable element', `was not found inside element ${new Locator(context).toString()}`);
1133
+ } else {
1134
+ assertElementExists(els, locator, 'Clickable element');
1135
+ }
1136
+ const elem = els[0];
1137
+ return this.executeScript((el) => {
1138
+ if (document.activeElement instanceof HTMLElement) {
1139
+ document.activeElement.blur();
1140
+ }
1141
+ const event = document.createEvent('MouseEvent');
1142
+ event.initEvent('click', true, true);
1143
+ return el.dispatchEvent(event);
1144
+ }, elem);
1145
+ }
1146
+
1059
1147
  /**
1060
1148
  * Performs a click on a link and waits for navigation before moving on.
1061
1149
  *
@@ -1533,6 +1621,8 @@ class Puppeteer extends Helper {
1533
1621
  *
1534
1622
  * @param {CodeceptJS.LocatorOrString} locator field located by label|name|CSS|XPath|strict locator.
1535
1623
  * @param {string} pathToFile local file path relative to codecept.json config file.
1624
+ *
1625
+ * > ⚠ There is an [issue with file upload in Puppeteer 2.1.0 & 2.1.1](https://github.com/puppeteer/puppeteer/issues/5420), downgrade to 2.0.0 if you face it.
1536
1626
  */
1537
1627
  async attachFile(locator, pathToFile) {
1538
1628
  const file = path.join(global.codecept_dir, pathToFile);
@@ -1831,13 +1921,21 @@ class Puppeteer extends Helper {
1831
1921
  }
1832
1922
 
1833
1923
  /**
1834
- * Sets a cookie.
1924
+ * Sets cookie(s).
1925
+ *
1926
+ * Can be a single cookie object or an array of cookies:
1835
1927
  *
1836
1928
  * ```js
1837
1929
  * I.setCookie({name: 'auth', value: true});
1930
+ *
1931
+ * // as array
1932
+ * I.setCookie([
1933
+ * {name: 'auth', value: true},
1934
+ * {name: 'agree', value: true}
1935
+ * ]);
1838
1936
  * ```
1839
1937
  *
1840
- * @param {object} cookie a cookie object.
1938
+ * @param {object|array} cookie a cookie object or array of cookie objects.
1841
1939
  */
1842
1940
  async setCookie(cookie) {
1843
1941
  if (Array.isArray(cookie)) {
@@ -2248,10 +2346,19 @@ class Puppeteer extends Helper {
2248
2346
  const outputFile = screenshotOutputFolder(fileName);
2249
2347
 
2250
2348
  this.debug(`Screenshot is saving to ${outputFile}`);
2251
- const openSessions = await this.browser.pages();
2252
- if (openSessions.length > 1) {
2253
- this.page = await this.browser.targets()[this.browser.targets().length - 1].page();
2349
+
2350
+ if (this.activeSessionName) {
2351
+ const activeSessionPage = this.sessionPages[this.activeSessionName];
2352
+
2353
+ if (activeSessionPage) {
2354
+ return activeSessionPage.screenshot({
2355
+ path: outputFile,
2356
+ fullPage: fullPageOption,
2357
+ type: 'png',
2358
+ });
2359
+ }
2254
2360
  }
2361
+
2255
2362
  return this.page.screenshot({ path: outputFile, fullPage: fullPageOption, type: 'png' });
2256
2363
  }
2257
2364
 
@@ -2626,7 +2733,7 @@ class Puppeteer extends Helper {
2626
2733
  }, { timeout: waitTimeout }, locator.value, text, $XPath.toString());
2627
2734
  }
2628
2735
  } else {
2629
- waiter = contextObject.waitForFunction(text => document.body.innerText.indexOf(text) > -1, { timeout: waitTimeout }, text);
2736
+ waiter = contextObject.waitForFunction(text => document.body && document.body.innerText.indexOf(text) > -1, { timeout: waitTimeout }, text);
2630
2737
  }
2631
2738
 
2632
2739
  return waiter.catch((err) => {
@@ -75,7 +75,9 @@ class REST extends Helper {
75
75
  }
76
76
 
77
77
  if ((typeof request.data) === 'string') {
78
- request.headers = Object.assign(request.headers, { 'Content-Type': 'application/x-www-form-urlencoded' });
78
+ if (!request.headers || !request.headers['Content-Type']) {
79
+ request.headers = Object.assign(request.headers, { 'Content-Type': 'application/x-www-form-urlencoded' });
80
+ }
79
81
  }
80
82
 
81
83
  if (this.config.onRequest) {
@@ -1339,13 +1339,21 @@ class TestCafe extends Helper {
1339
1339
  // TODO Add url assertions
1340
1340
 
1341
1341
  /**
1342
- * Sets a cookie.
1342
+ * Sets cookie(s).
1343
+ *
1344
+ * Can be a single cookie object or an array of cookies:
1343
1345
  *
1344
1346
  * ```js
1345
1347
  * I.setCookie({name: 'auth', value: true});
1348
+ *
1349
+ * // as array
1350
+ * I.setCookie([
1351
+ * {name: 'auth', value: true},
1352
+ * {name: 'agree', value: true}
1353
+ * ]);
1346
1354
  * ```
1347
1355
  *
1348
- * @param {object} cookie a cookie object.
1356
+ * @param {object|array} cookie a cookie object or array of cookie objects.
1349
1357
  */
1350
1358
  async setCookie(cookie) {
1351
1359
  if (Array.isArray(cookie)) {
@@ -1747,7 +1755,7 @@ async function proceedClick(locator, context = null) {
1747
1755
  await assertElementExists(els, locator, 'Clickable element');
1748
1756
  }
1749
1757
 
1750
- const firstElement = await els.nth(0);
1758
+ const firstElement = await els.filterVisible().nth(0);
1751
1759
 
1752
1760
  return this.t
1753
1761
  .click(firstElement)