codeceptjs 4.0.0-beta.2 → 4.0.0-beta.20

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 (209) hide show
  1. package/README.md +133 -120
  2. package/bin/codecept.js +107 -96
  3. package/bin/test-server.js +64 -0
  4. package/docs/webapi/clearCookie.mustache +1 -1
  5. package/docs/webapi/click.mustache +5 -1
  6. package/lib/actor.js +71 -103
  7. package/lib/ai.js +159 -188
  8. package/lib/assert/empty.js +22 -24
  9. package/lib/assert/equal.js +30 -37
  10. package/lib/assert/error.js +14 -14
  11. package/lib/assert/include.js +43 -48
  12. package/lib/assert/throws.js +11 -11
  13. package/lib/assert/truth.js +22 -22
  14. package/lib/assert.js +20 -18
  15. package/lib/codecept.js +262 -162
  16. package/lib/colorUtils.js +50 -52
  17. package/lib/command/check.js +206 -0
  18. package/lib/command/configMigrate.js +56 -51
  19. package/lib/command/definitions.js +96 -109
  20. package/lib/command/dryRun.js +77 -79
  21. package/lib/command/generate.js +234 -194
  22. package/lib/command/gherkin/init.js +42 -33
  23. package/lib/command/gherkin/snippets.js +76 -74
  24. package/lib/command/gherkin/steps.js +20 -17
  25. package/lib/command/info.js +74 -38
  26. package/lib/command/init.js +301 -290
  27. package/lib/command/interactive.js +41 -32
  28. package/lib/command/list.js +28 -27
  29. package/lib/command/run-multiple/chunk.js +51 -48
  30. package/lib/command/run-multiple/collection.js +5 -5
  31. package/lib/command/run-multiple/run.js +5 -1
  32. package/lib/command/run-multiple.js +97 -97
  33. package/lib/command/run-rerun.js +19 -25
  34. package/lib/command/run-workers.js +68 -92
  35. package/lib/command/run.js +39 -27
  36. package/lib/command/utils.js +80 -64
  37. package/lib/command/workers/runTests.js +388 -226
  38. package/lib/config.js +109 -50
  39. package/lib/container.js +641 -261
  40. package/lib/data/context.js +60 -61
  41. package/lib/data/dataScenarioConfig.js +47 -47
  42. package/lib/data/dataTableArgument.js +32 -32
  43. package/lib/data/table.js +22 -22
  44. package/lib/effects.js +307 -0
  45. package/lib/element/WebElement.js +327 -0
  46. package/lib/els.js +160 -0
  47. package/lib/event.js +173 -163
  48. package/lib/globals.js +141 -0
  49. package/lib/heal.js +89 -85
  50. package/lib/helper/AI.js +131 -41
  51. package/lib/helper/ApiDataFactory.js +107 -75
  52. package/lib/helper/Appium.js +542 -404
  53. package/lib/helper/FileSystem.js +100 -79
  54. package/lib/helper/GraphQL.js +44 -43
  55. package/lib/helper/GraphQLDataFactory.js +52 -52
  56. package/lib/helper/JSONResponse.js +126 -88
  57. package/lib/helper/Mochawesome.js +54 -29
  58. package/lib/helper/Playwright.js +2547 -1316
  59. package/lib/helper/Puppeteer.js +1578 -1181
  60. package/lib/helper/REST.js +209 -68
  61. package/lib/helper/WebDriver.js +1482 -1342
  62. package/lib/helper/errors/ConnectionRefused.js +6 -6
  63. package/lib/helper/errors/ElementAssertion.js +11 -16
  64. package/lib/helper/errors/ElementNotFound.js +5 -9
  65. package/lib/helper/errors/RemoteBrowserConnectionRefused.js +5 -5
  66. package/lib/helper/extras/Console.js +11 -11
  67. package/lib/helper/extras/PlaywrightLocator.js +110 -0
  68. package/lib/helper/extras/PlaywrightPropEngine.js +18 -18
  69. package/lib/helper/extras/PlaywrightReactVueLocator.js +17 -8
  70. package/lib/helper/extras/PlaywrightRestartOpts.js +25 -11
  71. package/lib/helper/extras/Popup.js +22 -22
  72. package/lib/helper/extras/React.js +27 -28
  73. package/lib/helper/network/actions.js +36 -42
  74. package/lib/helper/network/utils.js +78 -84
  75. package/lib/helper/scripts/blurElement.js +5 -5
  76. package/lib/helper/scripts/focusElement.js +5 -5
  77. package/lib/helper/scripts/highlightElement.js +8 -8
  78. package/lib/helper/scripts/isElementClickable.js +34 -34
  79. package/lib/helper.js +2 -3
  80. package/lib/history.js +23 -19
  81. package/lib/hooks.js +8 -8
  82. package/lib/html.js +94 -104
  83. package/lib/index.js +38 -27
  84. package/lib/listener/config.js +30 -23
  85. package/lib/listener/emptyRun.js +54 -0
  86. package/lib/listener/enhancedGlobalRetry.js +110 -0
  87. package/lib/listener/exit.js +16 -18
  88. package/lib/listener/globalRetry.js +70 -0
  89. package/lib/listener/globalTimeout.js +181 -0
  90. package/lib/listener/helpers.js +76 -51
  91. package/lib/listener/mocha.js +10 -11
  92. package/lib/listener/result.js +11 -0
  93. package/lib/listener/retryEnhancer.js +85 -0
  94. package/lib/listener/steps.js +71 -59
  95. package/lib/listener/store.js +20 -0
  96. package/lib/locator.js +214 -197
  97. package/lib/mocha/asyncWrapper.js +274 -0
  98. package/lib/mocha/bdd.js +167 -0
  99. package/lib/mocha/cli.js +341 -0
  100. package/lib/mocha/factory.js +163 -0
  101. package/lib/mocha/featureConfig.js +89 -0
  102. package/lib/mocha/gherkin.js +231 -0
  103. package/lib/mocha/hooks.js +121 -0
  104. package/lib/mocha/index.js +21 -0
  105. package/lib/mocha/inject.js +46 -0
  106. package/lib/{interfaces → mocha}/scenarioConfig.js +58 -34
  107. package/lib/mocha/suite.js +89 -0
  108. package/lib/mocha/test.js +184 -0
  109. package/lib/mocha/types.d.ts +42 -0
  110. package/lib/mocha/ui.js +242 -0
  111. package/lib/output.js +141 -71
  112. package/lib/parser.js +47 -44
  113. package/lib/pause.js +173 -145
  114. package/lib/plugin/analyze.js +403 -0
  115. package/lib/plugin/{autoLogin.js → auth.js} +178 -79
  116. package/lib/plugin/autoDelay.js +36 -40
  117. package/lib/plugin/coverage.js +131 -78
  118. package/lib/plugin/customLocator.js +22 -21
  119. package/lib/plugin/customReporter.js +53 -0
  120. package/lib/plugin/enhancedRetryFailedStep.js +99 -0
  121. package/lib/plugin/heal.js +101 -110
  122. package/lib/plugin/htmlReporter.js +3648 -0
  123. package/lib/plugin/pageInfo.js +140 -0
  124. package/lib/plugin/pauseOnFail.js +12 -11
  125. package/lib/plugin/retryFailedStep.js +82 -47
  126. package/lib/plugin/screenshotOnFail.js +111 -92
  127. package/lib/plugin/stepByStepReport.js +159 -101
  128. package/lib/plugin/stepTimeout.js +20 -25
  129. package/lib/plugin/subtitles.js +38 -38
  130. package/lib/recorder.js +193 -130
  131. package/lib/rerun.js +94 -49
  132. package/lib/result.js +238 -0
  133. package/lib/retryCoordinator.js +207 -0
  134. package/lib/secret.js +20 -18
  135. package/lib/session.js +95 -89
  136. package/lib/step/base.js +239 -0
  137. package/lib/step/comment.js +10 -0
  138. package/lib/step/config.js +50 -0
  139. package/lib/step/func.js +46 -0
  140. package/lib/step/helper.js +50 -0
  141. package/lib/step/meta.js +99 -0
  142. package/lib/step/record.js +74 -0
  143. package/lib/step/retry.js +11 -0
  144. package/lib/step/section.js +55 -0
  145. package/lib/step.js +18 -329
  146. package/lib/steps.js +54 -0
  147. package/lib/store.js +38 -7
  148. package/lib/template/heal.js +3 -12
  149. package/lib/template/prompts/generatePageObject.js +31 -0
  150. package/lib/template/prompts/healStep.js +13 -0
  151. package/lib/template/prompts/writeStep.js +9 -0
  152. package/lib/test-server.js +334 -0
  153. package/lib/timeout.js +60 -0
  154. package/lib/transform.js +8 -8
  155. package/lib/translation.js +34 -21
  156. package/lib/utils/loaderCheck.js +124 -0
  157. package/lib/utils/mask_data.js +47 -0
  158. package/lib/utils/typescript.js +237 -0
  159. package/lib/utils.js +411 -228
  160. package/lib/workerStorage.js +37 -34
  161. package/lib/workers.js +532 -296
  162. package/package.json +124 -95
  163. package/translations/de-DE.js +5 -3
  164. package/translations/fr-FR.js +5 -4
  165. package/translations/index.js +22 -12
  166. package/translations/it-IT.js +4 -3
  167. package/translations/ja-JP.js +4 -3
  168. package/translations/nl-NL.js +76 -0
  169. package/translations/pl-PL.js +4 -3
  170. package/translations/pt-BR.js +4 -3
  171. package/translations/ru-RU.js +4 -3
  172. package/translations/utils.js +10 -0
  173. package/translations/zh-CN.js +4 -3
  174. package/translations/zh-TW.js +4 -3
  175. package/typings/index.d.ts +546 -185
  176. package/typings/promiseBasedTypes.d.ts +150 -875
  177. package/typings/types.d.ts +547 -992
  178. package/lib/cli.js +0 -249
  179. package/lib/dirname.js +0 -5
  180. package/lib/helper/Expect.js +0 -425
  181. package/lib/helper/ExpectHelper.js +0 -399
  182. package/lib/helper/MockServer.js +0 -223
  183. package/lib/helper/Nightmare.js +0 -1411
  184. package/lib/helper/Protractor.js +0 -1835
  185. package/lib/helper/SoftExpectHelper.js +0 -381
  186. package/lib/helper/TestCafe.js +0 -1410
  187. package/lib/helper/clientscripts/nightmare.js +0 -213
  188. package/lib/helper/testcafe/testControllerHolder.js +0 -42
  189. package/lib/helper/testcafe/testcafe-utils.js +0 -63
  190. package/lib/interfaces/bdd.js +0 -98
  191. package/lib/interfaces/featureConfig.js +0 -69
  192. package/lib/interfaces/gherkin.js +0 -195
  193. package/lib/listener/artifacts.js +0 -19
  194. package/lib/listener/retry.js +0 -68
  195. package/lib/listener/timeout.js +0 -109
  196. package/lib/mochaFactory.js +0 -110
  197. package/lib/plugin/allure.js +0 -15
  198. package/lib/plugin/commentStep.js +0 -136
  199. package/lib/plugin/debugErrors.js +0 -67
  200. package/lib/plugin/eachElement.js +0 -127
  201. package/lib/plugin/fakerTransform.js +0 -49
  202. package/lib/plugin/retryTo.js +0 -121
  203. package/lib/plugin/selenoid.js +0 -371
  204. package/lib/plugin/standardActingHelpers.js +0 -9
  205. package/lib/plugin/tryTo.js +0 -105
  206. package/lib/plugin/wdio.js +0 -246
  207. package/lib/scenario.js +0 -222
  208. package/lib/ui.js +0 -238
  209. package/lib/within.js +0 -70
