codeceptjs 3.7.6-beta.4 → 4.0.0-beta.10.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 (191) hide show
  1. package/README.md +1 -3
  2. package/bin/codecept.js +51 -53
  3. package/bin/test-server.js +14 -3
  4. package/docs/webapi/click.mustache +5 -1
  5. package/lib/actor.js +15 -11
  6. package/lib/ai.js +72 -107
  7. package/lib/assert/empty.js +9 -8
  8. package/lib/assert/equal.js +15 -17
  9. package/lib/assert/error.js +2 -2
  10. package/lib/assert/include.js +9 -11
  11. package/lib/assert/throws.js +1 -1
  12. package/lib/assert/truth.js +8 -5
  13. package/lib/assert.js +18 -18
  14. package/lib/codecept.js +102 -75
  15. package/lib/colorUtils.js +48 -50
  16. package/lib/command/check.js +32 -27
  17. package/lib/command/configMigrate.js +11 -10
  18. package/lib/command/definitions.js +16 -10
  19. package/lib/command/dryRun.js +16 -16
  20. package/lib/command/generate.js +62 -27
  21. package/lib/command/gherkin/init.js +36 -38
  22. package/lib/command/gherkin/snippets.js +14 -14
  23. package/lib/command/gherkin/steps.js +21 -18
  24. package/lib/command/info.js +8 -8
  25. package/lib/command/init.js +36 -29
  26. package/lib/command/interactive.js +11 -10
  27. package/lib/command/list.js +10 -9
  28. package/lib/command/run-multiple/chunk.js +5 -5
  29. package/lib/command/run-multiple/collection.js +5 -5
  30. package/lib/command/run-multiple/run.js +3 -3
  31. package/lib/command/run-multiple.js +16 -13
  32. package/lib/command/run-rerun.js +6 -7
  33. package/lib/command/run-workers.js +24 -9
  34. package/lib/command/run.js +23 -8
  35. package/lib/command/utils.js +20 -18
  36. package/lib/command/workers/runTests.js +197 -114
  37. package/lib/config.js +124 -51
  38. package/lib/container.js +438 -87
  39. package/lib/data/context.js +6 -5
  40. package/lib/data/dataScenarioConfig.js +1 -1
  41. package/lib/data/dataTableArgument.js +1 -1
  42. package/lib/data/table.js +1 -1
  43. package/lib/effects.js +94 -10
  44. package/lib/element/WebElement.js +2 -2
  45. package/lib/els.js +11 -9
  46. package/lib/event.js +11 -10
  47. package/lib/globals.js +141 -0
  48. package/lib/heal.js +12 -12
  49. package/lib/helper/AI.js +11 -11
  50. package/lib/helper/ApiDataFactory.js +50 -19
  51. package/lib/helper/Appium.js +19 -27
  52. package/lib/helper/FileSystem.js +32 -12
  53. package/lib/helper/GraphQL.js +3 -3
  54. package/lib/helper/GraphQLDataFactory.js +4 -4
  55. package/lib/helper/JSONResponse.js +25 -29
  56. package/lib/helper/Mochawesome.js +7 -4
  57. package/lib/helper/Playwright.js +902 -164
  58. package/lib/helper/Puppeteer.js +383 -76
  59. package/lib/helper/REST.js +29 -12
  60. package/lib/helper/WebDriver.js +268 -61
  61. package/lib/helper/clientscripts/PollyWebDriverExt.js +1 -1
  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 +18 -9
  70. package/lib/helper/extras/PlaywrightRestartOpts.js +34 -23
  71. package/lib/helper/extras/Popup.js +1 -1
  72. package/lib/helper/extras/React.js +29 -30
  73. package/lib/helper/network/actions.js +29 -44
  74. package/lib/helper/network/utils.js +76 -83
  75. package/lib/helper/scripts/blurElement.js +6 -6
  76. package/lib/helper/scripts/focusElement.js +6 -6
  77. package/lib/helper/scripts/highlightElement.js +9 -9
  78. package/lib/helper/scripts/isElementClickable.js +34 -34
  79. package/lib/helper.js +2 -1
  80. package/lib/history.js +23 -20
  81. package/lib/hooks.js +10 -10
  82. package/lib/html.js +90 -100
  83. package/lib/index.js +48 -21
  84. package/lib/listener/config.js +19 -12
  85. package/lib/listener/emptyRun.js +6 -7
  86. package/lib/listener/enhancedGlobalRetry.js +6 -6
  87. package/lib/listener/exit.js +4 -3
  88. package/lib/listener/globalRetry.js +5 -5
  89. package/lib/listener/globalTimeout.js +30 -14
  90. package/lib/listener/helpers.js +39 -14
  91. package/lib/listener/mocha.js +3 -4
  92. package/lib/listener/result.js +4 -5
  93. package/lib/listener/retryEnhancer.js +3 -3
  94. package/lib/listener/steps.js +8 -7
  95. package/lib/listener/store.js +3 -3
  96. package/lib/locator.js +213 -192
  97. package/lib/mocha/asyncWrapper.js +105 -62
  98. package/lib/mocha/bdd.js +99 -13
  99. package/lib/mocha/cli.js +59 -26
  100. package/lib/mocha/factory.js +78 -19
  101. package/lib/mocha/featureConfig.js +1 -1
  102. package/lib/mocha/gherkin.js +56 -24
  103. package/lib/mocha/hooks.js +12 -3
  104. package/lib/mocha/index.js +13 -4
  105. package/lib/mocha/inject.js +22 -5
  106. package/lib/mocha/scenarioConfig.js +2 -2
  107. package/lib/mocha/suite.js +9 -2
  108. package/lib/mocha/test.js +10 -7
  109. package/lib/mocha/ui.js +28 -18
  110. package/lib/output.js +10 -8
  111. package/lib/parser.js +44 -44
  112. package/lib/pause.js +15 -16
  113. package/lib/plugin/analyze.js +19 -12
  114. package/lib/plugin/auth.js +20 -21
  115. package/lib/plugin/autoDelay.js +12 -8
  116. package/lib/plugin/coverage.js +28 -11
  117. package/lib/plugin/customLocator.js +3 -3
  118. package/lib/plugin/customReporter.js +3 -2
  119. package/lib/plugin/enhancedRetryFailedStep.js +6 -6
  120. package/lib/plugin/heal.js +14 -9
  121. package/lib/plugin/htmlReporter.js +724 -99
  122. package/lib/plugin/pageInfo.js +10 -10
  123. package/lib/plugin/pauseOnFail.js +4 -3
  124. package/lib/plugin/retryFailedStep.js +48 -5
  125. package/lib/plugin/screenshotOnFail.js +75 -37
  126. package/lib/plugin/stepByStepReport.js +14 -14
  127. package/lib/plugin/stepTimeout.js +4 -3
  128. package/lib/plugin/subtitles.js +6 -5
  129. package/lib/recorder.js +33 -14
  130. package/lib/rerun.js +69 -26
  131. package/lib/result.js +4 -4
  132. package/lib/retryCoordinator.js +2 -2
  133. package/lib/secret.js +18 -17
  134. package/lib/session.js +95 -89
  135. package/lib/step/base.js +7 -7
  136. package/lib/step/comment.js +2 -2
  137. package/lib/step/config.js +1 -1
  138. package/lib/step/func.js +3 -3
  139. package/lib/step/helper.js +3 -3
  140. package/lib/step/meta.js +5 -5
  141. package/lib/step/record.js +11 -11
  142. package/lib/step/retry.js +3 -3
  143. package/lib/step/section.js +3 -3
  144. package/lib/step.js +7 -10
  145. package/lib/steps.js +9 -5
  146. package/lib/store.js +1 -1
  147. package/lib/template/heal.js +1 -1
  148. package/lib/template/prompts/generatePageObject.js +31 -0
  149. package/lib/template/prompts/healStep.js +13 -0
  150. package/lib/template/prompts/writeStep.js +9 -0
  151. package/lib/test-server.js +17 -6
  152. package/lib/timeout.js +1 -7
  153. package/lib/transform.js +8 -8
  154. package/lib/translation.js +32 -18
  155. package/lib/utils/mask_data.js +4 -10
  156. package/lib/utils.js +66 -64
  157. package/lib/workerStorage.js +17 -17
  158. package/lib/workers.js +214 -84
  159. package/package.json +41 -37
  160. package/translations/de-DE.js +2 -2
  161. package/translations/fr-FR.js +2 -2
  162. package/translations/index.js +23 -10
  163. package/translations/it-IT.js +2 -2
  164. package/translations/ja-JP.js +2 -2
  165. package/translations/nl-NL.js +2 -2
  166. package/translations/pl-PL.js +2 -2
  167. package/translations/pt-BR.js +2 -2
  168. package/translations/ru-RU.js +2 -2
  169. package/translations/utils.js +4 -3
  170. package/translations/zh-CN.js +2 -2
  171. package/translations/zh-TW.js +2 -2
  172. package/typings/index.d.ts +5 -3
  173. package/typings/promiseBasedTypes.d.ts +4 -0
  174. package/typings/types.d.ts +4 -0
  175. package/lib/helper/Nightmare.js +0 -1486
  176. package/lib/helper/Protractor.js +0 -1840
  177. package/lib/helper/TestCafe.js +0 -1391
  178. package/lib/helper/clientscripts/nightmare.js +0 -213
  179. package/lib/helper/testcafe/testControllerHolder.js +0 -42
  180. package/lib/helper/testcafe/testcafe-utils.js +0 -61
  181. package/lib/plugin/allure.js +0 -15
  182. package/lib/plugin/autoLogin.js +0 -5
  183. package/lib/plugin/commentStep.js +0 -141
  184. package/lib/plugin/eachElement.js +0 -127
  185. package/lib/plugin/fakerTransform.js +0 -49
  186. package/lib/plugin/retryTo.js +0 -16
  187. package/lib/plugin/selenoid.js +0 -364
  188. package/lib/plugin/standardActingHelpers.js +0 -6
  189. package/lib/plugin/tryTo.js +0 -16
  190. package/lib/plugin/wdio.js +0 -247
  191. 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
 
