codeceptjs 3.0.6 → 3.1.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 (62) hide show
  1. package/CHANGELOG.md +92 -8
  2. package/README.md +9 -1
  3. package/bin/codecept.js +28 -17
  4. package/docs/build/Appium.js +69 -0
  5. package/docs/build/GraphQL.js +9 -10
  6. package/docs/build/Playwright.js +271 -63
  7. package/docs/build/Protractor.js +2 -0
  8. package/docs/build/Puppeteer.js +56 -18
  9. package/docs/build/REST.js +16 -3
  10. package/docs/build/WebDriver.js +82 -16
  11. package/docs/changelog.md +93 -9
  12. package/docs/configuration.md +15 -2
  13. package/docs/email.md +8 -8
  14. package/docs/examples.md +3 -3
  15. package/docs/helpers/Appium.md +66 -68
  16. package/docs/helpers/MockRequest.md +3 -3
  17. package/docs/helpers/Playwright.md +269 -203
  18. package/docs/helpers/Puppeteer.md +17 -1
  19. package/docs/helpers/REST.md +23 -9
  20. package/docs/helpers/WebDriver.md +3 -2
  21. package/docs/locators.md +27 -0
  22. package/docs/mobile.md +2 -1
  23. package/docs/nightmare.md +0 -5
  24. package/docs/parallel.md +14 -7
  25. package/docs/playwright.md +178 -11
  26. package/docs/plugins.md +61 -69
  27. package/docs/react.md +1 -1
  28. package/docs/reports.md +5 -4
  29. package/lib/actor.js +1 -2
  30. package/lib/codecept.js +13 -2
  31. package/lib/command/definitions.js +8 -1
  32. package/lib/command/interactive.js +4 -2
  33. package/lib/command/run-multiple/collection.js +4 -0
  34. package/lib/container.js +3 -3
  35. package/lib/helper/Appium.js +41 -0
  36. package/lib/helper/GraphQL.js +9 -10
  37. package/lib/helper/Playwright.js +218 -70
  38. package/lib/helper/Protractor.js +2 -0
  39. package/lib/helper/Puppeteer.js +56 -18
  40. package/lib/helper/REST.js +12 -0
  41. package/lib/helper/WebDriver.js +82 -16
  42. package/lib/helper/errors/ConnectionRefused.js +1 -1
  43. package/lib/helper/extras/Popup.js +1 -1
  44. package/lib/helper/extras/React.js +44 -32
  45. package/lib/interfaces/gherkin.js +1 -0
  46. package/lib/listener/exit.js +2 -4
  47. package/lib/listener/helpers.js +3 -4
  48. package/lib/locator.js +7 -0
  49. package/lib/mochaFactory.js +11 -6
  50. package/lib/output.js +5 -2
  51. package/lib/plugin/allure.js +7 -18
  52. package/lib/plugin/commentStep.js +1 -1
  53. package/lib/plugin/{puppeteerCoverage.js → coverage.js} +10 -22
  54. package/lib/plugin/customLocator.js +2 -2
  55. package/lib/plugin/screenshotOnFail.js +5 -0
  56. package/lib/plugin/subtitles.js +88 -0
  57. package/lib/plugin/tryTo.js +1 -1
  58. package/lib/step.js +4 -2
  59. package/lib/ui.js +6 -2
  60. package/package.json +5 -4
  61. package/typings/index.d.ts +44 -21
  62. package/typings/types.d.ts +137 -16
@@ -129,8 +129,9 @@ const consoleLogStore = new Console();
129
129
  * }
130
130
  * }
131
131
  * ```
132
+ * > Note: When connecting to remote browser `show` and specific `chrome` options (e.g. `headless` or `devtools`) are ignored.
132
133
  *
133
- * #### Example #5: Target URL with provided basic authentication
134
+ * #### Example #5: Target URL with provided basic authentication
134
135
  *
135
136
  * ```js
136
137
  * {
@@ -143,10 +144,25 @@ const consoleLogStore = new Console();
143
144
  * }
144
145
  * }
