codeceptjs 4.0.0-beta.4 → 4.0.0-beta.6.esm-aria

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 (188) hide show
  1. package/README.md +89 -119
  2. package/bin/codecept.js +53 -54
  3. package/docs/webapi/clearCookie.mustache +1 -1
  4. package/lib/actor.js +70 -102
  5. package/lib/ai.js +131 -121
  6. package/lib/assert/empty.js +11 -12
  7. package/lib/assert/equal.js +16 -21
  8. package/lib/assert/error.js +2 -2
  9. package/lib/assert/include.js +11 -15
  10. package/lib/assert/throws.js +3 -5
  11. package/lib/assert/truth.js +10 -7
  12. package/lib/assert.js +18 -18
  13. package/lib/codecept.js +112 -101
  14. package/lib/colorUtils.js +48 -50
  15. package/lib/command/check.js +206 -0
  16. package/lib/command/configMigrate.js +13 -14
  17. package/lib/command/definitions.js +24 -36
  18. package/lib/command/dryRun.js +16 -16
  19. package/lib/command/generate.js +38 -39
  20. package/lib/command/gherkin/init.js +36 -38
  21. package/lib/command/gherkin/snippets.js +76 -74
  22. package/lib/command/gherkin/steps.js +21 -18
  23. package/lib/command/info.js +49 -15
  24. package/lib/command/init.js +41 -37
  25. package/lib/command/interactive.js +22 -13
  26. package/lib/command/list.js +11 -10
  27. package/lib/command/run-multiple/chunk.js +50 -47
  28. package/lib/command/run-multiple/collection.js +5 -5
  29. package/lib/command/run-multiple/run.js +3 -3
  30. package/lib/command/run-multiple.js +27 -47
  31. package/lib/command/run-rerun.js +6 -7
  32. package/lib/command/run-workers.js +15 -66
  33. package/lib/command/run.js +8 -8
  34. package/lib/command/utils.js +22 -21
  35. package/lib/command/workers/runTests.js +131 -241
  36. package/lib/config.js +111 -49
  37. package/lib/container.js +589 -244
  38. package/lib/data/context.js +16 -18
  39. package/lib/data/dataScenarioConfig.js +9 -9
  40. package/lib/data/dataTableArgument.js +7 -7
  41. package/lib/data/table.js +6 -12
  42. package/lib/effects.js +307 -0
  43. package/lib/els.js +160 -0
  44. package/lib/event.js +24 -19
  45. package/lib/globals.js +141 -0
  46. package/lib/heal.js +89 -81
  47. package/lib/helper/AI.js +3 -2
  48. package/lib/helper/ApiDataFactory.js +19 -19
  49. package/lib/helper/Appium.js +47 -51
  50. package/lib/helper/FileSystem.js +35 -15
  51. package/lib/helper/GraphQL.js +1 -1
  52. package/lib/helper/GraphQLDataFactory.js +4 -4
  53. package/lib/helper/JSONResponse.js +72 -45
  54. package/lib/helper/Mochawesome.js +14 -11
  55. package/lib/helper/Playwright.js +832 -434
  56. package/lib/helper/Puppeteer.js +393 -292
  57. package/lib/helper/REST.js +32 -27
  58. package/lib/helper/WebDriver.js +320 -219
  59. package/lib/helper/errors/ConnectionRefused.js +6 -6
  60. package/lib/helper/errors/ElementAssertion.js +11 -16
  61. package/lib/helper/errors/ElementNotFound.js +5 -9
  62. package/lib/helper/errors/RemoteBrowserConnectionRefused.js +5 -5
  63. package/lib/helper/extras/Console.js +11 -11
  64. package/lib/helper/extras/PlaywrightLocator.js +110 -0
  65. package/lib/helper/extras/PlaywrightPropEngine.js +18 -18
  66. package/lib/helper/extras/PlaywrightRestartOpts.js +23 -23
  67. package/lib/helper/extras/Popup.js +22 -22
  68. package/lib/helper/extras/React.js +29 -30
  69. package/lib/helper/network/actions.js +33 -48
  70. package/lib/helper/network/utils.js +76 -83
  71. package/lib/helper/scripts/blurElement.js +6 -6
  72. package/lib/helper/scripts/focusElement.js +6 -6
  73. package/lib/helper/scripts/highlightElement.js +9 -9
  74. package/lib/helper/scripts/isElementClickable.js +34 -34
  75. package/lib/helper.js +2 -1
  76. package/lib/history.js +23 -20
  77. package/lib/hooks.js +10 -10
  78. package/lib/html.js +90 -100
  79. package/lib/index.js +48 -21
  80. package/lib/listener/config.js +8 -9
  81. package/lib/listener/emptyRun.js +54 -0
  82. package/lib/listener/exit.js +10 -12
  83. package/lib/listener/{retry.js → globalRetry.js} +10 -10
  84. package/lib/listener/globalTimeout.js +166 -0
  85. package/lib/listener/helpers.js +43 -24
  86. package/lib/listener/mocha.js +4 -5
  87. package/lib/listener/result.js +11 -0
  88. package/lib/listener/steps.js +26 -23
  89. package/lib/listener/store.js +20 -0
  90. package/lib/locator.js +213 -192
  91. package/lib/mocha/asyncWrapper.js +264 -0
  92. package/lib/mocha/bdd.js +167 -0
  93. package/lib/mocha/cli.js +341 -0
  94. package/lib/mocha/factory.js +160 -0
  95. package/lib/{interfaces → mocha}/featureConfig.js +33 -13
  96. package/lib/{interfaces → mocha}/gherkin.js +75 -45
  97. package/lib/mocha/hooks.js +121 -0
  98. package/lib/mocha/index.js +21 -0
  99. package/lib/mocha/inject.js +46 -0
  100. package/lib/{interfaces → mocha}/scenarioConfig.js +32 -8
  101. package/lib/mocha/suite.js +89 -0
  102. package/lib/mocha/test.js +178 -0
  103. package/lib/mocha/types.d.ts +42 -0
  104. package/lib/mocha/ui.js +229 -0
  105. package/lib/output.js +86 -64
  106. package/lib/parser.js +44 -44
  107. package/lib/pause.js +160 -139
  108. package/lib/plugin/analyze.js +403 -0
  109. package/lib/plugin/{autoLogin.js → auth.js} +137 -43
  110. package/lib/plugin/autoDelay.js +19 -15
  111. package/lib/plugin/coverage.js +22 -27
  112. package/lib/plugin/customLocator.js +5 -5
  113. package/lib/plugin/customReporter.js +53 -0
  114. package/lib/plugin/heal.js +49 -17
  115. package/lib/plugin/pageInfo.js +140 -0
  116. package/lib/plugin/pauseOnFail.js +4 -3
  117. package/lib/plugin/retryFailedStep.js +60 -19
  118. package/lib/plugin/screenshotOnFail.js +80 -83
  119. package/lib/plugin/stepByStepReport.js +70 -31
  120. package/lib/plugin/stepTimeout.js +7 -13
  121. package/lib/plugin/subtitles.js +10 -9
  122. package/lib/recorder.js +167 -126
  123. package/lib/rerun.js +94 -50
  124. package/lib/result.js +161 -0
  125. package/lib/secret.js +18 -17
  126. package/lib/session.js +95 -89
  127. package/lib/step/base.js +239 -0
  128. package/lib/step/comment.js +10 -0
  129. package/lib/step/config.js +50 -0
  130. package/lib/step/func.js +46 -0
  131. package/lib/step/helper.js +50 -0
  132. package/lib/step/meta.js +99 -0
  133. package/lib/step/record.js +74 -0
  134. package/lib/step/retry.js +11 -0
  135. package/lib/step/section.js +55 -0
  136. package/lib/step.js +18 -332
  137. package/lib/steps.js +54 -0
  138. package/lib/store.js +37 -5
  139. package/lib/template/heal.js +2 -11
  140. package/lib/timeout.js +60 -0
  141. package/lib/transform.js +8 -8
  142. package/lib/translation.js +32 -18
  143. package/lib/utils.js +354 -250
  144. package/lib/workerStorage.js +16 -16
  145. package/lib/workers.js +366 -282
  146. package/package.json +107 -95
  147. package/translations/de-DE.js +5 -4
  148. package/translations/fr-FR.js +5 -4
  149. package/translations/index.js +23 -9
  150. package/translations/it-IT.js +5 -4
  151. package/translations/ja-JP.js +5 -4
  152. package/translations/nl-NL.js +76 -0
  153. package/translations/pl-PL.js +5 -4
  154. package/translations/pt-BR.js +5 -4
  155. package/translations/ru-RU.js +5 -4
  156. package/translations/utils.js +18 -0
  157. package/translations/zh-CN.js +5 -4
  158. package/translations/zh-TW.js +5 -4
  159. package/typings/index.d.ts +177 -186
  160. package/typings/promiseBasedTypes.d.ts +3573 -5941
  161. package/typings/types.d.ts +4042 -6370
  162. package/lib/cli.js +0 -256
  163. package/lib/helper/ExpectHelper.js +0 -391
  164. package/lib/helper/Nightmare.js +0 -1504
  165. package/lib/helper/Protractor.js +0 -1863
  166. package/lib/helper/SoftExpectHelper.js +0 -381
  167. package/lib/helper/TestCafe.js +0 -1414
  168. package/lib/helper/clientscripts/nightmare.js +0 -213
  169. package/lib/helper/extras/PlaywrightReactVueLocator.js +0 -43
  170. package/lib/helper/testcafe/testControllerHolder.js +0 -42
  171. package/lib/helper/testcafe/testcafe-utils.js +0 -62
  172. package/lib/interfaces/bdd.js +0 -81
  173. package/lib/listener/artifacts.js +0 -19
  174. package/lib/listener/timeout.js +0 -109
  175. package/lib/mochaFactory.js +0 -113
  176. package/lib/plugin/allure.js +0 -15
  177. package/lib/plugin/commentStep.js +0 -136
  178. package/lib/plugin/debugErrors.js +0 -67
  179. package/lib/plugin/eachElement.js +0 -127
  180. package/lib/plugin/fakerTransform.js +0 -49
  181. package/lib/plugin/retryTo.js +0 -127
  182. package/lib/plugin/selenoid.js +0 -384
  183. package/lib/plugin/standardActingHelpers.js +0 -3
  184. package/lib/plugin/tryTo.js +0 -115
  185. package/lib/plugin/wdio.js +0 -249
  186. package/lib/scenario.js +0 -224
  187. package/lib/ui.js +0 -236
  188. package/lib/within.js +0 -70