@@ -448,7 +465,7 @@ class REST extends Helper {
448
465
  }
449
466
  }
450
467
 
451
- module.exports = REST
468
+ export { REST as default }
452
469
 
453
470
  function curlize(request) {
454
471
  if (request.data?.constructor.name.toLowerCase() === 'formdata') return 'cURL is not printed as the request body is not a JSON'
@@ -1,32 +1,47 @@
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
+
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'
25
27
 
26
28
  const SHADOW = 'shadow'
27
29
  const webRoot = 'body'
28
30
  let browserLogs = []
29
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
+ }
44
+
30
45
  /**
31
46
  * ## Configuration
32
47
  *
@@ -428,7 +443,7 @@ const config = {}
428
443
  class WebDriver extends Helper {
429
444
  constructor(config) {
430
445
  super(config)
431
- webdriverio = require('webdriverio')
446
+ // webdriverio will be loaded dynamically in _init method
432
447
 
433
448
  // set defaults
434
449
  this.root = webRoot
@@ -534,12 +549,26 @@ class WebDriver extends Helper {
534
549
 
535
550
  static _checkRequirements() {
536
551
  try {
537
- 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
538
555
  } catch (e) {
539
556
  return ['webdriverio@^6.12.1']
540
557
  }
541
558
  }
542
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
+ }
569
+ }
570
+ }
571
+
543
572
  static _config() {
544
573
  return [
545
574
  {
@@ -647,6 +676,7 @@ class WebDriver extends Helper {
647
676
  }
648
677
 
649
678
  async _before() {
679
+ if (!webdriverio) await this._init()
650
680
  this.context = this.root
651
681
  if (this.options.restart && !this.options.manualStart) return this._startBrowser()
652
682
  if (!this.isRunning && !this.options.manualStart) return this._startBrowser()
@@ -845,7 +875,7 @@ class WebDriver extends Helper {
845
875
  * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
846
876
  */