145
146
  * ```
147
+ * #### Troubleshooting
146
148
  *
149
+ * Error Message: `No usable sandbox!`
150
+ *
151
+ * When running Puppeteer on CI try to disable sandbox if you see that message
152
+ *
153
+ * ```
154
+ * helpers: {
155
+ * Puppeteer: {
156
+ * url: 'http://localhost',
157
+ * show: false,
158
+ * chrome: {
159
+ * args: ['--no-sandbox', '--disable-setuid-sandbox']
160
+ * }
161
+ * },
162
+ * }
163
+ * ```
147
164
  *
148
165
  *
149
- * Note: When connecting to remote browser `show` and specific `chrome` options (e.g. `headless` or `devtools`) are ignored.
150
166
  *
151
167
  * ## Access From Helpers
152
168
  *
@@ -701,7 +717,7 @@ class Puppeteer extends Helper {
701
717
  assertElementExists(els);
702
718
 
703
719
  // Use manual mouse.move instead of .hover() so the offset can be added to the coordinates
704
- const { x, y } = await els[0]._clickablePoint();
720
+ const { x, y } = await getClickablePoint(els[0]);
705
721
  await this.page.mouse.move(x + offsetX, y + offsetY);
706
722
  return this._waitForAction();
707
723
  }
@@ -790,7 +806,7 @@ class Puppeteer extends Helper {
790
806
  const els = await this._locate(locator);
791
807
  assertElementExists(els, locator, 'Element');
792
808
  await els[0]._scrollIntoViewIfNeeded();
793
- const elementCoordinates = await els[0]._clickablePoint();
809
+ const elementCoordinates = await getClickablePoint(els[0]);
794
810
  await this.executeScript((x, y) => window.scrollBy(x, y), elementCoordinates.x + offsetX, elementCoordinates.y + offsetY);
795
811
  } else {
796
812
  await this.executeScript((x, y) => window.scrollTo(x, y), offsetX, offsetY);
@@ -1047,7 +1063,10 @@ class Puppeteer extends Helper {
1047
1063
  */
1048
1064
  async seeElement(locator) {
1049
1065
  let els = await this._locate(locator);
1050
- els = await Promise.all(els.map(el => el.boundingBox()));
1066
+ els = (await Promise.all(els.map(el => el.boundingBox() && el))).filter(v => v);
1067
+ // Puppeteer visibility was ignored? | Remove when Puppeteer is fixed
1068
+ els = await Promise.all(els.map(async el => (await el.evaluate(node => window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none')) && el));
1069
+
1051
1070
  return empty('visible elements').negate(els.filter(v => v).fill('ELEMENT'));
1052
1071
  }
1053
1072
 
@@ -1063,7 +1082,10 @@ class Puppeteer extends Helper {
1063
1082
  */
1064
1083
  async dontSeeElement(locator) {
1065
1084
  let els = await this._locate(locator);
1066
- els = await Promise.all(els.map(el => el.boundingBox()));
1085
+ els = (await Promise.all(els.map(el => el.boundingBox() && el))).filter(v => v);
1086
+ // Puppeteer visibility was ignored? | Remove when Puppeteer is fixed
1087
+ els = await Promise.all(els.map(async el => (await el.evaluate(node => window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none')) && el));
1088
+
1067
1089
  return empty('visible elements').assert(els.filter(v => v).fill('ELEMENT'));
1068
1090
  }
1069
1091
 
@@ -1598,7 +1620,7 @@ class Puppeteer extends Helper {
1598
1620
  * {{ react }}
1599
1621
  */
1600
1622
  async fillField(field, value) {
1601
- const els = await findFields.call(this, field);
1623
+ const els = await findVisibleFields.call(this, field);
1602
1624
  assertElementExists(els, field, 'Field');
1603
1625
  const el = els[0];
1604
1626
  const tag = await el.getProperty('tagName').then(el => el.jsonValue());
@@ -1640,7 +1662,7 @@ class Puppeteer extends Helper {
1640
1662
  * {{ react }}
1641
1663
  */
1642
1664
  async appendField(field, value) {
1643
- const els = await findFields.call(this, field);
1665
+ const els = await findVisibleFields.call(this, field);
1644
1666
  assertElementExists(els, field, 'Field');
1645
1667
  await els[0].press('End');
1646
1668
  await els[0].type(value, { delay: this.options.pressKeyDelay });
@@ -1732,7 +1754,7 @@ class Puppeteer extends Helper {
1732
1754
  *
1733
1755
  */
1734
1756
  async selectOption(select, option) {
1735
- const els = await findFields.call(this, select);
1757
+ const els = await findVisibleFields.call(this, select);
1736
1758
  assertElementExists(els, select, 'Selectable field');
1737
1759
  const el = els[0];
1738
1760
  if (await el.getProperty('tagName').then(t => t.jsonValue()) !== 'SELECT') {
@@ -1774,7 +1796,10 @@ class Puppeteer extends Helper {
1774
1796
  */
1775
1797
  async grabNumberOfVisibleElements(locator) {
1776
1798
  let els = await this._locate(locator);
1777
- els = await Promise.all(els.map(el => el.boundingBox()));
1799
+ els = (await Promise.all(els.map(el => el.boundingBox() && el))).filter(v => v);
1800
+ // Puppeteer visibility was ignored? | Remove when Puppeteer is fixed
1801
+ els = await Promise.all(els.map(async el => (await el.evaluate(node => window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none')) && el));
1802
+
1778
1803
  return els.filter(v => v).length;
1779
1804
  }
1780
1805
 
@@ -2452,8 +2477,8 @@ class Puppeteer extends Helper {
2452
2477
  const src = await this._locate(locator);
2453
2478
  assertElementExists(src, locator, 'Slider Element');
2454
2479
 
2455
- // Note: Using private api ._clickablePoint because the .BoundingBox does not take into account iframe offsets!
2456
- const sliderSource = await src[0]._clickablePoint();
2480
+ // Note: Using public api .getClickablePoint because the .BoundingBox does not take into account iframe offsets
2481
+ const sliderSource = await getClickablePoint(src[0]);
2457
2482
 
2458
2483
  // Drag start point
2459
2484
  await this.page.mouse.move(sliderSource.x, sliderSource.y, { steps: 5 });
@@ -3202,7 +3227,7 @@ class Puppeteer extends Helper {
3202
3227
  module.exports = Puppeteer;
3203
3228
 
3204
3229
  async function findElements(matcher, locator) {
3205
- if (locator.react) return findReact(matcher, locator);
3230
+ if (locator.react) return findReact(matcher.executionContext(), locator);
3206
3231
  locator = new Locator(locator, 'css');
3207
3232
  if (!locator.isXPath()) return matcher.$$(locator.simplify());
3208
3233
  return matcher.$x(locator.value);
@@ -3231,7 +3256,7 @@ async function proceedClick(locator, context = null, options = {}) {
3231
3256
  }
3232
3257
 
3233
3258
  async function findClickable(matcher, locator) {
3234
- if (locator.react) return findReact(matcher, locator);
3259
+ if (locator.react) return findReact(matcher.executionContext(), locator);
3235
3260
  locator = new Locator(locator);
3236
3261
  if (!locator.isFuzzy()) return findElements.call(this, matcher, locator);
3237
3262
 
@@ -3314,6 +3339,12 @@ async function proceedIsChecked(assertType, option) {
3314
3339
  return truth(`checkable ${option}`, 'to be checked')[assertType](selected);
3315
3340
  }
3316
3341
 
3342
+ async function findVisibleFields(locator) {
3343
+ const els = await findFields.call(this, locator);
3344
+ const visible = await Promise.all(els.map(el => el.boundingBox()));
3345
+ return els.filter((el, index) => visible[index]);
3346
+ }
3347
+
3317
3348
  async function findFields(locator) {
3318
3349
  const matchedLocator = new Locator(locator);
3319
3350
  if (!matchedLocator.isFuzzy()) {
@@ -3344,9 +3375,9 @@ async function proceedDragAndDrop(sourceLocator, destinationLocator) {
3344
3375
  const dst = await this._locate(destinationLocator);
3345
3376
  assertElementExists(dst, destinationLocator, 'Destination Element');
3346
3377
 
3347
- // Note: Using private api ._clickablePoint becaues the .BoundingBox does not take into account iframe offsets!
3348
- const dragSource = await src[0]._clickablePoint();
3349
- const dragDestination = await dst[0]._clickablePoint();
3378
+ // Note: Using public api .getClickablePoint becaues the .BoundingBox does not take into account iframe offsets
3379
+ const dragSource = await getClickablePoint(src[0]);
3380
+ const dragDestination = await getClickablePoint(dst[0]);
3350
3381
 
3351
3382
  // Drag start point
3352
3383
  await this.page.mouse.move(dragSource.x, dragSource.y, { steps: 5 });
@@ -3360,7 +3391,7 @@ async function proceedDragAndDrop(sourceLocator, destinationLocator) {
3360
3391
  }
3361
3392
 
3362
3393
  async function proceedSeeInField(assertType, field, value) {
3363
- const els = await findFields.call(this, field);
3394
+ const els = await findVisibleFields.call(this, field);
3364
3395
  assertElementExists(els, field, 'Field');
3365
3396
  const el = els[0];
3366
3397
  const tag = await el.getProperty('tagName').then(el => el.jsonValue());
@@ -3493,6 +3524,13 @@ async function targetCreatedHandler(page) {
3493
3524
  }
3494
3525
  }
3495
3526
 
3527
+ // BC compatibility for Puppeteer < 10
3528
+ async function getClickablePoint(el) {
3529
+ if (el.clickablePoint) return el.clickablePoint();
3530
+ if (el._clickablePoint) return el._clickablePoint();
3531
+ return null;
3532
+ }
3533
+
3496
3534
  // List of key values to key definitions
3497
3535
  // https://github.com/GoogleChrome/puppeteer/blob/v1.20.0/lib/USKeyboardLayout.js
3498
3536
  const keyDefinitionMap = {
@@ -59,7 +59,8 @@ class REST extends Helper {
59
59
 
60
60
  this.options = { ...this.options, ...config };
61
61
  this.headers = { ...this.options.defaultHeaders };
62
- axios.defaults.headers = this.options.defaultHeaders;
62
+ this.axios = axios.create();
63
+ this.axios.defaults.headers = this.options.defaultHeaders;
63
64
  }
64
65
 
65
66
  static _checkRequirements() {
@@ -74,10 +75,12 @@ class REST extends Helper {
74
75
  * Executes axios request
75
76
  *
76
77
  * @param {*} request
78
+ *
79
+ * @returns {Promise<*>} response
77
80
  */
78
81
  async _executeRequest(request) {
79
82
  const _debugRequest = { ...request };
80
- axios.defaults.timeout = request.timeout || this.options.timeout;
83
+ this.axios.defaults.timeout = request.timeout || this.options.timeout;
81
84
 
82
85
  if (this.headers && this.headers.auth) {
83
86
  request.auth = this.headers.auth;
@@ -102,7 +105,7 @@ class REST extends Helper {
102
105
 
103
106
  let response;
104
107
  try {
105
- response = await axios(request);
108
+ response = await this.axios(request);
106
109
  } catch (err) {
107
110
  if (!err.response) throw err;
108
111
  this.debugSection('Response', `Response error. Status code: ${err.response.status}`);
@@ -142,6 +145,8 @@ class REST extends Helper {
142
145
  *
143
146
  * @param {*} url
144
147
  * @param {object} [headers={}] - the headers object to be sent. By default it is sent as an empty object
148
+ *
149
+ * @returns {Promise<*>} response
145
150
  */
146
151
  async sendGetRequest(url, headers = {}) {
147
152
  const request = {
@@ -165,6 +170,8 @@ class REST extends Helper {
165
170
  * @param {*} url
166
171
  * @param {*} [payload={}] - the payload to be sent. By default it is sent as an empty object
167
172
  * @param {object} [headers={}] - the headers object to be sent. By default it is sent as an empty object
173
+ *
174
+ * @returns {Promise<*>} response
168
175
  */
169
176
  async sendPostRequest(url, payload = {}, headers = {}) {
170
177
  const request = {
@@ -196,6 +203,8 @@ class REST extends Helper {
196
203
  * @param {string} url
197
204
  * @param {*} [payload={}] - the payload to be sent. By default it is sent as an empty object
198
205
  * @param {object} [headers={}] - the headers object to be sent. By default it is sent as an empty object
206
+ *
207
+ * @returns {Promise<*>} response
199
208
  */
200
209
  async sendPatchRequest(url, payload = {}, headers = {}) {
201
210
  const request = {
@@ -227,6 +236,8 @@ class REST extends Helper {
227
236
  * @param {string} url
228
237
  * @param {*} [payload={}] - the payload to be sent. By default it is sent as an empty object
229
238
  * @param {object} [headers={}] - the headers object to be sent. By default it is sent as an empty object
239
+ *
240
+ * @returns {Promise<*>} response
230
241
  */
231
242
  async sendPutRequest(url, payload = {}, headers = {}) {
232
243
  const request = {
@@ -253,6 +264,8 @@ class REST extends Helper {
253
264
  *
254
265
  * @param {*} url
255
266
  * @param {object} [headers={}] - the headers object to be sent. By default it is sent as an empty object
267
+ *
268
+ * @returns {Promise<*>} response
256
269
  */
257
270
  async sendDeleteRequest(url, headers = {}) {
258
271
  const request = {
@@ -396,6 +396,7 @@ class WebDriver extends Helper {
396
396
  this.isRunning = false;
397
397
  this.sessionWindows = {};
398
398
  this.activeSessionName = '';
399
+ this.customLocatorStrategies = config.customLocatorStrategies;
399
400
 
400
401
  this._setConfig(config);
401
402
 
@@ -503,6 +504,33 @@ class WebDriver extends Helper {
503
504
  }
504
505
  }
505
506
 
507
+ _lookupCustomLocator(customStrategy) {
508
+ if (typeof (this.customLocatorStrategies) !== 'object') {
509
+ return null;
510
+ }
511
+ const strategy = this.customLocatorStrategies[customStrategy];
512
+ return typeof (strategy) === 'function' ? strategy : null;
513
+ }
514
+
515
+ _isCustomLocator(locator) {
516
+ const locatorObj = new Locator(locator);
517
+ if (locatorObj.isCustom()) {
518
+ const customLocator = this._lookupCustomLocator(locatorObj.type);
519
+ if (customLocator) {
520
+ return true;
521
+ }
522
+ throw new Error('Please define "customLocatorStrategies" as an Object and the Locator Strategy as a "function".');
523
+ }
524
+ return false;
525
+ }
526
+
527
+ async _res(locator) {
528
+ const res = (this._isShadowLocator(locator) || this._isCustomLocator(locator))
529
+ ? await this._locate(locator)
530
+ : await this.$$(withStrictLocator(locator));
531
+ return res;
532
+ }
533
+
506
534
  async _startBrowser() {
507
535
  try {
508
536
  if (this.options.multiremote) {
@@ -530,9 +558,22 @@ class WebDriver extends Helper {
530
558
  await this._resizeWindowIfNeeded(this.browser, this.options.windowSize);
531
559
 
532
560
  this.$$ = this.browser.$$.bind(this.browser);
561
+
562
+ if (this._isCustomLocatorStrategyDefined()) {
563
+ Object.keys(this.customLocatorStrategies).forEach(async (customLocator) => {
564
+ this.debugSection('Weddriver', `adding custom locator strategy: ${customLocator}`);
565
+ const locatorFunction = this._lookupCustomLocator(customLocator);
566
+ this.browser.addLocatorStrategy(customLocator, locatorFunction);
567
+ });
568
+ }
569
+
533
570
  return this.browser;
534
571
  }
535
572
 
573
+ _isCustomLocatorStrategyDefined() {
574
+ return this.customLocatorStrategies && Object.keys(this.customLocatorStrategies).length;
575
+ }
576
+
536
577
  async _stopBrowser() {
537
578
  if (this.browser && this.isRunning) await this.browser.deleteSession();
538
579
  }
@@ -755,17 +796,34 @@ class WebDriver extends Helper {
755
796
  }
756
797
 
757
798
  if (!this.options.smartWait || !smartWait) {
799
+ if (this._isCustomLocator(locator)) {
800
+ const locatorObj = new Locator(locator);
801
+ return this.browser.custom$$(locatorObj.type, locatorObj.value);
802
+ }
803
+
758
804
  const els = await this.$$(withStrictLocator(locator));
759
805
  return els;
760
806
  }
761
807
 
762
808
  await this._smartWait(locator);
763
809
 
810
+ if (this._isCustomLocator(locator)) {
811
+ const locatorObj = new Locator(locator);
812
+ return this.browser.custom$$(locatorObj.type, locatorObj.value);
813
+ }
814
+
764
815
  const els = await this.$$(withStrictLocator(locator));
765
816
  await this.defineTimeout({ implicit: 0 });
766
817
  return els;
767
818
  }
768
819
 
820
+ _grabCustomLocator(locator) {
821
+ if (typeof locator === 'string') {
822
+ locator = new Locator(locator);
823
+ }
824
+ return locator.value ? locator.value : locator.custom;
825
+ }
826
+
769
827
  /**
770
828
  * Find a checkbox by providing human readable text:
771
829
  *
@@ -1079,6 +1137,7 @@ class WebDriver extends Helper {
1079
1137
  * @param {CodeceptJS.StringOrSecret} value text value to fill.
1080
1138
  *
1081
1139
  * {{ react }}
1140
+ * {{ custom }}
1082
1141
  *
1083
1142
  */
1084
1143
  async fillField(field, value) {
@@ -1462,7 +1521,6 @@ class WebDriver extends Helper {
1462
1521
  * @param {string} attr attribute name.
1463
1522
  * @returns {Promise<string[]>} attribute value
1464
1523
  *
1465
- * Appium: can be used for apps only with several values ("contentDescription", "text", "className", "resourceId")
1466
1524
  */
1467
1525
  async grabAttributeFromAll(locator, attr) {
1468
1526
  const res = await this._locate(locator, true);
@@ -1483,7 +1541,6 @@ class WebDriver extends Helper {
1483
1541
  * @param {string} attr attribute name.
1484
1542
  * @returns {Promise<string>} attribute value
1485
1543
  *
1486
- * Appium: can be used for apps only with several values ("contentDescription", "text", "className", "resourceId")
1487
1544
  */
1488
1545
  async grabAttributeFrom(locator, attr) {
1489
1546
  const attrs = await this.grabAttributeFromAll(locator, attr);
@@ -1723,7 +1780,7 @@ class WebDriver extends Helper {
1723
1780
  *
1724
1781
  */
1725
1782
  async seeElementInDOM(locator) {
1726
- const res = await this.$$(withStrictLocator(locator));
1783
+ const res = await this._res(locator);
1727
1784
  return empty('elements').negate(res);
1728
1785
  }
1729
1786
 
@@ -1738,7 +1795,7 @@ class WebDriver extends Helper {
1738
1795
  *
1739
1796
  */
1740
1797
  async dontSeeElementInDOM(locator) {
1741
- const res = await this.$$(withStrictLocator(locator));
1798
+ const res = await this._res(locator);
1742
1799
  return empty('elements').assert(res);
1743
1800
  }
1744
1801
 
@@ -2787,7 +2844,7 @@ class WebDriver extends Helper {
2787
2844
  }, aSec * 1000, `element (${new Locator(locator)}) still not enabled after ${aSec} sec`);
2788
2845
  }
2789
2846
  return this.browser.waitUntil(async () => {
2790
- const res = await this.$$(withStrictLocator(locator));
2847
+ const res = await this._res(locator);
2791
2848
  if (!res || res.length === 0) {
2792
2849
  return false;
2793
2850
  }
@@ -2823,7 +2880,7 @@ class WebDriver extends Helper {
2823
2880
  }, aSec * 1000, `element (${locator}) still not present on page after ${aSec} sec`);
2824
2881
  }
2825
2882
  return this.browser.waitUntil(async () => {
2826
- const res = await this.$$(withStrictLocator(locator));
2883
+ const res = await this._res(locator);
2827
2884
  return res && res.length;
2828
2885
  }, { timeout: aSec * 1000, timeoutMsg: `element (${locator}) still not present on page after ${aSec} sec` });
2829
2886
  }
@@ -3046,9 +3103,7 @@ class WebDriver extends Helper {
3046
3103
  }, aSec * 1000, `element (${new Locator(locator)}) still not visible after ${aSec} sec`);
3047
3104
  }
3048
3105
  return this.browser.waitUntil(async () => {
3049
- const res = (this._isShadowLocator(locator))
3050
- ? await this._locate(withStrictLocator(locator))
3051
- : await this.$$(withStrictLocator(locator));
3106
+ const res = await this._res(locator);
3052
3107
  if (!res || res.length === 0) return false;
3053
3108
  const selected = await forEachAsync(res, async el => el.isDisplayed());
3054
3109
  if (Array.isArray(selected)) {
@@ -3083,7 +3138,7 @@ class WebDriver extends Helper {
3083
3138
  }, aSec * 1000, `The number of elements (${new Locator(locator)}) is not ${num} after ${aSec} sec`);
3084
3139
  }
3085
3140
  return this.browser.waitUntil(async () => {
3086
- const res = await this.$$(withStrictLocator(locator));
3141
+ const res = await this._res(locator);
3087
3142
  if (!res || res.length === 0) return false;
3088
3143
  let selected = await forEachAsync(res, async el => el.isDisplayed());
3089
3144
 
@@ -3115,7 +3170,7 @@ class WebDriver extends Helper {
3115
3170
  }, aSec * 1000, `element (${new Locator(locator)}) still visible after ${aSec} sec`);
3116
3171
  }
3117
3172
  return this.browser.waitUntil(async () => {
3118
- const res = await this.$$(withStrictLocator(locator));
3173
+ const res = await this._res(locator);
3119
3174
  if (!res || res.length === 0) return true;
3120
3175
  const selected = await forEachAsync(res, async el => el.isDisplayed());
3121
3176
  return !selected.length;
@@ -3152,7 +3207,7 @@ class WebDriver extends Helper {
3152
3207
  const aSec = sec || this.options.waitForTimeout;
3153
3208
  if (isWebDriver5()) {
3154
3209
  return this.browser.waitUntil(async () => {
3155
- const res = await this.$$(withStrictLocator(locator));
3210
+ const res = await this._res(locator);
3156
3211
  if (!res || res.length === 0) {
3157
3212
  return true;
3158
3213
  }
@@ -3160,7 +3215,7 @@ class WebDriver extends Helper {
3160
3215
  }, aSec * 1000, `element (${new Locator(locator)}) still on page after ${aSec} sec`);
3161
3216
  }
3162
3217
  return this.browser.waitUntil(async () => {
3163
- const res = await this.$$(withStrictLocator(locator));
3218
+ const res = await this._res(locator);
3164
3219
  if (!res || res.length === 0) {
3165
3220
  return true;
3166
3221
  }
@@ -3563,12 +3618,9 @@ async function proceedSee(assertType, text, context, strict = false) {
3563
3618
  }
3564
3619
 
3565
3620
  const smartWaitEnabled = assertType === 'assert';
3566
-
3567
3621
  const res = await this._locate(withStrictLocator(context), smartWaitEnabled);
3568
3622
  assertElementExists(res, context);
3569
-
3570
3623
  const selected = await forEachAsync(res, async el => this.browser.getElementText(getElementId(el)));
3571
-
3572
3624
  if (strict) {
3573
3625
  if (Array.isArray(selected) && selected.length !== 0) {
3574
3626
  return selected.map(elText => equals(description)[assertType](text, elText));
@@ -3636,6 +3688,11 @@ async function filterAsync(array, callback) {
3636
3688
 
3637
3689
  async function findClickable(locator, locateFn) {
3638
3690
  locator = new Locator(locator);
3691
+
3692
+ if (this._isCustomLocator(locator)) {
3693
+ return locateFn(locator.value);
3694
+ }
3695
+
3639
3696
  if (locator.isAccessibilityId() && !this.isWeb) return locateFn(locator, true);
3640
3697
  if (!locator.isFuzzy()) return locateFn(locator, true);
3641
3698
 
@@ -3657,6 +3714,10 @@ async function findClickable(locator, locateFn) {
3657
3714
  async function findFields(locator) {
3658
3715
  locator = new Locator(locator);
3659
3716
 
3717
+ if (this._isCustomLocator(locator)) {
3718
+ return this._locate(locator);
3719
+ }
3720
+
3660
3721
  if (locator.isAccessibilityId() && !this.isWeb) return this._locate(locator, true);
3661
3722
  if (!locator.isFuzzy()) return this._locate(locator, true);
3662
3723
 
@@ -3762,6 +3823,10 @@ async function findCheckable(locator, locateFn) {
3762
3823
  let els;
3763
3824
  locator = new Locator(locator);
3764
3825
 
3826
+ if (this._isCustomLocator(locator)) {
3827
+ return locateFn(locator.value);
3828
+ }
3829
+
3765
3830
  if (locator.isAccessibilityId() && !this.isWeb) return locateFn(locator, true);
3766
3831
  if (!locator.isFuzzy()) return locateFn(locator, true);
3767
3832
 
@@ -3807,6 +3872,7 @@ function getElementId(el) {
3807
3872
  if (el.ELEMENT) {
3808
3873
  return el.ELEMENT;
3809
3874
  }
3875
+
3810
3876
  return null;
3811
3877
  }
3812
3878
 
package/docs/changelog.md CHANGED
@@ -7,13 +7,97 @@ layout: Section
7
7
 
8
8
  # Releases
9
9
 
10
+ ## 3.1.2
11
+
12
+ đŸ›Šī¸ Features:
13
+
14
+ * Added `coverage` plugin to generate code coverage for Playwright & Puppeteer. By **[anirudh-modi](https://github.com/anirudh-modi)**
15
+ * Added `subtitle` plugin to generate subtitles for videos recorded with Playwright. By **[anirudh-modi](https://github.com/anirudh-modi)**
16
+ * Configuration: `config.tests` to accept array of file patterns. See [#2994](https://github.com/codeceptjs/CodeceptJS/issues/2994) by **[monsteramba](https://github.com/monsteramba)**
17
+
18
+ ```js
19
+ exports.config = {
20
+ tests: ['./*_test.js','./sampleTest.js'],
21
+ // ...
22
+ }
23
+ ```
24
+ * Notification is shown for test files without `Feature()`. See [#3011](https://github.com/codeceptjs/CodeceptJS/issues/3011) by **[PeterNgTr](https://github.com/PeterNgTr)**
25
+
26
+ 🐛 Bugfixes:
27
+
28
+ * **[Playwright]** Fixed [#2986](https://github.com/codeceptjs/CodeceptJS/issues/2986) error is thrown when deleting a missing video. Fix by **[hatufacci](https://github.com/hatufacci)**
29
+ * Fixed false positive result when invalid function is called in a helper. See [#2997](https://github.com/codeceptjs/CodeceptJS/issues/2997) by **[abhimanyupandian](https://github.com/abhimanyupandian)**
30
+ * **[Appium]** Removed full page mode for `saveScreenshot`. See [#3002](https://github.com/codeceptjs/CodeceptJS/issues/3002) by **[nlespiaucq](https://github.com/nlespiaucq)**
31
+ * **[Playwright]** Fixed [#3003](https://github.com/codeceptjs/CodeceptJS/issues/3003) saving trace for a test with a long name. Fix by **[hatufacci](https://github.com/hatufacci)**
32
+
33
+ 🎱 Other:
34
+
35
+ * Deprecated `puppeteerCoverage` plugin in favor of `coverage` plugin.
36
+
37
+ ## 3.1.1
38
+
39
+ * **[Appium]** Fixed [#2759](https://github.com/codeceptjs/CodeceptJS/issues/2759)
40
+ `grabNumberOfVisibleElements`, `grabAttributeFrom`, `grabAttributeFromAll` to allow id locators.
41
+
42
+ ## 3.1.0
43
+
44
+ * **[Plawyright]** Updated to Playwright 1.13
45
+ * **[Playwright]** **Possible breaking change**: `BrowserContext` is initialized before each test and closed after. This behavior matches recommendation from Playwright team to use different contexts for tests.
46
+ * **[Puppeteer]** Updated to Puppeteer 10.2.
47
+ * **[Protractor]** Helper deprecated
48
+
49
+ đŸ›Šī¸ Features:
50
+
51
+ * **[Playwright]** Added recording of [video](https://codecept.io/playwright/#video) and [traces](https://codecept.io/playwright/#trace) by **[davertmik](https://github.com/davertmik)**
52
+ * **[Playwritght]** [Mocking requests](https://codecept.io/playwright/#mocking-network-requests) implemented via `route` API of Playwright by **[davertmik](https://github.com/davertmik)**
53
+ * **[Playwright]** Added **support for [React locators](https://codecept.io/react/#locators)** in [#2912](https://github.com/codeceptjs/CodeceptJS/issues/2912) by **[AAAstorga](https://github.com/AAAstorga)**
54
+
55
+ 🐛 Bugfixes:
56
+
57
+ * **[Puppeteer]** Fixed [#2244](https://github.com/codeceptjs/CodeceptJS/issues/2244) `els[0]._clickablePoint is not a function` by **[karunandrii](https://github.com/karunandrii)**.
58
+ * **[Puppeteer]** Fixed `fillField` to check for invisible elements. See [#2916](https://github.com/codeceptjs/CodeceptJS/issues/2916) by **[anne-open-xchange](https://github.com/anne-open-xchange)**
59
+ * **[Playwright]** Reset of dialog event listener before registration of new one. [#2946](https://github.com/codeceptjs/CodeceptJS/issues/2946) by **[nikocanvacom](https://github.com/nikocanvacom)**
60
+ * Fixed running Gherkin features with `run-multiple` using chunks. See [#2900](https://github.com/codeceptjs/CodeceptJS/issues/2900) by **[andrenoberto](https://github.com/andrenoberto)**
61
+ * Fixed [#2937](https://github.com/codeceptjs/CodeceptJS/issues/2937) broken typings for subfolders on Windows by **[jancorvus](https://github.com/jancorvus)**
62
+ * Fixed issue where cucumberJsonReporter not working with fakerTransform plugin. See [#2942](https://github.com/codeceptjs/CodeceptJS/issues/2942) by **[ilangv](https://github.com/ilangv)**
63
+ * Fixed [#2952](https://github.com/codeceptjs/CodeceptJS/issues/2952) finished job with status code 0 when playwright cannot connect to remote wss url. By **[davertmik](https://github.com/davertmik)**
64
+
65
+
66
+ ## 3.0.7
67
+
68
+ 📖 Documentation fixes:
69
+
70
+ * Remove broken link from `Nightmare helper`. See [#2860](https://github.com/codeceptjs/CodeceptJS/issues/2860) by **[Arhell](https://github.com/Arhell)**
71
+ * Fixed broken links in `playwright.md`. See [#2848](https://github.com/codeceptjs/CodeceptJS/issues/2848) by **[johnhoodjr](https://github.com/johnhoodjr)**
72
+ * Fix mocha-multi config example. See [#2881](https://github.com/codeceptjs/CodeceptJS/issues/2881) by **[rimesc](https://github.com/rimesc)**
73
+ * Fix small errors in email documentation file. See [#2884](https://github.com/codeceptjs/CodeceptJS/issues/2884) by **[mkrtchian](https://github.com/mkrtchian)**
74
+ * Improve documentation for `Sharing Data Between Workers` section. See [#2891](https://github.com/codeceptjs/CodeceptJS/issues/2891) by **[ngraf](https://github.com/ngraf)**
75
+
76
+ đŸ›Šī¸ Features:
77
+
78
+ * **[WebDriver]** Shadow DOM Support for `Webdriver`. See [#2741](https://github.com/codeceptjs/CodeceptJS/issues/2741) by **[gkushang](https://github.com/gkushang)**
79
+ * [Release management] Introduce the versioning automatically, it follows the semantics versioning. See [#2883](https://github.com/codeceptjs/CodeceptJS/issues/2883) by **[PeterNgTr](https://github.com/PeterNgTr)**
80
+ * Adding opts into `Scenario.skip` that it would be useful for building reports. See [#2867](https://github.com/codeceptjs/CodeceptJS/issues/2867) by **[AlexKo4](https://github.com/AlexKo4)**
81
+ * Added support for attaching screenshots to [cucumberJsonReporter](https://github.com/ktryniszewski-mdsol/codeceptjs-cucumber-json-reporter) See [#2888](https://github.com/codeceptjs/CodeceptJS/issues/2888) by **[fijijavis](https://github.com/fijijavis)**
82
+ * Supported config file for `codeceptjs shell` command. See [#2895](https://github.com/codeceptjs/CodeceptJS/issues/2895) by **[PeterNgTr](https://github.com/PeterNgTr)**:
83
+
84
+ ```
85
+ npx codeceptjs shell -c foo.conf.js
86
+ ```
87
+
88
+ Bug fixes:
89
+ * **[GraphQL]** Use a helper-specific instance of Axios to avoid contaminating global defaults. See [#2868](https://github.com/codeceptjs/CodeceptJS/issues/2868) by **[vanvoljg](https://github.com/vanvoljg)**
90
+ * A default system color is used when passing non supported system color when using I.say(). See [#2874](https://github.com/codeceptjs/CodeceptJS/issues/2874) by **[PeterNgTr](https://github.com/PeterNgTr)**
91
+ * **[Playwright]** Avoid the timout due to calling the click on invisible elements. See [#2875](https://github.com/codeceptjs/CodeceptJS/issues/2875) by cbayer97
92
+
93
+
10
94
  ## 3.0.6
11
95
 
12
- * **[Playwright]** Added `electron` as a browser to config. See [#2834](https://github.com/codeceptjs/CodeceptJS/issues/2834) by **[cbayer97](https://github.com/cbayer97)**
96
+ * **[Playwright]** Added `electron` as a browser to config. See [#2834](https://github.com/codeceptjs/CodeceptJS/issues/2834) by **[cbayer97](https://github.com/cbayer97)**
13
97
  * **[Playwright]** Implemented `launchPersistentContext` to be able to launch persistent remote browsers. See [#2817](https://github.com/codeceptjs/CodeceptJS/issues/2817) by **[brunoqueiros](https://github.com/brunoqueiros)**. Fixes [#2376](https://github.com/codeceptjs/CodeceptJS/issues/2376).
14
98
  * Fixed printing logs and stack traces for `run-workers`. See [#2857](https://github.com/codeceptjs/CodeceptJS/issues/2857) by **[haveac1gar](https://github.com/haveac1gar)**. Fixes [#2621](https://github.com/codeceptjs/CodeceptJS/issues/2621), [#2852](https://github.com/codeceptjs/CodeceptJS/issues/2852)
15
- * Emit custom messages from worker to the main thread. See [#2824](https://github.com/codeceptjs/CodeceptJS/issues/2824) by **[jccguimaraes](https://github.com/jccguimaraes)**
16
- * Improved workers processes output. See [#2804](https://github.com/codeceptjs/CodeceptJS/issues/2804) by **[drfiresign](https://github.com/drfiresign)**
99
+ * Emit custom messages from worker to the main thread. See [#2824](https://github.com/codeceptjs/CodeceptJS/issues/2824) by **[jccguimaraes](https://github.com/jccguimaraes)**
100
+ * Improved workers processes output. See [#2804](https://github.com/codeceptjs/CodeceptJS/issues/2804) by **[drfiresign](https://github.com/drfiresign)**
17
101
  * BDD. Added ability to use an array of feature files inside config in `gherkin.features`. See [#2814](https://github.com/codeceptjs/CodeceptJS/issues/2814) by **[jbergeronjr](https://github.com/jbergeronjr)**
18
102
 
19
103
  ```js