@@ -1,25 +1,27 @@
1
- import fs from 'fs';
2
- import { default as axios } from 'axios';
3
- import { v4 as uuidv4 } from 'uuid';
4
- import Webdriver from './WebDriver';
5
- import AssertionFailedError from '../assert/error.js';
6
- import { truth } from '../assert/truth';
7
- import recorder from '../recorder.js';
8
- import Locator from '../locator.js';
9
- import ConnectionRefused from './errors/ConnectionRefused.js';
10
-
11
- let webdriverio;
12
-
13
- const mobileRoot = '//*';
14
- const webRoot = 'body';
1
+ import * as webdriverio from 'webdriverio'
2
+ import fs from 'fs'
3
+ import axios from 'axios'
4
+ import { v4 as uuidv4 } from 'uuid'
5
+
6
+ import Webdriver from './WebDriver.js'
7
+ import AssertionFailedError from '../assert/error.js'
8
+ import { truth } from '../assert/truth.js'
9
+ import recorder from '../recorder.js'
10
+ import Locator from '../locator.js'
11
+ import ConnectionRefused from './errors/ConnectionRefused.js'
12
+ import ElementNotFound from './errors/ElementNotFound.js'
13
+ import { dontSeeElementError } from './errors/ElementAssertion.js'
14
+
15
+ const mobileRoot = '//*'
16
+ const webRoot = 'body'
15
17
  const supportedPlatform = {
16
18
  android: 'Android',
17
19
  iOS: 'iOS',
18
- };
20
+ }
19
21
 
20
22
  const vendorPrefix = {
21
23
  appium: 'appium',
22
- };
24
+ }
23
25
 
24
26
  /**
25
27
  * Appium helper extends [Webdriver](http://codecept.io/helpers/WebDriver/) helper.
@@ -43,7 +45,7 @@ const vendorPrefix = {
43
45
  *
44
46
  * This helper should be configured in codecept.conf.ts or codecept.conf.js
45
47
  *
46
- * * `appiumV2`: set this to true if you want to run tests with AppiumV2. See more how to setup [here](https://codecept.io/mobile/#setting-up)
48
+ * * `appiumV2`: by default is true, set this to false if you want to run tests with AppiumV1. See more how to setup [here](https://codecept.io/mobile/#setting-up)
47
49
  * * `app`: Application path. Local path or remote URL to an .ipa or .apk file, or a .zip containing one of these. Alias to desiredCapabilities.appPackage
48
50
  * * `host`: (default: 'localhost') Appium host
49
51
  * * `port`: (default: '4723') Appium port
@@ -123,7 +125,7 @@ const vendorPrefix = {
123
125
  * {
124
126
  * helpers: {
125
127
  * Appium: {
126
- * appiumV2: true,
128
+ * appiumV2: true, // By default is true, set to false if you want to run against Appium v1
127
129
  * host: "hub-cloud.browserstack.com",
128
130
  * port: 4444,
129
131
  * user: process.env.BROWSERSTACK_USER,
@@ -174,19 +176,16 @@ class Appium extends Webdriver {
174
176
 
175
177
  // @ts-ignore
176
178
  constructor(config) {
177
- super(config);
179
+ super(config)
178
180
 
179
- this.isRunning = false;
180
- if (config.appiumV2 === true) {
181
- this.appiumV2 = true;
182
- }
183
- this.axios = axios.create();
181
+ this.isRunning = false
182
+ this.appiumV2 = config.appiumV2 || true
183
+ this.axios = axios.create()
184
184
 
185
- webdriverio = require('webdriverio');
186
185
  if (!config.appiumV2) {
187
- console.log('The Appium core team does not maintain Appium 1.x anymore since the 1st of January 2022. Please migrating to Appium 2.x by adding appiumV2: true to your config.');
188
- console.log('More info: https://bit.ly/appium-v2-migration');
189
- console.log('This Appium 1.x support will be removed in next major release.');
186
+ console.log('The Appium core team does not maintain Appium 1.x anymore since the 1st of January 2022. Appium 2.x is used by default.')
187
+ console.log('More info: https://bit.ly/appium-v2-migration')
188
+ console.log('This Appium 1.x support will be removed in next major release.')
190
189
  }
191
190
  }
192
191
 
@@ -203,7 +202,7 @@ class Appium extends Webdriver {
203
202
  }
204
203
  }
205
204
  }
206
- `);
205
+ `)
207
206
  }
208
207
 
209
208
  // set defaults
@@ -224,166 +223,195 @@ class Appium extends Webdriver {
224
223
  timeouts: {
225
224
  script: 0, // ms
226
225
  },
227
- };
226
+ }
228
227
 
229
228
  // override defaults with config
230
- config = Object.assign(defaults, config);
229
+ config = Object.assign(defaults, config)
231
230
 
232
- config.baseUrl = config.url || config.baseUrl;
231
+ config.baseUrl = config.url || config.baseUrl
233
232
  if (config.desiredCapabilities && Object.keys(config.desiredCapabilities).length) {
234
- config.capabilities = this.appiumV2 === true ? this._convertAppiumV2Caps(config.desiredCapabilities) : config.desiredCapabilities;
233
+ config.capabilities = this.appiumV2 === true ? this._convertAppiumV2Caps(config.desiredCapabilities) : config.desiredCapabilities
235
234
  }
236
235
 
237
236
  if (this.appiumV2) {
238
- config.capabilities[`${vendorPrefix.appium}:deviceName`] = config[`${vendorPrefix.appium}:device`] || config.capabilities[`${vendorPrefix.appium}:deviceName`];
239
- config.capabilities[`${vendorPrefix.appium}:browserName`] = config[`${vendorPrefix.appium}:browser`] || config.capabilities[`${vendorPrefix.appium}:browserName`];
240
- config.capabilities[`${vendorPrefix.appium}:app`] = config[`${vendorPrefix.appium}:app`] || config.capabilities[`${vendorPrefix.appium}:app`];
241
- config.capabilities[`${vendorPrefix.appium}:tunnelIdentifier`] = config[`${vendorPrefix.appium}:tunnelIdentifier`] || config.capabilities[`${vendorPrefix.appium}:tunnelIdentifier`]; // Adding the code to connect to sauce labs via sauce tunnel
237
+ config.capabilities[`${vendorPrefix.appium}:deviceName`] = config[`${vendorPrefix.appium}:device`] || config.capabilities[`${vendorPrefix.appium}:deviceName`]
238
+ config.capabilities[`${vendorPrefix.appium}:browserName`] = config[`${vendorPrefix.appium}:browser`] || config.capabilities[`${vendorPrefix.appium}:browserName`]
239
+ config.capabilities[`${vendorPrefix.appium}:app`] = config[`${vendorPrefix.appium}:app`] || config.capabilities[`${vendorPrefix.appium}:app`]
240
+ config.capabilities[`${vendorPrefix.appium}:tunnelIdentifier`] = config[`${vendorPrefix.appium}:tunnelIdentifier`] || config.capabilities[`${vendorPrefix.appium}:tunnelIdentifier`] // Adding the code to connect to sauce labs via sauce tunnel
242
241
  } else {
243
- config.capabilities.deviceName = config.device || config.capabilities.deviceName;
244
- config.capabilities.browserName = config.browser || config.capabilities.browserName;
245
- config.capabilities.app = config.app || config.capabilities.app;
246
- config.capabilities.tunnelIdentifier = config.tunnelIdentifier || config.capabilities.tunnelIdentifier; // Adding the code to connect to sauce labs via sauce tunnel
242
+ config.capabilities.deviceName = config.device || config.capabilities.deviceName
243
+ config.capabilities.browserName = config.browser || config.capabilities.browserName
244
+ config.capabilities.app = config.app || config.capabilities.app
245
+ config.capabilities.tunnelIdentifier = config.tunnelIdentifier || config.capabilities.tunnelIdentifier // Adding the code to connect to sauce labs via sauce tunnel
247
246
  }
248
247
 
249
- config.capabilities.platformName = config.platform || config.capabilities.platformName;
250
- config.waitForTimeoutInSeconds = config.waitForTimeout / 1000; // convert to seconds
248
+ config.capabilities.platformName = config.platform || config.capabilities.platformName
249
+ config.waitForTimeoutInSeconds = config.waitForTimeout / 1000 // convert to seconds
251
250
 
252
251
  // [CodeceptJS compatible] transform host to hostname
253
- config.hostname = config.host || config.hostname;
252
+ config.hostname = config.host || config.hostname
254
253
 
255
254
  if (!config.app && config.capabilities.browserName) {
256
- this.isWeb = true;
257
- this.root = webRoot;
255
+ this.isWeb = true
256
+ this.root = webRoot
258
257
  } else {
259
- this.isWeb = false;
260
- this.root = mobileRoot;
258
+ this.isWeb = false
259
+ this.root = mobileRoot
261
260
  }
262
261
 
263
- this.platform = null;
262
+ this.platform = null
264
263
  if (config.capabilities[`${vendorPrefix.appium}:platformName`]) {
265
- this.platform = config.capabilities[`${vendorPrefix.appium}:platformName`].toLowerCase();
264
+ config.capabilities[`${vendorPrefix.appium}:platformName`] = config.capabilities[`${vendorPrefix.appium}:platformName`].toLowerCase()
265
+ this.platform = config.capabilities[`${vendorPrefix.appium}:platformName`]
266
266
  }
267
267
 
268
268
  if (config.capabilities.platformName) {
269
- this.platform = config.capabilities.platformName.toLowerCase();
269
+ config.capabilities.platformName = config.capabilities.platformName.toLowerCase()
270
+ this.platform = config.capabilities.platformName
270
271
  }
271
272
 
272
- return config;
273
+ return config
273
274
  }
274
275
 
275
276
  _convertAppiumV2Caps(capabilities) {
276
- const _convertedCaps = {};
277
+ const _convertedCaps = {}
277
278
  for (const [key, value] of Object.entries(capabilities)) {
278
279
  if (!key.startsWith(vendorPrefix.appium)) {
279
- if (key !== 'platformName' && key !== 'bstack:options') {
280
- _convertedCaps[`${vendorPrefix.appium}:${key}`] = value;
280
+ if (key !== 'platformName' && key !== 'bstack:options' && key !== 'sauce:options') {
281
+ _convertedCaps[`${vendorPrefix.appium}:${key}`] = value
281
282
  } else {
282
- _convertedCaps[`${key}`] = value;
283
+ _convertedCaps[`${key}`] = value
283
284
  }
284
285
  } else {
285
- _convertedCaps[`${key}`] = value;
286
+ _convertedCaps[`${key}`] = value
286
287
  }
287
288
  }
288
- return _convertedCaps;
289
+ return _convertedCaps
289
290
  }
290
291
 
291
292
  static _config() {
292
- return [{
293
- name: 'app',
294
- message: 'Application package. Path to file or url',
295
- default: 'http://localhost',
296
- }, {
297
- name: 'platform',
298
- message: 'Mobile Platform',
299
- type: 'list',
300
- choices: ['iOS', supportedPlatform.android],
301
- default: supportedPlatform.android,
302
- }, {
303
- name: 'device',
304
- message: 'Device to run tests on',
305
- default: 'emulator',
306
- }];
293
+ return [
294
+ {
295
+ name: 'app',
296
+ message: 'Application package. Path to file or url',
297
+ default: 'http://localhost',
298
+ },
299
+ {
300
+ name: 'platform',
301
+ message: 'Mobile Platform',
302
+ type: 'list',
303
+ choices: ['iOS', supportedPlatform.android],
304
+ default: supportedPlatform.android,
305
+ },
306
+ {
307
+ name: 'device',
308
+ message: 'Device to run tests on',
309
+ default: 'emulator',
310
+ },
311
+ ]
307
312
  }
308
313
 
309
314
  async _startBrowser() {
310
315
  if (this.appiumV2 === true) {
311
- this.options.capabilities = this._convertAppiumV2Caps(this.options.capabilities);
312
- this.options.desiredCapabilities = this._convertAppiumV2Caps(this.options.desiredCapabilities);
316
+ this.options.capabilities = this._convertAppiumV2Caps(this.options.capabilities)
317
+ this.options.desiredCapabilities = this._convertAppiumV2Caps(this.options.desiredCapabilities)
313
318
  }
314
319
 
315
320
  try {
316
321
  if (this.options.multiremote) {
317
- this.browser = await webdriverio.multiremote(this.options.multiremote);
322
+ this.browser = await webdriverio.multiremote(this.options.multiremote)
318
323
  } else {
319
- this.browser = await webdriverio.remote(this.options);
324
+ this.browser = await webdriverio.remote(this.options)
320
325
  }
321
326
  } catch (err) {
322
327
  if (err.toString().indexOf('ECONNREFUSED')) {
323
- throw new ConnectionRefused(err);
328
+ throw new ConnectionRefused(err)
324
329
  }
325
- throw err;
330
+ throw err
326
331
  }
327
- this.$$ = this.browser.$$.bind(this.browser);
332
+ this.$$ = this.browser.$$.bind(this.browser)
328
333
 
329
- this.isRunning = true;
334
+ this.isRunning = true
330
335
  if (this.options.timeouts && this.isWeb) {
331
- await this.defineTimeout(this.options.timeouts);
336
+ await this.defineTimeout(this.options.timeouts)
332
337
  }
333
338
  if (this.options.windowSize === 'maximize' && !this.platform) {
334
- const res = await this.browser.execute('return [screen.width, screen.height]');
339
+ const res = await this.browser.execute('return [screen.width, screen.height]')
335
340
  return this.browser.windowHandleSize({
336
341
  width: res.value[0],
337
342
  height: res.value[1],
338
- });
343
+ })
339
344
  }
340
345
  if (this.options.windowSize && this.options.windowSize.indexOf('x') > 0 && !this.platform) {
341
- const dimensions = this.options.windowSize.split('x');
346
+ const dimensions = this.options.windowSize.split('x')
342
347
  await this.browser.windowHandleSize({
343
348
  width: dimensions[0],
344
349
  height: dimensions[1],
345
- });
350
+ })
346
351
  }
347
352
  }
348
353
 
349
354
  async _after() {
350
- if (!this.isRunning) return;
355
+ if (!this.isRunning) return
351
356
  if (this.options.restart) {
352
- this.isRunning = false;
353
- return this.browser.deleteSession();
357
+ this.isRunning = false
358
+ return this.browser.deleteSession()
354
359
  }
355
360
  if (this.isWeb && !this.platform) {
356
- return super._after();
361
+ return super._after()
357
362
  }
358
363
  }
359
364
 
360
365
  async _withinBegin(context) {
361
366
  if (this.isWeb) {
362
- return super._withinBegin(context);
367
+ return super._withinBegin(context)
363
368
  }
364
369
  if (context === 'webview') {
365
- return this.switchToWeb();
370
+ return this.switchToWeb()
366
371
  }
367
372
  if (typeof context === 'object') {
368
- if (context.web) return this.switchToWeb(context.web);
369
- if (context.webview) return this.switchToWeb(context.webview);
373
+ if (context.web) return this.switchToWeb(context.web)
374
+ if (context.webview) return this.switchToWeb(context.webview)
370
375
  }
371
- return this.switchToContext(context);
376
+ return this.switchToContext(context)
372
377
  }
373
378
 
374
379
  _withinEnd() {
375
380
  if (this.isWeb) {
376
- return super._withinEnd();
381
+ return super._withinEnd()
377
382
  }
378
- return this.switchToNative();
383
+ return this.switchToNative()
379
384
  }
380
385
 
381
386
  _buildAppiumEndpoint() {
382
- const {
383
- protocol, port, hostname, path,
384
- } = this.browser.options;
387
+ const { protocol, port, hostname, path } = this.browser.options
388
+ // Ensure path does NOT end with a slash to prevent double slashes
389
+ const normalizedPath = path.replace(/\/$/, '')
385
390
  // Build path to Appium REST API endpoint
386
- return `${protocol}://${hostname}:${port}${path}`;
391
+ return `${protocol}://${hostname}:${port}${normalizedPath}/session/${this.browser.sessionId}`
392
+ }
393
+
394
+ /**
395
+ * Helper method to safely call isDisplayed() on mobile elements.
396
+ * Handles the case where webdriverio tries to use execute/sync which isn't supported in Appium.
397
+ * @private
398
+ */
399
+ async _isDisplayedSafe(element) {
400
+ if (this.isWeb) {
401
+ // For web contexts, use the normal isDisplayed
402
+ return element.isDisplayed()
403
+ }
404
+
405
+ try {
406
+ return await element.isDisplayed()
407
+ } catch (err) {
408
+ // If isDisplayed fails due to execute/sync not being supported in native mobile contexts,
409
+ // fall back to assuming the element is displayed (since we found it)
410
+ if (err.message && err.message.includes('Method is not implemented')) {
411
+ return true
412
+ }
413
+ throw err
414
+ }
387
415
  }
