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,21 +1,19 @@
1
- const axios = require('axios')
2
- const fs = require('fs')
3
- const fsExtra = require('fs-extra')
4
- const path = require('path')
5
-
6
- const Helper = require('@codeceptjs/helper')
7
- const { v4: uuidv4 } = require('uuid')
8
- const promiseRetry = require('promise-retry')
9
- const Locator = require('../locator')
10
- const recorder = require('../recorder')
11
- const store = require('../store')
12
- const stringIncludes = require('../assert/include').includes
13
- const { urlEquals } = require('../assert/equal')
14
- const { equals } = require('../assert/equal')
15
- const { empty } = require('../assert/empty')
16
- const { truth } = require('../assert/truth')
17
- const isElementClickable = require('./scripts/isElementClickable')
18
- const {
1
+ import axios from 'axios'
2
+ import fs from 'fs'
3
+ import fsExtra from 'fs-extra'
4
+ import path from 'path'
5
+ import Helper from '@codeceptjs/helper'
6
+ import { v4 as uuidv4 } from 'uuid'
7
+ import promiseRetry from 'promise-retry'
8
+ import Locator from '../locator.js'
9
+ import recorder from '../recorder.js'
10
+ import store from '../store.js'
11
+ import { includes as stringIncludes } from '../assert/include.js'
12
+ import { urlEquals, equals } from '../assert/equal.js'
13
+ import { empty } from '../assert/empty.js'
14
+ import { truth } from '../assert/truth.js'
15
+ import isElementClickable from './scripts/isElementClickable.js'
16
+ import {
19
17
  xpathLocator,
20
18
  ucfirst,
21
19
  fileExists,
@@ -28,29 +26,31 @@ const {
28
26
  isModifierKey,
29
27
  requireWithFallback,
30
28
  normalizeSpacesInString,
31
- } = require('../utils')
32
- const { isColorProperty, convertColorToRGBA } = require('../colorUtils')
33
- const ElementNotFound = require('./errors/ElementNotFound')
34
- const RemoteBrowserConnectionRefused = require('./errors/RemoteBrowserConnectionRefused')
35
- const Popup = require('./extras/Popup')
36
- const Console = require('./extras/Console')
37
- const { highlightElement } = require('./scripts/highlightElement')
38
- const { blurElement } = require('./scripts/blurElement')
39
- const {
40
- dontSeeElementError,
41
- seeElementError,
42
- dontSeeElementInDOMError,
43
- seeElementInDOMError,
44
- } = require('./errors/ElementAssertion')
45
- const {
46
- dontSeeTraffic,
47
- seeTraffic,
48
- grabRecordedNetworkTraffics,
49
- stopRecordingTraffic,
50
- flushNetworkTraffics,
51
- } = require('./network/actions')
29
+ } from '../utils.js'
30
+ import { isColorProperty, convertColorToRGBA } from '../colorUtils.js'
31
+ import ElementNotFound from './errors/ElementNotFound.js'
32
+ import RemoteBrowserConnectionRefused from './errors/RemoteBrowserConnectionRefused.js'
33
+ import Popup from './extras/Popup.js'
34
+ import Console from './extras/Console.js'
35
+ import { highlightElement } from './scripts/highlightElement.js'
36
+ import { blurElement } from './scripts/blurElement.js'
37
+ import { dontSeeElementError, seeElementError, dontSeeElementInDOMError, seeElementInDOMError } from './errors/ElementAssertion.js'
38
+ import { dontSeeTraffic, seeTraffic, grabRecordedNetworkTraffics, stopRecordingTraffic, flushNetworkTraffics } from './network/actions.js'
52
39
 
53
40
  let puppeteer
41
+
42
+ /**
43
+ * Wraps error objects that don't have a proper message property
44
+ * This is needed for ESM compatibility with Puppeteer error handling
45
+ */
46
+ function wrapError(e) {
47
+ if (e && typeof e === 'object' && !e.message) {
48
+ const err = new Error(String(e))
49
+ err.stack = e.stack
50
+ return err
51
+ }
52
+ return e
53
+ }
54
54
  let perfTiming
55
55
  const popupStore = new Popup()
56
56
  const consoleLogStore = new Console()
@@ -224,7 +224,7 @@ class Puppeteer extends Helper {
224
224
  constructor(config) {
225
225
  super(config)
226
226
 
227
- puppeteer = requireWithFallback('puppeteer', 'puppeteer-core')
227
+ // puppeteer will be loaded dynamically in _init method
228
228
  // set defaults
229
229
  this.isRemoteBrowser = false
230
230
  this.isRunning = false
@@ -271,9 +271,7 @@ class Puppeteer extends Helper {
271
271
  }
272
272
 
273
273
  _getOptions(config) {
274
- return config.browser === 'firefox'
275
- ? Object.assign(this.options.firefox, { product: 'firefox' })
276
- : this.options.chrome
274
+ return config.browser === 'firefox' ? Object.assign(this.options.firefox, { product: 'firefox' }) : this.options.chrome
277
275
  }
278
276
 
279
277
  _setConfig(config) {
@@ -306,13 +304,34 @@ class Puppeteer extends Helper {
306
304
 
307
305
  static _checkRequirements() {
308
306
  try {
309
- requireWithFallback('puppeteer', 'puppeteer-core')
307
+ // In ESM, puppeteer will be checked via dynamic import in _init
308
+ // The import will fail at module load time if puppeteer is missing
309
+ return null
310
310
  } catch (e) {
311
311
  return ['puppeteer']
312
312
  }
313
313
  }
314
314
 
315
- _init() {}
315
+ async _init() {
316
+ // Load puppeteer dynamically with fallback
317
+ if (!puppeteer) {
318
+ try {
319
+ const puppeteerModule = await import('puppeteer')
320
+ puppeteer = puppeteerModule.default || puppeteerModule
321
+ this.debugSection('Puppeteer', `Loaded puppeteer successfully, launch available: ${!!puppeteer.launch}`)
322
+ } catch (e) {
323
+ try {
324
+ const puppeteerModule = await import('puppeteer-core')
325
+ puppeteer = puppeteerModule.default || puppeteerModule
326
+ this.debugSection('Puppeteer', `Loaded puppeteer-core successfully, launch available: ${!!puppeteer.launch}`)
327
+ } catch (e2) {
328
+ throw new Error('Neither puppeteer nor puppeteer-core could be loaded. Please install one of them.')
329
+ }
330
+ }
331
+ } else {
332
+ this.debugSection('Puppeteer', `Puppeteer already loaded, launch available: ${!!puppeteer.launch}`)
333
+ }
334
+ }
316
335
 
317
336
  _beforeSuite() {
318
337
  if (!this.options.restart && !this.options.manualStart && !this.isRunning) {
@@ -325,8 +344,8 @@ class Puppeteer extends Helper {
325
344
  this.sessionPages = {}
326
345
  this.currentRunningTest = test
327
346
  recorder.retry({
328
- retries: process.env.FAILED_STEP_RETRIES || 3,
329
- when: (err) => {
347
+ retries: test?.opts?.conditionalRetries || 3,
348
+ when: err => {
330
349
  if (!err || typeof err.message !== 'string') {
331
350
  return false
332
351
  }
@@ -346,7 +365,7 @@ class Puppeteer extends Helper {
346
365
  const contexts = this.browser.browserContexts()
347
366
  const defaultCtx = contexts.shift()
348
367
 
349
- await Promise.all(contexts.map((c) => c.close()))
368
+ await Promise.all(contexts.map(c => c.close()))
350
369
 
351
370
  if (this.options.restart) {
352
371
  this.isRunning = false
@@ -355,7 +374,7 @@ class Puppeteer extends Helper {
355
374
 
356
375
  // ensure this.page is from default context
357
376
  if (this.page) {
358
- const existingPages = defaultCtx.targets().filter((t) => t.type() === 'page')
377
+ const existingPages = defaultCtx.targets().filter(t => t.type() === 'page')
359
378
  await this._setPage(await existingPages[0].page())
360
379
  }
361
380
 
@@ -368,10 +387,10 @@ class Puppeteer extends Helper {
368
387
  const currentUrl = await this.grabCurrentUrl()
369
388
 
370
389
  if (currentUrl.startsWith('http')) {
371
- await this.executeScript('localStorage.clear();').catch((err) => {
390
+ await this.executeScript('localStorage.clear();').catch(err => {
372
391
  if (!(err.message.indexOf("Storage is disabled inside 'data:' URLs.") > -1)) throw err
373
392
  })
374
- await this.executeScript('sessionStorage.clear();').catch((err) => {
393
+ await this.executeScript('sessionStorage.clear();').catch(err => {
375
394
  if (!(err.message.indexOf("Storage is disabled inside 'data:' URLs.") > -1)) throw err
376
395
  })
377
396
  }
@@ -400,12 +419,12 @@ class Puppeteer extends Helper {
400
419
  stop: async () => {
401
420
  // is closed by _after
402
421
  },
403
- loadVars: async (context) => {
404
- const existingPages = context.targets().filter((t) => t.type() === 'page')
422
+ loadVars: async context => {
423
+ const existingPages = context.targets().filter(t => t.type() === 'page')
405
424
  this.sessionPages[this.activeSessionName] = await existingPages[0].page()
406
425
  return this._setPage(this.sessionPages[this.activeSessionName])
407
426
  },
408
- restoreVars: async (session) => {
427
+ restoreVars: async session => {
409
428
  this.withinLocator = null
410
429
 
411
430
  if (!session) {
@@ -414,7 +433,7 @@ class Puppeteer extends Helper {
414
433
  this.activeSessionName = session
415
434
  }
416
435
  const defaultCtx = this.browser.defaultBrowserContext()
417
- const existingPages = defaultCtx.targets().filter((t) => t.type() === 'page')
436
+ const existingPages = defaultCtx.targets().filter(t => t.type() === 'page')
418
437
  await this._setPage(await existingPages[0].page())
419
438
 
420
439
  return this._waitForAction()
@@ -517,7 +536,7 @@ class Puppeteer extends Helper {
517
536
  if (!page) {
518
537
  return
519
538
  }
520
- page.on('error', async (error) => {
539
+ page.on('error', async error => {
521
540
  console.error('Puppeteer page error', error)
522
541
  })
523
542
  }
@@ -533,7 +552,7 @@ class Puppeteer extends Helper {
533
552
  if (!page) {
534
553
  return
535
554
  }
536
- page.on('dialog', async (dialog) => {
555
+ page.on('dialog', async dialog => {
537
556
  popupStore.popup = dialog
538
557
  const action = popupStore.actionType || this.options.defaultPopupAction
539
558
  await this._waitForAction()
@@ -575,6 +594,12 @@ class Puppeteer extends Helper {
575
594
  }
576
595
 
577
596
  async _startBrowser() {
597
+ this.debugSection('Puppeteer', `Starting browser. Puppeteer available: ${!!puppeteer}, launch available: ${!!puppeteer?.launch}`)
598
+
599
+ if (!puppeteer) {
600
+ throw new Error('Puppeteer is not loaded. Make sure _init() was called before _startBrowser()')
601
+ }
602
+
578
603
  if (this.isRemoteBrowser) {
579
604
  try {
580
605
  this.browser = await puppeteer.connect(this.puppeteerOptions)
@@ -588,15 +613,15 @@ class Puppeteer extends Helper {
588
613
  this.browser = await puppeteer.launch(this.puppeteerOptions)
589
614
  }
590
615
 
591
- this.browser.on('targetcreated', (target) =>
616
+ this.browser.on('targetcreated', target =>
592
617
  target
593
618
  .page()
594
- .then((page) => targetCreatedHandler.call(this, page))
595
- .catch((e) => {
619
+ .then(page => targetCreatedHandler.call(this, page))
620
+ .catch(e => {
596
621
  console.error('Puppeteer page error', e)
597
622
  }),
598
623
  )
599
- this.browser.on('targetchanged', (target) => {
624
+ this.browser.on('targetchanged', target => {
600
625
  this.debugSection('Url', target.url())
601
626
  })
602
627
 
@@ -639,9 +664,7 @@ class Puppeteer extends Helper {
639
664
 
640
665
  if (frame) {
641
666
  if (Array.isArray(frame)) {
642
- return this.switchTo(null).then(() =>
643
- frame.reduce((p, frameLocator) => p.then(() => this.switchTo(frameLocator)), Promise.resolve()),
644
- )
667
+ return this.switchTo(null).then(() => frame.reduce((p, frameLocator) => p.then(() => this.switchTo(frameLocator)), Promise.resolve()))
645
668
  }
646
669
  await this.switchTo(frame)
647
670
  this.withinLocator = new Locator(frame)
@@ -664,7 +687,7 @@ class Puppeteer extends Helper {
664
687
  const navigationStart = timing.navigationStart
665
688
 
666
689
  const extractedData = {}
667
- dataNames.forEach((name) => {
690
+ dataNames.forEach(name => {
668
691
  extractedData[name] = timing[name] - navigationStart
669
692
  })
670
693
 
@@ -694,17 +717,25 @@ class Puppeteer extends Helper {
694
717
  this.currentRunningTest.artifacts.trace = fileName
695
718
  }
696
719
 
697
- await this.page.goto(url, { waitUntil: this.options.waitForNavigation })
720
+ try {
721
+ await this.page.goto(url, { waitUntil: this.options.waitForNavigation })
722
+ } catch (err) {
723
+ // Handle terminal navigation errors that shouldn't be retried
724
+ if (
725
+ err.message &&
726
+ (err.message.includes('ERR_ABORTED') || err.message.includes('frame was detached') || err.message.includes('Target page, context or browser has been closed') || err.message.includes('Navigation timeout'))
727
+ ) {
728
+ // Mark this as a terminal error to prevent retries
729
+ const terminalError = new Error(err.message)
730
+ terminalError.isTerminal = true
731
+ throw terminalError
732
+ }
733
+ throw err
734
+ }
698
735
 
699
736
  const performanceTiming = JSON.parse(await this.page.evaluate(() => JSON.stringify(window.performance.timing)))
700
737
 
701
- perfTiming = this._extractDataFromPerformanceTiming(
702
- performanceTiming,
703
- 'responseEnd',
704
- 'domInteractive',
705
- 'domContentLoadedEventEnd',
706
- 'loadEventEnd',
707
- )
738
+ perfTiming = this._extractDataFromPerformanceTiming(performanceTiming, 'responseEnd', 'domInteractive', 'domContentLoadedEventEnd', 'loadEventEnd')
708
739
 
709
740
  return this._waitForAction()
710
741
  }
@@ -815,10 +846,7 @@ class Puppeteer extends Helper {
815
846
  return this.executeScript(() => {
816
847
  const body = document.body
817
848
  const html = document.documentElement
818
- window.scrollTo(
819
- 0,
820
- Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight),
821
- )
849
+ window.scrollTo(0, Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight))
822
850
  })
823
851
  }
824
852
 
@@ -836,13 +864,9 @@ class Puppeteer extends Helper {
836
864
  const els = await this._locate(locator)
837
865
  assertElementExists(els, locator, 'Element')
838
866
  const el = els[0]
839
- await el.evaluate((el) => el.scrollIntoView())
867
+ await el.evaluate(el => el.scrollIntoView())
840
868
  const elementCoordinates = await getClickablePoint(els[0])
841
- await this.executeScript(
842
- (x, y) => window.scrollBy(x, y),
843
- elementCoordinates.x + offsetX,
844
- elementCoordinates.y + offsetY,
845
- )
869
+ await this.executeScript((x, y) => window.scrollBy(x, y), elementCoordinates.x + offsetX, elementCoordinates.y + offsetY)
846
870
  } else {
847
871
  await this.executeScript((x, y) => window.scrollTo(x, y), offsetX, offsetY)
848
872
  }
@@ -955,6 +979,12 @@ class Puppeteer extends Helper {
955
979
  return this._locate(locator)
956
980
  }
957
981
 
982
+ async grabWebElement(locator) {
983
+ const els = await this._locate(locator)
984
+ assertElementExists(els, locator)
985
+ return els[0]
986
+ }
987
+
958
988
  /**
959
989
  * Switch focus to a particular tab by its number. It waits tabs loading and then switch tab
960
990
  *
@@ -1025,10 +1055,10 @@ class Puppeteer extends Helper {
1025
1055
  */
1026
1056
  async closeOtherTabs() {
1027
1057
  const pages = await this.browser.pages()
1028
- const otherPages = pages.filter((page) => page !== this.page)
1058
+ const otherPages = pages.filter(page => page !== this.page)
1029
1059
 
1030
1060
  let p = Promise.resolve()
1031
- otherPages.forEach((page) => {
1061
+ otherPages.forEach(page => {
1032
1062
  p = p.then(() => page.close())
1033
1063
  })
1034
1064
  await p
@@ -1061,19 +1091,11 @@ class Puppeteer extends Helper {
1061
1091
  */
1062
1092
  async seeElement(locator) {
1063
1093
  let els = await this._locate(locator)
1064
- els = (await Promise.all(els.map((el) => el.boundingBox() && el))).filter((v) => v)
1094
+ els = (await Promise.all(els.map(el => el.boundingBox() && el))).filter(v => v)
1065
1095
  // Puppeteer visibility was ignored? | Remove when Puppeteer is fixed
1066
- els = await Promise.all(
1067
- els.map(
1068
- async (el) =>
1069
- (await el.evaluate(
1070
- (node) =>
1071
- window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none',
1072
- )) && el,
1073
- ),
1074
- )
1096
+ els = await Promise.all(els.map(async el => (await el.evaluate(node => window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none')) && el))
1075
1097
  try {
1076
- return empty('visible elements').negate(els.filter((v) => v).fill('ELEMENT'))
1098
+ return empty('visible elements').negate(els.filter(v => v).fill('ELEMENT'))
1077
1099
  } catch (e) {
1078
1100
  dontSeeElementError(locator)
1079
1101
  }
@@ -1085,19 +1107,11 @@ class Puppeteer extends Helper {
1085
1107
  */
1086
1108
  async dontSeeElement(locator) {
1087
1109
  let els = await this._locate(locator)
1088
- els = (await Promise.all(els.map((el) => el.boundingBox() && el))).filter((v) => v)
1110
+ els = (await Promise.all(els.map(el => el.boundingBox() && el))).filter(v => v)
1089
1111
  // Puppeteer visibility was ignored? | Remove when Puppeteer is fixed
1090
- els = await Promise.all(
1091
- els.map(
1092
- async (el) =>
1093
- (await el.evaluate(
1094
- (node) =>
1095
- window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none',
1096
- )) && el,
1097
- ),
1098
- )
1112
+ els = await Promise.all(els.map(async el => (await el.evaluate(node => window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none')) && el))
1099
1113
  try {
1100
- return empty('visible elements').assert(els.filter((v) => v).fill('ELEMENT'))
1114
+ return empty('visible elements').assert(els.filter(v => v).fill('ELEMENT'))
1101
1115
  } catch (e) {
1102
1116
  seeElementError(locator)
1103
1117
  }
@@ -1109,7 +1123,7 @@ class Puppeteer extends Helper {
1109
1123
  async seeElementInDOM(locator) {
1110
1124
  const els = await this._locate(locator)
1111
1125
  try {
1112
- return empty('elements on page').negate(els.filter((v) => v).fill('ELEMENT'))
1126
+ return empty('elements on page').negate(els.filter(v => v).fill('ELEMENT'))
1113
1127
  } catch (e) {
1114
1128
  dontSeeElementInDOMError(locator)
1115
1129
  }
@@ -1121,7 +1135,7 @@ class Puppeteer extends Helper {
1121
1135
  async dontSeeElementInDOM(locator) {
1122
1136
  const els = await this._locate(locator)
1123
1137
  try {
1124
- return empty('elements on a page').assert(els.filter((v) => v).fill('ELEMENT'))
1138
+ return empty('elements on a page').assert(els.filter(v => v).fill('ELEMENT'))
1125
1139
  } catch (e) {
1126
1140
  seeElementInDOMError(locator)
1127
1141
  }
@@ -1151,17 +1165,12 @@ class Puppeteer extends Helper {
1151
1165
 
1152
1166
  const els = await findClickable.call(this, matcher, locator)
1153
1167
  if (context) {
1154
- assertElementExists(
1155
- els,
1156
- locator,
1157
- 'Clickable element',
1158
- `was not found inside element ${new Locator(context).toString()}`,
1159
- )
1168
+ assertElementExists(els, locator, 'Clickable element', `was not found inside element ${new Locator(context).toString()}`)
1160
1169
  } else {
1161
1170
  assertElementExists(els, locator, 'Clickable element')
1162
1171
  }
1163
1172
  const elem = els[0]
1164
- return this.executeScript((el) => {
1173
+ return this.executeScript(el => {
1165
1174
  if (document.activeElement instanceof HTMLElement) {
1166
1175
  document.activeElement.blur()
1167
1176
  }
@@ -1220,8 +1229,8 @@ class Puppeteer extends Helper {
1220
1229
  let fileName
1221
1230
  await this.page.setRequestInterception(true)
1222
1231
 
1223
- const xRequest = await new Promise((resolve) => {
1224
- this.page.on('request', (request) => {
1232
+ const xRequest = await new Promise(resolve => {
1233
+ this.page.on('request', request => {
1225
1234
  console.log('rq', request, customName)
1226
1235
  const grabbedFileName = request.url().split('/')[request.url().split('/').length - 1]
1227
1236
  const fileExtension = request.url().split('/')[request.url().split('/').length - 1].split('.')[1]
@@ -1248,7 +1257,7 @@ class Puppeteer extends Helper {
1248
1257
  }
1249
1258
 
1250
1259
  const cookies = await this.page.cookies()
1251
- options.headers.Cookie = cookies.map((ck) => `${ck.name}=${ck.value}`).join(';')
1260
+ options.headers.Cookie = cookies.map(ck => `${ck.name}=${ck.value}`).join(';')
1252
1261
 
1253
1262
  const response = await axios({
1254
1263
  method: options.method,
@@ -1302,8 +1311,16 @@ class Puppeteer extends Helper {
1302
1311
  */
1303
1312
  async checkOption(field, context = null) {
1304
1313
  const elm = await this._locateCheckable(field, context)
1305
- const curentlyChecked = await elm.getProperty('checked').then((checkedProperty) => checkedProperty.jsonValue())
1306
- // Only check if NOT currently checked
1314
+ let curentlyChecked = await elm
1315
+ .getProperty('checked')
1316
+ .then(checkedProperty => checkedProperty.jsonValue())
1317
+ .catch(() => null)
1318
+
1319
+ if (!curentlyChecked) {
1320
+ const ariaChecked = await elm.evaluate(el => el.getAttribute('aria-checked'))
1321
+ curentlyChecked = ariaChecked === 'true'
1322
+ }
1323
+
1307
1324
  if (!curentlyChecked) {
1308
1325
  await elm.click()
1309
1326
  return this._waitForAction()
@@ -1315,8 +1332,16 @@ class Puppeteer extends Helper {
1315
1332
  */
1316
1333
  async uncheckOption(field, context = null) {
1317
1334
  const elm = await this._locateCheckable(field, context)
1318
- const curentlyChecked = await elm.getProperty('checked').then((checkedProperty) => checkedProperty.jsonValue())
1319
- // Only uncheck if currently checked
1335
+ let curentlyChecked = await elm
1336
+ .getProperty('checked')
1337
+ .then(checkedProperty => checkedProperty.jsonValue())
1338
+ .catch(() => null)
1339
+
1340
+ if (!curentlyChecked) {
1341
+ const ariaChecked = await elm.evaluate(el => el.getAttribute('aria-checked'))
1342
+ curentlyChecked = ariaChecked === 'true'
1343
+ }
1344
+
1320
1345
  if (curentlyChecked) {
1321
1346
  await elm.click()
1322
1347
  return this._waitForAction()
@@ -1408,12 +1433,12 @@ class Puppeteer extends Helper {
1408
1433
  const els = await findVisibleFields.call(this, field)
1409
1434
  assertElementExists(els, field, 'Field')
1410
1435
  const el = els[0]
1411
- const tag = await el.getProperty('tagName').then((el) => el.jsonValue())
1412
- const editable = await el.getProperty('contenteditable').then((el) => el.jsonValue())
1436
+ const tag = await el.getProperty('tagName').then(el => el.jsonValue())
1437
+ const editable = await el.getProperty('contenteditable').then(el => el.jsonValue())
1413
1438
  if (tag === 'INPUT' || tag === 'TEXTAREA') {
1414
- await this._evaluateHandeInContext((el) => (el.value = ''), el)
1439
+ await this._evaluateHandeInContext(el => (el.value = ''), el)
1415
1440
  } else if (editable) {
1416
- await this._evaluateHandeInContext((el) => (el.innerHTML = ''), el)
1441
+ await this._evaluateHandeInContext(el => (el.innerHTML = ''), el)
1417
1442
  }
1418
1443
 
1419
1444
  highlightActiveElement.call(this, el, await this._getContext())
@@ -1483,7 +1508,7 @@ class Puppeteer extends Helper {
1483
1508
  const els = await findVisibleFields.call(this, select)
1484
1509
  assertElementExists(els, select, 'Selectable field')
1485
1510
  const el = els[0]
1486
- if ((await el.getProperty('tagName').then((t) => t.jsonValue())) !== 'SELECT') {
1511
+ if ((await el.getProperty('tagName').then(t => t.jsonValue())) !== 'SELECT') {
1487
1512
  throw new Error('Element is not <select>')
1488
1513
  }
1489
1514
  highlightActiveElement.call(this, els[0], await this._getContext())
@@ -1493,15 +1518,15 @@ class Puppeteer extends Helper {
1493
1518
  const opt = xpathLocator.literal(option[key])
1494
1519
  let optEl = await findElements.call(this, el, { xpath: Locator.select.byVisibleText(opt) })
1495
1520
  if (optEl.length) {
1496
- this._evaluateHandeInContext((el) => (el.selected = true), optEl[0])
1521
+ this._evaluateHandeInContext(el => (el.selected = true), optEl[0])
1497
1522
  continue
1498
1523
  }
1499
1524
  optEl = await findElements.call(this, el, { xpath: Locator.select.byValue(opt) })
1500
1525
  if (optEl.length) {
1501
- this._evaluateHandeInContext((el) => (el.selected = true), optEl[0])
1526
+ this._evaluateHandeInContext(el => (el.selected = true), optEl[0])
1502
1527
  }
1503
1528
  }
1504
- await this._evaluateHandeInContext((element) => {
1529
+ await this._evaluateHandeInContext(element => {
1505
1530
  element.dispatchEvent(new Event('input', { bubbles: true }))
1506
1531
  element.dispatchEvent(new Event('change', { bubbles: true }))
1507
1532
  }, el)
@@ -1515,19 +1540,11 @@ class Puppeteer extends Helper {
1515
1540
  */
1516
1541
  async grabNumberOfVisibleElements(locator) {
1517
1542
  let els = await this._locate(locator)
1518
- els = (await Promise.all(els.map((el) => el.boundingBox() && el))).filter((v) => v)
1543
+ els = (await Promise.all(els.map(el => el.boundingBox() && el))).filter(v => v)
1519
1544
  // Puppeteer visibility was ignored? | Remove when Puppeteer is fixed
1520
- els = await Promise.all(
1521
- els.map(
1522
- async (el) =>
1523
- (await el.evaluate(
1524
- (node) =>
1525
- window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none',
1526
- )) && el,
1527
- ),
1528
- )
1545
+ els = await Promise.all(els.map(async el => (await el.evaluate(node => window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none')) && el))
1529
1546
 
1530
- return els.filter((v) => v).length
1547
+ return els.filter(v => v).length
1531
1548
  }
1532
1549
 
1533
1550
  /**
@@ -1635,9 +1652,7 @@ class Puppeteer extends Helper {
1635
1652
  */
1636
1653
  async seeNumberOfElements(locator, num) {
1637
1654
  const elements = await this._locate(locator)
1638
- return equals(
1639
- `expected number of elements (${new Locator(locator)}) is ${num}, but found ${elements.length}`,
1640
- ).assert(elements.length, num)
1655
+ return equals(`expected number of elements (${new Locator(locator)}) is ${num}, but found ${elements.length}`).assert(elements.length, num)
1641
1656
  }
1642
1657
 
1643
1658
  /**
@@ -1647,10 +1662,7 @@ class Puppeteer extends Helper {
1647
1662
  */
1648
1663
  async seeNumberOfVisibleElements(locator, num) {
1649
1664
  const res = await this.grabNumberOfVisibleElements(locator)
1650
- return equals(`expected number of visible elements (${new Locator(locator)}) is ${num}, but found ${res}`).assert(
1651
- res,
1652
- num,
1653
- )
1665
+ return equals(`expected number of visible elements (${new Locator(locator)}) is ${num}, but found ${res}`).assert(res, num)
1654
1666
  }
1655
1667
 
1656
1668
  /**
@@ -1669,7 +1681,7 @@ class Puppeteer extends Helper {
1669
1681
  */
1670
1682
  async seeCookie(name) {
1671
1683
  const cookies = await this.page.cookies()
1672
- empty(`cookie ${name} to be set`).negate(cookies.filter((c) => c.name === name))
1684
+ empty(`cookie ${name} to be set`).negate(cookies.filter(c => c.name === name))
1673
1685
  }
1674
1686
 
1675
1687
  /**
@@ -1677,7 +1689,7 @@ class Puppeteer extends Helper {
1677
1689
  */
1678
1690
  async dontSeeCookie(name) {
1679
1691
  const cookies = await this.page.cookies()
1680
- empty(`cookie ${name} not to be set`).assert(cookies.filter((c) => c.name === name))
1692
+ empty(`cookie ${name} not to be set`).assert(cookies.filter(c => c.name === name))
1681
1693
  }
1682
1694
 
1683
1695
  /**
@@ -1688,7 +1700,7 @@ class Puppeteer extends Helper {
1688
1700
  async grabCookie(name) {
1689
1701
  const cookies = await this.page.cookies()
1690
1702
  if (!name) return cookies
1691
- const cookie = cookies.filter((c) => c.name === name)
1703
+ const cookie = cookies.filter(c => c.name === name)
1692
1704
  if (cookie[0]) return cookie[0]
1693
1705
  }
1694
1706
 
@@ -1708,9 +1720,9 @@ class Puppeteer extends Helper {
1708
1720
 
1709
1721
  return promiseRetry(
1710
1722
  async (retry, number) => {
1711
- const _grabCookie = async (name) => {
1723
+ const _grabCookie = async name => {
1712
1724
  const cookies = await this.page.cookies()
1713
- const cookie = cookies.filter((c) => c.name === name)
1725
+ const cookie = cookies.filter(c => c.name === name)
1714
1726
  if (cookie.length === 0) throw Error(`Cookie ${name} is not found after ${retries}s`)
1715
1727
  }
1716
1728
 
@@ -1735,7 +1747,7 @@ class Puppeteer extends Helper {
1735
1747
  if (!name) {
1736
1748
  return this.page.deleteCookie.apply(this.page, cookies)
1737
1749
  }
1738
- const cookie = cookies.filter((c) => c.name === name)
1750
+ const cookie = cookies.filter(c => c.name === name)
1739
1751
  if (!cookie[0]) return
1740
1752
  return this.page.deleteCookie(cookie[0])
1741
1753
  }
@@ -1760,8 +1772,8 @@ class Puppeteer extends Helper {
1760
1772
  async executeAsyncScript(...args) {
1761
1773
  const asyncFn = function () {
1762
1774
  const args = Array.from(arguments)
1763
- const fn = eval(`(${args.shift()})`) // eslint-disable-line no-eval
1764
- return new Promise((done) => {
1775
+ const fn = eval(`(${args.shift()})`)
1776
+ return new Promise(done => {
1765
1777
  args.push(done)
1766
1778
  fn.apply(null, args)
1767
1779
  })
@@ -1828,7 +1840,7 @@ class Puppeteer extends Helper {
1828
1840
  */
1829
1841
  async grabHTMLFromAll(locator) {
1830
1842
  const els = await this._locate(locator)
1831
- const values = await Promise.all(els.map((el) => el.evaluate((element) => element.innerHTML, el)))
1843
+ const values = await Promise.all(els.map(el => el.evaluate(element => element.innerHTML, el)))
1832
1844
  return values
1833
1845
  }
1834
1846
 
@@ -1851,10 +1863,8 @@ class Puppeteer extends Helper {
1851
1863
  */
1852
1864
  async grabCssPropertyFromAll(locator, cssProperty) {
1853
1865
  const els = await this._locate(locator)
1854
- const res = await Promise.all(
1855
- els.map((el) => el.evaluate((el) => JSON.parse(JSON.stringify(getComputedStyle(el))), el)),
1856
- )
1857
- const cssValues = res.map((props) => props[toCamelCase(cssProperty)])
1866
+ const res = await Promise.all(els.map(el => el.evaluate(el => JSON.parse(JSON.stringify(getComputedStyle(el))), el)))
1867
+ const cssValues = res.map(props => props[toCamelCase(cssProperty)])
1858
1868
 
1859
1869
  return cssValues
1860
1870
  }
@@ -1897,19 +1907,16 @@ class Puppeteer extends Helper {
1897
1907
  }
1898
1908
  }
1899
1909
 
1900
- const values = Object.keys(cssPropertiesCamelCase).map((key) => cssPropertiesCamelCase[key])
1910
+ const values = Object.keys(cssPropertiesCamelCase).map(key => cssPropertiesCamelCase[key])
1901
1911
  if (!Array.isArray(props)) props = [props]
1902
1912
  let chunked = chunkArray(props, values.length)
1903
- chunked = chunked.filter((val) => {
1913
+ chunked = chunked.filter(val => {
1904
1914
  for (let i = 0; i < val.length; ++i) {
1905
- // eslint-disable-next-line eqeqeq
1906
1915
  if (val[i] != values[i]) return false
1907
1916
  }
1908
1917
  return true
1909
1918
  })
1910
- return equals(
1911
- `all elements (${new Locator(locator)}) to have CSS property ${JSON.stringify(cssProperties)}`,
1912
- ).assert(chunked.length, elemAmount)
1919
+ return equals(`all elements (${new Locator(locator)}) to have CSS property ${JSON.stringify(cssProperties)}`).assert(chunked.length, elemAmount)
1913
1920
  }
1914
1921
 
1915
1922
  /**
@@ -1922,7 +1929,7 @@ class Puppeteer extends Helper {
1922
1929
 
1923
1930
  const expectedAttributes = Object.entries(attributes)
1924
1931
 
1925
- const valuesPromises = elements.map(async (element) => {
1932
+ const valuesPromises = elements.map(async element => {
1926
1933
  const elementAttributes = {}
1927
1934
  await Promise.all(
1928
1935
  expectedAttributes.map(async ([attribute, expectedValue]) => {
@@ -1935,7 +1942,7 @@ class Puppeteer extends Helper {
1935
1942
 
1936
1943
  const actualAttributes = await Promise.all(valuesPromises)
1937
1944
 
1938
- const matchingElements = actualAttributes.filter((attrs) =>
1945
+ const matchingElements = actualAttributes.filter(attrs =>
1939
1946
  expectedAttributes.every(([attribute, expectedValue]) => {
1940
1947
  const actualValue = attrs[attribute]
1941
1948
  if (!actualValue) return false
@@ -1947,10 +1954,7 @@ class Puppeteer extends Helper {
1947
1954
  const elementsCount = elements.length
1948
1955
  const matchingCount = matchingElements.length
1949
1956
 
1950
- return equals(`all elements (${new Locator(locator)}) to have attributes ${JSON.stringify(attributes)}`).assert(
1951
- matchingCount,
1952
- elementsCount,
1953
- )
1957
+ return equals(`all elements (${new Locator(locator)}) to have attributes ${JSON.stringify(attributes)}`).assert(matchingCount, elementsCount)
1954
1958
  }
1955
1959
 
1956
1960
  /**
@@ -2080,7 +2084,7 @@ class Puppeteer extends Helper {
2080
2084
  * {{> wait }}
2081
2085
  */
2082
2086
  async wait(sec) {
2083
- return new Promise((done) => {
2087
+ return new Promise(done => {
2084
2088
  setTimeout(done, sec * 1000)
2085
2089
  })
2086
2090
  }
@@ -2100,20 +2104,18 @@ class Puppeteer extends Helper {
2100
2104
  if (!els || els.length === 0) {
2101
2105
  return false
2102
2106
  }
2103
- return Array.prototype.filter.call(els, (el) => !el.disabled).length > 0
2107
+ return Array.prototype.filter.call(els, el => !el.disabled).length > 0
2104
2108
  }
2105
2109
  waiter = context.waitForFunction(enabledFn, { timeout: waitTimeout }, locator.value)
2106
2110
  } else {
2107
2111
  const enabledFn = function (locator, $XPath) {
2108
- eval($XPath) // eslint-disable-line no-eval
2109
- return $XPath(null, locator).filter((el) => !el.disabled).length > 0
2112
+ eval($XPath)
2113
+ return $XPath(null, locator).filter(el => !el.disabled).length > 0
2110
2114
  }
2111
2115
  waiter = context.waitForFunction(enabledFn, { timeout: waitTimeout }, locator.value, $XPath.toString())
2112
2116
  }
2113
- return waiter.catch((err) => {
2114
- throw new Error(
2115
- `element (${locator.toString()}) still not enabled after ${waitTimeout / 1000} sec\n${err.message}`,
2116
- )
2117
+ return waiter.catch(err => {
2118
+ throw new Error(`element (${locator.toString()}) still not enabled after ${waitTimeout / 1000} sec\n${err.message}`)
2117
2119
  })
2118
2120
  }
2119
2121
 
@@ -2132,21 +2134,19 @@ class Puppeteer extends Helper {
2132
2134
  if (!els || els.length === 0) {
2133
2135
  return false
2134
2136
  }
2135
- return Array.prototype.filter.call(els, (el) => (el.value.toString() || '').indexOf(value) !== -1).length > 0
2137
+ return Array.prototype.filter.call(els, el => (el.value.toString() || '').indexOf(value) !== -1).length > 0
2136
2138
  }
2137
2139
  waiter = context.waitForFunction(valueFn, { timeout: waitTimeout }, locator.value, value)
2138
2140
  } else {
2139
2141
  const valueFn = function (locator, $XPath, value) {
2140
- eval($XPath) // eslint-disable-line no-eval
2141
- return $XPath(null, locator).filter((el) => (el.value || '').indexOf(value) !== -1).length > 0
2142
+ eval($XPath)
2143
+ return $XPath(null, locator).filter(el => (el.value || '').indexOf(value) !== -1).length > 0
2142
2144
  }
2143
2145
  waiter = context.waitForFunction(valueFn, { timeout: waitTimeout }, locator.value, $XPath.toString(), value)
2144
2146
  }
2145
- return waiter.catch((err) => {
2147
+ return waiter.catch(err => {
2146
2148
  const loc = locator.toString()
2147
- throw new Error(
2148
- `element (${loc}) is not in DOM or there is no element(${loc}) with value "${value}" after ${waitTimeout / 1000} sec\n${err.message}`,
2149
- )
2149
+ throw new Error(`element (${loc}) is not in DOM or there is no element(${loc}) with value "${value}" after ${waitTimeout / 1000} sec\n${err.message}`)
2150
2150
  })
2151
2151
  }
2152
2152
 
@@ -2165,20 +2165,18 @@ class Puppeteer extends Helper {
2165
2165
  if (!els || els.length === 0) {
2166
2166
  return false
2167
2167
  }
2168
- return Array.prototype.filter.call(els, (el) => el.offsetParent !== null).length === num
2168
+ return Array.prototype.filter.call(els, el => el.offsetParent !== null).length === num
2169
2169
  }
2170
2170
  waiter = context.waitForFunction(visibleFn, { timeout: waitTimeout }, locator.value, num)
2171
2171
  } else {
2172
2172
  const visibleFn = function (locator, $XPath, num) {
2173
- eval($XPath) // eslint-disable-line no-eval
2174
- return $XPath(null, locator).filter((el) => el.offsetParent !== null).length === num
2173
+ eval($XPath)
2174
+ return $XPath(null, locator).filter(el => el.offsetParent !== null).length === num
2175
2175
  }
2176
2176
  waiter = context.waitForFunction(visibleFn, { timeout: waitTimeout }, locator.value, $XPath.toString(), num)
2177
2177
  }
2178
- return waiter.catch((err) => {
2179
- throw new Error(
2180
- `The number of elements (${locator.toString()}) is not ${num} after ${waitTimeout / 1000} sec\n${err.message}`,
2181
- )
2178
+ return waiter.catch(err => {
2179
+ throw new Error(`The number of elements (${locator.toString()}) is not ${num} after ${waitTimeout / 1000} sec\n${err.message}`)
2182
2180
  })
2183
2181
  }
2184
2182
 
@@ -2189,11 +2187,10 @@ class Puppeteer extends Helper {
2189
2187
  const els = await this._locate(locator)
2190
2188
  assertElementExists(els, locator)
2191
2189
 
2192
- return this.waitForFunction(isElementClickable, [els[0]], waitTimeout).catch(async (e) => {
2193
- if (/Waiting failed/i.test(e.message) || /failed: timeout/i.test(e.message)) {
2194
- throw new Error(
2195
- `element ${new Locator(locator).toString()} still not clickable after ${waitTimeout || this.options.waitForTimeout / 1000} sec`,
2196
- )
2190
+ return this.waitForFunction(isElementClickable, [els[0]], waitTimeout).catch(async e => {
2191
+ const errorMessage = e?.message || String(e)
2192
+ if (/Waiting failed/i.test(errorMessage) || /failed: timeout/i.test(errorMessage)) {
2193
+ throw new Error(`element ${new Locator(locator).toString()} still not clickable after ${waitTimeout || this.options.waitForTimeout / 1000} sec`)
2197
2194
  } else {
2198
2195
  throw e
2199
2196
  }
@@ -2215,10 +2212,8 @@ class Puppeteer extends Helper {
2215
2212
  } else {
2216
2213
  waiter = _waitForElement.call(this, locator, { timeout: waitTimeout })
2217
2214
  }
2218
- return waiter.catch((err) => {
2219
- throw new Error(
2220
- `element (${locator.toString()}) still not present on page after ${waitTimeout / 1000} sec\n${err.message}`,
2221
- )
2215
+ return waiter.catch(err => {
2216
+ throw new Error(`element (${locator.toString()}) still not present on page after ${waitTimeout / 1000} sec\n${err.message}`)
2222
2217
  })
2223
2218
  }
2224
2219
 
@@ -2238,10 +2233,8 @@ class Puppeteer extends Helper {
2238
2233
  } else {
2239
2234
  waiter = _waitForElement.call(this, locator, { timeout: waitTimeout, visible: true })
2240
2235
  }
2241
- return waiter.catch((err) => {
2242
- throw new Error(
2243
- `element (${locator.toString()}) still not visible after ${waitTimeout / 1000} sec\n${err.message}`,
2244
- )
2236
+ return waiter.catch(err => {
2237
+ throw new Error(`element (${locator.toString()}) still not visible after ${waitTimeout / 1000} sec\n${err.message}`)
2245
2238
  })
2246
2239
  }
2247
2240
 
@@ -2259,7 +2252,7 @@ class Puppeteer extends Helper {
2259
2252
  } else {
2260
2253
  waiter = _waitForElement.call(this, locator, { timeout: waitTimeout, hidden: true })
2261
2254
  }
2262
- return waiter.catch((err) => {
2255
+ return waiter.catch(err => {
2263
2256
  throw new Error(`element (${locator.toString()}) still visible after ${waitTimeout / 1000} sec\n${err.message}`)
2264
2257
  })
2265
2258
  }
@@ -2277,10 +2270,8 @@ class Puppeteer extends Helper {
2277
2270
  } else {
2278
2271
  waiter = _waitForElement.call(this, locator, { timeout: waitTimeout, hidden: true })
2279
2272
  }
2280
- return waiter.catch((err) => {
2281
- throw new Error(
2282
- `element (${locator.toString()}) still not hidden after ${waitTimeout / 1000} sec\n${err.message}`,
2283
- )
2273
+ return waiter.catch(err => {
2274
+ throw new Error(`element (${locator.toString()}) still not hidden after ${waitTimeout / 1000} sec\n${err.message}`)
2284
2275
  })
2285
2276
  }
2286
2277
 
@@ -2317,14 +2308,14 @@ class Puppeteer extends Helper {
2317
2308
 
2318
2309
  return this.page
2319
2310
  .waitForFunction(
2320
- (urlPart) => {
2311
+ urlPart => {
2321
2312
  const currUrl = decodeURIComponent(decodeURIComponent(decodeURIComponent(window.location.href)))
2322
2313
  return currUrl.indexOf(urlPart) > -1
2323
2314
  },
2324
2315
  { timeout: waitTimeout },
2325
2316
  urlPart,
2326
2317
  )
2327
- .catch(async (e) => {
2318
+ .catch(async e => {
2328
2319
  const currUrl = await this._getPageUrl() // Required because the waitForFunction can't return data.
2329
2320
  if (/Waiting failed:/i.test(e.message) || /failed: timeout/i.test(e.message)) {
2330
2321
  throw new Error(`expected url to include ${urlPart}, but found ${currUrl}`)
@@ -2347,14 +2338,14 @@ class Puppeteer extends Helper {
2347
2338
 
2348
2339
  return this.page
2349
2340
  .waitForFunction(
2350
- (urlPart) => {
2341
+ urlPart => {
2351
2342
  const currUrl = decodeURIComponent(decodeURIComponent(decodeURIComponent(window.location.href)))
2352
2343
  return currUrl.indexOf(urlPart) > -1
2353
2344
  },
2354
2345
  { timeout: waitTimeout },
2355
2346
  urlPart,
2356
2347
  )
2357
- .catch(async (e) => {
2348
+ .catch(async e => {
2358
2349
  const currUrl = await this._getPageUrl() // Required because the waitForFunction can't return data.
2359
2350
  if (/Waiting failed/i.test(e.message) || /failed: timeout/i.test(e.message)) {
2360
2351
  throw new Error(`expected url to be ${urlPart}, but found ${currUrl}`)
@@ -2391,7 +2382,7 @@ class Puppeteer extends Helper {
2391
2382
  if (locator.isXPath()) {
2392
2383
  waiter = contextObject.waitForFunction(
2393
2384
  (locator, text, $XPath) => {
2394
- eval($XPath) // eslint-disable-line no-eval
2385
+ eval($XPath)
2395
2386
  const el = $XPath(null, locator)
2396
2387
  if (!el.length) return false
2397
2388
  return el[0].innerText.indexOf(text) > -1
@@ -2403,14 +2394,10 @@ class Puppeteer extends Helper {
2403
2394
  )
2404
2395
  }
2405
2396
  } else {
2406
- waiter = contextObject.waitForFunction(
2407
- (text) => document.body && document.body.innerText.indexOf(text) > -1,
2408
- { timeout: waitTimeout },
2409
- text,
2410
- )
2397
+ waiter = contextObject.waitForFunction(text => document.body && document.body.innerText.indexOf(text) > -1, { timeout: waitTimeout }, text)
2411
2398
  }
2412
2399
 
2413
- return waiter.catch((err) => {
2400
+ return waiter.catch(err => {
2414
2401
  throw new Error(`Text "${text}" was not found on page after ${waitTimeout / 1000} sec\n${err.message}`)
2415
2402
  })
2416
2403
  }
@@ -2543,12 +2530,12 @@ class Puppeteer extends Helper {
2543
2530
  waiter = context.waitForFunction(visibleFn, { timeout: waitTimeout }, locator.value)
2544
2531
  } else {
2545
2532
  const visibleFn = function (locator, $XPath) {
2546
- eval($XPath) // eslint-disable-line no-eval
2533
+ eval($XPath)
2547
2534
  return $XPath(null, locator).length === 0
2548
2535
  }
2549
2536
  waiter = context.waitForFunction(visibleFn, { timeout: waitTimeout }, locator.value, $XPath.toString())
2550
2537
  }
2551
- return waiter.catch((err) => {
2538
+ return waiter.catch(err => {
2552
2539
  throw new Error(`element (${locator.toString()}) still on page after ${waitTimeout / 1000} sec\n${err.message}`)
2553
2540
  })
2554
2541
  }
@@ -2589,7 +2576,7 @@ class Puppeteer extends Helper {
2589
2576
  async mockRoute(url, handler) {
2590
2577
  await this.page.setRequestInterception(true)
2591
2578
 
2592
- this.page.on('request', (interceptedRequest) => {
2579
+ this.page.on('request', interceptedRequest => {
2593
2580
  if (interceptedRequest.url().match(url)) {
2594
2581
  // @ts-ignore
2595
2582
  handler(interceptedRequest)
@@ -2614,7 +2601,7 @@ class Puppeteer extends Helper {
2614
2601
  this.page.off('request')
2615
2602
 
2616
2603
  // Resume normal request handling for the given URL
2617
- this.page.on('request', (interceptedRequest) => {
2604
+ this.page.on('request', interceptedRequest => {
2618
2605
  if (interceptedRequest.url().includes(url)) {
2619
2606
  interceptedRequest.continue()
2620
2607
  } else {
@@ -2650,7 +2637,7 @@ class Puppeteer extends Helper {
2650
2637
 
2651
2638
  await this.page.setRequestInterception(true)
2652
2639
 
2653
- this.page.on('request', (request) => {
2640
+ this.page.on('request', request => {
2654
2641
  const information = {
2655
2642
  url: request.url(),
2656
2643
  method: request.method(),
@@ -2711,15 +2698,15 @@ class Puppeteer extends Helper {
2711
2698
  await this.cdpSession.send('Network.enable')
2712
2699
  await this.cdpSession.send('Page.enable')
2713
2700
 
2714
- this.cdpSession.on('Network.webSocketFrameReceived', (payload) => {
2701
+ this.cdpSession.on('Network.webSocketFrameReceived', payload => {
2715
2702
  this._logWebsocketMessages(this._getWebSocketLog('RECEIVED', payload))
2716
2703
  })
2717
2704
 
2718
- this.cdpSession.on('Network.webSocketFrameSent', (payload) => {
2705
+ this.cdpSession.on('Network.webSocketFrameSent', payload => {
2719
2706
  this._logWebsocketMessages(this._getWebSocketLog('SENT', payload))
2720
2707
  })
2721
2708
 
2722
- this.cdpSession.on('Network.webSocketFrameError', (payload) => {
2709
+ this.cdpSession.on('Network.webSocketFrameError', payload => {
2723
2710
  this._logWebsocketMessages(this._getWebSocketLog('ERROR', payload))
2724
2711
  })
2725
2712
  }
@@ -2743,9 +2730,7 @@ class Puppeteer extends Helper {
2743
2730
  grabWebSocketMessages() {
2744
2731
  if (!this.recordingWebSocketMessages) {
2745
2732
  if (!this.recordedWebSocketMessagesAtLeastOnce) {
2746
- throw new Error(
2747
- 'Failure in test automation. You use "I.grabWebSocketMessages", but "I.startRecordingWebSocketMessages" was never called before.',
2748
- )
2733
+ throw new Error('Failure in test automation. You use "I.grabWebSocketMessages", but "I.startRecordingWebSocketMessages" was never called before.')
2749
2734
  }
2750
2735
  }
2751
2736
  return this.webSocketMessages
@@ -2775,17 +2760,27 @@ class Puppeteer extends Helper {
2775
2760
  }
2776
2761
  }
2777
2762
 
2778
- module.exports = Puppeteer
2779
-
2780
2763
  async function findElements(matcher, locator) {
2781
- if (locator.react) return findReactElements.call(this, locator)
2782
- locator = new Locator(locator, 'css')
2783
- if (!locator.isXPath()) return matcher.$$(locator.simplify())
2784
- // puppeteer version < 19.4.0 is no longer supported. This one is backward support.
2785
- if (puppeteer.default?.defaultBrowserRevision) {
2786
- return matcher.$$(`xpath/${locator.value}`)
2787
- }
2788
- return matcher.$x(locator.value)
2764
+ const matchedLocator = new Locator(locator, 'css')
2765
+
2766
+ if (matchedLocator.type === 'react') return findReactElements.call(this, matchedLocator)
2767
+ if (matchedLocator.isRole()) return findByRole.call(this, matcher, matchedLocator)
2768
+
2769
+ if (!matchedLocator.isXPath()) return matcher.$$(matchedLocator.simplify())
2770
+
2771
+ // Handle backward compatibility for different Puppeteer versions
2772
+ // Puppeteer >= 19.4.0 uses xpath/ syntax, older versions use $x
2773
+ try {
2774
+ // Try the new xpath syntax first (for Puppeteer >= 19.4.0)
2775
+ return await matcher.$$(`xpath/${matchedLocator.value}`)
2776
+ } catch (error) {
2777
+ // Fall back to the old $x method for older Puppeteer versions
2778
+ if (matcher.$x && typeof matcher.$x === 'function') {
2779
+ return await matcher.$x(matchedLocator.value)
2780
+ }
2781
+ // If both methods fail, re-throw the original error
2782
+ throw error
2783
+ }
2789
2784
  }
2790
2785
 
2791
2786
  async function proceedClick(locator, context = null, options = {}) {
@@ -2797,12 +2792,7 @@ async function proceedClick(locator, context = null, options = {}) {
2797
2792
  }
2798
2793
  const els = await findClickable.call(this, matcher, locator)
2799
2794
  if (context) {
2800
- assertElementExists(
2801
- els,
2802
- locator,
2803
- 'Clickable element',
2804
- `was not found inside element ${new Locator(context).toString()}`,
2805
- )
2795
+ assertElementExists(els, locator, 'Clickable element', `was not found inside element ${new Locator(context).toString()}`)
2806
2796
  } else {
2807
2797
  assertElementExists(els, locator, 'Clickable element')
2808
2798
  }
@@ -2820,12 +2810,12 @@ async function proceedClick(locator, context = null, options = {}) {
2820
2810
  }
2821
2811
 
2822
2812
  async function findClickable(matcher, locator) {
2823
- if (locator.react) return findReactElements.call(this, locator)
2824
- locator = new Locator(locator)
2825
- if (!locator.isFuzzy()) return findElements.call(this, matcher, locator)
2813
+ const matchedLocator = new Locator(locator)
2814
+
2815
+ if (!matchedLocator.isFuzzy()) return findElements.call(this, matcher, matchedLocator)
2826
2816
 
2827
2817
  let els
2828
- const literal = xpathLocator.literal(locator.value)
2818
+ const literal = xpathLocator.literal(matchedLocator.value)
2829
2819
 
2830
2820
  els = await findElements.call(this, matcher, Locator.clickable.narrow(literal))
2831
2821
  if (els.length) return els
@@ -2840,7 +2830,15 @@ async function findClickable(matcher, locator) {
2840
2830
  // Do nothing
2841
2831
  }
2842
2832
 
2843
- return findElements.call(this, matcher, locator.value) // by css or xpath
2833
+ // Try ARIA selector for accessible name
2834
+ try {
2835
+ els = await matcher.$$(`::-p-aria(${matchedLocator.value})`)
2836
+ if (els.length) return els
2837
+ } catch (err) {
2838
+ // ARIA selector not supported or failed
2839
+ }
2840
+
2841
+ return findElements.call(this, matcher, matchedLocator.value) // by css or xpath
2844
2842
  }
2845
2843
 
2846
2844
  async function proceedSee(assertType, text, context, strict = false) {
@@ -2854,23 +2852,25 @@ async function proceedSee(assertType, text, context, strict = false) {
2854
2852
  el = await this.context.$('body')
2855
2853
  }
2856
2854
 
2857
- allText = [await el.getProperty('innerText').then((p) => p.jsonValue())]
2855
+ allText = [await el.getProperty('innerText').then(p => p.jsonValue())]
2858
2856
  description = 'web application'
2859
2857
  } else {
2860
2858
  const locator = new Locator(context, 'css')
2861
2859
  description = `element ${locator.toString()}`
2862
2860
  const els = await this._locate(locator)
2863
2861
  assertElementExists(els, locator.toString())
2864
- allText = await Promise.all(els.map((el) => el.getProperty('innerText').then((p) => p.jsonValue())))
2862
+ allText = await Promise.all(els.map(el => el.getProperty('innerText').then(p => p.jsonValue())))
2863
+ }
2864
+
2865
+ if (store?.currentStep?.opts?.ignoreCase === true) {
2866
+ text = text.toLowerCase()
2867
+ allText = allText.map(elText => elText.toLowerCase())
2865
2868
  }
2866
2869
 
2867
2870
  if (strict) {
2868
- return allText.map((elText) => equals(description)[assertType](text, elText))
2871
+ return allText.map(elText => equals(description)[assertType](text, elText))
2869
2872
  }
2870
- return stringIncludes(description)[assertType](
2871
- normalizeSpacesInString(text),
2872
- normalizeSpacesInString(allText.join(' | ')),
2873
- )
2873
+ return stringIncludes(description)[assertType](normalizeSpacesInString(text), normalizeSpacesInString(allText.join(' | ')))
2874
2874
  }
2875
2875
 
2876
2876
  async function findCheckable(locator, context) {
@@ -2882,10 +2882,10 @@ async function findCheckable(locator, context) {
2882
2882
 
2883
2883
  const matchedLocator = new Locator(locator)
2884
2884
  if (!matchedLocator.isFuzzy()) {
2885
- return findElements.call(this, contextEl, matchedLocator.simplify())
2885
+ return findElements.call(this, contextEl, matchedLocator)
2886
2886
  }
2887
2887
 
2888
- const literal = xpathLocator.literal(locator)
2888
+ const literal = xpathLocator.literal(matchedLocator.value)
2889
2889
  let els = await findElements.call(this, contextEl, Locator.checkable.byText(literal))
2890
2890
  if (els.length) {
2891
2891
  return els
@@ -2894,21 +2894,45 @@ async function findCheckable(locator, context) {
2894
2894
  if (els.length) {
2895
2895
  return els
2896
2896
  }
2897
- return findElements.call(this, contextEl, locator)
2897
+
2898
+ // Try ARIA selector for accessible name
2899
+ try {
2900
+ els = await contextEl.$$(`::-p-aria(${matchedLocator.value})`)
2901
+ if (els.length) return els
2902
+ } catch (err) {
2903
+ // ARIA selector not supported or failed
2904
+ }
2905
+
2906
+ return findElements.call(this, contextEl, matchedLocator.value)
2898
2907
  }
2899
2908
 
2900
2909
  async function proceedIsChecked(assertType, option) {
2901
2910
  let els = await findCheckable.call(this, option)
2902
2911
  assertElementExists(els, option, 'Checkable')
2903
- els = await Promise.all(els.map((el) => el.getProperty('checked')))
2904
- els = await Promise.all(els.map((el) => el.jsonValue()))
2905
- const selected = els.reduce((prev, cur) => prev || cur)
2912
+
2913
+ const checkedStates = await Promise.all(
2914
+ els.map(async el => {
2915
+ const checked = await el
2916
+ .getProperty('checked')
2917
+ .then(p => p.jsonValue())
2918
+ .catch(() => null)
2919
+
2920
+ if (checked) {
2921
+ return checked
2922
+ }
2923
+
2924
+ const ariaChecked = await el.evaluate(el => el.getAttribute('aria-checked'))
2925
+ return ariaChecked === 'true'
2926
+ }),
2927
+ )
2928
+
2929
+ const selected = checkedStates.reduce((prev, cur) => prev || cur)
2906
2930
  return truth(`checkable ${option}`, 'to be checked')[assertType](selected)
2907
2931
  }
2908
2932
 
2909
2933
  async function findVisibleFields(locator) {
2910
2934
  const els = await findFields.call(this, locator)
2911
- const visible = await Promise.all(els.map((el) => el.boundingBox()))
2935
+ const visible = await Promise.all(els.map(el => el.boundingBox()))
2912
2936
  return els.filter((el, index) => visible[index])
2913
2937
  }
2914
2938
 
@@ -2917,7 +2941,7 @@ async function findFields(locator) {
2917
2941
  if (!matchedLocator.isFuzzy()) {
2918
2942
  return this._locate(matchedLocator)
2919
2943
  }
2920
- const literal = xpathLocator.literal(locator)
2944
+ const literal = xpathLocator.literal(matchedLocator.value)
2921
2945
 
2922
2946
  let els = await this._locate({ xpath: Locator.field.labelEquals(literal) })
2923
2947
  if (els.length) {
@@ -2932,7 +2956,17 @@ async function findFields(locator) {
2932
2956
  if (els.length) {
2933
2957
  return els
2934
2958
  }
2935
- return this._locate({ css: locator })
2959
+
2960
+ // Try ARIA selector for accessible name
2961
+ try {
2962
+ const page = await this.context
2963
+ els = await page.$$(`::-p-aria(${matchedLocator.value})`)
2964
+ if (els.length) return els
2965
+ } catch (err) {
2966
+ // ARIA selector not supported or failed
2967
+ }
2968
+
2969
+ return this._locate({ css: matchedLocator.value })
2936
2970
  }
2937
2971
 
2938
2972
  async function proceedDragAndDrop(sourceLocator, destinationLocator) {
@@ -2961,15 +2995,15 @@ async function proceedSeeInField(assertType, field, value) {
2961
2995
  const els = await findVisibleFields.call(this, field)
2962
2996
  assertElementExists(els, field, 'Field')
2963
2997
  const el = els[0]
2964
- const tag = await el.getProperty('tagName').then((el) => el.jsonValue())
2965
- const fieldType = await el.getProperty('type').then((el) => el.jsonValue())
2998
+ const tag = await el.getProperty('tagName').then(el => el.jsonValue())
2999
+ const fieldType = await el.getProperty('type').then(el => el.jsonValue())
2966
3000
 
2967
- const proceedMultiple = async (elements) => {
3001
+ const proceedMultiple = async elements => {
2968
3002
  const fields = Array.isArray(elements) ? elements : [elements]
2969
3003
 
2970
3004
  const elementValues = []
2971
3005
  for (const element of fields) {
2972
- elementValues.push(await element.getProperty('value').then((el) => el.jsonValue()))
3006
+ elementValues.push(await element.getProperty('value').then(el => el.jsonValue()))
2973
3007
  }
2974
3008
 
2975
3009
  if (typeof value === 'boolean') {
@@ -2978,7 +3012,7 @@ async function proceedSeeInField(assertType, field, value) {
2978
3012
  if (assertType === 'assert') {
2979
3013
  equals(`select option by ${field}`)[assertType](true, elementValues.length > 0)
2980
3014
  }
2981
- elementValues.forEach((val) => stringIncludes(`fields by ${field}`)[assertType](value, val))
3015
+ elementValues.forEach(val => stringIncludes(`fields by ${field}`)[assertType](value, val))
2982
3016
  }
2983
3017
  }
2984
3018
 
@@ -3006,19 +3040,30 @@ async function proceedSeeInField(assertType, field, value) {
3006
3040
  }
3007
3041
  return proceedMultiple(els[0])
3008
3042
  }
3009
- const fieldVal = await el.getProperty('value').then((el) => el.jsonValue())
3043
+
3044
+ let fieldVal = await el.getProperty('value').then(el => el.jsonValue())
3045
+
3046
+ if (fieldVal === undefined || fieldVal === null) {
3047
+ fieldVal = await el.evaluate(el => el.textContent || el.innerText)
3048
+ }
3049
+
3010
3050
  return stringIncludes(`fields by ${field}`)[assertType](value, fieldVal)
3011
3051
  }
3012
3052
 
3013
3053
  async function filterFieldsByValue(elements, value, onlySelected) {
3014
3054
  const matches = []
3015
3055
  for (const element of elements) {
3016
- const val = await element.getProperty('value').then((el) => el.jsonValue())
3056
+ let val = await element.getProperty('value').then(el => el.jsonValue())
3057
+
3058
+ if (val === undefined || val === null) {
3059
+ val = await element.evaluate(el => el.textContent || el.innerText)
3060
+ }
3061
+
3017
3062
  let isSelected = true
3018
3063
  if (onlySelected) {
3019
3064
  isSelected = await elementSelected(element)
3020
3065
  }
3021
- if ((value == null || val.indexOf(value) > -1) && isSelected) {
3066
+ if ((value == null || (val && val.indexOf(value) > -1)) && isSelected) {
3022
3067
  matches.push(element)
3023
3068
  }
3024
3069
  }
@@ -3037,12 +3082,12 @@ async function filterFieldsBySelectionState(elements, state) {
3037
3082
  }
3038
3083
 
3039
3084
  async function elementSelected(element) {
3040
- const type = await element.getProperty('type').then((el) => el.jsonValue())
3085
+ const type = await element.getProperty('type').then(el => el.jsonValue())
3041
3086
 
3042
3087
  if (type === 'checkbox' || type === 'radio') {
3043
- return element.getProperty('checked').then((el) => el.jsonValue())
3088
+ return element.getProperty('checked').then(el => el.jsonValue())
3044
3089
  }
3045
- return element.getProperty('selected').then((el) => el.jsonValue())
3090
+ return element.getProperty('selected').then(el => el.jsonValue())
3046
3091
  }
3047
3092
 
3048
3093
  function isFrameLocator(locator) {
@@ -3077,9 +3122,9 @@ async function targetCreatedHandler(page) {
3077
3122
  page
3078
3123
  .$('body')
3079
3124
  .catch(() => null)
3080
- .then((context) => (this.context = context))
3125
+ .then(context => (this.context = context))
3081
3126
  })
3082
- page.on('console', (msg) => {
3127
+ page.on('console', msg => {
3083
3128
  this.debugSection(`Browser:${ucfirst(msg.type())}`, (msg._text || '') + msg.args().join(' '))
3084
3129
  consoleLogStore.add(msg)
3085
3130
  })
@@ -3180,13 +3225,18 @@ function _waitForElement(locator, options) {
3180
3225
  }
3181
3226
  }
3182
3227
 
3183
- async function findReactElements(locator, props = {}, state = {}) {
3228
+ async function findReactElements(locator) {
3229
+ const resolved = toLocatorConfig(locator, 'react')
3230
+
3231
+ // Use createRequire to access require.resolve in ESM
3232
+ const { createRequire } = await import('module')
3233
+ const require = createRequire(import.meta.url)
3184
3234
  const resqScript = await fs.promises.readFile(require.resolve('resq'), 'utf-8')
3185
3235
  await this.page.evaluate(resqScript.toString())
3186
3236
 
3187
3237
  await this.page.evaluate(() => window.resq.waitToLoadReact())
3188
3238
  const arrayHandle = await this.page.evaluateHandle(
3189
- (obj) => {
3239
+ obj => {
3190
3240
  const { selector, props, state } = obj
3191
3241
  let elements = window.resq.resq$$(selector)
3192
3242
  if (Object.keys(props).length) {
@@ -3205,7 +3255,7 @@ async function findReactElements(locator, props = {}, state = {}) {
3205
3255
  // [[div, div], [div, div]] => [div, div, div, div]
3206
3256
  let nodes = []
3207
3257
 
3208
- elements.forEach((element) => {
3258
+ elements.forEach(element => {
3209
3259
  let { node, isFragment } = element
3210
3260
 
3211
3261
  if (!node) {
@@ -3223,9 +3273,9 @@ async function findReactElements(locator, props = {}, state = {}) {
3223
3273
  return [...nodes]
3224
3274
  },
3225
3275
  {
3226
- selector: locator.react,
3227
- props: locator.props || {},
3228
- state: locator.state || {},
3276
+ selector: resolved.react,
3277
+ props: resolved.props || {},
3278
+ state: resolved.state || {},
3229
3279
  },
3230
3280
  )
3231
3281
 
@@ -3241,3 +3291,54 @@ async function findReactElements(locator, props = {}, state = {}) {
3241
3291
  await arrayHandle.dispose()
3242
3292
  return result
3243
3293
  }
3294
+
3295
+ async function findByRole(matcher, locator) {
3296
+ const resolved = toLocatorConfig(locator, 'role')
3297
+ const roleSelector = buildRoleSelector(resolved)
3298
+
3299
+ if (!resolved.text && !resolved.name) {
3300
+ return matcher.$$(roleSelector)
3301
+ }
3302
+
3303
+ const allElements = await matcher.$$(roleSelector)
3304
+ const filtered = []
3305
+ const accessibleName = resolved.text ?? resolved.name
3306
+ const matcherFn = createRoleTextMatcher(accessibleName, resolved.exact === true)
3307
+
3308
+ for (const el of allElements) {
3309
+ const texts = await el.evaluate(e => {
3310
+ const ariaLabel = e.hasAttribute('aria-label') ? e.getAttribute('aria-label') : ''
3311
+ const labelText = e.id ? document.querySelector(`label[for="${e.id}"]`)?.textContent.trim() || '' : ''
3312
+ const placeholder = e.getAttribute('placeholder') || ''
3313
+ const innerText = e.innerText ? e.innerText.trim() : ''
3314
+ return [ariaLabel || labelText, placeholder, innerText]
3315
+ })
3316
+
3317
+ if (texts.some(text => matcherFn(text))) filtered.push(el)
3318
+ }
3319
+
3320
+ return filtered
3321
+ }
3322
+
3323
+ function toLocatorConfig(locator, key) {
3324
+ const matchedLocator = new Locator(locator, key)
3325
+ if (matchedLocator.locator) return matchedLocator.locator
3326
+ return { [key]: matchedLocator.value }
3327
+ }
3328
+
3329
+ function buildRoleSelector(resolved) {
3330
+ return `::-p-aria([role="${resolved.role}"])`
3331
+ }
3332
+
3333
+ function createRoleTextMatcher(expected, exactMatch) {
3334
+ if (expected instanceof RegExp) {
3335
+ return value => expected.test(value || '')
3336
+ }
3337
+ const target = String(expected)
3338
+ if (exactMatch) {
3339
+ return value => value === target
3340
+ }
3341
+ return value => typeof value === 'string' && value.includes(target)
3342
+ }
3343
+
3344
+ export { Puppeteer as default }