codeceptjs 4.0.0-beta.3 → 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 (155) 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 +141 -86
  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/dryRun.js +30 -35
  17. package/lib/command/generate.js +10 -14
  18. package/lib/command/gherkin/snippets.js +75 -73
  19. package/lib/command/gherkin/steps.js +1 -1
  20. package/lib/command/info.js +42 -8
  21. package/lib/command/init.js +13 -12
  22. package/lib/command/interactive.js +10 -2
  23. package/lib/command/list.js +1 -1
  24. package/lib/command/run-multiple/chunk.js +48 -45
  25. package/lib/command/run-multiple.js +12 -35
  26. package/lib/command/run-workers.js +21 -58
  27. package/lib/command/utils.js +5 -6
  28. package/lib/command/workers/runTests.js +263 -222
  29. package/lib/container.js +386 -238
  30. package/lib/data/context.js +10 -13
  31. package/lib/data/dataScenarioConfig.js +8 -8
  32. package/lib/data/dataTableArgument.js +6 -6
  33. package/lib/data/table.js +5 -11
  34. package/lib/effects.js +223 -0
  35. package/lib/element/WebElement.js +327 -0
  36. package/lib/els.js +158 -0
  37. package/lib/event.js +21 -17
  38. package/lib/heal.js +88 -80
  39. package/lib/helper/AI.js +2 -1
  40. package/lib/helper/ApiDataFactory.js +4 -7
  41. package/lib/helper/Appium.js +50 -57
  42. package/lib/helper/FileSystem.js +3 -3
  43. package/lib/helper/GraphQLDataFactory.js +4 -4
  44. package/lib/helper/JSONResponse.js +75 -37
  45. package/lib/helper/Mochawesome.js +31 -9
  46. package/lib/helper/Nightmare.js +37 -58
  47. package/lib/helper/Playwright.js +267 -272
  48. package/lib/helper/Protractor.js +56 -87
  49. package/lib/helper/Puppeteer.js +247 -264
  50. package/lib/helper/REST.js +29 -17
  51. package/lib/helper/TestCafe.js +22 -47
  52. package/lib/helper/WebDriver.js +157 -368
  53. package/lib/helper/extras/PlaywrightPropEngine.js +2 -2
  54. package/lib/helper/extras/Popup.js +22 -22
  55. package/lib/helper/network/utils.js +1 -1
  56. package/lib/helper/testcafe/testcafe-utils.js +27 -28
  57. package/lib/listener/emptyRun.js +55 -0
  58. package/lib/listener/exit.js +7 -10
  59. package/lib/listener/{retry.js → globalRetry.js} +5 -5
  60. package/lib/listener/globalTimeout.js +165 -0
  61. package/lib/listener/helpers.js +15 -15
  62. package/lib/listener/mocha.js +1 -1
  63. package/lib/listener/result.js +12 -0
  64. package/lib/listener/retryEnhancer.js +85 -0
  65. package/lib/listener/steps.js +32 -18
  66. package/lib/listener/store.js +20 -0
  67. package/lib/locator.js +1 -1
  68. package/lib/mocha/asyncWrapper.js +231 -0
  69. package/lib/{interfaces → mocha}/bdd.js +3 -3
  70. package/lib/mocha/cli.js +308 -0
  71. package/lib/mocha/factory.js +104 -0
  72. package/lib/{interfaces → mocha}/featureConfig.js +32 -12
  73. package/lib/{interfaces → mocha}/gherkin.js +26 -28
  74. package/lib/mocha/hooks.js +112 -0
  75. package/lib/mocha/index.js +12 -0
  76. package/lib/mocha/inject.js +29 -0
  77. package/lib/{interfaces → mocha}/scenarioConfig.js +31 -7
  78. package/lib/mocha/suite.js +82 -0
  79. package/lib/mocha/test.js +181 -0
  80. package/lib/mocha/types.d.ts +42 -0
  81. package/lib/mocha/ui.js +232 -0
  82. package/lib/output.js +93 -65
  83. package/lib/pause.js +160 -138
  84. package/lib/plugin/analyze.js +396 -0
  85. package/lib/plugin/auth.js +435 -0
  86. package/lib/plugin/autoDelay.js +8 -8
  87. package/lib/plugin/autoLogin.js +3 -338
  88. package/lib/plugin/commentStep.js +6 -1
  89. package/lib/plugin/coverage.js +10 -22
  90. package/lib/plugin/customLocator.js +3 -3
  91. package/lib/plugin/customReporter.js +52 -0
  92. package/lib/plugin/eachElement.js +1 -1
  93. package/lib/plugin/fakerTransform.js +1 -1
  94. package/lib/plugin/heal.js +36 -9
  95. package/lib/plugin/htmlReporter.js +1947 -0
  96. package/lib/plugin/pageInfo.js +140 -0
  97. package/lib/plugin/retryFailedStep.js +17 -18
  98. package/lib/plugin/retryTo.js +2 -113
  99. package/lib/plugin/screenshotOnFail.js +17 -58
  100. package/lib/plugin/selenoid.js +15 -35
  101. package/lib/plugin/standardActingHelpers.js +4 -1
  102. package/lib/plugin/stepByStepReport.js +56 -17
  103. package/lib/plugin/stepTimeout.js +5 -12
  104. package/lib/plugin/subtitles.js +4 -4
  105. package/lib/plugin/tryTo.js +3 -102
  106. package/lib/plugin/wdio.js +8 -10
  107. package/lib/recorder.js +155 -124
  108. package/lib/rerun.js +43 -42
  109. package/lib/result.js +161 -0
  110. package/lib/secret.js +1 -2
  111. package/lib/step/base.js +239 -0
  112. package/lib/step/comment.js +10 -0
  113. package/lib/step/config.js +50 -0
  114. package/lib/step/func.js +46 -0
  115. package/lib/step/helper.js +50 -0
  116. package/lib/step/meta.js +99 -0
  117. package/lib/step/record.js +74 -0
  118. package/lib/step/retry.js +11 -0
  119. package/lib/step/section.js +55 -0
  120. package/lib/step.js +21 -332
  121. package/lib/steps.js +50 -0
  122. package/lib/store.js +37 -5
  123. package/lib/template/heal.js +2 -11
  124. package/lib/test-server.js +323 -0
  125. package/lib/timeout.js +66 -0
  126. package/lib/utils.js +351 -218
  127. package/lib/within.js +75 -55
  128. package/lib/workerStorage.js +2 -1
  129. package/lib/workers.js +386 -277
  130. package/package.json +81 -75
  131. package/translations/de-DE.js +5 -3
  132. package/translations/fr-FR.js +5 -4
  133. package/translations/index.js +1 -0
  134. package/translations/it-IT.js +4 -3
  135. package/translations/ja-JP.js +4 -3
  136. package/translations/nl-NL.js +76 -0
  137. package/translations/pl-PL.js +4 -3
  138. package/translations/pt-BR.js +4 -3
  139. package/translations/ru-RU.js +4 -3
  140. package/translations/utils.js +9 -0
  141. package/translations/zh-CN.js +4 -3
  142. package/translations/zh-TW.js +4 -3
  143. package/typings/index.d.ts +197 -187
  144. package/typings/promiseBasedTypes.d.ts +53 -903
  145. package/typings/types.d.ts +372 -1042
  146. package/lib/cli.js +0 -257
  147. package/lib/helper/ExpectHelper.js +0 -391
  148. package/lib/helper/MockServer.js +0 -221
  149. package/lib/helper/SoftExpectHelper.js +0 -381
  150. package/lib/listener/artifacts.js +0 -19
  151. package/lib/listener/timeout.js +0 -109
  152. package/lib/mochaFactory.js +0 -113
  153. package/lib/plugin/debugErrors.js +0 -67
  154. package/lib/scenario.js +0 -224
  155. 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
  }
