codeceptjs 4.0.0-beta.4 → 4.0.0-beta.5

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 (150) hide show
  1. package/README.md +134 -119
  2. package/bin/codecept.js +12 -2
  3. package/bin/test-server.js +53 -0
  4. package/docs/webapi/clearCookie.mustache +1 -1
  5. package/lib/actor.js +66 -102
  6. package/lib/ai.js +130 -121
  7. package/lib/assert/empty.js +3 -5
  8. package/lib/assert/equal.js +4 -7
  9. package/lib/assert/include.js +4 -6
  10. package/lib/assert/throws.js +2 -4
  11. package/lib/assert/truth.js +2 -2
  12. package/lib/codecept.js +139 -87
  13. package/lib/command/check.js +201 -0
  14. package/lib/command/configMigrate.js +2 -4
  15. package/lib/command/definitions.js +8 -26
  16. package/lib/command/generate.js +10 -14
  17. package/lib/command/gherkin/snippets.js +75 -73
  18. package/lib/command/gherkin/steps.js +1 -1
  19. package/lib/command/info.js +42 -8
  20. package/lib/command/init.js +13 -12
  21. package/lib/command/interactive.js +10 -2
  22. package/lib/command/list.js +1 -1
  23. package/lib/command/run-multiple/chunk.js +48 -45
  24. package/lib/command/run-multiple.js +12 -35
  25. package/lib/command/run-workers.js +21 -58
  26. package/lib/command/utils.js +5 -6
  27. package/lib/command/workers/runTests.js +262 -220
  28. package/lib/container.js +386 -238
  29. package/lib/data/context.js +10 -13
  30. package/lib/data/dataScenarioConfig.js +8 -8
  31. package/lib/data/dataTableArgument.js +6 -6
  32. package/lib/data/table.js +5 -11
  33. package/lib/effects.js +223 -0
  34. package/lib/element/WebElement.js +327 -0
  35. package/lib/els.js +158 -0
  36. package/lib/event.js +21 -17
  37. package/lib/heal.js +88 -80
  38. package/lib/helper/AI.js +2 -1
  39. package/lib/helper/ApiDataFactory.js +3 -6
  40. package/lib/helper/Appium.js +47 -51
  41. package/lib/helper/FileSystem.js +3 -3
  42. package/lib/helper/GraphQLDataFactory.js +3 -3
  43. package/lib/helper/JSONResponse.js +75 -37
  44. package/lib/helper/Mochawesome.js +31 -9
  45. package/lib/helper/Nightmare.js +35 -53
  46. package/lib/helper/Playwright.js +262 -267
  47. package/lib/helper/Protractor.js +54 -77
  48. package/lib/helper/Puppeteer.js +246 -260
  49. package/lib/helper/REST.js +5 -17
  50. package/lib/helper/TestCafe.js +21 -44
  51. package/lib/helper/WebDriver.js +151 -170
  52. package/lib/helper/extras/Popup.js +22 -22
  53. package/lib/helper/testcafe/testcafe-utils.js +26 -27
  54. package/lib/listener/emptyRun.js +55 -0
  55. package/lib/listener/exit.js +7 -10
  56. package/lib/listener/{retry.js → globalRetry.js} +5 -5
  57. package/lib/listener/globalTimeout.js +165 -0
  58. package/lib/listener/helpers.js +15 -15
  59. package/lib/listener/mocha.js +1 -1
  60. package/lib/listener/result.js +12 -0
  61. package/lib/listener/retryEnhancer.js +85 -0
  62. package/lib/listener/steps.js +32 -18
  63. package/lib/listener/store.js +20 -0
  64. package/lib/mocha/asyncWrapper.js +231 -0
  65. package/lib/{interfaces → mocha}/bdd.js +3 -3
  66. package/lib/mocha/cli.js +308 -0
  67. package/lib/mocha/factory.js +104 -0
  68. package/lib/{interfaces → mocha}/featureConfig.js +32 -12
  69. package/lib/{interfaces → mocha}/gherkin.js +26 -28
  70. package/lib/mocha/hooks.js +112 -0
  71. package/lib/mocha/index.js +12 -0
  72. package/lib/mocha/inject.js +29 -0
  73. package/lib/{interfaces → mocha}/scenarioConfig.js +31 -7
  74. package/lib/mocha/suite.js +82 -0
  75. package/lib/mocha/test.js +181 -0
  76. package/lib/mocha/types.d.ts +42 -0
  77. package/lib/mocha/ui.js +232 -0
  78. package/lib/output.js +82 -62
  79. package/lib/pause.js +160 -138
  80. package/lib/plugin/analyze.js +396 -0
  81. package/lib/plugin/auth.js +435 -0
  82. package/lib/plugin/autoDelay.js +8 -8
  83. package/lib/plugin/autoLogin.js +3 -338
  84. package/lib/plugin/commentStep.js +6 -1
  85. package/lib/plugin/coverage.js +10 -19
  86. package/lib/plugin/customLocator.js +3 -3
  87. package/lib/plugin/customReporter.js +52 -0
  88. package/lib/plugin/eachElement.js +1 -1
  89. package/lib/plugin/fakerTransform.js +1 -1
  90. package/lib/plugin/heal.js +36 -9
  91. package/lib/plugin/htmlReporter.js +1947 -0
  92. package/lib/plugin/pageInfo.js +140 -0
  93. package/lib/plugin/retryFailedStep.js +17 -18
  94. package/lib/plugin/retryTo.js +2 -113
  95. package/lib/plugin/screenshotOnFail.js +17 -58
  96. package/lib/plugin/selenoid.js +15 -35
  97. package/lib/plugin/standardActingHelpers.js +4 -1
  98. package/lib/plugin/stepByStepReport.js +56 -17
  99. package/lib/plugin/stepTimeout.js +5 -12
  100. package/lib/plugin/subtitles.js +4 -4
  101. package/lib/plugin/tryTo.js +3 -102
  102. package/lib/plugin/wdio.js +8 -10
  103. package/lib/recorder.js +155 -124
  104. package/lib/rerun.js +43 -42
  105. package/lib/result.js +161 -0
  106. package/lib/secret.js +1 -1
  107. package/lib/step/base.js +239 -0
  108. package/lib/step/comment.js +10 -0
  109. package/lib/step/config.js +50 -0
  110. package/lib/step/func.js +46 -0
  111. package/lib/step/helper.js +50 -0
  112. package/lib/step/meta.js +99 -0
  113. package/lib/step/record.js +74 -0
  114. package/lib/step/retry.js +11 -0
  115. package/lib/step/section.js +55 -0
  116. package/lib/step.js +21 -332
  117. package/lib/steps.js +50 -0
  118. package/lib/store.js +37 -5
  119. package/lib/template/heal.js +2 -11
  120. package/lib/test-server.js +323 -0
  121. package/lib/timeout.js +66 -0
  122. package/lib/utils.js +351 -218
  123. package/lib/within.js +75 -55
  124. package/lib/workerStorage.js +2 -1
  125. package/lib/workers.js +386 -276
  126. package/package.json +76 -70
  127. package/translations/de-DE.js +4 -3
  128. package/translations/fr-FR.js +4 -3
  129. package/translations/index.js +1 -0
  130. package/translations/it-IT.js +4 -3
  131. package/translations/ja-JP.js +4 -3
  132. package/translations/nl-NL.js +76 -0
  133. package/translations/pl-PL.js +4 -3
  134. package/translations/pt-BR.js +4 -3
  135. package/translations/ru-RU.js +4 -3
  136. package/translations/utils.js +9 -0
  137. package/translations/zh-CN.js +4 -3
  138. package/translations/zh-TW.js +4 -3
  139. package/typings/index.d.ts +188 -186
  140. package/typings/promiseBasedTypes.d.ts +18 -705
  141. package/typings/types.d.ts +301 -804
  142. package/lib/cli.js +0 -256
  143. package/lib/helper/ExpectHelper.js +0 -391
  144. package/lib/helper/SoftExpectHelper.js +0 -381
  145. package/lib/listener/artifacts.js +0 -19
  146. package/lib/listener/timeout.js +0 -109
  147. package/lib/mochaFactory.js +0 -113
  148. package/lib/plugin/debugErrors.js +0 -67
  149. package/lib/scenario.js +0 -224
  150. package/lib/ui.js +0 -236