388
416
 
389
417
  /**
@@ -421,11 +449,11 @@ class Appium extends Webdriver {
421
449
  * @param {*} fn
422
450
  */
423
451
  async runOnIOS(caps, fn) {
424
- if (this.platform !== 'ios') return;
425
- recorder.session.start('iOS-only actions');
426
- await this._runWithCaps(caps, fn);
427
- await recorder.add('restore from iOS session', () => recorder.session.restore());
428
- return recorder.promise();
452
+ if (this.platform !== 'ios') return
453
+ recorder.session.start('iOS-only actions')
454
+ this._runWithCaps(caps, fn)
455
+ recorder.add('restore from iOS session', () => recorder.session.restore())
456
+ return recorder.promise()
429
457
  }
430
458
 
431
459
  /**
@@ -463,11 +491,11 @@ class Appium extends Webdriver {
463
491
  * @param {*} fn
464
492
  */
465
493
  async runOnAndroid(caps, fn) {
466
- if (this.platform !== 'android') return;
467
- recorder.session.start('Android-only actions');
468
- await this._runWithCaps(caps, fn);
469
- await recorder.add('restore from Android session', () => recorder.session.restore());
470
- return recorder.promise();
494
+ if (this.platform !== 'android') return
495
+ recorder.session.start('Android-only actions')
496
+ this._runWithCaps(caps, fn)
497
+ recorder.add('restore from Android session', () => recorder.session.restore())
498
+ return recorder.promise()
471
499
  }
472
500
 
473
501
  /**
@@ -480,38 +508,36 @@ class Appium extends Webdriver {
480
508
  * });
481
509
  * ```
482
510
  *
483
- * @param {*} fn
484
511
  */