@@ -861,14 +842,13 @@ class Puppeteer extends Helper {
861
842
  * {{> grabPageScrollPosition }}
862
843
  */
863
844
  async grabPageScrollPosition() {
864
- /* eslint-disable comma-dangle */
865
845
  function getScrollPosition() {
866
846
  return {
867
847
  x: window.pageXOffset,
868
848
  y: window.pageYOffset,
869
849
  }
870
850
  }
871
- /* eslint-enable comma-dangle */
851
+
872
852
  return this.executeScript(getScrollPosition)
873
853
  }
874
854
 
@@ -910,6 +890,21 @@ class Puppeteer extends Helper {
910
890
  return findElements.call(this, context, locator)
911
891
  }
912
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
+
913
908
  /**
914
909
  * Find a checkbox by providing human-readable text:
915
910
  * NOTE: Assumes the checkable element exists
@@ -921,7 +916,9 @@ class Puppeteer extends Helper {
921
916
  async _locateCheckable(locator, providedContext = null) {
922
917
  const context = providedContext || (await this._getContext())
923
918
  const els = await findCheckable.call(this, locator, context)
924
- assertElementExists(els[0], locator, 'Checkbox or radio')
919
+ if (!els || els.length === 0) {
920
+ throw new ElementNotFound(locator, 'Checkbox or radio')
921
+ }
925
922
  return els[0]
926
923
  }
927
924
 
@@ -953,7 +950,20 @@ class Puppeteer extends Helper {
953
950
  *
954
951
  */
955
952
  async grabWebElements(locator) {
956
- 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)
957
967
  }
958
968
 
959
969
  /**
@@ -1026,10 +1036,10 @@ class Puppeteer extends Helper {
1026
1036
  */
