codeceptjs 4.0.0-beta.5 → 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 (179) hide show
  1. package/README.md +0 -45
  2. package/bin/codecept.js +46 -57
  3. package/lib/actor.js +15 -11
  4. package/lib/ai.js +6 -5
  5. package/lib/assert/empty.js +9 -8
  6. package/lib/assert/equal.js +15 -17
  7. package/lib/assert/error.js +2 -2
  8. package/lib/assert/include.js +9 -11
  9. package/lib/assert/throws.js +1 -1
  10. package/lib/assert/truth.js +8 -5
  11. package/lib/assert.js +18 -18
  12. package/lib/codecept.js +66 -107
  13. package/lib/colorUtils.js +48 -50
  14. package/lib/command/check.js +32 -27
  15. package/lib/command/configMigrate.js +11 -10
  16. package/lib/command/definitions.js +16 -10
  17. package/lib/command/dryRun.js +16 -16
  18. package/lib/command/generate.js +29 -26
  19. package/lib/command/gherkin/init.js +36 -38
  20. package/lib/command/gherkin/snippets.js +14 -14
  21. package/lib/command/gherkin/steps.js +21 -18
  22. package/lib/command/info.js +8 -8
  23. package/lib/command/init.js +34 -31
  24. package/lib/command/interactive.js +11 -10
  25. package/lib/command/list.js +10 -9
  26. package/lib/command/run-multiple/chunk.js +5 -5
  27. package/lib/command/run-multiple/collection.js +5 -5
  28. package/lib/command/run-multiple/run.js +3 -3
  29. package/lib/command/run-multiple.js +16 -13
  30. package/lib/command/run-rerun.js +6 -7
  31. package/lib/command/run-workers.js +10 -24
  32. package/lib/command/run.js +8 -8
  33. package/lib/command/utils.js +20 -18
  34. package/lib/command/workers/runTests.js +117 -269
  35. package/lib/config.js +111 -49
  36. package/lib/container.js +299 -102
  37. package/lib/data/context.js +6 -5
  38. package/lib/data/dataScenarioConfig.js +1 -1
  39. package/lib/data/dataTableArgument.js +1 -1
  40. package/lib/data/table.js +1 -1
  41. package/lib/effects.js +94 -10
  42. package/lib/els.js +11 -9
  43. package/lib/event.js +11 -10
  44. package/lib/globals.js +141 -0
  45. package/lib/heal.js +12 -12
  46. package/lib/helper/AI.js +1 -1
  47. package/lib/helper/ApiDataFactory.js +16 -13
  48. package/lib/helper/FileSystem.js +32 -12
  49. package/lib/helper/GraphQL.js +1 -1
  50. package/lib/helper/GraphQLDataFactory.js +1 -1
  51. package/lib/helper/JSONResponse.js +19 -30
  52. package/lib/helper/Mochawesome.js +9 -28
  53. package/lib/helper/Playwright.js +668 -265
  54. package/lib/helper/Puppeteer.js +284 -169
  55. package/lib/helper/REST.js +29 -12
  56. package/lib/helper/WebDriver.js +191 -71
  57. package/lib/helper/errors/ConnectionRefused.js +6 -6
  58. package/lib/helper/errors/ElementAssertion.js +11 -16
  59. package/lib/helper/errors/ElementNotFound.js +5 -9
  60. package/lib/helper/errors/RemoteBrowserConnectionRefused.js +5 -5
  61. package/lib/helper/extras/Console.js +11 -11
  62. package/lib/helper/extras/PlaywrightLocator.js +110 -0
  63. package/lib/helper/extras/PlaywrightPropEngine.js +18 -18
  64. package/lib/helper/extras/PlaywrightRestartOpts.js +23 -23
  65. package/lib/helper/extras/Popup.js +1 -1
  66. package/lib/helper/extras/React.js +29 -30
  67. package/lib/helper/network/actions.js +33 -48
  68. package/lib/helper/network/utils.js +76 -83
  69. package/lib/helper/scripts/blurElement.js +6 -6
  70. package/lib/helper/scripts/focusElement.js +6 -6
  71. package/lib/helper/scripts/highlightElement.js +9 -9
  72. package/lib/helper/scripts/isElementClickable.js +34 -34
  73. package/lib/helper.js +2 -1
  74. package/lib/history.js +23 -20
  75. package/lib/hooks.js +10 -10
  76. package/lib/html.js +90 -100
  77. package/lib/index.js +48 -21
  78. package/lib/listener/config.js +8 -9
  79. package/lib/listener/emptyRun.js +6 -7
  80. package/lib/listener/exit.js +4 -3
  81. package/lib/listener/globalRetry.js +5 -5
  82. package/lib/listener/globalTimeout.js +11 -10
  83. package/lib/listener/helpers.js +33 -14
  84. package/lib/listener/mocha.js +3 -4
  85. package/lib/listener/result.js +4 -5
  86. package/lib/listener/steps.js +7 -18
  87. package/lib/listener/store.js +3 -3
  88. package/lib/locator.js +213 -192
  89. package/lib/mocha/asyncWrapper.js +108 -75
  90. package/lib/mocha/bdd.js +99 -13
  91. package/lib/mocha/cli.js +60 -27
  92. package/lib/mocha/factory.js +75 -19
  93. package/lib/mocha/featureConfig.js +1 -1
  94. package/lib/mocha/gherkin.js +57 -25
  95. package/lib/mocha/hooks.js +12 -3
  96. package/lib/mocha/index.js +13 -4
  97. package/lib/mocha/inject.js +22 -5
  98. package/lib/mocha/scenarioConfig.js +2 -2
  99. package/lib/mocha/suite.js +9 -2
  100. package/lib/mocha/test.js +10 -13
  101. package/lib/mocha/ui.js +28 -31
  102. package/lib/output.js +11 -9
  103. package/lib/parser.js +44 -44
  104. package/lib/pause.js +15 -16
  105. package/lib/plugin/analyze.js +19 -12
  106. package/lib/plugin/auth.js +20 -21
  107. package/lib/plugin/autoDelay.js +12 -8
  108. package/lib/plugin/coverage.js +12 -8
  109. package/lib/plugin/customLocator.js +3 -3
  110. package/lib/plugin/customReporter.js +3 -2
  111. package/lib/plugin/heal.js +14 -9
  112. package/lib/plugin/pageInfo.js +10 -10
  113. package/lib/plugin/pauseOnFail.js +4 -3
  114. package/lib/plugin/retryFailedStep.js +47 -5
  115. package/lib/plugin/screenshotOnFail.js +75 -37
  116. package/lib/plugin/stepByStepReport.js +14 -14
  117. package/lib/plugin/stepTimeout.js +4 -3
  118. package/lib/plugin/subtitles.js +6 -5
  119. package/lib/recorder.js +33 -23
  120. package/lib/rerun.js +69 -26
  121. package/lib/result.js +4 -4
  122. package/lib/secret.js +18 -17
  123. package/lib/session.js +95 -89
  124. package/lib/step/base.js +6 -6
  125. package/lib/step/config.js +1 -1
  126. package/lib/step/func.js +3 -3
  127. package/lib/step/helper.js +3 -3
  128. package/lib/step/meta.js +4 -4
  129. package/lib/step/record.js +11 -11
  130. package/lib/step/retry.js +3 -3
  131. package/lib/step/section.js +3 -3
  132. package/lib/step.js +7 -10
  133. package/lib/steps.js +9 -5
  134. package/lib/store.js +1 -1
  135. package/lib/timeout.js +1 -7
  136. package/lib/transform.js +8 -8
  137. package/lib/translation.js +32 -18
  138. package/lib/utils.js +68 -97
  139. package/lib/workerStorage.js +16 -17
  140. package/lib/workers.js +145 -171
  141. package/package.json +63 -57
  142. package/translations/de-DE.js +2 -2
  143. package/translations/fr-FR.js +2 -2
  144. package/translations/index.js +23 -10
  145. package/translations/it-IT.js +2 -2
  146. package/translations/ja-JP.js +2 -2
  147. package/translations/nl-NL.js +2 -2
  148. package/translations/pl-PL.js +2 -2
  149. package/translations/pt-BR.js +2 -2
  150. package/translations/ru-RU.js +2 -2
  151. package/translations/utils.js +11 -2
  152. package/translations/zh-CN.js +2 -2
  153. package/translations/zh-TW.js +2 -2
  154. package/typings/index.d.ts +7 -18
  155. package/typings/promiseBasedTypes.d.ts +3769 -5450
  156. package/typings/types.d.ts +3953 -5778
  157. package/bin/test-server.js +0 -53
  158. package/lib/element/WebElement.js +0 -327
  159. package/lib/helper/Nightmare.js +0 -1486
  160. package/lib/helper/Protractor.js +0 -1840
  161. package/lib/helper/TestCafe.js +0 -1391
  162. package/lib/helper/clientscripts/nightmare.js +0 -213
  163. package/lib/helper/extras/PlaywrightReactVueLocator.js +0 -43
  164. package/lib/helper/testcafe/testControllerHolder.js +0 -42
  165. package/lib/helper/testcafe/testcafe-utils.js +0 -61
  166. package/lib/listener/retryEnhancer.js +0 -85
  167. package/lib/plugin/allure.js +0 -15
  168. package/lib/plugin/autoLogin.js +0 -5
  169. package/lib/plugin/commentStep.js +0 -141
  170. package/lib/plugin/eachElement.js +0 -127
  171. package/lib/plugin/fakerTransform.js +0 -49
  172. package/lib/plugin/htmlReporter.js +0 -1947
  173. package/lib/plugin/retryTo.js +0 -16
  174. package/lib/plugin/selenoid.js +0 -364
  175. package/lib/plugin/standardActingHelpers.js +0 -6
  176. package/lib/plugin/tryTo.js +0 -16
  177. package/lib/plugin/wdio.js +0 -247
  178. package/lib/test-server.js +0 -323
  179. package/lib/within.js +0 -90