485
- /* eslint-disable */
486
- async runInWeb(fn) {
487
- if (!this.isWeb) return;
488
- recorder.session.start('Web-only actions');
489
512
 
490
- recorder.add('restore from Web session', () => recorder.session.restore(), true);
491
- return recorder.promise();
513
+ async runInWeb() {
514
+ if (!this.isWeb) return
515
+ recorder.session.start('Web-only actions')
516
+
517
+ recorder.add('restore from Web session', () => recorder.session.restore(), true)
518
+ return recorder.promise()
492
519
  }
493
- /* eslint-enable */
494
520
 
495
- async _runWithCaps(caps, fn) {
521
+ _runWithCaps(caps, fn) {
496
522
  if (typeof caps === 'object') {
497
523
  for (const key in caps) {
498
524
  // skip if capabilities do not match
499
525
  if (this.config.desiredCapabilities[key] !== caps[key]) {
500
- return;
526
+ return
501
527
  }
502
528
  }
503
529
  }
504
530
  if (typeof caps === 'function') {
505
531
  if (!fn) {
506
- fn = caps;
532
+ fn = caps
507
533
  } else {
508
534
  // skip if capabilities are checked inside a function
509
- const enabled = caps(this.config.desiredCapabilities);
510
- if (!enabled) return;
535
+ const enabled = caps(this.config.desiredCapabilities)
536
+ if (!enabled) return
511
537
  }
512
538
  }
513
539
 
514
- fn();
540
+ fn()
515
541
  }
516
542
 
517
543
  /**
@@ -527,9 +553,9 @@ class Appium extends Webdriver {
527
553
  * Appium: support only Android
528
554
  */
529
555
  async checkIfAppIsInstalled(bundleId) {
530
- onlyForApps.call(this, supportedPlatform.android);
556
+ onlyForApps.call(this, supportedPlatform.android)
531
557
 
532
- return this.browser.isAppInstalled(bundleId);
558
+ return this.browser.isAppInstalled(bundleId)
533
559
  }
534
560
 
535
561
  /**
@@ -545,9 +571,9 @@ class Appium extends Webdriver {
545
571
  * Appium: support only Android
546
572
  */
547
573
  async seeAppIsInstalled(bundleId) {
548
- onlyForApps.call(this, supportedPlatform.android);
549
- const res = await this.browser.isAppInstalled(bundleId);
550
- return truth(`app ${bundleId}`, 'to be installed').assert(res);
574
+ onlyForApps.call(this, supportedPlatform.android)
575
+ const res = await this.browser.isAppInstalled(bundleId)
576
+ return truth(`app ${bundleId}`, 'to be installed').assert(res)
551
577
  }
552
578
 
553
579
  /**
@@ -563,9 +589,9 @@ class Appium extends Webdriver {
563
589
  * Appium: support only Android
564
590
  */
565
591
  async seeAppIsNotInstalled(bundleId) {
566
- onlyForApps.call(this, supportedPlatform.android);
567
- const res = await this.browser.isAppInstalled(bundleId);
568
- return truth(`app ${bundleId}`, 'not to be installed').negate(res);
592
+ onlyForApps.call(this, supportedPlatform.android)
593
+ const res = await this.browser.isAppInstalled(bundleId)
594
+ return truth(`app ${bundleId}`, 'not to be installed').negate(res)
569
595
  }
570
596
 
571
597
  /**
@@ -580,8 +606,8 @@ class Appium extends Webdriver {
580
606
  * Appium: support only Android
581
607
  */
582
608
  async installApp(path) {
583
- onlyForApps.call(this, supportedPlatform.android);
584
- return this.browser.installApp(path);
609
+ onlyForApps.call(this, supportedPlatform.android)
610
+ return this.browser.installApp(path)
585
611
  }
586
612
 
587
613
  /**
@@ -597,13 +623,13 @@ class Appium extends Webdriver {
597
623
  * @param {string} [bundleId] ID of bundle
598
624
  */
599
625
  async removeApp(appId, bundleId) {
600
- onlyForApps.call(this, supportedPlatform.android);
626
+ onlyForApps.call(this, supportedPlatform.android)
601
627
 
602
628
  return this.axios({
603
629
  method: 'post',
604
- url: `${this._buildAppiumEndpoint()}/session/${this.browser.sessionId}/appium/device/remove_app`,
630
+ url: `${this._buildAppiumEndpoint()}/appium/device/remove_app`,
605
631
  data: { appId, bundleId },
606
- });
632
+ })
607
633
  }
608
634
 
609
635
  /**
@@ -615,11 +641,12 @@ class Appium extends Webdriver {
615
641
  *
616
642
  */
617
643
  async resetApp() {
618
- onlyForApps.call(this);
644
+ onlyForApps.call(this)
645
+ this.isWeb = false // Reset to native context after app reset
619
646
  return this.axios({
620
647
  method: 'post',
621
- url: `${this._buildAppiumEndpoint()}/session/${this.browser.sessionId}/appium/app/reset`,
622
- });
648
+ url: `${this._buildAppiumEndpoint()}/appium/app/reset`,
649
+ })
623
650
  }
624
651
 
625
652
  /**
@@ -634,9 +661,9 @@ class Appium extends Webdriver {
634
661
  * Appium: support only Android
635
662
  */
636
663
  async seeCurrentActivityIs(currentActivity) {
637
- onlyForApps.call(this, supportedPlatform.android);
638
- const res = await this.browser.getCurrentActivity();
639
- return truth('current activity', `to be ${currentActivity}`).assert(res === currentActivity);
664
+ onlyForApps.call(this, supportedPlatform.android)
665
+ const res = await this.browser.getCurrentActivity()
666
+ return truth('current activity', `to be ${currentActivity}`).assert(res === currentActivity)
640
667
  }
641
668
 
642
669
  /**
@@ -651,9 +678,9 @@ class Appium extends Webdriver {
651
678
  * Appium: support only Android
652
679
  */
653
680
  async seeDeviceIsLocked() {
654
- onlyForApps.call(this, supportedPlatform.android);
655
- const res = await this.browser.isLocked();
656
- return truth('device', 'to be locked').assert(res);
681
+ onlyForApps.call(this, supportedPlatform.android)
682
+ const res = await this.browser.isLocked()
683
+ return truth('device', 'to be locked').assert(res)
657
684
  }
658
685
 
659
686
  /**
@@ -668,9 +695,9 @@ class Appium extends Webdriver {
668
695
  * Appium: support only Android
669
696
  */
670
697
  async seeDeviceIsUnlocked() {
671
- onlyForApps.call(this, supportedPlatform.android);
672
- const res = await this.browser.isLocked();
673
- return truth('device', 'to be locked').negate(res);
698
+ onlyForApps.call(this, supportedPlatform.android)
699
+ const res = await this.browser.isLocked()
700
+ return truth('device', 'to be locked').negate(res)
674
701
  }
675
702
 
676
703
  /**
@@ -688,15 +715,15 @@ class Appium extends Webdriver {
688
715
  * Appium: support Android and iOS
689
716
  */
690
717
  async seeOrientationIs(orientation) {
691
- onlyForApps.call(this);
718
+ onlyForApps.call(this)
692
719
 
693
720
  const res = await this.axios({
694
721
  method: 'get',
695
- url: `${this._buildAppiumEndpoint()}/session/${this.browser.sessionId}/orientation`,
696
- });
722
+ url: `${this._buildAppiumEndpoint()}/orientation`,
723
+ })
697
724
 
698
- const currentOrientation = res.data.value;
699
- return truth('orientation', `to be ${orientation}`).assert(currentOrientation === orientation);
725
+ const currentOrientation = res.data.value
726
+ return truth('orientation', `to be ${orientation}`).assert(currentOrientation === orientation)
700
727
  }
701
728
 
702
729
  /**
@@ -712,13 +739,13 @@ class Appium extends Webdriver {
712
739
  * Appium: support Android and iOS
713
740
  */
714
741
  async setOrientation(orientation) {
715
- onlyForApps.call(this);
742
+ onlyForApps.call(this)
716
743
 
717
744
  return this.axios({
718
745
  method: 'post',
719
- url: `${this._buildAppiumEndpoint()}/session/${this.browser.sessionId}/orientation`,
746
+ url: `${this._buildAppiumEndpoint()}/orientation`,
720
747
  data: { orientation },
721
- });
748
+ })
722
749
  }
723
750
 
724
751
  /**
@@ -733,8 +760,8 @@ class Appium extends Webdriver {
733
760
  * Appium: support Android and iOS
734
761
  */
735
762
  async grabAllContexts() {
736
- onlyForApps.call(this);
737
- return this.browser.getContexts();
763
+ onlyForApps.call(this)
764
+ return this.browser.getContexts()
738
765
  }
739
766
 
740
767
  /**
@@ -749,8 +776,8 @@ class Appium extends Webdriver {
749
776
  * Appium: support Android and iOS
750
777
  */
751
778
  async grabContext() {
752
- onlyForApps.call(this);
753
- return this.browser.getContext();
779
+ onlyForApps.call(this)
780
+ return this.browser.getContext()
754
781
  }
755
782
 
756
783
  /**
@@ -765,8 +792,8 @@ class Appium extends Webdriver {
765
792
  * Appium: support only Android
766
793
  */
767
794
  async grabCurrentActivity() {
768
- onlyForApps.call(this, supportedPlatform.android);
769
- return this.browser.getCurrentActivity();
795
+ onlyForApps.call(this, supportedPlatform.android)
796
+ return this.browser.getCurrentActivity()
770
797
  }
771
798
 
772
799
  /**
@@ -783,14 +810,14 @@ class Appium extends Webdriver {
783
810
  * Appium: support only Android
784
811
  */
785
812
  async grabNetworkConnection() {
786
- onlyForApps.call(this, supportedPlatform.android);
787
- const res = await this.browser.getNetworkConnection();
813
+ onlyForApps.call(this, supportedPlatform.android)
814
+ const res = await this.browser.getNetworkConnection()
788
815
  return {
789
816
  value: res,
790
817
  inAirplaneMode: res.inAirplaneMode,
791
818
  hasWifi: res.hasWifi,
792
819
  hasData: res.hasData,
793
- };
820
+ }
794
821
  }
795
822
 
796
823
  /**
@@ -805,10 +832,10 @@ class Appium extends Webdriver {
805
832
  * Appium: support Android and iOS
806
833
  */
807
834
  async grabOrientation() {
808
- onlyForApps.call(this);
809
- const res = await this.browser.orientation();
810
- this.debugSection('Orientation', res);
811
- return res;
835
+ onlyForApps.call(this)
836
+ const res = await this.browser.orientation()
837
+ this.debugSection('Orientation', res)
838
+ return res
812
839
  }
813
840
 
814
841
  /**
@@ -823,10 +850,10 @@ class Appium extends Webdriver {
823
850
  * Appium: support Android and iOS
824
851
  */
825
852
  async grabSettings() {
826
- onlyForApps.call(this);
827
- const res = await this.browser.getSettings();
828
- this.debugSection('Settings', JSON.stringify(res));
829
- return res;
853
+ onlyForApps.call(this)
854
+ const res = await this.browser.getSettings()
855
+ this.debugSection('Settings', JSON.stringify(res))
856
+ return res
830
857
  }
831
858
 
832
859
  /**
@@ -835,7 +862,7 @@ class Appium extends Webdriver {
835
862
  * @param {*} context the context to switch to
836
863
  */
837
864
  async switchToContext(context) {
838
- return this.browser.switchContext(context);
865
+ return this.browser.switchContext(context)
839
866
  }
840
867
 
841
868
  /**
@@ -855,17 +882,17 @@ class Appium extends Webdriver {
855
882
  * @param {string} [context]
856
883
  */