@@ -1,48 +1,44 @@
1
1
  let webdriverio
2
2
 
3
- const assert = require('assert')
4
- const path = require('path')
5
-
6
- const Helper = require('@codeceptjs/helper')
7
- const promiseRetry = require('promise-retry')
8
- const stringIncludes = require('../assert/include').includes
9
- const { urlEquals, equals } = require('../assert/equal')
10
- const { debug } = require('../output')
11
- const { empty } = require('../assert/empty')
12
- const { truth } = require('../assert/truth')
13
- const {
14
- xpathLocator,
15
- fileExists,
16
- decodeUrl,
17
- chunkArray,
18
- convertCssPropertiesToCamelCase,
19
- screenshotOutputFolder,
20
- getNormalizedKeyAttributeValue,
21
- modifierKeys,
22
- } = require('../utils')
23
- const { isColorProperty, convertColorToRGBA } = require('../colorUtils')
24
- const ElementNotFound = require('./errors/ElementNotFound')
25
- const ConnectionRefused = require('./errors/ConnectionRefused')
26
- const Locator = require('../locator')
27
- const { highlightElement } = require('./scripts/highlightElement')
28
- const { focusElement } = require('./scripts/focusElement')
29
- const { blurElement } = require('./scripts/blurElement')
30
- const {
31
- dontSeeElementError,
32
- seeElementError,
33
- seeElementInDOMError,
34
- dontSeeElementInDOMError,
35
- } = require('./errors/ElementAssertion')
36
- const {
37
- dontSeeTraffic,
38
- seeTraffic,
39
- grabRecordedNetworkTraffics,
40
- stopRecordingTraffic,
41
- flushNetworkTraffics,
42
- } = require('./network/actions')
3
+ import assert from 'assert'
4
+ import path from 'path'
5
+ import crypto from 'crypto'
6
+ import Helper from '@codeceptjs/helper'
7
+ import promiseRetry from 'promise-retry'
8
+ import { includes as stringIncludes } from '../assert/include.js'
9
+ import { urlEquals, equals } from '../assert/equal.js'
10
+ import store from '../store.js'
11
+ import output from '../output.js'
12
+ const { debug } = output
13
+ import { empty } from '../assert/empty.js'
14
+ import { truth } from '../assert/truth.js'
15
+ import { xpathLocator, fileExists, decodeUrl, chunkArray, convertCssPropertiesToCamelCase, screenshotOutputFolder, getNormalizedKeyAttributeValue, modifierKeys } from '../utils.js'
16
+ import { isColorProperty, convertColorToRGBA } from '../colorUtils.js'
17
+ import ElementNotFound from './errors/ElementNotFound.js'
18
+ import ConnectionRefused from './errors/ConnectionRefused.js'
19
+ import Locator from '../locator.js'
20
+ import { highlightElement } from './scripts/highlightElement.js'
21
+ import { focusElement } from './scripts/focusElement.js'
22
+ import { blurElement } from './scripts/blurElement.js'
23
+ import { dontSeeElementError, seeElementError, seeElementInDOMError, dontSeeElementInDOMError } from './errors/ElementAssertion.js'
24
+ import { dontSeeTraffic, seeTraffic, grabRecordedNetworkTraffics, stopRecordingTraffic, flushNetworkTraffics } from './network/actions.js'
43
25
 
44
26
  const SHADOW = 'shadow'
45
27
  const webRoot = 'body'
28
+ let browserLogs = []
29
+
30
+ /**
31
+ * Wraps error objects that don't have a proper message property
32
+ * This is needed for ESM compatibility with WebdriverIO error handling
33
+ */
34
+ function wrapError(e) {
35
+ if (e && typeof e === 'object' && !e.message) {
36
+ const err = new Error(e.error || e.timeoutMsg || String(e))
37
+ err.stack = e.stack
38
+ return err
39
+ }
40
+ return e
41
+ }
46
42
 
