codeceptjs 3.5.9 → 3.5.10

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.
package/README.md CHANGED
@@ -24,7 +24,7 @@ Reference: [Helpers API](https://github.com/codeceptjs/CodeceptJS/tree/master/do
24
24
  ## Supercharged E2E Testing
25
25
 
26
26
  CodeceptJS is a new testing framework for end-to-end testing with WebDriver (or others).
27
- It abstracts browser interaction to simple steps that are written from a user perspective.
27
+ It abstracts browser interaction to simple steps that are written from a user's perspective.
28
28
  A simple test that verifies the "Welcome" text is present on a main page of a site will look like:
29
29
 
30
30
  ```js
@@ -57,27 +57,27 @@ And more to come...
57
57
 
58
58
  CodeceptJS is a successor of [Codeception](http://codeception.com), a popular full-stack testing framework for PHP.
59
59
  With CodeceptJS your scenario-driven functional and acceptance tests will be as simple and clean as they can be.
60
- You don't need to worry about asynchronous nature of NodeJS or about various APIs of Selenium, Puppeteer, TestCafe, etc. as CodeceptJS unifies them and makes them work as they are synchronous.
60
+ You don't need to worry about asynchronous nature of NodeJS or about various APIs of Playwright, Selenium, Puppeteer, TestCafe, etc. as CodeceptJS unifies them and makes them work as they are synchronous.
61
61
 
62
62
 
63
63
  ## Features
64
64
 
65
- * 🪄 **AI-powered** with GPT features to assist and heal failing tests
66
- * Based on [Mocha](https://mochajs.org/) testing framework.
67
- * Designed for scenario driven acceptance testing in BDD-style
68
- * Uses ES6 natively without transpiler.
65
+ * 🪄 **AI-powered** with GPT features to assist and heal failing tests.
66
+ * Based on [Mocha](https://mochajs.org/) testing framework.
67
+ * 💼 Designed for scenario driven acceptance testing in BDD-style.
68
+ * 💻 Uses ES6 natively without transpiler.
69
69
  * Also plays nice with TypeScript.
70
- * Smart locators: use names, labels, matching text, CSS or XPath to locate elements.
71
- * Interactive debugging shell: pause test at any point and try different commands in a browser.
70
+ * </> Smart locators: use names, labels, matching text, CSS or XPath to locate elements.
71
+ * 🌐 Interactive debugging shell: pause test at any point and try different commands in a browser.
72
72
  * Easily create tests, pageobjects, stepobjects with CLI generators.
73
73
 
74
- ## Install
74
+ ## Installation
75
75
 
76
76
  ```sh
77
77
  npm i codeceptjs --save
78
78
  ```
79
79
 
80
- Move to directory where you'd like to have your tests (and codeceptjs config) stored, and execute
80
+ Move to directory where you'd like to have your tests (and CodeceptJS config) stored, and execute:
81
81
 
82
82
  ```sh
83
83
  npx codeceptjs init
@@ -131,8 +131,8 @@ Scenario('test some forms', ({ I }) => {
131
131
  });
132
132
  ```
133
133
 
134
- All actions are performed by I object; assertions functions start with `see` function.
135
- In this examples all methods of `I` are taken from WebDriver helper, see [reference](https://github.com/codeceptjs/CodeceptJS/blob/master/docs/helpers/WebDriver.md) to learn how to use them.
134
+ All actions are performed by `I` object; assertions functions start with `see` function.
135
+ In these examples all methods of `I` are taken from WebDriver helper, see [reference](https://github.com/codeceptjs/CodeceptJS/blob/master/docs/helpers/WebDriver.md) to learn how to use them.
136
136
 
137
137
  Let's execute this test with `run` command. Additional option `--steps` will show us the running process. We recommend use `--steps` or `--debug` during development.
138
138
 
@@ -198,17 +198,15 @@ In this case 'User is valid' string will be searched only inside elements locate
198
198
  ### Grabbers
199
199
 
200
200
  In case you need to return a value from a webpage and use it directly in test, you should use methods with `grab` prefix.
201
- They are expected to be used inside async/await functions, and their results will be available in test:
201
+ They are expected to be used inside `async/await` functions, and their results will be available in test:
202
202
 
203
203
  ```js
204
- const assert = require('assert');
205
-
206
204
  Feature('CodeceptJS Demonstration');
207
205
 
208
206
  Scenario('test page title', async ({ I }) => {
209
207
  I.amOnPage('http://simple-form-bootstrap.plataformatec.com.br/documentation');
210
208
  const title = await I.grabTitle();
211
- assert.equal(title, 'Example application with SimpleForm and Twitter Bootstrap');
209
+ I.expectEqual(title, 'Example application with SimpleForm and Twitter Bootstrap'); // Avaiable with Expect helper. -> https://codecept.io/helpers/Expect/
212
210
  });
213
211
  ```
214
212
 
@@ -276,7 +276,7 @@ class Appium extends Webdriver {
276
276
  const _convertedCaps = {};
277
277
  for (const [key, value] of Object.entries(capabilities)) {
278
278
  if (!key.startsWith(vendorPrefix.appium)) {
279
- if (key !== 'platformName') {
279
+ if (key !== 'platformName' && key !== 'bstack:options') {
280
280
  _convertedCaps[`${vendorPrefix.appium}:${key}`] = value;
281
281
  } else {
282
282
  _convertedCaps[`${key}`] = value;
@@ -368,7 +368,7 @@ class Appium extends Webdriver {
368
368
  if (context.web) return this.switchToWeb(context.web);
369
369
  if (context.webview) return this.switchToWeb(context.webview);
370
370
  }
371
- return this._switchToContext(context);
371
+ return this.switchToContext(context);
372
372
  }
373
373
 
374
374
  _withinEnd() {
@@ -423,8 +423,8 @@ class Appium extends Webdriver {
423
423
  async runOnIOS(caps, fn) {
424
424
  if (this.platform !== 'ios') return;
425
425
  recorder.session.start('iOS-only actions');
426
- this._runWithCaps(caps, fn);
427
- recorder.add('restore from iOS session', () => recorder.session.restore());
426
+ await this._runWithCaps(caps, fn);
427
+ await recorder.add('restore from iOS session', () => recorder.session.restore());
428
428
  return recorder.promise();
429
429
  }
430
430
 
@@ -465,8 +465,8 @@ class Appium extends Webdriver {
465
465
  async runOnAndroid(caps, fn) {
466
466
  if (this.platform !== 'android') return;
467
467
  recorder.session.start('Android-only actions');
468
- this._runWithCaps(caps, fn);
469
- recorder.add('restore from Android session', () => recorder.session.restore());
468
+ await this._runWithCaps(caps, fn);
469
+ await recorder.add('restore from Android session', () => recorder.session.restore());
470
470
  return recorder.promise();
471
471
  }
472
472
 
@@ -834,7 +834,7 @@ class Appium extends Webdriver {
834
834
  *
835
835
  * @param {*} context the context to switch to
836
836
  */
837
- async _switchToContext(context) {
837
+ async switchToContext(context) {
838
838
  return this.browser.switchContext(context);
839
839
  }
840
840
 
@@ -858,11 +858,11 @@ class Appium extends Webdriver {
858
858
  this.isWeb = true;
859
859
  this.defaultContext = 'body';
860
860
 
861
- if (context) return this._switchToContext(context);
861
+ if (context) return this.switchToContext(context);
862
862
  const contexts = await this.grabAllContexts();
863
863
  this.debugSection('Contexts', contexts.toString());
864
864
  for (const idx in contexts) {
865
- if (contexts[idx].match(/^WEBVIEW/)) return this._switchToContext(contexts[idx]);
865
+ if (contexts[idx].match(/^WEBVIEW/)) return this.switchToContext(contexts[idx]);
866
866
  }
867
867
 
868
868
  throw new Error('No WEBVIEW could be guessed, please specify one in params');
@@ -885,8 +885,8 @@ class Appium extends Webdriver {
885
885
  this.isWeb = false;
886
886
  this.defaultContext = '//*';
887
887
 
888
- if (context) return this._switchToContext(context);
889
- return this._switchToContext('NATIVE_APP');
888
+ if (context) return this.switchToContext(context);
889
+ return this.switchToContext('NATIVE_APP');
890
890
  }
891
891
 
892
892
  /**
@@ -1424,10 +1424,10 @@ class Appium extends Webdriver {
1424
1424
  *
1425
1425
  * @return {Promise<void>}
1426
1426
  *
1427
- * Appium: support only iOS
1427
+ * Appium: support both Android and iOS
1428
1428
  */
1429
1429
  async closeApp() {
1430
- onlyForApps.call(this, 'iOS');
1430
+ onlyForApps.call(this);
1431
1431
  return this.browser.closeApp();
1432
1432
  }
1433
1433
 
@@ -94,6 +94,7 @@ const pathSeparator = path.sep;
94
94
  * @prop {boolean} [ignoreHTTPSErrors] - Allows access to untrustworthy pages, e.g. to a page with an expired certificate. Default value is `false`
95
95
  * @prop {boolean} [bypassCSP] - bypass Content Security Policy or CSP
96
96
  * @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose).
97
+ * @prop {object} [recordHar] - record HAR and will be saved to `output/har`. See more of [HAR options](https://playwright.dev/docs/api/class-browser#browser-new-context-option-record-har).
97
98
  */
98
99
  const config = {};
99
100
 
@@ -141,6 +142,21 @@ const config = {};
141
142
  * * `trace`: enables trace recording for failed tests; trace are saved into `output/trace` folder
142
143
  * * `keepTraceForPassedTests`: - save trace for passed tests
143
144
  *
145
+ * #### HAR Recording Customization
146
+ *
147
+ * A HAR file is an HTTP Archive file that contains a record of all the network requests that are made when a page is loaded.
148
+ * It contains information about the request and response headers, cookies, content, timings, and more. You can use HAR files to mock network requests in your tests.
149
+ * HAR will be saved to `output/har`. More info could be found here https://playwright.dev/docs/api/class-browser#browser-new-context-option-record-har.
150
+ *
151
+ * ```
152
+ * ...
153
+ * recordHar: {
154
+ * mode: 'minimal', // possible values: 'minimal'|'full'.
155
+ * content: 'embed' // possible values: "omit"|"embed"|"attach".
156
+ * }
157
+ * ...
158
+ *```
159
+ *
144
160
  * #### Example #1: Wait for 0 network connections.
145
161
  *
146
162
  * ```js
@@ -346,7 +362,7 @@ class Playwright extends Helper {
346
362
  ignoreLog: ['warning', 'log'],
347
363
  uniqueScreenshotNames: false,
348
364
  manualStart: false,
349
- getPageTimeout: 0,
365
+ getPageTimeout: 30000,
350
366
  waitForNavigation: 'load',
351
367
  restart: false,
352
368
  keepCookies: false,
@@ -455,9 +471,10 @@ class Playwright extends Helper {
455
471
  }
456
472
  }
457
473
 
458
- async _before() {
474
+ async _before(test) {
475
+ this.currentRunningTest = test;
459
476
  recorder.retry({
460
- retries: 5,
477
+ retries: process.env.FAILED_STEP_RETRIES || 3,
461
478
  when: err => {
462
479
  if (!err || typeof (err.message) !== 'string') {
463
480
  return false;
@@ -487,6 +504,15 @@ class Playwright extends Helper {
487
504
  }
488
505
  if (this.options.bypassCSP) contextOptions.bypassCSP = this.options.bypassCSP;
489
506
  if (this.options.recordVideo) contextOptions.recordVideo = this.options.recordVideo;
507
+ if (this.options.recordHar) {
508
+ const harExt = this.options.recordHar.content && this.options.recordHar.content === 'attach' ? 'zip' : 'har';
509
+ const fileName = `${`${global.output_dir}${path.sep}har${path.sep}${uuidv4()}_${clearString(this.currentRunningTest.title)}`.slice(0, 245)}.${harExt}`;
510
+ const dir = path.dirname(fileName);
511
+ if (!fileExists(dir)) fs.mkdirSync(dir);
512
+ this.options.recordHar.path = fileName;
513
+ this.currentRunningTest.artifacts.har = fileName;
514
+ contextOptions.recordHar = this.options.recordHar;
515
+ }
490
516
  if (this.storageState) contextOptions.storageState = this.storageState;
491
517
  if (this.options.userAgent) contextOptions.userAgent = this.options.userAgent;
492
518
  if (this.options.locale) contextOptions.locale = this.options.locale;
@@ -842,6 +868,7 @@ class Playwright extends Helper {
842
868
  this.context = null;
843
869
  this.frame = null;
844
870
  popupStore.clear();
871
+ if (this.options.recordHar) await this.browserContext.close();
845
872
  await this.browser.close();
846
873
  }
847
874
 
@@ -1172,6 +1199,33 @@ class Playwright extends Helper {
1172
1199
  return this.page.reload({ timeout: this.options.getPageTimeout, waitUntil: this.options.waitForNavigation });
1173
1200
  }
1174
1201
 
1202
+ /**
1203
+ * Replaying from HAR
1204
+ *
1205
+ * ```js
1206
+ * // Replay API requests from HAR.
1207
+ * // Either use a matching response from the HAR,
1208
+ * // or abort the request if nothing matches.
1209
+ * I.replayFromHar('./output/har/something.har', { url: "*\/**\/api/v1/fruits" });
1210
+ * I.amOnPage('https://demo.playwright.dev/api-mocking');
1211
+ * I.see('CodeceptJS');
1212
+ * ```
1213
+ *
1214
+ * @param {string} harFilePath Path to recorded HAR file
1215
+ * @param {object} [opts] [Options for replaying from HAR](https://playwright.dev/docs/api/class-page#page-route-from-har)
1216
+ *
1217
+ * @returns Promise<void>
1218
+ */
1219
+ async replayFromHar(harFilePath, opts) {
1220
+ const file = path.join(global.codecept_dir, harFilePath);
1221
+
1222
+ if (!fileExists(file)) {
1223
+ throw new Error(`File at ${file} cannot be found on local system`);
1224
+ }
1225
+
1226
+ await this.page.routeFromHAR(harFilePath, opts);
1227
+ }
1228
+
1175
1229
  /**
1176
1230
  * Scroll page to the top.
1177
1231
  *
@@ -1396,6 +1450,40 @@ class Playwright extends Helper {
1396
1450
  return findFields.call(this, locator);
1397
1451
  }
1398
1452
 
1453
+ /**
1454
+ * Grab WebElements for given locator
1455
+ * Resumes test execution, so **should be used inside an async function with `await`** operator.
1456
+ *
1457
+ * ```js
1458
+ * const webElements = await I.grabWebElements('#button');
1459
+ * ```
1460
+ *
1461
+ * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
1462
+ * @returns {Promise<*>} WebElement of being used Web helper
1463
+ *
1464
+ *
1465
+ */
1466
+ async grabWebElements(locator) {
1467
+ return this._locate(locator);
1468
+ }
1469
+
1470
+ /**
1471
+ * Grab WebElement for given locator
1472
+ * Resumes test execution, so **should be used inside an async function with `await`** operator.
1473
+ *
1474
+ * ```js
1475
+ * const webElement = await I.grabWebElement('#button');
1476
+ * ```
1477
+ *
1478
+ * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
1479
+ * @returns {Promise<*>} WebElement of being used Web helper
1480
+ *
1481
+ *
1482
+ */
1483
+ async grabWebElement(locator) {
1484
+ return this._locateElement(locator);
1485
+ }
1486
+
1399
1487
  /**
1400
1488
  * Switch focus to a particular tab by its number. It waits tabs loading and then switch tab
1401
1489
  *
@@ -2211,8 +2299,15 @@ class Playwright extends Helper {
2211
2299
  const el = els[0];
2212
2300
 
2213
2301
  await highlightActiveElement.call(this, el);
2302
+ let optionToSelect = '';
2214
2303
 
2215
- if (!Array.isArray(option)) option = [option];
2304
+ try {
2305
+ optionToSelect = await el.locator('option', { hasText: option }).textContent();
2306
+ } catch (e) {
2307
+ optionToSelect = option;
2308
+ }
2309
+
2310
+ if (!Array.isArray(option)) option = [optionToSelect];
2216
2311
 
2217
2312
  await el.selectOption(option);
2218
2313
  return this._waitForAction();
@@ -2802,19 +2897,17 @@ class Playwright extends Helper {
2802
2897
 
2803
2898
  const cssPropertiesCamelCase = convertCssPropertiesToCamelCase(cssProperties);
2804
2899
  const elemAmount = res.length;
2805
- const commands = [];
2806
2900
  let props = [];
2807
2901
 
2808
2902
  for (const element of res) {
2809
- const cssProperties = await element.evaluate((el) => getComputedStyle(el));
2810
-
2811
- Object.keys(cssPropertiesCamelCase).forEach(prop => {
2903
+ for (const prop of Object.keys(cssProperties)) {
2904
+ const cssProp = await this.grabCssPropertyFrom(locator, prop);
2812
2905
  if (isColorProperty(prop)) {
2813
- props.push(convertColorToRGBA(cssProperties[prop]));
2906
+ props.push(convertColorToRGBA(cssProp));
2814
2907
  } else {
2815
- props.push(cssProperties[prop]);
2908
+ props.push(cssProp);
2816
2909
  }
2817
- });
2910
+ }
2818
2911
  }
2819
2912
 
2820
2913
  const values = Object.keys(cssPropertiesCamelCase).map(key => cssPropertiesCamelCase[key]);
@@ -2822,7 +2915,8 @@ class Playwright extends Helper {
2822
2915
  let chunked = chunkArray(props, values.length);
2823
2916
  chunked = chunked.filter((val) => {
2824
2917
  for (let i = 0; i < val.length; ++i) {
2825
- if (val[i] !== values[i]) return false;
2918
+ // eslint-disable-next-line eqeqeq
2919
+ if (val[i] != values[i]) return false;
2826
2920
  }
2827
2921
  return true;
2828
2922
  });
@@ -2860,7 +2954,7 @@ class Playwright extends Helper {
2860
2954
  let chunked = chunkArray(attrs, values.length);
2861
2955
  chunked = chunked.filter((val) => {
2862
2956
  for (let i = 0; i < val.length; ++i) {
2863
- if (val[i] !== values[i]) return false;
2957
+ if (!val[i].includes(values[i])) return false;
2864
2958
  }
2865
2959
  return true;
2866
2960
  });
@@ -3083,6 +3177,10 @@ class Playwright extends Helper {
3083
3177
  test.artifacts[`trace_${sessionName}`] = await saveTraceForContext(this.sessionPages[sessionName].context, `${test.title}_${sessionName}.failed`);
3084
3178
  }
3085
3179
  }
3180
+
3181
+ if (this.options.recordHar) {
3182
+ test.artifacts.har = this.currentRunningTest.artifacts.har;
3183
+ }
3086
3184
  }
3087
3185
 
3088
3186
  async _passed(test) {
@@ -3110,6 +3208,10 @@ class Playwright extends Helper {
3110
3208
  await this.browserContext.tracing.stop();
3111
3209
  }
3112
3210
  }
3211
+
3212
+ if (this.options.recordHar) {
3213
+ test.artifacts.har = this.currentRunningTest.artifacts.har;
3214
+ }
3113
3215
  }
3114
3216
 
3115
3217
  /**
@@ -3520,7 +3622,7 @@ class Playwright extends Helper {
3520
3622
  const _contextObject = this.frame ? this.frame : contextObject;
3521
3623
  let count = 0;
3522
3624
  do {
3523
- waiter = await _contextObject.locator(`:has-text('${text}')`).first().isVisible();
3625
+ waiter = await _contextObject.locator(`:has-text("${text}")`).first().isVisible();
3524
3626
  if (waiter) break;
3525
3627
  await this.wait(1);
3526
3628
  count += 1000;
@@ -297,7 +297,7 @@ class Puppeteer extends Helper {
297
297
  this.sessionPages = {};
298
298
  this.currentRunningTest = test;
299
299
  recorder.retry({
300
- retries: 3,
300
+ retries: process.env.FAILED_STEP_RETRIES || 3,
301
301
  when: err => {
302
302
  if (!err || typeof (err.message) !== 'string') {
303
303
  return false;
@@ -1063,6 +1063,23 @@ class Puppeteer extends Helper {
1063
1063
  return findFields.call(this, locator);
1064
1064
  }
1065
1065
 
1066
+ /**
1067
+ * Grab WebElements for given locator
1068
+ * Resumes test execution, so **should be used inside an async function with `await`** operator.
1069
+ *
1070
+ * ```js
1071
+ * const webElements = await I.grabWebElements('#button');
1072
+ * ```
1073
+ *
1074
+ * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
1075
+ * @returns {Promise<*>} WebElement of being used Web helper
1076
+ *
1077
+ *
1078
+ */
1079
+ async grabWebElements(locator) {
1080
+ return this._locate(locator);
1081
+ }
1082
+
1066
1083
  /**
1067
1084
  * Switch focus to a particular tab by its number. It waits tabs loading and then switch tab
1068
1085
  *
@@ -2589,29 +2606,26 @@ class Puppeteer extends Helper {
2589
2606
 
2590
2607
  const cssPropertiesCamelCase = convertCssPropertiesToCamelCase(cssProperties);
2591
2608
  const elemAmount = res.length;
2592
- const commands = [];
2593
- res.forEach((el) => {
2594
- Object.keys(cssPropertiesCamelCase).forEach((prop) => {
2595
- commands.push(el.executionContext()
2596
- .evaluate((el) => {
2597
- const style = window.getComputedStyle ? getComputedStyle(el) : el.currentStyle;
2598
- return JSON.parse(JSON.stringify(style));
2599
- }, el)
2600
- .then((props) => {
2601
- if (isColorProperty(prop)) {
2602
- return convertColorToRGBA(props[prop]);
2603
- }
2604
- return props[prop];
2605
- }));
2606
- });
2607
- });
2608
- let props = await Promise.all(commands);
2609
+ let props = [];
2610
+
2611
+ for (const element of res) {
2612
+ for (const prop of Object.keys(cssProperties)) {
2613
+ const cssProp = await this.grabCssPropertyFrom(locator, prop);
2614
+ if (isColorProperty(prop)) {
2615
+ props.push(convertColorToRGBA(cssProp));
2616
+ } else {
2617
+ props.push(cssProp);
2618
+ }
2619
+ }
2620
+ }
2621
+
2609
2622
  const values = Object.keys(cssPropertiesCamelCase).map(key => cssPropertiesCamelCase[key]);
2610
2623
  if (!Array.isArray(props)) props = [props];
2611
2624
  let chunked = chunkArray(props, values.length);
2612
2625
  chunked = chunked.filter((val) => {
2613
2626
  for (let i = 0; i < val.length; ++i) {
2614
- if (val[i] !== values[i]) return false;
2627
+ // eslint-disable-next-line eqeqeq
2628
+ if (val[i] != values[i]) return false;
2615
2629
  }
2616
2630
  return true;
2617
2631
  });
@@ -2651,7 +2665,9 @@ class Puppeteer extends Helper {
2651
2665
  let chunked = chunkArray(attrs, values.length);
2652
2666
  chunked = chunked.filter((val) => {
2653
2667
  for (let i = 0; i < val.length; ++i) {
2654
- if (val[i] !== values[i]) return false;
2668
+ const _actual = Number.isNaN(val[i]) || (typeof values[i]) === 'string' ? val[i] : Number.parseInt(values[i], 10);
2669
+ const _expected = Number.isNaN(values[i]) || (typeof values[i]) === 'string' ? values[i] : Number.parseInt(values[i], 10);
2670
+ if (!_actual.includes(_expected)) return false;
2655
2671
  }
2656
2672
  return true;
2657
2673
  });
@@ -869,6 +869,23 @@ class WebDriver extends Helper {
869
869
  return findFields.call(this, locator).then(res => res);
870
870
  }
871
871
 
872
+ /**
873
+ * Grab WebElements for given locator
874
+ * Resumes test execution, so **should be used inside an async function with `await`** operator.
875
+ *
876
+ * ```js
877
+ * const webElements = await I.grabWebElements('#button');
878
+ * ```
879
+ *
880
+ * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
881
+ * @returns {Promise<*>} WebElement of being used Web helper
882
+ *
883
+ *
884
+ */
885
+ async grabWebElements(locator) {
886
+ return this._locate(locator);
887
+ }
888
+
872
889
  /**
873
890
  * Set [WebDriver timeouts](https://webdriver.io/docs/timeouts.html) in realtime.
874
891
  *
@@ -2013,7 +2030,9 @@ class WebDriver extends Helper {
2013
2030
  let chunked = chunkArray(props, values.length);
2014
2031
  chunked = chunked.filter((val) => {
2015
2032
  for (let i = 0; i < val.length; ++i) {
2016
- if (val[i] !== values[i]) return false;
2033
+ const _acutal = Number.isNaN(val[i]) || (typeof values[i]) === 'string' ? val[i] : Number.parseInt(val[i], 10);
2034
+ const _expected = Number.isNaN(values[i]) || (typeof values[i]) === 'string' ? values[i] : Number.parseInt(values[i], 10);
2035
+ if (_acutal !== _expected) return false;
2017
2036
  }
2018
2037
  return true;
2019
2038
  });
@@ -2049,7 +2068,9 @@ class WebDriver extends Helper {
2049
2068
  let chunked = chunkArray(attrs, values.length);
2050
2069
  chunked = chunked.filter((val) => {
2051
2070
  for (let i = 0; i < val.length; ++i) {
2052
- if (val[i] !== values[i]) return false;
2071
+ const _acutal = Number.isNaN(val[i]) || (typeof values[i]) === 'string' ? val[i] : Number.parseInt(val[i], 10);
2072
+ const _expected = Number.isNaN(values[i]) || (typeof values[i]) === 'string' ? values[i] : Number.parseInt(values[i], 10);
2073
+ if (_acutal !== _expected) return false;
2053
2074
  }
2054
2075
  return true;
2055
2076
  });
@@ -440,7 +440,7 @@ let settings = await I.grabSettings();
440
440
 
441
441
  Returns **[Promise][6]&lt;[string][5]>** Appium: support Android and iOS
442
442
 
443
- ### \_switchToContext
443
+ ### switchToContext
444
444
 
445
445
  Switch to the specified context.
446
446
 
@@ -871,7 +871,7 @@ Close the given application.
871
871
  I.closeApp();
872
872
  ```
873
873
 
874
- Returns **[Promise][6]&lt;void>** Appium: support only iOS
874
+ Returns **[Promise][6]&lt;void>** Appium: support both Android and iOS
875
875
 
876
876
  ### appendField
877
877