@@ -22,8 +106,8 @@ layout: Section
22
106
  "./features/api_features/*.feature"
23
107
  ],
24
108
  ```
25
- * Added `getQueueId` to reporter to rerun a specific promise. See [#2837](https://github.com/codeceptjs/CodeceptJS/issues/2837) by **[jonatask](https://github.com/jonatask)**
26
- * **Added `fakerTransform` plugin** to use faker data in Gherkin scenarios. See [#2854](https://github.com/codeceptjs/CodeceptJS/issues/2854) by **[adrielcodeco](https://github.com/adrielcodeco)**
109
+ * Added `getQueueId` to reporter to rerun a specific promise. See [#2837](https://github.com/codeceptjs/CodeceptJS/issues/2837) by **[jonatask](https://github.com/jonatask)**
110
+ * **Added `fakerTransform` plugin** to use faker data in Gherkin scenarios. See [#2854](https://github.com/codeceptjs/CodeceptJS/issues/2854) by **[adrielcodeco](https://github.com/adrielcodeco)**
27
111
 
28
112
  ```feature
29
113
  Scenario Outline: ...
@@ -35,7 +119,7 @@ Scenario Outline: ...
35
119
  | productName | customer | email | anythingMore |
36
120
  | {{commerce.product}} | Dr. {{name.findName}} | {{internet.email}} | staticData |