847
877
  async _locate(locator, smartWait = false) {
848
- if (require('../store').debugMode) smartWait = false
878
+ if (store.debugMode) smartWait = false
849
879
 
850
880
  // special locator type for Shadow DOM
851
881
  if (this._isShadowLocator(locator)) {
@@ -865,6 +895,17 @@ class WebDriver extends Helper {
865
895
  return els
866
896
  }
867
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)
907
+ }
908
+
868
909
  if (!this.options.smartWait || !smartWait) {
869
910
  if (this._isCustomLocator(locator)) {
870
911
  const locatorObj = new Locator(locator)
@@ -935,6 +976,34 @@ class WebDriver extends Helper {
935
976
  return findFields.call(this, locator).then(res => res)
936
977
  }
937
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
1005
+ }
1006
+
938
1007
  /**
939
1008
  * {{> grabWebElements }}
940
1009
  *
@@ -1085,6 +1154,75 @@ class WebDriver extends Helper {
1085
1154
  await this.browser.buttonDown(2)
1086
1155
  }
1087
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()
1224
+ }
1225
+
1088
1226
  /**
1089
1227
  * {{> forceRightClick }}
1090
1228
  *
@@ -1122,7 +1260,17 @@ class WebDriver extends Helper {
1122
1260
  assertElementExists(res, field, 'Field')
1123
1261
  const elem = usingFirstElement(res)
1124
1262
  highlightActiveElement.call(this, elem)
1125
- await elem.clearValue()
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
+ }
1126
1274
  await elem.setValue(value.toString())
1127
1275
  }
1128
1276
 
@@ -1227,7 +1375,8 @@ class WebDriver extends Helper {
1227
1375
  const elementId = getElementId(elem)
1228
1376
  highlightActiveElement.call(this, elem)
1229
1377
 
1230
- const isSelected = await this.browser.isElementSelected(elementId)
1378
+ const isSelected = await isElementChecked(this.browser, elementId)
1379
+
1231
1380
  if (isSelected) return Promise.resolve(true)
1232
1381
  return this.browser[clickMethod](elementId)
1233
1382
  }
@@ -1247,7 +1396,8 @@ class WebDriver extends Helper {
1247
1396
  const elementId = getElementId(elem)
1248
1397
  highlightActiveElement.call(this, elem)
1249
1398
 
1250
- const isSelected = await this.browser.isElementSelected(elementId)
1399
+ const isSelected = await isElementChecked(this.browser, elementId)
1400
+
1251
1401
  if (!isSelected) return Promise.resolve(true)
1252
1402
  return this.browser[clickMethod](elementId)
1253
1403
  }
@@ -1776,7 +1926,7 @@ class WebDriver extends Helper {
1776
1926
  try {
1777
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
 
@@ -2305,10 +2455,14 @@ class WebDriver extends Helper {
2305
2455
  res = usingFirstElement(res)
2306
2456
  assertElementExists(res, locator)
2307
2457
 
2308
- return res.waitForClickable({
2309
- timeout: waitTimeout * 1000,
2310
- timeoutMsg: `element ${res.selector} still not clickable after ${waitTimeout} sec`,
2311
- })
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
+ })
2312
2466
  }
2313
2467
 
2314
2468
  /**
@@ -2330,6 +2484,7 @@ class WebDriver extends Helper {
2330
2484
  { timeout: aSec * 1000 },
2331
2485
  )
2332
2486
  .catch(e => {
2487
+ e = wrapError(e)
2333
2488
  if (e.message.indexOf('timeout')) {
2334
2489
  throw new Error(`expected url to include ${urlPart}, but found ${currUrl}`)
2335
2490
  }
@@ -2355,6 +2510,7 @@ class WebDriver extends Helper {
2355
2510
  })
2356
2511
  }, aSec * 1000)
2357
2512
  .catch(e => {
2513
+ e = wrapError(e)
2358
2514
  if (e.message.indexOf('timeout')) {
2359
2515
  throw new Error(`expected url to be ${urlPart}, but found ${currUrl}`)
2360
2516
  }
@@ -2441,21 +2597,25 @@ class WebDriver extends Helper {
2441
2597
  async waitNumberOfVisibleElements(locator, num, sec = null) {
2442
2598
  const aSec = sec || this.options.waitForTimeoutInSeconds
2443
2599
 
2444
- return this.browser.waitUntil(
2445
- async () => {
2446
- const res = await this._res(locator)
2447
- if (!res || res.length === 0) return false
2448
- let selected = await forEachAsync(res, async el => el.isDisplayed())
2449
-
2450
- if (!Array.isArray(selected)) selected = [selected]
2451
- selected = selected.filter(val => val === true)
2452
- return selected.length === num
2453
- },
2454
- {
2455
- timeout: aSec * 1000,
2456
- timeoutMsg: `The number of elements (${new Locator(locator)}) is not ${num} after ${aSec} sec`,
2457
- },
2458
- )
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
+ })
2459
2619
  }
2460
2620
 
2461
2621
  /**
@@ -2612,7 +2772,6 @@ class WebDriver extends Helper {
2612
2772
  */
2613
2773
  async openNewTab(url = 'about:blank', windowName = null) {
2614
2774
  const client = this.browser
2615
- const crypto = require('crypto')
2616
2775
  if (windowName == null) {
2617
2776
  windowName = crypto.randomBytes(32).toString('hex')
2618
2777
  }
@@ -2809,6 +2968,7 @@ async function findClickable(locator, locateFn) {
2809
2968
  }
2810
2969
 
2811
2970
  if (locator.isAccessibilityId() && !this.isWeb) return locateFn(locator, true)
2971
+ if (locator.isRole()) return locateFn(locator, true)
2812
2972
  if (!locator.isFuzzy()) return locateFn(locator, true)
2813
2973
 
2814
2974
  let els
@@ -2817,13 +2977,21 @@ async function findClickable(locator, locateFn) {
2817
2977
  els = await locateFn(Locator.clickable.narrow(literal))
2818
2978
  if (els.length) return els
2819
2979
 
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
+ }
2987
+
2820
2988
  els = await locateFn(Locator.clickable.wide(literal))
2821
2989
  if (els.length) return els
2822
2990
 
2823
2991
  els = await locateFn(Locator.clickable.self(literal))
2824
2992
  if (els.length) return els
2825
2993
 
2826
- return locateFn(locator.value) // by css or xpath
2994
+ return await locateFn(locator.value) // by css or xpath
2827
2995
  }
2828
2996
 
2829
2997
  async function findFields(locator) {
@@ -2834,6 +3002,7 @@ async function findFields(locator) {
2834
3002
  }
2835
3003
 
2836
3004
  if (locator.isAccessibilityId() && !this.isWeb) return this._locate(locator, true)
3005
+ if (locator.isRole()) return this._locate(locator, true)
2837
3006
  if (!locator.isFuzzy()) return this._locate(locator, true)
2838
3007
 
2839
3008
  const literal = xpathLocator.literal(locator.value)
@@ -2845,7 +3014,8 @@ async function findFields(locator) {
2845
3014
 
2846
3015
  els = await this._locate(Locator.field.byName(literal))
2847
3016
  if (els.length) return els
2848
- return this._locate(locator.value) // by css or xpath
3017
+
3018
+ return await this._locate(locator.value) // by css or xpath
2849
3019
  }
2850
3020
 
2851
3021
  async function proceedSeeField(assertType, field, value) {
@@ -2871,13 +3041,19 @@ async function proceedSeeField(assertType, field, value) {
2871
3041
  }
2872
3042
  }
2873
3043
 
2874
- const proceedSingle = el =>
2875
- el.getValue().then(res => {
2876
- if (res === null) {
2877
- throw new Error(`Element ${el.selector} has no value attribute`)
2878
- }
2879
- stringIncludes(`fields by ${field}`)[assertType](value, res)
2880
- })
3044
+ const proceedSingle = async el => {
3045
+ let res = await el.getValue()
3046
+
3047
+ if (res === null) {
3048
+ res = await el.getText()
3049
+ }
3050
+
3051
+ if (res === null || res === undefined) {
3052
+ throw new Error(`Element ${el.selector} has no value attribute`)
3053
+ }
3054
+
3055
+ stringIncludes(`fields by ${field}`)[assertType](value, res)
3056
+ }
2881
3057
 
2882
3058
  const filterBySelected = async elements => filterAsync(elements, async el => this.browser.isElementSelected(getElementId(el)))
2883
3059
 
@@ -2939,10 +3115,31 @@ async function proceedSeeCheckbox(assertType, field) {
2939
3115
  const res = await findFields.call(this, field)
2940
3116
  assertElementExists(res, field, 'Field')
2941
3117
 
2942
- const selected = await forEachAsync(res, async el => this.browser.isElementSelected(getElementId(el)))
3118
+ const selected = await forEachAsync(res, async el => {
3119
+ const elementId = getElementId(el)
3120
+ return isElementChecked(this.browser, elementId)
3121
+ })
3122
+
2943
3123
  return truth(`checkable field "${field}"`, 'to be checked')[assertType](selected)
2944
3124
  }
2945
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
+ }
3133
+
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
3141
+ }
3142
+
2946
3143
  async function findCheckable(locator, locateFn) {
2947
3144
  let els
2948
3145
  locator = new Locator(locator)
@@ -2952,15 +3149,25 @@ async function findCheckable(locator, locateFn) {
2952
3149
  }
2953
3150
 
2954
3151
  if (locator.isAccessibilityId() && !this.isWeb) return locateFn(locator, true)
3152
+ if (locator.isRole()) return locateFn(locator, true)
2955
3153
  if (!locator.isFuzzy()) return locateFn(locator, true)
2956
3154
 
2957
3155
  const literal = xpathLocator.literal(locator.value)
2958
3156
  els = await locateFn(Locator.checkable.byText(literal))
2959
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
+ }
3166
+
2960
3167
  els = await locateFn(Locator.checkable.byName(literal))
2961
3168
  if (els.length) return els
2962
3169
 
2963
- return locateFn(locator.value) // by css or xpath
3170
+ return await locateFn(locator.value) // by css or xpath
2964
3171
  }
2965
3172
 
2966
3173
  function withStrictLocator(locator) {
@@ -3169,4 +3376,4 @@ function logEvents(event) {
3169
3376
  browserLogs.push(event.text) // add log message to the array
3170
3377
  }
3171
3378
 
3172
- module.exports = WebDriver
3379
+ export { WebDriver as default }
@@ -108,4 +108,4 @@ const pollyWebDriver = {
108
108
  },
109
109
  };
110
110
 
111
- module.exports = pollyWebDriver;
111
+ export default pollyWebDriver;
@@ -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 }