857
884
  async switchToWeb(context) {
858
- this.isWeb = true;
859
- this.defaultContext = 'body';
885
+ this.isWeb = true
886
+ this.defaultContext = 'body'
860
887
 
861
- if (context) return this.switchToContext(context);
862
- const contexts = await this.grabAllContexts();
863
- this.debugSection('Contexts', contexts.toString());
888
+ if (context) return this.switchToContext(context)
889
+ const contexts = await this.grabAllContexts()
890
+ this.debugSection('Contexts', contexts.toString())
864
891
  for (const idx in contexts) {
865
- if (contexts[idx].match(/^WEBVIEW/)) return this.switchToContext(contexts[idx]);
892
+ if (contexts[idx].match(/^WEBVIEW/)) return this.switchToContext(contexts[idx])
866
893
  }
867
894
 
868
- throw new Error('No WEBVIEW could be guessed, please specify one in params');
895
+ throw new Error('No WEBVIEW could be guessed, please specify one in params')
869
896
  }
870
897
 
871
898
  /**
@@ -882,11 +909,11 @@ class Appium extends Webdriver {
882
909
  * @return {Promise<void>}
883
910
  */
884
911
  async switchToNative(context = null) {
885
- this.isWeb = false;
886
- this.defaultContext = '//*';
912
+ this.isWeb = false
913
+ this.defaultContext = '//*'
887
914
 
888
- if (context) return this.switchToContext(context);
889
- return this.switchToContext('NATIVE_APP');
915
+ if (context) return this.switchToContext(context)
916
+ return this.switchToContext('NATIVE_APP')
890
917
  }
891
918
 
892
919
  /**
@@ -903,8 +930,8 @@ class Appium extends Webdriver {
903
930
  * @return {Promise<void>}
904
931
  */
905
932
  async startActivity(appPackage, appActivity) {
906
- onlyForApps.call(this, supportedPlatform.android);
907
- return this.browser.startActivity(appPackage, appActivity);
933
+ onlyForApps.call(this, supportedPlatform.android)
934
+ return this.browser.startActivity(appPackage, appActivity)
908
935
  }
909
936
 
910
937
  /**
@@ -929,8 +956,8 @@ class Appium extends Webdriver {
929
956
  * @return {Promise<number>}
930
957
  */
931
958
  async setNetworkConnection(value) {
932
- onlyForApps.call(this, supportedPlatform.android);
933
- return this.browser.setNetworkConnection(value);
959
+ onlyForApps.call(this, supportedPlatform.android)
960
+ return this.browser.setNetworkConnection(value)
934
961
  }
935
962
 
936
963
  /**
@@ -945,8 +972,8 @@ class Appium extends Webdriver {
945
972
  * Appium: support Android and iOS
946
973
  */
947
974
  async setSettings(settings) {
948
- onlyForApps.call(this);
949
- return this.browser.settings(settings);
975
+ onlyForApps.call(this)
976
+ return this.browser.settings(settings)
950
977
  }
951
978
 
952
979
  /**
@@ -955,21 +982,19 @@ class Appium extends Webdriver {
955
982
  * ```js
956
983
  * // taps outside to hide keyboard per default
957
984
  * I.hideDeviceKeyboard();
958
- * I.hideDeviceKeyboard('tapOutside');
959
- *
960
- * // or by pressing key
961
- * I.hideDeviceKeyboard('pressKey', 'Done');
962
985
  * ```
963
986
  *
964
987
  * Appium: support Android and iOS
965
988
  *
966
- * @param {'tapOutside' | 'pressKey'} [strategy] Desired strategy to close keyboard (‘tapOutside’ or ‘pressKey’)
967
- * @param {string} [key] Optional key
968
989
  */
969
- async hideDeviceKeyboard(strategy, key) {
970
- onlyForApps.call(this);
971
- strategy = strategy || 'tapOutside';
972
- return this.browser.hideKeyboard(strategy, key);
990
+ async hideDeviceKeyboard() {
991
+ onlyForApps.call(this)
992
+
993
+ return this.axios({
994
+ method: 'post',
995
+ url: `${this._buildAppiumEndpoint()}/appium/device/hide_keyboard`,
996
+ data: {},
997
+ })
973
998
  }
974
999
 
975
1000
  /**
@@ -986,8 +1011,8 @@ class Appium extends Webdriver {
986
1011
  * Appium: support only Android
987
1012
  */
988
1013
  async sendDeviceKeyEvent(keyValue) {
989
- onlyForApps.call(this, supportedPlatform.android);
990
- return this.browser.pressKeyCode(keyValue);
1014
+ onlyForApps.call(this, supportedPlatform.android)
1015
+ return this.browser.pressKeyCode(keyValue)
991
1016
  }
992
1017
 
993
1018
  /**
@@ -1002,8 +1027,8 @@ class Appium extends Webdriver {
1002
1027
  * Appium: support only Android
1003
1028
  */
1004
1029
  async openNotifications() {
1005
- onlyForApps.call(this, supportedPlatform.android);
1006
- return this.browser.openNotifications();
1030
+ onlyForApps.call(this, supportedPlatform.android)
1031
+ return this.browser.openNotifications()
1007
1032
  }
1008
1033
 
1009
1034
  /**
@@ -1022,13 +1047,13 @@ class Appium extends Webdriver {
1022
1047
  * Appium: support Android and iOS
1023
1048
  */
1024
1049
  async makeTouchAction(locator, action) {
1025
- onlyForApps.call(this);
1026
- const element = await this.browser.$(parseLocator.call(this, locator));
1050
+ onlyForApps.call(this)
1051
+ const element = await this.browser.$(parseLocator.call(this, locator))
1027
1052
 
1028
1053
  return this.browser.touchAction({
1029
1054
  action,
1030
1055
  element,
1031
- });
1056
+ })
1032
1057
  }
1033
1058
 
1034
1059
  /**
@@ -1045,7 +1070,13 @@ class Appium extends Webdriver {
1045
1070
  * @param {*} locator
1046
1071
  */
1047
1072
  async tap(locator) {
1048
- return this.makeTouchAction(locator, 'tap');
1073
+ const { elementId } = await this.browser.$(parseLocator.call(this, locator))
1074
+
1075
+ return this.axios({
1076
+ method: 'post',
1077
+ url: `${this._buildAppiumEndpoint()}/element/${elementId}/click`,
1078
+ data: {},
1079
+ })
1049
1080
  }
1050
1081
 
1051
1082
  /**
@@ -1066,14 +1097,16 @@ class Appium extends Webdriver {
1066
1097
  *
1067
1098
  * Appium: support Android and iOS
1068
1099
  */
1069
- /* eslint-disable */
1100
+
1070
1101
  async swipe(locator, xoffset, yoffset, speed = 1000) {
1071
- onlyForApps.call(this);
1072
- const res = await this.browser.$(parseLocator.call(this, locator));
1102
+ onlyForApps.call(this)
1103
+ const res = await this.browser.$(parseLocator.call(this, locator))
1073
1104
  // if (!res.length) throw new ElementNotFound(locator, 'was not found in UI');
1074
- return this.performSwipe(await res.getLocation(), { x: (await res.getLocation()).x + xoffset, y: (await res.getLocation()).y + yoffset });
1105
+ return this.performSwipe(await res.getLocation(), {
1106
+ x: (await res.getLocation()).x + xoffset,
1107
+ y: (await res.getLocation()).y + yoffset,
1108
+ })
1075
1109
  }
1076
- /* eslint-enable */
1077
1110
 
1078
1111
  /**
1079
1112
  * Perform a swipe on the screen.
@@ -1088,42 +1121,44 @@ class Appium extends Webdriver {
1088
1121
  * Appium: support Android and iOS
1089
1122
  */