47
43
  /**
48
44
  * ## Configuration
@@ -53,6 +49,7 @@ const webRoot = 'body'
53
49
  * @type {object}
54
50
  * @prop {string} url - base url of website to be tested.
55
51
  * @prop {string} browser - Browser in which to perform testing.
52
+ * @prop {boolean} [bidiProtocol=false] - WebDriver Bidi Protocol. Default: false. More info: https://webdriver.io/docs/api/webdriverBidi/
56
53
  * @prop {string} [basicAuth] - (optional) the basic authentication to pass to base url. Example: {username: 'username', password: 'password'}
57
54
  * @prop {string} [host=localhost] - WebDriver host to connect.
58
55
  * @prop {number} [port=4444] - WebDriver port to connect.
@@ -444,7 +441,7 @@ const config = {}
444
441
  class WebDriver extends Helper {
445
442
  constructor(config) {
446
443
  super(config)
447
- webdriverio = require('webdriverio')
444
+ // webdriverio will be loaded dynamically in _init method
448
445
 
449
446
  // set defaults
450
447
  this.root = webRoot
@@ -506,6 +503,10 @@ class WebDriver extends Helper {
506
503
  config.capabilities = config.desiredCapabilities
507
504
  }
508
505
  config.capabilities.browserName = config.browser || config.capabilities.browserName
506
+
507
+ // WebDriver Bidi Protocol. Default: false
508
+ config.capabilities.webSocketUrl = config.bidiProtocol ?? config.capabilities.webSocketUrl ?? true
509
+
509
510
  config.capabilities.browserVersion = config.browserVersion || config.capabilities.browserVersion
510
511
  if (config.capabilities.chromeOptions) {
511
512
  config.capabilities['goog:chromeOptions'] = config.capabilities.chromeOptions
@@ -546,12 +547,26 @@ class WebDriver extends Helper {
546
547
 
547
548
  static _checkRequirements() {
548
549
  try {
549
- require('webdriverio')
550
+ // In ESM, webdriverio will be checked via dynamic import in _init
551
+ // The import will fail at module load time if webdriverio is missing
552
+ return null
550
553
  } catch (e) {
551
554
  return ['webdriverio@^6.12.1']
552
555
  }
553
556
  }
554
557
 
558
+ async _init() {
559
+ // Load webdriverio dynamically
560
+ if (!webdriverio) {
561
+ try {
562
+ webdriverio = await import('webdriverio')
563
+ webdriverio = webdriverio.default || webdriverio
564
+ } catch (e) {
565
+ throw new Error('webdriverio could not be loaded. Please install webdriverio.')
566
+ }
567
+ }
568
+ }
569
+
555
570
  static _config() {
556
571
  return [
557
572
  {
@@ -595,10 +610,7 @@ class WebDriver extends Helper {
595
610
  }
596
611
 
597
612
  async _res(locator) {
598
- const res =
599
- this._isShadowLocator(locator) || this._isCustomLocator(locator)
600
- ? await this._locate(locator)
601
- : await this.$$(withStrictLocator(locator))
613
+ const res = this._isShadowLocator(locator) || this._isCustomLocator(locator) ? await this._locate(locator) : await this.$$(withStrictLocator(locator))
602
614
  return res
603
615
  }
604
616
 
@@ -631,7 +643,7 @@ class WebDriver extends Helper {
631
643
  this.$$ = this.browser.$$.bind(this.browser)
632
644
 
633
645
  if (this._isCustomLocatorStrategyDefined()) {
634
- Object.keys(this.customLocatorStrategies).forEach(async (customLocator) => {
646
+ Object.keys(this.customLocatorStrategies).forEach(async customLocator => {
635
647
  this.debugSection('Weddriver', `adding custom locator strategy: ${customLocator}`)
636
648
  const locatorFunction = this._lookupCustomLocator(customLocator)
637
649
  this.browser.addLocatorStrategy(customLocator, locatorFunction)
@@ -642,6 +654,11 @@ class WebDriver extends Helper {
642
654
  this.browser.capabilities.platformName = this.browser.capabilities.platformName.toLowerCase()
643
655
  }
644
656
 
657
+ this.browser.on('dialog', () => {})
658
+
659
+ await this.browser.sessionSubscribe({ events: ['log.entryAdded'] })
660
+ this.browser.on('log.entryAdded', logEvents)
661
+
645
662
  return this.browser
646
663
  }
647
664
 
@@ -654,6 +671,7 @@ class WebDriver extends Helper {
654
671
  }
655
672
 
656
673
  async _before() {
674
+ if (!webdriverio) await this._init()
657
675
  this.context = this.root
658
676
  if (this.options.restart && !this.options.manualStart) return this._startBrowser()
659
677
  if (!this.isRunning && !this.options.manualStart) return this._startBrowser()
@@ -667,7 +685,7 @@ class WebDriver extends Helper {
667
685
  this.isRunning = false
668
686
  return this.browser.deleteSession()
669
687
  }
670
- if (this.browser.isInsideFrame) await this.browser.switchToFrame(null)
688
+ if (this.browser.isInsideFrame) await this.browser.switchFrame(null)
671
689
 
672
690
  if (this.options.keepBrowserState) return
673
691
 
@@ -675,10 +693,11 @@ class WebDriver extends Helper {
675
693
  this.debugSection('Session', 'cleaning cookies and localStorage')
676
694
  await this.browser.deleteCookies()
677
695
  }
678
- await this.browser.execute('localStorage.clear();').catch((err) => {
696
+ await this.browser.execute('localStorage.clear();').catch(err => {
679
697
  if (!(err.message.indexOf("Storage is disabled inside 'data:' URLs.") > -1)) throw err
680
698
  })
681
699
  await this.closeOtherTabs()
700
+ browserLogs = []
682
701
  return this.browser
683
702
  }
684
703
 
@@ -705,17 +724,17 @@ class WebDriver extends Helper {
705
724
 
706
725
  return browser
707
726
  },
708
- stop: async (browser) => {
727
+ stop: async browser => {
709
728
  if (!browser) return
710
729
  return browser.deleteSession()
711
730
  },
712
- loadVars: async (browser) => {
731
+ loadVars: async browser => {
713
732
  if (this.context !== this.root) throw new Error("Can't start session inside within block")
714
733
  this.browser = browser
715
734
  this.$$ = this.browser.$$.bind(this.browser)
716
735
  this.sessionWindows[this.activeSessionName] = browser
717
736
  },
718
- restoreVars: async (session) => {
737
+ restoreVars: async session => {
719
738
  if (!session) {
720
739
  this.activeSessionName = ''
721
740
  }
@@ -757,7 +776,7 @@ class WebDriver extends Helper {
757
776
  this.browser.isInsideFrame = true
758
777
  if (Array.isArray(frame)) {
759
778
  // this.switchTo(null);
760
- await forEachAsync(frame, async (f) => this.switchTo(f))
779
+ await forEachAsync(frame, async f => this.switchTo(f))
761
780
  return
762
781
  }
763
782
  await this.switchTo(frame)
@@ -835,10 +854,7 @@ class WebDriver extends Helper {
835
854
  * @param {object} locator
836
855
  */
837
856
  async _smartWait(locator) {
838
- this.debugSection(
839
- `SmartWait (${this.options.smartWait}ms)`,
840
- `Locating ${JSON.stringify(locator)} in ${this.options.smartWait}`,
841
- )
857
+ this.debugSection(`SmartWait (${this.options.smartWait}ms)`, `Locating ${JSON.stringify(locator)} in ${this.options.smartWait}`)
842
858
  await this.defineTimeout({ implicit: this.options.smartWait })
843
859
  }
844
860
 