1027
1037
  async closeOtherTabs() {
1028
1038
  const pages = await this.browser.pages()
1029
- const otherPages = pages.filter((page) => page !== this.page)
1039
+ const otherPages = pages.filter(page => page !== this.page)
1030
1040
 
1031
1041
  let p = Promise.resolve()
1032
- otherPages.forEach((page) => {
1042
+ otherPages.forEach(page => {
1033
1043
  p = p.then(() => page.close())
1034
1044
  })
1035
1045
  await p
@@ -1062,19 +1072,11 @@ class Puppeteer extends Helper {
1062
1072
  */
1063
1073
  async seeElement(locator) {
1064
1074
  let els = await this._locate(locator)
1065
- 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)
1066
1076
  // Puppeteer visibility was ignored? | Remove when Puppeteer is fixed
1067
- els = await Promise.all(
1068
- els.map(
1069
- async (el) =>
1070
- (await el.evaluate(
1071
- (node) =>
1072
- window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none',
1073
- )) && el,
1074
- ),
1075
- )
1077
+ els = await Promise.all(els.map(async el => (await el.evaluate(node => window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none')) && el))
1076
1078
  try {
1077
- return empty('visible elements').negate(els.filter((v) => v).fill('ELEMENT'))
1079
+ return empty('visible elements').negate(els.filter(v => v).fill('ELEMENT'))
1078
1080
  } catch (e) {
1079
1081
  dontSeeElementError(locator)
1080
1082
  }
@@ -1086,19 +1088,11 @@ class Puppeteer extends Helper {
1086
1088
  */
1087
1089
  async dontSeeElement(locator) {
1088
1090
  let els = await this._locate(locator)
1089
- 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)
1090
1092
  // Puppeteer visibility was ignored? | Remove when Puppeteer is fixed
1091
- els = await Promise.all(
1092
- els.map(
1093
- async (el) =>
1094
- (await el.evaluate(
1095
- (node) =>
1096
- window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none',
1097
- )) && el,
1098
- ),
1099
- )
1093
+ els = await Promise.all(els.map(async el => (await el.evaluate(node => window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none')) && el))
1100
1094
  try {
1101
- return empty('visible elements').assert(els.filter((v) => v).fill('ELEMENT'))
1095
+ return empty('visible elements').assert(els.filter(v => v).fill('ELEMENT'))
1102
1096
  } catch (e) {
1103
1097
  seeElementError(locator)
1104
1098
  }
@@ -1110,7 +1104,7 @@ class Puppeteer extends Helper {
1110
1104
  async seeElementInDOM(locator) {
1111
1105
  const els = await this._locate(locator)
1112
1106
  try {
1113
- 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'))
1114
1108
  } catch (e) {
1115
1109
  dontSeeElementInDOMError(locator)
1116
1110
  }
@@ -1122,7 +1116,7 @@ class Puppeteer extends Helper {
1122
1116
  async dontSeeElementInDOM(locator) {
1123
1117
  const els = await this._locate(locator)
1124
1118
  try {
1125
- 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'))
1126
1120
  } catch (e) {
1127
1121
  seeElementInDOMError(locator)
1128
1122
  }
@@ -1152,17 +1146,12 @@ class Puppeteer extends Helper {
1152
1146
 
1153
1147
  const els = await findClickable.call(this, matcher, locator)
1154
1148
  if (context) {
1155
- assertElementExists(
1156
- els,
1157
- locator,
1158
- 'Clickable element',
1159
- `was not found inside element ${new Locator(context).toString()}`,
1160
- )
1149
+ assertElementExists(els, locator, 'Clickable element', `was not found inside element ${new Locator(context).toString()}`)
1161
1150
  } else {
1162
1151
  assertElementExists(els, locator, 'Clickable element')
1163
1152
  }
1164
1153
  const elem = els[0]
1165
- return this.executeScript((el) => {
1154
+ return this.executeScript(el => {
1166
1155
  if (document.activeElement instanceof HTMLElement) {
1167
1156
  document.activeElement.blur()
1168
1157
  }
@@ -1221,8 +1210,8 @@ class Puppeteer extends Helper {
1221
1210
  let fileName
1222
1211
  await this.page.setRequestInterception(true)
1223
1212
 
1224
- const xRequest = await new Promise((resolve) => {
1225
- this.page.on('request', (request) => {
1213
+ const xRequest = await new Promise(resolve => {
1214
+ this.page.on('request', request => {
1226
1215
  console.log('rq', request, customName)
1227
1216
  const grabbedFileName = request.url().split('/')[request.url().split('/').length - 1]
1228
1217
  const fileExtension = request.url().split('/')[request.url().split('/').length - 1].split('.')[1]
@@ -1249,7 +1238,7 @@ class Puppeteer extends Helper {
1249
1238
  }
1250
1239
 
1251
1240
  const cookies = await this.page.cookies()
1252
- options.headers.Cookie = cookies.map((ck) => `${ck.name}=${ck.value}`).join(';')
1241
+ options.headers.Cookie = cookies.map(ck => `${ck.name}=${ck.value}`).join(';')
1253
1242
 
1254
1243
  const response = await axios({
1255
1244
  method: options.method,
@@ -1303,7 +1292,7 @@ class Puppeteer extends Helper {
1303
1292
  */
1304
1293
  async checkOption(field, context = null) {
1305
1294
  const elm = await this._locateCheckable(field, context)
1306
- const curentlyChecked = await elm.getProperty('checked').then((checkedProperty) => checkedProperty.jsonValue())
1295
+ const curentlyChecked = await elm.getProperty('checked').then(checkedProperty => checkedProperty.jsonValue())
1307
1296
  // Only check if NOT currently checked
1308
1297
  if (!curentlyChecked) {
1309
1298
  await elm.click()
@@ -1316,7 +1305,7 @@ class Puppeteer extends Helper {
1316
1305
  */
1317
1306
  async uncheckOption(field, context = null) {
1318
1307
  const elm = await this._locateCheckable(field, context)
1319
- const curentlyChecked = await elm.getProperty('checked').then((checkedProperty) => checkedProperty.jsonValue())
1308
+ const curentlyChecked = await elm.getProperty('checked').then(checkedProperty => checkedProperty.jsonValue())
1320
1309
  // Only uncheck if currently checked
1321
1310
  if (curentlyChecked) {
1322
1311
  await elm.click()
@@ -1409,12 +1398,12 @@ class Puppeteer extends Helper {
1409
1398
  const els = await findVisibleFields.call(this, field)
1410
1399
  assertElementExists(els, field, 'Field')
1411
1400
  const el = els[0]
1412
- const tag = await el.getProperty('tagName').then((el) => el.jsonValue())
1413
- 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())
1414
1403
  if (tag === 'INPUT' || tag === 'TEXTAREA') {
1415
- await this._evaluateHandeInContext((el) => (el.value = ''), el)
1404
+ await this._evaluateHandeInContext(el => (el.value = ''), el)
1416
1405
  } else if (editable) {
1417
- await this._evaluateHandeInContext((el) => (el.innerHTML = ''), el)
1406
+ await this._evaluateHandeInContext(el => (el.innerHTML = ''), el)
1418
1407
  }
1419
1408
 
1420
1409
  highlightActiveElement.call(this, el, await this._getContext())
@@ -1484,7 +1473,7 @@ class Puppeteer extends Helper {
1484
1473
  const els = await findVisibleFields.call(this, select)
1485
1474
  assertElementExists(els, select, 'Selectable field')
1486
1475
  const el = els[0]
1487
- if ((await el.getProperty('tagName').then((t) => t.jsonValue())) !== 'SELECT') {
1476
+ if ((await el.getProperty('tagName').then(t => t.jsonValue())) !== 'SELECT') {
1488
1477
  throw new Error('Element is not <select>')
1489
1478
  }
1490
1479
  highlightActiveElement.call(this, els[0], await this._getContext())
@@ -1494,15 +1483,15 @@ class Puppeteer extends Helper {
1494
1483
  const opt = xpathLocator.literal(option[key])
1495
1484
  let optEl = await findElements.call(this, el, { xpath: Locator.select.byVisibleText(opt) })
1496
1485
  if (optEl.length) {
1497
- this._evaluateHandeInContext((el) => (el.selected = true), optEl[0])
1486
+ this._evaluateHandeInContext(el => (el.selected = true), optEl[0])
1498
1487
  continue
1499
1488
  }
1500
1489
  optEl = await findElements.call(this, el, { xpath: Locator.select.byValue(opt) })
1501
1490
  if (optEl.length) {
1502
- this._evaluateHandeInContext((el) => (el.selected = true), optEl[0])
1491
+ this._evaluateHandeInContext(el => (el.selected = true), optEl[0])
1503
1492
  }
1504
1493
  }
1505
- await this._evaluateHandeInContext((element) => {
1494
+ await this._evaluateHandeInContext(element => {
1506
1495
  element.dispatchEvent(new Event('input', { bubbles: true }))
1507
1496
  element.dispatchEvent(new Event('change', { bubbles: true }))
1508
1497
  }, el)
@@ -1516,19 +1505,11 @@ class Puppeteer extends Helper {
1516
1505
  */
1517
1506
  async grabNumberOfVisibleElements(locator) {
1518
1507
  let els = await this._locate(locator)
1519
- 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)
1520
1509
  // Puppeteer visibility was ignored? | Remove when Puppeteer is fixed
1521
- els = await Promise.all(
1522
- els.map(
1523
- async (el) =>
1524
- (await el.evaluate(
1525
- (node) =>
1526
- window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none',
1527
- )) && el,
1528
- ),
1529
- )
1510
+ els = await Promise.all(els.map(async el => (await el.evaluate(node => window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none')) && el))
1530
1511
 
1531
- return els.filter((v) => v).length
1512
+ return els.filter(v => v).length
1532
1513
  }
1533
1514
 
1534
1515
  /**
@@ -1636,9 +1617,7 @@ class Puppeteer extends Helper {
1636
1617
  */
1637
1618
  async seeNumberOfElements(locator, num) {
1638
1619
  const elements = await this._locate(locator)
1639
- return equals(
1640
- `expected number of elements (${new Locator(locator)}) is ${num}, but found ${elements.length}`,
1641
- ).assert(elements.length, num)
1620
+ return equals(`expected number of elements (${new Locator(locator)}) is ${num}, but found ${elements.length}`).assert(elements.length, num)
1642
1621
  }
1643
1622
 
1644
1623
  /**
@@ -1648,10 +1627,7 @@ class Puppeteer extends Helper {
1648
1627
  */
1649
1628
  async seeNumberOfVisibleElements(locator, num) {
1650
1629
  const res = await this.grabNumberOfVisibleElements(locator)
1651
- return equals(`expected number of visible elements (${new Locator(locator)}) is ${num}, but found ${res}`).assert(
1652
- res,
1653
- num,
1654
- )
1630
+ return equals(`expected number of visible elements (${new Locator(locator)}) is ${num}, but found ${res}`).assert(res, num)
1655
1631
  }
1656
1632
 
1657
1633
  /**
@@ -1670,7 +1646,7 @@ class Puppeteer extends Helper {
1670
1646
  */
1671
1647
  async seeCookie(name) {
1672
1648
  const cookies = await this.page.cookies()
1673
- 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))
1674
1650
  }
1675
1651
 
1676
1652
  /**
@@ -1678,7 +1654,7 @@ class Puppeteer extends Helper {
1678
1654
  */
1679
1655
  async dontSeeCookie(name) {
1680
1656
  const cookies = await this.page.cookies()
1681
- 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))
1682
1658
  }
1683
1659
 
1684
1660
  /**
@@ -1689,7 +1665,7 @@ class Puppeteer extends Helper {
1689
1665
  async grabCookie(name) {
1690
1666
  const cookies = await this.page.cookies()
1691
1667
  if (!name) return cookies
1692
- const cookie = cookies.filter((c) => c.name === name)
1668
+ const cookie = cookies.filter(c => c.name === name)
1693
1669
  if (cookie[0]) return cookie[0]
1694
1670
  }
1695
1671
 
@@ -1709,9 +1685,9 @@ class Puppeteer extends Helper {
1709
1685
 
1710
1686
  return promiseRetry(
1711
1687
  async (retry, number) => {
1712
- const _grabCookie = async (name) => {
1688
+ const _grabCookie = async name => {
1713
1689
  const cookies = await this.page.cookies()
1714
- const cookie = cookies.filter((c) => c.name === name)
1690
+ const cookie = cookies.filter(c => c.name === name)
1715
1691
  if (cookie.length === 0) throw Error(`Cookie ${name} is not found after ${retries}s`)
1716
1692
  }
1717
1693
 
@@ -1736,7 +1712,7 @@ class Puppeteer extends Helper {
1736
1712
  if (!name) {
1737
1713
  return this.page.deleteCookie.apply(this.page, cookies)
1738
1714
  }
1739
- const cookie = cookies.filter((c) => c.name === name)
1715
+ const cookie = cookies.filter(c => c.name === name)
1740
1716
  if (!cookie[0]) return
1741
1717
  return this.page.deleteCookie(cookie[0])
1742
1718
  }
@@ -1761,8 +1737,8 @@ class Puppeteer extends Helper {
1761
1737
  async executeAsyncScript(...args) {
1762
1738
  const asyncFn = function () {
1763
1739
  const args = Array.from(arguments)
1764
- const fn = eval(`(${args.shift()})`) // eslint-disable-line no-eval
1765
- return new Promise((done) => {
1740
+ const fn = eval(`(${args.shift()})`)
1741
+ return new Promise(done => {
1766
1742
  args.push(done)
1767
1743
  fn.apply(null, args)
1768
1744
  })
@@ -1829,7 +1805,7 @@ class Puppeteer extends Helper {
1829
1805
  */
1830
1806
  async grabHTMLFromAll(locator) {
1831
1807
  const els = await this._locate(locator)
1832
- 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)))
1833
1809
  return values
1834
1810
  }
1835
1811
 
@@ -1852,10 +1828,8 @@ class Puppeteer extends Helper {
1852
1828
  */
1853
1829
  async grabCssPropertyFromAll(locator, cssProperty) {
1854
1830
  const els = await this._locate(locator)
1855
- const res = await Promise.all(
1856
- els.map((el) => el.evaluate((el) => JSON.parse(JSON.stringify(getComputedStyle(el))), el)),
1857
- )
1858
- 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)])
1859
1833
 
1860
1834
  return cssValues
1861
1835
  }
@@ -1898,19 +1872,16 @@ class Puppeteer extends Helper {
1898
1872
  }
1899
1873
  }
1900
1874
 
1901
- const values = Object.keys(cssPropertiesCamelCase).map((key) => cssPropertiesCamelCase[key])
1875
+ const values = Object.keys(cssPropertiesCamelCase).map(key => cssPropertiesCamelCase[key])
1902
1876
  if (!Array.isArray(props)) props = [props]
1903
1877
  let chunked = chunkArray(props, values.length)
1904
- chunked = chunked.filter((val) => {
1878
+ chunked = chunked.filter(val => {
1905
1879
  for (let i = 0; i < val.length; ++i) {
1906
- // eslint-disable-next-line eqeqeq
1907
1880
  if (val[i] != values[i]) return false
1908
1881
  }
1909
1882
  return true
1910
1883
  })
1911
- return equals(
1912
- `all elements (${new Locator(locator)}) to have CSS property ${JSON.stringify(cssProperties)}`,
1913
- ).assert(chunked.length, elemAmount)
1884
+ return equals(`all elements (${new Locator(locator)}) to have CSS property ${JSON.stringify(cssProperties)}`).assert(chunked.length, elemAmount)
1914
1885
  }
1915
1886
 
1916
1887
  /**
@@ -1923,7 +1894,7 @@ class Puppeteer extends Helper {
1923
1894
 
1924
1895
  const expectedAttributes = Object.entries(attributes)
1925
1896
 
1926
- const valuesPromises = elements.map(async (element) => {
1897
+ const valuesPromises = elements.map(async element => {
1927
1898
  const elementAttributes = {}
1928
1899
  await Promise.all(
1929
1900
  expectedAttributes.map(async ([attribute, expectedValue]) => {
@@ -1936,7 +1907,7 @@ class Puppeteer extends Helper {
1936
1907
 
1937
1908
  const actualAttributes = await Promise.all(valuesPromises)
1938
1909
 
1939
- const matchingElements = actualAttributes.filter((attrs) =>
1910
+ const matchingElements = actualAttributes.filter(attrs =>
1940
1911
  expectedAttributes.every(([attribute, expectedValue]) => {
1941
1912
  const actualValue = attrs[attribute]
1942
1913
  if (!actualValue) return false
@@ -1948,10 +1919,7 @@ class Puppeteer extends Helper {
1948
1919
  const elementsCount = elements.length
1949
1920
  const matchingCount = matchingElements.length
1950
1921
 
1951
- return equals(`all elements (${new Locator(locator)}) to have attributes ${JSON.stringify(attributes)}`).assert(
1952
- matchingCount,
1953
- elementsCount,
1954
- )
1922
+ return equals(`all elements (${new Locator(locator)}) to have attributes ${JSON.stringify(attributes)}`).assert(matchingCount, elementsCount)
1955
1923
  }
1956
1924
 
1957
1925
  /**
@@ -2081,7 +2049,7 @@ class Puppeteer extends Helper {
2081
2049
  * {{> wait }}
2082
2050
  */
2083
2051
  async wait(sec) {
2084
- return new Promise((done) => {
2052
+ return new Promise(done => {
2085
2053
  setTimeout(done, sec * 1000)
2086
2054
  })
2087
2055
  }
@@ -2101,20 +2069,18 @@ class Puppeteer extends Helper {
2101
2069
  if (!els || els.length === 0) {
2102
2070
  return false
2103
2071
  }
2104
- return Array.prototype.filter.call(els, (el) => !el.disabled).length > 0
2072
+ return Array.prototype.filter.call(els, el => !el.disabled).length > 0
2105
2073
  }
2106
2074
  waiter = context.waitForFunction(enabledFn, { timeout: waitTimeout }, locator.value)
2107
2075
  } else {
2108
2076
  const enabledFn = function (locator, $XPath) {
2109
- eval($XPath) // eslint-disable-line no-eval
2110
- return $XPath(null, locator).filter((el) => !el.disabled).length > 0
2077
+ eval($XPath)
2078
+ return $XPath(null, locator).filter(el => !el.disabled).length > 0
2111
2079
  }
2112
2080
  waiter = context.waitForFunction(enabledFn, { timeout: waitTimeout }, locator.value, $XPath.toString())
2113
2081
  }
2114
- return waiter.catch((err) => {
2115
- throw new Error(
2116
- `element (${locator.toString()}) still not enabled after ${waitTimeout / 1000} sec\n${err.message}`,
2117
- )
2082
+ return waiter.catch(err => {
2083
+ throw new Error(`element (${locator.toString()}) still not enabled after ${waitTimeout / 1000} sec\n${err.message}`)
2118
2084
  })
2119
2085
  }
2120
2086
 
@@ -2133,21 +2099,19 @@ class Puppeteer extends Helper {
2133
2099
  if (!els || els.length === 0) {
2134
2100
  return false
2135
2101
  }
2136
- 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
2137
2103
  }
2138
2104
  waiter = context.waitForFunction(valueFn, { timeout: waitTimeout }, locator.value, value)
2139
2105
  } else {
2140
2106
  const valueFn = function (locator, $XPath, value) {
2141
- eval($XPath) // eslint-disable-line no-eval
2142
- 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
2143
2109
  }
2144
2110
  waiter = context.waitForFunction(valueFn, { timeout: waitTimeout }, locator.value, $XPath.toString(), value)
2145
2111
  }
2146
- return waiter.catch((err) => {
2112
+ return waiter.catch(err => {
2147
2113
  const loc = locator.toString()
2148
- throw new Error(
2149
- `element (${loc}) is not in DOM or there is no element(${loc}) with value "${value}" after ${waitTimeout / 1000} sec\n${err.message}`,
2150
- )
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}`)
2151
2115
  })
2152
2116
  }
2153
2117
 
@@ -2166,20 +2130,18 @@ class Puppeteer extends Helper {
2166
2130
  if (!els || els.length === 0) {
2167
2131
  return false
2168
2132
  }
2169
- 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
2170
2134
  }
2171
2135
  waiter = context.waitForFunction(visibleFn, { timeout: waitTimeout }, locator.value, num)
2172
2136
  } else {
2173
2137
  const visibleFn = function (locator, $XPath, num) {
2174
- eval($XPath) // eslint-disable-line no-eval
2175
- 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
2176
2140
  }
2177
2141
  waiter = context.waitForFunction(visibleFn, { timeout: waitTimeout }, locator.value, $XPath.toString(), num)
2178
2142
  }
2179
- return waiter.catch((err) => {
2180
- throw new Error(
2181
- `The number of elements (${locator.toString()}) is not ${num} after ${waitTimeout / 1000} sec\n${err.message}`,
2182
- )
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}`)
2183
2145
  })
2184
2146
  }
2185
2147
 
@@ -2187,14 +2149,14 @@ class Puppeteer extends Helper {
2187
2149
  * {{> waitForClickable }}
2188
2150
  */
2189
2151
  async waitForClickable(locator, waitTimeout) {
2190
- const els = await this._locate(locator)
2191
- assertElementExists(els, locator)
2152
+ const el = await this._locateElement(locator)
2153
+ if (!el) {
2154
+ throw new ElementNotFound(locator, 'Element to wait for clickable')
2155
+ }
2192
2156
 
2193
- return this.waitForFunction(isElementClickable, [els[0]], waitTimeout).catch(async (e) => {
2157
+ return this.waitForFunction(isElementClickable, [el], waitTimeout).catch(async e => {
2194
2158
  if (/Waiting failed/i.test(e.message) || /failed: timeout/i.test(e.message)) {
2195
- throw new Error(
2196
- `element ${new Locator(locator).toString()} still not clickable after ${waitTimeout || this.options.waitForTimeout / 1000} sec`,
2197
- )
2159
+ throw new Error(`element ${new Locator(locator).toString()} still not clickable after ${waitTimeout || this.options.waitForTimeout / 1000} sec`)
2198
2160
  } else {
2199
2161
  throw e
2200
2162
  }
@@ -2216,10 +2178,8 @@ class Puppeteer extends Helper {
2216
2178
  } else {
2217
2179
  waiter = _waitForElement.call(this, locator, { timeout: waitTimeout })
2218
2180
  }
2219
- return waiter.catch((err) => {
2220
- throw new Error(
2221
- `element (${locator.toString()}) still not present on page after ${waitTimeout / 1000} sec\n${err.message}`,
2222
- )
2181
+ return waiter.catch(err => {
2182
+ throw new Error(`element (${locator.toString()}) still not present on page after ${waitTimeout / 1000} sec\n${err.message}`)
2223
2183
  })
2224
2184
  }
2225
2185
 
@@ -2239,10 +2199,8 @@ class Puppeteer extends Helper {
2239
2199
  } else {
2240
2200
  waiter = _waitForElement.call(this, locator, { timeout: waitTimeout, visible: true })
2241
2201
  }
2242
- return waiter.catch((err) => {
2243
- throw new Error(
2244
- `element (${locator.toString()}) still not visible after ${waitTimeout / 1000} sec\n${err.message}`,
2245
- )
2202
+ return waiter.catch(err => {
2203
+ throw new Error(`element (${locator.toString()}) still not visible after ${waitTimeout / 1000} sec\n${err.message}`)
2246
2204
  })
2247
2205
  }
2248
2206
 
@@ -2260,7 +2218,7 @@ class Puppeteer extends Helper {
2260
2218
  } else {
2261
2219
  waiter = _waitForElement.call(this, locator, { timeout: waitTimeout, hidden: true })
2262
2220
  }
2263
- return waiter.catch((err) => {
2221
+ return waiter.catch(err => {
2264
2222
  throw new Error(`element (${locator.toString()}) still visible after ${waitTimeout / 1000} sec\n${err.message}`)
2265
2223
  })
2266
2224
  }
@@ -2278,10 +2236,8 @@ class Puppeteer extends Helper {
2278
2236
  } else {
2279
2237
  waiter = _waitForElement.call(this, locator, { timeout: waitTimeout, hidden: true })
2280
2238
  }
2281
- return waiter.catch((err) => {
2282
- throw new Error(
2283
- `element (${locator.toString()}) still not hidden after ${waitTimeout / 1000} sec\n${err.message}`,
2284
- )
2239
+ return waiter.catch(err => {
2240
+ throw new Error(`element (${locator.toString()}) still not hidden after ${waitTimeout / 1000} sec\n${err.message}`)
2285
2241
  })
2286
2242
  }
2287
2243
 
@@ -2318,14 +2274,14 @@ class Puppeteer extends Helper {
2318
2274
 
2319
2275
  return this.page
2320
2276
  .waitForFunction(
2321
- (urlPart) => {
2277
+ urlPart => {
2322
2278
  const currUrl = decodeURIComponent(decodeURIComponent(decodeURIComponent(window.location.href)))
2323
2279
  return currUrl.indexOf(urlPart) > -1
2324
2280
  },
2325
2281
  { timeout: waitTimeout },
2326
2282
  urlPart,
2327
2283
  )
2328
- .catch(async (e) => {
2284
+ .catch(async e => {
2329
2285
  const currUrl = await this._getPageUrl() // Required because the waitForFunction can't return data.
2330
2286
  if (/Waiting failed:/i.test(e.message) || /failed: timeout/i.test(e.message)) {
2331
2287
  throw new Error(`expected url to include ${urlPart}, but found ${currUrl}`)
@@ -2348,14 +2304,14 @@ class Puppeteer extends Helper {
2348
2304
 
2349
2305
  return this.page
2350
2306
  .waitForFunction(
2351
- (urlPart) => {
2307
+ urlPart => {
2352
2308
  const currUrl = decodeURIComponent(decodeURIComponent(decodeURIComponent(window.location.href)))
2353
2309
  return currUrl.indexOf(urlPart) > -1
2354
2310
  },
2355
2311
  { timeout: waitTimeout },
2356
2312
  urlPart,
2357
2313
  )
2358
- .catch(async (e) => {
2314
+ .catch(async e => {
2359
2315
  const currUrl = await this._getPageUrl() // Required because the waitForFunction can't return data.
2360
2316
  if (/Waiting failed/i.test(e.message) || /failed: timeout/i.test(e.message)) {
2361
2317
  throw new Error(`expected url to be ${urlPart}, but found ${currUrl}`)
@@ -2392,7 +2348,7 @@ class Puppeteer extends Helper {
2392
2348
  if (locator.isXPath()) {
2393
2349
  waiter = contextObject.waitForFunction(
2394
2350
  (locator, text, $XPath) => {
2395
- eval($XPath) // eslint-disable-line no-eval
2351
+ eval($XPath)
2396
2352
  const el = $XPath(null, locator)
2397
2353
  if (!el.length) return false
2398
2354
  return el[0].innerText.indexOf(text) > -1
@@ -2404,14 +2360,10 @@ class Puppeteer extends Helper {
2404
2360
  )
2405
2361
  }
2406
2362
  } else {
2407
- waiter = contextObject.waitForFunction(
2408
- (text) => document.body && document.body.innerText.indexOf(text) > -1,
2409
- { timeout: waitTimeout },
2410
- text,
2411
- )
2363
+ waiter = contextObject.waitForFunction(text => document.body && document.body.innerText.indexOf(text) > -1, { timeout: waitTimeout }, text)
2412
2364
  }
2413
2365
 
2414
- return waiter.catch((err) => {
2366
+ return waiter.catch(err => {
2415
2367
  throw new Error(`Text "${text}" was not found on page after ${waitTimeout / 1000} sec\n${err.message}`)
2416
2368
  })
2417
2369
  }
@@ -2544,12 +2496,12 @@ class Puppeteer extends Helper {
2544
2496
  waiter = context.waitForFunction(visibleFn, { timeout: waitTimeout }, locator.value)
2545
2497
  } else {
2546
2498
  const visibleFn = function (locator, $XPath) {
2547
- eval($XPath) // eslint-disable-line no-eval
2499
+ eval($XPath)
2548
2500
  return $XPath(null, locator).length === 0
2549
2501
  }
2550
2502
  waiter = context.waitForFunction(visibleFn, { timeout: waitTimeout }, locator.value, $XPath.toString())
2551
2503
  }
2552
- return waiter.catch((err) => {
2504
+ return waiter.catch(err => {
2553
2505
  throw new Error(`element (${locator.toString()}) still on page after ${waitTimeout / 1000} sec\n${err.message}`)
2554
2506
  })
2555
2507
  }
@@ -2590,7 +2542,7 @@ class Puppeteer extends Helper {
2590
2542
  async mockRoute(url, handler) {
2591
2543
  await this.page.setRequestInterception(true)
2592
2544
 
2593
- this.page.on('request', (interceptedRequest) => {
2545
+ this.page.on('request', interceptedRequest => {
2594
2546
  if (interceptedRequest.url().match(url)) {
2595
2547
  // @ts-ignore
2596
2548
  handler(interceptedRequest)
@@ -2615,7 +2567,7 @@ class Puppeteer extends Helper {
2615
2567
  this.page.off('request')
2616
2568
 
2617
2569
  // Resume normal request handling for the given URL
2618
- this.page.on('request', (interceptedRequest) => {
2570
+ this.page.on('request', interceptedRequest => {
2619
2571
  if (interceptedRequest.url().includes(url)) {
2620
2572
  interceptedRequest.continue()
2621
2573
  } else {
@@ -2651,7 +2603,7 @@ class Puppeteer extends Helper {
2651
2603
 
2652
2604
  await this.page.setRequestInterception(true)
2653
2605
 
2654
- this.page.on('request', (request) => {
2606
+ this.page.on('request', request => {
2655
2607
  const information = {
2656
2608
  url: request.url(),
2657
2609
  method: request.method(),
@@ -2712,15 +2664,15 @@ class Puppeteer extends Helper {
2712
2664
  await this.cdpSession.send('Network.enable')
2713
2665
  await this.cdpSession.send('Page.enable')
2714
2666
 
2715
- this.cdpSession.on('Network.webSocketFrameReceived', (payload) => {
2667
+ this.cdpSession.on('Network.webSocketFrameReceived', payload => {
2716
2668
  this._logWebsocketMessages(this._getWebSocketLog('RECEIVED', payload))
2717
2669
  })
2718
2670
 
2719
- this.cdpSession.on('Network.webSocketFrameSent', (payload) => {
2671
+ this.cdpSession.on('Network.webSocketFrameSent', payload => {
2720
2672
  this._logWebsocketMessages(this._getWebSocketLog('SENT', payload))
2721
2673
  })
2722
2674
 
2723
- this.cdpSession.on('Network.webSocketFrameError', (payload) => {
2675
+ this.cdpSession.on('Network.webSocketFrameError', payload => {
2724
2676
  this._logWebsocketMessages(this._getWebSocketLog('ERROR', payload))
2725
2677
  })
2726
2678
  }
@@ -2744,9 +2696,7 @@ class Puppeteer extends Helper {
2744
2696
  grabWebSocketMessages() {
2745
2697
  if (!this.recordingWebSocketMessages) {
2746
2698
  if (!this.recordedWebSocketMessagesAtLeastOnce) {
2747
- throw new Error(
2748
- 'Failure in test automation. You use "I.grabWebSocketMessages", but "I.startRecordingWebSocketMessages" was never called before.',
2749
- )
2699
+ throw new Error('Failure in test automation. You use "I.grabWebSocketMessages", but "I.startRecordingWebSocketMessages" was never called before.')
2750
2700
  }
2751
2701
  }
2752
2702
  return this.webSocketMessages
@@ -2778,9 +2728,18 @@ class Puppeteer extends Helper {
2778
2728
 
2779
2729
  module.exports = Puppeteer
2780
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
+ */
2781
2738
  async function findElements(matcher, locator) {
2782
2739
  if (locator.react) return findReactElements.call(this, locator)
2783
2740
  locator = new Locator(locator, 'css')
2741
+
2742
+ // Use proven legacy approach - Puppeteer Locator API doesn't have .all() method
2784
2743
  if (!locator.isXPath()) return matcher.$$(locator.simplify())
2785
2744
  // puppeteer version < 19.4.0 is no longer supported. This one is backward support.
2786
2745
  if (puppeteer.default?.defaultBrowserRevision) {
@@ -2789,6 +2748,31 @@ async function findElements(matcher, locator) {
2789
2748
  return matcher.$x(locator.value)
2790
2749
  }
2791
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
+
2792
2776
  async function proceedClick(locator, context = null, options = {}) {
2793
2777
  let matcher = await this.context
2794
2778
  if (context) {
@@ -2798,12 +2782,7 @@ async function proceedClick(locator, context = null, options = {}) {
2798
2782
  }
2799
2783
  const els = await findClickable.call(this, matcher, locator)
2800
2784
  if (context) {
2801
- assertElementExists(
2802
- els,
2803
- locator,
2804
- 'Clickable element',
2805
- `was not found inside element ${new Locator(context).toString()}`,
2806
- )
2785
+ assertElementExists(els, locator, 'Clickable element', `was not found inside element ${new Locator(context).toString()}`)
2807
2786
  } else {
2808
2787
  assertElementExists(els, locator, 'Clickable element')
2809
2788
  }
@@ -2855,23 +2834,25 @@ async function proceedSee(assertType, text, context, strict = false) {
2855
2834
  el = await this.context.$('body')
2856
2835
  }
2857
2836
 
2858
- allText = [await el.getProperty('innerText').then((p) => p.jsonValue())]
2837
+ allText = [await el.getProperty('innerText').then(p => p.jsonValue())]
2859
2838
  description = 'web application'
2860
2839
  } else {
2861
2840
  const locator = new Locator(context, 'css')
2862
2841
  description = `element ${locator.toString()}`
2863
2842
  const els = await this._locate(locator)
2864
2843
  assertElementExists(els, locator.toString())
2865
- 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())
2866
2850
  }
2867
2851
 
2868
2852
  if (strict) {
2869
- return allText.map((elText) => equals(description)[assertType](text, elText))
2853
+ return allText.map(elText => equals(description)[assertType](text, elText))
2870
2854
  }
2871
- return stringIncludes(description)[assertType](
2872
- normalizeSpacesInString(text),
2873
- normalizeSpacesInString(allText.join(' | ')),
2874
- )
2855
+ return stringIncludes(description)[assertType](normalizeSpacesInString(text), normalizeSpacesInString(allText.join(' | ')))
2875
2856
  }
2876
2857
 
2877
2858
  async function findCheckable(locator, context) {
@@ -2901,15 +2882,15 @@ async function findCheckable(locator, context) {
2901
2882
  async function proceedIsChecked(assertType, option) {
2902
2883
  let els = await findCheckable.call(this, option)
2903
2884
  assertElementExists(els, option, 'Checkable')
2904
- els = await Promise.all(els.map((el) => el.getProperty('checked')))
2905
- 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()))
2906
2887
  const selected = els.reduce((prev, cur) => prev || cur)
2907
2888
  return truth(`checkable ${option}`, 'to be checked')[assertType](selected)
2908
2889
  }
2909
2890
 
2910
2891
  async function findVisibleFields(locator) {
2911
2892
  const els = await findFields.call(this, locator)
2912
- const visible = await Promise.all(els.map((el) => el.boundingBox()))
2893
+ const visible = await Promise.all(els.map(el => el.boundingBox()))
2913
2894
  return els.filter((el, index) => visible[index])
2914
2895
  }
2915
2896
 
@@ -2937,15 +2918,19 @@ async function findFields(locator) {
2937
2918
  }
2938
2919
 
2939
2920
  async function proceedDragAndDrop(sourceLocator, destinationLocator) {
2940
- const src = await this._locate(sourceLocator)
2941
- assertElementExists(src, sourceLocator, 'Source Element')
2921
+ const src = await this._locateElement(sourceLocator)
2922
+ if (!src) {
2923
+ throw new ElementNotFound(sourceLocator, 'Source Element')
2924
+ }
2942
2925
 
2943
- const dst = await this._locate(destinationLocator)
2944
- assertElementExists(dst, destinationLocator, 'Destination Element')
2926
+ const dst = await this._locateElement(destinationLocator)
2927
+ if (!dst) {
2928
+ throw new ElementNotFound(destinationLocator, 'Destination Element')
2929
+ }
2945
2930
 
2946
- // Note: Using public api .getClickablePoint becaues the .BoundingBox does not take into account iframe offsets
2947
- const dragSource = await getClickablePoint(src[0])
2948
- 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)
2949
2934
 
2950
2935
  // Drag start point
2951
2936
  await this.page.mouse.move(dragSource.x, dragSource.y, { steps: 5 })
@@ -2962,15 +2947,15 @@ async function proceedSeeInField(assertType, field, value) {
2962
2947
  const els = await findVisibleFields.call(this, field)
2963
2948
  assertElementExists(els, field, 'Field')
2964
2949
  const el = els[0]
2965
- const tag = await el.getProperty('tagName').then((el) => el.jsonValue())
2966
- 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())
2967
2952
 
2968
- const proceedMultiple = async (elements) => {
2953
+ const proceedMultiple = async elements => {
2969
2954
  const fields = Array.isArray(elements) ? elements : [elements]
2970
2955
 
2971
2956
  const elementValues = []
2972
2957
  for (const element of fields) {
2973
- elementValues.push(await element.getProperty('value').then((el) => el.jsonValue()))
2958
+ elementValues.push(await element.getProperty('value').then(el => el.jsonValue()))
2974
2959
  }
2975
2960
 
2976
2961
  if (typeof value === 'boolean') {
@@ -2979,7 +2964,7 @@ async function proceedSeeInField(assertType, field, value) {
2979
2964
  if (assertType === 'assert') {
2980
2965
  equals(`select option by ${field}`)[assertType](true, elementValues.length > 0)
2981
2966
  }
2982
- elementValues.forEach((val) => stringIncludes(`fields by ${field}`)[assertType](value, val))
2967
+ elementValues.forEach(val => stringIncludes(`fields by ${field}`)[assertType](value, val))
2983
2968
  }
2984
2969
  }
2985
2970
 
@@ -3007,14 +2992,14 @@ async function proceedSeeInField(assertType, field, value) {
3007
2992
  }
3008
2993
  return proceedMultiple(els[0])
3009
2994
  }
3010
- const fieldVal = await el.getProperty('value').then((el) => el.jsonValue())
2995
+ const fieldVal = await el.getProperty('value').then(el => el.jsonValue())
3011
2996
  return stringIncludes(`fields by ${field}`)[assertType](value, fieldVal)
3012
2997
  }
3013
2998
 
3014
2999
  async function filterFieldsByValue(elements, value, onlySelected) {
3015
3000
  const matches = []
3016
3001
  for (const element of elements) {
3017
- const val = await element.getProperty('value').then((el) => el.jsonValue())
3002
+ const val = await element.getProperty('value').then(el => el.jsonValue())
3018
3003
  let isSelected = true
3019
3004
  if (onlySelected) {
3020
3005
  isSelected = await elementSelected(element)
@@ -3038,12 +3023,12 @@ async function filterFieldsBySelectionState(elements, state) {
3038
3023
  }
3039
3024
 
3040
3025
  async function elementSelected(element) {
3041
- const type = await element.getProperty('type').then((el) => el.jsonValue())
3026
+ const type = await element.getProperty('type').then(el => el.jsonValue())
3042
3027
 
3043
3028
  if (type === 'checkbox' || type === 'radio') {
3044
- return element.getProperty('checked').then((el) => el.jsonValue())
3029
+ return element.getProperty('checked').then(el => el.jsonValue())
3045
3030
  }
3046
- return element.getProperty('selected').then((el) => el.jsonValue())
3031
+ return element.getProperty('selected').then(el => el.jsonValue())
3047
3032
  }
3048
3033
 
3049
3034
  function isFrameLocator(locator) {
@@ -3078,9 +3063,9 @@ async function targetCreatedHandler(page) {
3078
3063
  page
3079
3064
  .$('body')
3080
3065
  .catch(() => null)
3081
- .then((context) => (this.context = context))
3066
+ .then(context => (this.context = context))
3082
3067
  })
3083
- page.on('console', (msg) => {
3068
+ page.on('console', msg => {
3084
3069
  this.debugSection(`Browser:${ucfirst(msg.type())}`, (msg._text || '') + msg.args().join(' '))
3085
3070
  consoleLogStore.add(msg)
3086
3071
  })
@@ -3106,7 +3091,6 @@ async function getClickablePoint(el) {
3106
3091
  // List of key values to key definitions
3107
3092
  // https://github.com/GoogleChrome/puppeteer/blob/v1.20.0/lib/USKeyboardLayout.js
3108
3093
  const keyDefinitionMap = {
3109
- /* eslint-disable quote-props */
3110
3094
  0: 'Digit0',
3111
3095
  1: 'Digit1',
3112
3096
  2: 'Digit2',
@@ -3154,7 +3138,6 @@ const keyDefinitionMap = {
3154
3138
  '\\': 'Backslash',
3155
3139
  ']': 'BracketRight',
3156
3140
  "'": 'Quote',
3157
- /* eslint-enable quote-props */
3158
3141
  }
3159
3142
 
3160
3143
  function getNormalizedKey(key) {
@@ -3189,7 +3172,7 @@ async function findReactElements(locator, props = {}, state = {}) {
3189
3172
 
3190
3173
  await this.page.evaluate(() => window.resq.waitToLoadReact())
3191
3174
  const arrayHandle = await this.page.evaluateHandle(
3192
- (obj) => {
3175
+ obj => {
3193
3176
  const { selector, props, state } = obj
3194
3177
  let elements = window.resq.resq$$(selector)
3195
3178
  if (Object.keys(props).length) {
@@ -3208,7 +3191,7 @@ async function findReactElements(locator, props = {}, state = {}) {
3208
3191
  // [[div, div], [div, div]] => [div, div, div, div]
3209
3192
  let nodes = []
3210
3193
 
3211
- elements.forEach((element) => {
3194
+ elements.forEach(element => {
3212
3195
  let { node, isFragment } = element
3213
3196
 
3214
3197
  if (!node) {