codeceptjs 3.5.9 → 3.5.11

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 (52) hide show
  1. package/README.md +14 -16
  2. package/docs/build/Appium.js +49 -49
  3. package/docs/build/Expect.js +33 -33
  4. package/docs/build/Nightmare.js +50 -50
  5. package/docs/build/Playwright.js +239 -133
  6. package/docs/build/Protractor.js +59 -59
  7. package/docs/build/Puppeteer.js +127 -107
  8. package/docs/build/TestCafe.js +48 -48
  9. package/docs/build/WebDriver.js +112 -93
  10. package/docs/helpers/Appium.md +3 -3
  11. package/docs/helpers/Expect.md +33 -33
  12. package/docs/helpers/Playwright.md +431 -325
  13. package/docs/helpers/Puppeteer.md +50 -24
  14. package/docs/helpers/WebDriver.md +41 -13
  15. package/docs/internal-api.md +1 -0
  16. package/docs/parallel.md +114 -2
  17. package/docs/plugins.md +7 -5
  18. package/docs/react.md +2 -1
  19. package/docs/vue.md +22 -0
  20. package/docs/webapi/grabWebElement.mustache +9 -0
  21. package/docs/webapi/grabWebElements.mustache +9 -0
  22. package/docs/webapi/scrollIntoView.mustache +1 -1
  23. package/lib/ai.js +12 -3
  24. package/lib/colorUtils.js +10 -0
  25. package/lib/command/run-multiple.js +1 -1
  26. package/lib/command/run-workers.js +30 -4
  27. package/lib/command/workers/runTests.js +39 -0
  28. package/lib/event.js +2 -0
  29. package/lib/helper/Appium.js +13 -13
  30. package/lib/helper/Expect.js +33 -33
  31. package/lib/helper/Playwright.js +125 -37
  32. package/lib/helper/Puppeteer.js +49 -38
  33. package/lib/helper/WebDriver.js +29 -19
  34. package/lib/helper/extras/PlaywrightReactVueLocator.js +38 -0
  35. package/lib/html.js +3 -3
  36. package/lib/interfaces/gherkin.js +8 -1
  37. package/lib/interfaces/scenarioConfig.js +1 -0
  38. package/lib/locator.js +2 -2
  39. package/lib/pause.js +6 -3
  40. package/lib/plugin/autoLogin.js +4 -2
  41. package/lib/plugin/heal.js +40 -7
  42. package/lib/plugin/retryFailedStep.js +6 -1
  43. package/lib/plugin/stepByStepReport.js +2 -2
  44. package/lib/plugin/tryTo.js +5 -4
  45. package/lib/recorder.js +12 -5
  46. package/lib/ui.js +1 -0
  47. package/lib/workers.js +2 -0
  48. package/package.json +28 -25
  49. package/typings/index.d.ts +1 -1
  50. package/typings/promiseBasedTypes.d.ts +195 -76
  51. package/typings/types.d.ts +191 -145
  52. package/lib/helper/extras/PlaywrightReact.js +0 -9
@@ -33,7 +33,7 @@ const ElementNotFound = require('./errors/ElementNotFound');
33
33
  const RemoteBrowserConnectionRefused = require('./errors/RemoteBrowserConnectionRefused');
34
34
  const Popup = require('./extras/Popup');
35
35
  const Console = require('./extras/Console');
36
- const findReact = require('./extras/React');
36
+ const { findReact, findVue } = require('./extras/PlaywrightReactVueLocator');
37
37
 
38
38
  let playwright;
39
39
  let perfTiming;
@@ -94,6 +94,7 @@ const pathSeparator = path.sep;
94
94
  * @prop {boolean} [ignoreHTTPSErrors] - Allows access to untrustworthy pages, e.g. to a page with an expired certificate. Default value is `false`
95
95
  * @prop {boolean} [bypassCSP] - bypass Content Security Policy or CSP
96
96
  * @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose).