@@ -854,7 +870,7 @@ class WebDriver extends Helper {
854
870
  * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
855
871
  */
856
872
  async _locate(locator, smartWait = false) {
857
- if (require('../store').debugMode) smartWait = false
873
+ if (store.debugMode) smartWait = false
858
874
 
859
875
  // special locator type for Shadow DOM
860
876
  if (this._isShadowLocator(locator)) {
@@ -874,6 +890,17 @@ class WebDriver extends Helper {
874
890
  return els
875
891
  }
876
892
 
893
+ // special locator type for ARIA roles
894
+ if (locator.role) {
895
+ return this._locateByRole(locator)
896
+ }
897
+
898
+ // Handle role locators passed as Locator instances
899
+ const matchedLocator = new Locator(locator)
900
+ if (matchedLocator.isRole()) {
901
+ return this._locateByRole(matchedLocator.locator)
902
+ }
903
+
877
904
  if (!this.options.smartWait || !smartWait) {
878
905
  if (this._isCustomLocator(locator)) {
879
906
  const locatorObj = new Locator(locator)
@@ -913,7 +940,7 @@ class WebDriver extends Helper {
913
940
  * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
914
941
  */
915
942
  async _locateCheckable(locator) {
916
- return findCheckable.call(this, locator, this.$$.bind(this)).then((res) => res)
943
+ return findCheckable.call(this, locator, this.$$.bind(this)).then(res => res)
917
944
  }
918
945
 
919
946
  /**
@@ -941,7 +968,35 @@ class WebDriver extends Helper {
941
968
  * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
942
969
  */
943
970
  async _locateFields(locator) {
944
- return findFields.call(this, locator).then((res) => res)
971
+ return findFields.call(this, locator).then(res => res)
972
+ }
973
+
974
+ /**
975
+ * Locate elements by ARIA role using WebdriverIO accessibility selectors
976
+ *
977
+ * @param {object} locator - role locator object { role: string, text?: string, exact?: boolean }
978
+ */
979
+ async _locateByRole(locator) {
980
+ const role = locator.role
981
+
982
+ if (!locator.text) {
983
+ return this.browser.$$(`[role="${role}"]`)
984
+ }
985
+
986
+ const elements = await this.browser.$$(`[role="${role}"]`)
987
+ const filteredElements = []
988
+ const matchFn = locator.exact === true
989
+ ? t => t === locator.text
990
+ : t => t && t.includes(locator.text)
991
+
992
+ for (const element of elements) {
993
+ const texts = await getElementTextAttributes.call(this, element)
994
+ if (texts.some(matchFn)) {
995
+ filteredElements.push(element)
996
+ }
997
+ }
998
+
999
+ return filteredElements
945
1000
  }
946
1001
 
947
1002
  /**
@@ -1025,7 +1080,7 @@ class WebDriver extends Helper {
1025
1080
  const elem = usingFirstElement(res)
1026
1081
  highlightActiveElement.call(this, elem)
1027
1082
 
1028
- return this.executeScript((el) => {
1083
+ return this.executeScript(el => {
1029
1084
  if (document.activeElement instanceof HTMLElement) {
1030
1085
  document.activeElement.blur()
1031
1086
  }
@@ -1097,7 +1152,7 @@ class WebDriver extends Helper {
1097
1152
  }
1098
1153
  const elem = usingFirstElement(res)
1099
1154
 
1100
- return this.executeScript((el) => {
1155
+ return this.executeScript(el => {
1101
1156
  if (document.activeElement instanceof HTMLElement) {
1102
1157
  document.activeElement.blur()
1103
1158
  }
@@ -1160,15 +1215,9 @@ class WebDriver extends Helper {
1160
1215
  }
1161
1216
 
1162
1217
  // select options by visible text
1163
- let els = await forEachAsync(option, async (opt) =>
1164
- this.browser.findElementsFromElement(
1165
- getElementId(elem),
1166
- 'xpath',
1167
- Locator.select.byVisibleText(xpathLocator.literal(opt)),
1168
- ),
1169
- )
1218
+ let els = await forEachAsync(option, async opt => this.browser.findElementsFromElement(getElementId(elem), 'xpath', Locator.select.byVisibleText(xpathLocator.literal(opt))))
1170
1219
 
1171
- const clickOptionFn = async (el) => {
1220
+ const clickOptionFn = async el => {
1172
1221
  if (el[0]) el = el[0]
1173
1222
  const elementId = getElementId(el)
1174
1223
  if (elementId) return this.browser.elementClick(elementId)
@@ -1178,19 +1227,9 @@ class WebDriver extends Helper {
1178
1227
  return forEachAsync(els, clickOptionFn)
1179
1228
  }
1180
1229
  // select options by value
1181
- els = await forEachAsync(option, async (opt) =>
1182
- this.browser.findElementsFromElement(
1183
- getElementId(elem),
1184
- 'xpath',
1185
- Locator.select.byValue(xpathLocator.literal(opt)),
1186
- ),
1187
- )
1230
+ els = await forEachAsync(option, async opt => this.browser.findElementsFromElement(getElementId(elem), 'xpath', Locator.select.byValue(xpathLocator.literal(opt))))
1188
1231
  if (els.length === 0) {
1189
- throw new ElementNotFound(
1190
- select,
1191
- `Option "${option}" in`,
1192
- 'was not found neither by a visible text nor by a value',
1193
- )
1232
+ throw new ElementNotFound(select, `Option "${option}" in`, 'was not found neither by a visible text nor by a value')
1194
1233
  }
1195
1234
  return forEachAsync(els, clickOptionFn)
1196
1235
  }
@@ -1217,9 +1256,7 @@ class WebDriver extends Helper {
1217
1256
  this.debugSection('File', 'Uploading file to remote server')
1218
1257
  file = await this.browser.uploadFile(file)
1219
1258
  } catch (err) {
1220
- throw new Error(
1221
- `File can't be transferred to remote server. Set \`remoteFileUpload: false\` in config to upload file locally.\n${err.message}`,
1222
- )
1259
+ throw new Error(`File can't be transferred to remote server. Set \`remoteFileUpload: false\` in config to upload file locally.\n${err.message}`)
1223
1260
  }
1224
1261
  }
1225
1262
 
@@ -1241,7 +1278,8 @@ class WebDriver extends Helper {
1241
1278
  const elementId = getElementId(elem)
1242
1279
  highlightActiveElement.call(this, elem)
1243
1280
 
1244
- const isSelected = await this.browser.isElementSelected(elementId)
1281
+ const isSelected = await isElementChecked(this.browser, elementId)
1282
+
1245
1283
  if (isSelected) return Promise.resolve(true)
1246
1284
  return this.browser[clickMethod](elementId)
1247
1285
  }
@@ -1261,7 +1299,8 @@ class WebDriver extends Helper {
1261
1299
  const elementId = getElementId(elem)
1262
1300
  highlightActiveElement.call(this, elem)
1263
1301
 
1264
- const isSelected = await this.browser.isElementSelected(elementId)
1302
+ const isSelected = await isElementChecked(this.browser, elementId)
1303
+
1265
1304
  if (!isSelected) return Promise.resolve(true)
1266
1305
  return this.browser[clickMethod](elementId)
1267
1306
  }
@@ -1272,7 +1311,11 @@ class WebDriver extends Helper {
1272
1311
  */
1273
1312
  async grabTextFromAll(locator) {
1274
1313
  const res = await this._locate(locator, true)
1275
- const val = await forEachAsync(res, (el) => this.browser.getElementText(getElementId(el)))
1314
+ let val = []
1315
+ await forEachAsync(res, async el => {
1316
+ const text = await this.browser.getElementText(getElementId(el))
1317
+ val.push(text)
1318
+ })
1276
1319
  this.debugSection('GrabText', String(val))
1277
1320
  return val
1278
1321
  }
@@ -1297,7 +1340,7 @@ class WebDriver extends Helper {
1297
1340
  */
1298
1341
  async grabHTMLFromAll(locator) {
1299
1342
  const elems = await this._locate(locator, true)
1300
- const html = await forEachAsync(elems, (elem) => elem.getHTML(false))
1343
+ const html = await forEachAsync(elems, elem => elem.getHTML(false))
1301
1344
  this.debugSection('GrabHTML', String(html))
1302
1345
  return html
1303
1346
  }
@@ -1322,7 +1365,7 @@ class WebDriver extends Helper {
1322
1365
  */
1323
1366
  async grabValueFromAll(locator) {
1324
1367
  const res = await this._locate(locator, true)
1325
- const val = await forEachAsync(res, (el) => el.getValue())
1368
+ const val = await forEachAsync(res, el => el.getValue())
1326
1369
  this.debugSection('GrabValue', String(val))
1327
1370
 
1328
1371
  return val
@@ -1347,7 +1390,7 @@ class WebDriver extends Helper {
1347
1390
  */
1348
1391
  async grabCssPropertyFromAll(locator, cssProperty) {
1349
1392
  const res = await this._locate(locator, true)
1350
- const val = await forEachAsync(res, async (el) => this.browser.getElementCSSValue(getElementId(el), cssProperty))
1393
+ const val = await forEachAsync(res, async el => this.browser.getElementCSSValue(getElementId(el), cssProperty))
1351
1394
  this.debugSection('Grab', String(val))
1352
1395
  return val
1353
1396
  }
@@ -1371,7 +1414,7 @@ class WebDriver extends Helper {
1371
1414
  */
1372
1415
  async grabAttributeFromAll(locator, attr) {
1373
1416
  const res = await this._locate(locator, true)
1374
- const val = await forEachAsync(res, async (el) => el.getAttribute(attr))
1417
+ const val = await forEachAsync(res, async el => el.getAttribute(attr))
1375
1418
  this.debugSection('GrabAttribute', String(val))
1376
1419
  return val
1377
1420
  }
@@ -1488,7 +1531,7 @@ class WebDriver extends Helper {
1488
1531
  async seeElement(locator) {
1489
1532
  const res = await this._locate(locator, true)
1490
1533
  assertElementExists(res, locator)
1491
- const selected = await forEachAsync(res, async (el) => el.isDisplayed())
1534
+ const selected = await forEachAsync(res, async el => el.isDisplayed())
1492
1535
  try {
1493
1536
  return truth(`elements of ${new Locator(locator)}`, 'to be seen').assert(selected)
1494
1537
  } catch (e) {
@@ -1505,7 +1548,7 @@ class WebDriver extends Helper {
1505
1548
  if (!res || res.length === 0) {
1506
1549
  return truth(`elements of ${new Locator(locator)}`, 'to be seen').negate(false)
1507
1550
  }
1508
- const selected = await forEachAsync(res, async (el) => el.isDisplayed())
1551
+ const selected = await forEachAsync(res, async el => el.isDisplayed())
1509
1552
  try {
1510
1553
  return truth(`elements of ${new Locator(locator)}`, 'to be seen').negate(selected)
1511
1554
  } catch (e) {
@@ -1560,11 +1603,7 @@ class WebDriver extends Helper {
1560
1603
  * {{> grabBrowserLogs }}
1561
1604
  */
1562
1605
  async grabBrowserLogs() {
1563
- if (this.browser.isW3C) {
1564
- this.debug('Logs not available in W3C specification')
1565
- return
1566
- }
1567
- return this.browser.getLogs('browser')
1606
+ return browserLogs
1568
1607
  }
1569
1608
 
1570
1609
  /**
@@ -1590,11 +1629,7 @@ class WebDriver extends Helper {
1590
1629
  */
1591
1630
  async seeNumberOfElements(locator, num) {
1592
1631
  const res = await this._locate(locator)
1593
- return assert.equal(
1594
- res.length,
1595
- num,
1596
- `expected number of elements (${new Locator(locator)}) is ${num}, but found ${res.length}`,
1597
- )
1632
+ return assert.equal(res.length, num, `expected number of elements (${new Locator(locator)}) is ${num}, but found ${res.length}`)
1598
1633
  }
1599
1634
 
1600
1635
  /**
@@ -1603,11 +1638,7 @@ class WebDriver extends Helper {
1603
1638
  */
1604
1639
  async seeNumberOfVisibleElements(locator, num) {
1605
1640
  const res = await this.grabNumberOfVisibleElements(locator)
1606
- return assert.equal(
1607
- res,
1608
- num,
1609
- `expected number of visible elements (${new Locator(locator)}) is ${num}, but found ${res}`,
1610
- )
1641
+ return assert.equal(res, num, `expected number of visible elements (${new Locator(locator)}) is ${num}, but found ${res}`)
1611
1642
  }
1612
1643
 
1613
1644
  /**
@@ -1632,19 +1663,16 @@ class WebDriver extends Helper {
1632
1663
  }
1633
1664
  }
1634
1665
 
1635
- const values = Object.keys(cssPropertiesCamelCase).map((key) => cssPropertiesCamelCase[key])
1666
+ const values = Object.keys(cssPropertiesCamelCase).map(key => cssPropertiesCamelCase[key])
1636
1667
  if (!Array.isArray(props)) props = [props]
1637
1668
  let chunked = chunkArray(props, values.length)
1638
- chunked = chunked.filter((val) => {
1669
+ chunked = chunked.filter(val => {
1639
1670
  for (let i = 0; i < val.length; ++i) {
1640
- // eslint-disable-next-line eqeqeq
1641
1671
  if (val[i] != values[i]) return false
1642
1672
  }
1643
1673
  return true
1644
1674
  })
1645
- return equals(
1646
- `all elements (${new Locator(locator)}) to have CSS property ${JSON.stringify(cssProperties)}`,
1647
- ).assert(chunked.length, elemAmount)
1675
+ return equals(`all elements (${new Locator(locator)}) to have CSS property ${JSON.stringify(cssProperties)}`).assert(chunked.length, elemAmount)
1648
1676
  }
1649
1677
 
1650
1678
  /**
@@ -1655,28 +1683,24 @@ class WebDriver extends Helper {
1655
1683
  assertElementExists(res, locator)
1656
1684
  const elemAmount = res.length
1657
1685
 
1658
- let attrs = await forEachAsync(res, async (el) => {
1659
- return forEachAsync(Object.keys(attributes), async (attr) => el.getAttribute(attr))
1686
+ let attrs = await forEachAsync(res, async el => {
1687
+ return forEachAsync(Object.keys(attributes), async attr => el.getAttribute(attr))
1660
1688
  })
1661
1689
 
1662
- const values = Object.keys(attributes).map((key) => attributes[key])
1690
+ const values = Object.keys(attributes).map(key => attributes[key])
1663
1691
  if (!Array.isArray(attrs)) attrs = [attrs]
1664
1692
  let chunked = chunkArray(attrs, values.length)
1665
- chunked = chunked.filter((val) => {
1693
+ chunked = chunked.filter(val => {
1666
1694
  for (let i = 0; i < val.length; ++i) {
1667
1695
  const _actual = Number.isNaN(val[i]) || typeof values[i] === 'string' ? val[i] : Number.parseInt(val[i], 10)
1668
- const _expected =
1669
- Number.isNaN(values[i]) || typeof values[i] === 'string' ? values[i] : Number.parseInt(values[i], 10)
1696
+ const _expected = Number.isNaN(values[i]) || typeof values[i] === 'string' ? values[i] : Number.parseInt(values[i], 10)
1670
1697
  // the attribute could be a boolean
1671
1698
  if (typeof _actual === 'boolean') return _actual === _expected
1672
1699
  if (_actual !== _expected) return false
1673
1700
  }
1674
1701
  return true
1675
1702
  })
1676
- return assert.ok(
1677
- chunked.length === elemAmount,
1678
- `expected all elements (${new Locator(locator)}) to have attributes ${JSON.stringify(attributes)}`,
1679
- )
1703
+ return assert.ok(chunked.length === elemAmount, `expected all elements (${new Locator(locator)}) to have attributes ${JSON.stringify(attributes)}`)
1680
1704
  }
1681
1705
 
1682
1706
  /**
@@ -1685,9 +1709,9 @@ class WebDriver extends Helper {
1685
1709
  async grabNumberOfVisibleElements(locator) {
1686
1710
  const res = await this._locate(locator)
1687
1711
 
1688
- let selected = await forEachAsync(res, async (el) => el.isDisplayed())
1712
+ let selected = await forEachAsync(res, async el => el.isDisplayed())
1689
1713
  if (!Array.isArray(selected)) selected = [selected]
1690
- selected = selected.filter((val) => val === true)
1714
+ selected = selected.filter(val => val === true)
1691
1715
  return selected.length
1692
1716
  }
1693
1717
 
@@ -1805,7 +1829,7 @@ class WebDriver extends Helper {
1805
1829
  try {
1806
1830
  await elem.moveTo({ xOffset, yOffset })
1807
1831
  } catch (e) {
1808
- debug(e.message)
1832
+ output.debug(e.message)
1809
1833
  }
1810
1834
  }
1811
1835
 
@@ -1841,18 +1865,25 @@ class WebDriver extends Helper {
1841
1865
 
1842
1866
  if (browser) {
1843
1867
  this.debug(`Screenshot of ${sessionName} session has been saved to ${outputFile}`)
1844
- return browser.saveScreenshot(outputFile)
1868
+ await browser.saveScreenshot(outputFile)
1845
1869
  }
1846
1870
  }
1847
1871
  }
1848
1872
 
1849
1873
  if (!fullPage) {
1850
1874
  this.debug(`Screenshot has been saved to ${outputFile}`)
1851
- return this.browser.saveScreenshot(outputFile)
1875
+ await this.browser.saveScreenshot(outputFile)
1852
1876
  }
1853
1877
 
1854
1878
  const originalWindowSize = await this.browser.getWindowSize()
1855
1879
 
1880
+ // this case running on device, so we could not set the windowSize
1881
+ if (this.browser.isMobile) {
1882
+ this.debug(`Screenshot has been saved to ${outputFile}, size: ${originalWindowSize.width}x${originalWindowSize.height}`)
1883
+ const buffer = await this.browser.saveScreenshot(outputFile)
1884
+ return buffer
1885
+ }
1886
+
1856
1887
  let { width, height } = await this.browser
1857
1888
  .execute(function () {
1858
1889
  return {
@@ -1860,7 +1891,7 @@ class WebDriver extends Helper {
1860
1891
  width: document.body.scrollWidth,
1861
1892
  }
1862
1893
  })
1863
- .then((res) => res)
1894
+ .then(res => res)
1864
1895
 
1865
1896
  if (height < 100) height = 500 // errors for very small height
1866
1897
 
@@ -1928,7 +1959,7 @@ class WebDriver extends Helper {
1928
1959
 
1929
1960
  return promiseRetry(
1930
1961
  async (retry, number) => {
1931
- const _grabCookie = async (name) => {
1962
+ const _grabCookie = async name => {
1932
1963
  const cookie = await this.browser.getCookies([name])
1933
1964
  if (cookie.length === 0) throw Error(`Cookie ${name} is not found after ${retries}s`)
1934
1965
  }
@@ -1952,11 +1983,10 @@ class WebDriver extends Helper {
1952
1983
  * libraries](http://jster.net/category/windows-modals-popups).
1953
1984
  */
1954
1985
  async acceptPopup() {
1955
- return this.browser.getAlertText().then((res) => {
1956
- if (res !== null) {
1957
- return this.browser.acceptAlert()
1958
- }
1959
- })
1986
+ const text = await this.browser.getAlertText()
1987
+ if (text) {
1988
+ return await this.browser.acceptAlert()
1989
+ }
1960
1990
  }
1961
1991
 
1962
1992
  /**
@@ -1964,11 +1994,10 @@ class WebDriver extends Helper {
1964
1994
  *
1965
1995
  */
1966
1996
  async cancelPopup() {
1967
- return this.browser.getAlertText().then((res) => {
1968
- if (res !== null) {
1969
- return this.browser.dismissAlert()
1970
- }
1971
- })
1997
+ const text = await this.browser.getAlertText()
1998
+ if (text) {
1999
+ return await this.browser.dismissAlert()
2000
+ }
1972
2001
  }
1973
2002
 
1974
2003
  /**
@@ -1978,7 +2007,7 @@ class WebDriver extends Helper {
1978
2007
  * @param {string} text value to check.
1979
2008
  */
1980
2009
  async seeInPopup(text) {
1981
- return this.browser.getAlertText().then((res) => {
2010
+ return await this.browser.getAlertText().then(res => {
1982
2011
  if (res === null) {
1983
2012
  throw new Error('Popup is not opened')
1984
2013
  }
@@ -2259,9 +2288,9 @@ class WebDriver extends Helper {
2259
2288
  async closeOtherTabs() {
2260
2289
  const handles = await this.browser.getWindowHandles()
2261
2290
  const currentHandle = await this.browser.getWindowHandle()
2262
- const otherHandles = handles.filter((handle) => handle !== currentHandle)
2291
+ const otherHandles = handles.filter(handle => handle !== currentHandle)
2263
2292
 
2264
- await forEachAsync(otherHandles, async (handle) => {
2293
+ await forEachAsync(otherHandles, async handle => {
2265
2294
  await this.browser.switchToWindow(handle)
2266
2295
  await this.browser.closeWindow()
2267
2296
  })
@@ -2272,7 +2301,7 @@ class WebDriver extends Helper {
2272
2301
  * {{> wait }}
2273
2302
  */
2274
2303
  async wait(sec) {
2275
- return new Promise((resolve) => {
2304
+ return new Promise(resolve => {
2276
2305
  setTimeout(resolve, sec * 1000)
2277
2306
  })
2278
2307
  }
@@ -2289,9 +2318,9 @@ class WebDriver extends Helper {
2289
2318
  if (!res || res.length === 0) {
2290
2319
  return false
2291
2320
  }
2292
- const selected = await forEachAsync(res, async (el) => this.browser.isElementEnabled(getElementId(el)))
2321
+ const selected = await forEachAsync(res, async el => this.browser.isElementEnabled(getElementId(el)))
2293
2322
  if (Array.isArray(selected)) {
2294
- return selected.filter((val) => val === true).length > 0
2323
+ return selected.filter(val => val === true).length > 0
2295
2324
  }
2296
2325
  return selected
2297
2326
  },
@@ -2329,10 +2358,14 @@ class WebDriver extends Helper {
2329
2358
  res = usingFirstElement(res)
2330
2359
  assertElementExists(res, locator)
2331
2360
 
2332
- return res.waitForClickable({
2333
- timeout: waitTimeout * 1000,
2334
- timeoutMsg: `element ${res.selector} still not clickable after ${waitTimeout} sec`,
2335
- })
2361
+ return res
2362
+ .waitForClickable({
2363
+ timeout: waitTimeout * 1000,
2364
+ timeoutMsg: `element ${res.selector} still not clickable after ${waitTimeout} sec`,
2365
+ })
2366
+ .catch(e => {
2367
+ throw wrapError(e)
2368
+ })
2336
2369
  }
2337
2370
 
2338
2371
  /**
@@ -2346,14 +2379,15 @@ class WebDriver extends Helper {
2346
2379
  return client
2347
2380
  .waitUntil(
2348
2381
  function () {
2349
- return this.getUrl().then((res) => {
2382
+ return this.getUrl().then(res => {
2350
2383
  currUrl = decodeUrl(res)
2351
2384
  return currUrl.indexOf(urlPart) > -1
2352
2385
  })
2353
2386
  },
2354
2387
  { timeout: aSec * 1000 },
2355
2388
  )
2356
- .catch((e) => {
2389
+ .catch(e => {
2390
+ e = wrapError(e)
2357
2391
  if (e.message.indexOf('timeout')) {
2358
2392
  throw new Error(`expected url to include ${urlPart}, but found ${currUrl}`)
2359
2393
  }
@@ -2373,12 +2407,13 @@ class WebDriver extends Helper {
2373
2407
  let currUrl = ''
2374
2408
  return this.browser
2375
2409
  .waitUntil(function () {
2376
- return this.getUrl().then((res) => {
2410
+ return this.getUrl().then(res => {
2377
2411
  currUrl = decodeUrl(res)
2378
2412
  return currUrl === urlPart
2379
2413
  })
2380
2414
  }, aSec * 1000)
2381
- .catch((e) => {
2415
+ .catch(e => {
2416
+ e = wrapError(e)
2382
2417
  if (e.message.indexOf('timeout')) {
2383
2418
  throw new Error(`expected url to be ${urlPart}, but found ${currUrl}`)
2384
2419
  }
@@ -2398,9 +2433,9 @@ class WebDriver extends Helper {
2398
2433
  async () => {
2399
2434
  const res = await this.$$(withStrictLocator.call(this, _context))
2400
2435
  if (!res || res.length === 0) return false
2401
- const selected = await forEachAsync(res, async (el) => this.browser.getElementText(getElementId(el)))
2436
+ const selected = await forEachAsync(res, async el => this.browser.getElementText(getElementId(el)))
2402
2437
  if (Array.isArray(selected)) {
2403
- return selected.filter((part) => part.indexOf(text) >= 0).length > 0
2438
+ return selected.filter(part => part.indexOf(text) >= 0).length > 0
2404
2439
  }
2405
2440
  return selected.indexOf(text) >= 0
2406
2441
  },
@@ -2422,9 +2457,9 @@ class WebDriver extends Helper {
2422
2457
  async () => {
2423
2458
  const res = await findFields.call(this, field)
2424
2459
  if (!res || res.length === 0) return false
2425
- const selected = await forEachAsync(res, async (el) => el.getValue())
2460
+ const selected = await forEachAsync(res, async el => el.getValue())
2426
2461
  if (Array.isArray(selected)) {
2427
- return selected.filter((part) => part.indexOf(value) >= 0).length > 0
2462
+ return selected.filter(part => part.indexOf(value) >= 0).length > 0
2428
2463
  }
2429
2464
  return selected.indexOf(value) >= 0
2430
2465
  },
@@ -2446,9 +2481,9 @@ class WebDriver extends Helper {
2446
2481
  async () => {
2447
2482
  const res = await this._res(locator)
2448
2483
  if (!res || res.length === 0) return false
2449
- const selected = await forEachAsync(res, async (el) => el.isDisplayed())
2484
+ const selected = await forEachAsync(res, async el => el.isDisplayed())
2450
2485
  if (Array.isArray(selected)) {
2451
- return selected.filter((val) => val === true).length > 0
2486
+ return selected.filter(val => val === true).length > 0
2452
2487
  }
2453
2488
  return selected
2454
2489
  },
@@ -2465,21 +2500,25 @@ class WebDriver extends Helper {
2465
2500
  async waitNumberOfVisibleElements(locator, num, sec = null) {
2466
2501
  const aSec = sec || this.options.waitForTimeoutInSeconds
2467
2502
 
2468
- return this.browser.waitUntil(
2469
- async () => {
2470
- const res = await this._res(locator)
2471
- if (!res || res.length === 0) return false
2472
- let selected = await forEachAsync(res, async (el) => el.isDisplayed())
2473
-
2474
- if (!Array.isArray(selected)) selected = [selected]
2475
- selected = selected.filter((val) => val === true)
2476
- return selected.length === num
2477
- },
2478
- {
2479
- timeout: aSec * 1000,
2480
- timeoutMsg: `The number of elements (${new Locator(locator)}) is not ${num} after ${aSec} sec`,
2481
- },
2482
- )
2503
+ return this.browser
2504
+ .waitUntil(
2505
+ async () => {
2506
+ const res = await this._res(locator)
2507
+ if (!res || res.length === 0) return false
2508
+ let selected = await forEachAsync(res, async el => el.isDisplayed())
2509
+
2510
+ if (!Array.isArray(selected)) selected = [selected]
2511
+ selected = selected.filter(val => val === true)
2512
+ return selected.length === num
2513
+ },
2514
+ {
2515
+ timeout: aSec * 1000,
2516
+ timeoutMsg: `The number of elements (${new Locator(locator)}) is not ${num} after ${aSec} sec`,
2517
+ },
2518
+ )
2519
+ .catch(e => {
2520
+ throw wrapError(e)
2521
+ })
2483
2522
  }
2484
2523
 
2485
2524
  /**
@@ -2492,7 +2531,7 @@ class WebDriver extends Helper {
2492
2531
  async () => {
2493
2532
  const res = await this._res(locator)
2494
2533
  if (!res || res.length === 0) return true
2495
- const selected = await forEachAsync(res, async (el) => el.isDisplayed())
2534
+ const selected = await forEachAsync(res, async el => el.isDisplayed())
2496
2535
  return !selected.length
2497
2536
  },
2498
2537
  { timeout: aSec * 1000, timeoutMsg: `element (${new Locator(locator)}) still visible after ${aSec} sec` },
@@ -2568,17 +2607,14 @@ class WebDriver extends Helper {
2568
2607
  */
2569
2608
  async switchTo(locator) {
2570
2609
  this.browser.isInsideFrame = true
2571
- if (Number.isInteger(locator)) {
2572
- return this.browser.switchToFrame(locator)
2573
- }
2574
2610
  if (!locator) {
2575
- return this.browser.switchToFrame(null)
2611
+ return this.browser.switchFrame(null)
2576
2612
  }
2577
2613
 
2578
2614
  let res = await this._locate(locator, true)
2579
2615
  assertElementExists(res, locator)
2580
2616
  res = usingFirstElement(res)
2581
- return this.browser.switchToFrame(res)
2617
+ return this.browser.switchFrame(res)
2582
2618
  }
2583
2619
 
2584
2620
  /**
@@ -2591,7 +2627,7 @@ class WebDriver extends Helper {
2591
2627
 
2592
2628
  await this.browser.waitUntil(
2593
2629
  async () => {
2594
- await this.browser.getWindowHandles().then((handles) => {
2630
+ await this.browser.getWindowHandles().then(handles => {
2595
2631
  if (handles.indexOf(current) + num + 1 <= handles.length) {
2596
2632
  target = handles[handles.indexOf(current) + num]
2597
2633
  }
@@ -2613,7 +2649,7 @@ class WebDriver extends Helper {
2613
2649
 
2614
2650
  await this.browser.waitUntil(
2615
2651
  async () => {
2616
- await this.browser.getWindowHandles().then((handles) => {
2652
+ await this.browser.getWindowHandles().then(handles => {
2617
2653
  if (handles.indexOf(current) - num > -1) {
2618
2654
  target = handles[handles.indexOf(current) - num]
2619
2655
  }
@@ -2639,7 +2675,6 @@ class WebDriver extends Helper {
2639
2675
  */
2640
2676
  async openNewTab(url = 'about:blank', windowName = null) {
2641
2677
  const client = this.browser
2642
- const crypto = require('crypto')
2643
2678
  if (windowName == null) {
2644
2679
  windowName = crypto.randomBytes(32).toString('hex')
2645
2680
  }
@@ -2683,10 +2718,7 @@ class WebDriver extends Helper {
2683
2718
  return client.execute(function () {
2684
2719
  const body = document.body
2685
2720
  const html = document.documentElement
2686
- window.scrollTo(
2687
- 0,
2688
- Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight),
2689
- )
2721
+ window.scrollTo(0, Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight))
2690
2722
  })
2691
2723
  }
2692
2724
 
@@ -2760,10 +2792,17 @@ async function proceedSee(assertType, text, context, strict = false) {
2760
2792
  const smartWaitEnabled = assertType === 'assert'
2761
2793
  const res = await this._locate(withStrictLocator(context), smartWaitEnabled)
2762
2794
  assertElementExists(res, context)
2763
- const selected = await forEachAsync(res, async (el) => this.browser.getElementText(getElementId(el)))
2795
+ let selected = await forEachAsync(res, async el => this.browser.getElementText(getElementId(el)))
2796
+
2797
+ // apply ignoreCase option
2798
+ if (store?.currentStep?.opts?.ignoreCase === true) {
2799
+ text = text.toLowerCase()
2800
+ selected = selected.map(elText => elText.toLowerCase())
2801
+ }
2802
+
2764
2803
  if (strict) {
2765
2804
  if (Array.isArray(selected) && selected.length !== 0) {
2766
- return selected.map((elText) => equals(description)[assertType](text, elText))
2805
+ return selected.map(elText => equals(description)[assertType](text, elText))
2767
2806
  }
2768
2807
  return equals(description)[assertType](text, selected)
2769
2808
  }
@@ -2791,7 +2830,7 @@ async function forEachAsync(array, callback, options = { expandArrayResults: tru
2791
2830
  const res = await callback(inputArray[index], index, inputArray)
2792
2831
 
2793
2832
  if (Array.isArray(res) && expandArrayResults) {
2794
- res.forEach((val) => values.push(val))
2833
+ res.forEach(val => values.push(val))
2795
2834
  } else if (res) {
2796
2835
  values.push(res)
2797
2836
  }
@@ -2832,6 +2871,7 @@ async function findClickable(locator, locateFn) {
2832
2871
  }
2833
2872
 
2834
2873
  if (locator.isAccessibilityId() && !this.isWeb) return locateFn(locator, true)
2874
+ if (locator.isRole()) return locateFn(locator, true)
2835
2875
  if (!locator.isFuzzy()) return locateFn(locator, true)
2836
2876
 
2837
2877
  let els
@@ -2846,7 +2886,15 @@ async function findClickable(locator, locateFn) {
2846
2886
  els = await locateFn(Locator.clickable.self(literal))
2847
2887
  if (els.length) return els
2848
2888
 
2849
- return locateFn(locator.value) // by css or xpath
2889
+ // Try ARIA selector for accessible name
2890
+ try {
2891
+ els = await this.browser.$$(`aria/${locator.value}`)
2892
+ if (els.length) return els
2893
+ } catch (e) {
2894
+ // ARIA selector not supported or failed
2895
+ }
2896
+
2897
+ return await locateFn(locator.value) // by css or xpath
2850
2898
  }
2851
2899
 
2852
2900
  async function findFields(locator) {
@@ -2857,6 +2905,7 @@ async function findFields(locator) {
2857
2905
  }
2858
2906
 
2859
2907
  if (locator.isAccessibilityId() && !this.isWeb) return this._locate(locator, true)
2908
+ if (locator.isRole()) return this._locate(locator, true)
2860
2909
  if (!locator.isFuzzy()) return this._locate(locator, true)
2861
2910
 
2862
2911
  const literal = xpathLocator.literal(locator.value)
@@ -2868,7 +2917,16 @@ async function findFields(locator) {
2868
2917
 
2869
2918
  els = await this._locate(Locator.field.byName(literal))
2870
2919
  if (els.length) return els
2871
- return this._locate(locator.value) // by css or xpath
2920
+
2921
+ // Try ARIA selector for accessible name
2922
+ try {
2923
+ els = await this.browser.$$(`aria/${locator.value}`)
2924
+ if (els.length) return els
2925
+ } catch (e) {
2926
+ // ARIA selector not supported or failed
2927
+ }
2928
+
2929
+ return await this._locate(locator.value) // by css or xpath
2872
2930
  }
2873
2931
 
2874
2932
  async function proceedSeeField(assertType, field, value) {
@@ -2877,11 +2935,11 @@ async function proceedSeeField(assertType, field, value) {
2877
2935
  const elem = usingFirstElement(res)
2878
2936
  const elemId = getElementId(elem)
2879
2937
 
2880
- const proceedMultiple = async (fields) => {
2938
+ const proceedMultiple = async fields => {
2881
2939
  const fieldResults = toArray(
2882
- await forEachAsync(fields, async (el) => {
2940
+ await forEachAsync(fields, async el => {
2883
2941
  const elementId = getElementId(el)
2884
- return this.browser.isW3C ? el.getValue() : this.browser.getElementAttribute(elementId, 'value')
2942
+ return this.browser.getElementAttribute(elementId, 'value')
2885
2943
  }),
2886
2944
  )
2887
2945
 
@@ -2890,27 +2948,30 @@ async function proceedSeeField(assertType, field, value) {
2890
2948
  } else {
2891
2949
  // Assert that results were found so the forEach assert does not silently pass
2892
2950
  equals(`no. of items matching > 0: ${field}`)[assertType](true, !!fieldResults.length)
2893
- fieldResults.forEach((val) => stringIncludes(`fields by ${field}`)[assertType](value, val))
2951
+ fieldResults.forEach(val => stringIncludes(`fields by ${field}`)[assertType](value, val))
2894
2952
  }
2895
2953
  }
2896
2954
 
2897
- const proceedSingle = (el) =>
2898
- el.getValue().then((res) => {
2899
- if (res === null) {
2900
- throw new Error(`Element ${el.selector} has no value attribute`)
2901
- }
2902
- stringIncludes(`fields by ${field}`)[assertType](value, res)
2903
- })
2955
+ const proceedSingle = async el => {
2956
+ let res = await el.getValue()
2904
2957
 
2905
- const filterBySelected = async (elements) =>
2906
- filterAsync(elements, async (el) => this.browser.isElementSelected(getElementId(el)))
2958
+ if (res === null) {
2959
+ res = await el.getText()
2960
+ }
2961
+
2962
+ if (res === null || res === undefined) {
2963
+ throw new Error(`Element ${el.selector} has no value attribute`)
2964
+ }
2965
+
2966
+ stringIncludes(`fields by ${field}`)[assertType](value, res)
2967
+ }
2968
+
2969
+ const filterBySelected = async elements => filterAsync(elements, async el => this.browser.isElementSelected(getElementId(el)))
2907
2970
 
2908
2971
  const filterSelectedByValue = async (elements, value) => {
2909
- return filterAsync(elements, async (el) => {
2972
+ return filterAsync(elements, async el => {
2910
2973
  const elementId = getElementId(el)
2911
- const currentValue = this.browser.isW3C
2912
- ? await el.getValue()
2913
- : await this.browser.getElementAttribute(elementId, 'value')
2974
+ const currentValue = await this.browser.getElementAttribute(elementId, 'value')
2914
2975
  const isSelected = await this.browser.isElementSelected(elementId)
2915
2976
  return currentValue === value && isSelected
2916
2977
  })
@@ -2918,7 +2979,13 @@ async function proceedSeeField(assertType, field, value) {
2918
2979
 
2919
2980
  const tag = await elem.getTagName()
2920
2981
  if (tag === 'select') {
2921
- const subOptions = await this.browser.findElementsFromElement(elemId, 'css', 'option')
2982
+ let subOptions
2983
+
2984
+ try {
2985
+ subOptions = await this.browser.findElementsFromElement(elemId, 'css', 'option')
2986
+ } catch (e) {
2987
+ subOptions = await this.browser.findElementsFromElement(elemId, 'xpath', 'option')
2988
+ }
2922
2989
 
2923
2990
  if (value === '') {
2924
2991
  // Don't filter by value
@@ -2959,10 +3026,31 @@ async function proceedSeeCheckbox(assertType, field) {
2959
3026
  const res = await findFields.call(this, field)
2960
3027
  assertElementExists(res, field, 'Field')
2961
3028
 
2962
- const selected = await forEachAsync(res, async (el) => this.browser.isElementSelected(getElementId(el)))
3029
+ const selected = await forEachAsync(res, async el => {
3030
+ const elementId = getElementId(el)
3031
+ return isElementChecked(this.browser, elementId)
3032
+ })
3033
+
2963
3034
  return truth(`checkable field "${field}"`, 'to be checked')[assertType](selected)
2964
3035
  }
2965
3036
 
3037
+ async function getElementTextAttributes(element) {
3038
+ const elementId = getElementId(element)
3039
+ const ariaLabel = await this.browser.getElementAttribute(elementId, 'aria-label').catch(() => '')
3040
+ const placeholder = await this.browser.getElementAttribute(elementId, 'placeholder').catch(() => '')
3041
+ const innerText = await this.browser.getElementText(elementId).catch(() => '')
3042
+ return [ariaLabel, placeholder, innerText]
3043
+ }
3044
+
3045
+ async function isElementChecked(browser, elementId) {
3046
+ let isChecked = await browser.isElementSelected(elementId)
3047
+ if (!isChecked) {
3048
+ const ariaChecked = await browser.getElementAttribute(elementId, 'aria-checked')
3049
+ isChecked = ariaChecked === 'true'
3050
+ }
3051
+ return isChecked
3052
+ }
3053
+
2966
3054
  async function findCheckable(locator, locateFn) {
2967
3055
  let els
2968
3056
  locator = new Locator(locator)
@@ -2972,6 +3060,7 @@ async function findCheckable(locator, locateFn) {
2972
3060
  }
2973
3061
 
2974
3062
  if (locator.isAccessibilityId() && !this.isWeb) return locateFn(locator, true)
3063
+ if (locator.isRole()) return locateFn(locator, true)
2975
3064
  if (!locator.isFuzzy()) return locateFn(locator, true)
2976
3065
 
2977
3066
  const literal = xpathLocator.literal(locator.value)
@@ -2980,7 +3069,15 @@ async function findCheckable(locator, locateFn) {
2980
3069
  els = await locateFn(Locator.checkable.byName(literal))
2981
3070
  if (els.length) return els
2982
3071
 
2983
- return locateFn(locator.value) // by css or xpath
3072
+ // Try ARIA selector for accessible name
3073
+ try {
3074
+ els = await this.browser.$$(`aria/${locator.value}`)
3075
+ if (els.length) return els
3076
+ } catch (e) {
3077
+ // ARIA selector not supported or failed
3078
+ }
3079
+
3080
+ return await locateFn(locator.value) // by css or xpath
2984
3081
  }
2985
3082
 
2986
3083
  function withStrictLocator(locator) {
@@ -3160,7 +3257,7 @@ function getNormalizedKey(key) {
3160
3257
  return convertKeyToRawKey(normalizedKey)
3161
3258
  }
3162
3259
 
3163
- const unicodeModifierKeys = modifierKeys.map((k) => convertKeyToRawKey(k))
3260
+ const unicodeModifierKeys = modifierKeys.map(k => convertKeyToRawKey(k))
3164
3261
  function isModifierKey(key) {
3165
3262
  return unicodeModifierKeys.includes(key)
3166
3263
  }
@@ -3173,9 +3270,9 @@ function highlightActiveElement(element) {
3173
3270
 
3174
3271
  function prepareLocateFn(context) {
3175
3272
  if (!context) return this._locate.bind(this)
3176
- return (l) => {
3273
+ return l => {
3177
3274
  l = new Locator(l, 'css')
3178
- return this._locate(context, true).then(async (res) => {
3275
+ return this._locate(context, true).then(async res => {
3179
3276
  assertElementExists(res, context, 'Context element')
3180
3277
  if (l.react) {
3181
3278
  return res[0].react$$(l.react, l.props || undefined)
@@ -3185,4 +3282,8 @@ function prepareLocateFn(context) {
3185
3282
  }
3186
3283
  }
3187
3284
 
3188
- module.exports = WebDriver
3285
+ function logEvents(event) {
3286
+ browserLogs.push(event.text) // add log message to the array
3287
+ }
3288
+
3289
+ export { WebDriver as default }