codeceptjs 3.3.3 → 3.3.5-beta.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 (73) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/docs/api.md +4 -0
  3. package/docs/basics.md +2 -0
  4. package/docs/bdd.md +12 -0
  5. package/docs/build/JSONResponse.js +44 -3
  6. package/docs/build/Playwright.js +63 -40
  7. package/docs/build/Puppeteer.js +54 -43
  8. package/docs/build/REST.js +23 -9
  9. package/docs/build/WebDriver.js +39 -30
  10. package/docs/community-helpers.md +1 -0
  11. package/docs/configuration.md +21 -18
  12. package/docs/helpers/Appium.md +0 -723
  13. package/docs/helpers/JSONResponse.md +24 -0
  14. package/docs/helpers/Playwright.md +276 -264
  15. package/docs/helpers/Puppeteer.md +230 -222
  16. package/docs/helpers/REST.md +21 -6
  17. package/docs/helpers/WebDriver.md +265 -259
  18. package/docs/plugins.md +41 -1
  19. package/docs/reports.md +11 -0
  20. package/docs/secrets.md +30 -0
  21. package/docs/wiki/.git/FETCH_HEAD +1 -0
  22. package/docs/wiki/.git/HEAD +1 -0
  23. package/docs/wiki/.git/ORIG_HEAD +1 -0
  24. package/docs/wiki/.git/config +11 -0
  25. package/docs/wiki/.git/description +1 -0
  26. package/docs/wiki/.git/hooks/applypatch-msg.sample +15 -0
  27. package/docs/wiki/.git/hooks/commit-msg.sample +24 -0
  28. package/docs/wiki/.git/hooks/fsmonitor-watchman.sample +173 -0
  29. package/docs/wiki/.git/hooks/post-update.sample +8 -0
  30. package/docs/wiki/.git/hooks/pre-applypatch.sample +14 -0
  31. package/docs/wiki/.git/hooks/pre-commit.sample +49 -0
  32. package/docs/wiki/.git/hooks/pre-merge-commit.sample +13 -0
  33. package/docs/wiki/.git/hooks/pre-push.sample +53 -0
  34. package/docs/wiki/.git/hooks/pre-rebase.sample +169 -0
  35. package/docs/wiki/.git/hooks/pre-receive.sample +24 -0
  36. package/docs/wiki/.git/hooks/prepare-commit-msg.sample +42 -0
  37. package/docs/wiki/.git/hooks/push-to-checkout.sample +78 -0
  38. package/docs/wiki/.git/hooks/update.sample +128 -0
  39. package/docs/wiki/.git/index +0 -0
  40. package/docs/wiki/.git/info/exclude +6 -0
  41. package/docs/wiki/.git/logs/HEAD +1 -0
  42. package/docs/wiki/.git/logs/refs/heads/master +1 -0
  43. package/docs/wiki/.git/logs/refs/remotes/origin/HEAD +1 -0
  44. package/docs/wiki/.git/objects/pack/pack-5938044f9d30daf1c195fda4dec1d54850933935.idx +0 -0
  45. package/docs/wiki/.git/objects/pack/pack-5938044f9d30daf1c195fda4dec1d54850933935.pack +0 -0
  46. package/docs/wiki/.git/packed-refs +2 -0
  47. package/docs/wiki/.git/refs/heads/master +1 -0
  48. package/docs/wiki/.git/refs/remotes/origin/HEAD +1 -0
  49. package/docs/wiki/Community-Helpers-&-Plugins.md +7 -3
  50. package/docs/wiki/Converting-Playwright-to-Istanbul-Coverage.md +29 -0
  51. package/docs/wiki/Examples.md +39 -48
  52. package/docs/wiki/Release-Process.md +8 -8
  53. package/docs/wiki/Tests.md +62 -60
  54. package/docs/wiki/Upgrading-to-CodeceptJS-3.md +2 -2
  55. package/lib/command/generate.js +3 -0
  56. package/lib/command/init.js +88 -41
  57. package/lib/command/interactive.js +1 -1
  58. package/lib/config.js +9 -0
  59. package/lib/helper/JSONResponse.js +44 -3
  60. package/lib/helper/Playwright.js +63 -40
  61. package/lib/helper/Puppeteer.js +54 -43
  62. package/lib/helper/REST.js +23 -9
  63. package/lib/helper/WebDriver.js +39 -30
  64. package/lib/interfaces/gherkin.js +1 -1
  65. package/lib/plugin/customLocator.js +50 -3
  66. package/lib/plugin/retryFailedStep.js +1 -1
  67. package/lib/plugin/retryTo.js +1 -8
  68. package/lib/secret.js +31 -1
  69. package/lib/step.js +22 -10
  70. package/lib/utils.js +1 -6
  71. package/package.json +4 -4
  72. package/typings/index.d.ts +158 -0
  73. package/typings/types.d.ts +367 -96
