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,48 +1,46 @@
1
- import assert from 'assert';
2
- import path from 'path';
3
- import Helper from '@codeceptjs/helper';
4
- import promiseRetry from 'promise-retry';
5
- import { includes as stringIncludes } from '../assert/include.js';
6
- import { urlEquals, equals } from '../assert/equal.js';
7
- import { debug } from '../output.js';
8
- import { empty } from '../assert/empty.js';
9
- import { truth } from '../assert/truth';
10
-
11
- import {
12
- xpathLocator,
13
- fileExists,
14
- decodeUrl,
15
- chunkArray,
16
- convertCssPropertiesToCamelCase,
17
- screenshotOutputFolder,
18
- getNormalizedKeyAttributeValue,
19
- modifierKeys,
20
-
21
- } from '../utils.js';
22
-
23
- import { isColorProperty, convertColorToRGBA } from '../colorUtils';
24
- import ElementNotFound from './errors/ElementNotFound.js';
25
- import ConnectionRefused from './errors/ConnectionRefused.js';
26
- import Locator from '../locator.js';
27
- import { highlightElement } from './scripts/highlightElement';
28
- import { focusElement } from './scripts/focusElement';
29
- import { blurElement } from './scripts/blurElement';
30
-
31
- import {
32
- dontSeeElementError,
33
- seeElementError,
34
- seeElementInDOMError,
35
- dontSeeElementInDOMError,
36
- } from './errors/ElementAssertion';
37
-
38
- import {
39
- dontSeeTraffic, seeTraffic, grabRecordedNetworkTraffics, stopRecordingTraffic, flushNetworkTraffics,
40
- } from './network/actions';
41
-
42
- let webdriverio;
43
-
44
- const SHADOW = 'shadow';
45
- const webRoot = 'body';
1
+ let webdriverio
2
+
3
+ import assert from 'assert'
4
+ import path from 'path'
5
+ import crypto from 'crypto'
6
+
7
+ import Helper from '@codeceptjs/helper'
8
+ import promiseRetry from 'promise-retry'
9
+ import { includes as stringIncludes } from '../assert/include.js'
10
+ import { urlEquals, equals } from '../assert/equal.js'
11
+ import store from '../store.js'
12
+ import output from '../output.js'
13
+ const { debug } = output
14
+ import { empty } from '../assert/empty.js'
15
+ import { truth } from '../assert/truth.js'
16
+ import { xpathLocator, fileExists, decodeUrl, chunkArray, convertCssPropertiesToCamelCase, screenshotOutputFolder, getNormalizedKeyAttributeValue, modifierKeys } from '../utils.js'
17
+ import { isColorProperty, convertColorToRGBA } from '../colorUtils.js'
18
+ import ElementNotFound from './errors/ElementNotFound.js'
19
+ import ConnectionRefused from './errors/ConnectionRefused.js'
20
+ import Locator from '../locator.js'
21
+ import { highlightElement } from './scripts/highlightElement.js'
22
+ import { focusElement } from './scripts/focusElement.js'
23
+ import { blurElement } from './scripts/blurElement.js'
24
+ import { dontSeeElementError, seeElementError, seeElementInDOMError, dontSeeElementInDOMError } from './errors/ElementAssertion.js'
25
+ import { dontSeeTraffic, seeTraffic, grabRecordedNetworkTraffics, stopRecordingTraffic, flushNetworkTraffics } from './network/actions.js'
26
+ import WebElement from '../element/WebElement.js'
27
+
28
+ const SHADOW = 'shadow'
29
+ const webRoot = 'body'
30
+ let browserLogs = []
31
+
32
+ /**
33
+ * Wraps error objects that don't have a proper message property
34
+ * This is needed for ESM compatibility with WebdriverIO error handling
35
+ */
36
+ function wrapError(e) {
37
+ if (e && typeof e === 'object' && !e.message) {
38
+ const err = new Error(e.error || e.timeoutMsg || String(e))
39
+ err.stack = e.stack
40
+ return err
41
+ }
42
+ return e
43
+ }
46
44
 
47
45
  /**
48
46
  * ## Configuration
@@ -53,6 +51,7 @@ const webRoot = 'body';
53
51
  * @type {object}
54
52
  * @prop {string} url - base url of website to be tested.
55
53
  * @prop {string} browser - Browser in which to perform testing.
54
+ * @prop {boolean} [bidiProtocol=false] - WebDriver Bidi Protocol. Default: false. More info: https://webdriver.io/docs/api/webdriverBidi/
56
55
  * @prop {string} [basicAuth] - (optional) the basic authentication to pass to base url. Example: {username: 'username', password: 'password'}
57
56
  * @prop {string} [host=localhost] - WebDriver host to connect.
58
57
  * @prop {number} [port=4444] - WebDriver port to connect.
@@ -72,9 +71,8 @@ const webRoot = 'body';
72
71
  * @prop {object} [timeouts] [WebDriver timeouts](http://webdriver.io/docs/timeouts.html) defined as hash.
73
72
  * @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose).
74
73
  * @prop {string} [logLevel=silent] - level of logging verbosity. Default: silent. Options: trace | debug | info | warn | error | silent. More info: https://webdriver.io/docs/configuration/#loglevel
75
- * @prop {boolean} [devtoolsProtocol=false] - enable devtools protocol. Default: false. More info: https://webdriver.io/docs/automationProtocols/#devtools-protocol.
76
74
  */
77
- const config = {};
75
+ const config = {}
78
76
 
79
77
  /**
80
78
  * WebDriver helper which wraps [webdriverio](http://webdriver.io/) library to
@@ -180,7 +178,6 @@ const config = {};
180
178
  * WebDriver : {
181
179
  * url: "http://localhost",
182
180
  * browser: "chrome",
183
- * devtoolsProtocol: true,
184
181
  * desiredCapabilities: {
185
182
  * chromeOptions: {
186
183
  * args: [ "--headless", "--disable-gpu", "--no-sandbox" ]
@@ -445,34 +442,34 @@ const config = {};
445
442
  */
446
443
  class WebDriver extends Helper {
447
444
  constructor(config) {
448
- super(config);
449
- webdriverio = require('webdriverio');
445
+ super(config)
446
+ // webdriverio will be loaded dynamically in _init method
450
447
 
451
448
  // set defaults
452
- this.root = webRoot;
453
- this.isWeb = true;
454
- this.isRunning = false;
455
- this.sessionWindows = {};
456
- this.activeSessionName = '';
457
- this.customLocatorStrategies = config.customLocatorStrategies;
449
+ this.root = webRoot
450
+ this.isWeb = true
451
+ this.isRunning = false
452
+ this.sessionWindows = {}
453
+ this.activeSessionName = ''
454
+ this.customLocatorStrategies = config.customLocatorStrategies
458
455
 
459
456
  // for network stuff
460
- this.requests = [];
461
- this.recording = false;
462
- this.recordedAtLeastOnce = false;
457
+ this.requests = []
458
+ this.recording = false
459
+ this.recordedAtLeastOnce = false
463
460
 
464
- this._setConfig(config);
461
+ this._setConfig(config)
465
462
 
466
463
  Locator.addFilter((locator, result) => {
467
464
  if (typeof locator === 'string' && locator.indexOf('~') === 0) {
468
465
  // accessibility locator
469
466
  if (this.isWeb) {
470
- result.value = `[aria-label="${locator.slice(1)}"]`;
471
- result.type = 'css';
472
- result.output = `aria-label=${locator.slice(1)}`;
467
+ result.value = `[aria-label="${locator.slice(1)}"]`
468
+ result.type = 'css'
469
+ result.output = `aria-label=${locator.slice(1)}`
473
470
  }
474
471
  }
475
- });
472
+ })
476
473
  }
477
474
 
478
475
  _validateConfig(config) {
@@ -492,41 +489,45 @@ class WebDriver extends Helper {
492
489
  keepBrowserState: false,
493
490
  deprecationWarnings: false,
494
491
  highlightElement: false,
495
- };
492
+ }
496
493
 
497
494
  // override defaults with config
498
- config = Object.assign(defaults, config);
495
+ config = Object.assign(defaults, config)
499
496
 
500
497
  if (config.host) {
501
498
  // webdriverio spec
502
- config.hostname = config.host;
503
- config.path = config.path ? config.path : '/wd/hub';
499
+ config.hostname = config.host
500
+ config.path = config.path ? config.path : '/wd/hub'
504
501
  }
505
502
 
506
- config.baseUrl = config.url || config.baseUrl;
503
+ config.baseUrl = config.url || config.baseUrl
507
504
  if (config.desiredCapabilities && Object.keys(config.desiredCapabilities).length) {
508
- config.capabilities = config.desiredCapabilities;
505
+ config.capabilities = config.desiredCapabilities
509
506
  }
510
- config.capabilities.browserName = config.browser || config.capabilities.browserName;
511
- config.capabilities.browserVersion = config.browserVersion || config.capabilities.browserVersion;
507
+ config.capabilities.browserName = config.browser || config.capabilities.browserName
508
+
509
+ // WebDriver Bidi Protocol. Default: true
510
+ config.capabilities.webSocketUrl = config.bidiProtocol ?? config.capabilities.webSocketUrl ?? true
511
+
512
+ config.capabilities.browserVersion = config.browserVersion || config.capabilities.browserVersion
512
513
  if (config.capabilities.chromeOptions) {
513
- config.capabilities['goog:chromeOptions'] = config.capabilities.chromeOptions;
514
- delete config.capabilities.chromeOptions;
514
+ config.capabilities['goog:chromeOptions'] = config.capabilities.chromeOptions
515
+ delete config.capabilities.chromeOptions
515
516
  }
516
517
  if (config.capabilities.firefoxOptions) {
517
- config.capabilities['moz:firefoxOptions'] = config.capabilities.firefoxOptions;
518
- delete config.capabilities.firefoxOptions;
518
+ config.capabilities['moz:firefoxOptions'] = config.capabilities.firefoxOptions
519
+ delete config.capabilities.firefoxOptions
519
520
  }
520
521
  if (config.capabilities.ieOptions) {
521
- config.capabilities['se:ieOptions'] = config.capabilities.ieOptions;
522
- delete config.capabilities.ieOptions;
522
+ config.capabilities['se:ieOptions'] = config.capabilities.ieOptions
523
+ delete config.capabilities.ieOptions
523
524
  }
524
525
  if (config.capabilities.selenoidOptions) {
525
- config.capabilities['selenoid:options'] = config.capabilities.selenoidOptions;
526
- delete config.capabilities.selenoidOptions;
526
+ config.capabilities['selenoid:options'] = config.capabilities.selenoidOptions
527
+ delete config.capabilities.selenoidOptions
527
528
  }
528
529
 
529
- config.waitForTimeoutInSeconds = config.waitForTimeout / 1000; // convert to seconds
530
+ config.waitForTimeoutInSeconds = config.waitForTimeout / 1000 // convert to seconds
530
531
 