@@ -1,9 +1,8 @@
1
- const axios = require('axios').default
2
- const Helper = require('@codeceptjs/helper')
3
- const { Agent } = require('https')
4
- const Secret = require('../secret')
5
-
6
- const { beautify } = require('../utils')
1
+ import axios from 'axios'
2
+ import Helper from '@codeceptjs/helper'
3
+ import { Agent } from 'https'
4
+ import Secret from '../secret.js'
5
+ import { beautify } from '../utils.js'
7
6
 
8
7
  /**
9
8
  * ## Configuration
@@ -143,7 +142,9 @@ class REST extends Helper {
143
142
 
144
143
  static _checkRequirements() {
145
144
  try {
146
- require('axios')
145
+ // In ESM, axios is already imported at the top, so no need to check
146
+ // The import will fail at module load time if axios is missing
147
+ return null
147
148
  } catch (e) {
148
149
  return ['axios']
149
150
  }
@@ -218,10 +219,18 @@ class REST extends Helper {
218
219
  await this.config.onRequest(request)
219
220
  }
220
221
 
221
- this.options.prettyPrintJson ? this.debugSection('Request', beautify(JSON.stringify(_debugRequest))) : this.debugSection('Request', JSON.stringify(_debugRequest))
222
+ try {
223
+ this.options.prettyPrintJson ? this.debugSection('Request', beautify(JSON.stringify(_debugRequest))) : this.debugSection('Request', JSON.stringify(_debugRequest))
224
+ } catch (e) {
225
+ console.log('[REST] Request:', JSON.stringify(_debugRequest))
226
+ }
222
227
 
223
228
  if (this.options.printCurl) {
224
- this.debugSection('CURL Request', curlize(request))
229
+ try {
230
+ this.debugSection('CURL Request', curlize(request))
231
+ } catch (e) {
232
+ console.log('[REST] CURL Request:', curlize(request))
233
+ }
225
234
  }
226
235
 
227
236
  let response
@@ -229,13 +238,21 @@ class REST extends Helper {
229
238
  response = await this.axios(request)
230
239
  } catch (err) {
231
240
  if (!err.response) throw err
232
- this.debugSection('Response', `Response error. Status code: ${err.response.status}`)
241
+ try {
242
+ this.debugSection('Response', `Response error. Status code: ${err.response.status}`)
243
+ } catch (e) {
244
+ console.log('[REST] Response error. Status code:', err.response.status)
245
+ }
233
246
  response = err.response
234
247
  }
235
248
  if (this.config.onResponse) {
236
249
  await this.config.onResponse(response)
237
250
  }
238
- this.options.prettyPrintJson ? this.debugSection('Response', beautify(JSON.stringify(response.data))) : this.debugSection('Response', JSON.stringify(response.data))
251
+ try {
252
+ this.options.prettyPrintJson ? this.debugSection('Response', beautify(JSON.stringify(response.data))) : this.debugSection('Response', JSON.stringify(response.data))
253
+ } catch (e) {
254
+ console.log('[REST] Response:', JSON.stringify(response.data))
255
+ }
239
256
  return response
240
257
  }
241
258
 
@@ -427,7 +444,7 @@ class REST extends Helper {
427
444
  }
428
445
  }
429
446
 
430
- module.exports = REST
447
+ export { REST as default }
431
448
 
432
449
  function curlize(request) {
433
450
  if (request.data?.constructor.name.toLowerCase() === 'formdata') return 'cURL is not printed as the request body is not a JSON'
@@ -1,32 +1,45 @@
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 store = require('../store')
11
- const { debug } = require('../output')
12
- const { empty } = require('../assert/empty')
13
- const { truth } = require('../assert/truth')
14
- const { xpathLocator, fileExists, decodeUrl, chunkArray, convertCssPropertiesToCamelCase, screenshotOutputFolder, getNormalizedKeyAttributeValue, modifierKeys } = require('../utils')
15
- const { isColorProperty, convertColorToRGBA } = require('../colorUtils')
16
- const ElementNotFound = require('./errors/ElementNotFound')
17
- const ConnectionRefused = require('./errors/ConnectionRefused')
18
- const Locator = require('../locator')
19
- const { highlightElement } = require('./scripts/highlightElement')
20
- const { focusElement } = require('./scripts/focusElement')
21
- const { blurElement } = require('./scripts/blurElement')
22
- const { dontSeeElementError, seeElementError, seeElementInDOMError, dontSeeElementInDOMError } = require('./errors/ElementAssertion')
23
- const { dontSeeTraffic, seeTraffic, grabRecordedNetworkTraffics, stopRecordingTraffic, flushNetworkTraffics } = require('./network/actions')
24
- const WebElement = require('../element/WebElement')
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'
25
25
 
26
26
  const SHADOW = 'shadow'
27
27
  const webRoot = 'body'
28
28
  let browserLogs = []
29
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
+ }
42
+
30
43
  /**
31
44
  * ## Configuration
32
45
  *
@@ -428,7 +441,7 @@ const config = {}
428
441
  class WebDriver extends Helper {
429
442
  constructor(config) {
430
443
  super(config)
431
- webdriverio = require('webdriverio')
444
+ // webdriverio will be loaded dynamically in _init method
432
445
 
433
446
  // set defaults
434
447
  this.root = webRoot
@@ -534,12 +547,26 @@ class WebDriver extends Helper {
534
547
 
535
548
  static _checkRequirements() {
536
549
  try {
537
- 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
538
553
  } catch (e) {
539
554
  return ['webdriverio@^6.12.1']
540
555
  }
541
556
  }
542
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
+
543
570
  static _config() {
544
571
  return [
545
572
  {
@@ -644,6 +671,7 @@ class WebDriver extends Helper {
644
671
  }
645
672
 
646
673
  async _before() {
674
+ if (!webdriverio) await this._init()
647
675
  this.context = this.root
648
676
  if (this.options.restart && !this.options.manualStart) return this._startBrowser()
649
677
  if (!this.isRunning && !this.options.manualStart) return this._startBrowser()
@@ -842,7 +870,7 @@ class WebDriver extends Helper {
842
870
  * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
843
871
  */