@@ -39,6 +39,35 @@ let perfTiming;
39
39
  const popupStore = new Popup();
40
40
  const consoleLogStore = new Console();
41
41
 
42
+ /**
43
+ * ## Configuration
44
+ *
45
+ * This helper should be configured in codecept.conf.js
46
+ *
47
+ * @typedef PuppeteerConfig
48
+ * @type {object}
49
+ * @prop {string} url - base url of website to be tested
50
+ * @prop {object} [basicAuth] (optional) the basic authentication to pass to base url. Example: {username: 'username', password: 'password'}
51
+ * @prop {boolean} [show] - show Google Chrome window for debug.
52
+ * @prop {boolean} [restart=true] - restart browser between tests.
53
+ * @prop {boolean} [disableScreenshots=false] - don't save screenshot on failure.
54
+ * @prop {boolean} [fullPageScreenshots=false] - make full page screenshots on failure.
55
+ * @prop {boolean} [uniqueScreenshotNames=false] - option to prevent screenshot override if you have scenarios with the same name in different suites.
56
+ * @prop {boolean} [keepBrowserState=false] - keep browser state between tests when `restart` is set to false.
57
+ * @prop {boolean} [keepCookies=false] - keep cookies between tests when `restart` is set to false.
58
+ * @prop {number} [waitForAction=100] - how long to wait after click, doubleClick or PressKey actions in ms. Default: 100.
59
+ * @prop {string} [waitForNavigation=load] - when to consider navigation succeeded. Possible options: `load`, `domcontentloaded`, `networkidle0`, `networkidle2`. See [Puppeteer API](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagewaitfornavigationoptions). Array values are accepted as well.
60
+ * @prop {number} [pressKeyDelay=10] - delay between key presses in ms. Used when calling Puppeteers page.type(...) in fillField/appendField
61
+ * @prop {number} [getPageTimeout=30000] - config option to set maximum navigation time in milliseconds. If the timeout is set to 0, then timeout will be disabled.
62
+ * @prop {number} [waitForTimeout=1000] - default wait* timeout in ms.
63
+ * @prop {string} [windowSize] - default window size. Set a dimension in format WIDTHxHEIGHT like `640x480`.
64
+ * @prop {string} [userAgent] - user-agent string.
65
+ * @prop {boolean} [manualStart=false] - do not start browser before a test, start it manually inside a helper with `this.helpers["Puppeteer"]._startBrowser()`.
66
+ * @prop {string} [browser=chrome] - can be changed to `firefox` when using [puppeteer-firefox](https://codecept.io/helpers/Puppeteer-firefox).
67
+ * @prop {object} [chrome] - pass additional [Puppeteer run options](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions).
68
+ */
69
+ const config = {};
70
+
42
71
  /**
43
72
  * Uses [Google Chrome's Puppeteer](https://github.com/GoogleChrome/puppeteer) library to run tests inside headless Chrome.
44
73
  * Browser control is executed via DevTools Protocol (instead of Selenium).
@@ -56,30 +85,7 @@ const consoleLogStore = new Console();
56
85
  *
57
86
  * > Experimental Firefox support [can be activated](https://codecept.io/helpers/Puppeteer-firefox).
58
87
  *
59
- * ## Configuration
60
- *
61
- * This helper should be configured in codecept.json or codecept.conf.js
62
- *
63
- * * `url`: base url of website to be tested
64
- * * `basicAuth`: (optional) the basic authentication to pass to base url. Example: {username: 'username', password: 'password'}
65
- * * `show`: (optional, default: false) - show Google Chrome window for debug.
66
- * * `restart`: (optional, default: true) - restart browser between tests.
67
- * * `disableScreenshots`: (optional, default: false) - don't save screenshot on failure.
68
- * * `fullPageScreenshots` (optional, default: false) - make full page screenshots on failure.
69
- * * `uniqueScreenshotNames`: (optional, default: false) - option to prevent screenshot override if you have scenarios with the same name in different suites.
70
- * * `keepBrowserState`: (optional, default: false) - keep browser state between tests when `restart` is set to false.
71
- * * `keepCookies`: (optional, default: false) - keep cookies between tests when `restart` is set to false.
72
- * * `waitForAction`: (optional) how long to wait after click, doubleClick or PressKey actions in ms. Default: 100.
73
- * * `waitForNavigation`: (optional, default: 'load'). When to consider navigation succeeded. Possible options: `load`, `domcontentloaded`, `networkidle0`, `networkidle2`. See [Puppeteer API](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagewaitfornavigationoptions). Array values are accepted as well.
74
- * * `pressKeyDelay`: (optional, default: '10'). Delay between key presses in ms. Used when calling Puppeteers page.type(...) in fillField/appendField
75
- * * `getPageTimeout` (optional, default: '30000') config option to set maximum navigation time in milliseconds. If the timeout is set to 0, then timeout will be disabled.
76
- * * `waitForTimeout`: (optional) default wait* timeout in ms. Default: 1000.
77
- * * `windowSize`: (optional) default window size. Set a dimension like `640x480`.
78
- * * `userAgent`: (optional) user-agent string.
79
- * * `manualStart`: (optional, default: false) - do not start browser before a test, start it manually inside a helper with `this.helpers["Puppeteer"]._startBrowser()`.
80
- * * `browser`: (optional, default: chrome) - can be changed to `firefox` when using [puppeteer-firefox](https://codecept.io/helpers/Puppeteer-firefox).
81
- * * `chrome`: (optional) pass additional [Puppeteer run options](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions).
82
- *
88
+ * <!-- configuration -->
83
89
  *
84
90
  * #### Example #1: Wait for 0 network connections.
85
91
  *
@@ -192,7 +198,6 @@ class Puppeteer extends Helper {
192
198
  super(config);
193
199
 
194
200
  puppeteer = requireWithFallback('puppeteer', 'puppeteer-core');
195
-
196
201
  // set defaults
197
202
  this.isRemoteBrowser = false;
198
203
  this.isRunning = false;
@@ -372,22 +377,22 @@ class Puppeteer extends Helper {
372
377
  }
373
378
 
374
379
  /**
375
- * Use Puppeteer API inside a test.
376
- *
377
- * First argument is a description of an action.
378
- * Second argument is async function that gets this helper as parameter.
379
- *
380
- * { [`page`](https://github.com/puppeteer/puppeteer/blob/master/docs/api.md#class-page), [`browser`](https://github.com/puppeteer/puppeteer/blob/master/docs/api.md#class-browser) } from Puppeteer API are available.
381
- *
382
- * ```js
383
- * I.usePuppeteerTo('emulate offline mode', async ({ page }) {
384
- * await page.setOfflineMode(true);
385
- * });
386
- * ```
387
- *
388
- * @param {string} description used to show in logs.
389
- * @param {function} fn async function that is executed with Puppeteer as argument
390
- */
380
+ * Use Puppeteer API inside a test.
381
+ *
382
+ * First argument is a description of an action.
383
+ * Second argument is async function that gets this helper as parameter.
384
+ *
385
+ * { [`page`](https://github.com/puppeteer/puppeteer/blob/master/docs/api.md#class-page), [`browser`](https://github.com/puppeteer/puppeteer/blob/master/docs/api.md#class-browser) } from Puppeteer API are available.
386
+ *
387
+ * ```js
388
+ * I.usePuppeteerTo('emulate offline mode', async ({ page }) {
389
+ * await page.setOfflineMode(true);
390
+ * });
391
+ * ```
392
+ *
393
+ * @param {string} description used to show in logs.
394
+ * @param {function} fn async function that is executed with Puppeteer as argument
395
+ */
391
396
  usePuppeteerTo(description, fn) {
392
397
  return this._useTo(...arguments);
393
398
  }
@@ -749,7 +754,8 @@ class Puppeteer extends Helper {
749
754
  if (locator) {
750
755
  const els = await this._locate(locator);
751
756
  assertElementExists(els, locator, 'Element');
752
- await els[0]._scrollIntoViewIfNeeded();
757
+ const el = els[0];
758
+ await el.evaluate((el) => el.scrollIntoView());
753
759
  const elementCoordinates = await getClickablePoint(els[0]);
754
760
  await this.executeScript((x, y) => window.scrollBy(x, y), elementCoordinates.x + offsetX, elementCoordinates.y + offsetY);
755
761
  } else {
@@ -1069,7 +1075,12 @@ class Puppeteer extends Helper {
1069
1075
  fs.mkdirSync(downloadPath, '0777');
1070
1076
  }
1071
1077
  fsExtra.emptyDirSync(downloadPath);
1072
- return this.page._client.send('Page.setDownloadBehavior', { behavior: 'allow', downloadPath });
1078
+
1079
+ try {
1080
+ return this.page._client.send('Page.setDownloadBehavior', { behavior: 'allow', downloadPath });
1081
+ } catch (e) {
1082
+ return this.page._client().send('Page.setDownloadBehavior', { behavior: 'allow', downloadPath });
1083
+ }
1073
1084
  }
1074
1085
 
1075
1086
  /**
@@ -2,18 +2,28 @@ const axios = require('axios').default;
2
2
  const Secret = require('../secret');
3
3
 
4
4
  const Helper = require('../helper');
5
+ const { beautify } = require('../utils');
6
+
7
+ /**
8
+ * ## Configuration
9
+ *
10
+ * @typedef RESTConfig
11
+ * @type {object}
12
+ * @prop {string} endpoint - API base URL
13
+ * @prop {boolean} [prettyPrintJson=false] - pretty print json for response/request on console logs
14
+ * @prop {number} [timeout=1000] - timeout for requests in milliseconds. 10000ms by default
15
+ * @prop {object} [defaultHeaders] - a list of default headers
16
+ * @prop {function} [onRequest] - a async function which can update request object.
17
+ * @prop {function} [onResponse] - a async function which can update response object.
18
+ * @prop {number} [maxUploadFileSize] - set the max content file size in MB when performing api calls.
19
+ */
20
+ const config = {};
5
21
 
6
22
  /**
7
23
  * REST helper allows to send additional requests to the REST API during acceptance tests.
8
24
  * [Axios](https://github.com/axios/axios) library is used to perform requests.
9
25
  *
10
- * ## Configuration
11
- *
12
- * * endpoint: API base URL
13
- * * timeout: timeout for requests in milliseconds. 10000ms by default
14
- * * defaultHeaders: a list of default headers
15
- * * onRequest: a async function which can update request object.
16
- * * maxUploadFileSize: set the max content file size in MB when performing api calls.
26
+ * <!-- configuration -->
17
27
  *
18
28
  * ## Example
19
29
  *
@@ -22,6 +32,7 @@ const Helper = require('../helper');
22
32
  * helpers: {
23
33
  * REST: {
24
34
  * endpoint: 'http://site.com/api',
35
+ * prettyPrintJson: true,
25
36
  * onRequest: (request) => {
26
37
  * request.headers.auth = '123';
27
38
  * }
@@ -49,6 +60,9 @@ class REST extends Helper {
49
60
  timeout: 10000,
50
61
  defaultHeaders: {},
51
62
  endpoint: '',
63
+ prettyPrintJson: false,
64
+ onRequest: null,
65
+ onResponse: null,
52
66
  };
53
67
 
54
68
  if (this.options.maxContentLength) {
@@ -137,7 +151,7 @@ class REST extends Helper {
137
151
  await this.config.onRequest(request);
138
152
  }
139
153
 
140
- this.debugSection('Request', JSON.stringify(_debugRequest));
154
+ this.options.prettyPrintJson ? this.debugSection('Request', beautify(JSON.stringify(_debugRequest))) : this.debugSection('Request', JSON.stringify(_debugRequest));
141
155
 
142
156
  let response;
143
157
  try {
@@ -150,7 +164,7 @@ class REST extends Helper {
150
164
  if (this.config.onResponse) {
151
165
  await this.config.onResponse(response);
152
166
  }
153
- this.debugSection('Response', JSON.stringify(response.data));
167
+ this.options.prettyPrintJson ? this.debugSection('Response', beautify(JSON.stringify(response.data))) : this.debugSection('Response', JSON.stringify(response.data));
154
168
  return response;
155
169
  }
156
170
 
@@ -32,37 +32,43 @@ const SHADOW = 'shadow';
32
32
  const webRoot = 'body';
33
33
 
34
34
  let version;
35
+
36
+ /**
37
+ * ## Configuration
38
+ *
39
+ * This helper should be configured in codecept.conf.js
40
+ *
41
+ * @typedef WebDriverConfig
42
+ * @type {object}
43
+ * @prop {string} url - base url of website to be tested.
44
+ * @prop {string} browser browser in which to perform testing.
45
+ * @prop {string} [basicAuth] - (optional) the basic authentication to pass to base url. Example: {username: 'username', password: 'password'}
46
+ * @prop {string} [host=localhost] - WebDriver host to connect.
47
+ * @prop {string} [port=4444] - WebDriver port to connect.
48
+ * @prop {string} [protocol=http] - protocol for WebDriver server.
49
+ * @prop {string} [path=/wd/hub] - path to WebDriver server,
50
+ * @prop {boolean} [restart=true] - restart browser between tests.
51
+ * @prop {boolean} [smartWait=false] - **enables [SmartWait](http://codecept.io/acceptance/#smartwait)**; wait for additional milliseconds for element to appear. Enable for 5 secs: "smartWait": 5000.
52
+ * @prop {boolean} [disableScreenshots=false] - don't save screenshots on failure.
53
+ * @prop {boolean} [fullPageScreenshots=false] (optional - make full page screenshots on failure.
54
+ * @prop {boolean} [uniqueScreenshotNames=false] - option to prevent screenshot override if you have scenarios with the same name in different suites.
55
+ * @prop {boolean} [keepBrowserState=false] - keep browser state between tests when `restart` is set to false.
56
+ * @prop {boolean} [keepCookies=false] - keep cookies between tests when `restart` set to false.
57
+ * @prop {string} [windowSize=window] default window size. Set to `maximize` or a dimension in the format `640x480`.
58
+ * @prop {number} [waitForTimeout=1000] sets default wait time in *ms* for all `wait*` functions.
59
+ * @prop {object} [desiredCapabilities] Selenium's [desired capabilities](https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities).
60
+ * @prop {boolean} [manualStart=false] - do not start browser before a test, start it manually inside a helper with `this.helpers["WebDriver"]._startBrowser()`.
61
+ * @prop {object} [timeouts] [WebDriver timeouts](http://webdriver.io/docs/timeouts.html) defined as hash.
62
+ */
63
+ const config = {};
64
+
35
65
  /**
36
66
  * WebDriver helper which wraps [webdriverio](http://webdriver.io/) library to
37
67
  * manipulate browser using Selenium WebDriver or PhantomJS.
38
68
  *
39
69
  * WebDriver requires Selenium Server and ChromeDriver/GeckoDriver to be installed. Those tools can be easily installed via NPM. Please check [Testing with WebDriver](https://codecept.io/webdriver/#testing-with-webdriver) for more details.
40
70
  *
41
- * ### Configuration
42
- *
43
- * This helper should be configured in codecept.json or codecept.conf.js
44
- *
45
- * * `url`: base url of website to be tested.
46
- * * `basicAuth`: (optional) the basic authentication to pass to base url. Example: {username: 'username', password: 'password'}
47
- * * `browser`: browser in which to perform testing.
48
- * * `host`: (optional, default: localhost) - WebDriver host to connect.
49
- * * `port`: (optional, default: 4444) - WebDriver port to connect.
50
- * * `protocol`: (optional, default: http) - protocol for WebDriver server.
51
- * * `path`: (optional, default: /wd/hub) - path to WebDriver server,
52
- * * `restart`: (optional, default: true) - restart browser between tests.
53
- * * `smartWait`: (optional) **enables [SmartWait](http://codecept.io/acceptance/#smartwait)**; wait for additional milliseconds for element to appear. Enable for 5 secs: "smartWait": 5000.
54
- * * `disableScreenshots`: (optional, default: false) - don't save screenshots on failure.
55
- * * `fullPageScreenshots` (optional, default: false) - make full page screenshots on failure.
56
- * * `uniqueScreenshotNames`: (optional, default: false) - option to prevent screenshot override if you have scenarios with the same name in different suites.
57
- * * `keepBrowserState`: (optional, default: false) - keep browser state between tests when `restart` is set to false.
58
- * * `keepCookies`: (optional, default: false) - keep cookies between tests when `restart` set to false.
59
- * * `windowSize`: (optional) default window size. Set to `maximize` or a dimension in the format `640x480`.
60
- * * `waitForTimeout`: (optional, default: 1000) sets default wait time in *ms* for all `wait*` functions.
61
- * * `desiredCapabilities`: Selenium's [desired
62
- * capabilities](https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities).
63
- * * `manualStart`: (optional, default: false) - do not start browser before a test, start it manually inside a helper
64
- * with `this.helpers["WebDriver"]._startBrowser()`.
65
- * * `timeouts`: [WebDriver timeouts](http://webdriver.io/docs/timeouts.html) defined as hash.
71
+ * <!-- configuration -->
66
72
  *
67
73
  * Example:
68
74
  *
@@ -567,6 +573,9 @@ class WebDriver extends Helper {
567
573
  });
568
574
  }
569
575
 
576
+ if (this.browser.capabilities && this.browser.capabilities.platformName) {
577
+ this.browser.capabilities.platformName = this.browser.capabilities.platformName.toLowerCase();
578
+ }
570
579
  return this.browser;
571
580
  }
572
581
 
@@ -907,7 +916,7 @@ class WebDriver extends Helper {
907
916
  * {{ react }}
908
917
  */
909
918
  async click(locator, context = null) {
910
- const clickMethod = this.browser.isMobile ? 'touchClick' : 'elementClick';
919
+ const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick';
911
920
  const locateFn = prepareLocateFn.call(this, context);
912
921
 
913
922
  const res = await findClickable.call(this, locator, locateFn);
@@ -1117,7 +1126,7 @@ class WebDriver extends Helper {
1117
1126
  * Appium: not tested
1118
1127
  */
1119
1128
  async checkOption(field, context = null) {
1120
- const clickMethod = this.browser.isMobile ? 'touchClick' : 'elementClick';
1129
+ const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick';
1121
1130
  const locateFn = prepareLocateFn.call(this, context);
1122
1131
 
1123
1132
  const res = await findCheckable.call(this, field, locateFn);
@@ -1136,7 +1145,7 @@ class WebDriver extends Helper {
1136
1145
  * Appium: not tested
1137
1146
  */
1138
1147
  async uncheckOption(field, context = null) {
1139
- const clickMethod = this.browser.isMobile ? 'touchClick' : 'elementClick';
1148
+ const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick';
1140
1149
  const locateFn = prepareLocateFn.call(this, context);
1141
1150
 
1142
1151
  const res = await findCheckable.call(this, field, locateFn);
@@ -1623,7 +1632,7 @@ class WebDriver extends Helper {
1623
1632
  assertElementExists(res);
1624
1633
  const elem = usingFirstElement(res);
1625
1634
  const elementId = getElementId(elem);
1626
- if (this.browser.isMobile) return this.browser.touchScroll(offsetX, offsetY, elementId);
1635
+ if (this.browser.isMobile && this.browser.capabilities.platformName !== 'android') return this.browser.touchScroll(offsetX, offsetY, elementId);
1627
1636
  const location = await elem.getLocation();
1628
1637
  assertElementExists(location, 'Failed to receive', 'location');
1629
1638
  /* eslint-disable prefer-arrow-callback */
@@ -1631,7 +1640,7 @@ class WebDriver extends Helper {
1631
1640
  /* eslint-enable */
1632
1641
  }
1633
1642
 
1634
- if (this.browser.isMobile) return this.browser.touchScroll(locator, offsetX, offsetY);
1643
+ if (this.browser.isMobile && this.browser.capabilities.platformName !== 'android') return this.browser.touchScroll(locator, offsetX, offsetY);
1635
1644
 
1636
1645
  /* eslint-disable prefer-arrow-callback, comma-dangle */
1637
1646
  return this.browser.execute(function (x, y) { return window.scrollTo(x, y); }, offsetX, offsetY);
@@ -91,7 +91,7 @@ module.exports = (text, file) => {
91
91
  current[placeholder] = value;
92
92
  exampleSteps = exampleSteps.map((step) => {
93
93
  step = { ...step };
94
- step.text = step.text.replace(`<${placeholder}>`, value);
94
+ step.text = step.text.split(`<${placeholder}>`).join(value);
95
95
  return step;
96
96
  });
97
97
  }
@@ -70,23 +70,70 @@ const defaultConfig = {
70
70
  * I.seeElement('=user'); // matches => [data-qa=user]
71
71
  * I.click('=sign-up'); // matches => [data-qa=sign-up]
72
72
  * ```
73
+ *
74
+ * Using `data-qa` OR `data-test` attribute with `=` prefix:
75
+ *
76
+ * ```js
77
+ * // in codecept.conf.js
78
+ * plugins: {
79
+ * customLocator: {
80
+ * enabled: true,
81
+ * prefix: '=',
82
+ * attribute: ['data-qa', 'data-test'],
83
+ * strategy: 'xpath'
84
+ * }
85
+ * }
86
+ * ```
87
+ *
88
+ * In a test:
89
+ *
90
+ * ```js
91
+ * I.seeElement('=user'); // matches => //*[@data-qa=user or @data-test=user]
92
+ * I.click('=sign-up'); // matches => //*[data-qa=sign-up or @data-test=sign-up]
93
+ * ```
94
+ *
95
+ * ```js
96
+ * // in codecept.conf.js
97
+ * plugins: {
98
+ * customLocator: {
99
+ * enabled: true,
100
+ * prefix: '=',
101
+ * attribute: ['data-qa', 'data-test'],
102
+ * strategy: 'css'
103
+ * }
104
+ * }
105
+ * ```
106
+ *
107
+ * In a test:
108
+ *
109
+ * ```js
110
+ * I.seeElement('=user'); // matches => [data-qa=user],[data-test=user]
111
+ * I.click('=sign-up'); // matches => [data-qa=sign-up],[data-test=sign-up]
112
+ * ```
73
113
  */
74
114
  module.exports = (config) => {
75
- config = Object.assign(defaultConfig, config);
115
+ config = { ...defaultConfig, ...config };
76
116
 
77
117
  Locator.addFilter((value, locatorObj) => {
78
118
  if (typeof value !== 'string') return;
79
119
  if (!value.startsWith(config.prefix)) return;
80
120
 
121
+ if (!['String', 'Array'].includes(config.attribute.constructor.name)) return;
122
+
81
123
  const val = value.substr(config.prefix.length);
82
124
 
83
125
  if (config.strategy.toLowerCase() === 'xpath') {
84
- locatorObj.value = `.//*[@${config.attribute}=${xpathLocator.literal(val)}]`;
126
+ locatorObj.value = `.//*[${
127
+ [].concat(config.attribute)
128
+ .map((attr) => `@${attr}=${xpathLocator.literal(val)}`)
129
+ .join(' or ')}]`;
85
130
  locatorObj.type = 'xpath';
86
131
  }
87
132
 
88
133
  if (config.strategy.toLowerCase() === 'css') {
89
- locatorObj.value = `[${config.attribute}=${val}]`;
134
+ locatorObj.value = [].concat(config.attribute)
135
+ .map((attr) => `[${attr}=${val}]`)
136
+ .join(',');
90
137
  locatorObj.type = 'css';
91
138
  }
92
139
 
@@ -60,7 +60,7 @@ const defaultConfig = {
60
60
  * plugins: {
61
61
  * retryFailedStep: {
62
62
  * enabled: true,
63
- * ignoreSteps: [
63
+ * ignoredSteps: [
64
64
  * 'scroll*', // ignore all scroll steps
65
65
  * /Cookie/, // ignore all steps with a Cookie in it (by regexp)
66
66
  * ]
@@ -101,27 +101,20 @@ module.exports = function (config) {
101
101
  err = e;
102
102
  recorder.session.restore(`retryTo ${tries}`);
103
103
  tries++;
104
- // recorder.session.restore(`retryTo`);
105
104
  if (tries <= maxTries) {
106
105
  debug(`Error ${err}... Retrying`);
107
106
  err = null;
108
107
 
109
- recorder.add(`retryTo ${tries}`, () => {
110
- tryBlock();
111
- // recorder.add(() => new Promise(done => setTimeout(done, pollInterval)));
112
- });
108
+ recorder.add(`retryTo ${tries}`, () => setTimeout(tryBlock, pollInterval));
113
109
  } else {
114
- // recorder.throw(err);
115
110
  done(null);
116
111
  }
117
112
  });
118
- // return recorder.promise();
119
113
  };
120
114
 
121
115
  recorder.add('retryTo', async () => {
122
116
  store.debugMode = true;
123
117
  tryBlock();
124
- // recorder.add(() => recorder.session.restore(`retryTo ${tries-1}`));
125
118
  });
126
119
  }).then(() => {
127
120
  if (err) recorder.throw(err);
package/lib/secret.js CHANGED
@@ -1,3 +1,6 @@
1
+ /* eslint-disable max-classes-per-file */
2
+ const { deepClone } = require('./utils');
3
+
1
4
  /** @param {string} secret */
2
5
  class Secret {
3
6
  constructor(secret) {
@@ -9,13 +12,40 @@ class Secret {
9
12
  return this._secret;
10
13
  }
11
14
 
15
+ getMasked() {
16
+ return '*****';
17
+ }
18
+
12
19
  /**
13
- * @param {*} secret
20
+ * @param {...*} secret
14
21
  * @returns {Secret}
15
22
  */
16
23
  static secret(secret) {
24
+ if (typeof secret === 'object') {
25
+ const fields = Array.from(arguments);
26
+ fields.shift();
27
+ return secretObject(secret, fields);
28
+ }
17
29
  return new Secret(secret);
18
30
  }
19
31
  }
20
32
 
33
+ function secretObject(obj, fieldsToHide = []) {
34
+ const handler = {
35
+ get(obj, prop) {
36
+ if (prop === 'toString') {
37
+ return function () {
38
+ const maskedObject = deepClone(obj);
39
+ fieldsToHide.forEach(f => maskedObject[f] = '****');
40
+ return JSON.stringify(maskedObject);
41
+ };
42
+ }
43
+
44
+ return obj[prop];
45
+ },
46
+ };
47
+
48
+ return new Proxy(obj, handler);
49
+ }
50
+
21
51
  module.exports = Secret;
package/lib/step.js CHANGED
@@ -168,7 +168,7 @@ class Step {
168
168
  } else if (typeof arg === 'undefined') {
169
169
  return `${arg}`;
170
170
  } else if (arg instanceof Secret) {
171
- return '*****';
171
+ return arg.getMasked();
172
172
  } else if (arg.toString && arg.toString() !== '[object Object]') {
173
173
  return arg.toString();
174
174
  } else if (typeof arg === 'object') {
@@ -269,15 +269,27 @@ class MetaStep extends Step {
269
269
  };
270
270
  event.dispatcher.prependListener(event.step.before, registerStep);
271
271
  let rethrownError = null;
272
- try {
273
- this.startTime = Date.now();
274
- result = fn.apply(this.context, this.args);
275
- } catch (error) {
276
- this.setStatus('failed');
277
- rethrownError = error;
278
- } finally {
279
- this.endTime = Date.now();
280
- event.dispatcher.removeListener(event.step.before, registerStep);
272
+ if (fn.constructor.name === 'AsyncFunction') {
273
+ result = fn.apply(this.context, this.args).then((result) => {
274
+ return result;
275
+ }).catch(function (error) {
276
+ this.setStatus('failed');
277
+ rethrownError = error;
278
+ }).finally(() => {
279
+ this.endTime = Date.now();
280
+ event.dispatcher.removeListener(event.step.before, registerStep);
281
+ });
282
+ } else {
283
+ try {
284
+ this.startTime = Date.now();
285
+ result = fn.apply(this.context, this.args);
286
+ } catch (error) {
287
+ this.setStatus('failed');
288
+ rethrownError = error;
289
+ } finally {
290
+ this.endTime = Date.now();
291
+ event.dispatcher.removeListener(event.step.before, registerStep);
292
+ }
281
293
  }
282
294
  if (rethrownError) { throw rethrownError; }
283
295
  return result;
package/lib/utils.js CHANGED
@@ -33,12 +33,7 @@ const isAsyncFunction = module.exports.isAsyncFunction = function (fn) {
33
33
  };
34
34
 
35
35
  module.exports.fileExists = function (filePath) {
36
- try {
37
- fs.statSync(filePath);
38
- } catch (err) {
39
- if (err.code === 'ENOENT') return false;
40
- }
41
- return true;
36
+ return fs.existsSync(filePath);
42
37
  };
43
38
 
44
39
  module.exports.isFile = function (filePath) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeceptjs",
3
- "version": "3.3.3",
3
+ "version": "3.3.5-beta.2",
4
4
  "description": "Supercharged End 2 End Testing Framework for NodeJS",
5
5
  "keywords": [
6
6
  "acceptance",
@@ -101,7 +101,7 @@
101
101
  "@wdio/selenium-standalone-service": "^5.16.10",
102
102
  "@wdio/utils": "^5.23.0",
103
103
  "apollo-server-express": "^2.25.3",
104
- "chai-as-promised": "^5.2.0",
104
+ "chai-as-promised": "^7.1.1",
105
105
  "chai-subset": "^1.6.0",
106
106
  "contributor-faces": "^1.0.3",
107
107
  "documentation": "^12.3.0",
@@ -122,7 +122,7 @@
122
122
  "mocha-parallel-tests": "^2.3.0",
123
123
  "nightmare": "^3.0.2",
124
124
  "nodemon": "^1.19.4",
125
- "playwright": "^1.18.1",
125
+ "playwright": "^1.23.2",
126
126
  "puppeteer": "^10.4.0",
127
127
  "qrcode-terminal": "^0.12.0",
128
128
  "rosie": "^1.6.0",
@@ -132,7 +132,7 @@
132
132
  "sinon-chai": "^3.7.0",
133
133
  "testcafe": "^1.18.3",
134
134
  "ts-morph": "^3.1.3",
135
- "tsd-jsdoc": "^2.5.0",
135
+ "tsd-jsdoc": "https://github.com/englercj/tsd-jsdoc.git",
136
136
  "typescript": "^4.4.3",
137
137
  "wdio-docker-service": "^1.5.0",
138
138
  "webdriverio": "^7.16.14",