1090
1123
  async performSwipe(from, to) {
1091
- await this.browser.performActions([{
1092
- id: uuidv4(),
1093
- type: 'pointer',
1094
- parameters: {
1095
- pointerType: 'touch',
1096
- },
1097
- actions: [
1098
- {
1099
- duration: 0,
1100
- x: from.x,
1101
- y: from.y,
1102
- type: 'pointerMove',
1103
- origin: 'viewport',
1104
- },
1105
- {
1106
- button: 1,
1107
- type: 'pointerDown',
1108
- },
1109
- {
1110
- duration: 200,
1111
- type: 'pause',
1112
- },
1113
- {
1114
- duration: 600,
1115
- x: to.x,
1116
- y: to.y,
1117
- type: 'pointerMove',
1118
- origin: 'viewport',
1124
+ await this.browser.performActions([
1125
+ {
1126
+ id: uuidv4(),
1127
+ type: 'pointer',
1128
+ parameters: {
1129
+ pointerType: 'touch',
1119
1130
  },
1120
- {
1121
- button: 1,
1122
- type: 'pointerUp',
1123
- },
1124
- ],
1125
- }]);
1126
- await this.browser.pause(1000);
1131
+ actions: [
1132
+ {
1133
+ duration: 0,
1134
+ x: from.x,
1135
+ y: from.y,
1136
+ type: 'pointerMove',
1137
+ origin: 'viewport',
1138
+ },
1139
+ {
1140
+ button: 1,
1141
+ type: 'pointerDown',
1142
+ },
1143
+ {
1144
+ duration: 200,
1145
+ type: 'pause',
1146
+ },
1147
+ {
1148
+ duration: 600,
1149
+ x: to.x,
1150
+ y: to.y,
1151
+ type: 'pointerMove',
1152
+ origin: 'viewport',
1153
+ },
1154
+ {
1155
+ button: 1,
1156
+ type: 'pointerUp',
1157
+ },
1158
+ ],
1159
+ },
1160
+ ])
1161
+ await this.browser.pause(2000)
1127
1162
  }
1128
1163
 
1129
1164
  /**
@@ -1144,14 +1179,14 @@ class Appium extends Webdriver {
1144
1179
  * Appium: support Android and iOS
1145
1180
  */
1146
1181
  async swipeDown(locator, yoffset = 1000, speed) {
1147
- onlyForApps.call(this);
1182
+ onlyForApps.call(this)
1148
1183
 
1149
1184
  if (!speed) {
1150
- speed = yoffset;
1151
- yoffset = 100;
1185
+ speed = yoffset
1186
+ yoffset = 100
1152
1187
  }
1153
1188
 
1154
- return this.swipe(parseLocator.call(this, locator), 0, yoffset, speed);
1189
+ return this.swipe(parseLocator.call(this, locator), 0, yoffset, speed)
1155
1190
  }
1156
1191
 
1157
1192
  /**
@@ -1173,13 +1208,13 @@ class Appium extends Webdriver {
1173
1208
  * Appium: support Android and iOS
1174
1209
  */
1175
1210
  async swipeLeft(locator, xoffset = 1000, speed) {
1176
- onlyForApps.call(this);
1211
+ onlyForApps.call(this)
1177
1212
  if (!speed) {
1178
- speed = xoffset;
1179
- xoffset = 100;
1213
+ speed = xoffset
1214
+ xoffset = 100
1180
1215
  }
1181
1216
 
1182
- return this.swipe(parseLocator.call(this, locator), -xoffset, 0, speed);
1217
+ return this.swipe(parseLocator.call(this, locator), -xoffset, 0, speed)
1183
1218
  }
1184
1219
 
1185
1220
  /**
@@ -1200,13 +1235,13 @@ class Appium extends Webdriver {
1200
1235
  * Appium: support Android and iOS
1201
1236
  */
1202
1237
  async swipeRight(locator, xoffset = 1000, speed) {
1203
- onlyForApps.call(this);
1238
+ onlyForApps.call(this)
1204
1239
  if (!speed) {
1205
- speed = xoffset;
1206
- xoffset = 100;
1240
+ speed = xoffset
1241
+ xoffset = 100
1207
1242
  }
1208
1243
 
1209
- return this.swipe(parseLocator.call(this, locator), xoffset, 0, speed);
1244
+ return this.swipe(parseLocator.call(this, locator), xoffset, 0, speed)
1210
1245
  }
1211
1246
 
1212
1247
  /**
@@ -1227,14 +1262,14 @@ class Appium extends Webdriver {
1227
1262
  * Appium: support Android and iOS
1228
1263
  */
1229
1264
  async swipeUp(locator, yoffset = 1000, speed) {
1230
- onlyForApps.call(this);
1265
+ onlyForApps.call(this)
1231
1266
 
1232
1267
  if (!speed) {
1233
- speed = yoffset;
1234
- yoffset = 100;
1268
+ speed = yoffset
1269
+ yoffset = 100
1235
1270
  }
1236
1271
 
1237
- return this.swipe(parseLocator.call(this, locator), 0, -yoffset, speed);
1272
+ return this.swipe(parseLocator.call(this, locator), 0, -yoffset, speed)
1238
1273
  }
1239
1274
 
1240
1275
  /**
@@ -1261,55 +1296,61 @@ class Appium extends Webdriver {
1261
1296
  * Appium: support Android and iOS
1262
1297
  */
1263
1298
  async swipeTo(searchableLocator, scrollLocator, direction, timeout, offset, speed) {
1264
- onlyForApps.call(this);
1265
- direction = direction || 'down';
1299
+ onlyForApps.call(this)
1300
+ direction = direction || 'down'
1266
1301
  switch (direction) {
1267
1302
  case 'down':
1268
- direction = 'swipeDown';
1269
- break;
1303
+ direction = 'swipeDown'
1304
+ break
1270
1305
  case 'up':
1271
- direction = 'swipeUp';
1272
- break;
1306
+ direction = 'swipeUp'
1307
+ break
1273
1308
  case 'left':
1274
- direction = 'swipeLeft';
1275
- break;
1309
+ direction = 'swipeLeft'
1310
+ break
1276
1311
  case 'right':
1277
- direction = 'swipeRight';
1278
- break;
1312
+ direction = 'swipeRight'
1313
+ break
1279
1314
  }
1280
- timeout = timeout || this.options.waitForTimeoutInSeconds;
1281
-
1282
- const errorMsg = `element ("${searchableLocator}") still not visible after ${timeout}seconds`;
1283
- const browser = this.browser;
1284
- let err = false;
1285
- let currentSource;
1286
- return browser.waitUntil(() => {
1287
- if (err) {
1288
- return new Error(`Scroll to the end and element ${searchableLocator} was not found`);
1289
- }
1290
- return browser.$$(parseLocator.call(this, searchableLocator))
1291
- .then(els => els.length && els[0].isDisplayed())
1292
- .then((res) => {
1293
- if (res) {
1294
- return true;
1315
+ timeout = timeout || this.options.waitForTimeoutInSeconds
1316
+
1317
+ const errorMsg = `element ("${searchableLocator}") still not visible after ${timeout}seconds`
1318
+ const browser = this.browser
1319
+ let err = false
1320
+ let currentSource
1321
+ return browser
1322
+ .waitUntil(
1323
+ async () => {
1324
+ if (err) {
1325
+ return new Error(`Scroll to the end and element ${searchableLocator} was not found`)
1295
1326
  }
1296
- return this[direction](scrollLocator, offset, speed).getSource().then((source) => {
1297
- if (source === currentSource) {
1298
- err = true;
1299
- } else {
1300
- currentSource = source;
1301
- return false;
1327
+ const els = await browser.$$(parseLocator.call(this, searchableLocator))
1328
+ if (els.length) {
1329
+ const displayed = await this._isDisplayedSafe(els[0])
1330
+ if (displayed) {
1331
+ return true
1302
1332
  }
1303
- });
1304
- });
1305
- }, timeout * 1000, errorMsg)
1306
- .catch((e) => {
1333
+ }
1334
+
1335
+ await this[direction](scrollLocator, offset, speed)
1336
+ const source = await this.browser.getPageSource()
1337
+ if (source === currentSource) {
1338
+ err = true
1339
+ } else {
1340
+ currentSource = source
1341
+ return false
1342
+ }
1343
+ },
1344
+ timeout * 1000,
1345
+ errorMsg,
1346
+ )
1347
+ .catch(e => {
1307
1348
  if (e.message.indexOf('timeout') && e.type !== 'NoSuchElement') {
1308
- throw new AssertionFailedError({ customMessage: `Scroll to the end and element ${searchableLocator} was not found` }, '');
1349
+ throw new AssertionFailedError({ customMessage: `Scroll to the end and element ${searchableLocator} was not found` }, '')
1309
1350
  } else {
1310
- throw e;
1351
+ throw e
1311
1352
  }
1312
- });
1353
+ })
1313
1354
  }
1314
1355
 
1315
1356
  /**
@@ -1341,8 +1382,8 @@ class Appium extends Webdriver {
1341
1382
  * @param {Array} actions Array of touch actions
1342
1383
  */
1343
1384
  async touchPerform(actions) {
1344
- onlyForApps.call(this);
1345
- return this.browser.touchPerform(actions);
1385
+ onlyForApps.call(this)
1386
+ return this.browser.touchPerform(actions)
1346
1387
  }
1347
1388
 
1348
1389
  /**
@@ -1361,13 +1402,15 @@ class Appium extends Webdriver {
1361
1402
  * Appium: support Android and iOS
1362
1403
  */
1363
1404
  async pullFile(path, dest) {
1364
- onlyForApps.call(this);
1365
- return this.browser.pullFile(path).then(res => fs.writeFile(dest, Buffer.from(res, 'base64'), (err) => {
1366
- if (err) {
1367
- return false;
1368
- }
1369
- return true;
1370
- }));
1405
+ onlyForApps.call(this)
1406
+ return this.browser.pullFile(path).then(res =>
1407
+ fs.writeFile(dest, Buffer.from(res, 'base64'), err => {
1408
+ if (err) {
1409
+ return false
1410
+ }
1411
+ return true
1412
+ }),
1413
+ )
1371
1414
  }
1372
1415
 
1373
1416
  /**
@@ -1382,8 +1425,8 @@ class Appium extends Webdriver {
1382
1425
  * Appium: support only iOS
1383
1426
  */
1384
1427
  async shakeDevice() {
1385
- onlyForApps.call(this, 'iOS');
1386
- return this.browser.shake();
1428
+ onlyForApps.call(this, 'iOS')
1429
+ return this.browser.shake()
1387
1430
  }
1388
1431
 
1389
1432
  /**
@@ -1400,8 +1443,8 @@ class Appium extends Webdriver {
1400
1443
  * Appium: support only iOS
1401
1444
  */
1402
1445
  async rotate(x, y, duration, radius, rotation, touchCount) {
1403
- onlyForApps.call(this, 'iOS');
1404
- return this.browser.rotate(x, y, duration, radius, rotation, touchCount);
1446
+ onlyForApps.call(this, 'iOS')
1447
+ return this.browser.rotate(x, y, duration, radius, rotation, touchCount)
1405
1448
  }
1406
1449
 
1407
1450
  /**
@@ -1414,8 +1457,8 @@ class Appium extends Webdriver {
1414
1457
  * Appium: support only iOS
1415
1458
  */
1416
1459
  async setImmediateValue(id, value) {
1417
- onlyForApps.call(this, 'iOS');
1418
- return this.browser.setImmediateValue(id, value);
1460
+ onlyForApps.call(this, 'iOS')
1461
+ return this.browser.setImmediateValue(id, value)
1419
1462
  }
1420
1463
 
1421
1464
  /**
@@ -1433,9 +1476,9 @@ class Appium extends Webdriver {
1433
1476
  * TODO: not tested
1434
1477
  */
1435
1478
  async simulateTouchId(match) {
1436
- onlyForApps.call(this, 'iOS');
1437
- match = match || true;
1438
- return this.browser.touchId(match);
1479
+ onlyForApps.call(this, 'iOS')
1480
+ match = match || true
1481
+ return this.browser.touchId(match)
1439
1482
  }
1440
1483
 
1441
1484
  /**
@@ -1450,8 +1493,8 @@ class Appium extends Webdriver {
1450
1493
  * Appium: support both Android and iOS
1451
1494
  */
1452
1495
  async closeApp() {
1453
- onlyForApps.call(this);
1454
- return this.browser.closeApp();
1496
+ onlyForApps.call(this)
1497
+ return this.browser.closeApp()
1455
1498
  }
1456
1499
 
1457
1500
  /**
@@ -1459,8 +1502,8 @@ class Appium extends Webdriver {
1459
1502
  *
1460
1503
  */
1461
1504
  async appendField(field, value) {
1462
- if (this.isWeb) return super.appendField(field, value);
1463
- return super.appendField(parseLocator.call(this, field), value);
1505
+ if (this.isWeb) return super.appendField(field, value)
1506
+ return super.appendField(parseLocator.call(this, field), value)
1464
1507
  }
1465
1508
 
1466
1509
  /**
@@ -1468,8 +1511,8 @@ class Appium extends Webdriver {
1468
1511
  *
1469
1512
  */
1470
1513
  async checkOption(field) {
1471
- if (this.isWeb) return super.checkOption(field);
1472
- return super.checkOption(parseLocator.call(this, field));
1514
+ if (this.isWeb) return super.checkOption(field)
1515
+ return super.checkOption(parseLocator.call(this, field))
1473
1516
  }
1474
1517
 
1475
1518
  /**
@@ -1477,8 +1520,15 @@ class Appium extends Webdriver {
1477
1520
  *
1478
1521
  */
1479
1522
  async click(locator, context) {
1480
- if (this.isWeb) return super.click(locator, context);
1481
- return super.click(parseLocator.call(this, locator), parseLocator.call(this, context));
1523
+ if (this.isWeb) return super.click(locator, context)
1524
+
1525
+ const { elementId } = await this.browser.$(parseLocator.call(this, locator), parseLocator.call(this, context))
1526
+
1527
+ return this.axios({
1528
+ method: 'post',
1529
+ url: `${this._buildAppiumEndpoint()}/element/${elementId}/click`,
1530
+ data: {},
1531
+ })
1482
1532
  }
1483
1533
 
1484
1534
  /**
@@ -1486,16 +1536,35 @@ class Appium extends Webdriver {
1486
1536
  *
1487
1537
  */
1488
1538
  async dontSeeCheckboxIsChecked(field) {
1489
- if (this.isWeb) return super.dontSeeCheckboxIsChecked(field);
1490
- return super.dontSeeCheckboxIsChecked(parseLocator.call(this, field));
1539
+ if (this.isWeb) return super.dontSeeCheckboxIsChecked(field)
1540
+ return super.dontSeeCheckboxIsChecked(parseLocator.call(this, field))
1491
1541
  }
1492
1542
 
1493
1543
  /**
1494
1544
  * {{> dontSeeElement }}
1495
1545
  */
1496
1546
  async dontSeeElement(locator) {
1497
- if (this.isWeb) return super.dontSeeElement(locator);
1498
- return super.dontSeeElement(parseLocator.call(this, locator));
1547
+ if (this.isWeb) return super.dontSeeElement(locator)
1548
+
1549
+ // For mobile native apps, use safe isDisplayed wrapper
1550
+ const parsedLocator = parseLocator.call(this, locator)
1551
+ const res = await this._locate(parsedLocator, false)
1552
+
1553
+ if (!res || res.length === 0) {
1554
+ return truth(`elements of ${Locator.build(parsedLocator)}`, 'to be seen').negate(false)
1555
+ }
1556
+
1557
+ const selected = []
1558
+ for (const el of res) {
1559
+ const displayed = await this._isDisplayedSafe(el)
1560
+ if (displayed) selected.push(true)
1561
+ }
1562
+
1563
+ try {
1564
+ return truth(`elements of ${Locator.build(parsedLocator)}`, 'to be seen').negate(selected)
1565
+ } catch (err) {
1566
+ throw err
1567
+ }
1499
1568
  }
1500
1569
 
1501
1570
  /**
@@ -1503,17 +1572,17 @@ class Appium extends Webdriver {
1503
1572
  *
1504
1573
  */
1505
1574
  async dontSeeInField(field, value) {
1506
- const _value = (typeof value === 'boolean') ? value : value.toString();
1507
- if (this.isWeb) return super.dontSeeInField(field, _value);
1508
- return super.dontSeeInField(parseLocator.call(this, field), _value);
1575
+ const _value = typeof value === 'boolean' ? value : value.toString()
1576
+ if (this.isWeb) return super.dontSeeInField(field, _value)
1577
+ return super.dontSeeInField(parseLocator.call(this, field), _value)
1509
1578
  }
1510
1579
 
1511
1580
  /**
1512
1581
  * {{> dontSee }}
1513
1582
  */
1514
1583
  async dontSee(text, context = null) {
1515
- if (this.isWeb) return super.dontSee(text, context);
1516
- return super.dontSee(text, parseLocator.call(this, context));
1584
+ if (this.isWeb) return super.dontSee(text, context)
1585
+ return super.dontSee(text, parseLocator.call(this, context))
1517
1586
  }
1518
1587
 
1519
1588
  /**
@@ -1521,9 +1590,9 @@ class Appium extends Webdriver {
1521
1590
  *
1522
1591
  */
1523
1592
  async fillField(field, value) {
1524
- value = value.toString();
1525
- if (this.isWeb) return super.fillField(field, value);
1526
- return super.fillField(parseLocator.call(this, field), value);
1593
+ value = value.toString()
1594
+ if (this.isWeb) return super.fillField(field, value)
1595
+ return super.fillField(parseLocator.call(this, field), value)
1527
1596
  }
1528
1597
 
1529
1598
  /**
@@ -1531,8 +1600,8 @@ class Appium extends Webdriver {
1531
1600
  *
1532
1601
  */
1533
1602
  async grabTextFromAll(locator) {
1534
- if (this.isWeb) return super.grabTextFromAll(locator);
1535
- return super.grabTextFromAll(parseLocator.call(this, locator));
1603
+ if (this.isWeb) return super.grabTextFromAll(locator)
1604
+ return super.grabTextFromAll(parseLocator.call(this, locator))
1536
1605
  }
1537
1606
 
1538
1607
  /**
@@ -1540,16 +1609,27 @@ class Appium extends Webdriver {
1540
1609
  *
1541
1610
  */
1542
1611
  async grabTextFrom(locator) {
1543
- if (this.isWeb) return super.grabTextFrom(locator);
1544
- return super.grabTextFrom(parseLocator.call(this, locator));
1612
+ if (this.isWeb) return super.grabTextFrom(locator)
1613
+ return super.grabTextFrom(parseLocator.call(this, locator))
1545
1614
  }
1546
1615
 
1547
1616
  /**
1548
1617
  * {{> grabNumberOfVisibleElements }}
1549
1618
  */
1550
1619
  async grabNumberOfVisibleElements(locator) {
1551
- if (this.isWeb) return super.grabNumberOfVisibleElements(locator);
1552
- return super.grabNumberOfVisibleElements(parseLocator.call(this, locator));
1620
+ if (this.isWeb) return super.grabNumberOfVisibleElements(locator)
1621
+
1622
+ // For mobile native apps, use safe isDisplayed wrapper
1623
+ const parsedLocator = parseLocator.call(this, locator)
1624
+ const res = await this._locate(parsedLocator)
1625
+
1626
+ const selected = []
1627
+ for (const el of res) {
1628
+ const displayed = await this._isDisplayedSafe(el)
1629
+ if (displayed) selected.push(true)
1630
+ }
1631
+
1632
+ return selected.length
1553
1633
  }
1554
1634
 
1555
1635
  /**
@@ -1558,8 +1638,8 @@ class Appium extends Webdriver {
1558
1638
  * {{> grabAttributeFrom }}
1559
1639
  */
1560
1640
  async grabAttributeFrom(locator, attr) {
1561
- if (this.isWeb) return super.grabAttributeFrom(locator, attr);
1562
- return super.grabAttributeFrom(parseLocator.call(this, locator), attr);
1641
+ if (this.isWeb) return super.grabAttributeFrom(locator, attr)
1642
+ return super.grabAttributeFrom(parseLocator.call(this, locator), attr)
1563
1643
  }
1564
1644
 
1565
1645
  /**
@@ -1567,8 +1647,8 @@ class Appium extends Webdriver {
1567
1647
  * {{> grabAttributeFromAll }}
1568
1648
  */
1569
1649
  async grabAttributeFromAll(locator, attr) {
1570
- if (this.isWeb) return super.grabAttributeFromAll(locator, attr);
1571
- return super.grabAttributeFromAll(parseLocator.call(this, locator), attr);
1650
+ if (this.isWeb) return super.grabAttributeFromAll(locator, attr)
1651
+ return super.grabAttributeFromAll(parseLocator.call(this, locator), attr)
1572
1652
  }
1573
1653
 
1574
1654
  /**
@@ -1576,8 +1656,8 @@ class Appium extends Webdriver {
1576
1656
  *
1577
1657
  */
1578
1658
  async grabValueFromAll(locator) {
1579
- if (this.isWeb) return super.grabValueFromAll(locator);
1580
- return super.grabValueFromAll(parseLocator.call(this, locator));
1659
+ if (this.isWeb) return super.grabValueFromAll(locator)
1660
+ return super.grabValueFromAll(parseLocator.call(this, locator))
1581
1661
  }
1582
1662
 
1583
1663
  /**
@@ -1585,8 +1665,8 @@ class Appium extends Webdriver {
1585
1665
  *
1586
1666
  */
1587
1667
  async grabValueFrom(locator) {
1588
- if (this.isWeb) return super.grabValueFrom(locator);
1589
- return super.grabValueFrom(parseLocator.call(this, locator));
1668
+ if (this.isWeb) return super.grabValueFrom(locator)
1669
+ return super.grabValueFrom(parseLocator.call(this, locator))
1590
1670
  }
1591
1671
 
1592
1672
  /**
@@ -1601,7 +1681,7 @@ class Appium extends Webdriver {
1601
1681
  * @return {Promise<void>}
1602
1682
  */
1603
1683
  async saveScreenshot(fileName) {
1604
- return super.saveScreenshot(fileName, false);
1684
+ return super.saveScreenshot(fileName, false)
1605
1685
  }
1606
1686
 
1607
1687
  /**
@@ -1610,7 +1690,7 @@ class Appium extends Webdriver {
1610
1690
  * Supported only for web testing
1611
1691
  */
1612
1692
  async scrollIntoView(locator, scrollIntoViewOptions) {
1613
- if (this.isWeb) return super.scrollIntoView(locator, scrollIntoViewOptions);
1693
+ if (this.isWeb) return super.scrollIntoView(locator, scrollIntoViewOptions)
1614
1694
  }
1615
1695
 
1616
1696
  /**
@@ -1618,8 +1698,8 @@ class Appium extends Webdriver {
1618
1698
  *
1619
1699
  */
1620
1700
  async seeCheckboxIsChecked(field) {
1621
- if (this.isWeb) return super.seeCheckboxIsChecked(field);
1622
- return super.seeCheckboxIsChecked(parseLocator.call(this, field));
1701
+ if (this.isWeb) return super.seeCheckboxIsChecked(field)
1702
+ return super.seeCheckboxIsChecked(parseLocator.call(this, field))
1623
1703
  }
1624
1704
 
1625
1705
  /**
@@ -1627,8 +1707,27 @@ class Appium extends Webdriver {
1627
1707
  *
1628
1708
  */
1629
1709
  async seeElement(locator) {
1630
- if (this.isWeb) return super.seeElement(locator);
1631
- return super.seeElement(parseLocator.call(this, locator));
1710
+ if (this.isWeb) return super.seeElement(locator)
1711
+
1712
+ // For mobile native apps, use safe isDisplayed wrapper
1713
+ const parsedLocator = parseLocator.call(this, locator)
1714
+ const res = await this._locate(parsedLocator, true)
1715
+
1716
+ if (!res || res.length === 0) {
1717
+ throw new ElementNotFound(parsedLocator)
1718
+ }
1719
+
1720
+ const selected = []
1721
+ for (const el of res) {
1722
+ const displayed = await this._isDisplayedSafe(el)
1723
+ if (displayed) selected.push(true)
1724
+ }
1725
+
1726
+ try {
1727
+ return truth(`elements of ${Locator.build(parsedLocator)}`, 'to be seen').assert(selected)
1728
+ } catch (e) {
1729
+ dontSeeElementError(parsedLocator)
1730
+ }
1632
1731
  }
1633
1732
 
1634
1733
  /**
@@ -1636,9 +1735,9 @@ class Appium extends Webdriver {
1636
1735
  *
1637
1736
  */
1638
1737
  async seeInField(field, value) {
1639
- const _value = (typeof value === 'boolean') ? value : value.toString();
1640
- if (this.isWeb) return super.seeInField(field, _value);
1641
- return super.seeInField(parseLocator.call(this, field), _value);
1738
+ const _value = typeof value === 'boolean' ? value : value.toString()
1739
+ if (this.isWeb) return super.seeInField(field, _value)
1740
+ return super.seeInField(parseLocator.call(this, field), _value)
1642
1741
  }
1643
1742
 
1644
1743
  /**
@@ -1646,8 +1745,8 @@ class Appium extends Webdriver {
1646
1745
  *
1647
1746
  */
1648
1747
  async see(text, context) {
1649
- if (this.isWeb) return super.see(text, context);
1650
- return super.see(text, parseLocator.call(this, context));
1748
+ if (this.isWeb) return super.see(text, context)
1749
+ return super.see(text, parseLocator.call(this, context))
1651
1750
  }
1652
1751
 
1653
1752
  /**
@@ -1656,8 +1755,8 @@ class Appium extends Webdriver {
1656
1755
  * Supported only for web testing
1657
1756
  */
1658
1757
  async selectOption(select, option) {
1659
- if (this.isWeb) return super.selectOption(select, option);
1660
- throw new Error('Should be used only in Web context. In native context use \'click\' method instead');
1758
+ if (this.isWeb) return super.selectOption(select, option)
1759
+ throw new Error("Should be used only in Web context. In native context use 'click' method instead")
1661
1760
  }
1662
1761
 
1663
1762
  /**
@@ -1665,8 +1764,8 @@ class Appium extends Webdriver {
1665
1764
  *
1666
1765
  */
1667
1766
  async waitForElement(locator, sec = null) {
1668
- if (this.isWeb) return super.waitForElement(locator, sec);
1669
- return super.waitForElement(parseLocator.call(this, locator), sec);
1767
+ if (this.isWeb) return super.waitForElement(locator, sec)
1768
+ return super.waitForElement(parseLocator.call(this, locator), sec)
1670
1769
  }
1671
1770
 
1672
1771
  /**
@@ -1674,8 +1773,30 @@ class Appium extends Webdriver {
1674
1773
  *
1675
1774
  */
1676
1775
  async waitForVisible(locator, sec = null) {
1677
- if (this.isWeb) return super.waitForVisible(locator, sec);
1678
- return super.waitForVisible(parseLocator.call(this, locator), sec);
1776
+ if (this.isWeb) return super.waitForVisible(locator, sec)
1777
+
1778
+ // For mobile native apps, use safe isDisplayed wrapper
1779
+ const parsedLocator = parseLocator.call(this, locator)
1780
+ const aSec = sec || this.options.waitForTimeoutInSeconds
1781
+
1782
+ return this.browser.waitUntil(
1783
+ async () => {
1784
+ const res = await this._res(parsedLocator)
1785
+ if (!res || res.length === 0) return false
1786
+
1787
+ const selected = []
1788
+ for (const el of res) {
1789
+ const displayed = await this._isDisplayedSafe(el)
1790
+ if (displayed) selected.push(true)
1791
+ }
1792
+
1793
+ return selected.length > 0
1794
+ },
1795
+ {
1796
+ timeout: aSec * 1000,
1797
+ timeoutMsg: `element (${Locator.build(parsedLocator)}) still not visible after ${aSec} sec`,
1798
+ },
1799
+ )
1679
1800
  }
1680
1801
 
1681
1802
  /**
@@ -1683,8 +1804,27 @@ class Appium extends Webdriver {
1683
1804
  *
1684
1805
  */
1685
1806
  async waitForInvisible(locator, sec = null) {
1686
- if (this.isWeb) return super.waitForInvisible(locator, sec);
1687
- return super.waitForInvisible(parseLocator.call(this, locator), sec);
1807
+ if (this.isWeb) return super.waitForInvisible(locator, sec)
1808
+
1809
+ // For mobile native apps, use safe isDisplayed wrapper
1810
+ const parsedLocator = parseLocator.call(this, locator)
1811
+ const aSec = sec || this.options.waitForTimeoutInSeconds
1812
+
1813
+ return this.browser.waitUntil(
1814
+ async () => {
1815
+ const res = await this._res(parsedLocator)
1816
+ if (!res || res.length === 0) return true
1817
+
1818
+ const selected = []
1819
+ for (const el of res) {
1820
+ const displayed = await this._isDisplayedSafe(el)
1821
+ if (displayed) selected.push(true)
1822
+ }
1823
+
1824
+ return selected.length === 0
1825
+ },
1826
+ { timeout: aSec * 1000, timeoutMsg: `element (${Locator.build(parsedLocator)}) still visible after ${aSec} sec` },
1827
+ )
1688
1828
  }
1689
1829
 
1690
1830
  /**
@@ -1692,74 +1832,72 @@ class Appium extends Webdriver {
1692
1832
  *
1693
1833
  */
1694
1834
  async waitForText(text, sec = null, context = null) {
1695
- if (this.isWeb) return super.waitForText(text, sec, context);
1696
- return super.waitForText(text, sec, parseLocator.call(this, context));
1835
+ if (this.isWeb) return super.waitForText(text, sec, context)
1836
+ return super.waitForText(text, sec, parseLocator.call(this, context))
1697
1837
  }
1698
1838
  }
1699
1839
 
1700
1840
  function parseLocator(locator) {
1701
- if (!locator) return null;
1841
+ if (!locator) return null
1702
1842
 
1703
1843
  if (typeof locator === 'object') {
1704
1844
  if (locator.web && this.isWeb) {
1705
- return parseLocator.call(this, locator.web);
1845
+ return parseLocator.call(this, locator.web)
1706
1846
  }
1707
1847
 
1708
1848
  if (locator.android && this.platform === 'android') {
1709
1849
  if (typeof locator.android === 'string') {
1710
- return parseLocator.call(this, locator.android);
1850
+ return parseLocator.call(this, locator.android)
1711
1851
  }
1712
1852
  // The locator is an Android DataMatcher or ViewMatcher locator so return as is
1713
- return locator.android;
1853
+ return locator.android
1714
1854
  }
1715
1855
 
1716
1856
  if (locator.ios && this.platform === 'ios') {
1717
- return parseLocator.call(this, locator.ios);
1857
+ return parseLocator.call(this, locator.ios)
1718
1858
  }
1719
1859
  }
1720
1860
 
1721
1861
  if (typeof locator === 'string') {
1722
- if (locator[0] === '~') return locator;
1723
- if (locator.substr(0, 2) === '//') return locator;
1862
+ if (locator[0] === '~') return locator
1863
+ if (locator.substr(0, 2) === '//') return locator
1724
1864
  if (locator[0] === '#' && !this.isWeb) {
1725
1865
  // hook before webdriverio supports native # locators
1726
- return parseLocator.call(this, { id: locator.slice(1) });
1866
+ return parseLocator.call(this, { id: locator.slice(1) })
1727
1867
  }
1728
1868
 
1729
1869
  if (this.platform === 'android' && !this.isWeb) {
1730
- const isNativeLocator = /^\-?android=?/.exec(locator);
1731
- return isNativeLocator
1732
- ? locator
1733
- : `android=new UiSelector().text("${locator}")`;
1870
+ const isNativeLocator = /^\-?android=?/.exec(locator)
1871
+ return isNativeLocator ? locator : `android=new UiSelector().text("${locator}")`
1734
1872
  }
1735
1873
  }
1736
1874
 
1737
- locator = new Locator(locator, 'xpath');
1738
- if (locator.type === 'css' && !this.isWeb) throw new Error('Unable to use css locators in apps. Locator strategies for this request: xpath, id, class name or accessibility id');
1739
- if (locator.type === 'name' && !this.isWeb) throw new Error("Can't locate element by name in Native context. Use either ID, class name or accessibility id");
1740
- if (locator.type === 'id' && !this.isWeb && this.platform === 'android') return `//*[@resource-id='${locator.value}']`;
1741
- return locator.simplify();
1875
+ locator = new Locator(locator, 'xpath')
1876
+ if (locator.type === 'css' && !this.isWeb) throw new Error('Unable to use css locators in apps. Locator strategies for this request: xpath, id, class name or accessibility id')
1877
+ if (locator.type === 'name' && !this.isWeb) throw new Error("Can't locate element by name in Native context. Use either ID, class name or accessibility id")
1878
+ if (locator.type === 'id' && !this.isWeb && this.platform === 'android') return `//*[@resource-id='${locator.value}']`
1879
+ return locator.simplify()
1742
1880
  }
1743
1881
 
1744
1882
  // in the end of a file
1745
1883
  function onlyForApps(expectedPlatform) {
1746
- const stack = new Error().stack || '';
1747
- const re = /Appium.(\w+)/g;
1748
- const caller = stack.split('\n')[2].trim();
1749
- const m = re.exec(caller);
1884
+ const stack = new Error().stack || ''
1885
+ const re = /Appium.(\w+)/g
1886
+ const caller = stack.split('\n')[2].trim()
1887
+ const m = re.exec(caller)
1750
1888
 
1751
1889
  if (!m) {
1752
- throw new Error(`Invalid caller ${caller}`);
1890
+ throw new Error(`Invalid caller ${caller}`)
1753
1891
  }
1754
1892
 
1755
- const callerName = m[1] || m[2];
1893
+ const callerName = m[1] || m[2]
1756
1894
  if (!expectedPlatform) {
1757
1895
  if (!this.platform) {
1758
- throw new Error(`${callerName} method can be used only with apps`);
1896
+ throw new Error(`${callerName} method can be used only with apps`)
1759
1897
  }
1760
1898
  } else if (this.platform !== expectedPlatform.toLowerCase()) {
1761
- throw new Error(`${callerName} method can be used only with ${expectedPlatform} apps`);
1899
+ throw new Error(`${callerName} method can be used only with ${expectedPlatform} apps`)
1762
1900
  }
1763
1901
  }
1764
1902
 
1765
- export default Appium;
1903
+ export default Appium