97
+ * @prop {object} [recordHar] - record HAR and will be saved to `output/har`. See more of [HAR options](https://playwright.dev/docs/api/class-browser#browser-new-context-option-record-har).
97
98
  */
98
99
  const config = {};
99
100
 
@@ -141,6 +142,21 @@ const config = {};
141
142
  * * `trace`: enables trace recording for failed tests; trace are saved into `output/trace` folder
142
143
  * * `keepTraceForPassedTests`: - save trace for passed tests
143
144
  *
145
+ * #### HAR Recording Customization
146
+ *
147
+ * A HAR file is an HTTP Archive file that contains a record of all the network requests that are made when a page is loaded.
148
+ * It contains information about the request and response headers, cookies, content, timings, and more. You can use HAR files to mock network requests in your tests.
149
+ * HAR will be saved to `output/har`. More info could be found here https://playwright.dev/docs/api/class-browser#browser-new-context-option-record-har.
150
+ *
151
+ * ```
152
+ * ...
153
+ * recordHar: {
154
+ * mode: 'minimal', // possible values: 'minimal'|'full'.
155
+ * content: 'embed' // possible values: "omit"|"embed"|"attach".
156
+ * }
157
+ * ...
158
+ *```
159
+ *
144
160
  * #### Example #1: Wait for 0 network connections.
145
161
  *
146
162
  * ```js
@@ -346,7 +362,7 @@ class Playwright extends Helper {
346
362
  ignoreLog: ['warning', 'log'],
347
363
  uniqueScreenshotNames: false,
348
364
  manualStart: false,
349
- getPageTimeout: 0,
365
+ getPageTimeout: 30000,
350
366
  waitForNavigation: 'load',
351
367
  restart: false,
352
368
  keepCookies: false,
@@ -455,9 +471,10 @@ class Playwright extends Helper {
455
471
  }
456
472
  }
457
473
 
458
- async _before() {
474
+ async _before(test) {
475
+ this.currentRunningTest = test;
459
476
  recorder.retry({
460
- retries: 5,
477
+ retries: process.env.FAILED_STEP_RETRIES || 3,
461
478
  when: err => {
462
479
  if (!err || typeof (err.message) !== 'string') {
463
480
  return false;
@@ -487,6 +504,15 @@ class Playwright extends Helper {
487
504
  }
488
505
  if (this.options.bypassCSP) contextOptions.bypassCSP = this.options.bypassCSP;
489
506
  if (this.options.recordVideo) contextOptions.recordVideo = this.options.recordVideo;
507
+ if (this.options.recordHar) {
508
+ const harExt = this.options.recordHar.content && this.options.recordHar.content === 'attach' ? 'zip' : 'har';
509
+ const fileName = `${`${global.output_dir}${path.sep}har${path.sep}${uuidv4()}_${clearString(this.currentRunningTest.title)}`.slice(0, 245)}.${harExt}`;
510
+ const dir = path.dirname(fileName);
511
+ if (!fileExists(dir)) fs.mkdirSync(dir);
512
+ this.options.recordHar.path = fileName;
513
+ this.currentRunningTest.artifacts.har = fileName;
514
+ contextOptions.recordHar = this.options.recordHar;
515
+ }
490
516
  if (this.storageState) contextOptions.storageState = this.storageState;
491
517
  if (this.options.userAgent) contextOptions.userAgent = this.options.userAgent;
492
518
  if (this.options.locale) contextOptions.locale = this.options.locale;
@@ -640,7 +666,7 @@ class Playwright extends Helper {
640
666
  * ```
641
667
  *
642
668
  * @param {string} description used to show in logs.
643
- * @param {function} fn async function that executed with Playwright helper as argumen
669
+ * @param {function} fn async function that executed with Playwright helper as arguments
644
670
  */
645
671
  usePlaywrightTo(description, fn) {
646
672
  return this._useTo(...arguments);
@@ -834,6 +860,7 @@ class Playwright extends Helper {
834
860
  this.context = null;
835
861
  this.frame = null;
836
862
  popupStore.clear();
863
+ if (this.options.recordHar) await this.browserContext.close();
837
864
  await this.browser.close();
838
865
  }
839
866
 
@@ -919,10 +946,9 @@ class Playwright extends Helper {
919
946
  }
920
947
 
921
948
  /**
922
- * {{> resizeWindow }}
923
949
  *
924
950
  * Unlike other drivers Playwright changes the size of a viewport, not the window!
925
- * Playwright does not control the window of a browser so it can't adjust its real size.
951
+ * Playwright does not control the window of a browser, so it can't adjust its real size.
926
952
  * It also can't maximize a window.
927
953
  *
928
954
  * Update configuration to change real window size on start:
@@ -932,6 +958,8 @@ class Playwright extends Helper {
932
958
  * // @codeceptjs/configure package must be installed
933
959
  * { setWindowSize } = require('@codeceptjs/configure');
934
960
  * ````
961
+ *
962
+ * {{> resizeWindow }}
935
963
  */
936
964
  async resizeWindow(width, height) {
937
965
  if (width === 'maximize') {
@@ -1031,8 +1059,6 @@ class Playwright extends Helper {
1031
1059
  }
1032
1060
 
1033
1061
  /**
1034
- * {{> dragAndDrop }}
1035
- * @param {any} [options] [Additional options](https://playwright.dev/docs/api/class-page#page-drag-and-drop) can be passed as 3rd argument.
1036
1062
  *
1037
1063
  * ```js
1038
1064
  * // specify coordinates for source position
@@ -1040,6 +1066,10 @@ class Playwright extends Helper {
1040
1066
  * ```
1041
1067
  *
1042
1068
  * > When no option is set, custom drag and drop would be used, to use the dragAndDrop API from Playwright, please set options, for example `force: true`
1069
+ *
1070
+ * {{> dragAndDrop }}
1071
+ * @param {any} [options] [Additional options](https://playwright.dev/docs/api/class-page#page-drag-and-drop) can be passed as 3rd argument.
1072
+ *
1043
1073
  */
1044
1074
  async dragAndDrop(srcElement, destElement, options) {
1045
1075
  const src = new Locator(srcElement);
@@ -1089,6 +1119,33 @@ class Playwright extends Helper {
1089
1119
  return this.page.reload({ timeout: this.options.getPageTimeout, waitUntil: this.options.waitForNavigation });
1090
1120
  }
1091
1121
 
1122
+ /**
1123
+ * Replaying from HAR
1124
+ *
1125
+ * ```js
1126
+ * // Replay API requests from HAR.
1127
+ * // Either use a matching response from the HAR,
1128
+ * // or abort the request if nothing matches.
1129
+ * I.replayFromHar('./output/har/something.har', { url: "*\/**\/api/v1/fruits" });
1130
+ * I.amOnPage('https://demo.playwright.dev/api-mocking');
1131
+ * I.see('CodeceptJS');
1132
+ * ```
1133
+ *
1134
+ * @param {string} harFilePath Path to recorded HAR file
1135
+ * @param {object} [opts] [Options for replaying from HAR](https://playwright.dev/docs/api/class-page#page-route-from-har)
1136
+ *
1137
+ * @returns Promise<void>
1138
+ */
1139
+ async replayFromHar(harFilePath, opts) {
1140
+ const file = path.join(global.codecept_dir, harFilePath);
1141
+
1142
+ if (!fileExists(file)) {
1143
+ throw new Error(`File at ${file} cannot be found on local system`);
1144
+ }
1145
+
1146
+ await this.page.routeFromHAR(harFilePath, opts);
1147
+ }
1148
+
1092
1149
  /**
1093
1150
  * {{> scrollPageToTop }}
1094
1151
  */
@@ -1250,6 +1307,22 @@ class Playwright extends Helper {
1250
1307
  return findFields.call(this, locator);
1251
1308
  }
1252
1309
 
1310
+ /**
1311
+ * {{> grabWebElements }}
1312
+ *
1313
+ */
1314
+ async grabWebElements(locator) {
1315
+ return this._locate(locator);
1316
+ }
1317
+
1318
+ /**
1319
+ * {{> grabWebElement }}
1320
+ *
1321
+ */
1322
+ async grabWebElement(locator) {
1323
+ return this._locateElement(locator);
1324
+ }
1325
+
1253
1326
  /**
1254
1327
  * Switch focus to a particular tab by its number. It waits tabs loading and then switch tab
1255
1328
  *
@@ -1444,7 +1517,7 @@ class Playwright extends Helper {
1444
1517
  *
1445
1518
  * @param {any} [options] [Additional options](https://playwright.dev/docs/api/class-page#page-click) for click available as 3rd argument.
1446
1519
  *
1447
- * Examples:
1520
+ * @example
1448
1521
  *
1449
1522
  * ```js
1450
1523
  * // click on element at position
@@ -1477,8 +1550,6 @@ class Playwright extends Helper {
1477
1550
 
1478
1551
  /**
1479
1552
  * {{> doubleClick }}
1480
- *
1481
- *
1482
1553
  */
1483
1554
  async doubleClick(locator, context = null) {
1484
1555
  return proceedClick.call(this, locator, context, { clickCount: 2 });
@@ -1486,15 +1557,12 @@ class Playwright extends Helper {
1486
1557
 
1487
1558
  /**
1488
1559
  * {{> rightClick }}
1489
- *
1490
- *
1491
1560
  */
1492
1561
  async rightClick(locator, context = null) {
1493
1562
  return proceedClick.call(this, locator, context, { button: 'right' });
1494
1563
  }
1495
1564
 
1496
1565
  /**
1497
- * {{> checkOption }}
1498
1566
  *
1499
1567
  * [Additional options](https://playwright.dev/docs/api/class-elementhandle#element-handle-check) for check available as 3rd argument.
1500
1568
  *
@@ -1505,6 +1573,9 @@ class Playwright extends Helper {
1505
1573
  * I.checkOption('Agree', '.signup', { position: { x: 5, y: 5 } })
1506
1574
  * ```
1507
1575
  * > ⚠️ To avoid flakiness, option `force: true` is set by default
1576
+ *
1577
+ * {{> checkOption }}
1578
+ *
1508
1579
  */
1509
1580
  async checkOption(field, context = null, options = { force: true }) {
1510
1581
  const elm = await this._locateCheckable(field, context);
@@ -1513,7 +1584,6 @@ class Playwright extends Helper {
1513
1584
  }
1514
1585
 
1515
1586
  /**
1516
- * {{> uncheckOption }}
1517
1587
  *
1518
1588
  * [Additional options](https://playwright.dev/docs/api/class-elementhandle#element-handle-uncheck) for uncheck available as 3rd argument.
1519
1589
  *
@@ -1524,6 +1594,8 @@ class Playwright extends Helper {
1524
1594
  * I.uncheckOption('Agree', '.signup', { position: { x: 5, y: 5 } })
1525
1595
  * ```
1526
1596
  * > ⚠️ To avoid flakiness, option `force: true` is set by default
1597
+ *
1598
+ * {{> uncheckOption }}
1527
1599
  */
1528
1600
  async uncheckOption(field, context = null, options = { force: true }) {
1529
1601
  const elm = await this._locateCheckable(field, context);
@@ -1564,9 +1636,10 @@ class Playwright extends Helper {
1564
1636
  }
1565
1637
 
1566
1638
  /**
1567
- * {{> pressKeyWithKeyNormalization }}
1568
1639
  *
1569
1640
  * _Note:_ Shortcuts like `'Meta'` + `'A'` do not work on macOS ([GoogleChrome/Puppeteer#1313](https://github.com/GoogleChrome/puppeteer/issues/1313)).
1641
+ *
1642
+ * {{> pressKeyWithKeyNormalization }}
1570
1643
  */
1571
1644
  async pressKey(key) {
1572
1645
  const modifiers = [];
@@ -1658,8 +1731,6 @@ class Playwright extends Helper {
1658
1731
 
1659
1732
  /**
1660
1733
  * {{> appendField }}
1661
- *
1662
- *
1663
1734
  */
1664
1735
  async appendField(field, value) {
1665
1736
  const els = await findFields.call(this, field);
@@ -1711,8 +1782,15 @@ class Playwright extends Helper {
1711
1782
  const el = els[0];
1712
1783
 
1713
1784
  await highlightActiveElement.call(this, el);
1785
+ let optionToSelect = '';
1714
1786
 
1715
- if (!Array.isArray(option)) option = [option];
1787
+ try {
1788
+ optionToSelect = await el.locator('option', { hasText: option }).textContent();
1789
+ } catch (e) {
1790
+ optionToSelect = option;
1791
+ }
1792
+
1793
+ if (!Array.isArray(option)) option = [optionToSelect];
1716
1794
 
1717
1795
  await el.selectOption(option);
1718
1796
  return this._waitForAction();
@@ -1876,9 +1954,9 @@ class Playwright extends Helper {
1876
1954
  }
1877
1955
 
1878
1956
  /**
1879
- * {{> grabCookie }}
1880
- *
1881
1957
  * Returns cookie in JSON format. If name not passed returns all cookies for this domain.
1958
+ *
1959
+ * {{> grabCookie }}
1882
1960
  */
1883
1961
  async grabCookie(name) {
1884
1962
  const cookies = await this.browserContext.cookies();
@@ -1909,8 +1987,8 @@ class Playwright extends Helper {
1909
1987
  * ```js
1910
1988
  * I.executeScript(({x, y}) => x + y, {x, y});
1911
1989
  * ```
1912
- * You can pass only one parameter into a function
1913
- * but you can pass in array or object.
1990
+ * You can pass only one parameter into a function,
1991
+ * or you can pass in array or object.
1914
1992
  *
1915
1993
  * ```js
1916
1994
  * I.executeScript(([x, y]) => x + y, [x, y]);
@@ -2042,19 +2120,17 @@ class Playwright extends Helper {
2042
2120
 
2043
2121
  const cssPropertiesCamelCase = convertCssPropertiesToCamelCase(cssProperties);
2044
2122
  const elemAmount = res.length;
2045
- const commands = [];
2046
2123
  let props = [];
2047
2124
 
2048
2125
  for (const element of res) {
2049
- const cssProperties = await element.evaluate((el) => getComputedStyle(el));
2050
-
2051
- Object.keys(cssPropertiesCamelCase).forEach(prop => {
2126
+ for (const prop of Object.keys(cssProperties)) {
2127
+ const cssProp = await this.grabCssPropertyFrom(locator, prop);
2052
2128
  if (isColorProperty(prop)) {
2053
- props.push(convertColorToRGBA(cssProperties[prop]));
2129
+ props.push(convertColorToRGBA(cssProp));
2054
2130
  } else {
2055
- props.push(cssProperties[prop]);
2131
+ props.push(cssProp);
2056
2132
  }
2057
- });
2133
+ }
2058
2134
  }
2059
2135
 
2060
2136
  const values = Object.keys(cssPropertiesCamelCase).map(key => cssPropertiesCamelCase[key]);
@@ -2062,7 +2138,8 @@ class Playwright extends Helper {
2062
2138
  let chunked = chunkArray(props, values.length);
2063
2139
  chunked = chunked.filter((val) => {
2064
2140
  for (let i = 0; i < val.length; ++i) {
2065
- if (val[i] !== values[i]) return false;
2141
+ // eslint-disable-next-line eqeqeq
2142
+ if (val[i] != values[i]) return false;
2066
2143
  }
2067
2144
  return true;
2068
2145
  });
@@ -2091,7 +2168,8 @@ class Playwright extends Helper {
2091
2168
  let chunked = chunkArray(attrs, values.length);
2092
2169
  chunked = chunked.filter((val) => {
2093
2170
  for (let i = 0; i < val.length; ++i) {
2094
- if (val[i] !== values[i]) return false;
2171
+ // if the attribute doesn't exist, returns false as well
2172
+ if (!val[i] || !val[i].includes(values[i])) return false;
2095
2173
  }
2096
2174
  return true;
2097
2175
  });
@@ -2262,6 +2340,10 @@ class Playwright extends Helper {
2262
2340
  test.artifacts[`trace_${sessionName}`] = await saveTraceForContext(this.sessionPages[sessionName].context, `${test.title}_${sessionName}.failed`);
2263
2341
  }
2264
2342
  }
2343
+
2344
+ if (this.options.recordHar) {
2345
+ test.artifacts.har = this.currentRunningTest.artifacts.har;
2346
+ }
2265
2347
  }
2266
2348
 
2267
2349
  async _passed(test) {
@@ -2289,6 +2371,10 @@ class Playwright extends Helper {
2289
2371
  await this.browserContext.tracing.stop();
2290
2372
  }
2291
2373
  }
2374
+
2375
+ if (this.options.recordHar) {
2376
+ test.artifacts.har = this.currentRunningTest.artifacts.har;
2377
+ }
2292
2378
  }
2293
2379
 
2294
2380
  /**
@@ -2409,9 +2495,9 @@ class Playwright extends Helper {
2409
2495
  }
2410
2496
 
2411
2497
  /**
2412
- * {{> waitForVisible }}
2413
- *
2414
2498
  * This method accepts [React selectors](https://codecept.io/react).
2499
+ *
2500
+ * {{> waitForVisible }}
2415
2501
  */
2416
2502
  async waitForVisible(locator, sec) {
2417
2503
  const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
@@ -2581,7 +2667,7 @@ class Playwright extends Helper {
2581
2667
  const _contextObject = this.frame ? this.frame : contextObject;
2582
2668
  let count = 0;
2583
2669
  do {
2584
- waiter = await _contextObject.locator(`:has-text('${text}')`).first().isVisible();
2670
+ waiter = await _contextObject.locator(`:has-text("${text}")`).first().isVisible();
2585
2671
  if (waiter) break;
2586
2672
  await this.wait(1);
2587
2673
  count += 1000;
@@ -3176,7 +3262,7 @@ class Playwright extends Helper {
3176
3262
  /**
3177
3263
  * Returns all URLs of all network requests recorded so far during execution of test scenario.
3178
3264
  *
3179
- * @return {string} List of URLs recorded as a string, seperaeted by new lines after each URL
3265
+ * @return {string} List of URLs recorded as a string, separated by new lines after each URL
3180
3266
  * @private
3181
3267
  */
3182
3268
  _getTrafficDump() {
@@ -3358,6 +3444,7 @@ function buildLocatorString(locator) {
3358
3444
 
3359
3445
  async function findElements(matcher, locator) {
3360
3446
  if (locator.react) return findReact(matcher, locator);
3447
+ if (locator.vue) return findVue(matcher, locator);
3361
3448
  locator = new Locator(locator, 'css');
3362
3449
 
3363
3450
  return matcher.locator(buildLocatorString(locator)).all();
@@ -3419,6 +3506,7 @@ async function proceedClick(locator, context = null, options = {}) {
3419
3506
 
3420
3507
  async function findClickable(matcher, locator) {
3421
3508
  if (locator.react) return findReact(matcher, locator);
3509
+ if (locator.vue) return findVue(matcher, locator);
3422
3510
 
3423
3511
  locator = new Locator(locator);
3424
3512
  if (!locator.isFuzzy()) return findElements.call(this, matcher, locator);
@@ -259,6 +259,7 @@ class Puppeteer extends Helper {
259
259
  headless: !this.options.show,
260
260
  ...this._getOptions(config),
261
261
  };
262
+ if (this.puppeteerOptions.headless) this.puppeteerOptions.headless = 'new';
262
263
  this.isRemoteBrowser = !!this.puppeteerOptions.browserWSEndpoint;
263
264
  popupStore.defaultAction = this.options.defaultPopupAction;
264
265
  }
@@ -297,7 +298,7 @@ class Puppeteer extends Helper {
297
298
  this.sessionPages = {};
298
299
  this.currentRunningTest = test;
299
300
  recorder.retry({
300
- retries: 3,
301
+ retries: process.env.FAILED_STEP_RETRIES || 3,
301
302
  when: err => {
302
303
  if (!err || typeof (err.message) !== 'string') {
303
304
  return false;
@@ -684,11 +685,13 @@ class Puppeteer extends Helper {
684
685
  }
685
686
 
686
687
  /**
687
- * {{> resizeWindow }}
688
688
  *
689
689
  * Unlike other drivers Puppeteer changes the size of a viewport, not the window!
690
- * Puppeteer does not control the window of a browser so it can't adjust its real size.
690
+ * Puppeteer does not control the window of a browser, so it can't adjust its real size.
691
691
  * It also can't maximize a window.
692
+ *
693
+ * {{> resizeWindow }}
694
+ *
692
695
  */
693
696
  async resizeWindow(width, height) {
694
697
  if (width === 'maximize') {
@@ -740,7 +743,8 @@ class Puppeteer extends Helper {
740
743
  assertElementExists(els, locator, 'Element to focus');
741
744
  const el = els[0];
742
745
 
743
- await focusElement(el, this.page);
746
+ await el.click();
747
+ await el.focus();
744
748
  return this._waitForAction();
745
749
  }
746
750
 
@@ -880,7 +884,7 @@ class Puppeteer extends Helper {
880
884
  }
881
885
 
882
886
  /**
883
- * Find a checkbox by providing human readable text:
887
+ * Find a checkbox by providing human-readable text:
884
888
  * NOTE: Assumes the checkable element exists
885
889
  *
886
890
  * ```js
@@ -895,7 +899,7 @@ class Puppeteer extends Helper {
895
899
  }
896
900
 
897
901
  /**
898
- * Find a clickable element by providing human readable text:
902
+ * Find a clickable element by providing human-readable text:
899
903
  *
900
904
  * ```js
901
905
  * this.helpers['Puppeteer']._locateClickable('Next page').then // ...
@@ -907,7 +911,7 @@ class Puppeteer extends Helper {
907
911
  }
908
912
 
909
913
  /**
910
- * Find field elements by providing human readable text:
914
+ * Find field elements by providing human-readable text:
911
915
  *
912
916
  * ```js
913
917
  * this.helpers['Puppeteer']._locateFields('Your email').then // ...
@@ -917,6 +921,14 @@ class Puppeteer extends Helper {
917
921
  return findFields.call(this, locator);
918
922
  }
919
923
 
924
+ /**
925
+ * {{> grabWebElements }}
926
+ *
927
+ */
928
+ async grabWebElements(locator) {
929
+ return this._locate(locator);
930
+ }
931
+
920
932
  /**
921
933
  * Switch focus to a particular tab by its number. It waits tabs loading and then switch tab
922
934
  *
@@ -1111,7 +1123,7 @@ class Puppeteer extends Helper {
1111
1123
  * Sets a directory to where save files. Allows to test file downloads.
1112
1124
  * Should be used with [FileSystem helper](https://codecept.io/helpers/FileSystem) to check that file were downloaded correctly.
1113
1125
  *
1114
- * By default files are saved to `output/downloads`.
1126
+ * By default, files are saved to `output/downloads`.
1115
1127
  * This directory is cleaned on every `handleDownloads` call, to ensure no old files are kept.
1116
1128
  *
1117
1129
  * ```js
@@ -1283,9 +1295,9 @@ class Puppeteer extends Helper {
1283
1295
  }
1284
1296
 
1285
1297
  /**
1286
- * {{> pressKeyWithKeyNormalization }}
1287
- *
1288
1298
  * _Note:_ Shortcuts like `'Meta'` + `'A'` do not work on macOS ([GoogleChrome/puppeteer#1313](https://github.com/GoogleChrome/puppeteer/issues/1313)).
1299
+ *
1300
+ * {{> pressKeyWithKeyNormalization }}
1289
1301
  */
1290
1302
  async pressKey(key) {
1291
1303
  const modifiers = [];
@@ -1387,9 +1399,9 @@ class Puppeteer extends Helper {
1387
1399
  }
1388
1400
 
1389
1401
  /**
1390
- * {{> attachFile }}
1391
- *
1392
1402
  * > ⚠ 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.
1403
+ *
1404
+ * {{> attachFile }}
1393
1405
  */
1394
1406
  async attachFile(locator, pathToFile) {
1395
1407
  const file = path.join(global.codecept_dir, pathToFile);
@@ -1620,9 +1632,9 @@ class Puppeteer extends Helper {
1620
1632
  }
1621
1633
 
1622
1634
  /**
1623
- * {{> executeScript }}
1635
+ * If a function returns a Promise, tt will wait for its resolution.
1624
1636
  *
1625
- * If a function returns a Promise It will wait for it resolution.
1637
+ * {{> executeScript }}
1626
1638
  */
1627
1639
  async executeScript(...args) {
1628
1640
  let context = this.page;
@@ -1633,9 +1645,8 @@ class Puppeteer extends Helper {
1633
1645
  }
1634
1646
 
1635
1647
  /**
1636
- * {{> executeAsyncScript }}
1637
- *
1638
1648
  * Asynchronous scripts can also be executed with `executeScript` if a function returns a Promise.
1649
+ * {{> executeAsyncScript }}
1639
1650
  */
1640
1651
  async executeAsyncScript(...args) {
1641
1652
  const asyncFn = function () {
@@ -1762,29 +1773,26 @@ class Puppeteer extends Helper {
1762
1773
 
1763
1774
  const cssPropertiesCamelCase = convertCssPropertiesToCamelCase(cssProperties);
1764
1775
  const elemAmount = res.length;
1765
- const commands = [];
1766
- res.forEach((el) => {
1767
- Object.keys(cssPropertiesCamelCase).forEach((prop) => {
1768
- commands.push(el.executionContext()
1769
- .evaluate((el) => {
1770
- const style = window.getComputedStyle ? getComputedStyle(el) : el.currentStyle;
1771
- return JSON.parse(JSON.stringify(style));
1772
- }, el)
1773
- .then((props) => {
1774
- if (isColorProperty(prop)) {
1775
- return convertColorToRGBA(props[prop]);
1776
- }
1777
- return props[prop];
1778
- }));
1779
- });
1780
- });
1781
- let props = await Promise.all(commands);
1776
+ let props = [];
1777
+
1778
+ for (const element of res) {
1779
+ for (const prop of Object.keys(cssProperties)) {
1780
+ const cssProp = await this.grabCssPropertyFrom(locator, prop);
1781
+ if (isColorProperty(prop)) {
1782
+ props.push(convertColorToRGBA(cssProp));
1783
+ } else {
1784
+ props.push(cssProp);
1785
+ }
1786
+ }
1787
+ }
1788
+
1782
1789
  const values = Object.keys(cssPropertiesCamelCase).map(key => cssPropertiesCamelCase[key]);
1783
1790
  if (!Array.isArray(props)) props = [props];
1784
1791
  let chunked = chunkArray(props, values.length);
1785
1792
  chunked = chunked.filter((val) => {
1786
1793
  for (let i = 0; i < val.length; ++i) {
1787
- if (val[i] !== values[i]) return false;
1794
+ // eslint-disable-next-line eqeqeq
1795
+ if (val[i] != values[i]) return false;
1788
1796
  }
1789
1797
  return true;
1790
1798
  });
@@ -1815,7 +1823,10 @@ class Puppeteer extends Helper {
1815
1823
  let chunked = chunkArray(attrs, values.length);
1816
1824
  chunked = chunked.filter((val) => {
1817
1825
  for (let i = 0; i < val.length; ++i) {
1818
- if (val[i] !== values[i]) return false;
1826
+ const _actual = Number.isNaN(val[i]) || (typeof values[i]) === 'string' ? val[i] : Number.parseInt(values[i], 10);
1827
+ const _expected = Number.isNaN(values[i]) || (typeof values[i]) === 'string' ? values[i] : Number.parseInt(values[i], 10);
1828
+ // if the attribute doesn't exist, returns false as well
1829
+ if (!_actual || !_actual.includes(_expected)) return false;
1819
1830
  }
1820
1831
  return true;
1821
1832
  });
@@ -2085,7 +2096,7 @@ class Puppeteer extends Helper {
2085
2096
  /**
2086
2097
  * {{> waitForVisible }}
2087
2098
  *
2088
- * This method accepts [React selectors](https://codecept.io/react).
2099
+ * {{ react }}
2089
2100
  */
2090
2101
  async waitForVisible(locator, sec) {
2091
2102
  const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
@@ -2314,9 +2325,9 @@ class Puppeteer extends Helper {
2314
2325
  }
2315
2326
 
2316
2327
  /**
2317
- * Waits for navigation to finish. By default takes configured `waitForNavigation` option.
2328
+ * Waits for navigation to finish. By default, takes configured `waitForNavigation` option.
2318
2329
  *
2319
- * See [Pupeteer's reference](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagewaitfornavigationoptions)
2330
+ * See [Puppeteer's reference](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagewaitfornavigationoptions)
2320
2331
  *
2321
2332
  * @param {*} opts
2322
2333
  */