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.
- package/README.md +134 -119
- package/bin/codecept.js +12 -2
- package/bin/test-server.js +53 -0
- package/docs/webapi/clearCookie.mustache +1 -1
- package/lib/actor.js +66 -102
- package/lib/ai.js +130 -121
- package/lib/assert/empty.js +3 -5
- package/lib/assert/equal.js +4 -7
- package/lib/assert/include.js +4 -6
- package/lib/assert/throws.js +2 -4
- package/lib/assert/truth.js +2 -2
- package/lib/codecept.js +141 -86
- package/lib/command/check.js +201 -0
- package/lib/command/configMigrate.js +2 -4
- package/lib/command/definitions.js +8 -26
- package/lib/command/dryRun.js +30 -35
- package/lib/command/generate.js +10 -14
- package/lib/command/gherkin/snippets.js +75 -73
- package/lib/command/gherkin/steps.js +1 -1
- package/lib/command/info.js +42 -8
- package/lib/command/init.js +13 -12
- package/lib/command/interactive.js +10 -2
- package/lib/command/list.js +1 -1
- package/lib/command/run-multiple/chunk.js +48 -45
- package/lib/command/run-multiple.js +12 -35
- package/lib/command/run-workers.js +21 -58
- package/lib/command/utils.js +5 -6
- package/lib/command/workers/runTests.js +263 -222
- package/lib/container.js +386 -238
- package/lib/data/context.js +10 -13
- package/lib/data/dataScenarioConfig.js +8 -8
- package/lib/data/dataTableArgument.js +6 -6
- package/lib/data/table.js +5 -11
- package/lib/effects.js +223 -0
- package/lib/element/WebElement.js +327 -0
- package/lib/els.js +158 -0
- package/lib/event.js +21 -17
- package/lib/heal.js +88 -80
- package/lib/helper/AI.js +2 -1
- package/lib/helper/ApiDataFactory.js +4 -7
- package/lib/helper/Appium.js +50 -57
- package/lib/helper/FileSystem.js +3 -3
- package/lib/helper/GraphQLDataFactory.js +4 -4
- package/lib/helper/JSONResponse.js +75 -37
- package/lib/helper/Mochawesome.js +31 -9
- package/lib/helper/Nightmare.js +37 -58
- package/lib/helper/Playwright.js +267 -272
- package/lib/helper/Protractor.js +56 -87
- package/lib/helper/Puppeteer.js +247 -264
- package/lib/helper/REST.js +29 -17
- package/lib/helper/TestCafe.js +22 -47
- package/lib/helper/WebDriver.js +157 -368
- package/lib/helper/extras/PlaywrightPropEngine.js +2 -2
- package/lib/helper/extras/Popup.js +22 -22
- package/lib/helper/network/utils.js +1 -1
- package/lib/helper/testcafe/testcafe-utils.js +27 -28
- package/lib/listener/emptyRun.js +55 -0
- package/lib/listener/exit.js +7 -10
- package/lib/listener/{retry.js → globalRetry.js} +5 -5
- package/lib/listener/globalTimeout.js +165 -0
- package/lib/listener/helpers.js +15 -15
- package/lib/listener/mocha.js +1 -1
- package/lib/listener/result.js +12 -0
- package/lib/listener/retryEnhancer.js +85 -0
- package/lib/listener/steps.js +32 -18
- package/lib/listener/store.js +20 -0
- package/lib/locator.js +1 -1
- package/lib/mocha/asyncWrapper.js +231 -0
- package/lib/{interfaces → mocha}/bdd.js +3 -3
- package/lib/mocha/cli.js +308 -0
- package/lib/mocha/factory.js +104 -0
- package/lib/{interfaces → mocha}/featureConfig.js +32 -12
- package/lib/{interfaces → mocha}/gherkin.js +26 -28
- package/lib/mocha/hooks.js +112 -0
- package/lib/mocha/index.js +12 -0
- package/lib/mocha/inject.js +29 -0
- package/lib/{interfaces → mocha}/scenarioConfig.js +31 -7
- package/lib/mocha/suite.js +82 -0
- package/lib/mocha/test.js +181 -0
- package/lib/mocha/types.d.ts +42 -0
- package/lib/mocha/ui.js +232 -0
- package/lib/output.js +93 -65
- package/lib/pause.js +160 -138
- package/lib/plugin/analyze.js +396 -0
- package/lib/plugin/auth.js +435 -0
- package/lib/plugin/autoDelay.js +8 -8
- package/lib/plugin/autoLogin.js +3 -338
- package/lib/plugin/commentStep.js +6 -1
- package/lib/plugin/coverage.js +10 -22
- package/lib/plugin/customLocator.js +3 -3
- package/lib/plugin/customReporter.js +52 -0
- package/lib/plugin/eachElement.js +1 -1
- package/lib/plugin/fakerTransform.js +1 -1
- package/lib/plugin/heal.js +36 -9
- package/lib/plugin/htmlReporter.js +1947 -0
- package/lib/plugin/pageInfo.js +140 -0
- package/lib/plugin/retryFailedStep.js +17 -18
- package/lib/plugin/retryTo.js +2 -113
- package/lib/plugin/screenshotOnFail.js +17 -58
- package/lib/plugin/selenoid.js +15 -35
- package/lib/plugin/standardActingHelpers.js +4 -1
- package/lib/plugin/stepByStepReport.js +56 -17
- package/lib/plugin/stepTimeout.js +5 -12
- package/lib/plugin/subtitles.js +4 -4
- package/lib/plugin/tryTo.js +3 -102
- package/lib/plugin/wdio.js +8 -10
- package/lib/recorder.js +155 -124
- package/lib/rerun.js +43 -42
- package/lib/result.js +161 -0
- package/lib/secret.js +1 -2
- package/lib/step/base.js +239 -0
- package/lib/step/comment.js +10 -0
- package/lib/step/config.js +50 -0
- package/lib/step/func.js +46 -0
- package/lib/step/helper.js +50 -0
- package/lib/step/meta.js +99 -0
- package/lib/step/record.js +74 -0
- package/lib/step/retry.js +11 -0
- package/lib/step/section.js +55 -0
- package/lib/step.js +21 -332
- package/lib/steps.js +50 -0
- package/lib/store.js +37 -5
- package/lib/template/heal.js +2 -11
- package/lib/test-server.js +323 -0
- package/lib/timeout.js +66 -0
- package/lib/utils.js +351 -218
- package/lib/within.js +75 -55
- package/lib/workerStorage.js +2 -1
- package/lib/workers.js +386 -277
- package/package.json +81 -75
- package/translations/de-DE.js +5 -3
- package/translations/fr-FR.js +5 -4
- package/translations/index.js +1 -0
- package/translations/it-IT.js +4 -3
- package/translations/ja-JP.js +4 -3
- package/translations/nl-NL.js +76 -0
- package/translations/pl-PL.js +4 -3
- package/translations/pt-BR.js +4 -3
- package/translations/ru-RU.js +4 -3
- package/translations/utils.js +9 -0
- package/translations/zh-CN.js +4 -3
- package/translations/zh-TW.js +4 -3
- package/typings/index.d.ts +197 -187
- package/typings/promiseBasedTypes.d.ts +53 -903
- package/typings/types.d.ts +372 -1042
- package/lib/cli.js +0 -257
- package/lib/helper/ExpectHelper.js +0 -391
- package/lib/helper/MockServer.js +0 -221
- package/lib/helper/SoftExpectHelper.js +0 -381
- package/lib/listener/artifacts.js +0 -19
- package/lib/listener/timeout.js +0 -109
- package/lib/mochaFactory.js +0 -113
- package/lib/plugin/debugErrors.js +0 -67
- package/lib/scenario.js +0 -224
- package/lib/ui.js +0 -236
package/lib/helper/Puppeteer.js
CHANGED
|
@@ -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
|
-
|
|
41
|
-
|
|
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:
|
|
329
|
-
when:
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
404
|
-
const existingPages = context.targets().filter(
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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',
|
|
579
|
+
this.browser.on('targetcreated', target =>
|
|
592
580
|
target
|
|
593
581
|
.page()
|
|
594
|
-
.then(
|
|
595
|
-
.catch(
|
|
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',
|
|
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
|
|
652
|
-
|
|
653
|
-
|
|
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(
|
|
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
|
|
754
|
-
|
|
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(
|
|
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
|
|
768
|
-
|
|
769
|
-
|
|
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
|
|
782
|
-
|
|
766
|
+
const el = await this._locateElement(locator)
|
|
767
|
+
if (!el) {
|
|
768
|
+
throw new ElementNotFound(locator, 'Element to blur')
|
|
769
|
+
}
|
|
783
770
|
|
|
784
|
-
await blurElement(
|
|
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
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
await
|
|
842
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
1039
|
+
const otherPages = pages.filter(page => page !== this.page)
|
|
1030
1040
|
|
|
1031
1041
|
let p = Promise.resolve()
|
|
1032
|
-
otherPages.forEach(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
1225
|
-
this.page.on('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(
|
|
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(
|
|
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(
|
|
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(
|
|
1413
|
-
const editable = await el.getProperty('contenteditable').then(
|
|
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(
|
|
1404
|
+
await this._evaluateHandeInContext(el => (el.value = ''), el)
|
|
1416
1405
|
} else if (editable) {
|
|
1417
|
-
await this._evaluateHandeInContext(
|
|
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(
|
|
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(
|
|
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(
|
|
1491
|
+
this._evaluateHandeInContext(el => (el.selected = true), optEl[0])
|
|
1503
1492
|
}
|
|
1504
1493
|
}
|
|
1505
|
-
await this._evaluateHandeInContext(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
1688
|
+
const _grabCookie = async name => {
|
|
1713
1689
|
const cookies = await this.page.cookies()
|
|
1714
|
-
const cookie = cookies.filter(
|
|
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(
|
|
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()})`)
|
|
1765
|
-
return new Promise(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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,
|
|
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)
|
|
2110
|
-
return $XPath(null, locator).filter(
|
|
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(
|
|
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,
|
|
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)
|
|
2142
|
-
return $XPath(null, locator).filter(
|
|
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(
|
|
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,
|
|
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)
|
|
2175
|
-
return $XPath(null, locator).filter(
|
|
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(
|
|
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
|
|
2191
|
-
|
|
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, [
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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)
|
|
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(
|
|
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)
|
|
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(
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
2905
|
-
els = await Promise.all(els.map(
|
|
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(
|
|
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.
|
|
2941
|
-
|
|
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.
|
|
2944
|
-
|
|
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
|
|
2947
|
-
const dragSource = await getClickablePoint(src
|
|
2948
|
-
const dragDestination = await getClickablePoint(dst
|
|
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(
|
|
2966
|
-
const fieldType = await el.getProperty('type').then(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
3029
|
+
return element.getProperty('checked').then(el => el.jsonValue())
|
|
3045
3030
|
}
|
|
3046
|
-
return element.getProperty('selected').then(
|
|
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(
|
|
3066
|
+
.then(context => (this.context = context))
|
|
3082
3067
|
})
|
|
3083
|
-
page.on('console',
|
|
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
|
-
|
|
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(
|
|
3194
|
+
elements.forEach(element => {
|
|
3212
3195
|
let { node, isFragment } = element
|
|
3213
3196
|
|
|
3214
3197
|
if (!node) {
|