codeceptjs 3.0.7 → 3.1.3

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 (57) hide show
  1. package/CHANGELOG.md +96 -2
  2. package/README.md +9 -1
  3. package/bin/codecept.js +27 -17
  4. package/docs/bdd.md +55 -1
  5. package/docs/build/Appium.js +76 -4
  6. package/docs/build/Playwright.js +186 -69
  7. package/docs/build/Protractor.js +2 -0
  8. package/docs/build/Puppeteer.js +56 -18
  9. package/docs/build/REST.js +12 -0
  10. package/docs/build/WebDriver.js +1 -3
  11. package/docs/changelog.md +96 -2
  12. package/docs/commands.md +21 -7
  13. package/docs/configuration.md +15 -2
  14. package/docs/helpers/Appium.md +96 -94
  15. package/docs/helpers/Playwright.md +259 -202
  16. package/docs/helpers/Puppeteer.md +17 -1
  17. package/docs/helpers/REST.md +23 -9
  18. package/docs/helpers/WebDriver.md +2 -2
  19. package/docs/mobile.md +2 -1
  20. package/docs/playwright.md +156 -6
  21. package/docs/plugins.md +61 -69
  22. package/docs/react.md +1 -1
  23. package/docs/reports.md +21 -3
  24. package/lib/actor.js +2 -3
  25. package/lib/codecept.js +13 -2
  26. package/lib/command/definitions.js +8 -1
  27. package/lib/command/run-multiple/collection.js +4 -0
  28. package/lib/config.js +1 -1
  29. package/lib/container.js +3 -3
  30. package/lib/data/dataTableArgument.js +35 -0
  31. package/lib/helper/Appium.js +49 -4
  32. package/lib/helper/Playwright.js +186 -69
  33. package/lib/helper/Protractor.js +2 -0
  34. package/lib/helper/Puppeteer.js +56 -18
  35. package/lib/helper/REST.js +12 -0
  36. package/lib/helper/WebDriver.js +1 -3
  37. package/lib/helper/errors/ConnectionRefused.js +1 -1
  38. package/lib/helper/extras/Popup.js +1 -1
  39. package/lib/helper/extras/React.js +44 -32
  40. package/lib/index.js +2 -0
  41. package/lib/interfaces/gherkin.js +8 -1
  42. package/lib/listener/exit.js +2 -4
  43. package/lib/listener/helpers.js +4 -4
  44. package/lib/locator.js +7 -0
  45. package/lib/mochaFactory.js +13 -9
  46. package/lib/output.js +2 -2
  47. package/lib/plugin/allure.js +7 -18
  48. package/lib/plugin/commentStep.js +1 -1
  49. package/lib/plugin/{puppeteerCoverage.js → coverage.js} +10 -22
  50. package/lib/plugin/customLocator.js +2 -2
  51. package/lib/plugin/subtitles.js +88 -0
  52. package/lib/plugin/tryTo.js +1 -1
  53. package/lib/recorder.js +5 -3
  54. package/lib/step.js +4 -2
  55. package/package.json +4 -3
  56. package/typings/index.d.ts +2 -0
  57. package/typings/types.d.ts +158 -18