531
532
  if (!config.capabilities.platformName && (!config.url || !config.browser)) {
532
533
  throw new Error(`
@@ -540,196 +541,212 @@ class WebDriver extends Helper {
540
541
  }
541
542
  }
542
543
  }
543
- `);
544
+ `)
544
545
  }
545
546
 
546
- return config;
547
+ return config
547
548
  }
548
549
 
549
550
  static _checkRequirements() {
550
551
  try {
551
- require('webdriverio');
552
+ // In ESM, webdriverio will be checked via dynamic import in _init
553
+ // The import will fail at module load time if webdriverio is missing
554
+ return null
552
555
  } catch (e) {
553
- return ['webdriverio@^6.12.1'];
556
+ return ['webdriverio@^6.12.1']
557
+ }
558
+ }
559
+
560
+ async _init() {
561
+ // Load webdriverio dynamically
562
+ if (!webdriverio) {
563
+ try {
564
+ webdriverio = await import('webdriverio')
565
+ webdriverio = webdriverio.default || webdriverio
566
+ } catch (e) {
567
+ throw new Error('webdriverio could not be loaded. Please install webdriverio.')
568
+ }
554
569
  }
555
570
  }
556
571
 
557
572
  static _config() {
558
- return [{
559
- name: 'url',
560
- message: 'Base url of site to be tested',
561
- default: 'http://localhost',
562
- }, {
563
- name: 'browser',
564
- message: 'Browser in which testing will be performed',
565
- default: 'chrome',
566
- }];
573
+ return [
574
+ {
575
+ name: 'url',
576
+ message: 'Base url of site to be tested',
577
+ default: 'http://localhost',
578
+ },
579
+ {
580
+ name: 'browser',
581
+ message: 'Browser in which testing will be performed',
582
+ default: 'chrome',
583
+ },
584
+ ]
567
585
  }
568
586
 
569
587
  _beforeSuite() {
570
588
  if (!this.options.restart && !this.options.manualStart && !this.isRunning) {
571
- this.debugSection('Session', 'Starting singleton browser session');
572
- return this._startBrowser();
589
+ this.debugSection('Session', 'Starting singleton browser session')
590
+ return this._startBrowser()
573
591
  }
574
592
  }
575
593
 
576
594
  _lookupCustomLocator(customStrategy) {
577
- if (typeof (this.customLocatorStrategies) !== 'object') {
578
- return null;
595
+ if (typeof this.customLocatorStrategies !== 'object') {
596
+ return null
579
597
  }
580
- const strategy = this.customLocatorStrategies[customStrategy];
581
- return typeof (strategy) === 'function' ? strategy : null;
598
+ const strategy = this.customLocatorStrategies[customStrategy]
599
+ return typeof strategy === 'function' ? strategy : null
582
600
  }
583
601
 
584
602
  _isCustomLocator(locator) {
585
- const locatorObj = new Locator(locator);
603
+ const locatorObj = new Locator(locator)
586
604
  if (locatorObj.isCustom()) {
587
- const customLocator = this._lookupCustomLocator(locatorObj.type);
605
+ const customLocator = this._lookupCustomLocator(locatorObj.type)
588
606
  if (customLocator) {
589
- return true;
607
+ return true
590
608
  }
591
- throw new Error('Please define "customLocatorStrategies" as an Object and the Locator Strategy as a "function".');
609
+ throw new Error('Please define "customLocatorStrategies" as an Object and the Locator Strategy as a "function".')
592
610
  }
593
- return false;
611
+ return false
594
612
  }
595
613
 
596
614
  async _res(locator) {
597
- const res = (this._isShadowLocator(locator) || this._isCustomLocator(locator))
598
- ? await this._locate(locator)
599
- : await this.$$(withStrictLocator(locator));
600
- return res;
615
+ const res = this._isShadowLocator(locator) || this._isCustomLocator(locator) ? await this._locate(locator) : await this.$$(withStrictLocator(locator))
616
+ return res
601
617
  }
602
618
 
603
619
  async _startBrowser() {
604
620
  try {
605
621
  if (this.options.multiremote) {
606
- this.browser = await webdriverio.multiremote(this.options.multiremote);
622
+ this.browser = await webdriverio.multiremote(this.options.multiremote)
607
623
  } else {
608
624
  // remove non w3c capabilities
609
- delete this.options.capabilities.protocol;
610
- delete this.options.capabilities.hostname;
611
- delete this.options.capabilities.port;
612
- delete this.options.capabilities.path;
613
- if (this.options.devtoolsProtocol) {
614
- if (!['chrome', 'chromium'].includes(this.options.browser.toLowerCase())) throw Error('The devtools protocol is only working with Chrome or Chromium');
615
- this.options.automationProtocol = 'devtools';
616
- }
617
- this.browser = await webdriverio.remote(this.options);
625
+ delete this.options.capabilities.protocol
626
+ delete this.options.capabilities.hostname
627
+ delete this.options.capabilities.port
628
+ delete this.options.capabilities.path
629
+ this.browser = await webdriverio.remote(this.options)
618
630
  }
619
631
  } catch (err) {
620
632
  if (err.toString().indexOf('ECONNREFUSED')) {
621
- throw new ConnectionRefused(err);
633
+ throw new ConnectionRefused(err)
622
634
  }
623
- throw err;
635
+ throw err
624
636
  }
625
637
 
626
- this.isRunning = true;
638
+ this.isRunning = true
627
639
  if (this.options.timeouts && this.isWeb) {
628
- await this.defineTimeout(this.options.timeouts);
640
+ await this.defineTimeout(this.options.timeouts)
629
641
  }
630
642
 
631
- await this._resizeWindowIfNeeded(this.browser, this.options.windowSize);
643
+ await this._resizeWindowIfNeeded(this.browser, this.options.windowSize)
632
644
 
633
- this.$$ = this.browser.$$.bind(this.browser);
645
+ this.$$ = this.browser.$$.bind(this.browser)
634
646
 
635
647
  if (this._isCustomLocatorStrategyDefined()) {
636
- Object.keys(this.customLocatorStrategies).forEach(async (customLocator) => {
637
- this.debugSection('Weddriver', `adding custom locator strategy: ${customLocator}`);
638
- const locatorFunction = this._lookupCustomLocator(customLocator);
639
- this.browser.addLocatorStrategy(customLocator, locatorFunction);
640
- });
648
+ Object.keys(this.customLocatorStrategies).forEach(async customLocator => {
649
+ this.debugSection('Weddriver', `adding custom locator strategy: ${customLocator}`)
650
+ const locatorFunction = this._lookupCustomLocator(customLocator)
651
+ this.browser.addLocatorStrategy(customLocator, locatorFunction)
652
+ })
641
653
  }
642
654
 
643
655
  if (this.browser.capabilities && this.browser.capabilities.platformName) {
644
- this.browser.capabilities.platformName = this.browser.capabilities.platformName.toLowerCase();
656
+ this.browser.capabilities.platformName = this.browser.capabilities.platformName.toLowerCase()
645
657
  }
646
658
 
647
- if (this.options.automationProtocol) {
648
- this.puppeteerBrowser = await this.browser.getPuppeteer();
659
+ this.browser.on('dialog', () => {})
660
+
661
+ // Check for Bidi, because "sessionSubscribe" is an exclusive Bidi protocol feature. Otherwise, error will be thrown.
662
+ if (this.browser.capabilities && this.browser.capabilities.webSocketUrl) {
663
+ await this.browser.sessionSubscribe({ events: ['log.entryAdded'] })
664
+ this.browser.on('log.entryAdded', logEvents)
649
665
  }
650
666
 
651
- return this.browser;
667
+ return this.browser
652
668
  }
653
669
 
654
670
  _isCustomLocatorStrategyDefined() {
655
- return this.customLocatorStrategies && Object.keys(this.customLocatorStrategies).length;
671
+ return this.customLocatorStrategies && Object.keys(this.customLocatorStrategies).length
656
672
  }
657
673
 
658
674
  async _stopBrowser() {
659
- if (this.browser && this.isRunning) await this.browser.deleteSession();
675
+ if (this.browser && this.isRunning) await this.browser.deleteSession()
660
676
  }
661
677
 
662
678
  async _before() {
663
- this.context = this.root;
664
- if (this.options.restart && !this.options.manualStart) return this._startBrowser();
665
- if (!this.isRunning && !this.options.manualStart) return this._startBrowser();
666
- if (this.browser) this.$$ = this.browser.$$.bind(this.browser);
667
- return this.browser;
679
+ if (!webdriverio) await this._init()
680
+ this.context = this.root
681
+ if (this.options.restart && !this.options.manualStart) return this._startBrowser()
682
+ if (!this.isRunning && !this.options.manualStart) return this._startBrowser()
683
+ if (this.browser) this.$$ = this.browser.$$.bind(this.browser)
684
+ return this.browser
668
685
  }
669
686
 
670
687
  async _after() {
671
- if (!this.isRunning) return;
688
+ if (!this.isRunning) return
672
689
  if (this.options.restart) {
673
- this.isRunning = false;
674
- return this.browser.deleteSession();
690
+ this.isRunning = false
691
+ return this.browser.deleteSession()
675
692
  }
676
- if (this.browser.isInsideFrame) await this.browser.switchToFrame(null);
693
+ if (this.browser.isInsideFrame) await this.browser.switchFrame(null)
677
694
 
678
- if (this.options.keepBrowserState) return;
695
+ if (this.options.keepBrowserState) return
679
696
 
680
697
  if (!this.options.keepCookies && this.options.capabilities.browserName) {
681
- this.debugSection('Session', 'cleaning cookies and localStorage');
682
- await this.browser.deleteCookies();
698
+ this.debugSection('Session', 'cleaning cookies and localStorage')
699
+ await this.browser.deleteCookies()
683
700
  }
684
- await this.browser.execute('localStorage.clear();').catch((err) => {
685
- if (!(err.message.indexOf("Storage is disabled inside 'data:' URLs.") > -1)) throw err;
686
- });
687
- await this.closeOtherTabs();
688
- return this.browser;
701
+ await this.browser.execute('localStorage.clear();').catch(err => {
702
+ if (!(err.message.indexOf("Storage is disabled inside 'data:' URLs.") > -1)) throw err
703
+ })
704
+ await this.closeOtherTabs()
705
+ browserLogs = []
706
+ return this.browser
689
707
  }
690
708
 
691
- _afterSuite() {
692
- }
709
+ _afterSuite() {}
693
710
 
694
711
  _finishTest() {
695
- if (!this.options.restart && this.isRunning) return this._stopBrowser();
712
+ if (!this.options.restart && this.isRunning) return this._stopBrowser()
696
713
  }
697
714
 
698
715
  _session() {
699
- const defaultSession = this.browser;
716
+ const defaultSession = this.browser
700
717
  return {
701
718
  start: async (sessionName, opts) => {
702
719
  // opts.disableScreenshots = true; // screenshots cant be saved as session will be already closed
703
- opts = this._validateConfig(Object.assign(this.options, opts));
704
- this.debugSection('New Browser', JSON.stringify(opts));
705
- const browser = await webdriverio.remote(opts);
706
- this.activeSessionName = sessionName;
720
+ opts = this._validateConfig(Object.assign(this.options, opts))
721
+ this.debugSection('New Browser', JSON.stringify(opts))
722
+ const browser = await webdriverio.remote(opts)
723
+ this.activeSessionName = sessionName
707
724
  if (opts.timeouts && this.isWeb) {
708
- await this._defineBrowserTimeout(browser, opts.timeouts);
725
+ await this._defineBrowserTimeout(browser, opts.timeouts)
709
726
  }
710
727
 
711
- await this._resizeWindowIfNeeded(browser, opts.windowSize);
728
+ await this._resizeWindowIfNeeded(browser, opts.windowSize)
712
729
 
713
- return browser;
730
+ return browser
714
731
  },
715
- stop: async (browser) => {
716
- if (!browser) return;
717
- return browser.deleteSession();
732
+ stop: async browser => {
733
+ if (!browser) return
734
+ return browser.deleteSession()
718
735
  },
719
- loadVars: async (browser) => {
720
- if (this.context !== this.root) throw new Error('Can\'t start session inside within block');
721
- this.browser = browser;
722
- this.$$ = this.browser.$$.bind(this.browser);
723
- this.sessionWindows[this.activeSessionName] = browser;
736
+ loadVars: async browser => {
737
+ if (this.context !== this.root) throw new Error("Can't start session inside within block")
738
+ this.browser = browser
739
+ this.$$ = this.browser.$$.bind(this.browser)
740
+ this.sessionWindows[this.activeSessionName] = browser
724
741
  },
725
- restoreVars: async (session) => {
742
+ restoreVars: async session => {
726
743
  if (!session) {
727
- this.activeSessionName = '';
744
+ this.activeSessionName = ''
728
745
  }
729
- this.browser = defaultSession;
730
- this.$$ = this.browser.$$.bind(this.browser);
746
+ this.browser = defaultSession
747
+ this.$$ = this.browser.$$.bind(this.browser)
731
748
  },
732
- };
749
+ }
733
750
  }
734
751
 
735
752
  /**
@@ -751,41 +768,41 @@ class WebDriver extends Helper {
751
768
  * @param {function} fn async functuion that executed with WebDriver helper as argument
752
769
  */
753
770
  useWebDriverTo(description, fn) {
754
- return this._useTo(...arguments);
771
+ return this._useTo(...arguments)
755
772
  }
756
773
 
757
774
  async _failed() {
758
- if (this.context !== this.root) await this._withinEnd();
775
+ if (this.context !== this.root) await this._withinEnd()
759
776
  }
760
777
 
761
778
  async _withinBegin(locator) {
762
- const frame = isFrameLocator(locator);
779
+ const frame = isFrameLocator(locator)
763
780
  if (frame) {
764
- this.browser.isInsideFrame = true;
781
+ this.browser.isInsideFrame = true
765
782
  if (Array.isArray(frame)) {
766
783
  // this.switchTo(null);
767
- await forEachAsync(frame, async f => this.switchTo(f));
768
- return;
784
+ await forEachAsync(frame, async f => this.switchTo(f))
785
+ return
769
786
  }
770
- await this.switchTo(frame);
771
- return;
787
+ await this.switchTo(frame)
788
+ return
772
789
  }
773
- this.context = locator;
790
+ this.context = locator
774
791
 
775
- let res = await this.browser.$$(withStrictLocator(locator));
776
- assertElementExists(res, locator);
777
- res = usingFirstElement(res);
778
- this.context = res.selector;
779
- this.$$ = res.$$.bind(res);
792
+ let res = await this.browser.$$(withStrictLocator(locator))
793
+ assertElementExists(res, locator)
794
+ res = usingFirstElement(res)
795
+ this.context = res.selector
796
+ this.$$ = res.$$.bind(res)
780
797
  }
781
798
 
782
799
  async _withinEnd() {
783
800
  if (this.browser.isInsideFrame) {
784
- this.browser.isInsideFrame = false;
785
- return this.switchTo(null);
801
+ this.browser.isInsideFrame = false
802
+ return this.switchTo(null)
786
803
  }
787
- this.context = this.root;
788
- this.$$ = this.browser.$$.bind(this.browser);
804
+ this.context = this.root
805
+ this.$$ = this.browser.$$.bind(this.browser)
789
806
  }
790
807
 
791
808
  /**
@@ -794,7 +811,7 @@ class WebDriver extends Helper {
794
811
  * @param {object} locator
795
812
  */
796
813
  _isShadowLocator(locator) {
797
- return locator.type === SHADOW || locator[SHADOW];
814
+ return locator.type === SHADOW || locator[SHADOW]
798
815
  }
799
816
 
800
817
  /**
@@ -803,35 +820,37 @@ class WebDriver extends Helper {
803
820
  * @param {object} locator
804
821
  */
805
822
  async _locateShadow(locator) {
806
- const shadow = locator.value ? locator.value : locator[SHADOW];
807
- const shadowSequence = [];
808
- let elements;
823
+ const shadow = locator.value ? locator.value : locator[SHADOW]
824
+ const shadowSequence = []
825
+ let elements
809
826
 
810
827
  if (!Array.isArray(shadow)) {
811
- throw new Error(`Shadow '${shadow}' should be defined as an Array of elements.`);
828
+ throw new Error(`Shadow '${shadow}' should be defined as an Array of elements.`)
812
829
  }
813
830
 
814
831
  // traverse through the Shadow locators in sequence
815
832
  for (let index = 0; index < shadow.length; index++) {
816
- const shadowElement = shadow[index];
817
- shadowSequence.push(shadowElement);
833
+ const shadowElement = shadow[index]
834
+ shadowSequence.push(shadowElement)
818
835
 
819
836
  if (!elements) {
820
- elements = await (this.browser.$$(shadowElement));
837
+ elements = await this.browser.$$(shadowElement)
821
838
  } else if (Array.isArray(elements)) {
822
- elements = await elements[0].shadow$$(shadowElement);
839
+ elements = await elements[0].shadow$$(shadowElement)
823
840
  } else if (elements) {
824
- elements = await elements.shadow$$(shadowElement);
841
+ elements = await elements.shadow$$(shadowElement)
825
842
  }
826
843
 
827
844
  if (!elements || !elements[0]) {
828
- throw new Error(`Shadow Element '${shadowElement}' is not found. It is possible the element is incorrect or elements sequence is incorrect. Please verify the sequence '${shadowSequence.join('>')}' is correctly chained.`);
845
+ throw new Error(
846
+ `Shadow Element '${shadowElement}' is not found. It is possible the element is incorrect or elements sequence is incorrect. Please verify the sequence '${shadowSequence.join('>')}' is correctly chained.`,
847
+ )
829
848
  }
830
849
  }
831
850
 
832
- this.debugSection('Elements', `Found ${elements.length} '${SHADOW}' elements`);
851
+ this.debugSection('Elements', `Found ${elements.length} '${SHADOW}' elements`)
833
852
 
834
- return elements;
853
+ return elements
835
854
  }
836
855
 
837
856
  /**
@@ -840,8 +859,8 @@ class WebDriver extends Helper {
840
859
  * @param {object} locator
841
860
  */
842
861
  async _smartWait(locator) {
843
- this.debugSection(`SmartWait (${this.options.smartWait}ms)`, `Locating ${JSON.stringify(locator)} in ${this.options.smartWait}`);
844
- await this.defineTimeout({ implicit: this.options.smartWait });
862
+ this.debugSection(`SmartWait (${this.options.smartWait}ms)`, `Locating ${JSON.stringify(locator)} in ${this.options.smartWait}`)
863
+ await this.defineTimeout({ implicit: this.options.smartWait })
845
864
  }
846
865
 
847
866
  /**
@@ -856,53 +875,64 @@ class WebDriver extends Helper {
856
875
  * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
857
876
  */
858
877
  async _locate(locator, smartWait = false) {
859
- if (require('../store.js').debugMode) smartWait = false;
878
+ if (store.debugMode) smartWait = false
860
879
 
861
880
  // special locator type for Shadow DOM
862
881
  if (this._isShadowLocator(locator)) {
863
882
  if (!this.options.smartWait || !smartWait) {
864
- const els = await this._locateShadow(locator);
865
- return els;
883
+ const els = await this._locateShadow(locator)
884
+ return els
866
885
  }
867
886
 
868
- const els = await this._locateShadow(locator);
869
- return els;
887
+ const els = await this._locateShadow(locator)
888
+ return els
870
889
  }
871
890
 
872
891
  // special locator type for React
873
892
  if (locator.react) {
874
- const els = await this.browser.react$$(locator.react, locator.props || undefined, locator.state || undefined);
875
- this.debugSection('Elements', `Found ${els.length} react components`);
876
- return els;
893
+ const els = await this.browser.react$$(locator.react, locator.props || undefined, locator.state || undefined)
894
+ this.debugSection('Elements', `Found ${els.length} react components`)
895
+ return els
896
+ }
897
+
898
+ // special locator type for ARIA roles
899
+ if (locator.role) {
900
+ return this._locateByRole(locator)
901
+ }
902
+
903
+ // Handle role locators passed as Locator instances
904
+ const matchedLocator = new Locator(locator)
905
+ if (matchedLocator.isRole()) {
906
+ return this._locateByRole(matchedLocator.locator)
877
907
  }
878
908
 
879
909
  if (!this.options.smartWait || !smartWait) {
880
910
  if (this._isCustomLocator(locator)) {
881
- const locatorObj = new Locator(locator);
882
- return this.browser.custom$$(locatorObj.type, locatorObj.value);
911
+ const locatorObj = new Locator(locator)
912
+ return this.browser.custom$$(locatorObj.type, locatorObj.value)
883
913
  }
884
914
 
885
- const els = await this.$$(withStrictLocator(locator));
886
- return els;
915
+ const els = await this.$$(withStrictLocator(locator))
916
+ return els
887
917
  }
888
918
 
889
- await this._smartWait(locator);
919
+ await this._smartWait(locator)
890
920
 
891
921
  if (this._isCustomLocator(locator)) {
892
- const locatorObj = new Locator(locator);
893
- return this.browser.custom$$(locatorObj.type, locatorObj.value);
922
+ const locatorObj = new Locator(locator)
923
+ return this.browser.custom$$(locatorObj.type, locatorObj.value)
894
924
  }
895
925
 
896
- const els = await this.$$(withStrictLocator(locator));
897
- await this.defineTimeout({ implicit: 0 });
898
- return els;
926
+ const els = await this.$$(withStrictLocator(locator))
927
+ await this.defineTimeout({ implicit: 0 })
928
+ return els
899
929
  }
900
930
 
901
931
  _grabCustomLocator(locator) {
902
932
  if (typeof locator === 'string') {
903
- locator = new Locator(locator);
933
+ locator = new Locator(locator)
904
934
  }
905
- return locator.value ? locator.value : locator.custom;
935
+ return locator.value ? locator.value : locator.custom
906
936
  }
907
937
 
908
938
  /**
@@ -915,7 +945,7 @@ class WebDriver extends Helper {
915
945
  * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
916
946
  */
917
947
  async _locateCheckable(locator) {
918
- return findCheckable.call(this, locator, this.$$.bind(this)).then(res => res);
948
+ return findCheckable.call(this, locator, this.$$.bind(this)).then(res => res)
919
949
  }
920
950
 
921
951
  /**
@@ -929,8 +959,8 @@ class WebDriver extends Helper {
929
959
  * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
930
960
  */
931
961
  async _locateClickable(locator, context) {
932
- const locateFn = prepareLocateFn.call(this, context);
933
- return findClickable.call(this, locator, locateFn);
962
+ const locateFn = prepareLocateFn.call(this, context)
963
+ return findClickable.call(this, locator, locateFn)
934
964
  }
935
965
 
936
966
  /**
@@ -943,7 +973,35 @@ class WebDriver extends Helper {
943
973
  * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
944
974
  */
945
975
  async _locateFields(locator) {
946
- return findFields.call(this, locator).then(res => res);
976
+ return findFields.call(this, locator).then(res => res)
977
+ }
978
+
979
+ /**
980
+ * Locate elements by ARIA role using WebdriverIO accessibility selectors
981
+ *
982
+ * @param {object} locator - role locator object { role: string, text?: string, exact?: boolean }
983
+ */
984
+ async _locateByRole(locator) {
985
+ const role = locator.role
986
+
987
+ if (!locator.text) {
988
+ return this.browser.$$(`[role="${role}"]`)
989
+ }
990
+
991
+ const elements = await this.browser.$$(`[role="${role}"]`)
992
+ const filteredElements = []
993
+ const matchFn = locator.exact === true
994
+ ? t => t === locator.text
995
+ : t => t && t.includes(locator.text)
996
+
997
+ for (const element of elements) {
998
+ const texts = await getElementTextAttributes.call(this, element)
999
+ if (texts.some(matchFn)) {
1000
+ filteredElements.push(element)
1001
+ }
1002
+ }
1003
+
1004
+ return filteredElements
947
1005
  }
948
1006
 
949
1007
  /**
@@ -951,7 +1009,20 @@ class WebDriver extends Helper {
951
1009
  *
952
1010
  */
953
1011
  async grabWebElements(locator) {
954
- return this._locate(locator);
1012
+ const elements = await this._locate(locator)
1013
+ return elements.map(element => new WebElement(element, this))
1014
+ }
1015
+
1016
+ /**
1017
+ * {{> grabWebElement }}
1018
+ *
1019
+ */
1020
+ async grabWebElement(locator) {
1021
+ const elements = await this._locate(locator)
1022
+ if (elements.length === 0) {
1023
+ throw new ElementNotFound(locator, 'Element')
1024
+ }
1025
+ return new WebElement(elements[0], this)
955
1026
  }
956
1027
 
957
1028
  /**
@@ -967,11 +1038,11 @@ class WebDriver extends Helper {
967
1038
  * @param {*} timeouts WebDriver timeouts object.
968
1039
  */
969
1040
  defineTimeout(timeouts) {
970
- return this._defineBrowserTimeout(this.browser, timeouts);
1041
+ return this._defineBrowserTimeout(this.browser, timeouts)
971
1042
  }
972
1043
 
973
1044
  _defineBrowserTimeout(browser, timeouts) {
974
- return browser.setTimeout(timeouts);
1045
+ return browser.setTimeout(timeouts)
975
1046
  }
976
1047
 
977
1048
  /**
@@ -979,15 +1050,15 @@ class WebDriver extends Helper {
979
1050
  *
980
1051
  */
981
1052
  amOnPage(url) {
982
- let split_url;
1053
+ let split_url
983
1054
  if (this.options.basicAuth) {
984
1055
  if (url.startsWith('/')) {
985
- url = this.options.url + url;
1056
+ url = this.options.url + url
986
1057
  }
987
- split_url = url.split('//');
988
- url = `${split_url[0]}//${this.options.basicAuth.username}:${this.options.basicAuth.password}@${split_url[1]}`;
1058
+ split_url = url.split('//')
1059
+ url = `${split_url[0]}//${this.options.basicAuth.username}:${this.options.basicAuth.password}@${split_url[1]}`
989
1060
  }
990
- return this.browser.url(url);
1061
+ return this.browser.url(url)
991
1062
  }
992
1063
 
993
1064
  /**
@@ -996,18 +1067,18 @@ class WebDriver extends Helper {
996
1067
  * {{ react }}
997
1068
  */
998
1069
  async click(locator, context = null) {
999
- const clickMethod = this.browser.isMobile && !this.browser.isW3C ? 'touchClick' : 'elementClick';
1000
- const locateFn = prepareLocateFn.call(this, context);
1070
+ const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick'
1071
+ const locateFn = prepareLocateFn.call(this, context)
1001
1072
 
1002
- const res = await findClickable.call(this, locator, locateFn);
1073
+ const res = await findClickable.call(this, locator, locateFn)
1003
1074
  if (context) {
1004
- assertElementExists(res, locator, 'Clickable element', `was not found inside element ${new Locator(context)}`);
1075
+ assertElementExists(res, locator, 'Clickable element', `was not found inside element ${new Locator(context)}`)
1005
1076
  } else {
1006
- assertElementExists(res, locator, 'Clickable element');
1077
+ assertElementExists(res, locator, 'Clickable element')
1007
1078
  }
1008
- const elem = usingFirstElement(res);
1009
- highlightActiveElement.call(this, elem);
1010
- return this.browser[clickMethod](getElementId(elem));
1079
+ const elem = usingFirstElement(res)
1080
+ highlightActiveElement.call(this, elem)
1081
+ return this.browser[clickMethod](getElementId(elem))
1011
1082
  }
1012
1083
 
1013
1084
  /**
@@ -1016,25 +1087,25 @@ class WebDriver extends Helper {
1016
1087
  * {{ react }}
1017
1088
  */
1018
1089
  async forceClick(locator, context = null) {
1019
- const locateFn = prepareLocateFn.call(this, context);
1090
+ const locateFn = prepareLocateFn.call(this, context)
1020
1091
 
1021
- const res = await findClickable.call(this, locator, locateFn);
1092
+ const res = await findClickable.call(this, locator, locateFn)
1022
1093
  if (context) {
1023
- assertElementExists(res, locator, 'Clickable element', `was not found inside element ${new Locator(context)}`);
1094
+ assertElementExists(res, locator, 'Clickable element', `was not found inside element ${new Locator(context)}`)
1024
1095
  } else {
1025
- assertElementExists(res, locator, 'Clickable element');
1096
+ assertElementExists(res, locator, 'Clickable element')
1026
1097
  }
1027
- const elem = usingFirstElement(res);
1028
- highlightActiveElement.call(this, elem);
1098
+ const elem = usingFirstElement(res)
1099
+ highlightActiveElement.call(this, elem)
1029
1100
 
1030
- return this.executeScript((el) => {
1101
+ return this.executeScript(el => {
1031
1102
  if (document.activeElement instanceof HTMLElement) {
1032
- document.activeElement.blur();
1103
+ document.activeElement.blur()
1033
1104
  }
1034
- const event = document.createEvent('MouseEvent');
1035
- event.initEvent('click', true, true);
1036
- return el.dispatchEvent(event);
1037
- }, elem);
1105
+ const event = document.createEvent('MouseEvent')
1106
+ event.initEvent('click', true, true)
1107
+ return el.dispatchEvent(event)
1108
+ }, elem)
1038
1109
  }
1039
1110
 
1040
1111
  /**
@@ -1043,18 +1114,18 @@ class WebDriver extends Helper {
1043
1114
  * {{ react }}
1044
1115
  */
1045
1116
  async doubleClick(locator, context = null) {
1046
- const locateFn = prepareLocateFn.call(this, context);
1117
+ const locateFn = prepareLocateFn.call(this, context)
1047
1118
 
1048
- const res = await findClickable.call(this, locator, locateFn);
1119
+ const res = await findClickable.call(this, locator, locateFn)
1049
1120
  if (context) {
1050
- assertElementExists(res, locator, 'Clickable element', `was not found inside element ${new Locator(context)}`);
1121
+ assertElementExists(res, locator, 'Clickable element', `was not found inside element ${new Locator(context)}`)
1051
1122
  } else {
1052
- assertElementExists(res, locator, 'Clickable element');
1123
+ assertElementExists(res, locator, 'Clickable element')
1053
1124
  }
1054
1125
 
1055
- const elem = usingFirstElement(res);
1056
- highlightActiveElement.call(this, elem);
1057
- return elem.doubleClick();
1126
+ const elem = usingFirstElement(res)
1127
+ highlightActiveElement.call(this, elem)
1128
+ return elem.doubleClick()
1058
1129
  }
1059
1130
 
1060
1131
  /**
@@ -1063,24 +1134,93 @@ class WebDriver extends Helper {
1063
1134
  * {{ react }}
1064
1135
  */
1065
1136
  async rightClick(locator, context) {
1066
- const locateFn = prepareLocateFn.call(this, context);
1137
+ const locateFn = prepareLocateFn.call(this, context)
1067
1138
 
1068
- const res = await findClickable.call(this, locator, locateFn);
1139
+ const res = await findClickable.call(this, locator, locateFn)
1069
1140
  if (context) {
1070
- assertElementExists(res, locator, 'Clickable element', `was not found inside element ${new Locator(context)}`);
1141
+ assertElementExists(res, locator, 'Clickable element', `was not found inside element ${new Locator(context)}`)
1071
1142
  } else {
1072
- assertElementExists(res, locator, 'Clickable element');
1143
+ assertElementExists(res, locator, 'Clickable element')
1073
1144
  }
1074
1145
 
1075
- const el = usingFirstElement(res);
1146
+ const el = usingFirstElement(res)
1076
1147
 
1077
- await el.moveTo();
1148
+ await el.moveTo()
1078
1149
 
1079
1150
  if (this.browser.isW3C) {
1080
- return el.click({ button: 'right' });
1151
+ return el.click({ button: 'right' })
1081
1152
  }
1082
1153
  // JSON Wire version
1083
- await this.browser.buttonDown(2);
1154
+ await this.browser.buttonDown(2)
1155
+ }
1156
+
1157
+ /**
1158
+ * Performs click at specific coordinates.
1159
+ * If locator is provided, the coordinates are relative to the element's top-left corner.
1160
+ * If locator is not provided, the coordinates are relative to the body element.
1161
+ *
1162
+ * ```js
1163
+ * // Click at coordinates (100, 200) relative to body
1164
+ * I.clickXY(100, 200);
1165
+ *
1166
+ * // Click at coordinates (50, 30) relative to element's top-left corner
1167
+ * I.clickXY('#someElement', 50, 30);
1168
+ * ```
1169
+ *
1170
+ * @param {CodeceptJS.LocatorOrString|number} locator Element to click on or X coordinate if no element.
1171
+ * @param {number} [x] X coordinate relative to element's top-left, or Y coordinate if locator is a number.
1172
+ * @param {number} [y] Y coordinate relative to element's top-left.
1173
+ * @returns {Promise<void>}
1174
+ */
1175
+ async clickXY(locator, x, y) {
1176
+ // If locator is a number, treat it as X coordinate and use body as base
1177
+ if (typeof locator === 'number') {
1178
+ const globalX = locator
1179
+ const globalY = x
1180
+ locator = '//body'
1181
+ x = globalX
1182
+ y = globalY
1183
+ }
1184
+
1185
+ // Locate the base element
1186
+ const res = await this._locate(withStrictLocator(locator), true)
1187
+ assertElementExists(res, locator, 'Element to click')
1188
+ const el = usingFirstElement(res)
1189
+
1190
+ // Get element position and size to calculate top-left corner
1191
+ const location = await el.getLocation()
1192
+ const size = await el.getSize()
1193
+
1194
+ // WebDriver clicks at center by default, so we need to offset from center to top-left
1195
+ // then add our desired x, y coordinates
1196
+ const offsetX = -(size.width / 2) + x
1197
+ const offsetY = -(size.height / 2) + y
1198
+
1199
+ if (this.browser.isW3C) {
1200
+ // Use performActions for W3C WebDriver
1201
+ return this.browser.performActions([
1202
+ {
1203
+ type: 'pointer',
1204
+ id: 'pointer1',
1205
+ parameters: { pointerType: 'mouse' },
1206
+ actions: [
1207
+ {
1208
+ type: 'pointerMove',
1209
+ origin: el,
1210
+ duration: 0,
1211
+ x: Math.round(offsetX),
1212
+ y: Math.round(offsetY),
1213
+ },
1214
+ { type: 'pointerDown', button: 0 },
1215
+ { type: 'pointerUp', button: 0 },
1216
+ ],
1217
+ },
1218
+ ])
1219
+ }
1220
+
1221
+ // Fallback for non-W3C browsers
1222
+ await el.moveTo({ xOffset: Math.round(offsetX), yOffset: Math.round(offsetY) })
1223
+ return el.click()
1084
1224
  }
1085
1225
 
1086
1226
  /**
@@ -1089,24 +1229,24 @@ class WebDriver extends Helper {
1089
1229
  * {{ react }}
1090
1230
  */
1091
1231
  async forceRightClick(locator, context = null) {
1092
- const locateFn = prepareLocateFn.call(this, context);
1232
+ const locateFn = prepareLocateFn.call(this, context)
1093
1233
 
1094
- const res = await findClickable.call(this, locator, locateFn);
1234
+ const res = await findClickable.call(this, locator, locateFn)
1095
1235
  if (context) {
1096
- assertElementExists(res, locator, 'Clickable element', `was not found inside element ${new Locator(context)}`);
1236
+ assertElementExists(res, locator, 'Clickable element', `was not found inside element ${new Locator(context)}`)
1097
1237
  } else {
1098
- assertElementExists(res, locator, 'Clickable element');
1238
+ assertElementExists(res, locator, 'Clickable element')
1099
1239
  }
1100
- const elem = usingFirstElement(res);
1240
+ const elem = usingFirstElement(res)
1101
1241
 
1102
- return this.executeScript((el) => {
1242
+ return this.executeScript(el => {
1103
1243
  if (document.activeElement instanceof HTMLElement) {
1104
- document.activeElement.blur();
1244
+ document.activeElement.blur()
1105
1245
  }
1106
- const event = document.createEvent('MouseEvent');
1107
- event.initEvent('contextmenu', true, true);
1108
- return el.dispatchEvent(event);
1109
- }, elem);
1246
+ const event = document.createEvent('MouseEvent')
1247
+ event.initEvent('contextmenu', true, true)
1248
+ return el.dispatchEvent(event)
1249
+ }, elem)
1110
1250
  }
1111
1251
 
1112
1252
  /**
@@ -1116,12 +1256,22 @@ class WebDriver extends Helper {
1116
1256
  *
1117
1257
  */
1118
1258
  async fillField(field, value) {
1119
- const res = await findFields.call(this, field);
1120
- assertElementExists(res, field, 'Field');
1121
- const elem = usingFirstElement(res);
1122
- highlightActiveElement.call(this, elem);
1123
- await elem.clearValue();
1124
- await elem.setValue(value.toString());
1259
+ const res = await findFields.call(this, field)
1260
+ assertElementExists(res, field, 'Field')
1261
+ const elem = usingFirstElement(res)
1262
+ highlightActiveElement.call(this, elem)
1263
+ try {
1264
+ await elem.clearValue()
1265
+ } catch (err) {
1266
+ if (err.message && err.message.includes('invalid element state')) {
1267
+ await this.executeScript(el => {
1268
+ el.value = ''
1269
+ }, elem)
1270
+ } else {
1271
+ throw err
1272
+ }
1273
+ }
1274
+ await elem.setValue(value.toString())
1125
1275
  }
1126
1276
 
1127
1277
  /**
@@ -1129,15 +1279,11 @@ class WebDriver extends Helper {
1129
1279
  * {{ react }}
1130
1280
  */
1131
1281
  async appendField(field, value) {
1132
- const res = await findFields.call(this, field);
1133
- assertElementExists(res, field, 'Field');
1134
- const elem = usingFirstElement(res);
1135
- highlightActiveElement.call(this, elem);
1136
- if (this.options.automationProtocol) {
1137
- const curentValue = await elem.getValue();
1138
- return elem.setValue(curentValue + value.toString());
1139
- }
1140
- return elem.addValue(value.toString());
1282
+ const res = await findFields.call(this, field)
1283
+ assertElementExists(res, field, 'Field')
1284
+ const elem = usingFirstElement(res)
1285
+ highlightActiveElement.call(this, elem)
1286
+ return elem.addValue(value.toString())
1141
1287
  }
1142
1288
 
1143
1289
  /**
@@ -1145,47 +1291,44 @@ class WebDriver extends Helper {
1145
1291
  *
1146
1292
  */
1147
1293
  async clearField(field) {
1148
- const res = await findFields.call(this, field);
1149
- assertElementExists(res, field, 'Field');
1150
- const elem = usingFirstElement(res);
1151
- highlightActiveElement.call(this, elem);
1152
- if (this.options.automationProtocol) {
1153
- return elem.setValue('');
1154
- }
1155
- return elem.clearValue(getElementId(elem));
1294
+ const res = await findFields.call(this, field)
1295
+ assertElementExists(res, field, 'Field')
1296
+ const elem = usingFirstElement(res)
1297
+ highlightActiveElement.call(this, elem)
1298
+ return elem.clearValue(getElementId(elem))
1156
1299
  }
1157
1300
 
1158
1301
  /**
1159
1302
  * {{> selectOption }}
1160
1303
  */
1161
1304
  async selectOption(select, option) {
1162
- const res = await findFields.call(this, select);
1163
- assertElementExists(res, select, 'Selectable field');
1164
- const elem = usingFirstElement(res);
1165
- highlightActiveElement.call(this, elem);
1305
+ const res = await findFields.call(this, select)
1306
+ assertElementExists(res, select, 'Selectable field')
1307
+ const elem = usingFirstElement(res)
1308
+ highlightActiveElement.call(this, elem)
1166
1309
 
1167
1310
  if (!Array.isArray(option)) {
1168
- option = [option];
1311
+ option = [option]
1169
1312
  }
1170
1313
 
1171
1314
  // select options by visible text
1172
- let els = await forEachAsync(option, async opt => this.browser.findElementsFromElement(getElementId(elem), 'xpath', Locator.select.byVisibleText(xpathLocator.literal(opt))));
1315
+ let els = await forEachAsync(option, async opt => this.browser.findElementsFromElement(getElementId(elem), 'xpath', Locator.select.byVisibleText(xpathLocator.literal(opt))))
1173
1316
 
1174
- const clickOptionFn = async (el) => {
1175
- if (el[0]) el = el[0];
1176
- const elementId = getElementId(el);
1177
- if (elementId) return this.browser.elementClick(elementId);
1178
- };
1317
+ const clickOptionFn = async el => {
1318
+ if (el[0]) el = el[0]
1319
+ const elementId = getElementId(el)
1320
+ if (elementId) return this.browser.elementClick(elementId)
1321
+ }
1179
1322
 
1180
1323
  if (Array.isArray(els) && els.length) {
1181
- return forEachAsync(els, clickOptionFn);
1324
+ return forEachAsync(els, clickOptionFn)
1182
1325
  }
1183
1326
  // select options by value
1184
- els = await forEachAsync(option, async opt => this.browser.findElementsFromElement(getElementId(elem), 'xpath', Locator.select.byValue(xpathLocator.literal(opt))));
1327
+ els = await forEachAsync(option, async opt => this.browser.findElementsFromElement(getElementId(elem), 'xpath', Locator.select.byValue(xpathLocator.literal(opt))))
1185
1328
  if (els.length === 0) {
1186
- throw new ElementNotFound(select, `Option "${option}" in`, 'was not found neither by a visible text nor by a value');
1329
+ throw new ElementNotFound(select, `Option "${option}" in`, 'was not found neither by a visible text nor by a value')
1187
1330
  }
1188
- return forEachAsync(els, clickOptionFn);
1331
+ return forEachAsync(els, clickOptionFn)
1189
1332
  }
1190
1333
 
1191
1334
  /**
@@ -1194,27 +1337,27 @@ class WebDriver extends Helper {
1194
1337
  * {{> attachFile }}
1195
1338
  */
1196
1339
  async attachFile(locator, pathToFile) {
1197
- let file = path.join(global.codecept_dir, pathToFile);
1340
+ let file = path.join(global.codecept_dir, pathToFile)
1198
1341
  if (!fileExists(file)) {
1199
- throw new Error(`File at ${file} can not be found on local system`);
1342
+ throw new Error(`File at ${file} can not be found on local system`)
1200
1343
  }
1201
1344
 
1202
- const res = await findFields.call(this, locator);
1203
- this.debug(`Uploading ${file}`);
1204
- assertElementExists(res, locator, 'File field');
1205
- const el = usingFirstElement(res);
1345
+ const res = await findFields.call(this, locator)
1346
+ this.debug(`Uploading ${file}`)
1347
+ assertElementExists(res, locator, 'File field')
1348
+ const el = usingFirstElement(res)
1206
1349
 
1207
1350
  // Remote Upload (when running Selenium Server)
1208
- if (this.options.remoteFileUpload && !this.options.automationProtocol) {
1351
+ if (this.options.remoteFileUpload) {
1209
1352
  try {
1210
- this.debugSection('File', 'Uploading file to remote server');
1211
- file = await this.browser.uploadFile(file);
1353
+ this.debugSection('File', 'Uploading file to remote server')
1354
+ file = await this.browser.uploadFile(file)
1212
1355
  } catch (err) {
1213
- throw new Error(`File can't be transferred to remote server. Set \`remoteFileUpload: false\` in config to upload file locally.\n${err.message}`);
1356
+ throw new Error(`File can't be transferred to remote server. Set \`remoteFileUpload: false\` in config to upload file locally.\n${err.message}`)
1214
1357
  }
1215
1358
  }
1216
1359
 
1217
- return el.addValue(file);
1360
+ return el.addValue(file)
1218
1361
  }
1219
1362
 
1220
1363
  /**
@@ -1222,19 +1365,20 @@ class WebDriver extends Helper {
1222
1365
  * {{> checkOption }}
1223
1366
  */
1224
1367
  async checkOption(field, context = null) {
1225
- const clickMethod = this.browser.isMobile && !this.browser.isW3C ? 'touchClick' : 'elementClick';
1226
- const locateFn = prepareLocateFn.call(this, context);
1368
+ const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick'
1369
+ const locateFn = prepareLocateFn.call(this, context)
1370
+
1371
+ const res = await findCheckable.call(this, field, locateFn)
1227
1372
 
1228
- const res = await findCheckable.call(this, field, locateFn);
1373
+ assertElementExists(res, field, 'Checkable')
1374
+ const elem = usingFirstElement(res)
1375
+ const elementId = getElementId(elem)
1376
+ highlightActiveElement.call(this, elem)
1229
1377
 
1230
- assertElementExists(res, field, 'Checkable');
1231
- const elem = usingFirstElement(res);
1232
- const elementId = getElementId(elem);
1233
- highlightActiveElement.call(this, elem);
1378
+ const isSelected = await isElementChecked(this.browser, elementId)
1234
1379
 
1235
- const isSelected = await this.browser.isElementSelected(elementId);
1236
- if (isSelected) return Promise.resolve(true);
1237
- return this.browser[clickMethod](elementId);
1380
+ if (isSelected) return Promise.resolve(true)
1381
+ return this.browser[clickMethod](elementId)
1238
1382
  }
1239
1383
 
1240
1384
  /**
@@ -1242,19 +1386,20 @@ class WebDriver extends Helper {
1242
1386
  * {{> uncheckOption }}
1243
1387
  */
1244
1388
  async uncheckOption(field, context = null) {
1245
- const clickMethod = this.browser.isMobile && !this.browser.isW3C ? 'touchClick' : 'elementClick';
1246
- const locateFn = prepareLocateFn.call(this, context);
1389
+ const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick'
1390
+ const locateFn = prepareLocateFn.call(this, context)
1247
1391
 
1248
- const res = await findCheckable.call(this, field, locateFn);
1392
+ const res = await findCheckable.call(this, field, locateFn)
1249
1393
 
1250
- assertElementExists(res, field, 'Checkable');
1251
- const elem = usingFirstElement(res);
1252
- const elementId = getElementId(elem);
1253
- highlightActiveElement.call(this, elem);
1394
+ assertElementExists(res, field, 'Checkable')
1395
+ const elem = usingFirstElement(res)
1396
+ const elementId = getElementId(elem)
1397
+ highlightActiveElement.call(this, elem)
1254
1398
 
1255
- const isSelected = await this.browser.isElementSelected(elementId);
1256
- if (!isSelected) return Promise.resolve(true);
1257
- return this.browser[clickMethod](elementId);
1399
+ const isSelected = await isElementChecked(this.browser, elementId)
1400
+
1401
+ if (!isSelected) return Promise.resolve(true)
1402
+ return this.browser[clickMethod](elementId)
1258
1403
  }
1259
1404
 
1260
1405
  /**
@@ -1262,10 +1407,14 @@ class WebDriver extends Helper {
1262
1407
  *
1263
1408
  */
1264
1409
  async grabTextFromAll(locator) {
1265
- const res = await this._locate(locator, true);
1266
- const val = await forEachAsync(res, el => this.browser.getElementText(getElementId(el)));
1267
- this.debugSection('GrabText', String(val));
1268
- return val;
1410
+ const res = await this._locate(locator, true)
1411
+ let val = []
1412
+ await forEachAsync(res, async el => {
1413
+ const text = await this.browser.getElementText(getElementId(el))
1414
+ val.push(text)
1415
+ })
1416
+ this.debugSection('GrabText', String(val))
1417
+ return val
1269
1418
  }
1270
1419
 
1271
1420
  /**
@@ -1273,13 +1422,13 @@ class WebDriver extends Helper {
1273
1422
  *
1274
1423
  */
1275
1424
  async grabTextFrom(locator) {
1276
- const texts = await this.grabTextFromAll(locator);
1277
- assertElementExists(texts, locator);
1425
+ const texts = await this.grabTextFromAll(locator)
1426
+ assertElementExists(texts, locator)
1278
1427
  if (texts.length > 1) {
1279
- this.debugSection('GrabText', `Using first element out of ${texts.length}`);
1428
+ this.debugSection('GrabText', `Using first element out of ${texts.length}`)
1280
1429
  }
1281
1430
 
1282
- return texts[0];
1431
+ return texts[0]
1283
1432
  }
1284
1433
 
1285
1434
  /**
@@ -1287,10 +1436,10 @@ class WebDriver extends Helper {
1287
1436
  *
1288
1437
  */
1289
1438
  async grabHTMLFromAll(locator) {
1290
- const elems = await this._locate(locator, true);
1291
- const html = await forEachAsync(elems, elem => elem.getHTML(false));
1292
- this.debugSection('GrabHTML', String(html));
1293
- return html;
1439
+ const elems = await this._locate(locator, true)
1440
+ const html = await forEachAsync(elems, elem => elem.getHTML(false))
1441
+ this.debugSection('GrabHTML', String(html))
1442
+ return html
1294
1443
  }
1295
1444
 
1296
1445
  /**
@@ -1298,13 +1447,13 @@ class WebDriver extends Helper {
1298
1447
  *
1299
1448
  */
1300
1449
  async grabHTMLFrom(locator) {
1301
- const html = await this.grabHTMLFromAll(locator);
1302
- assertElementExists(html, locator);
1450
+ const html = await this.grabHTMLFromAll(locator)
1451
+ assertElementExists(html, locator)
1303
1452
  if (html.length > 1) {
1304
- this.debugSection('GrabHTML', `Using first element out of ${html.length}`);
1453
+ this.debugSection('GrabHTML', `Using first element out of ${html.length}`)
1305
1454
  }
1306
1455
 
1307
- return html[0];
1456
+ return html[0]
1308
1457
  }
1309
1458
 
1310
1459
  /**
@@ -1312,11 +1461,11 @@ class WebDriver extends Helper {
1312
1461
  *
1313
1462
  */
1314
1463
  async grabValueFromAll(locator) {
1315
- const res = await this._locate(locator, true);
1316
- const val = await forEachAsync(res, el => el.getValue());
1317
- this.debugSection('GrabValue', String(val));
1464
+ const res = await this._locate(locator, true)
1465
+ const val = await forEachAsync(res, el => el.getValue())
1466
+ this.debugSection('GrabValue', String(val))
1318
1467
 
1319
- return val;
1468
+ return val
1320
1469
  }
1321
1470
 
1322
1471
  /**
@@ -1324,92 +1473,92 @@ class WebDriver extends Helper {
1324
1473
  *
1325
1474
  */
1326
1475
  async grabValueFrom(locator) {
1327
- const values = await this.grabValueFromAll(locator);
1328
- assertElementExists(values, locator);
1476
+ const values = await this.grabValueFromAll(locator)
1477
+ assertElementExists(values, locator)
1329
1478
  if (values.length > 1) {
1330
- this.debugSection('GrabValue', `Using first element out of ${values.length}`);
1479
+ this.debugSection('GrabValue', `Using first element out of ${values.length}`)
1331
1480
  }
1332
1481
 
1333
- return values[0];
1482
+ return values[0]
1334
1483
  }
1335
1484
 
1336
1485
  /**
1337
1486
  * {{> grabCssPropertyFromAll }}
1338
1487
  */
1339
1488
  async grabCssPropertyFromAll(locator, cssProperty) {
1340
- const res = await this._locate(locator, true);
1341
- const val = await forEachAsync(res, async el => this.browser.getElementCSSValue(getElementId(el), cssProperty));
1342
- this.debugSection('Grab', String(val));
1343
- return val;
1489
+ const res = await this._locate(locator, true)
1490
+ const val = await forEachAsync(res, async el => this.browser.getElementCSSValue(getElementId(el), cssProperty))
1491
+ this.debugSection('Grab', String(val))
1492
+ return val
1344
1493
  }
1345
1494
 
1346
1495
  /**
1347
1496
  * {{> grabCssPropertyFrom }}
1348
1497
  */
1349
1498
  async grabCssPropertyFrom(locator, cssProperty) {
1350
- const cssValues = await this.grabCssPropertyFromAll(locator, cssProperty);
1351
- assertElementExists(cssValues, locator);
1499
+ const cssValues = await this.grabCssPropertyFromAll(locator, cssProperty)
1500
+ assertElementExists(cssValues, locator)
1352
1501
 
1353
1502
  if (cssValues.length > 1) {
1354
- this.debugSection('GrabCSS', `Using first element out of ${cssValues.length}`);
1503
+ this.debugSection('GrabCSS', `Using first element out of ${cssValues.length}`)
1355
1504
  }
1356
1505
 
1357
- return cssValues[0];
1506
+ return cssValues[0]
1358
1507
  }
1359
1508
 
1360
1509
  /**
1361
1510
  * {{> grabAttributeFromAll }}
1362
1511
  */
1363
1512
  async grabAttributeFromAll(locator, attr) {
1364
- const res = await this._locate(locator, true);
1365
- const val = await forEachAsync(res, async el => el.getAttribute(attr));
1366
- this.debugSection('GrabAttribute', String(val));
1367
- return val;
1513
+ const res = await this._locate(locator, true)
1514
+ const val = await forEachAsync(res, async el => el.getAttribute(attr))
1515
+ this.debugSection('GrabAttribute', String(val))
1516
+ return val
1368
1517
  }
1369
1518
 
1370
1519
  /**
1371
1520
  * {{> grabAttributeFrom }}
1372
1521
  */
1373
1522
  async grabAttributeFrom(locator, attr) {
1374
- const attrs = await this.grabAttributeFromAll(locator, attr);
1375
- assertElementExists(attrs, locator);
1523
+ const attrs = await this.grabAttributeFromAll(locator, attr)
1524
+ assertElementExists(attrs, locator)
1376
1525
  if (attrs.length > 1) {
1377
- this.debugSection('GrabAttribute', `Using first element out of ${attrs.length}`);
1526
+ this.debugSection('GrabAttribute', `Using first element out of ${attrs.length}`)
1378
1527
  }
1379
- return attrs[0];
1528
+ return attrs[0]
1380
1529
  }
1381
1530
 
1382
1531
  /**
1383
1532
  * {{> seeInTitle }}
1384
1533
  */
1385
1534
  async seeInTitle(text) {
1386
- const title = await this.browser.getTitle();
1387
- return stringIncludes('web page title').assert(text, title);
1535
+ const title = await this.browser.getTitle()
1536
+ return stringIncludes('web page title').assert(text, title)
1388
1537
  }
1389
1538
 
1390
1539
  /**
1391
1540
  * {{> seeTitleEquals }}
1392
1541
  */
1393
1542
  async seeTitleEquals(text) {
1394
- const title = await this.browser.getTitle();
1395
- return assert.equal(title, text, `expected web page title to be ${text}, but found ${title}`);
1543
+ const title = await this.browser.getTitle()
1544
+ return assert.equal(title, text, `expected web page title to be ${text}, but found ${title}`)
1396
1545
  }
1397
1546
 
1398
1547
  /**
1399
1548
  * {{> dontSeeInTitle }}
1400
1549
  */
1401
1550
  async dontSeeInTitle(text) {
1402
- const title = await this.browser.getTitle();
1403
- return stringIncludes('web page title').negate(text, title);
1551
+ const title = await this.browser.getTitle()
1552
+ return stringIncludes('web page title').negate(text, title)
1404
1553
  }
1405
1554
 
1406
1555
  /**
1407
1556
  * {{> grabTitle }}
1408
1557
  */
1409
1558
  async grabTitle() {
1410
- const title = await this.browser.getTitle();
1411
- this.debugSection('Title', title);
1412
- return title;
1559
+ const title = await this.browser.getTitle()
1560
+ this.debugSection('Title', title)
1561
+ return title
1413
1562
  }
1414
1563
 
1415
1564
  /**
@@ -1418,14 +1567,14 @@ class WebDriver extends Helper {
1418
1567
  * {{ react }}
1419
1568
  */
1420
1569
  async see(text, context = null) {
1421
- return proceedSee.call(this, 'assert', text, context);
1570
+ return proceedSee.call(this, 'assert', text, context)
1422
1571
  }
1423
1572
 
1424
1573
  /**
1425
1574
  * {{> seeTextEquals }}
1426
1575
  */
1427
1576
  async seeTextEquals(text, context = null) {
1428
- return proceedSee.call(this, 'assert', text, context, true);
1577
+ return proceedSee.call(this, 'assert', text, context, true)
1429
1578
  }
1430
1579
 
1431
1580
  /**
@@ -1434,7 +1583,7 @@ class WebDriver extends Helper {
1434
1583
  * {{ react }}
1435
1584
  */
1436
1585
  async dontSee(text, context = null) {
1437
- return proceedSee.call(this, 'negate', text, context);
1586
+ return proceedSee.call(this, 'negate', text, context)
1438
1587
  }
1439
1588
 
1440
1589
  /**
@@ -1442,8 +1591,8 @@ class WebDriver extends Helper {
1442
1591
  *
1443
1592
  */
1444
1593
  async seeInField(field, value) {
1445
- const _value = (typeof value === 'boolean') ? value : value.toString();
1446
- return proceedSeeField.call(this, 'assert', field, _value);
1594
+ const _value = typeof value === 'boolean' ? value : value.toString()
1595
+ return proceedSeeField.call(this, 'assert', field, _value)
1447
1596
  }
1448
1597
 
1449
1598
  /**
@@ -1451,8 +1600,8 @@ class WebDriver extends Helper {
1451
1600
  *
1452
1601
  */
1453
1602
  async dontSeeInField(field, value) {
1454
- const _value = (typeof value === 'boolean') ? value : value.toString();
1455
- return proceedSeeField.call(this, 'negate', field, _value);
1603
+ const _value = typeof value === 'boolean' ? value : value.toString()
1604
+ return proceedSeeField.call(this, 'negate', field, _value)
1456
1605
  }
1457
1606
 
1458
1607
  /**
@@ -1460,7 +1609,7 @@ class WebDriver extends Helper {
1460
1609
  * {{> seeCheckboxIsChecked }}
1461
1610
  */
1462
1611
  async seeCheckboxIsChecked(field) {
1463
- return proceedSeeCheckbox.call(this, 'assert', field);
1612
+ return proceedSeeCheckbox.call(this, 'assert', field)
1464
1613
  }
1465
1614
 
1466
1615
  /**
@@ -1468,7 +1617,7 @@ class WebDriver extends Helper {
1468
1617
  * {{> dontSeeCheckboxIsChecked }}
1469
1618
  */
1470
1619
  async dontSeeCheckboxIsChecked(field) {
1471
- return proceedSeeCheckbox.call(this, 'negate', field);
1620
+ return proceedSeeCheckbox.call(this, 'negate', field)
1472
1621
  }
1473
1622
 
1474
1623
  /**
@@ -1477,13 +1626,13 @@ class WebDriver extends Helper {
1477
1626
  *
1478
1627
  */
1479
1628
  async seeElement(locator) {
1480
- const res = await this._locate(locator, true);
1481
- assertElementExists(res, locator);
1482
- const selected = await forEachAsync(res, async el => el.isDisplayed());
1629
+ const res = await this._locate(locator, true)
1630
+ assertElementExists(res, locator)
1631
+ const selected = await forEachAsync(res, async el => el.isDisplayed())
1483
1632
  try {
1484
- return truth(`elements of ${(new Locator(locator))}`, 'to be seen').assert(selected);
1633
+ return truth(`elements of ${new Locator(locator)}`, 'to be seen').assert(selected)
1485
1634
  } catch (e) {
1486
- dontSeeElementError(locator);
1635
+ dontSeeElementError(locator)
1487
1636
  }
1488
1637
  }
1489
1638
 
@@ -1492,15 +1641,15 @@ class WebDriver extends Helper {
1492
1641
  * {{ react }}
1493
1642
  */
1494
1643
  async dontSeeElement(locator) {
1495
- const res = await this._locate(locator, false);
1644
+ const res = await this._locate(locator, false)
1496
1645
  if (!res || res.length === 0) {
1497
- return truth(`elements of ${(new Locator(locator))}`, 'to be seen').negate(false);
1646
+ return truth(`elements of ${new Locator(locator)}`, 'to be seen').negate(false)
1498
1647
  }
1499
- const selected = await forEachAsync(res, async el => el.isDisplayed());
1648
+ const selected = await forEachAsync(res, async el => el.isDisplayed())
1500
1649
  try {
1501
- return truth(`elements of ${(new Locator(locator))}`, 'to be seen').negate(selected);
1650
+ return truth(`elements of ${new Locator(locator)}`, 'to be seen').negate(selected)
1502
1651
  } catch (e) {
1503
- seeElementError(locator);
1652
+ seeElementError(locator)
1504
1653
  }
1505
1654
  }
1506
1655
 
@@ -1509,11 +1658,11 @@ class WebDriver extends Helper {
1509
1658
  *
1510
1659
  */
1511
1660
  async seeElementInDOM(locator) {
1512
- const res = await this._res(locator);
1661
+ const res = await this._res(locator)
1513
1662
  try {
1514
- return empty('elements').negate(res);
1663
+ return empty('elements').negate(res)
1515
1664
  } catch (e) {
1516
- dontSeeElementInDOMError(locator);
1665
+ dontSeeElementInDOMError(locator)
1517
1666
  }
1518
1667
  }
1519
1668
 
@@ -1522,11 +1671,11 @@ class WebDriver extends Helper {
1522
1671
  *
1523
1672
  */
1524
1673
  async dontSeeElementInDOM(locator) {
1525
- const res = await this._res(locator);
1674
+ const res = await this._res(locator)
1526
1675
  try {
1527
- return empty('elements').assert(res);
1676
+ return empty('elements').assert(res)
1528
1677
  } catch (e) {
1529
- seeElementInDOMError(locator);
1678
+ seeElementInDOMError(locator)
1530
1679
  }
1531
1680
  }
1532
1681
 
@@ -1535,8 +1684,8 @@ class WebDriver extends Helper {
1535
1684
  *
1536
1685
  */
1537
1686
  async seeInSource(text) {
1538
- const source = await this.browser.getPageSource();
1539
- return stringIncludes('HTML source of a page').assert(text, source);
1687
+ const source = await this.browser.getPageSource()
1688
+ return stringIncludes('HTML source of a page').assert(text, source)
1540
1689
  }
1541
1690
 
1542
1691
  /**
@@ -1544,35 +1693,31 @@ class WebDriver extends Helper {
1544
1693
  *
1545
1694
  */
1546
1695
  async grabSource() {
1547
- return this.browser.getPageSource();
1696
+ return this.browser.getPageSource()
1548
1697
  }
1549
1698
 
1550
1699
  /**
1551
1700
  * {{> grabBrowserLogs }}
1552
1701
  */
1553
1702
  async grabBrowserLogs() {
1554
- if (this.browser.isW3C) {
1555
- this.debug('Logs not available in W3C specification');
1556
- return;
1557
- }
1558
- return this.browser.getLogs('browser');
1703
+ return browserLogs
1559
1704
  }
1560
1705
 
1561
1706
  /**
1562
1707
  * {{> grabCurrentUrl }}
1563
1708
  */
1564
1709
  async grabCurrentUrl() {
1565
- const res = await this.browser.getUrl();
1566
- this.debugSection('Url', res);
1567
- return res;
1710
+ const res = await this.browser.getUrl()
1711
+ this.debugSection('Url', res)
1712
+ return res
1568
1713
  }
1569
1714
 
1570
1715
  /**
1571
1716
  * {{> dontSeeInSource }}
1572
1717
  */
1573
1718
  async dontSeeInSource(text) {
1574
- const source = await this.browser.getPageSource();
1575
- return stringIncludes('HTML source of a page').negate(text, source);
1719
+ const source = await this.browser.getPageSource()
1720
+ return stringIncludes('HTML source of a page').negate(text, source)
1576
1721
  }
1577
1722
 
1578
1723
  /**
@@ -1580,8 +1725,8 @@ class WebDriver extends Helper {
1580
1725
  * {{ react }}
1581
1726
  */
1582
1727
  async seeNumberOfElements(locator, num) {
1583
- const res = await this._locate(locator);
1584
- return assert.equal(res.length, num, `expected number of elements (${(new Locator(locator))}) is ${num}, but found ${res.length}`);
1728
+ const res = await this._locate(locator)
1729
+ return assert.equal(res.length, num, `expected number of elements (${new Locator(locator)}) is ${num}, but found ${res.length}`)
1585
1730
  }
1586
1731
 
1587
1732
  /**
@@ -1589,86 +1734,82 @@ class WebDriver extends Helper {
1589
1734
  * {{ react }}
1590
1735
  */
1591
1736
  async seeNumberOfVisibleElements(locator, num) {
1592
- const res = await this.grabNumberOfVisibleElements(locator);
1593
- return assert.equal(res, num, `expected number of visible elements (${(new Locator(locator))}) is ${num}, but found ${res}`);
1737
+ const res = await this.grabNumberOfVisibleElements(locator)
1738
+ return assert.equal(res, num, `expected number of visible elements (${new Locator(locator)}) is ${num}, but found ${res}`)
1594
1739
  }
1595
1740
 
1596
1741
  /**
1597
1742
  * {{> seeCssPropertiesOnElements }}
1598
1743
  */
1599
1744
  async seeCssPropertiesOnElements(locator, cssProperties) {
1600
- const res = await this._locate(locator);
1601
- assertElementExists(res, locator);
1745
+ const res = await this._locate(locator)
1746
+ assertElementExists(res, locator)
1602
1747
 
1603
- const cssPropertiesCamelCase = convertCssPropertiesToCamelCase(cssProperties);
1604
- const elemAmount = res.length;
1605
- let props = [];
1748
+ const cssPropertiesCamelCase = convertCssPropertiesToCamelCase(cssProperties)
1749
+ const elemAmount = res.length
1750
+ let props = []
1606
1751
 
1607
1752
  for (const element of res) {
1608
1753
  for (const prop of Object.keys(cssProperties)) {
1609
- const cssProp = await this.grabCssPropertyFrom(locator, prop);
1754
+ const cssProp = await this.grabCssPropertyFrom(locator, prop)
1610
1755
  if (isColorProperty(prop)) {
1611
- props.push(convertColorToRGBA(cssProp));
1756
+ props.push(convertColorToRGBA(cssProp))
1612
1757
  } else {
1613
- props.push(cssProp);
1758
+ props.push(cssProp)
1614
1759
  }
1615
1760
  }
1616
1761
  }
1617
1762
 
1618
- const values = Object.keys(cssPropertiesCamelCase).map(key => cssPropertiesCamelCase[key]);
1619
- if (!Array.isArray(props)) props = [props];
1620
- let chunked = chunkArray(props, values.length);
1621
- chunked = chunked.filter((val) => {
1763
+ const values = Object.keys(cssPropertiesCamelCase).map(key => cssPropertiesCamelCase[key])
1764
+ if (!Array.isArray(props)) props = [props]
1765
+ let chunked = chunkArray(props, values.length)
1766
+ chunked = chunked.filter(val => {
1622
1767
  for (let i = 0; i < val.length; ++i) {
1623
- // eslint-disable-next-line eqeqeq
1624
- if (val[i] != values[i]) return false;
1768
+ if (val[i] != values[i]) return false
1625
1769
  }
1626
- return true;
1627
- });
1628
- return equals(`all elements (${(new Locator(locator))}) to have CSS property ${JSON.stringify(cssProperties)}`).assert(chunked.length, elemAmount);
1770
+ return true
1771
+ })
1772
+ return equals(`all elements (${new Locator(locator)}) to have CSS property ${JSON.stringify(cssProperties)}`).assert(chunked.length, elemAmount)
1629
1773
  }
1630
1774
 
1631
1775
  /**
1632
1776
  * {{> seeAttributesOnElements }}
1633
1777
  */
1634
1778
  async seeAttributesOnElements(locator, attributes) {
1635
- const res = await this._locate(locator);
1636
- assertElementExists(res, locator);
1637
- const elemAmount = res.length;
1638
-
1639
- let attrs = await forEachAsync(res, async (el) => {
1640
- return forEachAsync(Object.keys(attributes), async attr => el.getAttribute(attr));
1641
- });
1642
-
1643
- const values = Object.keys(attributes).map(key => attributes[key]);
1644
- if (!Array.isArray(attrs)) attrs = [attrs];
1645
- let chunked = chunkArray(attrs, values.length);
1646
- chunked = chunked.filter((val) => {
1779
+ const res = await this._locate(locator)
1780
+ assertElementExists(res, locator)
1781
+ const elemAmount = res.length
1782
+
1783
+ let attrs = await forEachAsync(res, async el => {
1784
+ return forEachAsync(Object.keys(attributes), async attr => el.getAttribute(attr))
1785
+ })
1786
+
1787
+ const values = Object.keys(attributes).map(key => attributes[key])
1788
+ if (!Array.isArray(attrs)) attrs = [attrs]
1789
+ let chunked = chunkArray(attrs, values.length)
1790
+ chunked = chunked.filter(val => {
1647
1791
  for (let i = 0; i < val.length; ++i) {
1648
- const _actual = Number.isNaN(val[i]) || (typeof values[i]) === 'string' ? val[i] : Number.parseInt(val[i], 10);
1649
- const _expected = Number.isNaN(values[i]) || (typeof values[i]) === 'string' ? values[i] : Number.parseInt(values[i], 10);
1792
+ const _actual = Number.isNaN(val[i]) || typeof values[i] === 'string' ? val[i] : Number.parseInt(val[i], 10)
1793
+ const _expected = Number.isNaN(values[i]) || typeof values[i] === 'string' ? values[i] : Number.parseInt(values[i], 10)
1650
1794
  // the attribute could be a boolean
1651
- if (typeof _actual === 'boolean') return _actual === _expected;
1652
- if (_actual !== _expected) return false;
1795
+ if (typeof _actual === 'boolean') return _actual === _expected
1796
+ if (_actual !== _expected) return false
1653
1797
  }
1654
- return true;
1655
- });
1656
- return assert.ok(
1657
- chunked.length === elemAmount,
1658
- `expected all elements (${(new Locator(locator))}) to have attributes ${JSON.stringify(attributes)}`,
1659
- );
1798
+ return true
1799
+ })
1800
+ return assert.ok(chunked.length === elemAmount, `expected all elements (${new Locator(locator)}) to have attributes ${JSON.stringify(attributes)}`)
1660
1801
  }
1661
1802
 
1662
1803
  /**
1663
1804
  * {{> grabNumberOfVisibleElements }}
1664
1805
  */
1665
1806
  async grabNumberOfVisibleElements(locator) {
1666
- const res = await this._locate(locator);
1807
+ const res = await this._locate(locator)
1667
1808
 
1668
- let selected = await forEachAsync(res, async el => el.isDisplayed());
1669
- if (!Array.isArray(selected)) selected = [selected];
1670
- selected = selected.filter(val => val === true);
1671
- return selected.length;
1809
+ let selected = await forEachAsync(res, async el => el.isDisplayed())
1810
+ if (!Array.isArray(selected)) selected = [selected]
1811
+ selected = selected.filter(val => val === true)
1812
+ return selected.length
1672
1813
  }
1673
1814
 
1674
1815
  /**
@@ -1676,8 +1817,8 @@ class WebDriver extends Helper {
1676
1817
  *
1677
1818
  */
1678
1819
  async seeInCurrentUrl(url) {
1679
- const res = await this.browser.getUrl();
1680
- return stringIncludes('url').assert(url, decodeUrl(res));
1820
+ const res = await this.browser.getUrl()
1821
+ return stringIncludes('url').assert(url, decodeUrl(res))
1681
1822
  }
1682
1823
 
1683
1824
  /**
@@ -1685,8 +1826,8 @@ class WebDriver extends Helper {
1685
1826
  *
1686
1827
  */
1687
1828
  async dontSeeInCurrentUrl(url) {
1688
- const res = await this.browser.getUrl();
1689
- return stringIncludes('url').negate(url, decodeUrl(res));
1829
+ const res = await this.browser.getUrl()
1830
+ return stringIncludes('url').negate(url, decodeUrl(res))
1690
1831
  }
1691
1832
 
1692
1833
  /**
@@ -1694,8 +1835,8 @@ class WebDriver extends Helper {
1694
1835
  *
1695
1836
  */
1696
1837
  async seeCurrentUrlEquals(url) {
1697
- const res = await this.browser.getUrl();
1698
- return urlEquals(this.options.url).assert(url, decodeUrl(res));
1838
+ const res = await this.browser.getUrl()
1839
+ return urlEquals(this.options.url).assert(url, decodeUrl(res))
1699
1840
  }
1700
1841
 
1701
1842
  /**
@@ -1703,8 +1844,8 @@ class WebDriver extends Helper {
1703
1844
  *
1704
1845
  */
1705
1846
  async dontSeeCurrentUrlEquals(url) {
1706
- const res = await this.browser.getUrl();
1707
- return urlEquals(this.options.url).negate(url, decodeUrl(res));
1847
+ const res = await this.browser.getUrl()
1848
+ return urlEquals(this.options.url).negate(url, decodeUrl(res))
1708
1849
  }
1709
1850
 
1710
1851
  /**
@@ -1713,7 +1854,7 @@ class WebDriver extends Helper {
1713
1854
  * {{> executeScript }}
1714
1855
  */
1715
1856
  executeScript(...args) {
1716
- return this.browser.execute.apply(this.browser, args);
1857
+ return this.browser.execute.apply(this.browser, args)
1717
1858
  }
1718
1859
 
1719
1860
  /**
@@ -1721,7 +1862,7 @@ class WebDriver extends Helper {
1721
1862
  *
1722
1863
  */
1723
1864
  executeAsyncScript(...args) {
1724
- return this.browser.executeAsync.apply(this.browser, args);
1865
+ return this.browser.executeAsync.apply(this.browser, args)
1725
1866
  }
1726
1867
 
1727
1868
  /**
@@ -1729,10 +1870,10 @@ class WebDriver extends Helper {
1729
1870
  *
1730
1871
  */
1731
1872
  async scrollIntoView(locator, scrollIntoViewOptions) {
1732
- const res = await this._locate(withStrictLocator(locator), true);
1733
- assertElementExists(res, locator);
1734
- const elem = usingFirstElement(res);
1735
- return elem.scrollIntoView(scrollIntoViewOptions);
1873
+ const res = await this._locate(withStrictLocator(locator), true)
1874
+ assertElementExists(res, locator)
1875
+ const elem = usingFirstElement(res)
1876
+ return elem.scrollIntoView(scrollIntoViewOptions)
1736
1877
  }
1737
1878
 
1738
1879
  /**
@@ -1741,42 +1882,51 @@ class WebDriver extends Helper {
1741
1882
  */
1742
1883
  async scrollTo(locator, offsetX = 0, offsetY = 0) {
1743
1884
  if (typeof locator === 'number' && typeof offsetX === 'number') {
1744
- offsetY = offsetX;
1745
- offsetX = locator;
1746
- locator = null;
1885
+ offsetY = offsetX
1886
+ offsetX = locator
1887
+ locator = null
1747
1888
  }
1748
1889
 
1749
1890
  if (locator) {
1750
- const res = await this._locate(withStrictLocator(locator), true);
1751
- assertElementExists(res, locator);
1752
- const elem = usingFirstElement(res);
1753
- const elementId = getElementId(elem);
1754
- if (this.browser.isMobile && !this.browser.isW3C) return this.browser.touchScroll(offsetX, offsetY, elementId);
1755
- const location = await elem.getLocation();
1756
- assertElementExists(location, locator, 'Failed to receive', 'location');
1757
- /* eslint-disable prefer-arrow-callback */
1758
- return this.browser.execute(function (x, y) { return window.scrollTo(x, y); }, location.x + offsetX, location.y + offsetY);
1759
- /* eslint-enable */
1760
- }
1761
-
1762
- if (this.browser.isMobile && !this.browser.isW3C) return this.browser.touchScroll(locator, offsetX, offsetY);
1763
-
1764
- /* eslint-disable prefer-arrow-callback, comma-dangle */
1765
- return this.browser.execute(function (x, y) { return window.scrollTo(x, y); }, offsetX, offsetY);
1766
- /* eslint-enable */
1891
+ const res = await this._locate(withStrictLocator(locator), true)
1892
+ assertElementExists(res, locator)
1893
+ const elem = usingFirstElement(res)
1894
+ const elementId = getElementId(elem)
1895
+ if (this.browser.isMobile && !this.browser.isW3C) return this.browser.touchScroll(offsetX, offsetY, elementId)
1896
+ const location = await elem.getLocation()
1897
+ assertElementExists(location, locator, 'Failed to receive', 'location')
1898
+
1899
+ return this.browser.execute(
1900
+ function (x, y) {
1901
+ return window.scrollTo(x, y)
1902
+ },
1903
+ location.x + offsetX,
1904
+ location.y + offsetY,
1905
+ )
1906
+ }
1907
+
1908
+ if (this.browser.isMobile && !this.browser.isW3C) return this.browser.touchScroll(locator, offsetX, offsetY)
1909
+
1910
+ return this.browser.execute(
1911
+ function (x, y) {
1912
+ return window.scrollTo(x, y)
1913
+ },
1914
+ offsetX,
1915
+ offsetY,
1916
+ )
1767
1917
  }
1768
1918
 
1769
1919
  /**
1770
1920
  * {{> moveCursorTo }}
1771
1921
  */
1772
1922
  async moveCursorTo(locator, xOffset, yOffset) {
1773
- const res = await this._locate(withStrictLocator(locator), true);
1774
- assertElementExists(res, locator);
1775
- const elem = usingFirstElement(res);
1923
+ const res = await this._locate(withStrictLocator(locator), true)
1924
+ assertElementExists(res, locator)
1925
+ const elem = usingFirstElement(res)
1776
1926
  try {
1777
- await elem.moveTo({ xOffset, yOffset });
1927
+ await elem.moveTo({ xOffset, yOffset })
1778
1928
  } catch (e) {
1779
- debug(e.message);
1929
+ output.debug(e.message)
1780
1930
  }
1781
1931
  }
1782
1932
 
@@ -1785,54 +1935,68 @@ class WebDriver extends Helper {
1785
1935
  *
1786
1936
  */
1787
1937
  async saveElementScreenshot(locator, fileName) {
1788
- const outputFile = screenshotOutputFolder(fileName);
1938
+ const outputFile = screenshotOutputFolder(fileName)
1789
1939
 
1790
- const res = await this._locate(withStrictLocator(locator), true);
1791
- assertElementExists(res, locator);
1792
- const elem = usingFirstElement(res);
1940
+ const res = await this._locate(withStrictLocator(locator), true)
1941
+ assertElementExists(res, locator)
1942
+ const elem = usingFirstElement(res)
1793
1943
 
1794
- this.debug(`Screenshot of ${(new Locator(locator))} element has been saved to ${outputFile}`);
1795
- return elem.saveScreenshot(outputFile);
1944
+ this.debug(`Screenshot of ${new Locator(locator)} element has been saved to ${outputFile}`)
1945
+ return elem.saveScreenshot(outputFile)
1796
1946
  }
1797
1947
 
1798
1948
  /**
1799
1949
  * {{> saveScreenshot }}
1800
1950
  */
1801
1951
  async saveScreenshot(fileName, fullPage = false) {
1802
- const outputFile = screenshotOutputFolder(fileName);
1952
+ let outputFile = screenshotOutputFolder(fileName)
1803
1953
 
1804
1954
  if (this.activeSessionName) {
1805
- const browser = this.sessionWindows[this.activeSessionName];
1955
+ const browser = this.sessionWindows[this.activeSessionName]
1956
+
1957
+ for (const sessionName in this.sessionWindows) {
1958
+ const activeSessionPage = this.sessionWindows[sessionName]
1959
+ outputFile = screenshotOutputFolder(`${sessionName}_${fileName}`)
1960
+
1961
+ this.debug(`${sessionName} - Screenshot is saving to ${outputFile}`)
1806
1962
 
1807
- if (browser) {
1808
- this.debug(`Screenshot of ${this.activeSessionName} session has been saved to ${outputFile}`);
1809
- return browser.saveScreenshot(outputFile);
1963
+ if (browser) {
1964
+ this.debug(`Screenshot of ${sessionName} session has been saved to ${outputFile}`)
1965
+ await browser.saveScreenshot(outputFile)
1966
+ }
1810
1967
  }
1811
1968
  }
1812
1969
 
1813
1970
  if (!fullPage) {
1814
- this.debug(`Screenshot has been saved to ${outputFile}`);
1815
- return this.browser.saveScreenshot(outputFile);
1971
+ this.debug(`Screenshot has been saved to ${outputFile}`)
1972
+ await this.browser.saveScreenshot(outputFile)
1816
1973
  }
1817
1974
 
1818
- /* eslint-disable prefer-arrow-callback, comma-dangle, prefer-const */
1819
- const originalWindowSize = await this.browser.getWindowSize();
1975
+ const originalWindowSize = await this.browser.getWindowSize()
1820
1976
 
1821
- let { width, height } = await this.browser.execute(function () {
1822
- return {
1823
- height: document.body.scrollHeight,
1824
- width: document.body.scrollWidth
1825
- };
1826
- }).then(res => res);
1977
+ // this case running on device, so we could not set the windowSize
1978
+ if (this.browser.isMobile) {
1979
+ this.debug(`Screenshot has been saved to ${outputFile}, size: ${originalWindowSize.width}x${originalWindowSize.height}`)
1980
+ const buffer = await this.browser.saveScreenshot(outputFile)
1981
+ return buffer
1982
+ }
1983
+
1984
+ let { width, height } = await this.browser
1985
+ .execute(function () {
1986
+ return {
1987
+ height: document.body.scrollHeight,
1988
+ width: document.body.scrollWidth,
1989
+ }
1990
+ })
1991
+ .then(res => res)
1827
1992
 
1828
- if (height < 100) height = 500; // errors for very small height
1829
- /* eslint-enable */
1993
+ if (height < 100) height = 500 // errors for very small height
1830
1994
 
1831
- await this.browser.setWindowSize(width, height);
1832
- this.debug(`Screenshot has been saved to ${outputFile}, size: ${width}x${height}`);
1833
- const buffer = await this.browser.saveScreenshot(outputFile);
1834
- await this.browser.setWindowSize(originalWindowSize.width, originalWindowSize.height);
1835
- return buffer;
1995
+ await this.browser.setWindowSize(width, height)
1996
+ this.debug(`Screenshot has been saved to ${outputFile}, size: ${width}x${height}`)
1997
+ const buffer = await this.browser.saveScreenshot(outputFile)
1998
+ await this.browser.setWindowSize(originalWindowSize.width, originalWindowSize.height)
1999
+ return buffer
1836
2000
  }
1837
2001
 
1838
2002
  /**
@@ -1840,40 +2004,40 @@ class WebDriver extends Helper {
1840
2004
  * {{> setCookie }}
1841
2005
  */
1842
2006
  async setCookie(cookie) {
1843
- return this.browser.setCookies(cookie);
2007
+ return this.browser.setCookies(cookie)
1844
2008
  }
1845
2009
 
1846
2010
  /**
1847
2011
  * {{> clearCookie }}
1848
2012
  */
1849
2013
  async clearCookie(cookie) {
1850
- return this.browser.deleteCookies(cookie);
2014
+ return this.browser.deleteCookies(cookie)
1851
2015
  }
1852
2016
 
1853
2017
  /**
1854
2018
  * {{> seeCookie }}
1855
2019
  */
1856
2020
  async seeCookie(name) {
1857
- const cookie = await this.browser.getCookies([name]);
1858
- return truth(`cookie ${name}`, 'to be set').assert(cookie);
2021
+ const cookie = await this.browser.getCookies([name])
2022
+ return truth(`cookie ${name}`, 'to be set').assert(cookie)
1859
2023
  }
1860
2024
 
1861
2025
  /**
1862
2026
  * {{> dontSeeCookie }}
1863
2027
  */
1864
2028
  async dontSeeCookie(name) {
1865
- const cookie = await this.browser.getCookies([name]);
1866
- return truth(`cookie ${name}`, 'to be set').negate(cookie);
2029
+ const cookie = await this.browser.getCookies([name])
2030
+ return truth(`cookie ${name}`, 'to be set').negate(cookie)
1867
2031
  }
1868
2032
 
1869
2033
  /**
1870
2034
  * {{> grabCookie }}
1871
2035
  */
1872
2036
  async grabCookie(name) {
1873
- if (!name) return this.browser.getCookies();
1874
- const cookie = await this.browser.getCookies([name]);
1875
- this.debugSection('Cookie', JSON.stringify(cookie));
1876
- return cookie[0];
2037
+ if (!name) return this.browser.getCookies()
2038
+ const cookie = await this.browser.getCookies([name])
2039
+ this.debugSection('Cookie', JSON.stringify(cookie))
2040
+ return cookie[0]
1877
2041
  }
1878
2042
 
1879
2043
  /**
@@ -1881,30 +2045,33 @@ class WebDriver extends Helper {
1881
2045
  */
1882
2046
  async waitForCookie(name, sec) {
1883
2047
  // by default, we will retry 3 times
1884
- let retries = 3;
1885
- const waitTimeout = sec || this.options.waitForTimeoutInSeconds;
2048
+ let retries = 3
2049
+ const waitTimeout = sec || this.options.waitForTimeoutInSeconds
1886
2050
 
1887
2051
  if (sec) {
1888
- retries = sec;
2052
+ retries = sec
1889
2053
  } else {
1890
- retries = waitTimeout - 1;
2054
+ retries = waitTimeout - 1
1891
2055
  }
1892
2056
 
1893
- return promiseRetry(async (retry, number) => {
1894
- const _grabCookie = async (name) => {
1895
- const cookie = await this.browser.getCookies([name]);
1896
- if (cookie.length === 0) throw Error(`Cookie ${name} is not found after ${retries}s`);
1897
- };
2057
+ return promiseRetry(
2058
+ async (retry, number) => {
2059
+ const _grabCookie = async name => {
2060
+ const cookie = await this.browser.getCookies([name])
2061
+ if (cookie.length === 0) throw Error(`Cookie ${name} is not found after ${retries}s`)
2062
+ }
1898
2063
 
1899
- this.debugSection('Wait for cookie: ', name);
1900
- if (number > 1) this.debugSection('Retrying... Attempt #', number);
2064
+ this.debugSection('Wait for cookie: ', name)
2065
+ if (number > 1) this.debugSection('Retrying... Attempt #', number)
1901
2066
 
1902
- try {
1903
- await _grabCookie(name);
1904
- } catch (e) {
1905
- retry(e);
1906
- }
1907
- }, { retries, maxTimeout: 1000 });
2067
+ try {
2068
+ await _grabCookie(name)
2069
+ } catch (e) {
2070
+ retry(e)
2071
+ }
2072
+ },
2073
+ { retries, maxTimeout: 1000 },
2074
+ )
1908
2075
  }
1909
2076
 
1910
2077
  /**
@@ -1913,11 +2080,10 @@ class WebDriver extends Helper {
1913
2080
  * libraries](http://jster.net/category/windows-modals-popups).
1914
2081
  */
1915
2082
  async acceptPopup() {
1916
- return this.browser.getAlertText().then((res) => {
1917
- if (res !== null) {
1918
- return this.browser.acceptAlert();
1919
- }
1920
- });
2083
+ const text = await this.browser.getAlertText()
2084
+ if (text) {
2085
+ return await this.browser.acceptAlert()
2086
+ }
1921
2087
  }
1922
2088
 
1923
2089
  /**
@@ -1925,11 +2091,10 @@ class WebDriver extends Helper {
1925
2091
  *
1926
2092
  */
1927
2093
  async cancelPopup() {
1928
- return this.browser.getAlertText().then((res) => {
1929
- if (res !== null) {
1930
- return this.browser.dismissAlert();
1931
- }
1932
- });
2094
+ const text = await this.browser.getAlertText()
2095
+ if (text) {
2096
+ return await this.browser.dismissAlert()
2097
+ }
1933
2098
  }
1934
2099
 
1935
2100
  /**
@@ -1939,12 +2104,12 @@ class WebDriver extends Helper {
1939
2104
  * @param {string} text value to check.
1940
2105
  */
1941
2106
  async seeInPopup(text) {
1942
- return this.browser.getAlertText().then((res) => {
2107
+ return await this.browser.getAlertText().then(res => {
1943
2108
  if (res === null) {
1944
- throw new Error('Popup is not opened');
2109
+ throw new Error('Popup is not opened')
1945
2110
  }
1946
- stringIncludes('text in popup').assert(text, res);
1947
- });
2111
+ stringIncludes('text in popup').assert(text, res)
2112
+ })
1948
2113
  }
1949
2114
 
1950
2115
  /**
@@ -1952,9 +2117,9 @@ class WebDriver extends Helper {
1952
2117
  */
1953
2118
  async grabPopupText() {
1954
2119
  try {
1955
- return await this.browser.getAlertText();
2120
+ return await this.browser.getAlertText()
1956
2121
  } catch (err) {
1957
- this.debugSection('Popup', 'Error getting text from popup');
2122
+ this.debugSection('Popup', 'Error getting text from popup')
1958
2123
  }
1959
2124
  }
1960
2125
 
@@ -1962,36 +2127,44 @@ class WebDriver extends Helper {
1962
2127
  * {{> pressKeyDown }}
1963
2128
  */
1964
2129
  async pressKeyDown(key) {
1965
- key = getNormalizedKey.call(this, key);
2130
+ key = getNormalizedKey.call(this, key)
1966
2131
  if (!this.browser.isW3C) {
1967
- return this.browser.sendKeys([key]);
2132
+ return this.browser.sendKeys([key])
1968
2133
  }
1969
- return this.browser.performActions([{
1970
- type: 'key',
1971
- id: 'keyboard',
1972
- actions: [{
1973
- type: 'keyDown',
1974
- value: key,
1975
- }],
1976
- }]);
2134
+ return this.browser.performActions([
2135
+ {
2136
+ type: 'key',
2137
+ id: 'keyboard',
2138
+ actions: [
2139
+ {
2140
+ type: 'keyDown',
2141
+ value: key,
2142
+ },
2143
+ ],
2144
+ },
2145
+ ])
1977
2146
  }
1978
2147
 
1979
2148
  /**
1980
2149
  * {{> pressKeyUp }}
1981
2150
  */
1982
2151
  async pressKeyUp(key) {
1983
- key = getNormalizedKey.call(this, key);
2152
+ key = getNormalizedKey.call(this, key)
1984
2153
  if (!this.browser.isW3C) {
1985
- return this.browser.sendKeys([key]);
2154
+ return this.browser.sendKeys([key])
1986
2155
  }
1987
- return this.browser.performActions([{
1988
- type: 'key',
1989
- id: 'keyboard',
1990
- actions: [{
1991
- type: 'keyUp',
1992
- value: key,
1993
- }],
1994
- }]);
2156
+ return this.browser.performActions([
2157
+ {
2158
+ type: 'key',
2159
+ id: 'keyboard',
2160
+ actions: [
2161
+ {
2162
+ type: 'keyUp',
2163
+ value: key,
2164
+ },
2165
+ ],
2166
+ },
2167
+ ])
1995
2168
  }
1996
2169
 
1997
2170
  /**
@@ -2000,40 +2173,45 @@ class WebDriver extends Helper {
2000
2173
  * {{> pressKeyWithKeyNormalization }}
2001
2174
  */
2002
2175
  async pressKey(key) {
2003
- const modifiers = [];
2176
+ const modifiers = []
2004
2177
  if (Array.isArray(key)) {
2005
2178
  for (let k of key) {
2006
- k = getNormalizedKey.call(this, k);
2179
+ k = getNormalizedKey.call(this, k)
2007
2180
  if (isModifierKey(k)) {
2008
- modifiers.push(k);
2181
+ modifiers.push(k)
2009
2182
  } else {
2010
- key = k;
2011
- break;
2183
+ key = k
2184
+ break
2012
2185
  }
2013
2186
  }
2014
2187
  } else {
2015
- key = getNormalizedKey.call(this, key);
2188
+ key = getNormalizedKey.call(this, key)
2016
2189
  }
2017
2190
  for (const modifier of modifiers) {
2018
- await this.pressKeyDown(modifier);
2191
+ await this.pressKeyDown(modifier)
2019
2192
  }
2020
2193
  if (!this.browser.isW3C) {
2021
- await this.browser.sendKeys([key]);
2194
+ await this.browser.sendKeys([key])
2022
2195
  } else {
2023
- await this.browser.performActions([{
2024
- type: 'key',
2025
- id: 'keyboard',
2026
- actions: [{
2027
- type: 'keyDown',
2028
- value: key,
2029
- }, {
2030
- type: 'keyUp',
2031
- value: key,
2032
- }],
2033
- }]);
2196
+ await this.browser.performActions([
2197
+ {
2198
+ type: 'key',
2199
+ id: 'keyboard',
2200
+ actions: [
2201
+ {
2202
+ type: 'keyDown',
2203
+ value: key,
2204
+ },
2205
+ {
2206
+ type: 'keyUp',
2207
+ value: key,
2208
+ },
2209
+ ],
2210
+ },
2211
+ ])
2034
2212
  }
2035
2213
  for (const modifier of modifiers) {
2036
- await this.pressKeyUp(modifier);
2214
+ await this.pressKeyUp(modifier)
2037
2215
  }
2038
2216
  }
2039
2217
 
@@ -2042,17 +2220,17 @@ class WebDriver extends Helper {
2042
2220
  */
2043
2221
  async type(keys, delay = null) {
2044
2222
  if (!Array.isArray(keys)) {
2045
- keys = keys.toString();
2046
- keys = keys.split('');
2223
+ keys = keys.toString()
2224
+ keys = keys.split('')
2047
2225
  }
2048
2226
  if (delay) {
2049
2227
  for (const key of keys) {
2050
- await this.browser.keys(key);
2051
- await this.wait(delay / 1000);
2228
+ await this.browser.keys(key)
2229
+ await this.wait(delay / 1000)
2052
2230
  }
2053
- return;
2231
+ return
2054
2232
  }
2055
- await this.browser.keys(keys);
2233
+ await this.browser.keys(keys)
2056
2234
  }
2057
2235
 
2058
2236
  /**
@@ -2061,27 +2239,27 @@ class WebDriver extends Helper {
2061
2239
  * {{> resizeWindow }}
2062
2240
  */
2063
2241
  async resizeWindow(width, height) {
2064
- return this.browser.setWindowSize(width, height);
2242
+ return this.browser.setWindowSize(width, height)
2065
2243
  }
2066
2244
 
2067
2245
  async _resizeBrowserWindow(browser, width, height) {
2068
2246
  if (width === 'maximize') {
2069
- const size = await browser.maximizeWindow();
2070
- this.debugSection('Window Size', size);
2071
- return;
2247
+ const size = await browser.maximizeWindow()
2248
+ this.debugSection('Window Size', size)
2249
+ return
2072
2250
  }
2073
2251
  if (browser.isW3C) {
2074
- return browser.setWindowRect(null, null, parseInt(width, 10), parseInt(height, 10));
2252
+ return browser.setWindowRect(null, null, parseInt(width, 10), parseInt(height, 10))
2075
2253
  }
2076
- return browser.setWindowSize(parseInt(width, 10), parseInt(height, 10));
2254
+ return browser.setWindowSize(parseInt(width, 10), parseInt(height, 10))
2077
2255
  }
2078
2256
 
2079
2257
  async _resizeWindowIfNeeded(browser, windowSize) {
2080
2258
  if (this.isWeb && windowSize === 'maximize') {
2081
- await this._resizeBrowserWindow(browser, 'maximize');
2259
+ await this._resizeBrowserWindow(browser, 'maximize')
2082
2260
  } else if (this.isWeb && windowSize && windowSize.indexOf('x') > 0) {
2083
- const dimensions = windowSize.split('x');
2084
- await this._resizeBrowserWindow(browser, dimensions[0], dimensions[1]);
2261
+ const dimensions = windowSize.split('x')
2262
+ await this._resizeBrowserWindow(browser, dimensions[0], dimensions[1])
2085
2263
  }
2086
2264
  }
2087
2265
 
@@ -2090,11 +2268,11 @@ class WebDriver extends Helper {
2090
2268
  *
2091
2269
  */
2092
2270
  async focus(locator) {
2093
- const els = await this._locate(locator);
2094
- assertElementExists(els, locator, 'Element to focus');
2095
- const el = usingFirstElement(els);
2271
+ const els = await this._locate(locator)
2272
+ assertElementExists(els, locator, 'Element to focus')
2273
+ const el = usingFirstElement(els)
2096
2274
 
2097
- await focusElement(el, this.browser);
2275
+ await focusElement(el, this.browser)
2098
2276
  }
2099
2277
 
2100
2278
  /**
@@ -2102,11 +2280,11 @@ class WebDriver extends Helper {
2102
2280
  *
2103
2281
  */
2104
2282
  async blur(locator) {
2105
- const els = await this._locate(locator);
2106
- assertElementExists(els, locator, 'Element to blur');
2107
- const el = usingFirstElement(els);
2283
+ const els = await this._locate(locator)
2284
+ assertElementExists(els, locator, 'Element to blur')
2285
+ const el = usingFirstElement(els)
2108
2286
 
2109
- await blurElement(el, this.browser);
2287
+ await blurElement(el, this.browser)
2110
2288
  }
2111
2289
 
2112
2290
  /**
@@ -2114,64 +2292,73 @@ class WebDriver extends Helper {
2114
2292
  * {{> dragAndDrop }}
2115
2293
  */
2116
2294
  async dragAndDrop(srcElement, destElement) {
2117
- let sourceEl = await this._locate(srcElement);
2118
- assertElementExists(sourceEl, srcElement);
2119
- sourceEl = usingFirstElement(sourceEl);
2295
+ let sourceEl = await this._locate(srcElement)
2296
+ assertElementExists(sourceEl, srcElement)
2297
+ sourceEl = usingFirstElement(sourceEl)
2120
2298
 
2121
- let destEl = await this._locate(destElement);
2122
- assertElementExists(destEl, destElement);
2123
- destEl = usingFirstElement(destEl);
2299
+ let destEl = await this._locate(destElement)
2300
+ assertElementExists(destEl, destElement)
2301
+ destEl = usingFirstElement(destEl)
2124
2302
 
2125
- return sourceEl.dragAndDrop(destEl);
2303
+ return sourceEl.dragAndDrop(destEl)
2126
2304
  }
2127
2305
 
2128
2306
  /**
2129
2307
  * {{> dragSlider }}
2130
2308
  */
2131
2309
  async dragSlider(locator, offsetX = 0) {
2132
- const browser = this.browser;
2133
- await this.moveCursorTo(locator);
2310
+ const browser = this.browser
2311
+ await this.moveCursorTo(locator)
2134
2312
 
2135
2313
  // for chrome
2136
2314
  if (browser.isW3C) {
2137
- const xOffset = await this.grabElementBoundingRect(locator, 'x');
2138
- const yOffset = await this.grabElementBoundingRect(locator, 'y');
2139
-
2140
- return browser.performActions([{
2141
- type: 'pointer',
2142
- id: 'pointer1',
2143
- parameters: { pointerType: 'mouse' },
2144
- actions: [
2145
- {
2146
- type: 'pointerMove', origin: 'pointer', duration: 1000, x: xOffset, y: yOffset,
2147
- },
2148
- { type: 'pointerDown', button: 0 },
2149
- {
2150
- type: 'pointerMove', origin: 'pointer', duration: 1000, x: offsetX, y: 0,
2151
- },
2152
- { type: 'pointerUp', button: 0 },
2153
- ],
2154
- },
2155
- ]);
2156
- }
2157
-
2158
- await browser.buttonDown(0);
2159
- await browser.moveToElement(null, offsetX, 0);
2160
- await browser.buttonUp(0);
2315
+ const xOffset = await this.grabElementBoundingRect(locator, 'x')
2316
+ const yOffset = await this.grabElementBoundingRect(locator, 'y')
2317
+
2318
+ return browser.performActions([
2319
+ {
2320
+ type: 'pointer',
2321
+ id: 'pointer1',
2322
+ parameters: { pointerType: 'mouse' },
2323
+ actions: [
2324
+ {
2325
+ type: 'pointerMove',
2326
+ origin: 'pointer',
2327
+ duration: 1000,
2328
+ x: xOffset,
2329
+ y: yOffset,
2330
+ },
2331
+ { type: 'pointerDown', button: 0 },
2332
+ {
2333
+ type: 'pointerMove',
2334
+ origin: 'pointer',
2335
+ duration: 1000,
2336
+ x: offsetX,
2337
+ y: 0,
2338
+ },
2339
+ { type: 'pointerUp', button: 0 },
2340
+ ],
2341
+ },
2342
+ ])
2343
+ }
2344
+
2345
+ await browser.buttonDown(0)
2346
+ await browser.moveToElement(null, offsetX, 0)
2347
+ await browser.buttonUp(0)
2161
2348
  }
2162
2349
 
2163
2350
  /**
2164
2351
  * {{> grabAllWindowHandles }}
2165
2352
  */
2166
2353
  async grabAllWindowHandles() {
2167
- return this.browser.getWindowHandles();
2354
+ return this.browser.getWindowHandles()
2168
2355
  }
2169
2356
 
2170
2357
  /**
2171
2358
  * {{> grabCurrentWindowHandle }}
2172
2359
  */
2173
2360
  async grabCurrentWindowHandle() {
2174
- return this.browser.getWindowHandle();
2361
+ return this.browser.getWindowHandle()
2175
2362
  }
2176
2363
 
2177
2364
  /**
@@ -2189,22 +2376,22 @@ class WebDriver extends Helper {
2189
2376
  * @param {string} window name of window handle.
2190
2377
  */
2191
2378
  async switchToWindow(window) {
2192
- await this.browser.switchToWindow(window);
2379
+ await this.browser.switchToWindow(window)
2193
2380
  }
2194
2381
 
2195
2382
  /**
2196
2383
  * {{> closeOtherTabs }}
2197
2384
  */
2198
2385
  async closeOtherTabs() {
2199
- const handles = await this.browser.getWindowHandles();
2200
- const currentHandle = await this.browser.getWindowHandle();
2201
- const otherHandles = handles.filter(handle => handle !== currentHandle);
2386
+ const handles = await this.browser.getWindowHandles()
2387
+ const currentHandle = await this.browser.getWindowHandle()
2388
+ const otherHandles = handles.filter(handle => handle !== currentHandle)
2202
2389
 
2203
- await forEachAsync(otherHandles, async (handle) => {
2204
- await this.browser.switchToWindow(handle);
2205
- await this.browser.closeWindow();
2206
- });
2207
- await this.browser.switchToWindow(currentHandle);
2390
+ await forEachAsync(otherHandles, async handle => {
2391
+ await this.browser.switchToWindow(handle)
2392
+ await this.browser.closeWindow()
2393
+ })
2394
+ await this.browser.switchToWindow(currentHandle)
2208
2395
  }
2209
2396
 
2210
2397
  /**
@@ -2212,102 +2399,123 @@ class WebDriver extends Helper {
2212
2399
  */
2213
2400
  async wait(sec) {
2214
2401
  return new Promise(resolve => {
2215
- setTimeout(resolve, sec * 1000);
2216
- });
2402
+ setTimeout(resolve, sec * 1000)
2403
+ })
2217
2404
  }
2218
2405
 
2219
2406
  /**
2220
2407
  * {{> waitForEnabled }}
2221
2408
  */
2222
2409
  async waitForEnabled(locator, sec = null) {
2223
- const aSec = sec || this.options.waitForTimeoutInSeconds;
2410
+ const aSec = sec || this.options.waitForTimeoutInSeconds
2224
2411
 
2225
- return this.browser.waitUntil(async () => {
2226
- const res = await this._res(locator);
2227
- if (!res || res.length === 0) {
2228
- return false;
2229
- }
2230
- const selected = await forEachAsync(res, async el => this.browser.isElementEnabled(getElementId(el)));
2231
- if (Array.isArray(selected)) {
2232
- return selected.filter(val => val === true).length > 0;
2233
- }
2234
- return selected;
2235
- }, {
2236
- timeout: aSec * 1000,
2237
- timeoutMsg: `element (${new Locator(locator)}) still not enabled after ${aSec} sec`,
2238
- });
2412
+ return this.browser.waitUntil(
2413
+ async () => {
2414
+ const res = await this._res(locator)
2415
+ if (!res || res.length === 0) {
2416
+ return false
2417
+ }
2418
+ const selected = await forEachAsync(res, async el => this.browser.isElementEnabled(getElementId(el)))
2419
+ if (Array.isArray(selected)) {
2420
+ return selected.filter(val => val === true).length > 0
2421
+ }
2422
+ return selected
2423
+ },
2424
+ {
2425
+ timeout: aSec * 1000,
2426
+ timeoutMsg: `element (${new Locator(locator)}) still not enabled after ${aSec} sec`,
2427
+ },
2428
+ )
2239
2429
  }
2240
2430
 
2241
2431
  /**
2242
2432
  * {{> waitForElement }}
2243
2433
  */
2244
2434
  async waitForElement(locator, sec = null) {
2245
- const aSec = sec || this.options.waitForTimeoutInSeconds;
2435
+ const aSec = sec || this.options.waitForTimeoutInSeconds
2246
2436
 
2247
- return this.browser.waitUntil(async () => {
2248
- const res = await this._res(locator);
2249
- return res && res.length;
2250
- }, { timeout: aSec * 1000, timeoutMsg: `element (${(new Locator(locator))}) still not present on page after ${aSec} sec` });
2437
+ return this.browser.waitUntil(
2438
+ async () => {
2439
+ const res = await this._res(locator)
2440
+ return res && res.length
2441
+ },
2442
+ {
2443
+ timeout: aSec * 1000,
2444
+ timeoutMsg: `element (${new Locator(locator)}) still not present on page after ${aSec} sec`,
2445
+ },
2446
+ )
2251
2447
  }
2252
2448
 
2253
2449
  /**
2254
2450
  * {{> waitForClickable }}
2255
2451
  */
2256
2452
  async waitForClickable(locator, waitTimeout) {
2257
- waitTimeout = waitTimeout || this.options.waitForTimeoutInSeconds;
2258
- let res = await this._locate(locator);
2259
- res = usingFirstElement(res);
2260
- assertElementExists(res, locator);
2453
+ waitTimeout = waitTimeout || this.options.waitForTimeoutInSeconds
2454
+ let res = await this._locate(locator)
2455
+ res = usingFirstElement(res)
2456
+ assertElementExists(res, locator)
2261
2457
 
2262
- return res.waitForClickable({
2263
- timeout: waitTimeout * 1000,
2264
- timeoutMsg: `element ${res.selector} still not clickable after ${waitTimeout} sec`,
2265
- });
2458
+ return res
2459
+ .waitForClickable({
2460
+ timeout: waitTimeout * 1000,
2461
+ timeoutMsg: `element ${res.selector} still not clickable after ${waitTimeout} sec`,
2462
+ })
2463
+ .catch(e => {
2464
+ throw wrapError(e)
2465
+ })
2266
2466
  }
2267
2467
 
2268
2468
  /**
2269
2469
  * {{> waitInUrl }}
2270
2470
  */
2271
2471
  async waitInUrl(urlPart, sec = null) {
2272
- const client = this.browser;
2273
- const aSec = sec || this.options.waitForTimeoutInSeconds;
2274
- let currUrl = '';
2472
+ const client = this.browser
2473
+ const aSec = sec || this.options.waitForTimeoutInSeconds
2474
+ let currUrl = ''
2275
2475
 
2276
2476
  return client
2277
- .waitUntil(function () {
2278
- return this.getUrl().then((res) => {
2279
- currUrl = decodeUrl(res);
2280
- return currUrl.indexOf(urlPart) > -1;
2281
- });
2282
- }, { timeout: aSec * 1000 }).catch((e) => {
2477
+ .waitUntil(
2478
+ function () {
2479
+ return this.getUrl().then(res => {
2480
+ currUrl = decodeUrl(res)
2481
+ return currUrl.indexOf(urlPart) > -1
2482
+ })
2483
+ },
2484
+ { timeout: aSec * 1000 },
2485
+ )
2486
+ .catch(e => {
2487
+ e = wrapError(e)
2283
2488
  if (e.message.indexOf('timeout')) {
2284
- throw new Error(`expected url to include ${urlPart}, but found ${currUrl}`);
2489
+ throw new Error(`expected url to include ${urlPart}, but found ${currUrl}`)
2285
2490
  }
2286
- throw e;
2287
- });
2491
+ throw e
2492
+ })
2288
2493
  }
2289
2494
 
2290
2495
  /**
2291
2496
  * {{> waitUrlEquals }}
2292
2497
  */
2293
2498
  async waitUrlEquals(urlPart, sec = null) {
2294
- const aSec = sec || this.options.waitForTimeoutInSeconds;
2295
- const baseUrl = this.options.url;
2499
+ const aSec = sec || this.options.waitForTimeoutInSeconds
2500
+ const baseUrl = this.options.url
2296
2501
  if (urlPart.indexOf('http') < 0) {
2297
- urlPart = baseUrl + urlPart;
2298
- }
2299
- let currUrl = '';
2300
- return this.browser.waitUntil(function () {
2301
- return this.getUrl().then((res) => {
2302
- currUrl = decodeUrl(res);
2303
- return currUrl === urlPart;
2304
- });
2305
- }, aSec * 1000).catch((e) => {
2306
- if (e.message.indexOf('timeout')) {
2307
- throw new Error(`expected url to be ${urlPart}, but found ${currUrl}`);
2308
- }
2309
- throw e;
2310
- });
2502
+ urlPart = baseUrl + urlPart
2503
+ }
2504
+ let currUrl = ''
2505
+ return this.browser
2506
+ .waitUntil(function () {
2507
+ return this.getUrl().then(res => {
2508
+ currUrl = decodeUrl(res)
2509
+ return currUrl === urlPart
2510
+ })
2511
+ }, aSec * 1000)
2512
+ .catch(e => {
2513
+ e = wrapError(e)
2514
+ if (e.message.indexOf('timeout')) {
2515
+ throw new Error(`expected url to be ${urlPart}, but found ${currUrl}`)
2516
+ }
2517
+ throw e
2518
+ })
2311
2519
  }
2312
2520
 
2313
2521
  /**
@@ -2315,42 +2523,48 @@ class WebDriver extends Helper {
2315
2523
  *
2316
2524
  */
2317
2525
  async waitForText(text, sec = null, context = null) {
2318
- const aSec = sec || this.options.waitForTimeoutInSeconds;
2319
- const _context = context || this.root;
2320
-
2321
- return this.browser.waitUntil(async () => {
2322
- const res = await this.$$(withStrictLocator.call(this, _context));
2323
- if (!res || res.length === 0) return false;
2324
- const selected = await forEachAsync(res, async el => this.browser.getElementText(getElementId(el)));
2325
- if (Array.isArray(selected)) {
2326
- return selected.filter(part => part.indexOf(text) >= 0).length > 0;
2327
- }
2328
- return selected.indexOf(text) >= 0;
2329
- }, {
2330
- timeout: aSec * 1000,
2331
- timeoutMsg: `element (${_context}) is not in DOM or there is no element(${_context}) with text "${text}" after ${aSec} sec`,
2332
- });
2526
+ const aSec = sec || this.options.waitForTimeoutInSeconds
2527
+ const _context = context || this.root
2528
+
2529
+ return this.browser.waitUntil(
2530
+ async () => {
2531
+ const res = await this.$$(withStrictLocator.call(this, _context))
2532
+ if (!res || res.length === 0) return false
2533
+ const selected = await forEachAsync(res, async el => this.browser.getElementText(getElementId(el)))
2534
+ if (Array.isArray(selected)) {
2535
+ return selected.filter(part => part.indexOf(text) >= 0).length > 0
2536
+ }
2537
+ return selected.indexOf(text) >= 0
2538
+ },
2539
+ {
2540
+ timeout: aSec * 1000,
2541
+ timeoutMsg: `element (${_context}) is not in DOM or there is no element(${_context}) with text "${text}" after ${aSec} sec`,
2542
+ },
2543
+ )
2333
2544
  }
2334
2545
 
2335
2546
  /**
2336
2547
  * {{> waitForValue }}
2337
2548
  */
2338
2549
  async waitForValue(field, value, sec = null) {
2339
- const client = this.browser;
2340
- const aSec = sec || this.options.waitForTimeoutInSeconds;
2341
-
2342
- return client.waitUntil(async () => {
2343
- const res = await findFields.call(this, field);
2344
- if (!res || res.length === 0) return false;
2345
- const selected = await forEachAsync(res, async el => el.getValue());
2346
- if (Array.isArray(selected)) {
2347
- return selected.filter(part => part.indexOf(value) >= 0).length > 0;
2348
- }
2349
- return selected.indexOf(value) >= 0;
2350
- }, {
2351
- timeout: aSec * 1000,
2352
- timeoutMsg: `element (${field}) is not in DOM or there is no element(${field}) with value "${value}" after ${aSec} sec`,
2353
- });
2550
+ const client = this.browser
2551
+ const aSec = sec || this.options.waitForTimeoutInSeconds
2552
+
2553
+ return client.waitUntil(
2554
+ async () => {
2555
+ const res = await findFields.call(this, field)
2556
+ if (!res || res.length === 0) return false
2557
+ const selected = await forEachAsync(res, async el => el.getValue())
2558
+ if (Array.isArray(selected)) {
2559
+ return selected.filter(part => part.indexOf(value) >= 0).length > 0
2560
+ }
2561
+ return selected.indexOf(value) >= 0
2562
+ },
2563
+ {
2564
+ timeout: aSec * 1000,
2565
+ timeoutMsg: `element (${field}) is not in DOM or there is no element(${field}) with value "${value}" after ${aSec} sec`,
2566
+ },
2567
+ )
2354
2568
  }
2355
2569
 
2356
2570
  /**
@@ -2358,304 +2572,281 @@ class WebDriver extends Helper {
2358
2572
  *
2359
2573
  */
2360
2574
  async waitForVisible(locator, sec = null) {
2361
- const aSec = sec || this.options.waitForTimeoutInSeconds;
2362
-
2363
- return this.browser.waitUntil(async () => {
2364
- const res = await this._res(locator);
2365
- if (!res || res.length === 0) return false;
2366
- const selected = await forEachAsync(res, async el => el.isDisplayed());
2367
- if (Array.isArray(selected)) {
2368
- return selected.filter(val => val === true).length > 0;
2369
- }
2370
- return selected;
2371
- }, { timeout: aSec * 1000, timeoutMsg: `element (${new Locator(locator)}) still not visible after ${aSec} sec` });
2575
+ const aSec = sec || this.options.waitForTimeoutInSeconds
2576
+
2577
+ return this.browser.waitUntil(
2578
+ async () => {
2579
+ const res = await this._res(locator)
2580
+ if (!res || res.length === 0) return false
2581
+ const selected = await forEachAsync(res, async el => el.isDisplayed())
2582
+ if (Array.isArray(selected)) {
2583
+ return selected.filter(val => val === true).length > 0
2584
+ }
2585
+ return selected
2586
+ },
2587
+ {
2588
+ timeout: aSec * 1000,
2589
+ timeoutMsg: `element (${new Locator(locator)}) still not visible after ${aSec} sec`,
2590
+ },
2591
+ )
2372
2592
  }
2373
2593
 
2374
2594
  /**
2375
2595
  * {{> waitNumberOfVisibleElements }}
2376
2596
  */
2377
2597
  async waitNumberOfVisibleElements(locator, num, sec = null) {
2378
- const aSec = sec || this.options.waitForTimeoutInSeconds;
2379
-
2380
- return this.browser.waitUntil(async () => {
2381
- const res = await this._res(locator);
2382
- if (!res || res.length === 0) return false;
2383
- let selected = await forEachAsync(res, async el => el.isDisplayed());
2384
-
2385
- if (!Array.isArray(selected)) selected = [selected];
2386
- selected = selected.filter(val => val === true);
2387
- return selected.length === num;
2388
- }, { timeout: aSec * 1000, timeoutMsg: `The number of elements (${new Locator(locator)}) is not ${num} after ${aSec} sec` });
2598
+ const aSec = sec || this.options.waitForTimeoutInSeconds
2599
+
2600
+ return this.browser
2601
+ .waitUntil(
2602
+ async () => {
2603
+ const res = await this._res(locator)
2604
+ if (!res || res.length === 0) return false
2605
+ let selected = await forEachAsync(res, async el => el.isDisplayed())
2606
+
2607
+ if (!Array.isArray(selected)) selected = [selected]
2608
+ selected = selected.filter(val => val === true)
2609
+ return selected.length === num
2610
+ },
2611
+ {
2612
+ timeout: aSec * 1000,
2613
+ timeoutMsg: `The number of elements (${new Locator(locator)}) is not ${num} after ${aSec} sec`,
2614
+ },
2615
+ )
2616
+ .catch(e => {
2617
+ throw wrapError(e)
2618
+ })
2389
2619
  }
2390
2620
 
2391
2621
  /**
2392
2622
  * {{> waitForInvisible }}
2393
2623
  */
2394
2624
  async waitForInvisible(locator, sec = null) {
2395
- const aSec = sec || this.options.waitForTimeoutInSeconds;
2396
-
2397
- return this.browser.waitUntil(async () => {
2398
- const res = await this._res(locator);
2399
- if (!res || res.length === 0) return true;
2400
- const selected = await forEachAsync(res, async el => el.isDisplayed());
2401
- return !selected.length;
2402
- }, { timeout: aSec * 1000, timeoutMsg: `element (${new Locator(locator)}) still visible after ${aSec} sec` });
2625
+ const aSec = sec || this.options.waitForTimeoutInSeconds
2626
+
2627
+ return this.browser.waitUntil(
2628
+ async () => {
2629
+ const res = await this._res(locator)
2630
+ if (!res || res.length === 0) return true
2631
+ const selected = await forEachAsync(res, async el => el.isDisplayed())
2632
+ return !selected.length
2633
+ },
2634
+ { timeout: aSec * 1000, timeoutMsg: `element (${new Locator(locator)}) still visible after ${aSec} sec` },
2635
+ )
2403
2636
  }
2404
2637
 
2405
2638
  /**
2406
2639
  * {{> waitToHide }}
2407
2640
  */
2408
2641
  async waitToHide(locator, sec = null) {
2409
- return this.waitForInvisible(locator, sec);
2642
+ return this.waitForInvisible(locator, sec)
2410
2643
  }
2411
2644
 
2412
2645
  /**
2413
2646
  * {{> waitForDetached }}
2414
2647
  */
2415
2648
  async waitForDetached(locator, sec = null) {
2416
- const aSec = sec || this.options.waitForTimeoutInSeconds;
2649
+ const aSec = sec || this.options.waitForTimeoutInSeconds
2417
2650
 
2418
- return this.browser.waitUntil(async () => {
2419
- const res = await this._res(locator);
2420
- if (!res || res.length === 0) {
2421
- return true;
2422
- }
2423
- return false;
2424
- }, { timeout: aSec * 1000, timeoutMsg: `element (${new Locator(locator)}) still on page after ${aSec} sec` });
2651
+ return this.browser.waitUntil(
2652
+ async () => {
2653
+ const res = await this._res(locator)
2654
+ if (!res || res.length === 0) {
2655
+ return true
2656
+ }
2657
+ return false
2658
+ },
2659
+ { timeout: aSec * 1000, timeoutMsg: `element (${new Locator(locator)}) still on page after ${aSec} sec` },
2660
+ )
2425
2661
  }
2426
2662
 
2427
2663
  /**
2428
2664
  * {{> waitForFunction }}
2429
2665
  */
2430
2666
  async waitForFunction(fn, argsOrSec = null, sec = null) {
2431
- let args = [];
2667
+ let args = []
2432
2668
  if (argsOrSec) {
2433
2669
  if (Array.isArray(argsOrSec)) {
2434
- args = argsOrSec;
2670
+ args = argsOrSec
2435
2671
  } else if (typeof argsOrSec === 'number') {
2436
- sec = argsOrSec;
2672
+ sec = argsOrSec
2437
2673
  }
2438
2674
  }
2439
2675
 
2440
- const aSec = sec || this.options.waitForTimeoutInSeconds;
2676
+ const aSec = sec || this.options.waitForTimeoutInSeconds
2441
2677
 
2442
- return this.browser.waitUntil(async () => this.browser.execute(fn, ...args), { timeout: aSec * 1000, timeoutMsg: '' });
2678
+ return this.browser.waitUntil(async () => this.browser.execute(fn, ...args), {
2679
+ timeout: aSec * 1000,
2680
+ timeoutMsg: '',
2681
+ })
2443
2682
  }
2444
2683
 
2445
2684
  /**
2446
2685
  * {{> waitForNumberOfTabs }}
2447
2686
  */
2448
2687
  async waitForNumberOfTabs(expectedTabs, sec) {
2449
- const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeoutInSeconds;
2450
- let currentTabs;
2451
- let count = 0;
2688
+ const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeoutInSeconds
2689
+ let currentTabs
2690
+ let count = 0
2452
2691
 
2453
2692
  do {
2454
- currentTabs = await this.grabNumberOfOpenTabs();
2455
- await this.wait(1);
2456
- count += 1000;
2457
- if (currentTabs >= expectedTabs) return;
2458
- } while (count <= waitTimeout);
2693
+ currentTabs = await this.grabNumberOfOpenTabs()
2694
+ await this.wait(1)
2695
+ count += 1000
2696
+ if (currentTabs >= expectedTabs) return
2697
+ } while (count <= waitTimeout)
2459
2698
 
2460
- throw new Error(`Expected ${expectedTabs} tabs are not met after ${waitTimeout / 1000} sec.`);
2699
+ throw new Error(`Expected ${expectedTabs} tabs are not met after ${waitTimeout / 1000} sec.`)
2461
2700
  }
2462
2701
 
2463
2702
  /**
2464
2703
  * {{> switchTo }}
2465
2704
  */
2466
2705
  async switchTo(locator) {
2467
- this.browser.isInsideFrame = true;
2468
- if (Number.isInteger(locator)) {
2469
- if (this.options.automationProtocol) {
2470
- return this.browser.switchToFrame(locator + 1);
2471
- }
2472
- return this.browser.switchToFrame(locator);
2473
- }
2706
+ this.browser.isInsideFrame = true
2474
2707
  if (!locator) {
2475
- return this.browser.switchToFrame(null);
2708
+ return this.browser.switchFrame(null)
2476
2709
  }
2477
2710
 
2478
- let res = await this._locate(locator, true);
2479
- assertElementExists(res, locator);
2480
- res = usingFirstElement(res);
2481
- return this.browser.switchToFrame(res);
2711
+ let res = await this._locate(locator, true)
2712
+ assertElementExists(res, locator)
2713
+ res = usingFirstElement(res)
2714
+ return this.browser.switchFrame(res)
2482
2715
  }
2483
2716
 
2484
2717
  /**
2485
2718
  * {{> switchToNextTab }}
2486
2719
  */
2487
2720
  async switchToNextTab(num = 1, sec = null) {
2488
- const aSec = sec || this.options.waitForTimeoutInSeconds;
2489
- let target;
2490
- const current = await this.browser.getWindowHandle();
2491
-
2492
- await this.browser.waitUntil(async () => {
2493
- await this.browser.getWindowHandles().then((handles) => {
2494
- if (handles.indexOf(current) + num + 1 <= handles.length) {
2495
- target = handles[handles.indexOf(current) + num];
2496
- }
2497
- });
2498
- return target;
2499
- }, { timeout: aSec * 1000, timeoutMsg: `There is no ability to switch to next tab with offset ${num}` });
2500
- return this.browser.switchToWindow(target);
2721
+ const aSec = sec || this.options.waitForTimeoutInSeconds
2722
+ let target
2723
+ const current = await this.browser.getWindowHandle()
2724
+
2725
+ await this.browser.waitUntil(
2726
+ async () => {
2727
+ await this.browser.getWindowHandles().then(handles => {
2728
+ if (handles.indexOf(current) + num + 1 <= handles.length) {
2729
+ target = handles[handles.indexOf(current) + num]
2730
+ }
2731
+ })
2732
+ return target
2733
+ },
2734
+ { timeout: aSec * 1000, timeoutMsg: `There is no ability to switch to next tab with offset ${num}` },
2735
+ )
2736
+ return this.browser.switchToWindow(target)
2501
2737
  }
2502
2738
 
2503
2739
  /**
2504
2740
  * {{> switchToPreviousTab }}
2505
2741
  */
2506
2742
  async switchToPreviousTab(num = 1, sec = null) {
2507
- const aSec = sec || this.options.waitForTimeoutInSeconds;
2508
- const current = await this.browser.getWindowHandle();
2509
- let target;
2510
-
2511
- await this.browser.waitUntil(async () => {
2512
- await this.browser.getWindowHandles().then((handles) => {
2513
- if (handles.indexOf(current) - num > -1) {
2514
- target = handles[handles.indexOf(current) - num];
2515
- }
2516
- });
2517
- return target;
2518
- }, { timeout: aSec * 1000, timeoutMsg: `There is no ability to switch to previous tab with offset ${num}` });
2519
- return this.browser.switchToWindow(target);
2743
+ const aSec = sec || this.options.waitForTimeoutInSeconds
2744
+ const current = await this.browser.getWindowHandle()
2745
+ let target
2746
+
2747
+ await this.browser.waitUntil(
2748
+ async () => {
2749
+ await this.browser.getWindowHandles().then(handles => {
2750
+ if (handles.indexOf(current) - num > -1) {
2751
+ target = handles[handles.indexOf(current) - num]
2752
+ }
2753
+ })
2754
+ return target
2755
+ },
2756
+ { timeout: aSec * 1000, timeoutMsg: `There is no ability to switch to previous tab with offset ${num}` },
2757
+ )
2758
+ return this.browser.switchToWindow(target)
2520
2759
  }
2521
2760
 
2522
2761
  /**
2523
2762
  * {{> closeCurrentTab }}
2524
2763
  */
2525
2764
  async closeCurrentTab() {
2526
- await this.browser.closeWindow();
2527
- const handles = await this.browser.getWindowHandles();
2528
- if (handles[0]) await this.browser.switchToWindow(handles[0]);
2765
+ await this.browser.closeWindow()
2766
+ const handles = await this.browser.getWindowHandles()
2767
+ if (handles[0]) await this.browser.switchToWindow(handles[0])
2529
2768
  }
2530
2769
 
2531
2770
  /**
2532
2771
  * {{> openNewTab }}
2533
2772
  */
2534
2773
  async openNewTab(url = 'about:blank', windowName = null) {
2535
- const client = this.browser;
2536
- const crypto = require('crypto');
2774
+ const client = this.browser
2537
2775
  if (windowName == null) {
2538
- windowName = crypto.randomBytes(32).toString('hex');
2776
+ windowName = crypto.randomBytes(32).toString('hex')
2539
2777
  }
2540
- return client.newWindow(url, windowName);
2778
+ return client.newWindow(url, windowName)
2541
2779
  }
2542
2780
 
2543
2781
  /**
2544
2782
  * {{> grabNumberOfOpenTabs }}
2545
2783
  */
2546
2784
  async grabNumberOfOpenTabs() {
2547
- const pages = await this.browser.getWindowHandles();
2548
- this.debugSection('Tabs', `Total ${pages.length}`);
2549
- return pages.length;
2785
+ const pages = await this.browser.getWindowHandles()
2786
+ this.debugSection('Tabs', `Total ${pages.length}`)
2787
+ return pages.length
2550
2788
  }
2551
2789
 
2552
2790
  /**
2553
2791
  * {{> refreshPage }}
2554
2792
  */
2555
2793
  async refreshPage() {
2556
- const client = this.browser;
2557
- return client.refresh();
2794
+ const client = this.browser
2795
+ return client.refresh()
2558
2796
  }
2559
2797
 
2560
2798
  /**
2561
2799
  * {{> scrollPageToTop }}
2562
2800
  */
2563
2801
  scrollPageToTop() {
2564
- const client = this.browser;
2565
- /* eslint-disable prefer-arrow-callback */
2802
+ const client = this.browser
2803
+
2566
2804
  return client.execute(function () {
2567
- window.scrollTo(0, 0);
2568
- });
2569
- /* eslint-enable */
2805
+ window.scrollTo(0, 0)
2806
+ })
2570
2807
  }
2571
2808
 
2572
2809
  /**
2573
2810
  * {{> scrollPageToBottom }}
2574
2811
  */
2575
2812
  scrollPageToBottom() {
2576
- const client = this.browser;
2577
- /* eslint-disable prefer-arrow-callback, comma-dangle */
2813
+ const client = this.browser
2814
+
2578
2815
  return client.execute(function () {
2579
- const body = document.body;
2580
- const html = document.documentElement;
2581
- window.scrollTo(0, Math.max(
2582
- body.scrollHeight,
2583
- body.offsetHeight,
2584
- html.clientHeight,
2585
- html.scrollHeight,
2586
- html.offsetHeight
2587
- ));
2588
- });
2589
- /* eslint-enable */
2816
+ const body = document.body
2817
+ const html = document.documentElement
2818
+ window.scrollTo(0, Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight))
2819
+ })
2590
2820
  }
2591
2821
 
2592
2822
  /**
2593
2823
  * {{> grabPageScrollPosition }}
2594
2824
  */
2595
2825
  async grabPageScrollPosition() {
2596
- /* eslint-disable comma-dangle */
2597
2826
  function getScrollPosition() {
2598
2827
  return {
2599
2828
  x: window.pageXOffset,
2600
- y: window.pageYOffset
2601
- };
2602
- }
2603
- /* eslint-enable comma-dangle */
2604
- return this.executeScript(getScrollPosition);
2605
- }
2606
-
2607
- /**
2608
- * This method is **deprecated**.
2609
- *
2610
- *
2611
- * {{> setGeoLocation }}
2612
- */
2613
- async setGeoLocation(latitude, longitude) {
2614
- if (!this.options.automationProtocol) {
2615
- console.log(`setGeoLocation deprecated:
2616
- * This command is deprecated due to using deprecated JSON Wire Protocol command. More info: https://webdriver.io/docs/api/jsonwp/#setgeolocation
2617
- * Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration`);
2618
- return;
2829
+ y: window.pageYOffset,
2830
+ }
2619
2831
  }
2620
- this.geoLocation = { latitude, longitude };
2621
2832
 
2622
- await this.browser.call(async () => {
2623
- const pages = await this.puppeteerBrowser.pages();
2624
- await pages[0].setGeolocation({ latitude, longitude });
2625
- });
2626
- }
2627
-
2628
- /**
2629
- * This method is **deprecated**.
2630
- *
2631
- * {{> grabGeoLocation }}
2632
- *
2633
- */
2634
- async grabGeoLocation() {
2635
- if (!this.options.automationProtocol) {
2636
- console.log(`grabGeoLocation deprecated:
2637
- * This command is deprecated due to using deprecated JSON Wire Protocol command. More info: https://webdriver.io/docs/api/jsonwp/#getgeolocation
2638
- * Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration`);
2639
- return;
2640
- }
2641
- if (!this.geoLocation) return 'No GeoLocation is set!';
2642
- return this.geoLocation;
2833
+ return this.executeScript(getScrollPosition)
2643
2834
  }
2644
2835
 
2645
2836
  /**
2646
2837
  * {{> grabElementBoundingRect }}
2647
2838
  */
2648
2839
  async grabElementBoundingRect(locator, prop) {
2649
- const res = await this._locate(locator, true);
2650
- assertElementExists(res, locator);
2651
- const el = usingFirstElement(res);
2840
+ const res = await this._locate(locator, true)
2841
+ assertElementExists(res, locator)
2842
+ const el = usingFirstElement(res)
2652
2843
 
2653
2844
  const rect = {
2654
2845
  ...(await el.getLocation()),
2655
2846
  ...(await el.getSize()),
2656
- };
2657
- if (prop) return rect[prop];
2658
- return rect;
2847
+ }
2848
+ if (prop) return rect[prop]
2849
+ return rect
2659
2850
  }
2660
2851
 
2661
2852
  /**
@@ -2663,164 +2854,56 @@ class WebDriver extends Helper {
2663
2854
  * @param {*} caps
2664
2855
  * @param {*} fn
2665
2856
  */
2666
- /* eslint-disable */
2667
- runOnIOS(caps, fn) {
2668
- }
2857
+
2858
+ runOnIOS(caps, fn) {}
2669
2859
 
2670
2860
  /**
2671
2861
  * Placeholder for ~ locator only test case write once run on both Appium and WebDriver.
2672
2862
  * @param {*} caps
2673
2863
  * @param {*} fn
2674
2864
  */
2675
- runOnAndroid(caps, fn) {
2676
- }
2677
- /* eslint-enable */
2865
+ runOnAndroid(caps, fn) {}
2678
2866
 
2679
2867
  /**
2680
2868
  * Placeholder for ~ locator only test case write once run on both Appium and WebDriver.
2681
2869
  */
2682
- runInWeb(fn) {
2683
- return fn();
2684
- }
2685
-
2686
- /**
2687
- *
2688
- * _Note:_ Only works when devtoolsProtocol is enabled.
2689
- *
2690
- * {{> flushNetworkTraffics }}
2691
- */
2692
- flushNetworkTraffics() {
2693
- if (!this.options.automationProtocol) {
2694
- console.log('* Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration');
2695
- return;
2696
- }
2697
- this.requests = [];
2698
- }
2699
-
2700
- /**
2701
- *
2702
- * _Note:_ Only works when devtoolsProtocol is enabled.
2703
- *
2704
- * {{> stopRecordingTraffic }}
2705
- */
2706
- stopRecordingTraffic() {
2707
- if (!this.options.automationProtocol) {
2708
- console.log('* Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration');
2709
- return;
2710
- }
2711
- this.page.removeAllListeners('request');
2712
- this.recording = false;
2713
- }
2714
-
2715
- /**
2716
- *
2717
- * _Note:_ Only works when devtoolsProtocol is enabled.
2718
- *
2719
- * {{> startRecordingTraffic }}
2720
- *
2721
- */
2722
- async startRecordingTraffic() {
2723
- if (!this.options.automationProtocol) {
2724
- console.log('* Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration');
2725
- return;
2726
- }
2727
- this.flushNetworkTraffics();
2728
- this.recording = true;
2729
- this.recordedAtLeastOnce = true;
2730
-
2731
- this.page = (await this.puppeteerBrowser.pages())[0];
2732
- await this.page.setRequestInterception(true);
2733
-
2734
- this.page.on('request', (request) => {
2735
- const information = {
2736
- url: request.url(),
2737
- method: request.method(),
2738
- requestHeaders: request.headers(),
2739
- requestPostData: request.postData(),
2740
- response: request.response(),
2741
- };
2742
-
2743
- this.debugSection('REQUEST: ', JSON.stringify(information));
2744
-
2745
- if (typeof information.requestPostData === 'object') {
2746
- information.requestPostData = JSON.parse(information.requestPostData);
2747
- }
2748
- request.continue();
2749
- this.requests.push(information);
2750
- });
2751
- }
2752
-
2753
- /**
2754
- *
2755
- * _Note:_ Only works when devtoolsProtocol is enabled.
2756
- *
2757
- * {{> grabRecordedNetworkTraffics }}
2758
- */
2759
- async grabRecordedNetworkTraffics() {
2760
- if (!this.options.automationProtocol) {
2761
- console.log('* Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration');
2762
- return;
2763
- }
2764
- return grabRecordedNetworkTraffics.call(this);
2765
- }
2766
-
2767
- /**
2768
- *
2769
- * _Note:_ Only works when devtoolsProtocol is enabled.
2770
- *
2771
- * {{> seeTraffic }}
2772
- */
2773
- async seeTraffic({
2774
- name, url, parameters, requestPostData, timeout = 10,
2775
- }) {
2776
- if (!this.options.automationProtocol) {
2777
- console.log('* Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration');
2778
- return;
2779
- }
2780
- await seeTraffic.call(this, ...arguments);
2781
- }
2782
-
2783
- /**
2784
- *
2785
- * _Note:_ Only works when devtoolsProtocol is enabled.
2786
- *
2787
- * {{> dontSeeTraffic }}
2788
- *
2789
- */
2790
- dontSeeTraffic({ name, url }) {
2791
- if (!this.options.automationProtocol) {
2792
- console.log('* Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration');
2793
- return;
2794
- }
2795
- dontSeeTraffic.call(this, ...arguments);
2870
+ async runInWeb(fn) {
2871
+ return fn()
2796
2872
  }
2797
2873
  }
2798
2874
 
2799
2875
  async function proceedSee(assertType, text, context, strict = false) {
2800
- let description;
2876
+ let description
2801
2877
  if (!context) {
2802
2878
  if (this.context === webRoot) {
2803
- context = this.context;
2804
- description = 'web page';
2879
+ context = this.context
2880
+ description = 'web page'
2805
2881
  } else {
2806
- description = `current context ${this.context}`;
2807
- context = './/*';
2882
+ description = `current context ${this.context}`
2883
+ context = './/*'
2808
2884
  }
2809
2885
  } else {
2810
- description = `element ${context}`;
2886
+ description = `element ${context}`
2887
+ }
2888
+
2889
+ const smartWaitEnabled = assertType === 'assert'
2890
+ const res = await this._locate(withStrictLocator(context), smartWaitEnabled)
2891
+ assertElementExists(res, context)
2892
+ let selected = await forEachAsync(res, async el => this.browser.getElementText(getElementId(el)))
2893
+
2894
+ // apply ignoreCase option
2895
+ if (store?.currentStep?.opts?.ignoreCase === true) {
2896
+ text = text.toLowerCase()
2897
+ selected = selected.map(elText => elText.toLowerCase())
2811
2898
  }
2812
2899
 
2813
- const smartWaitEnabled = assertType === 'assert';
2814
- const res = await this._locate(withStrictLocator(context), smartWaitEnabled);
2815
- assertElementExists(res, context);
2816
- const selected = await forEachAsync(res, async el => this.browser.getElementText(getElementId(el)));
2817
2900
  if (strict) {
2818
2901
  if (Array.isArray(selected) && selected.length !== 0) {
2819
- return selected.map(elText => equals(description)[assertType](text, elText));
2902
+ return selected.map(elText => equals(description)[assertType](text, elText))
2820
2903
  }
2821
- return equals(description)[assertType](text, selected);
2904
+ return equals(description)[assertType](text, selected)
2822
2905
  }
2823
- return stringIncludes(description)[assertType](text, selected);
2906
+ return stringIncludes(description)[assertType](text, selected)
2824
2907
  }
2825
2908
 
2826
2909
  /**
@@ -2837,21 +2920,19 @@ async function proceedSee(assertType, text, context, strict = false) {
2837
2920
  * @return {Promise<Array>} - Array of values.
2838
2921
  */
2839
2922
  async function forEachAsync(array, callback, options = { expandArrayResults: true }) {
2840
- const {
2841
- expandArrayResults = true,
2842
- } = options;
2843
- const inputArray = Array.isArray(array) ? array : [array];
2844
- const values = [];
2923
+ const { expandArrayResults = true } = options
2924
+ const inputArray = Array.isArray(array) ? array : [array]
2925
+ const values = []
2845
2926
  for (let index = 0; index < inputArray.length; index++) {
2846
- const res = await callback(inputArray[index], index, inputArray);
2927
+ const res = await callback(inputArray[index], index, inputArray)
2847
2928
 
2848
2929
  if (Array.isArray(res) && expandArrayResults) {
2849
- res.forEach(val => values.push(val));
2930
+ res.forEach(val => values.push(val))
2850
2931
  } else if (res) {
2851
- values.push(res);
2932
+ values.push(res)
2852
2933
  }
2853
2934
  }
2854
- return values;
2935
+ return values
2855
2936
  }
2856
2937
 
2857
2938
  /**
@@ -2866,374 +2947,433 @@ async function forEachAsync(array, callback, options = { expandArrayResults: tru
2866
2947
  * @return {Promise<Array>} - Array of values.
2867
2948
  */
2868
2949
  async function filterAsync(array, callback) {
2869
- const inputArray = Array.isArray(array) ? array : [array];
2870
- const values = [];
2950
+ const inputArray = Array.isArray(array) ? array : [array]
2951
+ const values = []
2871
2952
  for (let index = 0; index < inputArray.length; index++) {
2872
- const res = await callback(inputArray[index], index, inputArray);
2873
- const value = Array.isArray(res) ? res[0] : res;
2953
+ const res = await callback(inputArray[index], index, inputArray)
2954
+ const value = Array.isArray(res) ? res[0] : res
2874
2955
 
2875
2956
  if (value) {
2876
- values.push(inputArray[index]);
2957
+ values.push(inputArray[index])
2877
2958
  }
2878
2959
  }
2879
- return values;
2960
+ return values
2880
2961
  }
2881
2962
 
2882
2963
  async function findClickable(locator, locateFn) {
2883
- locator = new Locator(locator);
2964
+ locator = new Locator(locator)
2884
2965
 
2885
2966
  if (this._isCustomLocator(locator)) {
2886
- return locateFn(locator.value);
2967
+ return locateFn(locator.value)
2887
2968
  }
2888
2969
 
2889
- if (locator.isAccessibilityId() && !this.isWeb) return locateFn(locator, true);
2890
- if (!locator.isFuzzy()) return locateFn(locator, true);
2970
+ if (locator.isAccessibilityId() && !this.isWeb) return locateFn(locator, true)
2971
+ if (locator.isRole()) return locateFn(locator, true)
2972
+ if (!locator.isFuzzy()) return locateFn(locator, true)
2973
+
2974
+ let els
2975
+ const literal = xpathLocator.literal(locator.value)
2891
2976
 
2892
- let els;
2893
- const literal = xpathLocator.literal(locator.value);
2977
+ els = await locateFn(Locator.clickable.narrow(literal))
2978
+ if (els.length) return els
2894
2979
 
2895
- els = await locateFn(Locator.clickable.narrow(literal));
2896
- if (els.length) return els;
2980
+ // Try ARIA selector for accessible name
2981
+ try {
2982
+ els = await locateFn(`aria/${locator.value}`)
2983
+ if (els.length) return els
2984
+ } catch (e) {
2985
+ // ARIA selector not supported or failed
2986
+ }
2897
2987
 
2898
- els = await locateFn(Locator.clickable.wide(literal));
2899
- if (els.length) return els;
2988
+ els = await locateFn(Locator.clickable.wide(literal))
2989
+ if (els.length) return els
2900
2990
 
2901
- els = await locateFn(Locator.clickable.self(literal));
2902
- if (els.length) return els;
2991
+ els = await locateFn(Locator.clickable.self(literal))
2992
+ if (els.length) return els
2903
2993
 
2904
- return locateFn(locator.value); // by css or xpath
2994
+ return await locateFn(locator.value) // by css or xpath
2905
2995
  }
2906
2996
 
2907
2997
  async function findFields(locator) {
2908
- locator = new Locator(locator);
2998
+ locator = new Locator(locator)
2909
2999
 
2910
3000
  if (this._isCustomLocator(locator)) {
2911
- return this._locate(locator);
3001
+ return this._locate(locator)
2912
3002
  }
2913
3003
 
2914
- if (locator.isAccessibilityId() && !this.isWeb) return this._locate(locator, true);
2915
- if (!locator.isFuzzy()) return this._locate(locator, true);
3004
+ if (locator.isAccessibilityId() && !this.isWeb) return this._locate(locator, true)
3005
+ if (locator.isRole()) return this._locate(locator, true)
3006
+ if (!locator.isFuzzy()) return this._locate(locator, true)
3007
+
3008
+ const literal = xpathLocator.literal(locator.value)
3009
+ let els = await this._locate(Locator.field.labelEquals(literal))
3010
+ if (els.length) return els
2916
3011
 
2917
- const literal = xpathLocator.literal(locator.value);
2918
- let els = await this._locate(Locator.field.labelEquals(literal));
2919
- if (els.length) return els;
3012
+ els = await this._locate(Locator.field.labelContains(literal))
3013
+ if (els.length) return els
2920
3014
 
2921
- els = await this._locate(Locator.field.labelContains(literal));
2922
- if (els.length) return els;
3015
+ els = await this._locate(Locator.field.byName(literal))
3016
+ if (els.length) return els
2923
3017
 
2924
- els = await this._locate(Locator.field.byName(literal));
2925
- if (els.length) return els;
2926
- return this._locate(locator.value); // by css or xpath
3018
+ return await this._locate(locator.value) // by css or xpath
2927
3019
  }
2928
3020
 
2929
3021
  async function proceedSeeField(assertType, field, value) {
2930
- const res = await findFields.call(this, field);
2931
- assertElementExists(res, field, 'Field');
2932
- const elem = usingFirstElement(res);
2933
- const elemId = getElementId(elem);
2934
-
2935
- const proceedMultiple = async (fields) => {
2936
- const fieldResults = toArray(await forEachAsync(fields, async (el) => {
2937
- const elementId = getElementId(el);
2938
- return this.browser.isW3C ? el.getValue() : this.browser.getElementAttribute(elementId, 'value');
2939
- }));
3022
+ const res = await findFields.call(this, field)
3023
+ assertElementExists(res, field, 'Field')
3024
+ const elem = usingFirstElement(res)
3025
+ const elemId = getElementId(elem)
3026
+
3027
+ const proceedMultiple = async fields => {
3028
+ const fieldResults = toArray(
3029
+ await forEachAsync(fields, async el => {
3030
+ const elementId = getElementId(el)
3031
+ return this.browser.getElementAttribute(elementId, 'value')
3032
+ }),
3033
+ )
2940
3034
 
2941
3035
  if (typeof value === 'boolean') {
2942
- equals(`no. of items matching > 0: ${field}`)[assertType](value, !!fieldResults.length);
3036
+ equals(`no. of items matching > 0: ${field}`)[assertType](value, !!fieldResults.length)
2943
3037
  } else {
2944
3038
  // Assert that results were found so the forEach assert does not silently pass
2945
- equals(`no. of items matching > 0: ${field}`)[assertType](true, !!fieldResults.length);
2946
- fieldResults.forEach(val => stringIncludes(`fields by ${field}`)[assertType](value, val));
3039
+ equals(`no. of items matching > 0: ${field}`)[assertType](true, !!fieldResults.length)
3040
+ fieldResults.forEach(val => stringIncludes(`fields by ${field}`)[assertType](value, val))
2947
3041
  }
2948
- };
3042
+ }
3043
+
3044
+ const proceedSingle = async el => {
3045
+ let res = await el.getValue()
2949
3046
 
2950
- const proceedSingle = el => el.getValue().then((res) => {
2951
3047
  if (res === null) {
2952
- throw new Error(`Element ${el.selector} has no value attribute`);
3048
+ res = await el.getText()
3049
+ }
3050
+
3051
+ if (res === null || res === undefined) {
3052
+ throw new Error(`Element ${el.selector} has no value attribute`)
2953
3053
  }
2954
- stringIncludes(`fields by ${field}`)[assertType](value, res);
2955
- });
2956
3054
 
2957
- const filterBySelected = async elements => filterAsync(elements, async el => this.browser.isElementSelected(getElementId(el)));
3055
+ stringIncludes(`fields by ${field}`)[assertType](value, res)
3056
+ }
3057
+
3058
+ const filterBySelected = async elements => filterAsync(elements, async el => this.browser.isElementSelected(getElementId(el)))
2958
3059
 
2959
3060
  const filterSelectedByValue = async (elements, value) => {
2960
- return filterAsync(elements, async (el) => {
2961
- const elementId = getElementId(el);
2962
- const currentValue = this.browser.isW3C ? await el.getValue() : await this.browser.getElementAttribute(elementId, 'value');
2963
- const isSelected = await this.browser.isElementSelected(elementId);
2964
- return currentValue === value && isSelected;
2965
- });
2966
- };
2967
-
2968
- const tag = await elem.getTagName();
3061
+ return filterAsync(elements, async el => {
3062
+ const elementId = getElementId(el)
3063
+ const currentValue = await this.browser.getElementAttribute(elementId, 'value')
3064
+ const isSelected = await this.browser.isElementSelected(elementId)
3065
+ return currentValue === value && isSelected
3066
+ })
3067
+ }
3068
+
3069
+ const tag = await elem.getTagName()
2969
3070
  if (tag === 'select') {
2970
- const subOptions = await this.browser.findElementsFromElement(elemId, 'css', 'option');
3071
+ let subOptions
3072
+
3073
+ try {
3074
+ subOptions = await this.browser.findElementsFromElement(elemId, 'css', 'option')
3075
+ } catch (e) {
3076
+ subOptions = await this.browser.findElementsFromElement(elemId, 'xpath', 'option')
3077
+ }
2971
3078
 
2972
3079
  if (value === '') {
2973
3080
  // Don't filter by value
2974
- const selectedOptions = await filterBySelected(subOptions);
2975
- return proceedMultiple(selectedOptions);
3081
+ const selectedOptions = await filterBySelected(subOptions)
3082
+ return proceedMultiple(selectedOptions)
2976
3083
  }
2977
3084
 
2978
- const options = await filterSelectedByValue(subOptions, value);
2979
- return proceedMultiple(options);
3085
+ const options = await filterSelectedByValue(subOptions, value)
3086
+ return proceedMultiple(options)
2980
3087
  }
2981
3088
 
2982
3089
  if (tag === 'input') {
2983
- const fieldType = await elem.getAttribute('type');
3090
+ const fieldType = await elem.getAttribute('type')
2984
3091
 
2985
3092
  if (fieldType === 'checkbox' || fieldType === 'radio') {
2986
3093
  if (typeof value === 'boolean') {
2987
3094
  // Support boolean values
2988
- const options = await filterBySelected(res);
2989
- return proceedMultiple(options);
3095
+ const options = await filterBySelected(res)
3096
+ return proceedMultiple(options)
2990
3097
  }
2991
3098
 
2992
- const options = await filterSelectedByValue(res, value);
2993
- return proceedMultiple(options);
3099
+ const options = await filterSelectedByValue(res, value)
3100
+ return proceedMultiple(options)
2994
3101
  }
2995
- return proceedSingle(elem);
3102
+ return proceedSingle(elem)
2996
3103
  }
2997
- return proceedSingle(elem);
3104
+ return proceedSingle(elem)
2998
3105
  }
2999
3106
 
3000
3107
  function toArray(item) {
3001
3108
  if (!Array.isArray(item)) {
3002
- return [item];
3109
+ return [item]
3003
3110
  }
3004
- return item;
3111
+ return item
3005
3112
  }
3006
3113
 
3007
3114
  async function proceedSeeCheckbox(assertType, field) {
3008
- const res = await findFields.call(this, field);
3009
- assertElementExists(res, field, 'Field');
3115
+ const res = await findFields.call(this, field)
3116
+ assertElementExists(res, field, 'Field')
3117
+
3118
+ const selected = await forEachAsync(res, async el => {
3119
+ const elementId = getElementId(el)
3120
+ return isElementChecked(this.browser, elementId)
3121
+ })
3122
+
3123
+ return truth(`checkable field "${field}"`, 'to be checked')[assertType](selected)
3124
+ }
3125
+
3126
+ async function getElementTextAttributes(element) {
3127
+ const elementId = getElementId(element)
3128
+ const ariaLabel = await this.browser.getElementAttribute(elementId, 'aria-label').catch(() => '')
3129
+ const placeholder = await this.browser.getElementAttribute(elementId, 'placeholder').catch(() => '')
3130
+ const innerText = await this.browser.getElementText(elementId).catch(() => '')
3131
+ return [ariaLabel, placeholder, innerText]
3132
+ }
3010
3133
 
3011
- const selected = await forEachAsync(res, async el => this.browser.isElementSelected(getElementId(el)));
3012
- return truth(`checkable field "${field}"`, 'to be checked')[assertType](selected);
3134
+ async function isElementChecked(browser, elementId) {
3135
+ let isChecked = await browser.isElementSelected(elementId)
3136
+ if (!isChecked) {
3137
+ const ariaChecked = await browser.getElementAttribute(elementId, 'aria-checked')
3138
+ isChecked = ariaChecked === 'true'
3139
+ }
3140
+ return isChecked
3013
3141
  }
3014
3142
 
3015
3143
  async function findCheckable(locator, locateFn) {
3016
- let els;
3017
- locator = new Locator(locator);
3144
+ let els
3145
+ locator = new Locator(locator)
3018
3146
 
3019
3147
  if (this._isCustomLocator(locator)) {
3020
- return locateFn(locator.value);
3148
+ return locateFn(locator.value)
3021
3149
  }
3022
3150
 
3023
- if (locator.isAccessibilityId() && !this.isWeb) return locateFn(locator, true);
3024
- if (!locator.isFuzzy()) return locateFn(locator, true);
3151
+ if (locator.isAccessibilityId() && !this.isWeb) return locateFn(locator, true)
3152
+ if (locator.isRole()) return locateFn(locator, true)
3153
+ if (!locator.isFuzzy()) return locateFn(locator, true)
3154
+
3155
+ const literal = xpathLocator.literal(locator.value)
3156
+ els = await locateFn(Locator.checkable.byText(literal))
3157
+ if (els.length) return els
3158
+
3159
+ // Try ARIA selector for accessible name
3160
+ try {
3161
+ els = await locateFn(`aria/${locator.value}`)
3162
+ if (els.length) return els
3163
+ } catch (e) {
3164
+ // ARIA selector not supported or failed
3165
+ }
3025
3166
 
3026
- const literal = xpathLocator.literal(locator.value);
3027
- els = await locateFn(Locator.checkable.byText(literal));
3028
- if (els.length) return els;
3029
- els = await locateFn(Locator.checkable.byName(literal));
3030
- if (els.length) return els;
3167
+ els = await locateFn(Locator.checkable.byName(literal))
3168
+ if (els.length) return els
3031
3169
 
3032
- return locateFn(locator.value); // by css or xpath
3170
+ return await locateFn(locator.value) // by css or xpath
3033
3171
  }
3034
3172
 
3035
3173
  function withStrictLocator(locator) {
3036
- locator = new Locator(locator);
3037
- return locator.simplify();
3174
+ locator = new Locator(locator)
3175
+ return locator.simplify()
3038
3176
  }
3039
3177
 
3040
3178
  function isFrameLocator(locator) {
3041
- locator = new Locator(locator);
3042
- if (locator.isFrame()) return locator.value;
3043
- return false;
3179
+ locator = new Locator(locator)
3180
+ if (locator.isFrame()) return locator.value
3181
+ return false
3044
3182
  }
3045
3183
 
3046
3184
  function assertElementExists(res, locator, prefix, suffix) {
3047
3185
  if (!res || res.length === 0) {
3048
- throw new ElementNotFound(locator, prefix, suffix);
3186
+ throw new ElementNotFound(locator, prefix, suffix)
3049
3187
  }
3050
3188
  }
3051
3189
 
3052
3190
  function usingFirstElement(els) {
3053
- if (els.length > 1) debug(`[Elements] Using first element out of ${els.length}`);
3054
- return els[0];
3191
+ if (els.length > 1) debug(`[Elements] Using first element out of ${els.length}`)
3192
+ return els[0]
3055
3193
  }
3056
3194
 
3057
3195
  function getElementId(el) {
3058
3196
  // W3C WebDriver web element identifier
3059
3197
  // https://w3c.github.io/webdriver/#dfn-web-element-identifier
3060
3198
  if (el['element-6066-11e4-a52e-4f735466cecf']) {
3061
- return el['element-6066-11e4-a52e-4f735466cecf'];
3199
+ return el['element-6066-11e4-a52e-4f735466cecf']
3062
3200
  }
3063
3201
  // (deprecated) JsonWireProtocol identifier
3064
3202
  // https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#webelement-json-object
3065
3203
  if (el.ELEMENT) {
3066
- return el.ELEMENT;
3204
+ return el.ELEMENT
3067
3205
  }
3068
3206
 
3069
- return null;
3207
+ return null
3070
3208
  }
3071
3209
 
3072
3210
  // List of known key values to unicode code points
3073
3211
  // https://www.w3.org/TR/webdriver/#keyboard-actions
3074
3212
  const keyUnicodeMap = {
3075
- /* eslint-disable quote-props */
3076
- 'Unidentified': '\uE000',
3077
- 'Cancel': '\uE001',
3078
- 'Clear': '\uE005',
3079
- 'Help': '\uE002',
3080
- 'Pause': '\uE00B',
3081
- 'Backspace': '\uE003',
3082
- 'Return': '\uE006',
3083
- 'Enter': '\uE007',
3084
- 'Escape': '\uE00C',
3085
- 'Alt': '\uE00A',
3086
- 'AltLeft': '\uE00A',
3087
- 'AltRight': '\uE052',
3088
- 'Control': '\uE009',
3089
- 'ControlLeft': '\uE009',
3090
- 'ControlRight': '\uE051',
3091
- 'Meta': '\uE03D',
3092
- 'MetaLeft': '\uE03D',
3093
- 'MetaRight': '\uE053',
3094
- 'Shift': '\uE008',
3095
- 'ShiftLeft': '\uE008',
3096
- 'ShiftRight': '\uE050',
3097
- 'Space': '\uE00D',
3213
+ Unidentified: '\uE000',
3214
+ Cancel: '\uE001',
3215
+ Clear: '\uE005',
3216
+ Help: '\uE002',
3217
+ Pause: '\uE00B',
3218
+ Backspace: '\uE003',
3219
+ Return: '\uE006',
3220
+ Enter: '\uE007',
3221
+ Escape: '\uE00C',
3222
+ Alt: '\uE00A',
3223
+ AltLeft: '\uE00A',
3224
+ AltRight: '\uE052',
3225
+ Control: '\uE009',
3226
+ ControlLeft: '\uE009',
3227
+ ControlRight: '\uE051',
3228
+ Meta: '\uE03D',
3229
+ MetaLeft: '\uE03D',
3230
+ MetaRight: '\uE053',
3231
+ Shift: '\uE008',
3232
+ ShiftLeft: '\uE008',
3233
+ ShiftRight: '\uE050',
3234
+ Space: '\uE00D',
3098
3235
  ' ': '\uE00D',
3099
- 'Tab': '\uE004',
3100
- 'Insert': '\uE016',
3101
- 'Delete': '\uE017',
3102
- 'End': '\uE010',
3103
- 'Home': '\uE011',
3104
- 'PageUp': '\uE00E',
3105
- 'PageDown': '\uE00F',
3106
- 'ArrowDown': '\uE015',
3107
- 'ArrowLeft': '\uE012',
3108
- 'ArrowRight': '\uE014',
3109
- 'ArrowUp': '\uE013',
3110
- 'F1': '\uE031',
3111
- 'F2': '\uE032',
3112
- 'F3': '\uE033',
3113
- 'F4': '\uE034',
3114
- 'F5': '\uE035',
3115
- 'F6': '\uE036',
3116
- 'F7': '\uE037',
3117
- 'F8': '\uE038',
3118
- 'F9': '\uE039',
3119
- 'F10': '\uE03A',
3120
- 'F11': '\uE03B',
3121
- 'F12': '\uE03C',
3122
- 'Numpad0': '\uE01A',
3123
- 'Numpad1': '\uE01B',
3124
- 'Numpad2': '\uE01C',
3125
- 'Numpad3': '\uE01D',
3126
- 'Numpad4': '\uE01E',
3127
- 'Numpad5': '\uE01F',
3128
- 'Numpad6': '\uE020',
3129
- 'Numpad7': '\uE021',
3130
- 'Numpad8': '\uE022',
3131
- 'Numpad9': '\uE023',
3132
- 'NumpadMultiply': '\uE024',
3133
- 'NumpadAdd': '\uE025',
3134
- 'NumpadSubtract': '\uE027',
3135
- 'NumpadDecimal': '\uE028',
3136
- 'NumpadDivide': '\uE029',
3137
- 'NumpadEnter': '\uE007',
3138
- 'NumpadInsert': '\uE05C', // 'Numpad0' alternate (when NumLock off)
3139
- 'NumpadDelete': '\uE05D', // 'NumpadDecimal' alternate (when NumLock off)
3140
- 'NumpadEnd': '\uE056', // 'Numpad1' alternate (when NumLock off)
3141
- 'NumpadHome': '\uE057', // 'Numpad7' alternate (when NumLock off)
3142
- 'NumpadPageDown': '\uE055', // 'Numpad3' alternate (when NumLock off)
3143
- 'NumpadPageUp': '\uE054', // 'Numpad9' alternate (when NumLock off)
3144
- 'NumpadArrowDown': '\uE05B', // 'Numpad2' alternate (when NumLock off)
3145
- 'NumpadArrowLeft': '\uE058', // 'Numpad4' alternate (when NumLock off)
3146
- 'NumpadArrowRight': '\uE05A', // 'Numpad6' alternate (when NumLock off)
3147
- 'NumpadArrowUp': '\uE059', // 'Numpad8' alternate (when NumLock off)
3148
- 'Comma': '\uE026', // ',' alias
3149
- 'Digit0': '0', // '0' alias
3150
- 'Digit1': '1', // '1' alias
3151
- 'Digit2': '2', // '2' alias
3152
- 'Digit3': '3', // '3' alias
3153
- 'Digit4': '4', // '4' alias
3154
- 'Digit5': '5', // '5' alias
3155
- 'Digit6': '6', // '6' alias
3156
- 'Digit7': '7', // '7' alias
3157
- 'Digit8': '8', // '8' alias
3158
- 'Digit9': '9', // '9' alias
3159
- 'Equal': '\uE019', // '=' alias
3160
- 'KeyA': 'a', // 'a' alias
3161
- 'KeyB': 'b', // 'b' alias
3162
- 'KeyC': 'c', // 'c' alias
3163
- 'KeyD': 'd', // 'd' alias
3164
- 'KeyE': 'e', // 'e' alias
3165
- 'KeyF': 'f', // 'f' alias
3166
- 'KeyG': 'g', // 'g' alias
3167
- 'KeyH': 'h', // 'h' alias
3168
- 'KeyI': 'i', // 'i' alias
3169
- 'KeyJ': 'j', // 'j' alias
3170
- 'KeyK': 'k', // 'k' alias
3171
- 'KeyL': 'l', // 'l' alias
3172
- 'KeyM': 'm', // 'm' alias
3173
- 'KeyN': 'n', // 'n' alias
3174
- 'KeyO': 'o', // 'o' alias
3175
- 'KeyP': 'p', // 'p' alias
3176
- 'KeyQ': 'q', // 'q' alias
3177
- 'KeyR': 'r', // 'r' alias
3178
- 'KeyS': 's', // 's' alias
3179
- 'KeyT': 't', // 't' alias
3180
- 'KeyU': 'u', // 'u' alias
3181
- 'KeyV': 'v', // 'v' alias
3182
- 'KeyW': 'w', // 'w' alias
3183
- 'KeyX': 'x', // 'x' alias
3184
- 'KeyY': 'y', // 'y' alias
3185
- 'KeyZ': 'z', // 'z' alias
3186
- 'Period': '.', // '.' alias
3187
- 'Semicolon': '\uE018', // ';' alias
3188
- 'Slash': '/', // '/' alias
3189
- 'ZenkakuHankaku': '\uE040',
3190
- /* eslint-enable quote-props */
3191
- };
3236
+ Tab: '\uE004',
3237
+ Insert: '\uE016',
3238
+ Delete: '\uE017',
3239
+ End: '\uE010',
3240
+ Home: '\uE011',
3241
+ PageUp: '\uE00E',
3242
+ PageDown: '\uE00F',
3243
+ ArrowDown: '\uE015',
3244
+ ArrowLeft: '\uE012',
3245
+ ArrowRight: '\uE014',
3246
+ ArrowUp: '\uE013',
3247
+ F1: '\uE031',
3248
+ F2: '\uE032',
3249
+ F3: '\uE033',
3250
+ F4: '\uE034',
3251
+ F5: '\uE035',
3252
+ F6: '\uE036',
3253
+ F7: '\uE037',
3254
+ F8: '\uE038',
3255
+ F9: '\uE039',
3256
+ F10: '\uE03A',
3257
+ F11: '\uE03B',
3258
+ F12: '\uE03C',
3259
+ Numpad0: '\uE01A',
3260
+ Numpad1: '\uE01B',
3261
+ Numpad2: '\uE01C',
3262
+ Numpad3: '\uE01D',
3263
+ Numpad4: '\uE01E',
3264
+ Numpad5: '\uE01F',
3265
+ Numpad6: '\uE020',
3266
+ Numpad7: '\uE021',
3267
+ Numpad8: '\uE022',
3268
+ Numpad9: '\uE023',
3269
+ NumpadMultiply: '\uE024',
3270
+ NumpadAdd: '\uE025',
3271
+ NumpadSubtract: '\uE027',
3272
+ NumpadDecimal: '\uE028',
3273
+ NumpadDivide: '\uE029',
3274
+ NumpadEnter: '\uE007',
3275
+ NumpadInsert: '\uE05C', // 'Numpad0' alternate (when NumLock off)
3276
+ NumpadDelete: '\uE05D', // 'NumpadDecimal' alternate (when NumLock off)
3277
+ NumpadEnd: '\uE056', // 'Numpad1' alternate (when NumLock off)
3278
+ NumpadHome: '\uE057', // 'Numpad7' alternate (when NumLock off)
3279
+ NumpadPageDown: '\uE055', // 'Numpad3' alternate (when NumLock off)
3280
+ NumpadPageUp: '\uE054', // 'Numpad9' alternate (when NumLock off)
3281
+ NumpadArrowDown: '\uE05B', // 'Numpad2' alternate (when NumLock off)
3282
+ NumpadArrowLeft: '\uE058', // 'Numpad4' alternate (when NumLock off)
3283
+ NumpadArrowRight: '\uE05A', // 'Numpad6' alternate (when NumLock off)
3284
+ NumpadArrowUp: '\uE059', // 'Numpad8' alternate (when NumLock off)
3285
+ Comma: '\uE026', // ',' alias
3286
+ Digit0: '0', // '0' alias
3287
+ Digit1: '1', // '1' alias
3288
+ Digit2: '2', // '2' alias
3289
+ Digit3: '3', // '3' alias
3290
+ Digit4: '4', // '4' alias
3291
+ Digit5: '5', // '5' alias
3292
+ Digit6: '6', // '6' alias
3293
+ Digit7: '7', // '7' alias
3294
+ Digit8: '8', // '8' alias
3295
+ Digit9: '9', // '9' alias
3296
+ Equal: '\uE019', // '=' alias
3297
+ KeyA: 'a', // 'a' alias
3298
+ KeyB: 'b', // 'b' alias
3299
+ KeyC: 'c', // 'c' alias
3300
+ KeyD: 'd', // 'd' alias
3301
+ KeyE: 'e', // 'e' alias
3302
+ KeyF: 'f', // 'f' alias
3303
+ KeyG: 'g', // 'g' alias
3304
+ KeyH: 'h', // 'h' alias
3305
+ KeyI: 'i', // 'i' alias
3306
+ KeyJ: 'j', // 'j' alias
3307
+ KeyK: 'k', // 'k' alias
3308
+ KeyL: 'l', // 'l' alias
3309
+ KeyM: 'm', // 'm' alias
3310
+ KeyN: 'n', // 'n' alias
3311
+ KeyO: 'o', // 'o' alias
3312
+ KeyP: 'p', // 'p' alias
3313
+ KeyQ: 'q', // 'q' alias
3314
+ KeyR: 'r', // 'r' alias
3315
+ KeyS: 's', // 's' alias
3316
+ KeyT: 't', // 't' alias
3317
+ KeyU: 'u', // 'u' alias
3318
+ KeyV: 'v', // 'v' alias
3319
+ KeyW: 'w', // 'w' alias
3320
+ KeyX: 'x', // 'x' alias
3321
+ KeyY: 'y', // 'y' alias
3322
+ KeyZ: 'z', // 'z' alias
3323
+ Period: '.', // '.' alias
3324
+ Semicolon: '\uE018', // ';' alias
3325
+ Slash: '/', // '/' alias
3326
+ ZenkakuHankaku: '\uE040',
3327
+ }
3192
3328
 
3193
3329
  function convertKeyToRawKey(key) {
3194
3330
  if (Object.prototype.hasOwnProperty.call(keyUnicodeMap, key)) {
3195
- return keyUnicodeMap[key];
3331
+ return keyUnicodeMap[key]
3196
3332
  }
3197
3333
  // Key is raw key when no representative unicode code point for value
3198
- return key;
3334
+ return key
3199
3335
  }
3200
3336
 
3201
3337
  function getNormalizedKey(key) {
3202
- let normalizedKey = getNormalizedKeyAttributeValue(key);
3338
+ let normalizedKey = getNormalizedKeyAttributeValue(key)
3203
3339
  // Always use "left" modifier keys for non-W3C sessions,
3204
3340
  // as JsonWireProtocol does not support "right" modifier keys
3205
3341
  if (!this.browser.isW3C) {
3206
- normalizedKey = normalizedKey.replace(/^(Alt|Control|Meta|Shift)Right$/, '$1');
3342
+ normalizedKey = normalizedKey.replace(/^(Alt|Control|Meta|Shift)Right$/, '$1')
3207
3343
  }
3208
3344
  if (key !== normalizedKey) {
3209
- this.debugSection('Input', `Mapping key '${key}' to '${normalizedKey}'`);
3345
+ this.debugSection('Input', `Mapping key '${key}' to '${normalizedKey}'`)
3210
3346
  }
3211
- return convertKeyToRawKey(normalizedKey);
3347
+ return convertKeyToRawKey(normalizedKey)
3212
3348
  }
3213
3349
 
3214
- const unicodeModifierKeys = modifierKeys.map(k => convertKeyToRawKey(k));
3350
+ const unicodeModifierKeys = modifierKeys.map(k => convertKeyToRawKey(k))
3215
3351
  function isModifierKey(key) {
3216
- return unicodeModifierKeys.includes(key);
3352
+ return unicodeModifierKeys.includes(key)
3217
3353
  }
3218
3354
 
3219
3355
  function highlightActiveElement(element) {
3220
3356
  if (this.options.highlightElement && global.debugMode) {
3221
- highlightElement(element, this.browser);
3357
+ highlightElement(element, this.browser)
3222
3358
  }
3223
3359
  }
3224
3360
 
3225
3361
  function prepareLocateFn(context) {
3226
- if (!context) return this._locate.bind(this);
3227
- return (l) => {
3228
- l = new Locator(l, 'css');
3229
- return this._locate(context, true).then(async (res) => {
3230
- assertElementExists(res, context, 'Context element');
3362
+ if (!context) return this._locate.bind(this)
3363
+ return l => {
3364
+ l = new Locator(l, 'css')
3365
+ return this._locate(context, true).then(async res => {
3366
+ assertElementExists(res, context, 'Context element')
3231
3367
  if (l.react) {
3232
- return res[0].react$$(l.react, l.props || undefined);
3368
+ return res[0].react$$(l.react, l.props || undefined)
3233
3369
  }
3234
- return res[0].$$(l.simplify());
3235
- });
3236
- };
3370
+ return res[0].$$(l.simplify())
3371
+ })
3372
+ }
3373
+ }
3374
+
3375
+ function logEvents(event) {
3376
+ browserLogs.push(event.text) // add log message to the array
3237
3377
  }
3238
3378
 
3239
- export default WebDriver;
3379
+ export { WebDriver as default }