37
121
  ```
38
- * **[REST]** Use class instance of axios, not the global instance, to avoid contaminating global configuration. [#2846](https://github.com/codeceptjs/CodeceptJS/issues/2846) by **[vanvoljg](https://github.com/vanvoljg)**
122
+ * **[REST]** Use class instance of axios, not the global instance, to avoid contaminating global configuration. [#2846](https://github.com/codeceptjs/CodeceptJS/issues/2846) by **[vanvoljg](https://github.com/vanvoljg)**
39
123
  * **[Appium]** Added `tunnelIdentifier` config option to provide tunnel for SauceLabs. See [#2832](https://github.com/codeceptjs/CodeceptJS/issues/2832) by **[gurjeetbains](https://github.com/gurjeetbains)**
40
124
 
41
125
  ## 3.0.5
@@ -43,9 +127,9 @@ Scenario Outline: ...
43
127
 
44
128
  Features:
45
129
 
46
- * **[Official Docker image for CodeceptJS v3](https://hub.docker.com/r/codeceptjs/codeceptjs)**. New Docker image is based on official Playwright image and supports Playwright, Puppeteer, WebDriver engines. Thanks **[VikentyShevyrin](https://github.com/VikentyShevyrin)**
130
+ * **[Official Docker image for CodeceptJS v3](https://hub.docker.com/r/codeceptjs/codeceptjs)**. New Docker image is based on official Playwright image and supports Playwright, Puppeteer, WebDriver engines. Thanks **[VikentyShevyrin](https://github.com/VikentyShevyrin)**
47
131
  * Better support for Typescript `codecept.conf.ts` configuration files. See [#2750](https://github.com/codeceptjs/CodeceptJS/issues/2750) by **[elaichenkov](https://github.com/elaichenkov)**
48
- * Propagate more events for custom parallel script. See [#2796](https://github.com/codeceptjs/CodeceptJS/issues/2796) by **[jccguimaraes](https://github.com/jccguimaraes)**
132
+ * Propagate more events for custom parallel script. See [#2796](https://github.com/codeceptjs/CodeceptJS/issues/2796) by **[jccguimaraes](https://github.com/jccguimaraes)**
49
133
  * [mocha-junit-reporter] Now supports attachments, see documentation for details. See [#2675](https://github.com/codeceptjs/CodeceptJS/issues/2675) by **[Shard](https://github.com/Shard)**
50
134
  * CustomLocators interface for TypeScript to extend from LocatorOrString. See [#2798](https://github.com/codeceptjs/CodeceptJS/issues/2798) by **[danielrentz](https://github.com/danielrentz)**
51
135
  * **[REST]** Mask sensitive data from log messages.
@@ -144,7 +228,7 @@ Scenario('title', (I, loginPage) => {});
144
228
  Scenario('title', ({ I, loginPage }) => {});
145
229
  ```
146
230
 
147
- * **BREAKING** Replaced bootstrap/teardown scripts to accept only functions or async functions. Async function with callback (with done parameter) should be replaced with async/await. [See our upgrde guide](https://bit.ly/codecept3Up).
231
+ * **BREAKING** Replaced bootstrap/teardown scripts to accept only functions or async functions. Async function with callback (with done parameter) should be replaced with async/await. [See our upgrade guide](https://bit.ly/codecept3Up).
148
232
  * **[TypeScript guide](/typescript)** and [boilerplate project](https://github.com/codeceptjs/typescript-boilerplate)
149
233
  * [tryTo](/plugins/#tryto) and [pauseOnFail](/plugins/#pauseOnFail) plugins installed by default
150
234
  * Introduced one-line installer: