codeceptjs 3.4.1 → 3.5.1

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 (75) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/README.md +11 -9
  3. package/bin/codecept.js +1 -1
  4. package/docs/ai.md +248 -0
  5. package/docs/build/Appium.js +47 -7
  6. package/docs/build/JSONResponse.js +4 -4
  7. package/docs/build/Nightmare.js +3 -1
  8. package/docs/build/OpenAI.js +122 -0
  9. package/docs/build/Playwright.js +234 -54
  10. package/docs/build/Protractor.js +3 -1
  11. package/docs/build/Puppeteer.js +101 -12
  12. package/docs/build/REST.js +15 -5
  13. package/docs/build/TestCafe.js +61 -2
  14. package/docs/build/WebDriver.js +85 -5
  15. package/docs/changelog.md +85 -0
  16. package/docs/helpers/Appium.md +152 -147
  17. package/docs/helpers/JSONResponse.md +4 -4
  18. package/docs/helpers/Nightmare.md +2 -0
  19. package/docs/helpers/OpenAI.md +70 -0
  20. package/docs/helpers/Playwright.md +228 -151
  21. package/docs/helpers/Puppeteer.md +153 -101
  22. package/docs/helpers/REST.md +6 -5
  23. package/docs/helpers/TestCafe.md +97 -49
  24. package/docs/helpers/WebDriver.md +159 -107
  25. package/docs/mobile.md +49 -2
  26. package/docs/parallel.md +56 -0
  27. package/docs/plugins.md +87 -33
  28. package/docs/secrets.md +6 -0
  29. package/docs/tutorial.md +2 -2
  30. package/docs/webapi/appendField.mustache +2 -0
  31. package/docs/webapi/blur.mustache +17 -0
  32. package/docs/webapi/focus.mustache +12 -0
  33. package/docs/webapi/type.mustache +3 -0
  34. package/lib/ai.js +171 -0
  35. package/lib/cli.js +10 -2
  36. package/lib/codecept.js +4 -0
  37. package/lib/command/dryRun.js +9 -1
  38. package/lib/command/generate.js +46 -3
  39. package/lib/command/init.js +23 -1
  40. package/lib/command/interactive.js +15 -1
  41. package/lib/command/run-workers.js +2 -1
  42. package/lib/container.js +13 -3
  43. package/lib/event.js +2 -0
  44. package/lib/helper/Appium.js +45 -7
  45. package/lib/helper/JSONResponse.js +4 -4
  46. package/lib/helper/Nightmare.js +1 -1
  47. package/lib/helper/OpenAI.js +122 -0
  48. package/lib/helper/Playwright.js +200 -45
  49. package/lib/helper/Protractor.js +1 -1
  50. package/lib/helper/Puppeteer.js +67 -12
  51. package/lib/helper/REST.js +15 -5
  52. package/lib/helper/TestCafe.js +30 -2
  53. package/lib/helper/WebDriver.js +51 -5
  54. package/lib/helper/scripts/blurElement.js +17 -0
  55. package/lib/helper/scripts/focusElement.js +17 -0
  56. package/lib/helper/scripts/highlightElement.js +20 -0
  57. package/lib/html.js +258 -0
  58. package/lib/interfaces/gherkin.js +8 -0
  59. package/lib/listener/retry.js +2 -1
  60. package/lib/pause.js +73 -17
  61. package/lib/plugin/debugErrors.js +67 -0
  62. package/lib/plugin/fakerTransform.js +4 -6
  63. package/lib/plugin/heal.js +177 -0
  64. package/lib/plugin/screenshotOnFail.js +11 -2
  65. package/lib/recorder.js +11 -8
  66. package/lib/secret.js +5 -4
  67. package/lib/step.js +6 -1
  68. package/lib/ui.js +4 -3
  69. package/lib/utils.js +17 -0
  70. package/lib/workers.js +57 -9
  71. package/package.json +25 -16
  72. package/translations/ja-JP.js +9 -9
  73. package/typings/index.d.ts +43 -9
  74. package/typings/promiseBasedTypes.d.ts +242 -25
  75. package/typings/types.d.ts +260 -35
@@ -6,6 +6,7 @@ const path = require('path');
6
6
  const Helper = require('@codeceptjs/helper');
7
7
  const Locator = require('../locator');
8
8
  const recorder = require('../recorder');
9
+ const store = require('../store');
9
10
  const stringIncludes = require('../assert/include').includes;
10
11
  const { urlEquals } = require('../assert/equal');
11
12
  const { equals } = require('../assert/equal');
@@ -33,6 +34,9 @@ const RemoteBrowserConnectionRefused = require('./errors/RemoteBrowserConnection
33
34
  const Popup = require('./extras/Popup');
34
35
  const Console = require('./extras/Console');
35
36
  const findReact = require('./extras/React');
37
+ const { highlightElement } = require('./scripts/highlightElement');
38
+ const { blurElement } = require('./scripts/blurElement');
39
+ const { focusElement } = require('./scripts/focusElement');
36
40
 
37
41
  let puppeteer;
38
42
  let perfTiming;
@@ -65,6 +69,7 @@ const consoleLogStore = new Console();
65
69
  * @prop {boolean} [manualStart=false] - do not start browser before a test, start it manually inside a helper with `this.helpers["Puppeteer"]._startBrowser()`.
66
70
  * @prop {string} [browser=chrome] - can be changed to `firefox` when using [puppeteer-firefox](https://codecept.io/helpers/Puppeteer-firefox).
67
71
  * @prop {object} [chrome] - pass additional [Puppeteer run options](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions).
72
+ * @prop {boolean} [highlightElement] - highlight the interacting elements
68
73
  */
69
74
  const config = {};
70
75
 
@@ -740,6 +745,60 @@ class Puppeteer extends Helper {
740
745
  return this._waitForAction();
741
746
  }
742
747
 
748
+ /**
749
+ * Calls [focus](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus) on the matching element.
750
+ *
751
+ * Examples:
752
+ *
753
+ * ```js
754
+ * I.dontSee('#add-to-cart-btn');
755
+ * I.focus('#product-tile')
756
+ * I.see('#add-to-cart-bnt');
757
+ * ```
758
+ *
759
+ * @param {CodeceptJS.LocatorOrString} locator field located by label|name|CSS|XPath|strict locator.
760
+ * @param {any} [options] Playwright only: [Additional options](https://playwright.dev/docs/api/class-locator#locator-focus) for available options object as 2nd argument.
761
+ *
762
+ *
763
+ */
764
+ async focus(locator) {
765
+ const els = await this._locate(locator);
766
+ assertElementExists(els, locator, 'Element to focus');
767
+ const el = els[0];
768
+
769
+ await focusElement(el, this.page);
770
+ return this._waitForAction();
771
+ }
772
+
773
+ /**
774
+ * Remove focus from a text input, button, etc.
775
+ * Calls [blur](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus) on the element.
776
+ *
777
+ * Examples:
778
+ *
779
+ * ```js
780
+ * I.blur('.text-area')
781
+ * ```
782
+ * ```js
783
+ * //element `#product-tile` is focused
784
+ * I.see('#add-to-cart-btn');
785
+ * I.blur('#product-tile')
786
+ * I.dontSee('#add-to-cart-btn');
787
+ * ```
788
+ *
789
+ * @param {CodeceptJS.LocatorOrString} locator field located by label|name|CSS|XPath|strict locator.
790
+ * @param {any} [options] Playwright only: [Additional options](https://playwright.dev/docs/api/class-locator#locator-blur) for available options object as 2nd argument.
791
+ *
792
+ *
793
+ */
794
+ async blur(locator) {
795
+ const els = await this._locate(locator);
796
+ assertElementExists(els, locator, 'Element to blur');
797
+
798
+ await blurElement(els[0], this.page);
799
+ return this._waitForAction();
800
+ }
801
+
743
802
  /**
744
803
  * Drag an item to a destination element.
745
804
  *
@@ -1643,6 +1702,9 @@ class Puppeteer extends Helper {
1643
1702
  *
1644
1703
  * // passing in an array
1645
1704
  * I.type(['T', 'E', 'X', 'T']);
1705
+ *
1706
+ * // passing a secret
1707
+ * I.type(secret('123456'));
1646
1708
  * ```
1647
1709
  *
1648
1710
  * @param {string|string[]} key or array of keys to type.
@@ -1652,6 +1714,7 @@ class Puppeteer extends Helper {
1652
1714
  */
1653
1715
  async type(keys, delay = null) {
1654
1716
  if (!Array.isArray(keys)) {
1717
+ keys = keys.toString();
1655
1718
  keys = keys.split('');
1656
1719
  }
1657
1720
 
@@ -1692,7 +1755,10 @@ class Puppeteer extends Helper {
1692
1755
  } else if (editable) {
1693
1756
  await this._evaluateHandeInContext(el => el.innerHTML = '', el);
1694
1757
  }
1758
+
1759
+ highlightActiveElement.call(this, el, this.page);
1695
1760
  await el.type(value.toString(), { delay: this.options.pressKeyDelay });
1761
+
1696
1762
  return this._waitForAction();
1697
1763
  }
1698
1764
 
@@ -1718,6 +1784,8 @@ class Puppeteer extends Helper {
1718
1784
  *
1719
1785
  * ```js
1720
1786
  * I.appendField('#myTextField', 'appended');
1787
+ * // typing secret
1788
+ * I.appendField('password', secret('123456'));
1721
1789
  * ```
1722
1790
  * @param {CodeceptJS.LocatorOrString} field located by label|name|CSS|XPath|strict locator
1723
1791
  * @param {string} value text value to append.
@@ -1729,8 +1797,9 @@ class Puppeteer extends Helper {
1729
1797
  async appendField(field, value) {
1730
1798
  const els = await findVisibleFields.call(this, field);
1731
1799
  assertElementExists(els, field, 'Field');
1800
+ highlightActiveElement.call(this, els[0], this.page);
1732
1801
  await els[0].press('End');
1733
- await els[0].type(value, { delay: this.options.pressKeyDelay });
1802
+ await els[0].type(value.toString(), { delay: this.options.pressKeyDelay });
1734
1803
  return this._waitForAction();
1735
1804
  }
1736
1805
 
@@ -1831,6 +1900,7 @@ class Puppeteer extends Helper {
1831
1900
  if (await el.getProperty('tagName').then(t => t.jsonValue()) !== 'SELECT') {
1832
1901
  throw new Error('Element is not <select>');
1833
1902
  }
1903
+ highlightActiveElement.call(this, els[0], this.page);
1834
1904
  if (!Array.isArray(option)) option = [option];
1835
1905
 
1836
1906
  for (const key in option) {
@@ -2680,23 +2750,32 @@ class Puppeteer extends Helper {
2680
2750
  */
2681
2751
  async saveScreenshot(fileName, fullPage) {
2682
2752
  const fullPageOption = fullPage || this.options.fullPageScreenshots;
2683
- const outputFile = screenshotOutputFolder(fileName);
2753
+ let outputFile = screenshotOutputFolder(fileName);
2684
2754
 
2685
2755
  this.debug(`Screenshot is saving to ${outputFile}`);
2686
2756
 
2757
+ await this.page.screenshot({
2758
+ path: outputFile,
2759
+ fullPage: fullPageOption,
2760
+ type: 'png',
2761
+ });
2762
+
2687
2763
  if (this.activeSessionName) {
2688
- const activeSessionPage = this.sessionPages[this.activeSessionName];
2689
-
2690
- if (activeSessionPage) {
2691
- return activeSessionPage.screenshot({
2692
- path: outputFile,
2693
- fullPage: fullPageOption,
2694
- type: 'png',
2695
- });
2764
+ for (const sessionName in this.sessionPages) {
2765
+ const activeSessionPage = this.sessionPages[sessionName];
2766
+ outputFile = screenshotOutputFolder(`${sessionName}_${fileName}`);
2767
+
2768
+ this.debug(`${sessionName} - Screenshot is saving to ${outputFile}`);
2769
+
2770
+ if (activeSessionPage) {
2771
+ await activeSessionPage.screenshot({
2772
+ path: outputFile,
2773
+ fullPage: fullPageOption,
2774
+ type: 'png',
2775
+ });
2776
+ }
2696
2777
  }
2697
2778
  }
2698
-
2699
- return this.page.screenshot({ path: outputFile, fullPage: fullPageOption, type: 'png' });
2700
2779
  }
2701
2780
 
2702
2781
  async _failed() {
@@ -3358,12 +3437,16 @@ async function proceedClick(locator, context = null, options = {}) {
3358
3437
  } else {
3359
3438
  assertElementExists(els, locator, 'Clickable element');
3360
3439
  }
3440
+
3441
+ highlightActiveElement.call(this, els[0], this.page);
3442
+
3361
3443
  await els[0].click(options);
3362
3444
  const promises = [];
3363
3445
  if (options.waitForNavigation) {
3364
3446
  promises.push(this.waitForNavigation());
3365
3447
  }
3366
3448
  promises.push(this._waitForAction());
3449
+
3367
3450
  return Promise.all(promises);
3368
3451
  }
3369
3452
 
@@ -3708,3 +3791,9 @@ function getNormalizedKey(key) {
3708
3791
  }
3709
3792
  return normalizedKey;
3710
3793
  }
3794
+
3795
+ function highlightActiveElement(element, context) {
3796
+ if (!this.options.enableHighlight && !store.debugMode) return;
3797
+
3798
+ highlightElement(element, context);
3799
+ }
@@ -34,7 +34,8 @@ const config = {};
34
34
  * endpoint: 'http://site.com/api',
35
35
  * prettyPrintJson: true,
36
36
  * onRequest: (request) => {
37
- * request.headers.auth = '123';
37
+ * request.headers.auth = '123';
38
+ * }
38
39
  * }
39
40
  * }
40
41
  *}
@@ -136,6 +137,15 @@ class REST extends Helper {
136
137
  request.auth = this.headers.auth;
137
138
  }
138
139
 
140
+ if (typeof request.data === 'object') {
141
+ const returnedValue = {};
142
+ for (const [key, value] of Object.entries(request.data)) {
143
+ returnedValue[key] = value;
144
+ if (value instanceof Secret) returnedValue[key] = value.getMasked();
145
+ }
146
+ _debugRequest.data = returnedValue;
147
+ }
148
+
139
149
  if (request.data instanceof Secret) {
140
150
  _debugRequest.data = '*****';
141
151
  request.data = (typeof request.data === 'object' && !(request.data instanceof Secret)) ? { ...request.data.toString() } : request.data.toString();
@@ -198,7 +208,7 @@ class REST extends Helper {
198
208
  * ```
199
209
  *
200
210
  * @param {*} url
201
- * @param {object} [headers={}] - the headers object to be sent. By default it is sent as an empty object
211
+ * @param {object} [headers={}] - the headers object to be sent. By default, it is sent as an empty object
202
212
  *
203
213
  * @returns {Promise<*>} response
204
214
  */
@@ -222,8 +232,8 @@ class REST extends Helper {
222
232
  * ```
223
233
  *
224
234
  * @param {*} url
225
- * @param {*} [payload={}] - the payload to be sent. By default it is sent as an empty object
226
- * @param {object} [headers={}] - the headers object to be sent. By default it is sent as an empty object
235
+ * @param {*} [payload={}] - the payload to be sent. By default, it is sent as an empty object
236
+ * @param {object} [headers={}] - the headers object to be sent. By default, it is sent as an empty object
227
237
  *
228
238
  * @returns {Promise<*>} response
229
239
  */
@@ -317,7 +327,7 @@ class REST extends Helper {
317
327
  * ```
318
328
  *
319
329
  * @param {*} url
320
- * @param {object} [headers={}] - the headers object to be sent. By default it is sent as an empty object
330
+ * @param {object} [headers={}] - the headers object to be sent. By default, it is sent as an empty object
321
331
  *
322
332
  * @returns {Promise<*>} response
323
333
  */
@@ -362,6 +362,63 @@ class TestCafe extends Helper {
362
362
  return this.t.resizeWindow(width, height).catch(mapError);
363
363
  }
364
364
 
365
+ /**
366
+ * Calls [focus](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus) on the matching element.
367
+ *
368
+ * Examples:
369
+ *
370
+ * ```js
371
+ * I.dontSee('#add-to-cart-btn');
372
+ * I.focus('#product-tile')
373
+ * I.see('#add-to-cart-bnt');
374
+ * ```
375
+ *
376
+ * @param {CodeceptJS.LocatorOrString} locator field located by label|name|CSS|XPath|strict locator.
377
+ * @param {any} [options] Playwright only: [Additional options](https://playwright.dev/docs/api/class-locator#locator-focus) for available options object as 2nd argument.
378
+ *
379
+ *
380
+ */
381
+ async focus(locator) {
382
+ const els = await this._locate(locator);
383
+ await assertElementExists(els, locator, 'Element to focus');
384
+ const element = await els.nth(0);
385
+
386
+ const focusElement = ClientFunction(() => element().focus(), { boundTestRun: this.t, dependencies: { element } });
387
+
388
+ return focusElement();
389
+ }
390
+
391
+ /**
392
+ * Remove focus from a text input, button, etc.
393
+ * Calls [blur](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus) on the element.
394
+ *
395
+ * Examples:
396
+ *
397
+ * ```js
398
+ * I.blur('.text-area')
399
+ * ```
400
+ * ```js
401
+ * //element `#product-tile` is focused
402
+ * I.see('#add-to-cart-btn');
403
+ * I.blur('#product-tile')
404
+ * I.dontSee('#add-to-cart-btn');
405
+ * ```
406
+ *
407
+ * @param {CodeceptJS.LocatorOrString} locator field located by label|name|CSS|XPath|strict locator.
408
+ * @param {any} [options] Playwright only: [Additional options](https://playwright.dev/docs/api/class-locator#locator-blur) for available options object as 2nd argument.
409
+ *
410
+ *
411
+ */
412
+ async blur(locator) {
413
+ const els = await this._locate(locator);
414
+ await assertElementExists(els, locator, 'Element to blur');
415
+ const element = await els.nth(0);
416
+
417
+ const blurElement = ClientFunction(() => element().blur(), { boundTestRun: this.t, dependencies: { element } });
418
+
419
+ return blurElement();
420
+ }
421
+
365
422
  /**
366
423
  * Perform a click on a link or a button, given by a locator.
367
424
  * If a fuzzy locator is given, the page will be searched for a button, link, or image matching the locator string.
@@ -488,6 +545,8 @@ class TestCafe extends Helper {
488
545
  *
489
546
  * ```js
490
547
  * I.appendField('#myTextField', 'appended');
548
+ * // typing secret
549
+ * I.appendField('password', secret('123456'));
491
550
  * ```
492
551
  * @param {CodeceptJS.LocatorOrString} field located by label|name|CSS|XPath|strict locator
493
552
  * @param {string} value text value to append.
@@ -501,7 +560,7 @@ class TestCafe extends Helper {
501
560
  const el = await els.nth(0);
502
561
 
503
562
  return this.t
504
- .typeText(el, value, { replace: false })
563
+ .typeText(el, value.toString(), { replace: false })
505
564
  .catch(mapError);
506
565
  }
507
566
 
@@ -1212,7 +1271,7 @@ class TestCafe extends Helper {
1212
1271
  * âš ī¸ returns a _promise_ which is synchronized internally by recorder
1213
1272
  *
1214
1273
  *
1215
- * If a function returns a Promise It will wait for it resolution.
1274
+ * If a function returns a Promise It will wait for its resolution.
1216
1275
  */
1217
1276
  async executeScript(fn, ...args) {
1218
1277
  const browserFn = createClientFunction(fn, args).with({ boundTestRun: this.t });
@@ -5,6 +5,7 @@ const path = require('path');
5
5
  const fs = require('fs');
6
6
 
7
7
  const Helper = require('@codeceptjs/helper');
8
+ const crypto = require('crypto');
8
9
  const stringIncludes = require('../assert/include').includes;
9
10
  const { urlEquals, equals } = require('../assert/equal');
10
11
  const { debug } = require('../output');
@@ -27,6 +28,10 @@ const {
27
28
  const ElementNotFound = require('./errors/ElementNotFound');
28
29
  const ConnectionRefused = require('./errors/ConnectionRefused');
29
30
  const Locator = require('../locator');
31
+ const { highlightElement } = require('./scripts/highlightElement');
32
+ const store = require('../store');
33
+ const { focusElement } = require('./scripts/focusElement');
34
+ const { blurElement } = require('./scripts/blurElement');
30
35
 
31
36
  const SHADOW = 'shadow';
32
37
  const webRoot = 'body';
@@ -39,7 +44,7 @@ const webRoot = 'body';
39
44
  * @typedef WebDriverConfig
40
45
  * @type {object}
41
46
  * @prop {string} url - base url of website to be tested.
42
- * @prop {string} browser browser in which to perform testing.
47
+ * @prop {string} browser - Browser in which to perform testing.
43
48
  * @prop {string} [basicAuth] - (optional) the basic authentication to pass to base url. Example: {username: 'username', password: 'password'}
44
49
  * @prop {string} [host=localhost] - WebDriver host to connect.
45
50
  * @prop {number} [port=4444] - WebDriver port to connect.
@@ -57,6 +62,7 @@ const webRoot = 'body';
57
62
  * @prop {object} [desiredCapabilities] Selenium's [desired capabilities](https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities).
58
63
  * @prop {boolean} [manualStart=false] - do not start browser before a test, start it manually inside a helper with `this.helpers["WebDriver"]._startBrowser()`.
59
64
  * @prop {object} [timeouts] [WebDriver timeouts](http://webdriver.io/docs/timeouts.html) defined as hash.
65
+ * @prop {boolean} [highlightElement] - highlight the interacting elements
60
66
  */
61
67
  const config = {};
62
68
 
@@ -822,7 +828,7 @@ class WebDriver extends Helper {
822
828
  }
823
829
 
824
830
  /**
825
- * Find a checkbox by providing human readable text:
831
+ * Find a checkbox by providing human-readable text:
826
832
  *
827
833
  * ```js
828
834
  * this.helpers['WebDriver']._locateCheckable('I agree with terms and conditions').then // ...
@@ -835,7 +841,7 @@ class WebDriver extends Helper {
835
841
  }
836
842
 
837
843
  /**
838
- * Find a clickable element by providing human readable text:
844
+ * Find a clickable element by providing human-readable text:
839
845
  *
840
846
  * ```js
841
847
  * const els = await this.helpers.WebDriver._locateClickable('Next page');
@@ -850,7 +856,7 @@ class WebDriver extends Helper {
850
856
  }
851
857
 
852
858
  /**
853
- * Find field elements by providing human readable text:
859
+ * Find field elements by providing human-readable text:
854
860
  *
855
861
  * ```js
856
862
  * this.helpers['WebDriver']._locateFields('Your email').then // ...
@@ -949,6 +955,7 @@ class WebDriver extends Helper {
949
955
  assertElementExists(res, locator, 'Clickable element');
950
956
  }
951
957
  const elem = usingFirstElement(res);
958
+ highlightActiveElement.call(this, elem);
952
959
  return this.browser[clickMethod](getElementId(elem));
953
960
  }
954
961
 
@@ -995,6 +1002,7 @@ class WebDriver extends Helper {
995
1002
  assertElementExists(res, locator, 'Clickable element');
996
1003
  }
997
1004
  const elem = usingFirstElement(res);
1005
+ highlightActiveElement.call(this, elem);
998
1006
 
999
1007
  return this.executeScript((el) => {
1000
1008
  if (document.activeElement instanceof HTMLElement) {
@@ -1035,6 +1043,7 @@ class WebDriver extends Helper {
1035
1043
  }
1036
1044
 
1037
1045
  const elem = usingFirstElement(res);
1046
+ highlightActiveElement.call(this, elem);
1038
1047
  return elem.doubleClick();
1039
1048
  }
1040
1049
 
@@ -1148,6 +1157,7 @@ class WebDriver extends Helper {
1148
1157
  const res = await findFields.call(this, field);
1149
1158
  assertElementExists(res, field, 'Field');
1150
1159
  const elem = usingFirstElement(res);
1160
+ highlightActiveElement.call(this, elem);
1151
1161
  return elem.setValue(value.toString());
1152
1162
  }
1153
1163
 
@@ -1157,6 +1167,8 @@ class WebDriver extends Helper {
1157
1167
  *
1158
1168
  * ```js
1159
1169
  * I.appendField('#myTextField', 'appended');
1170
+ * // typing secret
1171
+ * I.appendField('password', secret('123456'));
1160
1172
  * ```
1161
1173
  * @param {CodeceptJS.LocatorOrString} field located by label|name|CSS|XPath|strict locator
1162
1174
  * @param {string} value text value to append.
@@ -1168,7 +1180,8 @@ class WebDriver extends Helper {
1168
1180
  const res = await findFields.call(this, field);
1169
1181
  assertElementExists(res, field, 'Field');
1170
1182
  const elem = usingFirstElement(res);
1171
- return elem.addValue(value);
1183
+ highlightActiveElement.call(this, elem);
1184
+ return elem.addValue(value.toString());
1172
1185
  }
1173
1186
 
1174
1187
  /**
@@ -1188,6 +1201,7 @@ class WebDriver extends Helper {
1188
1201
  const res = await findFields.call(this, field);
1189
1202
  assertElementExists(res, field, 'Field');
1190
1203
  const elem = usingFirstElement(res);
1204
+ highlightActiveElement.call(this, elem);
1191
1205
  return elem.clearValue(getElementId(elem));
1192
1206
  }
1193
1207
 
@@ -1219,6 +1233,7 @@ class WebDriver extends Helper {
1219
1233
  const res = await findFields.call(this, select);
1220
1234
  assertElementExists(res, select, 'Selectable field');
1221
1235
  const elem = usingFirstElement(res);
1236
+ highlightActiveElement.call(this, elem);
1222
1237
 
1223
1238
  if (!Array.isArray(option)) {
1224
1239
  option = [option];
@@ -1310,6 +1325,7 @@ class WebDriver extends Helper {
1310
1325
  assertElementExists(res, field, 'Checkable');
1311
1326
  const elem = usingFirstElement(res);
1312
1327
  const elementId = getElementId(elem);
1328
+ highlightActiveElement.call(this, elem);
1313
1329
 
1314
1330
  const isSelected = await this.browser.isElementSelected(elementId);
1315
1331
  if (isSelected) return Promise.resolve(true);
@@ -1342,6 +1358,7 @@ class WebDriver extends Helper {
1342
1358
  assertElementExists(res, field, 'Checkable');
1343
1359
  const elem = usingFirstElement(res);
1344
1360
  const elementId = getElementId(elem);
1361
+ highlightActiveElement.call(this, elem);
1345
1362
 
1346
1363
  const isSelected = await this.browser.isElementSelected(elementId);
1347
1364
  if (!isSelected) return Promise.resolve(true);
@@ -2689,6 +2706,9 @@ class WebDriver extends Helper {
2689
2706
  *
2690
2707
  * // passing in an array
2691
2708
  * I.type(['T', 'E', 'X', 'T']);
2709
+ *
2710
+ * // passing a secret
2711
+ * I.type(secret('123456'));
2692
2712
  * ```
2693
2713
  *
2694
2714
  * @param {string|string[]} key or array of keys to type.
@@ -2698,6 +2718,7 @@ class WebDriver extends Helper {
2698
2718
  */
2699
2719
  async type(keys, delay = null) {
2700
2720
  if (!Array.isArray(keys)) {
2721
+ keys = keys.toString();
2701
2722
  keys = keys.split('');
2702
2723
  }
2703
2724
  if (delay) {
@@ -2745,6 +2766,59 @@ class WebDriver extends Helper {
2745
2766
  }
2746
2767
  }
2747
2768
 
2769
+ /**
2770
+ * Calls [focus](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus) on the matching element.
2771
+ *
2772
+ * Examples:
2773
+ *
2774
+ * ```js
2775
+ * I.dontSee('#add-to-cart-btn');
2776
+ * I.focus('#product-tile')
2777
+ * I.see('#add-to-cart-bnt');
2778
+ * ```
2779
+ *
2780
+ * @param {CodeceptJS.LocatorOrString} locator field located by label|name|CSS|XPath|strict locator.
2781
+ * @param {any} [options] Playwright only: [Additional options](https://playwright.dev/docs/api/class-locator#locator-focus) for available options object as 2nd argument.
2782
+ *
2783
+ *
2784
+ */
2785
+ async focus(locator) {
2786
+ const els = await this._locate(locator);
2787
+ assertElementExists(els, locator, 'Element to focus');
2788
+ const el = usingFirstElement(els);
2789
+
2790
+ await focusElement(el, this.browser);
2791
+ }
2792
+
2793
+ /**
2794
+ * Remove focus from a text input, button, etc.
2795
+ * Calls [blur](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus) on the element.
2796
+ *
2797
+ * Examples:
2798
+ *
2799
+ * ```js
2800
+ * I.blur('.text-area')
2801
+ * ```
2802
+ * ```js
2803
+ * //element `#product-tile` is focused
2804
+ * I.see('#add-to-cart-btn');
2805
+ * I.blur('#product-tile')
2806
+ * I.dontSee('#add-to-cart-btn');
2807
+ * ```
2808
+ *
2809
+ * @param {CodeceptJS.LocatorOrString} locator field located by label|name|CSS|XPath|strict locator.
2810
+ * @param {any} [options] Playwright only: [Additional options](https://playwright.dev/docs/api/class-locator#locator-blur) for available options object as 2nd argument.
2811
+ *
2812
+ *
2813
+ */
2814
+ async blur(locator) {
2815
+ const els = await this._locate(locator);
2816
+ assertElementExists(els, locator, 'Element to blur');
2817
+ const el = usingFirstElement(els);
2818
+
2819
+ await blurElement(el, this.browser);
2820
+ }
2821
+
2748
2822
  /**
2749
2823
  * Drag an item to a destination element.
2750
2824
  *
@@ -4000,6 +4074,12 @@ function isModifierKey(key) {
4000
4074
  return unicodeModifierKeys.includes(key);
4001
4075
  }
4002
4076
 
4077
+ function highlightActiveElement(element) {
4078
+ if (!this.options.enableHighlight && !store.debugMode) return;
4079
+
4080
+ highlightElement(element, this.browser);
4081
+ }
4082
+
4003
4083
  function prepareLocateFn(context) {
4004
4084
  if (!context) return this._locate.bind(this);
4005
4085
  return (l) => {
package/docs/changelog.md CHANGED
@@ -7,6 +7,91 @@ layout: Section
7
7
 
8
8
  # Releases
9
9
 
10
+ ## 3.5.1
11
+
12
+ đŸ›Šī¸ Features
13
+
14
+ * [Puppeteer][WebDriver][TestCafe] Added methods by **[KobeNguyenT](https://github.com/KobeNguyenT)** in [#3737](https://github.com/codeceptjs/CodeceptJS/issues/3737)
15
+ * `blur`
16
+ * `focus`
17
+ * Improved BDD output to print steps without `I.` commands` by **[davertmik](https://github.com/davertmik)** [#3739](https://github.com/codeceptjs/CodeceptJS/issues/3739)
18
+ * Improved `codecept init` setup for Electron tests by **[KobeNguyenT](https://github.com/KobeNguyenT)**. See [#3733](https://github.com/codeceptjs/CodeceptJS/issues/3733)
19
+
20
+ 🐛 Bug Fixes
21
+
22
+ * Fixed serializing of custom errors making tests stuck. Fix [#3739](https://github.com/codeceptjs/CodeceptJS/issues/3739) by **[davertmik](https://github.com/davertmik)**.
23
+
24
+ 📖 Documentation
25
+
26
+ * Fixed Playwright docs by **[Horsty80](https://github.com/Horsty80)**
27
+ * Fixed ai docs by **[ngraf](https://github.com/ngraf)**
28
+ * Various fixes by **[KobeNguyenT](https://github.com/KobeNguyenT)**
29
+
30
+ ## 3.5.0
31
+
32
+ đŸ›Šī¸ Features
33
+
34
+ - **đŸĒ„ [AI Powered Test Automation](/ai)** - use OpenAI as a copilot for test automation. [#3713](https://github.com/codeceptjs/CodeceptJS/issues/3713) By **[davertmik](https://github.com/davertmik)**
35
+ ![](https://user-images.githubusercontent.com/220264/250418764-c382709a-3ccb-4eb5-b6bc-538f3b3b3d35.png)
36
+ * [AI guide](/ai) added
37
+ * added support for OpenAI in `pause()`
38
+ * added [`heal` plugin](/plugins#heal) for self-healing tests
39
+ * added [`OpenAI`](/helpers/openai) helper
40
+
41
+
42
+ - [Playwright][Puppeteer][WebDriver] Highlight the interacting elements in debug mode or with `highlightElement` option set ([#3672](https://github.com/codeceptjs/CodeceptJS/issues/3672)) - by **[KobeNguyenT](https://github.com/KobeNguyenT)**
43
+
44
+ ![](https://user-images.githubusercontent.com/220264/250415226-a7620418-56a4-4837-b790-b15e91e5d1f0.png)
45
+
46
+ - **[Playwright]** Support for APIs in Playwright ([#3665](https://github.com/codeceptjs/CodeceptJS/issues/3665)) - by Egor Bodnar
47
+ * `clearField` replaced to use new Playwright API
48
+ * `blur` added
49
+ * `focus` added
50
+
51
+ - **[Added support for multiple browsers](/parallel#Parallel-Execution-by-Workers-on-Multiple-Browsers)** in `run-workers` ([#3606](https://github.com/codeceptjs/CodeceptJS/issues/3606)) by **[karanshah-browserstack](https://github.com/karanshah-browserstack)** :
52
+
53
+ Multiple browsers configured as profiles:
54
+
55
+ ```js
56
+ exports.config = {
57
+ helpers: {
58
+ WebDriver: {
59
+ url: 'http://localhost:3000',
60
+ }
61
+ },
62
+ multiple: {
63
+ profile1: {
64
+ browsers: [
65
+ {
66
+ browser: "firefox",
67
+ },
68
+ {
69
+ browser: "chrome",
70
+ }
71
+ ]
72
+ },
73
+ ```
74
+ And executed via `run-workers` with `all` argument
75
+
76
+ ```
77
+ npx codeceptjs run-workers 2 all
78
+ ```
79
+
80
+ - **[Appium]** Add Appium v2 support ([#3622](https://github.com/codeceptjs/CodeceptJS/issues/3622)) - by **[KobeNguyenT](https://github.com/KobeNguyenT)**
81
+ - Improve `gpo` command to create page objects as modules or as classes ([#3625](https://github.com/codeceptjs/CodeceptJS/issues/3625)) - by **[KobeNguyenT](https://github.com/KobeNguyenT)**
82
+ - Added `emptyOutputFolder` config to clean up output before running tests ([#3604](https://github.com/codeceptjs/CodeceptJS/issues/3604)) - by **[KobeNguyenT](https://github.com/KobeNguyenT)**
83
+ - Add `secret()` function support to `append()` and `type()` ([#3615](https://github.com/codeceptjs/CodeceptJS/issues/3615)) - by **[anils92](https://github.com/anils92)**
84
+ - **[Playwright]** Add `bypassCSP` option to helper's config ([#3641](https://github.com/codeceptjs/CodeceptJS/issues/3641)) - by **[KobeNguyenT](https://github.com/KobeNguyenT)**
85
+ - Print number of tests for each suite in dryRun ([#3620](https://github.com/codeceptjs/CodeceptJS/issues/3620)) - by **[KobeNguyenT](https://github.com/KobeNguyenT)**
86
+
87
+ 🐛 Bug Fixes
88
+
89
+ - Support `--grep` in dry-run command ([#3673](https://github.com/codeceptjs/CodeceptJS/issues/3673)) - by **[KobeNguyenT](https://github.com/KobeNguyenT)**
90
+ - Fix typings improvements in playwright ([#3650](https://github.com/codeceptjs/CodeceptJS/issues/3650)) - by **[KobeNguyenT](https://github.com/KobeNguyenT)**
91
+ - Fixed global retry [#3667](https://github.com/codeceptjs/CodeceptJS/issues/3667) by **[KobeNguyenT](https://github.com/KobeNguyenT)**
92
+ - Fixed creating JavaScript test using "codeceptjs gt" ([#3611](https://github.com/codeceptjs/CodeceptJS/issues/3611)) - by Jaromir Obr
93
+
94
+
10
95
  ## 3.4.1
11
96
 
12
97
  * Updated mocha to v 10.2. Fixes [#3591](https://github.com/codeceptjs/CodeceptJS/issues/3591)