package/CHANGELOG.md CHANGED
@@ -1,13 +1,107 @@
1
+ ## 3.1.3
2
+
3
+ 🛩️ Features:
4
+
5
+ * BDD Improvement. Added `DataTableArgument` class to work with table data structures.
6
+
7
+ ```js
8
+ const { DataTableArgument } = require('codeceptjs');
9
+ //...
10
+ Given('I have an employee card', (table) => {
11
+ const dataTableArgument = new DataTableArgument(table);
12
+ const hashes = dataTableArgument.hashes();
13
+ // hashes = [{ name: 'Harry', surname: 'Potter', position: 'Seeker' }];
14
+ const rows = dataTableArgument.rows();
15
+ // rows = [['Harry', 'Potter', Seeker]];
16
+ }
17
+ ```
18
+ See updated [BDD section](https://codecept.io/bdd/) for more API options.
19
+
20
+ * Support `cjs` file extensions for config file: `codecept.conf.cjs`. See #3052 by @kalvenschraut
21
+ * API updates: Added `test.file` and `suite.file` properties to `test` and `suite` objects to use in helpers and plugins.
22
+
23
+ 🐛 Bugfixes:
24
+
25
+ * [Playwright] Fixed resetting `test.artifacts` for failing tests. See #3033 by @jancorvus. Fixes #3032
26
+ * [Playwright] Apply `basicAuth` credentials to all opened browser contexts. See #3036 by @nikocanvacom. Fixes #3035
27
+ * [WebDriver] Updated `webdriverio` default version to `^6.12.1`. See #3043 by @sridhareaswaran
28
+ * [Playwright] `I.haveRequestHeaders` affects all tabs. See #3049 by @jancorvus
29
+ * BDD: Fixed unhandled empty feature files. Fix #3046 by @abhimanyupandian
30
+ * Fixed `RangeError: Invalid string length` in `recorder.js` when running huge amount of tests.
31
+
32
+ 📖 Documentation:
33
+
34
+ * Added Testrail reporter [Reports Docs](https://codecept.io/reports/#testrail)
35
+
36
+
37
+ ## 3.1.2
38
+
39
+ 🛩️ Features:
40
+
41
+ * Added `coverage` plugin to generate code coverage for Playwright & Puppeteer. By @anirudh-modi
42
+ * Added `subtitle` plugin to generate subtitles for videos recorded with Playwright. By @anirudh-modi
43
+ * Configuration: `config.tests` to accept array of file patterns. See #2994 by @monsteramba
44
+
45
+ ```js
46
+ exports.config = {
47
+ tests: ['./*_test.js','./sampleTest.js'],
48
+ // ...
49
+ }
50
+ ```
51
+ * Notification is shown for test files without `Feature()`. See #3011 by @PeterNgTr
52
+
53
+ 🐛 Bugfixes:
54
+
55
+ * [Playwright] Fixed #2986 error is thrown when deleting a missing video. Fix by @hatufacci
56
+ * Fixed false positive result when invalid function is called in a helper. See #2997 by @abhimanyupandian
57
+ * [Appium] Removed full page mode for `saveScreenshot`. See #3002 by @nlespiaucq
58
+ * [Playwright] Fixed #3003 saving trace for a test with a long name. Fix by @hatufacci
59
+
60
+ 🎱 Other:
61
+
62
+ * Deprecated `puppeteerCoverage` plugin in favor of `coverage` plugin.
63
+
64
+ ## 3.1.1
65
+
66
+ * [Appium] Fixed #2759
67
+ `grabNumberOfVisibleElements`, `grabAttributeFrom`, `grabAttributeFromAll` to allow id locators.
68
+
69
+ ## 3.1.0
70
+
71
+ * [Plawyright] Updated to Playwright 1.13
72
+ * [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.
73
+ * [Puppeteer] Updated to Puppeteer 10.2.
74
+ * [Protractor] Helper deprecated
75
+
76
+ 🛩️ Features:
77
+
78
+ * [Playwright] Added recording of [video](https://codecept.io/playwright/#video) and [traces](https://codecept.io/playwright/#trace) by @davertmik
79
+ * [Playwritght] [Mocking requests](https://codecept.io/playwright/#mocking-network-requests) implemented via `route` API of Playwright by @davertmik
80
+ * [Playwright] Added **support for [React locators](https://codecept.io/react/#locators)** in #2912 by @AAAstorga
81
+
82
+ 🐛 Bugfixes:
83
+
84
+ * [Puppeteer] Fixed #2244 `els[0]._clickablePoint is not a function` by @karunandrii.
85
+ * [Puppeteer] Fixed `fillField` to check for invisible elements. See #2916 by @anne-open-xchange
86
+ * [Playwright] Reset of dialog event listener before registration of new one. #2946 by @nikocanvacom
87
+ * Fixed running Gherkin features with `run-multiple` using chunks. See #2900 by @andrenoberto
88
+ * Fixed #2937 broken typings for subfolders on Windows by @jancorvus
89
+ * Fixed issue where cucumberJsonReporter not working with fakerTransform plugin. See #2942 by @ilangv
90
+ * Fixed #2952 finished job with status code 0 when playwright cannot connect to remote wss url. By @davertmik
91
+
92
+
1
93
  ## 3.0.7
2
94
 
3
- Documentation fixes:
95
+ 📖 Documentation fixes:
96
+
4
97
  * Remove broken link from `Nightmare helper`. See #2860 by @Arhell
5
98
  * Fixed broken links in `playwright.md`. See #2848 by @johnhoodjr
6
99
  * Fix mocha-multi config example. See #2881 by @rimesc
7
100
  * Fix small errors in email documentation file. See #2884 by @mkrtchian
8
101
  * Improve documentation for `Sharing Data Between Workers` section. See #2891 by @ngraf
9
102
 
10
- Features:
103
+ 🛩️ Features:
104
+
11
105
  * [WebDriver] Shadow DOM Support for `Webdriver`. See #2741 by @gkushang
12
106
  * [Release management] Introduce the versioning automatically, it follows the semantics versioning. See #2883 by @PeterNgTr
13
107
  * Adding opts into `Scenario.skip` that it would be useful for building reports. See #2867 by @AlexKo4
package/README.md CHANGED
@@ -1,4 +1,12 @@
1
- [<img src="https://img.shields.io/badge/slack-@codeceptjs-purple.svg?logo=slack">](https://join.slack.com/t/codeceptjs/shared_invite/enQtMzA5OTM4NDM2MzA4LWE4MThhN2NmYTgxNTU5MTc4YzAyYWMwY2JkMmZlYWI5MWQ2MDM5MmRmYzZmYmNiNmY5NTAzM2EwMGIwOTNhOGQ) [<img src="https://img.shields.io/badge/discourse-codeceptjs-purple">](https://codecept.discourse.group) [![NPM version][npm-image]][npm-url] [![Build Status](https://travis-ci.org/Codeception/CodeceptJS.svg?branch=master)](https://travis-ci.org/Codeception/CodeceptJS) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/1823c38c74e44724b5555e3641f72621)](https://www.codacy.com/app/DavertMik/CodeceptJS?utm_source=github.com&utm_medium=referral&utm_content=Codeception/CodeceptJS&utm_campaign=badger) [![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](https://houndci.com)
1
+ [<img src="https://img.shields.io/badge/slack-@codeceptjs-purple.svg?logo=slack">](https://join.slack.com/t/codeceptjs/shared_invite/enQtMzA5OTM4NDM2MzA4LWE4MThhN2NmYTgxNTU5MTc4YzAyYWMwY2JkMmZlYWI5MWQ2MDM5MmRmYzZmYmNiNmY5NTAzM2EwMGIwOTNhOGQ) [<img src="https://img.shields.io/badge/discourse-codeceptjs-purple">](https://codecept.discourse.group) [![NPM version][npm-image]][npm-url]
2
+
3
+ Build Status:
4
+
5
+ [![Playwright Tests](https://github.com/codeceptjs/CodeceptJS/actions/workflows/playwright.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/playwright.yml)
6
+ [![Puppeteer Tests](https://github.com/codeceptjs/CodeceptJS/actions/workflows/puppeteer.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/puppeteer.yml)
7
+ [![WebDriver Tests](https://github.com/codeceptjs/CodeceptJS/actions/workflows/webdriver.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/webdriver.yml)
8
+ [![Appium Tests](https://github.com/codeceptjs/CodeceptJS/actions/workflows/appium.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/appium.yml)
9
+ [![TestCafe Tests](https://github.com/codeceptjs/CodeceptJS/actions/workflows/testcafe.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/testcafe.yml)
2
10
 
3
11
  # CodeceptJS
4
12
 
package/bin/codecept.js CHANGED
@@ -2,6 +2,16 @@
2
2
  const program = require('commander');
3
3
  const Codecept = require('../lib/codecept');
4
4
  const { print, error } = require('../lib/output');
5
+ const { printError } = require('../lib/command/utils');
6
+
7
+ const errorHandler = (fn) => async (...args) => {
8
+ try {
9
+ await fn(...args);
10
+ } catch (e) {
11
+ printError(e);
12
+ process.exitCode = 1;
13
+ }
14
+ };
5
15
 
6
16
  if (process.versions.node && process.versions.node.split('.') && process.versions.node.split('.')[0] < 8) {
7
17
  error('NodeJS >= 8 is required to run.');
@@ -16,11 +26,11 @@ program.version(Codecept.version());
16
26
 
17
27
  program.command('init [path]')
18
28
  .description('Creates dummy config in current dir or [path]')
19
- .action(require('../lib/command/init'));
29
+ .action(errorHandler(require('../lib/command/init')));
20
30
 
21
31
  program.command('migrate [path]')
22
32
  .description('Migrate json config to js config in current dir or [path]')
23
- .action(require('../lib/command/configMigrate'));
33
+ .action(errorHandler(require('../lib/command/configMigrate')));
24
34
 
25
35
  program.command('shell [path]')
26
36
  .alias('sh')
@@ -28,30 +38,30 @@ program.command('shell [path]')
28
38
  .option('--verbose', 'output internal logging information')
29
39
  .option('--profile [value]', 'configuration profile to be used')
30
40
  .option('-c, --config [file]', 'configuration file to be used')
31
- .action(require('../lib/command/interactive'));
41
+ .action(errorHandler(require('../lib/command/interactive')));
32
42
 
33
43
  program.command('list [path]')
34
44
  .alias('l')
35
45
  .description('List all actions for I.')
36
- .action(require('../lib/command/list'));
46
+ .action(errorHandler(require('../lib/command/list')));
37
47
 
38
48
  program.command('def [path]')
39
49
  .description('Generates TypeScript definitions for all I actions.')
40
50
  .option('-c, --config [file]', 'configuration file to be used')
41
51
  .option('-o, --output [folder]', 'target folder to paste definitions')
42
- .action(require('../lib/command/definitions'));
52
+ .action(errorHandler(require('../lib/command/definitions')));
43
53
 
44
54
  program.command('gherkin:init [path]')
45
55
  .alias('bdd:init')
46
56
  .description('Prepare CodeceptJS to run feature files.')
47
57
  .option('-c, --config [file]', 'configuration file to be used')
48
- .action(require('../lib/command/gherkin/init'));
58
+ .action(errorHandler(require('../lib/command/gherkin/init')));
49
59
 
50
60
  program.command('gherkin:steps [path]')
51
61
  .alias('bdd:steps')
52
62
  .description('Prints all defined gherkin steps.')
53
63
  .option('-c, --config [file]', 'configuration file to be used')
54
- .action(require('../lib/command/gherkin/steps'));
64
+ .action(errorHandler(require('../lib/command/gherkin/steps')));
55
65
 
56
66
  program.command('gherkin:snippets [path]')
57
67
  .alias('bdd:snippets')
@@ -60,28 +70,28 @@ program.command('gherkin:snippets [path]')
60
70
  .option('-c, --config [file]', 'configuration file to be used')
61
71
  .option('--feature [file]', 'feature files(s) to scan')
62
72
  .option('--path [file]', 'file in which to place the new snippets')
63
- .action(require('../lib/command/gherkin/snippets'));
73
+ .action(errorHandler(require('../lib/command/gherkin/snippets')));
64
74
 
65
75
  program.command('generate:test [path]')
66
76
  .alias('gt')
67
77
  .description('Generates an empty test')
68
- .action(require('../lib/command/generate').test);
78
+ .action(errorHandler(require('../lib/command/generate').test));
69
79
 
70
80
  program.command('generate:pageobject [path]')
71
81
  .alias('gpo')
72
82
  .description('Generates an empty page object')
73
- .action(require('../lib/command/generate').pageObject);
83
+ .action(errorHandler(require('../lib/command/generate').pageObject));
74
84
 
75
85
  program.command('generate:object [path]')
76
86
  .alias('go')
77
87
  .option('--type, -t [kind]', 'type of object to be created')
78
88
  .description('Generates an empty support object (page/step/fragment)')
79
- .action(require('../lib/command/generate').pageObject);
89
+ .action(errorHandler(require('../lib/command/generate').pageObject));
80
90
 
81
91
  program.command('generate:helper [path]')
82
92
  .alias('gh')
83
93
  .description('Generates a new helper')
84
- .action(require('../lib/command/generate').helper);
94
+ .action(errorHandler(require('../lib/command/generate').helper));
85
95
 
86
96
  program.command('run [test]')
87
97
  .description('Executes tests')
@@ -117,8 +127,8 @@ program.command('run [test]')
117
127
  .option('--recursive', 'include sub directories')
118
128
  .option('--trace', 'trace function calls')
119
129
  .option('--child <string>', 'option for child processes')
130
+ .action(errorHandler(require('../lib/command/run')));
120
131
 
121
- .action(require('../lib/command/run'));
122
132
  program.command('run-workers <workers>')
123
133
  .description('Executes tests in workers')
124
134
  .option('-c, --config [file]', 'configuration file to be used')
@@ -134,7 +144,7 @@ program.command('run-workers <workers>')
134
144
  .option('-p, --plugins <k=v,k2=v2,...>', 'enable plugins, comma-separated')
135
145
  .option('-O, --reporter-options <k=v,k2=v2,...>', 'reporter-specific options')
136
146
  .option('-R, --reporter <name>', 'specify the reporter to use')
137
- .action(require('../lib/command/run-workers'));
147
+ .action(errorHandler(require('../lib/command/run-workers')));
138
148
 
139
149
  program.command('run-multiple [suites...]')
140
150
  .description('Executes tests multiple')
@@ -158,12 +168,12 @@ program.command('run-multiple [suites...]')
158
168
  // mocha options
159
169
  .option('--colors', 'force enabling of colors')
160
170
 
161
- .action(require('../lib/command/run-multiple'));
171
+ .action(errorHandler(require('../lib/command/run-multiple')));
162
172
 
163
173
  program.command('info [path]')
164
174
  .description('Print debugging information concerning the local environment')
165
175
  .option('-c, --config', 'your config file path')
166
- .action(require('../lib/command/info'));
176
+ .action(errorHandler(require('../lib/command/info')));
167
177
 
168
178
  program.command('dry-run [test]')
169
179
  .description('Prints step-by-step scenario for a test without actually running it')
@@ -179,7 +189,7 @@ program.command('dry-run [test]')
179
189
  .option('--steps', 'show step-by-step execution')
180
190
  .option('--verbose', 'output internal logging information')
181
191
  .option('--debug', 'output additional information')
182
- .action(require('../lib/command/dryRun'));
192
+ .action(errorHandler(require('../lib/command/dryRun')));
183
193
 
184
194
  program.on('command:*', (cmd) => {
185
195
  console.log(`\nUnknown command ${cmd}\n`);
package/docs/bdd.md CHANGED
@@ -264,8 +264,10 @@ You can also use the `parse()` method to obtain an object that allow you to get
264
264
  - `raw()` - returns the table as a 2-D array
265
265
  - `rows()` - returns the table as a 2-D array, without the first row
266
266
  - `hashes()` - returns an array of objects where each row is converted to an object (column header is the key)
267
+ - `rowsHash()` - returns an object where each row corresponds to an entry(first column is the key, second column is the value)
268
+ - `transpose()` - transpose the data, returns nothing. To work with the transposed table use the methods above.
267
269
 
268
- If we use hashes() with the previous exemple :
270
+ If we use hashes() with the previous example :
269
271
 
270
272
  ```js
271
273
  Given('I have products in my cart', (table) => { // eslint-disable-line
@@ -281,7 +283,59 @@ Given('I have products in my cart', (table) => { // eslint-disable-line
281
283
  }
282
284
  });
283
285
  ```
286
+ Examples of tables using:
284
287
 
288
+ ```gherkin
289
+ Given I have a short employees card
290
+ | Harry | Potter |
291
+ | Chuck | Norris |
292
+ ```
293
+ ```js
294
+ const { DataTableArgument } = require('codeceptjs');
295
+ //...
296
+ Given('I have a short employees card', (table) => {
297
+ const dataTableArgument = new DataTableArgument(table);
298
+ const raw = dataTableArgument.raw();
299
+ // row = [['Harry', 'Potter'], ['Chuck', 'Norris']]
300
+ dataTableArgument.transpose();
301
+ const transposedRaw = dataTableArgument.raw();
302
+ // transposedRaw = [['Harry', 'Chuck'], ['Potter', 'Norris']];
303
+ }
304
+ );
305
+ ```
306
+ ```gherkin
307
+ Given I have an employee card
308
+ | name | surname | position |
309
+ | Harry | Potter | Seeker |
310
+ ```
311
+ ```js
312
+ const { DataTableArgument } = require('codeceptjs');
313
+ //...
314
+ Given('I have an employee card', (table) => {
315
+ const dataTableArgument = new DataTableArgument(table);
316
+ const hashes = dataTableArgument.hashes();
317
+ // hashes = [{ name: 'Harry', surname: 'Potter', position: 'Seeker' }];
318
+ const rows = dataTableArgument.rows();
319
+ // rows = [['Harry', 'Potter', Seeker]];
320
+ }
321
+ );
322
+ ```
323
+ ```gherkin
324
+ Given I have a formatted employee card
325
+ | name | Harry |
326
+ | surname | Potter |
327
+ | position | Seeker |
328
+ ```
329
+ ```js
330
+ const { DataTableArgument } = require('codeceptjs');
331
+ //...
332
+ Given('I have a formatted employee card', (table) => {
333
+ const dataTableArgument = new DataTableArgument(table);
334
+ const rawHash = dataTableArgument.rowsHash();
335
+ // rawHash = { name: 'Harry', surname: 'Potter', position: 'Seeker' };
336
+ }
337
+ );
338
+ ```
285
339
  ### Examples
286
340
 
287
341
  In case scenarios represent the same logic but differ on data, we can use *Scenario Outline* to provide different examples for the same behavior. Scenario outline is just like a basic scenario with some values replaced with placeholders, which are filled from a table. Each set of values is executed as a different test.
@@ -481,10 +481,11 @@ class Appium extends Webdriver {
481
481
  * ```js
482
482
  * I.removeApp('appName', 'com.example.android.apis');
483
483
  * ```
484
- * @param {string} appId
485
- * @param {string} bundleId String ID of bundle
486
484
  *
487
485
  * Appium: support only Android
486
+ *
487
+ * @param {string} appId
488
+ * @param {string} [bundleId] ID of bundle
488
489
  */
489
490
  async removeApp(appId, bundleId) {
490
491
  onlyForApps.call(this, 'Android');
@@ -820,9 +821,10 @@ class Appium extends Webdriver {
820
821
  * I.hideDeviceKeyboard('pressKey', 'Done');
821
822
  * ```
822
823
  *
823
- * @param {'tapOutside' | 'pressKey'} strategy desired strategy to close keyboard (‘tapOutside’ or ‘pressKey’)
824
- *
825
824
  * Appium: support Android and iOS
825
+ *
826
+ * @param {'tapOutside' | 'pressKey'} [strategy] Desired strategy to close keyboard (‘tapOutside’ or ‘pressKey’)
827
+ * @param {string} [key] Optional key
826
828
  */
827
829
  async hideDeviceKeyboard(strategy, key) {
828
830
  onlyForApps.call(this);
@@ -1162,6 +1164,8 @@ class Appium extends Webdriver {
1162
1164
  * ```
1163
1165
  *
1164
1166
  * Appium: support Android and iOS
1167
+ *
1168
+ * @param {Array} actions Array of touch actions
1165
1169
  */
1166
1170
  async touchPerform(actions) {
1167
1171
  onlyForApps.call(this);
@@ -1462,6 +1466,60 @@ class Appium extends Webdriver {
1462
1466
  return super.grabTextFrom(parseLocator.call(this, locator));
1463
1467
  }
1464
1468
 
1469
+ /**
1470
+ * Grab number of visible elements by locator.
1471
+ * Resumes test execution, so **should be used inside async function with `await`** operator.
1472
+ *
1473
+ * ```js
1474
+ * let numOfElements = await I.grabNumberOfVisibleElements('p');
1475
+ * ```
1476
+ *
1477
+ * @param {CodeceptJS.LocatorOrString} locator located by CSS|XPath|strict locator.
1478
+ * @returns {Promise<number>} number of visible elements
1479
+ */
1480
+ async grabNumberOfVisibleElements(locator) {
1481
+ if (this.isWeb) return super.grabNumberOfVisibleElements(locator);
1482
+ return super.grabNumberOfVisibleElements(parseLocator.call(this, locator));
1483
+ }
1484
+
1485
+ /**
1486
+ * Can be used for apps only with several values ("contentDescription", "text", "className", "resourceId")
1487
+ *
1488
+ * Retrieves an attribute from an element located by CSS or XPath and returns it to test.
1489
+ * Resumes test execution, so **should be used inside async with `await`** operator.
1490
+ * If more than one element is found - attribute of first element is returned.
1491
+ *
1492
+ * ```js
1493
+ * let hint = await I.grabAttributeFrom('#tooltip', 'title');
1494
+ * ```
1495
+ * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
1496
+ * @param {string} attr attribute name.
1497
+ * @returns {Promise<string>} attribute value
1498
+ *
1499
+ */
1500
+ async grabAttributeFrom(locator, attr) {
1501
+ if (this.isWeb) return super.grabAttributeFrom(locator, attr);
1502
+ return super.grabAttributeFrom(parseLocator.call(this, locator), attr);
1503
+ }
1504
+
1505
+ /**
1506
+ * Can be used for apps only with several values ("contentDescription", "text", "className", "resourceId")
1507
+ * Retrieves an array of attributes from elements located by CSS or XPath and returns it to test.
1508
+ * Resumes test execution, so **should be used inside async with `await`** operator.
1509
+ *
1510
+ * ```js
1511
+ * let hints = await I.grabAttributeFromAll('.tooltip', 'title');
1512
+ * ```
1513
+ * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
1514
+ * @param {string} attr attribute name.
1515
+ * @returns {Promise<string[]>} attribute value
1516
+ *
1517
+ */
1518
+ async grabAttributeFromAll(locator, attr) {
1519
+ if (this.isWeb) return super.grabAttributeFromAll(locator, attr);
1520
+ return super.grabAttributeFromAll(parseLocator.call(this, locator), attr);
1521
+ }
1522
+
1465
1523
  /**
1466
1524
  * Retrieves an array of value from a form located by CSS or XPath and returns it to test.
1467
1525
  * Resumes test execution, so **should be used inside async function with `await`** operator.
@@ -1497,6 +1555,20 @@ class Appium extends Webdriver {
1497
1555
  return super.grabValueFrom(parseLocator.call(this, locator));
1498
1556
  }
1499
1557
 
1558
+ /**
1559
+ * Saves a screenshot to ouput folder (set in codecept.json or codecept.conf.js).
1560
+ * Filename is relative to output folder.
1561
+ *
1562
+ * ```js
1563
+ * I.saveScreenshot('debug.png');
1564
+ * ```
1565
+ *
1566
+ * @param {string} fileName file name to save.
1567
+ */
1568
+ async saveScreenshot(fileName) {
1569
+ return super.saveScreenshot(fileName, false);
1570
+ }
1571
+
1500
1572
  /**
1501
1573
  * Scroll element into viewport.
1502
1574
  *