844
872
  async _locate(locator, smartWait = false) {
845
- if (require('../store').debugMode) smartWait = false
873
+ if (store.debugMode) smartWait = false
846
874
 
847
875
  // special locator type for Shadow DOM
848
876
  if (this._isShadowLocator(locator)) {
@@ -862,6 +890,17 @@ class WebDriver extends Helper {
862
890
  return els
863
891
  }
864
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
+
865
904
  if (!this.options.smartWait || !smartWait) {
866
905
  if (this._isCustomLocator(locator)) {
867
906
  const locatorObj = new Locator(locator)
@@ -933,24 +972,39 @@ class WebDriver extends Helper {
933
972
  }
934
973
 
935
974
  /**
936
- * {{> grabWebElements }}
975
+ * Locate elements by ARIA role using WebdriverIO accessibility selectors
937
976
  *
977
+ * @param {object} locator - role locator object { role: string, text?: string, exact?: boolean }
938
978
  */
939
- async grabWebElements(locator) {
940
- const elements = await this._locate(locator)
941
- return elements.map(element => new WebElement(element, this))
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
942
1000
  }
943
1001
 
944
1002
  /**
945
- * {{> grabWebElement }}
1003
+ * {{> grabWebElements }}
946
1004
  *
947
1005
  */
948
- async grabWebElement(locator) {
949
- const elements = await this._locate(locator)
950
- if (elements.length === 0) {
951
- throw new ElementNotFound(locator, 'Element')
952
- }
953
- return new WebElement(elements[0], this)
1006
+ async grabWebElements(locator) {
1007
+ return this._locate(locator)
954
1008
  }
955
1009
 
956
1010
  /**
@@ -1224,7 +1278,8 @@ class WebDriver extends Helper {
1224
1278
  const elementId = getElementId(elem)
1225
1279
  highlightActiveElement.call(this, elem)
1226
1280
 
1227
- const isSelected = await this.browser.isElementSelected(elementId)
1281
+ const isSelected = await isElementChecked(this.browser, elementId)
1282
+
1228
1283
  if (isSelected) return Promise.resolve(true)
1229
1284
  return this.browser[clickMethod](elementId)
1230
1285
  }
@@ -1244,7 +1299,8 @@ class WebDriver extends Helper {
1244
1299
  const elementId = getElementId(elem)
1245
1300
  highlightActiveElement.call(this, elem)
1246
1301
 
1247
- const isSelected = await this.browser.isElementSelected(elementId)
1302
+ const isSelected = await isElementChecked(this.browser, elementId)
1303
+
1248
1304
  if (!isSelected) return Promise.resolve(true)
1249
1305
  return this.browser[clickMethod](elementId)
1250
1306
  }
@@ -1773,7 +1829,7 @@ class WebDriver extends Helper {
1773
1829
  try {
1774
1830
  await elem.moveTo({ xOffset, yOffset })
1775
1831
  } catch (e) {
1776
- debug(e.message)
1832
+ output.debug(e.message)
1777
1833
  }
1778
1834
  }
1779
1835
 
@@ -2302,10 +2358,14 @@ class WebDriver extends Helper {
2302
2358
  res = usingFirstElement(res)
2303
2359
  assertElementExists(res, locator)
2304
2360
 
2305
- return res.waitForClickable({
2306
- timeout: waitTimeout * 1000,
2307
- timeoutMsg: `element ${res.selector} still not clickable after ${waitTimeout} sec`,
2308
- })
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
+ })
2309
2369
  }
2310
2370
 
2311
2371
  /**
@@ -2327,6 +2387,7 @@ class WebDriver extends Helper {
2327
2387
  { timeout: aSec * 1000 },
2328
2388
  )
2329
2389
  .catch(e => {
2390
+ e = wrapError(e)
2330
2391
  if (e.message.indexOf('timeout')) {
2331
2392
  throw new Error(`expected url to include ${urlPart}, but found ${currUrl}`)
2332
2393
  }
@@ -2352,6 +2413,7 @@ class WebDriver extends Helper {
2352
2413
  })
2353
2414
  }, aSec * 1000)
2354
2415
  .catch(e => {
2416
+ e = wrapError(e)
2355
2417
  if (e.message.indexOf('timeout')) {
2356
2418
  throw new Error(`expected url to be ${urlPart}, but found ${currUrl}`)
2357
2419
  }
@@ -2438,21 +2500,25 @@ class WebDriver extends Helper {
2438
2500
  async waitNumberOfVisibleElements(locator, num, sec = null) {
2439
2501
  const aSec = sec || this.options.waitForTimeoutInSeconds
2440
2502
 
2441
- return this.browser.waitUntil(
2442
- async () => {
2443
- const res = await this._res(locator)
2444
- if (!res || res.length === 0) return false
2445
- let selected = await forEachAsync(res, async el => el.isDisplayed())
2446
-
2447
- if (!Array.isArray(selected)) selected = [selected]
2448
- selected = selected.filter(val => val === true)
2449
- return selected.length === num
2450
- },
2451
- {
2452
- timeout: aSec * 1000,
2453
- timeoutMsg: `The number of elements (${new Locator(locator)}) is not ${num} after ${aSec} sec`,
2454
- },
2455
- )
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
+ })
2456
2522
  }
2457
2523
 
2458
2524
  /**
@@ -2609,7 +2675,6 @@ class WebDriver extends Helper {
2609
2675
  */
2610
2676
  async openNewTab(url = 'about:blank', windowName = null) {
2611
2677
  const client = this.browser
2612
- const crypto = require('crypto')
2613
2678
  if (windowName == null) {
2614
2679
  windowName = crypto.randomBytes(32).toString('hex')
2615
2680
  }
@@ -2806,6 +2871,7 @@ async function findClickable(locator, locateFn) {
2806
2871
  }
2807
2872
 
2808
2873
  if (locator.isAccessibilityId() && !this.isWeb) return locateFn(locator, true)
2874
+ if (locator.isRole()) return locateFn(locator, true)
2809
2875
  if (!locator.isFuzzy()) return locateFn(locator, true)
2810
2876
 
2811
2877
  let els
@@ -2820,7 +2886,15 @@ async function findClickable(locator, locateFn) {
2820
2886
  els = await locateFn(Locator.clickable.self(literal))
2821
2887
  if (els.length) return els
2822
2888
 
2823
- 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
2824
2898
  }
2825
2899
 
2826
2900
  async function findFields(locator) {
@@ -2831,6 +2905,7 @@ async function findFields(locator) {
2831
2905
  }
2832
2906
 
2833
2907
  if (locator.isAccessibilityId() && !this.isWeb) return this._locate(locator, true)
2908
+ if (locator.isRole()) return this._locate(locator, true)
2834
2909
  if (!locator.isFuzzy()) return this._locate(locator, true)
2835
2910
 
2836
2911
  const literal = xpathLocator.literal(locator.value)
@@ -2842,7 +2917,16 @@ async function findFields(locator) {
2842
2917
 
2843
2918
  els = await this._locate(Locator.field.byName(literal))
2844
2919
  if (els.length) return els
2845
- 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
2846
2930
  }
2847
2931
 
2848
2932
  async function proceedSeeField(assertType, field, value) {
@@ -2868,13 +2952,19 @@ async function proceedSeeField(assertType, field, value) {
2868
2952
  }
2869
2953
  }
2870
2954
 
2871
- const proceedSingle = el =>
2872
- el.getValue().then(res => {
2873
- if (res === null) {
2874
- throw new Error(`Element ${el.selector} has no value attribute`)
2875
- }
2876
- stringIncludes(`fields by ${field}`)[assertType](value, res)
2877
- })
2955
+ const proceedSingle = async el => {
2956
+ let res = await el.getValue()
2957
+
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
+ }
2878
2968
 
2879
2969
  const filterBySelected = async elements => filterAsync(elements, async el => this.browser.isElementSelected(getElementId(el)))
2880
2970
 
@@ -2936,10 +3026,31 @@ async function proceedSeeCheckbox(assertType, field) {
2936
3026
  const res = await findFields.call(this, field)
2937
3027
  assertElementExists(res, field, 'Field')
2938
3028
 
2939
- 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
+
2940
3034
  return truth(`checkable field "${field}"`, 'to be checked')[assertType](selected)
2941
3035
  }
2942
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
+
2943
3054
  async function findCheckable(locator, locateFn) {
2944
3055
  let els
2945
3056
  locator = new Locator(locator)
@@ -2949,6 +3060,7 @@ async function findCheckable(locator, locateFn) {
2949
3060
  }
2950
3061
 
2951
3062
  if (locator.isAccessibilityId() && !this.isWeb) return locateFn(locator, true)
3063
+ if (locator.isRole()) return locateFn(locator, true)
2952
3064
  if (!locator.isFuzzy()) return locateFn(locator, true)
2953
3065
 
2954
3066
  const literal = xpathLocator.literal(locator.value)
@@ -2957,7 +3069,15 @@ async function findCheckable(locator, locateFn) {
2957
3069
  els = await locateFn(Locator.checkable.byName(literal))
2958
3070
  if (els.length) return els
2959
3071
 
2960
- 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
2961
3081
  }
2962
3082
 
2963
3083
  function withStrictLocator(locator) {
@@ -3166,4 +3286,4 @@ function logEvents(event) {
3166
3286
  browserLogs.push(event.text) // add log message to the array
3167
3287
  }
3168
3288
 
3169
- module.exports = WebDriver
3289
+ export { WebDriver as default }
@@ -1,10 +1,10 @@
1
1
  function ConnectionRefused(err) {
2
- this.message = "Can't connect to WebDriver.\n";
3
- this.message += `${err}\n\n`;
4
- this.message += 'Please make sure Selenium Server is running and accessible';
5
- this.stack = err.stack;
2
+ this.message = "Can't connect to WebDriver.\n"
3
+ this.message += `${err}\n\n`
4
+ this.message += 'Please make sure Selenium Server is running and accessible'
5
+ this.stack = err.stack
6
6
  }
7
7
 
8
- ConnectionRefused.prototype = Object.create(Error.prototype);
8
+ ConnectionRefused.prototype = Object.create(Error.prototype)
9
9
 
10
- module.exports = ConnectionRefused;
10
+ export default ConnectionRefused
@@ -1,38 +1,33 @@
1
- const Locator = require('../../locator');
1
+ import Locator from '../../locator.js'
2
2
 
3
- const prefixMessage = 'Element';
3
+ const prefixMessage = 'Element'
4
4
 
5
5
  function seeElementError(locator) {
6
6
  if (typeof locator === 'object') {
7
- locator = JSON.stringify(locator);
7
+ locator = JSON.stringify(locator)
8
8
  }
9
- throw new Error(`${prefixMessage} "${(new Locator(locator))}" is still visible on page.`);
9
+ throw new Error(`${prefixMessage} "${new Locator(locator)}" is still visible on page.`)
10
10
  }
11
11
 
12
12
  function seeElementInDOMError(locator) {
13
13
  if (typeof locator === 'object') {
14
- locator = JSON.stringify(locator);
14
+ locator = JSON.stringify(locator)
15
15
  }
16
- throw new Error(`${prefixMessage} "${(new Locator(locator))}" is still seen in DOM.`);
16
+ throw new Error(`${prefixMessage} "${new Locator(locator)}" is still seen in DOM.`)
17
17
  }
18
18
 
19
19
  function dontSeeElementError(locator) {
20
20
  if (typeof locator === 'object') {
21
- locator = JSON.stringify(locator);
21
+ locator = JSON.stringify(locator)
22
22
  }
23
- throw new Error(`${prefixMessage} "${(new Locator(locator))}" is not visible on page.`);
23
+ throw new Error(`${prefixMessage} "${new Locator(locator)}" is not visible on page.`)
24
24
  }
25
25
 
26
26
  function dontSeeElementInDOMError(locator) {
27
27
  if (typeof locator === 'object') {
28
- locator = JSON.stringify(locator);
28
+ locator = JSON.stringify(locator)
29
29
  }
30
- throw new Error(`${prefixMessage} "${(new Locator(locator))}" is not seen in DOM.`);
30
+ throw new Error(`${prefixMessage} "${new Locator(locator)}" is not seen in DOM.`)
31
31
  }
32
32
 
33
- module.exports = {
34
- seeElementError,
35
- dontSeeElementError,
36
- seeElementInDOMError,
37
- dontSeeElementInDOMError,
38
- };
33
+ export { seeElementError, dontSeeElementError, seeElementInDOMError, dontSeeElementInDOMError }
@@ -1,19 +1,15 @@
1
- const Locator = require('../../locator');
1
+ import Locator from '../../locator.js'
2
2
  /**
3
3
  * Uses to throw readable element not found error
4
4
  * Stringify object's locators
5
5
  */
6
6
  class ElementNotFound {
7
- constructor(
8
- locator,
9
- prefixMessage = 'Element',
10
- postfixMessage = 'was not found by text|CSS|XPath',
11
- ) {
7
+ constructor(locator, prefixMessage = 'Element', postfixMessage = 'was not found by text|CSS|XPath') {
12
8
  if (typeof locator === 'object') {
13
- locator = JSON.stringify(locator);
9
+ locator = JSON.stringify(locator)
14
10
  }
15
- throw new Error(`${prefixMessage} "${(new Locator(locator))}" ${postfixMessage}`);
11
+ throw new Error(`${prefixMessage} "${new Locator(locator)}" ${postfixMessage}`)
16
12
  }
17
13
  }
18
14
 
19
- module.exports = ElementNotFound;
15
+ export default ElementNotFound
@@ -1,9 +1,9 @@
1
1
  function RemoteBrowserConnectionRefused(err) {
2
- this.message = 'Cannot connect to websocket endpoint.\n\n';
3
- this.message += 'Please make sure remote browser is running and accessible.';
4
- this.stack = err.error || err;
2
+ this.message = 'Cannot connect to websocket endpoint.\n\n'
3
+ this.message += 'Please make sure remote browser is running and accessible.'
4
+ this.stack = err.error || err
5
5
  }
6
6
 
7
- RemoteBrowserConnectionRefused.prototype = Object.create(Error.prototype);
7
+ RemoteBrowserConnectionRefused.prototype = Object.create(Error.prototype)
8
8
 
9
- module.exports = RemoteBrowserConnectionRefused;
9
+ export default RemoteBrowserConnectionRefused
@@ -3,31 +3,31 @@
3
3
  */
4
4
  class Console {
5
5
  constructor() {
6
- this._logEntries = [];
6
+ this._logEntries = []
7
7
  }
8
8
 
9
9
  get entries() {
10
- return this._logEntries;
10
+ return this._logEntries
11
11
  }
12
12
 
13
13
  clear() {
14
- this._logEntries = [];
14
+ this._logEntries = []
15
15
  }
16
16
 
17
17
  includes(msg) {
18
- const prev = this._logEntries[this._logEntries.length - 1];
19
- if (!prev) return false;
20
- const text = msg.text && msg.text() || msg._text || '';
21
- const prevText = prev.text && prev.text() || prev._text || '';
22
- return text === prevText;
18
+ const prev = this._logEntries[this._logEntries.length - 1]
19
+ if (!prev) return false
20
+ const text = (msg.text && msg.text()) || msg._text || ''
21
+ const prevText = (prev.text && prev.text()) || prev._text || ''
22
+ return text === prevText
23
23
  }
24
24
 
25
25
  add(entry) {
26
26
  if (Array.isArray(entry)) {
27
- this._logEntries = this._logEntries.concat(entry);
27
+ this._logEntries = this._logEntries.concat(entry)
28
28
  }
29
- this._logEntries.push(entry);
29
+ this._logEntries.push(entry)
30
30
  }
31
31
  }
32
32
 
33
- module.exports = Console;
33
+ export default Console