@@ -36,19 +36,9 @@ const Popup = require('./extras/Popup')
36
36
  const Console = require('./extras/Console')
37
37
  const { highlightElement } = require('./scripts/highlightElement')
38
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')
39
+ const { dontSeeElementError, seeElementError, dontSeeElementInDOMError, seeElementInDOMError } = require('./errors/ElementAssertion')
40
+ const { dontSeeTraffic, seeTraffic, grabRecordedNetworkTraffics, stopRecordingTraffic, flushNetworkTraffics } = require('./network/actions')
41
+ const WebElement = require('../element/WebElement')
52
42
 
53
43
  let puppeteer
54
44
  let perfTiming
@@ -271,9 +261,7 @@ class Puppeteer extends Helper {
271
261
  }
272
262
 
273
263
  _getOptions(config) {
274
- return config.browser === 'firefox'
275
- ? Object.assign(this.options.firefox, { product: 'firefox' })
276
- : this.options.chrome
264
+ return config.browser === 'firefox' ? Object.assign(this.options.firefox, { product: 'firefox' }) : this.options.chrome
277
265
  }
278
266
 
279
267
  _setConfig(config) {
@@ -325,8 +313,8 @@ class Puppeteer extends Helper {
325
313
  this.sessionPages = {}
326
314
  this.currentRunningTest = test
327
315
  recorder.retry({
328
- retries: process.env.FAILED_STEP_RETRIES || 3,
329
- when: (err) => {
316
+ retries: test?.opts?.conditionalRetries || 3,
317
+ when: err => {
330
318
  if (!err || typeof err.message !== 'string') {
331
319
  return false
332
320
  }
@@ -346,7 +334,7 @@ class Puppeteer extends Helper {
346
334
  const contexts = this.browser.browserContexts()
347
335
  const defaultCtx = contexts.shift()
348
336
 
349
- await Promise.all(contexts.map((c) => c.close()))
337
+ await Promise.all(contexts.map(c => c.close()))
350
338
 
351
339
  if (this.options.restart) {
352
340
  this.isRunning = false
@@ -355,7 +343,7 @@ class Puppeteer extends Helper {
355
343
 
356
344
  // ensure this.page is from default context
357
345
  if (this.page) {
358
- const existingPages = defaultCtx.targets().filter((t) => t.type() === 'page')
346
+ const existingPages = defaultCtx.targets().filter(t => t.type() === 'page')
359
347
  await this._setPage(await existingPages[0].page())
360
348
  }
361
349
 
@@ -368,10 +356,10 @@ class Puppeteer extends Helper {
368
356
  const currentUrl = await this.grabCurrentUrl()
369
357
 
370
358
  if (currentUrl.startsWith('http')) {
371
- await this.executeScript('localStorage.clear();').catch((err) => {
359
+ await this.executeScript('localStorage.clear();').catch(err => {
372
360
  if (!(err.message.indexOf("Storage is disabled inside 'data:' URLs.") > -1)) throw err
373
361
  })
374
- await this.executeScript('sessionStorage.clear();').catch((err) => {
362
+ await this.executeScript('sessionStorage.clear();').catch(err => {
375
363
  if (!(err.message.indexOf("Storage is disabled inside 'data:' URLs.") > -1)) throw err
376
364
  })
377
365
  }
@@ -400,12 +388,12 @@ class Puppeteer extends Helper {
400
388
  stop: async () => {
401
389
  // is closed by _after
402
390
  },
403
- loadVars: async (context) => {
404
- const existingPages = context.targets().filter((t) => t.type() === 'page')
391
+ loadVars: async context => {
392
+ const existingPages = context.targets().filter(t => t.type() === 'page')
405
393
  this.sessionPages[this.activeSessionName] = await existingPages[0].page()
406
394
  return this._setPage(this.sessionPages[this.activeSessionName])
407
395
  },
408
- restoreVars: async (session) => {
396
+ restoreVars: async session => {
409
397
  this.withinLocator = null
410
398
 
411
399
  if (!session) {
@@ -414,7 +402,7 @@ class Puppeteer extends Helper {
414
402
  this.activeSessionName = session
415
403
  }
416
404
  const defaultCtx = this.browser.defaultBrowserContext()
417
- const existingPages = defaultCtx.targets().filter((t) => t.type() === 'page')
405
+ const existingPages = defaultCtx.targets().filter(t => t.type() === 'page')
418
406
  await this._setPage(await existingPages[0].page())
419
407
 
420
408
  return this._waitForAction()
@@ -517,7 +505,7 @@ class Puppeteer extends Helper {
517
505
  if (!page) {
518
506
  return
519
507
  }
520
- page.on('error', async (error) => {
508
+ page.on('error', async error => {
521
509
  console.error('Puppeteer page error', error)
522
510
  })
523
511
  }
@@ -533,7 +521,7 @@ class Puppeteer extends Helper {
533
521
  if (!page) {
534
522
  return
535
523
  }
536
- page.on('dialog', async (dialog) => {
524
+ page.on('dialog', async dialog => {
537
525
  popupStore.popup = dialog
538
526
  const action = popupStore.actionType || this.options.defaultPopupAction
539
527
  await this._waitForAction()
@@ -588,15 +576,15 @@ class Puppeteer extends Helper {
588
576
  this.browser = await puppeteer.launch(this.puppeteerOptions)
589
577
  }
590
578
 
591
- this.browser.on('targetcreated', (target) =>
579
+ this.browser.on('targetcreated', target =>
592
580
  target
593
581
  .page()
594
- .then((page) => targetCreatedHandler.call(this, page))
595
- .catch((e) => {
582
+ .then(page => targetCreatedHandler.call(this, page))
583
+ .catch(e => {
596
584
  console.error('Puppeteer page error', e)
597
585
  }),
598
586
  )
599
- this.browser.on('targetchanged', (target) => {
587
+ this.browser.on('targetchanged', target => {
600
588
  this.debugSection('Url', target.url())
601
589
  })
602
590
 
@@ -639,18 +627,18 @@ class Puppeteer extends Helper {
639
627
 
640
628
  if (frame) {
641
629
  if (Array.isArray(frame)) {
642
- return this.switchTo(null).then(() =>
643
- frame.reduce((p, frameLocator) => p.then(() => this.switchTo(frameLocator)), Promise.resolve()),
644
- )
630
+ return this.switchTo(null).then(() => frame.reduce((p, frameLocator) => p.then(() => this.switchTo(frameLocator)), Promise.resolve()))
645
631
  }
646
632
  await this.switchTo(frame)
647
633
  this.withinLocator = new Locator(frame)
648
634
  return
649
635
  }
650
636
 
651
- const els = await this._locate(locator)
652
- assertElementExists(els, locator)
653
- this.context = els[0]
637
+ const el = await this._locateElement(locator)
638
+ if (!el) {
639
+ throw new ElementNotFound(locator, 'Element for within context')
640
+ }
641
+ this.context = el
654
642
 
655
643
  this.withinLocator = new Locator(locator)
656
644
  }
@@ -664,7 +652,7 @@ class Puppeteer extends Helper {
664
652
  const navigationStart = timing.navigationStart
665
653
 
666
654
  const extractedData = {}
667
- dataNames.forEach((name) => {
655
+ dataNames.forEach(name => {
668
656
  extractedData[name] = timing[name] - navigationStart
669
657
  })
670
658
 
@@ -698,13 +686,7 @@ class Puppeteer extends Helper {
698
686
 
699
687
  const performanceTiming = JSON.parse(await this.page.evaluate(() => JSON.stringify(window.performance.timing)))
700
688
 
701
- perfTiming = this._extractDataFromPerformanceTiming(
702
- performanceTiming,
703
- 'responseEnd',
704
- 'domInteractive',
705
- 'domContentLoadedEventEnd',
706
- 'loadEventEnd',
707
- )
689
+ perfTiming = this._extractDataFromPerformanceTiming(performanceTiming, 'responseEnd', 'domInteractive', 'domContentLoadedEventEnd', 'loadEventEnd')
708
690
 
709
691
  return this._waitForAction()
710
692
  }
@@ -750,11 +732,13 @@ class Puppeteer extends Helper {
750
732
  * {{ react }}
751
733
  */
752
734
  async moveCursorTo(locator, offsetX = 0, offsetY = 0) {
753
- const els = await this._locate(locator)
754
- assertElementExists(els, locator)
735
+ const el = await this._locateElement(locator)
736
+ if (!el) {
737
+ throw new ElementNotFound(locator, 'Element to move cursor to')
738
+ }
755
739
 
756
740
  // Use manual mouse.move instead of .hover() so the offset can be added to the coordinates
757
- const { x, y } = await getClickablePoint(els[0])
741
+ const { x, y } = await getClickablePoint(el)
758
742
  await this.page.mouse.move(x + offsetX, y + offsetY)
759
743
  return this._waitForAction()
760
744
  }
@@ -764,9 +748,10 @@ class Puppeteer extends Helper {
764
748
  *
765
749
  */
766
750
  async focus(locator) {
767
- const els = await this._locate(locator)
768
- assertElementExists(els, locator, 'Element to focus')
769
- const el = els[0]
751
+ const el = await this._locateElement(locator)
752
+ if (!el) {
753
+ throw new ElementNotFound(locator, 'Element to focus')
754
+ }
770
755
 
771
756
  await el.click()
772
757
  await el.focus()
@@ -778,10 +763,12 @@ class Puppeteer extends Helper {
778
763
  *
779
764
  */
780
765
  async blur(locator) {
781
- const els = await this._locate(locator)
782
- assertElementExists(els, locator, 'Element to blur')
766
+ const el = await this._locateElement(locator)
767
+ if (!el) {
768
+ throw new ElementNotFound(locator, 'Element to blur')
769
+ }
783
770
 
784
- await blurElement(els[0], this.page)
771
+ await blurElement(el, this.page)
785
772
  return this._waitForAction()
786
773
  }
787
774
 
@@ -815,10 +802,7 @@ class Puppeteer extends Helper {
815
802
  return this.executeScript(() => {
816
803
  const body = document.body
817
804
  const html = document.documentElement
818
- window.scrollTo(
819
- 0,
820
- Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight),
821
- )
805
+ window.scrollTo(0, Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight))
822
806
  })
823
807
  }
824
808
 
@@ -833,16 +817,13 @@ class Puppeteer extends Helper {
833
817
  }
834
818
 
835
819
  if (locator) {
836
- const els = await this._locate(locator)
837
- assertElementExists(els, locator, 'Element')
838
- const el = els[0]
839
- await el.evaluate((el) => el.scrollIntoView())
840
- 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
- )
820
+ const el = await this._locateElement(locator)
821
+ if (!el) {
822
+ throw new ElementNotFound(locator, 'Element to scroll into view')
823
+ }
824
+ await el.evaluate(el => el.scrollIntoView())
825
+ const elementCoordinates = await getClickablePoint(el)
826
+ await this.executeScript((x, y) => window.scrollBy(x, y), elementCoordinates.x + offsetX, elementCoordinates.y + offsetY)
846
827
  } else {
847
828
  await this.executeScript((x, y) => window.scrollTo(x, y), offsetX, offsetY)
848
829
  }
@@ -909,6 +890,21 @@ class Puppeteer extends Helper {
909
890
  return findElements.call(this, context, locator)
910
891
  }
911
892
 
893
+ /**
894
+ * Get single element by different locator types, including strict locator
895
+ * Should be used in custom helpers:
896
+ *
897
+ * ```js
898
+ * const element = await this.helpers['Puppeteer']._locateElement({name: 'password'});
899
+ * ```
900
+ *
901
+ * {{ react }}
902
+ */
903
+ async _locateElement(locator) {
904
+ const context = await this.context
905
+ return findElement.call(this, context, locator)
906
+ }
907
+
912
908
  /**
913
909
  * Find a checkbox by providing human-readable text:
914
910
  * NOTE: Assumes the checkable element exists
@@ -920,7 +916,9 @@ class Puppeteer extends Helper {
920
916
  async _locateCheckable(locator, providedContext = null) {
921
917
  const context = providedContext || (await this._getContext())
922
918
  const els = await findCheckable.call(this, locator, context)
923
- assertElementExists(els[0], locator, 'Checkbox or radio')
919
+ if (!els || els.length === 0) {
920
+ throw new ElementNotFound(locator, 'Checkbox or radio')
921
+ }
924
922
  return els[0]
925
923
  }
926
924
 
@@ -952,7 +950,20 @@ class Puppeteer extends Helper {
952
950
  *
953
951
  */
954
952
  async grabWebElements(locator) {
955
- return this._locate(locator)
953
+ const elements = await this._locate(locator)
954
+ return elements.map(element => new WebElement(element, this))
955
+ }
956
+
957
+ /**
958
+ * {{> grabWebElement }}
959
+ *
960
+ */
961
+ async grabWebElement(locator) {
962
+ const elements = await this._locate(locator)
963
+ if (elements.length === 0) {
964
+ throw new ElementNotFound(locator, 'Element')
965
+ }
966
+ return new WebElement(elements[0], this)
956
967
  }
957
968
 
958
969
  /**
@@ -1025,10 +1036,10 @@ class Puppeteer extends Helper {
1025
1036
  */
1026
1037
  async closeOtherTabs() {
1027
1038
  const pages = await this.browser.pages()
1028
- const otherPages = pages.filter((page) => page !== this.page)
1039
+ const otherPages = pages.filter(page => page !== this.page)
1029
1040
 
1030
1041
  let p = Promise.resolve()
1031
- otherPages.forEach((page) => {
1042
+ otherPages.forEach(page => {
1032
1043
  p = p.then(() => page.close())
1033
1044
  })
1034
1045
  await p
@@ -1061,19 +1072,11 @@ class Puppeteer extends Helper {
1061
1072
  */
1062
1073
  async seeElement(locator) {
1063
1074
  let els = await this._locate(locator)
1064
- els = (await Promise.all(els.map((el) => el.boundingBox() && el))).filter((v) => v)
1075
+ els = (await Promise.all(els.map(el => el.boundingBox() && el))).filter(v => v)
1065
1076
  // 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
- )
1077
+ els = await Promise.all(els.map(async el => (await el.evaluate(node => window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none')) && el))
1075
1078
  try {
1076
- return empty('visible elements').negate(els.filter((v) => v).fill('ELEMENT'))
1079
+ return empty('visible elements').negate(els.filter(v => v).fill('ELEMENT'))
1077
1080
  } catch (e) {
1078
1081
  dontSeeElementError(locator)
1079
1082
  }
@@ -1085,19 +1088,11 @@ class Puppeteer extends Helper {
1085
1088
  */
1086
1089
  async dontSeeElement(locator) {
1087
1090
  let els = await this._locate(locator)
1088
- els = (await Promise.all(els.map((el) => el.boundingBox() && el))).filter((v) => v)
1091
+ els = (await Promise.all(els.map(el => el.boundingBox() && el))).filter(v => v)
1089
1092
  // 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
- )
1093
+ els = await Promise.all(els.map(async el => (await el.evaluate(node => window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none')) && el))
1099
1094
  try {
1100
- return empty('visible elements').assert(els.filter((v) => v).fill('ELEMENT'))
1095
+ return empty('visible elements').assert(els.filter(v => v).fill('ELEMENT'))
1101
1096
  } catch (e) {
1102
1097
  seeElementError(locator)
1103
1098
  }
@@ -1109,7 +1104,7 @@ class Puppeteer extends Helper {
1109
1104
  async seeElementInDOM(locator) {
1110
1105
  const els = await this._locate(locator)
1111
1106
  try {
1112
- return empty('elements on page').negate(els.filter((v) => v).fill('ELEMENT'))
1107
+ return empty('elements on page').negate(els.filter(v => v).fill('ELEMENT'))
1113
1108
  } catch (e) {
1114
1109
  dontSeeElementInDOMError(locator)
1115
1110
  }
@@ -1121,7 +1116,7 @@ class Puppeteer extends Helper {
1121
1116
  async dontSeeElementInDOM(locator) {
1122
1117
  const els = await this._locate(locator)
1123
1118
  try {
1124
- return empty('elements on a page').assert(els.filter((v) => v).fill('ELEMENT'))
1119
+ return empty('elements on a page').assert(els.filter(v => v).fill('ELEMENT'))
1125
1120
  } catch (e) {
1126
1121
  seeElementInDOMError(locator)
1127
1122
  }
@@ -1151,17 +1146,12 @@ class Puppeteer extends Helper {
1151
1146
 
1152
1147
  const els = await findClickable.call(this, matcher, locator)
1153
1148
  if (context) {
1154
- assertElementExists(
1155
- els,
1156
- locator,
1157
- 'Clickable element',
1158
- `was not found inside element ${new Locator(context).toString()}`,
1159
- )
1149
+ assertElementExists(els, locator, 'Clickable element', `was not found inside element ${new Locator(context).toString()}`)
1160
1150
  } else {
1161
1151
  assertElementExists(els, locator, 'Clickable element')
1162
1152
  }
1163
1153
  const elem = els[0]
1164
- return this.executeScript((el) => {
1154
+ return this.executeScript(el => {
1165
1155
  if (document.activeElement instanceof HTMLElement) {
1166
1156
  document.activeElement.blur()
1167
1157
  }
@@ -1220,8 +1210,8 @@ class Puppeteer extends Helper {
1220
1210
  let fileName
1221
1211
  await this.page.setRequestInterception(true)
1222
1212
 
1223
- const xRequest = await new Promise((resolve) => {
1224
- this.page.on('request', (request) => {
1213
+ const xRequest = await new Promise(resolve => {
1214
+ this.page.on('request', request => {
1225
1215
  console.log('rq', request, customName)
1226
1216
  const grabbedFileName = request.url().split('/')[request.url().split('/').length - 1]
1227
1217
  const fileExtension = request.url().split('/')[request.url().split('/').length - 1].split('.')[1]
@@ -1248,7 +1238,7 @@ class Puppeteer extends Helper {
1248
1238
  }
1249
1239
 
1250
1240
  const cookies = await this.page.cookies()
1251
- options.headers.Cookie = cookies.map((ck) => `${ck.name}=${ck.value}`).join(';')
1241
+ options.headers.Cookie = cookies.map(ck => `${ck.name}=${ck.value}`).join(';')
1252
1242
 
1253
1243
  const response = await axios({
1254
1244
  method: options.method,
@@ -1302,7 +1292,7 @@ class Puppeteer extends Helper {
1302
1292
  */
1303
1293
  async checkOption(field, context = null) {
1304
1294
  const elm = await this._locateCheckable(field, context)
1305
- const curentlyChecked = await elm.getProperty('checked').then((checkedProperty) => checkedProperty.jsonValue())
1295
+ const curentlyChecked = await elm.getProperty('checked').then(checkedProperty => checkedProperty.jsonValue())
1306
1296
  // Only check if NOT currently checked
1307
1297
  if (!curentlyChecked) {
1308
1298
  await elm.click()
@@ -1315,7 +1305,7 @@ class Puppeteer extends Helper {
1315
1305
  */
1316
1306
  async uncheckOption(field, context = null) {
1317
1307
  const elm = await this._locateCheckable(field, context)
1318
- const curentlyChecked = await elm.getProperty('checked').then((checkedProperty) => checkedProperty.jsonValue())
1308
+ const curentlyChecked = await elm.getProperty('checked').then(checkedProperty => checkedProperty.jsonValue())
1319
1309
  // Only uncheck if currently checked
1320
1310
  if (curentlyChecked) {
1321
1311
  await elm.click()
@@ -1408,12 +1398,12 @@ class Puppeteer extends Helper {
1408
1398
  const els = await findVisibleFields.call(this, field)
1409
1399
  assertElementExists(els, field, 'Field')
1410
1400
  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())
1401
+ const tag = await el.getProperty('tagName').then(el => el.jsonValue())
1402
+ const editable = await el.getProperty('contenteditable').then(el => el.jsonValue())
1413
1403
  if (tag === 'INPUT' || tag === 'TEXTAREA') {
1414
- await this._evaluateHandeInContext((el) => (el.value = ''), el)
1404
+ await this._evaluateHandeInContext(el => (el.value = ''), el)
1415
1405
  } else if (editable) {
1416
- await this._evaluateHandeInContext((el) => (el.innerHTML = ''), el)
1406
+ await this._evaluateHandeInContext(el => (el.innerHTML = ''), el)
1417
1407
  }
1418
1408
 
1419
1409
  highlightActiveElement.call(this, el, await this._getContext())
@@ -1483,7 +1473,7 @@ class Puppeteer extends Helper {
1483
1473
  const els = await findVisibleFields.call(this, select)
1484
1474
  assertElementExists(els, select, 'Selectable field')
1485
1475
  const el = els[0]
1486
- if ((await el.getProperty('tagName').then((t) => t.jsonValue())) !== 'SELECT') {
1476
+ if ((await el.getProperty('tagName').then(t => t.jsonValue())) !== 'SELECT') {
1487
1477
  throw new Error('Element is not <select>')
1488
1478
  }
1489
1479
  highlightActiveElement.call(this, els[0], await this._getContext())
@@ -1493,15 +1483,15 @@ class Puppeteer extends Helper {
1493
1483
  const opt = xpathLocator.literal(option[key])
1494
1484
  let optEl = await findElements.call(this, el, { xpath: Locator.select.byVisibleText(opt) })
1495
1485
  if (optEl.length) {
1496
- this._evaluateHandeInContext((el) => (el.selected = true), optEl[0])
1486
+ this._evaluateHandeInContext(el => (el.selected = true), optEl[0])
1497
1487
  continue
1498
1488
  }
1499
1489
  optEl = await findElements.call(this, el, { xpath: Locator.select.byValue(opt) })
1500
1490
  if (optEl.length) {
1501
- this._evaluateHandeInContext((el) => (el.selected = true), optEl[0])
1491
+ this._evaluateHandeInContext(el => (el.selected = true), optEl[0])
1502
1492
  }
1503
1493
  }
1504
- await this._evaluateHandeInContext((element) => {
1494
+ await this._evaluateHandeInContext(element => {
1505
1495
  element.dispatchEvent(new Event('input', { bubbles: true }))
1506
1496
  element.dispatchEvent(new Event('change', { bubbles: true }))
1507
1497
  }, el)
@@ -1515,19 +1505,11 @@ class Puppeteer extends Helper {
1515
1505
  */
1516
1506
  async grabNumberOfVisibleElements(locator) {
1517
1507
  let els = await this._locate(locator)
1518
- els = (await Promise.all(els.map((el) => el.boundingBox() && el))).filter((v) => v)
1508
+ els = (await Promise.all(els.map(el => el.boundingBox() && el))).filter(v => v)
1519
1509
  // 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
- )
1510
+ els = await Promise.all(els.map(async el => (await el.evaluate(node => window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none')) && el))
1529
1511
 
1530
- return els.filter((v) => v).length
1512
+ return els.filter(v => v).length
1531
1513
  }
1532
1514
 
1533
1515
  /**
@@ -1635,9 +1617,7 @@ class Puppeteer extends Helper {
1635
1617
  */
1636
1618
  async seeNumberOfElements(locator, num) {
1637
1619
  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)
1620
+ return equals(`expected number of elements (${new Locator(locator)}) is ${num}, but found ${elements.length}`).assert(elements.length, num)
1641
1621
  }
1642
1622
 
1643
1623
  /**
@@ -1647,10 +1627,7 @@ class Puppeteer extends Helper {
1647
1627
  */
1648
1628
  async seeNumberOfVisibleElements(locator, num) {
1649
1629
  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
- )
1630
+ return equals(`expected number of visible elements (${new Locator(locator)}) is ${num}, but found ${res}`).assert(res, num)
1654
1631
  }
1655
1632
 
1656
1633
  /**
@@ -1669,7 +1646,7 @@ class Puppeteer extends Helper {
1669
1646
  */
1670
1647
  async seeCookie(name) {
1671
1648
  const cookies = await this.page.cookies()
1672
- empty(`cookie ${name} to be set`).negate(cookies.filter((c) => c.name === name))
1649
+ empty(`cookie ${name} to be set`).negate(cookies.filter(c => c.name === name))
1673
1650
  }
1674
1651
 
1675
1652
  /**
@@ -1677,7 +1654,7 @@ class Puppeteer extends Helper {
1677
1654
  */
1678
1655
  async dontSeeCookie(name) {
1679
1656
  const cookies = await this.page.cookies()
1680
- empty(`cookie ${name} not to be set`).assert(cookies.filter((c) => c.name === name))
1657
+ empty(`cookie ${name} not to be set`).assert(cookies.filter(c => c.name === name))
1681
1658
  }
1682
1659
 
1683
1660
  /**
@@ -1688,7 +1665,7 @@ class Puppeteer extends Helper {
1688
1665
  async grabCookie(name) {
1689
1666
  const cookies = await this.page.cookies()
1690
1667
  if (!name) return cookies
1691
- const cookie = cookies.filter((c) => c.name === name)
1668
+ const cookie = cookies.filter(c => c.name === name)
1692
1669
  if (cookie[0]) return cookie[0]
1693
1670
  }
1694
1671
 
@@ -1708,9 +1685,9 @@ class Puppeteer extends Helper {
1708
1685
 
1709
1686
  return promiseRetry(
1710
1687
  async (retry, number) => {
1711
- const _grabCookie = async (name) => {
1688
+ const _grabCookie = async name => {
1712
1689
  const cookies = await this.page.cookies()
1713
- const cookie = cookies.filter((c) => c.name === name)
1690
+ const cookie = cookies.filter(c => c.name === name)
1714
1691
  if (cookie.length === 0) throw Error(`Cookie ${name} is not found after ${retries}s`)
1715
1692
  }
1716
1693
 
@@ -1735,7 +1712,7 @@ class Puppeteer extends Helper {
1735
1712
  if (!name) {
1736
1713
  return this.page.deleteCookie.apply(this.page, cookies)
1737
1714
  }
1738
- const cookie = cookies.filter((c) => c.name === name)
1715
+ const cookie = cookies.filter(c => c.name === name)
1739
1716
  if (!cookie[0]) return
1740
1717
  return this.page.deleteCookie(cookie[0])
1741
1718
  }
@@ -1760,8 +1737,8 @@ class Puppeteer extends Helper {
1760
1737
  async executeAsyncScript(...args) {
1761
1738
  const asyncFn = function () {
1762
1739
  const args = Array.from(arguments)
1763
- const fn = eval(`(${args.shift()})`) // eslint-disable-line no-eval
1764
- return new Promise((done) => {
1740
+ const fn = eval(`(${args.shift()})`)
1741
+ return new Promise(done => {
1765
1742
  args.push(done)
1766
1743
  fn.apply(null, args)
1767
1744
  })
@@ -1828,7 +1805,7 @@ class Puppeteer extends Helper {
1828
1805
  */
1829
1806
  async grabHTMLFromAll(locator) {
1830
1807
  const els = await this._locate(locator)
1831
- const values = await Promise.all(els.map((el) => el.evaluate((element) => element.innerHTML, el)))
1808
+ const values = await Promise.all(els.map(el => el.evaluate(element => element.innerHTML, el)))
1832
1809
  return values
1833
1810
  }
1834
1811
 
@@ -1851,10 +1828,8 @@ class Puppeteer extends Helper {
1851
1828
  */
1852
1829
  async grabCssPropertyFromAll(locator, cssProperty) {
1853
1830
  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)])
1831
+ const res = await Promise.all(els.map(el => el.evaluate(el => JSON.parse(JSON.stringify(getComputedStyle(el))), el)))
1832
+ const cssValues = res.map(props => props[toCamelCase(cssProperty)])
1858
1833
 
1859
1834
  return cssValues
1860
1835
  }
@@ -1897,19 +1872,16 @@ class Puppeteer extends Helper {
1897
1872
  }
1898
1873
  }
1899
1874
 
1900
- const values = Object.keys(cssPropertiesCamelCase).map((key) => cssPropertiesCamelCase[key])
1875
+ const values = Object.keys(cssPropertiesCamelCase).map(key => cssPropertiesCamelCase[key])
1901
1876
  if (!Array.isArray(props)) props = [props]
1902
1877
  let chunked = chunkArray(props, values.length)
1903
- chunked = chunked.filter((val) => {
1878
+ chunked = chunked.filter(val => {
1904
1879
  for (let i = 0; i < val.length; ++i) {
1905
- // eslint-disable-next-line eqeqeq
1906
1880
  if (val[i] != values[i]) return false
1907
1881
  }
1908
1882
  return true
1909
1883
  })
1910
- return equals(
1911
- `all elements (${new Locator(locator)}) to have CSS property ${JSON.stringify(cssProperties)}`,
1912
- ).assert(chunked.length, elemAmount)
1884
+ return equals(`all elements (${new Locator(locator)}) to have CSS property ${JSON.stringify(cssProperties)}`).assert(chunked.length, elemAmount)
1913
1885
  }
1914
1886
 
1915
1887
  /**
@@ -1922,7 +1894,7 @@ class Puppeteer extends Helper {
1922
1894
 
1923
1895
  const expectedAttributes = Object.entries(attributes)
1924
1896
 
1925
- const valuesPromises = elements.map(async (element) => {
1897
+ const valuesPromises = elements.map(async element => {
1926
1898
  const elementAttributes = {}
1927
1899
  await Promise.all(
1928
1900
  expectedAttributes.map(async ([attribute, expectedValue]) => {
@@ -1935,7 +1907,7 @@ class Puppeteer extends Helper {
1935
1907
 
1936
1908
  const actualAttributes = await Promise.all(valuesPromises)
1937
1909
 
1938
- const matchingElements = actualAttributes.filter((attrs) =>
1910
+ const matchingElements = actualAttributes.filter(attrs =>
1939
1911
  expectedAttributes.every(([attribute, expectedValue]) => {
1940
1912
  const actualValue = attrs[attribute]
1941
1913
  if (!actualValue) return false
@@ -1947,10 +1919,7 @@ class Puppeteer extends Helper {
1947
1919
  const elementsCount = elements.length
1948
1920
  const matchingCount = matchingElements.length
1949
1921
 
1950
- return equals(`all elements (${new Locator(locator)}) to have attributes ${JSON.stringify(attributes)}`).assert(
1951
- matchingCount,
1952
- elementsCount,
1953
- )
1922
+ return equals(`all elements (${new Locator(locator)}) to have attributes ${JSON.stringify(attributes)}`).assert(matchingCount, elementsCount)
1954
1923
  }
1955
1924
 
1956
1925
  /**
@@ -2080,7 +2049,7 @@ class Puppeteer extends Helper {
2080
2049
  * {{> wait }}
2081
2050
  */
2082
2051
  async wait(sec) {
2083
- return new Promise((done) => {
2052
+ return new Promise(done => {
2084
2053
  setTimeout(done, sec * 1000)
2085
2054
  })
2086
2055
  }
@@ -2100,20 +2069,18 @@ class Puppeteer extends Helper {
2100
2069
  if (!els || els.length === 0) {
2101
2070
  return false
2102
2071
  }
2103
- return Array.prototype.filter.call(els, (el) => !el.disabled).length > 0
2072
+ return Array.prototype.filter.call(els, el => !el.disabled).length > 0
2104
2073
  }
2105
2074
  waiter = context.waitForFunction(enabledFn, { timeout: waitTimeout }, locator.value)
2106
2075
  } else {
2107
2076
  const enabledFn = function (locator, $XPath) {
2108
- eval($XPath) // eslint-disable-line no-eval
2109
- return $XPath(null, locator).filter((el) => !el.disabled).length > 0
2077
+ eval($XPath)
2078
+ return $XPath(null, locator).filter(el => !el.disabled).length > 0
2110
2079
  }
2111
2080
  waiter = context.waitForFunction(enabledFn, { timeout: waitTimeout }, locator.value, $XPath.toString())
2112
2081
  }
2113
- return waiter.catch((err) => {
2114
- throw new Error(
2115
- `element (${locator.toString()}) still not enabled after ${waitTimeout / 1000} sec\n${err.message}`,
2116
- )
2082
+ return waiter.catch(err => {
2083
+ throw new Error(`element (${locator.toString()}) still not enabled after ${waitTimeout / 1000} sec\n${err.message}`)
2117
2084
  })
2118
2085
  }
2119
2086
 
@@ -2132,21 +2099,19 @@ class Puppeteer extends Helper {
2132
2099
  if (!els || els.length === 0) {
2133
2100
  return false
2134
2101
  }
2135
- return Array.prototype.filter.call(els, (el) => (el.value.toString() || '').indexOf(value) !== -1).length > 0
2102
+ return Array.prototype.filter.call(els, el => (el.value.toString() || '').indexOf(value) !== -1).length > 0
2136
2103
  }
2137
2104
  waiter = context.waitForFunction(valueFn, { timeout: waitTimeout }, locator.value, value)
2138
2105
  } else {
2139
2106
  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
2107
+ eval($XPath)
2108
+ return $XPath(null, locator).filter(el => (el.value || '').indexOf(value) !== -1).length > 0
2142
2109
  }
2143
2110
  waiter = context.waitForFunction(valueFn, { timeout: waitTimeout }, locator.value, $XPath.toString(), value)
2144
2111
  }
2145
- return waiter.catch((err) => {
2112
+ return waiter.catch(err => {
2146
2113
  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
- )
2114
+ 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
2115
  })
2151
2116
  }
2152
2117
 
@@ -2165,20 +2130,18 @@ class Puppeteer extends Helper {
2165
2130
  if (!els || els.length === 0) {
2166
2131
  return false
2167
2132
  }
2168
- return Array.prototype.filter.call(els, (el) => el.offsetParent !== null).length === num
2133
+ return Array.prototype.filter.call(els, el => el.offsetParent !== null).length === num
2169
2134
  }
2170
2135
  waiter = context.waitForFunction(visibleFn, { timeout: waitTimeout }, locator.value, num)
2171
2136
  } else {
2172
2137
  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
2138
+ eval($XPath)
2139
+ return $XPath(null, locator).filter(el => el.offsetParent !== null).length === num
2175
2140
  }
2176
2141
  waiter = context.waitForFunction(visibleFn, { timeout: waitTimeout }, locator.value, $XPath.toString(), num)
2177
2142
  }
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
- )
2143
+ return waiter.catch(err => {
2144
+ throw new Error(`The number of elements (${locator.toString()}) is not ${num} after ${waitTimeout / 1000} sec\n${err.message}`)
2182
2145
  })
2183
2146
  }
2184
2147
 
@@ -2186,14 +2149,14 @@ class Puppeteer extends Helper {
2186
2149
  * {{> waitForClickable }}
2187
2150
  */
2188
2151
  async waitForClickable(locator, waitTimeout) {
2189
- const els = await this._locate(locator)
2190
- assertElementExists(els, locator)
2152
+ const el = await this._locateElement(locator)
2153
+ if (!el) {
2154
+ throw new ElementNotFound(locator, 'Element to wait for clickable')
2155
+ }
2191
2156
 
2192
- return this.waitForFunction(isElementClickable, [els[0]], waitTimeout).catch(async (e) => {
2157
+ return this.waitForFunction(isElementClickable, [el], waitTimeout).catch(async e => {
2193
2158
  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
- )
2159
+ throw new Error(`element ${new Locator(locator).toString()} still not clickable after ${waitTimeout || this.options.waitForTimeout / 1000} sec`)
2197
2160
  } else {
2198
2161
  throw e
2199
2162
  }
@@ -2215,10 +2178,8 @@ class Puppeteer extends Helper {
2215
2178
  } else {
2216
2179
  waiter = _waitForElement.call(this, locator, { timeout: waitTimeout })
2217
2180
  }
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
- )
2181
+ return waiter.catch(err => {
2182
+ throw new Error(`element (${locator.toString()}) still not present on page after ${waitTimeout / 1000} sec\n${err.message}`)
2222
2183
  })
2223
2184
  }
2224
2185
 
@@ -2238,10 +2199,8 @@ class Puppeteer extends Helper {
2238
2199
  } else {
2239
2200
  waiter = _waitForElement.call(this, locator, { timeout: waitTimeout, visible: true })
2240
2201
  }
2241
- return waiter.catch((err) => {
2242
- throw new Error(
2243
- `element (${locator.toString()}) still not visible after ${waitTimeout / 1000} sec\n${err.message}`,
2244
- )
2202
+ return waiter.catch(err => {
2203
+ throw new Error(`element (${locator.toString()}) still not visible after ${waitTimeout / 1000} sec\n${err.message}`)
2245
2204
  })
2246
2205
  }
2247
2206
 
@@ -2259,7 +2218,7 @@ class Puppeteer extends Helper {
2259
2218
  } else {
2260
2219
  waiter = _waitForElement.call(this, locator, { timeout: waitTimeout, hidden: true })
2261
2220
  }
2262
- return waiter.catch((err) => {
2221
+ return waiter.catch(err => {
2263
2222
  throw new Error(`element (${locator.toString()}) still visible after ${waitTimeout / 1000} sec\n${err.message}`)
2264
2223
  })
2265
2224
  }
@@ -2277,10 +2236,8 @@ class Puppeteer extends Helper {
2277
2236
  } else {
2278
2237
  waiter = _waitForElement.call(this, locator, { timeout: waitTimeout, hidden: true })
2279
2238
  }
2280
- return waiter.catch((err) => {
2281
- throw new Error(
2282
- `element (${locator.toString()}) still not hidden after ${waitTimeout / 1000} sec\n${err.message}`,
2283
- )
2239
+ return waiter.catch(err => {
2240
+ throw new Error(`element (${locator.toString()}) still not hidden after ${waitTimeout / 1000} sec\n${err.message}`)
2284
2241
  })
2285
2242
  }
2286
2243
 
@@ -2317,14 +2274,14 @@ class Puppeteer extends Helper {
2317
2274
 
2318
2275
  return this.page
2319
2276
  .waitForFunction(
2320
- (urlPart) => {
2277
+ urlPart => {
2321
2278
  const currUrl = decodeURIComponent(decodeURIComponent(decodeURIComponent(window.location.href)))
2322
2279
  return currUrl.indexOf(urlPart) > -1
2323
2280
  },
2324
2281
  { timeout: waitTimeout },
2325
2282
  urlPart,
2326
2283
  )
2327
- .catch(async (e) => {
2284
+ .catch(async e => {
2328
2285
  const currUrl = await this._getPageUrl() // Required because the waitForFunction can't return data.
2329
2286
  if (/Waiting failed:/i.test(e.message) || /failed: timeout/i.test(e.message)) {
2330
2287
  throw new Error(`expected url to include ${urlPart}, but found ${currUrl}`)
@@ -2347,14 +2304,14 @@ class Puppeteer extends Helper {
2347
2304
 
2348
2305
  return this.page
2349
2306
  .waitForFunction(
2350
- (urlPart) => {
2307
+ urlPart => {
2351
2308
  const currUrl = decodeURIComponent(decodeURIComponent(decodeURIComponent(window.location.href)))
2352
2309
  return currUrl.indexOf(urlPart) > -1
2353
2310
  },
2354
2311
  { timeout: waitTimeout },
2355
2312
  urlPart,
2356
2313
  )
2357
- .catch(async (e) => {
2314
+ .catch(async e => {
2358
2315
  const currUrl = await this._getPageUrl() // Required because the waitForFunction can't return data.
2359
2316
  if (/Waiting failed/i.test(e.message) || /failed: timeout/i.test(e.message)) {
2360
2317
  throw new Error(`expected url to be ${urlPart}, but found ${currUrl}`)
@@ -2391,7 +2348,7 @@ class Puppeteer extends Helper {
2391
2348
  if (locator.isXPath()) {
2392
2349
  waiter = contextObject.waitForFunction(
2393
2350
  (locator, text, $XPath) => {
2394
- eval($XPath) // eslint-disable-line no-eval
2351
+ eval($XPath)
2395
2352
  const el = $XPath(null, locator)
2396
2353
  if (!el.length) return false
2397
2354
  return el[0].innerText.indexOf(text) > -1
@@ -2403,14 +2360,10 @@ class Puppeteer extends Helper {
2403
2360
  )
2404
2361
  }
2405
2362
  } else {
2406
- waiter = contextObject.waitForFunction(
2407
- (text) => document.body && document.body.innerText.indexOf(text) > -1,
2408
- { timeout: waitTimeout },
2409
- text,
2410
- )
2363
+ waiter = contextObject.waitForFunction(text => document.body && document.body.innerText.indexOf(text) > -1, { timeout: waitTimeout }, text)
2411
2364
  }
2412
2365
 
2413
- return waiter.catch((err) => {
2366
+ return waiter.catch(err => {
2414
2367
  throw new Error(`Text "${text}" was not found on page after ${waitTimeout / 1000} sec\n${err.message}`)
2415
2368
  })
2416
2369
  }
@@ -2543,12 +2496,12 @@ class Puppeteer extends Helper {
2543
2496
  waiter = context.waitForFunction(visibleFn, { timeout: waitTimeout }, locator.value)
2544
2497
  } else {
2545
2498
  const visibleFn = function (locator, $XPath) {
2546
- eval($XPath) // eslint-disable-line no-eval
2499
+ eval($XPath)
2547
2500
  return $XPath(null, locator).length === 0
2548
2501
  }
2549
2502
  waiter = context.waitForFunction(visibleFn, { timeout: waitTimeout }, locator.value, $XPath.toString())
2550
2503
  }
2551
- return waiter.catch((err) => {
2504
+ return waiter.catch(err => {
2552
2505
  throw new Error(`element (${locator.toString()}) still on page after ${waitTimeout / 1000} sec\n${err.message}`)
2553
2506
  })
2554
2507
  }
@@ -2589,7 +2542,7 @@ class Puppeteer extends Helper {
2589
2542
  async mockRoute(url, handler) {
2590
2543
  await this.page.setRequestInterception(true)
2591
2544
 
2592
- this.page.on('request', (interceptedRequest) => {
2545
+ this.page.on('request', interceptedRequest => {
2593
2546
  if (interceptedRequest.url().match(url)) {
2594
2547
  // @ts-ignore
2595
2548
  handler(interceptedRequest)
@@ -2614,7 +2567,7 @@ class Puppeteer extends Helper {
2614
2567
  this.page.off('request')
2615
2568
 
2616
2569
  // Resume normal request handling for the given URL
2617
- this.page.on('request', (interceptedRequest) => {
2570
+ this.page.on('request', interceptedRequest => {
2618
2571
  if (interceptedRequest.url().includes(url)) {
2619
2572
  interceptedRequest.continue()
2620
2573
  } else {
@@ -2650,7 +2603,7 @@ class Puppeteer extends Helper {
2650
2603
 
2651
2604
  await this.page.setRequestInterception(true)
2652
2605
 
2653
- this.page.on('request', (request) => {
2606
+ this.page.on('request', request => {
2654
2607
  const information = {
2655
2608
  url: request.url(),
2656
2609
  method: request.method(),
@@ -2711,15 +2664,15 @@ class Puppeteer extends Helper {
2711
2664
  await this.cdpSession.send('Network.enable')
2712
2665
  await this.cdpSession.send('Page.enable')
2713
2666
 
2714
- this.cdpSession.on('Network.webSocketFrameReceived', (payload) => {
2667
+ this.cdpSession.on('Network.webSocketFrameReceived', payload => {
2715
2668
  this._logWebsocketMessages(this._getWebSocketLog('RECEIVED', payload))
2716
2669
  })
2717
2670
 
2718
- this.cdpSession.on('Network.webSocketFrameSent', (payload) => {
2671
+ this.cdpSession.on('Network.webSocketFrameSent', payload => {
2719
2672
  this._logWebsocketMessages(this._getWebSocketLog('SENT', payload))
2720
2673
  })
2721
2674
 
2722
- this.cdpSession.on('Network.webSocketFrameError', (payload) => {
2675
+ this.cdpSession.on('Network.webSocketFrameError', payload => {
2723
2676
  this._logWebsocketMessages(this._getWebSocketLog('ERROR', payload))
2724
2677
  })
2725
2678
  }
@@ -2743,9 +2696,7 @@ class Puppeteer extends Helper {
2743
2696
  grabWebSocketMessages() {
2744
2697
  if (!this.recordingWebSocketMessages) {
2745
2698
  if (!this.recordedWebSocketMessagesAtLeastOnce) {
2746
- throw new Error(
2747
- 'Failure in test automation. You use "I.grabWebSocketMessages", but "I.startRecordingWebSocketMessages" was never called before.',
2748
- )
2699
+ throw new Error('Failure in test automation. You use "I.grabWebSocketMessages", but "I.startRecordingWebSocketMessages" was never called before.')
2749
2700
  }
2750
2701
  }
2751
2702
  return this.webSocketMessages
@@ -2777,9 +2728,18 @@ class Puppeteer extends Helper {
2777
2728
 
2778
2729
  module.exports = Puppeteer
2779
2730
 
2731
+ /**
2732
+ * Find elements using Puppeteer's native element discovery methods
2733
+ * Note: Unlike Playwright, Puppeteer's Locator API doesn't have .all() method for multiple elements
2734
+ * @param {Object} matcher - Puppeteer context to search within
2735
+ * @param {Object|string} locator - Locator specification
2736
+ * @returns {Promise<Array>} Array of ElementHandle objects
2737
+ */
2780
2738
  async function findElements(matcher, locator) {
2781
2739
  if (locator.react) return findReactElements.call(this, locator)
2782
2740
  locator = new Locator(locator, 'css')
2741
+
2742
+ // Use proven legacy approach - Puppeteer Locator API doesn't have .all() method
2783
2743
  if (!locator.isXPath()) return matcher.$$(locator.simplify())
2784
2744
  // puppeteer version < 19.4.0 is no longer supported. This one is backward support.
2785
2745
  if (puppeteer.default?.defaultBrowserRevision) {
@@ -2788,6 +2748,31 @@ async function findElements(matcher, locator) {
2788
2748
  return matcher.$x(locator.value)
2789
2749
  }
2790
2750
 
2751
+ /**
2752
+ * Find a single element using Puppeteer's native element discovery methods
2753
+ * Note: Puppeteer Locator API doesn't have .first() method like Playwright
2754
+ * @param {Object} matcher - Puppeteer context to search within
2755
+ * @param {Object|string} locator - Locator specification
2756
+ * @returns {Promise<Object>} Single ElementHandle object
2757
+ */
2758
+ async function findElement(matcher, locator) {
2759
+ if (locator.react) return findReactElements.call(this, locator)
2760
+ locator = new Locator(locator, 'css')
2761
+
2762
+ // Use proven legacy approach - Puppeteer Locator API doesn't have .first() method
2763
+ if (!locator.isXPath()) {
2764
+ const elements = await matcher.$$(locator.simplify())
2765
+ return elements[0]
2766
+ }
2767
+ // puppeteer version < 19.4.0 is no longer supported. This one is backward support.
2768
+ if (puppeteer.default?.defaultBrowserRevision) {
2769
+ const elements = await matcher.$$(`xpath/${locator.value}`)
2770
+ return elements[0]
2771
+ }
2772
+ const elements = await matcher.$x(locator.value)
2773
+ return elements[0]
2774
+ }
2775
+
2791
2776
  async function proceedClick(locator, context = null, options = {}) {
2792
2777
  let matcher = await this.context
2793
2778
  if (context) {
@@ -2797,12 +2782,7 @@ async function proceedClick(locator, context = null, options = {}) {
2797
2782
  }
2798
2783
  const els = await findClickable.call(this, matcher, locator)
2799
2784
  if (context) {
2800
- assertElementExists(
2801
- els,
2802
- locator,
2803
- 'Clickable element',
2804
- `was not found inside element ${new Locator(context).toString()}`,
2805
- )
2785
+ assertElementExists(els, locator, 'Clickable element', `was not found inside element ${new Locator(context).toString()}`)
2806
2786
  } else {
2807
2787
  assertElementExists(els, locator, 'Clickable element')
2808
2788
  }
@@ -2854,23 +2834,25 @@ async function proceedSee(assertType, text, context, strict = false) {
2854
2834
  el = await this.context.$('body')
2855
2835
  }
2856
2836
 
2857
- allText = [await el.getProperty('innerText').then((p) => p.jsonValue())]
2837
+ allText = [await el.getProperty('innerText').then(p => p.jsonValue())]
2858
2838
  description = 'web application'
2859
2839
  } else {
2860
2840
  const locator = new Locator(context, 'css')
2861
2841
  description = `element ${locator.toString()}`
2862
2842
  const els = await this._locate(locator)
2863
2843
  assertElementExists(els, locator.toString())
2864
- allText = await Promise.all(els.map((el) => el.getProperty('innerText').then((p) => p.jsonValue())))
2844
+ allText = await Promise.all(els.map(el => el.getProperty('innerText').then(p => p.jsonValue())))
2845
+ }
2846
+
2847
+ if (store?.currentStep?.opts?.ignoreCase === true) {
2848
+ text = text.toLowerCase()
2849
+ allText = allText.map(elText => elText.toLowerCase())
2865
2850
  }
2866
2851
 
2867
2852
  if (strict) {
2868
- return allText.map((elText) => equals(description)[assertType](text, elText))
2853
+ return allText.map(elText => equals(description)[assertType](text, elText))
2869
2854
  }
2870
- return stringIncludes(description)[assertType](
2871
- normalizeSpacesInString(text),
2872
- normalizeSpacesInString(allText.join(' | ')),
2873
- )
2855
+ return stringIncludes(description)[assertType](normalizeSpacesInString(text), normalizeSpacesInString(allText.join(' | ')))
2874
2856
  }
2875
2857
 
2876
2858
  async function findCheckable(locator, context) {
@@ -2900,15 +2882,15 @@ async function findCheckable(locator, context) {
2900
2882
  async function proceedIsChecked(assertType, option) {
2901
2883
  let els = await findCheckable.call(this, option)
2902
2884
  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()))
2885
+ els = await Promise.all(els.map(el => el.getProperty('checked')))
2886
+ els = await Promise.all(els.map(el => el.jsonValue()))
2905
2887
  const selected = els.reduce((prev, cur) => prev || cur)
2906
2888
  return truth(`checkable ${option}`, 'to be checked')[assertType](selected)
2907
2889
  }
2908
2890
 
2909
2891
  async function findVisibleFields(locator) {
2910
2892
  const els = await findFields.call(this, locator)
2911
- const visible = await Promise.all(els.map((el) => el.boundingBox()))
2893
+ const visible = await Promise.all(els.map(el => el.boundingBox()))
2912
2894
  return els.filter((el, index) => visible[index])
2913
2895
  }
2914
2896
 
@@ -2936,15 +2918,19 @@ async function findFields(locator) {
2936
2918
  }
2937
2919
 
2938
2920
  async function proceedDragAndDrop(sourceLocator, destinationLocator) {
2939
- const src = await this._locate(sourceLocator)
2940
- assertElementExists(src, sourceLocator, 'Source Element')
2921
+ const src = await this._locateElement(sourceLocator)
2922
+ if (!src) {
2923
+ throw new ElementNotFound(sourceLocator, 'Source Element')
2924
+ }
2941
2925
 
2942
- const dst = await this._locate(destinationLocator)
2943
- assertElementExists(dst, destinationLocator, 'Destination Element')
2926
+ const dst = await this._locateElement(destinationLocator)
2927
+ if (!dst) {
2928
+ throw new ElementNotFound(destinationLocator, 'Destination Element')
2929
+ }
2944
2930
 
2945
- // Note: Using public api .getClickablePoint becaues the .BoundingBox does not take into account iframe offsets
2946
- const dragSource = await getClickablePoint(src[0])
2947
- const dragDestination = await getClickablePoint(dst[0])
2931
+ // Note: Using public api .getClickablePoint because the .BoundingBox does not take into account iframe offsets
2932
+ const dragSource = await getClickablePoint(src)
2933
+ const dragDestination = await getClickablePoint(dst)
2948
2934
 
2949
2935
  // Drag start point
2950
2936
  await this.page.mouse.move(dragSource.x, dragSource.y, { steps: 5 })
@@ -2961,15 +2947,15 @@ async function proceedSeeInField(assertType, field, value) {
2961
2947
  const els = await findVisibleFields.call(this, field)
2962
2948
  assertElementExists(els, field, 'Field')
2963
2949
  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())
2950
+ const tag = await el.getProperty('tagName').then(el => el.jsonValue())
2951
+ const fieldType = await el.getProperty('type').then(el => el.jsonValue())
2966
2952
 
2967
- const proceedMultiple = async (elements) => {
2953
+ const proceedMultiple = async elements => {
2968
2954
  const fields = Array.isArray(elements) ? elements : [elements]
2969
2955
 
2970
2956
  const elementValues = []
2971
2957
  for (const element of fields) {
2972
- elementValues.push(await element.getProperty('value').then((el) => el.jsonValue()))
2958
+ elementValues.push(await element.getProperty('value').then(el => el.jsonValue()))
2973
2959
  }
2974
2960
 
2975
2961
  if (typeof value === 'boolean') {
@@ -2978,7 +2964,7 @@ async function proceedSeeInField(assertType, field, value) {
2978
2964
  if (assertType === 'assert') {
2979
2965
  equals(`select option by ${field}`)[assertType](true, elementValues.length > 0)
2980
2966
  }
2981
- elementValues.forEach((val) => stringIncludes(`fields by ${field}`)[assertType](value, val))
2967
+ elementValues.forEach(val => stringIncludes(`fields by ${field}`)[assertType](value, val))
2982
2968
  }
2983
2969
  }
2984
2970
 
@@ -3006,14 +2992,14 @@ async function proceedSeeInField(assertType, field, value) {
3006
2992
  }
3007
2993
  return proceedMultiple(els[0])
3008
2994
  }
3009
- const fieldVal = await el.getProperty('value').then((el) => el.jsonValue())
2995
+ const fieldVal = await el.getProperty('value').then(el => el.jsonValue())
3010
2996
  return stringIncludes(`fields by ${field}`)[assertType](value, fieldVal)
3011
2997
  }
3012
2998
 
3013
2999
  async function filterFieldsByValue(elements, value, onlySelected) {
3014
3000
  const matches = []
3015
3001
  for (const element of elements) {
3016
- const val = await element.getProperty('value').then((el) => el.jsonValue())
3002
+ const val = await element.getProperty('value').then(el => el.jsonValue())
3017
3003
  let isSelected = true
3018
3004
  if (onlySelected) {
3019
3005
  isSelected = await elementSelected(element)
@@ -3037,12 +3023,12 @@ async function filterFieldsBySelectionState(elements, state) {
3037
3023
  }
3038
3024
 
3039
3025
  async function elementSelected(element) {
3040
- const type = await element.getProperty('type').then((el) => el.jsonValue())
3026
+ const type = await element.getProperty('type').then(el => el.jsonValue())
3041
3027
 
3042
3028
  if (type === 'checkbox' || type === 'radio') {
3043
- return element.getProperty('checked').then((el) => el.jsonValue())
3029
+ return element.getProperty('checked').then(el => el.jsonValue())
3044
3030
  }
3045
- return element.getProperty('selected').then((el) => el.jsonValue())
3031
+ return element.getProperty('selected').then(el => el.jsonValue())
3046
3032
  }
3047
3033
 
3048
3034
  function isFrameLocator(locator) {
@@ -3077,9 +3063,9 @@ async function targetCreatedHandler(page) {
3077
3063
  page
3078
3064
  .$('body')
3079
3065
  .catch(() => null)
3080
- .then((context) => (this.context = context))
3066
+ .then(context => (this.context = context))
3081
3067
  })
3082
- page.on('console', (msg) => {
3068
+ page.on('console', msg => {
3083
3069
  this.debugSection(`Browser:${ucfirst(msg.type())}`, (msg._text || '') + msg.args().join(' '))
3084
3070
  consoleLogStore.add(msg)
3085
3071
  })
@@ -3186,7 +3172,7 @@ async function findReactElements(locator, props = {}, state = {}) {
3186
3172
 
3187
3173
  await this.page.evaluate(() => window.resq.waitToLoadReact())
3188
3174
  const arrayHandle = await this.page.evaluateHandle(
3189
- (obj) => {
3175
+ obj => {
3190
3176
  const { selector, props, state } = obj
3191
3177
  let elements = window.resq.resq$$(selector)
3192
3178
  if (Object.keys(props).length) {
@@ -3205,7 +3191,7 @@ async function findReactElements(locator, props = {}, state = {}) {
3205
3191
  // [[div, div], [div, div]] => [div, div, div, div]
3206
3192
  let nodes = []
3207
3193
 
3208
- elements.forEach((element) => {
3194
+ elements.forEach(element => {
3209
3195
  let { node, isFragment } = element
3210
3196
 
3211
3197
  if (!node) {