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/Playwright.js
CHANGED
|
@@ -7,6 +7,7 @@ const assert = require('assert')
|
|
|
7
7
|
const promiseRetry = require('promise-retry')
|
|
8
8
|
const Locator = require('../locator')
|
|
9
9
|
const recorder = require('../recorder')
|
|
10
|
+
const store = require('../store')
|
|
10
11
|
const stringIncludes = require('../assert/include').includes
|
|
11
12
|
const { urlEquals } = require('../assert/equal')
|
|
12
13
|
const { equals } = require('../assert/equal')
|
|
@@ -24,6 +25,7 @@ const {
|
|
|
24
25
|
clearString,
|
|
25
26
|
requireWithFallback,
|
|
26
27
|
normalizeSpacesInString,
|
|
28
|
+
relativeDir,
|
|
27
29
|
} = require('../utils')
|
|
28
30
|
const { isColorProperty, convertColorToRGBA } = require('../colorUtils')
|
|
29
31
|
const ElementNotFound = require('./errors/ElementNotFound')
|
|
@@ -31,6 +33,7 @@ const RemoteBrowserConnectionRefused = require('./errors/RemoteBrowserConnection
|
|
|
31
33
|
const Popup = require('./extras/Popup')
|
|
32
34
|
const Console = require('./extras/Console')
|
|
33
35
|
const { findReact, findVue, findByPlaywrightLocator } = require('./extras/PlaywrightReactVueLocator')
|
|
36
|
+
const WebElement = require('../element/WebElement')
|
|
34
37
|
|
|
35
38
|
let playwright
|
|
36
39
|
let perfTiming
|
|
@@ -40,26 +43,10 @@ const popupStore = new Popup()
|
|
|
40
43
|
const consoleLogStore = new Console()
|
|
41
44
|
const availableBrowsers = ['chromium', 'webkit', 'firefox', 'electron']
|
|
42
45
|
|
|
43
|
-
const {
|
|
44
|
-
setRestartStrategy,
|
|
45
|
-
restartsSession,
|
|
46
|
-
restartsContext,
|
|
47
|
-
restartsBrowser,
|
|
48
|
-
} = require('./extras/PlaywrightRestartOpts')
|
|
46
|
+
const { setRestartStrategy, restartsSession, restartsContext, restartsBrowser } = require('./extras/PlaywrightRestartOpts')
|
|
49
47
|
const { createValueEngine, createDisabledEngine } = require('./extras/PlaywrightPropEngine')
|
|
50
|
-
const {
|
|
51
|
-
|
|
52
|
-
dontSeeElementError,
|
|
53
|
-
dontSeeElementInDOMError,
|
|
54
|
-
seeElementInDOMError,
|
|
55
|
-
} = require('./errors/ElementAssertion')
|
|
56
|
-
const {
|
|
57
|
-
dontSeeTraffic,
|
|
58
|
-
seeTraffic,
|
|
59
|
-
grabRecordedNetworkTraffics,
|
|
60
|
-
stopRecordingTraffic,
|
|
61
|
-
flushNetworkTraffics,
|
|
62
|
-
} = require('./network/actions')
|
|
48
|
+
const { seeElementError, dontSeeElementError, dontSeeElementInDOMError, seeElementInDOMError } = require('./errors/ElementAssertion')
|
|
49
|
+
const { dontSeeTraffic, seeTraffic, grabRecordedNetworkTraffics, stopRecordingTraffic, flushNetworkTraffics } = require('./network/actions')
|
|
63
50
|
|
|
64
51
|
const pathSeparator = path.sep
|
|
65
52
|
|
|
@@ -392,9 +379,7 @@ class Playwright extends Helper {
|
|
|
392
379
|
config = Object.assign(defaults, config)
|
|
393
380
|
|
|
394
381
|
if (availableBrowsers.indexOf(config.browser) < 0) {
|
|
395
|
-
throw new Error(
|
|
396
|
-
`Invalid config. Can't use browser "${config.browser}". Accepted values: ${availableBrowsers.join(', ')}`,
|
|
397
|
-
)
|
|
382
|
+
throw new Error(`Invalid config. Can't use browser "${config.browser}". Accepted values: ${availableBrowsers.join(', ')}`)
|
|
398
383
|
}
|
|
399
384
|
|
|
400
385
|
return config
|
|
@@ -440,9 +425,7 @@ class Playwright extends Helper {
|
|
|
440
425
|
}
|
|
441
426
|
this.isRemoteBrowser = !!this.playwrightOptions.browserWSEndpoint
|
|
442
427
|
this.isElectron = this.options.browser === 'electron'
|
|
443
|
-
this.userDataDir = this.playwrightOptions.userDataDir
|
|
444
|
-
? `${this.playwrightOptions.userDataDir}_${Date.now().toString()}`
|
|
445
|
-
: undefined
|
|
428
|
+
this.userDataDir = this.playwrightOptions.userDataDir ? `${this.playwrightOptions.userDataDir}_${Date.now().toString()}` : undefined
|
|
446
429
|
this.isCDPConnection = this.playwrightOptions.cdpConnection
|
|
447
430
|
popupStore.defaultAction = this.options.defaultPopupAction
|
|
448
431
|
}
|
|
@@ -458,14 +441,14 @@ class Playwright extends Helper {
|
|
|
458
441
|
name: 'url',
|
|
459
442
|
message: 'Base url of site to be tested',
|
|
460
443
|
default: 'http://localhost',
|
|
461
|
-
when:
|
|
444
|
+
when: answers => answers.Playwright_browser !== 'electron',
|
|
462
445
|
},
|
|
463
446
|
{
|
|
464
447
|
name: 'show',
|
|
465
448
|
message: 'Show browser window',
|
|
466
449
|
default: true,
|
|
467
450
|
type: 'confirm',
|
|
468
|
-
when:
|
|
451
|
+
when: answers => answers.Playwright_browser !== 'electron',
|
|
469
452
|
},
|
|
470
453
|
]
|
|
471
454
|
}
|
|
@@ -500,9 +483,10 @@ class Playwright extends Helper {
|
|
|
500
483
|
|
|
501
484
|
async _before(test) {
|
|
502
485
|
this.currentRunningTest = test
|
|
486
|
+
|
|
503
487
|
recorder.retry({
|
|
504
|
-
retries:
|
|
505
|
-
when:
|
|
488
|
+
retries: test?.opts?.conditionalRetries || 3,
|
|
489
|
+
when: err => {
|
|
506
490
|
if (!err || typeof err.message !== 'string') {
|
|
507
491
|
return false
|
|
508
492
|
}
|
|
@@ -540,12 +524,17 @@ class Playwright extends Helper {
|
|
|
540
524
|
this.currentRunningTest.artifacts.har = fileName
|
|
541
525
|
contextOptions.recordHar = this.options.recordHar
|
|
542
526
|
}
|
|
527
|
+
|
|
528
|
+
// load pre-saved cookies
|
|
529
|
+
if (test?.opts?.cookies) contextOptions.storageState = { cookies: test.opts.cookies }
|
|
530
|
+
|
|
543
531
|
if (this.storageState) contextOptions.storageState = this.storageState
|
|
544
532
|
if (this.options.userAgent) contextOptions.userAgent = this.options.userAgent
|
|
545
533
|
if (this.options.locale) contextOptions.locale = this.options.locale
|
|
546
534
|
if (this.options.colorScheme) contextOptions.colorScheme = this.options.colorScheme
|
|
547
535
|
this.contextOptions = contextOptions
|
|
548
536
|
if (!this.browserContext || !restartsSession()) {
|
|
537
|
+
this.debugSection('New Session', JSON.stringify(this.contextOptions))
|
|
549
538
|
this.browserContext = await this.browser.newContext(this.contextOptions) // Adding the HTTPSError ignore in the context so that we can ignore those errors
|
|
550
539
|
}
|
|
551
540
|
}
|
|
@@ -559,10 +548,7 @@ class Playwright extends Helper {
|
|
|
559
548
|
mainPage = existingPages[0] || (await this.browserContext.newPage())
|
|
560
549
|
} catch (e) {
|
|
561
550
|
if (this.playwrightOptions.userDataDir) {
|
|
562
|
-
this.browser = await playwright[this.options.browser].launchPersistentContext(
|
|
563
|
-
this.userDataDir,
|
|
564
|
-
this.playwrightOptions,
|
|
565
|
-
)
|
|
551
|
+
this.browser = await playwright[this.options.browser].launchPersistentContext(this.userDataDir, this.playwrightOptions)
|
|
566
552
|
this.browserContext = this.browser
|
|
567
553
|
const existingPages = await this.browserContext.pages()
|
|
568
554
|
mainPage = existingPages[0]
|
|
@@ -573,6 +559,15 @@ class Playwright extends Helper {
|
|
|
573
559
|
|
|
574
560
|
await this._setPage(mainPage)
|
|
575
561
|
|
|
562
|
+
try {
|
|
563
|
+
// set metadata for reporting
|
|
564
|
+
test.meta.browser = this.browser.browserType().name()
|
|
565
|
+
test.meta.browserVersion = this.browser.version()
|
|
566
|
+
test.meta.windowSize = `${this.page.viewportSize().width}x${this.page.viewportSize().height}`
|
|
567
|
+
} catch (e) {
|
|
568
|
+
this.debug('Failed to set metadata for reporting')
|
|
569
|
+
}
|
|
570
|
+
|
|
576
571
|
if (this.options.trace) await this.browserContext.tracing.start({ screenshots: true, snapshots: true })
|
|
577
572
|
|
|
578
573
|
return this.browser
|
|
@@ -583,7 +578,7 @@ class Playwright extends Helper {
|
|
|
583
578
|
|
|
584
579
|
if (this.isElectron) {
|
|
585
580
|
this.browser.close()
|
|
586
|
-
this.electronSessions.forEach(
|
|
581
|
+
this.electronSessions.forEach(session => session.close())
|
|
587
582
|
return
|
|
588
583
|
}
|
|
589
584
|
|
|
@@ -605,7 +600,7 @@ class Playwright extends Helper {
|
|
|
605
600
|
this.storageState = await currentContext.storageState()
|
|
606
601
|
}
|
|
607
602
|
|
|
608
|
-
await Promise.all(contexts.map(
|
|
603
|
+
await Promise.all(contexts.map(c => c.close()))
|
|
609
604
|
}
|
|
610
605
|
} catch (e) {
|
|
611
606
|
console.log(e)
|
|
@@ -641,10 +636,7 @@ class Playwright extends Helper {
|
|
|
641
636
|
page = await browserContext.newPage()
|
|
642
637
|
} catch (e) {
|
|
643
638
|
if (this.playwrightOptions.userDataDir) {
|
|
644
|
-
browserContext = await playwright[this.options.browser].launchPersistentContext(
|
|
645
|
-
`${this.userDataDir}_${this.activeSessionName}`,
|
|
646
|
-
this.playwrightOptions,
|
|
647
|
-
)
|
|
639
|
+
browserContext = await playwright[this.options.browser].launchPersistentContext(`${this.userDataDir}_${this.activeSessionName}`, this.playwrightOptions)
|
|
648
640
|
this.browser = browserContext
|
|
649
641
|
page = await browserContext.pages()[0]
|
|
650
642
|
}
|
|
@@ -660,7 +652,7 @@ class Playwright extends Helper {
|
|
|
660
652
|
stop: async () => {
|
|
661
653
|
// is closed by _after
|
|
662
654
|
},
|
|
663
|
-
loadVars: async
|
|
655
|
+
loadVars: async context => {
|
|
664
656
|
if (context) {
|
|
665
657
|
this.browserContext = context
|
|
666
658
|
const existingPages = await context.pages()
|
|
@@ -668,7 +660,7 @@ class Playwright extends Helper {
|
|
|
668
660
|
return this._setPage(this.sessionPages[this.activeSessionName])
|
|
669
661
|
}
|
|
670
662
|
},
|
|
671
|
-
restoreVars: async
|
|
663
|
+
restoreVars: async session => {
|
|
672
664
|
this.withinLocator = null
|
|
673
665
|
this.browserContext = defaultContext
|
|
674
666
|
|
|
@@ -793,7 +785,7 @@ class Playwright extends Helper {
|
|
|
793
785
|
return
|
|
794
786
|
}
|
|
795
787
|
page.removeAllListeners('dialog')
|
|
796
|
-
page.on('dialog', async
|
|
788
|
+
page.on('dialog', async dialog => {
|
|
797
789
|
popupStore.popup = dialog
|
|
798
790
|
const action = popupStore.actionType || this.options.defaultPopupAction
|
|
799
791
|
await this._waitForAction()
|
|
@@ -856,16 +848,13 @@ class Playwright extends Helper {
|
|
|
856
848
|
throw err
|
|
857
849
|
}
|
|
858
850
|
} else if (this.playwrightOptions.userDataDir) {
|
|
859
|
-
this.browser = await playwright[this.options.browser].launchPersistentContext(
|
|
860
|
-
this.userDataDir,
|
|
861
|
-
this.playwrightOptions,
|
|
862
|
-
)
|
|
851
|
+
this.browser = await playwright[this.options.browser].launchPersistentContext(this.userDataDir, this.playwrightOptions)
|
|
863
852
|
} else {
|
|
864
853
|
this.browser = await playwright[this.options.browser].launch(this.playwrightOptions)
|
|
865
854
|
}
|
|
866
855
|
|
|
867
856
|
// works only for Chromium
|
|
868
|
-
this.browser.on('targetchanged',
|
|
857
|
+
this.browser.on('targetchanged', target => {
|
|
869
858
|
this.debugSection('Url', target.url())
|
|
870
859
|
})
|
|
871
860
|
|
|
@@ -940,7 +929,7 @@ class Playwright extends Helper {
|
|
|
940
929
|
const navigationStart = timing.navigationStart
|
|
941
930
|
|
|
942
931
|
const extractedData = {}
|
|
943
|
-
dataNames.forEach(
|
|
932
|
+
dataNames.forEach(name => {
|
|
944
933
|
extractedData[name] = timing[name] - navigationStart
|
|
945
934
|
})
|
|
946
935
|
|
|
@@ -955,7 +944,8 @@ class Playwright extends Helper {
|
|
|
955
944
|
throw new Error('Cannot open pages inside an Electron container')
|
|
956
945
|
}
|
|
957
946
|
if (!/^\w+\:(\/\/|.+)/.test(url)) {
|
|
958
|
-
url = this.options.url + (url.startsWith('/') ? url : `/${url}`)
|
|
947
|
+
url = this.options.url + (!this.options.url.endsWith('/') && url.startsWith('/') ? url : `/${url}`)
|
|
948
|
+
this.debug(`Changed URL to base url + relative path: ${url}`)
|
|
959
949
|
}
|
|
960
950
|
|
|
961
951
|
if (this.options.basicAuth && this.isAuthenticated !== true) {
|
|
@@ -969,13 +959,7 @@ class Playwright extends Helper {
|
|
|
969
959
|
|
|
970
960
|
const performanceTiming = JSON.parse(await this.page.evaluate(() => JSON.stringify(window.performance.timing)))
|
|
971
961
|
|
|
972
|
-
perfTiming = this._extractDataFromPerformanceTiming(
|
|
973
|
-
performanceTiming,
|
|
974
|
-
'responseEnd',
|
|
975
|
-
'domInteractive',
|
|
976
|
-
'domContentLoadedEventEnd',
|
|
977
|
-
'loadEventEnd',
|
|
978
|
-
)
|
|
962
|
+
perfTiming = this._extractDataFromPerformanceTiming(performanceTiming, 'responseEnd', 'domInteractive', 'domContentLoadedEventEnd', 'loadEventEnd')
|
|
979
963
|
|
|
980
964
|
return this._waitForAction()
|
|
981
965
|
}
|
|
@@ -1197,10 +1181,7 @@ class Playwright extends Helper {
|
|
|
1197
1181
|
return this.executeScript(() => {
|
|
1198
1182
|
const body = document.body
|
|
1199
1183
|
const html = document.documentElement
|
|
1200
|
-
window.scrollTo(
|
|
1201
|
-
0,
|
|
1202
|
-
Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight),
|
|
1203
|
-
)
|
|
1184
|
+
window.scrollTo(0, Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight))
|
|
1204
1185
|
})
|
|
1205
1186
|
}
|
|
1206
1187
|
|
|
@@ -1241,14 +1222,13 @@ class Playwright extends Helper {
|
|
|
1241
1222
|
* {{> grabPageScrollPosition }}
|
|
1242
1223
|
*/
|
|
1243
1224
|
async grabPageScrollPosition() {
|
|
1244
|
-
/* eslint-disable comma-dangle */
|
|
1245
1225
|
function getScrollPosition() {
|
|
1246
1226
|
return {
|
|
1247
1227
|
x: window.pageXOffset,
|
|
1248
1228
|
y: window.pageYOffset,
|
|
1249
1229
|
}
|
|
1250
1230
|
}
|
|
1251
|
-
|
|
1231
|
+
|
|
1252
1232
|
return this.executeScript(getScrollPosition)
|
|
1253
1233
|
}
|
|
1254
1234
|
|
|
@@ -1284,11 +1264,26 @@ class Playwright extends Helper {
|
|
|
1284
1264
|
* ```
|
|
1285
1265
|
*/
|
|
1286
1266
|
async _locate(locator) {
|
|
1287
|
-
const context =
|
|
1267
|
+
const context = await this._getContext()
|
|
1288
1268
|
|
|
1289
1269
|
if (this.frame) return findElements(this.frame, locator)
|
|
1290
1270
|
|
|
1291
|
-
|
|
1271
|
+
const els = await findElements(context, locator)
|
|
1272
|
+
|
|
1273
|
+
if (store.debugMode) {
|
|
1274
|
+
const previewElements = els.slice(0, 3)
|
|
1275
|
+
let htmls = await Promise.all(previewElements.map(el => elToString(el, previewElements.length)))
|
|
1276
|
+
if (els.length > 3) htmls.push('...')
|
|
1277
|
+
if (els.length > 1) {
|
|
1278
|
+
this.debugSection(`Elements (${els.length})`, htmls.join('|').trim())
|
|
1279
|
+
} else if (els.length === 1) {
|
|
1280
|
+
this.debugSection('Element', htmls.join('|').trim())
|
|
1281
|
+
} else {
|
|
1282
|
+
this.debug(`No elements found by ${JSON.stringify(locator).slice(0, 50)}....`)
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
return els
|
|
1292
1287
|
}
|
|
1293
1288
|
|
|
1294
1289
|
/**
|
|
@@ -1300,7 +1295,7 @@ class Playwright extends Helper {
|
|
|
1300
1295
|
* ```
|
|
1301
1296
|
*/
|
|
1302
1297
|
async _locateElement(locator) {
|
|
1303
|
-
const context =
|
|
1298
|
+
const context = await this._getContext()
|
|
1304
1299
|
return findElement(context, locator)
|
|
1305
1300
|
}
|
|
1306
1301
|
|
|
@@ -1347,7 +1342,8 @@ class Playwright extends Helper {
|
|
|
1347
1342
|
*
|
|
1348
1343
|
*/
|
|
1349
1344
|
async grabWebElements(locator) {
|
|
1350
|
-
|
|
1345
|
+
const elements = await this._locate(locator)
|
|
1346
|
+
return elements.map(element => new WebElement(element, this))
|
|
1351
1347
|
}
|
|
1352
1348
|
|
|
1353
1349
|
/**
|
|
@@ -1355,7 +1351,8 @@ class Playwright extends Helper {
|
|
|
1355
1351
|
*
|
|
1356
1352
|
*/
|
|
1357
1353
|
async grabWebElement(locator) {
|
|
1358
|
-
|
|
1354
|
+
const element = await this._locateElement(locator)
|
|
1355
|
+
return new WebElement(element, this)
|
|
1359
1356
|
}
|
|
1360
1357
|
|
|
1361
1358
|
/**
|
|
@@ -1438,10 +1435,10 @@ class Playwright extends Helper {
|
|
|
1438
1435
|
*/
|
|
1439
1436
|
async closeOtherTabs() {
|
|
1440
1437
|
const pages = await this.browserContext.pages()
|
|
1441
|
-
const otherPages = pages.filter(
|
|
1438
|
+
const otherPages = pages.filter(page => page !== this.page)
|
|
1442
1439
|
if (otherPages.length) {
|
|
1443
1440
|
this.debug(`Closing ${otherPages.length} tabs`)
|
|
1444
|
-
return Promise.all(otherPages.map(
|
|
1441
|
+
return Promise.all(otherPages.map(p => p.close()))
|
|
1445
1442
|
}
|
|
1446
1443
|
return Promise.resolve()
|
|
1447
1444
|
}
|
|
@@ -1484,9 +1481,9 @@ class Playwright extends Helper {
|
|
|
1484
1481
|
*/
|
|
1485
1482
|
async seeElement(locator) {
|
|
1486
1483
|
let els = await this._locate(locator)
|
|
1487
|
-
els = await Promise.all(els.map(
|
|
1484
|
+
els = await Promise.all(els.map(el => el.isVisible()))
|
|
1488
1485
|
try {
|
|
1489
|
-
return empty('visible elements').negate(els.filter(
|
|
1486
|
+
return empty('visible elements').negate(els.filter(v => v).fill('ELEMENT'))
|
|
1490
1487
|
} catch (e) {
|
|
1491
1488
|
dontSeeElementError(locator)
|
|
1492
1489
|
}
|
|
@@ -1498,9 +1495,9 @@ class Playwright extends Helper {
|
|
|
1498
1495
|
*/
|
|
1499
1496
|
async dontSeeElement(locator) {
|
|
1500
1497
|
let els = await this._locate(locator)
|
|
1501
|
-
els = await Promise.all(els.map(
|
|
1498
|
+
els = await Promise.all(els.map(el => el.isVisible()))
|
|
1502
1499
|
try {
|
|
1503
|
-
return empty('visible elements').assert(els.filter(
|
|
1500
|
+
return empty('visible elements').assert(els.filter(v => v).fill('ELEMENT'))
|
|
1504
1501
|
} catch (e) {
|
|
1505
1502
|
seeElementError(locator)
|
|
1506
1503
|
}
|
|
@@ -1512,7 +1509,7 @@ class Playwright extends Helper {
|
|
|
1512
1509
|
async seeElementInDOM(locator) {
|
|
1513
1510
|
const els = await this._locate(locator)
|
|
1514
1511
|
try {
|
|
1515
|
-
return empty('elements on page').negate(els.filter(
|
|
1512
|
+
return empty('elements on page').negate(els.filter(v => v).fill('ELEMENT'))
|
|
1516
1513
|
} catch (e) {
|
|
1517
1514
|
dontSeeElementInDOMError(locator)
|
|
1518
1515
|
}
|
|
@@ -1524,7 +1521,7 @@ class Playwright extends Helper {
|
|
|
1524
1521
|
async dontSeeElementInDOM(locator) {
|
|
1525
1522
|
const els = await this._locate(locator)
|
|
1526
1523
|
try {
|
|
1527
|
-
return empty('elements on a page').assert(els.filter(
|
|
1524
|
+
return empty('elements on a page').assert(els.filter(v => v).fill('ELEMENT'))
|
|
1528
1525
|
} catch (e) {
|
|
1529
1526
|
seeElementInDOMError(locator)
|
|
1530
1527
|
}
|
|
@@ -1548,7 +1545,7 @@ class Playwright extends Helper {
|
|
|
1548
1545
|
* @return {Promise<void>}
|
|
1549
1546
|
*/
|
|
1550
1547
|
async handleDownloads(fileName) {
|
|
1551
|
-
this.page.waitForEvent('download').then(async
|
|
1548
|
+
this.page.waitForEvent('download').then(async download => {
|
|
1552
1549
|
const filePath = await download.path()
|
|
1553
1550
|
fileName = fileName || `downloads/${path.basename(filePath)}`
|
|
1554
1551
|
|
|
@@ -1742,6 +1739,7 @@ class Playwright extends Helper {
|
|
|
1742
1739
|
const el = els[0]
|
|
1743
1740
|
|
|
1744
1741
|
await el.clear()
|
|
1742
|
+
if (store.debugMode) this.debugSection('Focused', await elToString(el, 1))
|
|
1745
1743
|
|
|
1746
1744
|
await highlightActiveElement.call(this, el)
|
|
1747
1745
|
|
|
@@ -1853,8 +1851,8 @@ class Playwright extends Helper {
|
|
|
1853
1851
|
*/
|
|
1854
1852
|
async grabNumberOfVisibleElements(locator) {
|
|
1855
1853
|
let els = await this._locate(locator)
|
|
1856
|
-
els = await Promise.all(els.map(
|
|
1857
|
-
return els.filter(
|
|
1854
|
+
els = await Promise.all(els.map(el => el.isVisible()))
|
|
1855
|
+
return els.filter(v => v).length
|
|
1858
1856
|
}
|
|
1859
1857
|
|
|
1860
1858
|
/**
|
|
@@ -1964,9 +1962,7 @@ class Playwright extends Helper {
|
|
|
1964
1962
|
*/
|
|
1965
1963
|
async seeNumberOfElements(locator, num) {
|
|
1966
1964
|
const elements = await this._locate(locator)
|
|
1967
|
-
return equals(
|
|
1968
|
-
`expected number of elements (${new Locator(locator)}) is ${num}, but found ${elements.length}`,
|
|
1969
|
-
).assert(elements.length, num)
|
|
1965
|
+
return equals(`expected number of elements (${new Locator(locator)}) is ${num}, but found ${elements.length}`).assert(elements.length, num)
|
|
1970
1966
|
}
|
|
1971
1967
|
|
|
1972
1968
|
/**
|
|
@@ -1976,10 +1972,7 @@ class Playwright extends Helper {
|
|
|
1976
1972
|
*/
|
|
1977
1973
|
async seeNumberOfVisibleElements(locator, num) {
|
|
1978
1974
|
const res = await this.grabNumberOfVisibleElements(locator)
|
|
1979
|
-
return equals(`expected number of visible elements (${new Locator(locator)}) is ${num}, but found ${res}`).assert(
|
|
1980
|
-
res,
|
|
1981
|
-
num,
|
|
1982
|
-
)
|
|
1975
|
+
return equals(`expected number of visible elements (${new Locator(locator)}) is ${num}, but found ${res}`).assert(res, num)
|
|
1983
1976
|
}
|
|
1984
1977
|
|
|
1985
1978
|
/**
|
|
@@ -1998,7 +1991,7 @@ class Playwright extends Helper {
|
|
|
1998
1991
|
*/
|
|
1999
1992
|
async seeCookie(name) {
|
|
2000
1993
|
const cookies = await this.browserContext.cookies()
|
|
2001
|
-
empty(`cookie ${name} to be set`).negate(cookies.filter(
|
|
1994
|
+
empty(`cookie ${name} to be set`).negate(cookies.filter(c => c.name === name))
|
|
2002
1995
|
}
|
|
2003
1996
|
|
|
2004
1997
|
/**
|
|
@@ -2006,7 +1999,7 @@ class Playwright extends Helper {
|
|
|
2006
1999
|
*/
|
|
2007
2000
|
async dontSeeCookie(name) {
|
|
2008
2001
|
const cookies = await this.browserContext.cookies()
|
|
2009
|
-
empty(`cookie ${name} not to be set`).assert(cookies.filter(
|
|
2002
|
+
empty(`cookie ${name} not to be set`).assert(cookies.filter(c => c.name === name))
|
|
2010
2003
|
}
|
|
2011
2004
|
|
|
2012
2005
|
/**
|
|
@@ -2017,17 +2010,18 @@ class Playwright extends Helper {
|
|
|
2017
2010
|
async grabCookie(name) {
|
|
2018
2011
|
const cookies = await this.browserContext.cookies()
|
|
2019
2012
|
if (!name) return cookies
|
|
2020
|
-
const cookie = cookies.filter(
|
|
2013
|
+
const cookie = cookies.filter(c => c.name === name)
|
|
2021
2014
|
if (cookie[0]) return cookie[0]
|
|
2022
2015
|
}
|
|
2023
2016
|
|
|
2024
2017
|
/**
|
|
2025
2018
|
* {{> clearCookie }}
|
|
2026
2019
|
*/
|
|
2027
|
-
async clearCookie() {
|
|
2028
|
-
// Playwright currently doesn't support to delete a certain cookie
|
|
2029
|
-
// https://github.com/microsoft/playwright/blob/main/docs/src/api/class-browsercontext.md#async-method-browsercontextclearcookies
|
|
2020
|
+
async clearCookie(cookieName) {
|
|
2030
2021
|
if (!this.browserContext) return
|
|
2022
|
+
if (cookieName) {
|
|
2023
|
+
return this.browserContext.clearCookies({ name: cookieName })
|
|
2024
|
+
}
|
|
2031
2025
|
return this.browserContext.clearCookies()
|
|
2032
2026
|
}
|
|
2033
2027
|
|
|
@@ -2099,7 +2093,7 @@ class Playwright extends Helper {
|
|
|
2099
2093
|
const els = await this._locate(locator)
|
|
2100
2094
|
const texts = []
|
|
2101
2095
|
for (const el of els) {
|
|
2102
|
-
texts.push(await
|
|
2096
|
+
texts.push(await el.innerText())
|
|
2103
2097
|
}
|
|
2104
2098
|
this.debug(`Matched ${els.length} elements`)
|
|
2105
2099
|
return texts
|
|
@@ -2121,7 +2115,7 @@ class Playwright extends Helper {
|
|
|
2121
2115
|
async grabValueFromAll(locator) {
|
|
2122
2116
|
const els = await findFields.call(this, locator)
|
|
2123
2117
|
this.debug(`Matched ${els.length} elements`)
|
|
2124
|
-
return Promise.all(els.map(
|
|
2118
|
+
return Promise.all(els.map(el => el.inputValue()))
|
|
2125
2119
|
}
|
|
2126
2120
|
|
|
2127
2121
|
/**
|
|
@@ -2140,7 +2134,7 @@ class Playwright extends Helper {
|
|
|
2140
2134
|
async grabHTMLFromAll(locator) {
|
|
2141
2135
|
const els = await this._locate(locator)
|
|
2142
2136
|
this.debug(`Matched ${els.length} elements`)
|
|
2143
|
-
return Promise.all(els.map(
|
|
2137
|
+
return Promise.all(els.map(el => el.innerHTML()))
|
|
2144
2138
|
}
|
|
2145
2139
|
|
|
2146
2140
|
/**
|
|
@@ -2161,11 +2155,7 @@ class Playwright extends Helper {
|
|
|
2161
2155
|
async grabCssPropertyFromAll(locator, cssProperty) {
|
|
2162
2156
|
const els = await this._locate(locator)
|
|
2163
2157
|
this.debug(`Matched ${els.length} elements`)
|
|
2164
|
-
const cssValues = await Promise.all(
|
|
2165
|
-
els.map((el) =>
|
|
2166
|
-
el.evaluate((el, cssProperty) => getComputedStyle(el).getPropertyValue(cssProperty), cssProperty),
|
|
2167
|
-
),
|
|
2168
|
-
)
|
|
2158
|
+
const cssValues = await Promise.all(els.map(el => el.evaluate((el, cssProperty) => getComputedStyle(el).getPropertyValue(cssProperty), cssProperty)))
|
|
2169
2159
|
|
|
2170
2160
|
return cssValues
|
|
2171
2161
|
}
|
|
@@ -2193,19 +2183,16 @@ class Playwright extends Helper {
|
|
|
2193
2183
|
}
|
|
2194
2184
|
}
|
|
2195
2185
|
|
|
2196
|
-
const values = Object.keys(cssPropertiesCamelCase).map(
|
|
2186
|
+
const values = Object.keys(cssPropertiesCamelCase).map(key => cssPropertiesCamelCase[key])
|
|
2197
2187
|
if (!Array.isArray(props)) props = [props]
|
|
2198
2188
|
let chunked = chunkArray(props, values.length)
|
|
2199
|
-
chunked = chunked.filter(
|
|
2189
|
+
chunked = chunked.filter(val => {
|
|
2200
2190
|
for (let i = 0; i < val.length; ++i) {
|
|
2201
|
-
// eslint-disable-next-line eqeqeq
|
|
2202
2191
|
if (val[i] != values[i]) return false
|
|
2203
2192
|
}
|
|
2204
2193
|
return true
|
|
2205
2194
|
})
|
|
2206
|
-
return equals(
|
|
2207
|
-
`all elements (${new Locator(locator)}) to have CSS property ${JSON.stringify(cssProperties)}`,
|
|
2208
|
-
).assert(chunked.length, elemAmount)
|
|
2195
|
+
return equals(`all elements (${new Locator(locator)}) to have CSS property ${JSON.stringify(cssProperties)}`).assert(chunked.length, elemAmount)
|
|
2209
2196
|
}
|
|
2210
2197
|
|
|
2211
2198
|
/**
|
|
@@ -2218,16 +2205,16 @@ class Playwright extends Helper {
|
|
|
2218
2205
|
|
|
2219
2206
|
const elemAmount = res.length
|
|
2220
2207
|
const commands = []
|
|
2221
|
-
res.forEach(
|
|
2222
|
-
Object.keys(attributes).forEach(
|
|
2208
|
+
res.forEach(el => {
|
|
2209
|
+
Object.keys(attributes).forEach(prop => {
|
|
2223
2210
|
commands.push(el.evaluate((el, attr) => el[attr] || el.getAttribute(attr), prop))
|
|
2224
2211
|
})
|
|
2225
2212
|
})
|
|
2226
2213
|
let attrs = await Promise.all(commands)
|
|
2227
|
-
const values = Object.keys(attributes).map(
|
|
2214
|
+
const values = Object.keys(attributes).map(key => attributes[key])
|
|
2228
2215
|
if (!Array.isArray(attrs)) attrs = [attrs]
|
|
2229
2216
|
let chunked = chunkArray(attrs, values.length)
|
|
2230
|
-
chunked = chunked.filter(
|
|
2217
|
+
chunked = chunked.filter(val => {
|
|
2231
2218
|
for (let i = 0; i < val.length; ++i) {
|
|
2232
2219
|
// the attribute could be a boolean
|
|
2233
2220
|
if (typeof val[i] === 'boolean') return val[i] === values[i]
|
|
@@ -2236,10 +2223,7 @@ class Playwright extends Helper {
|
|
|
2236
2223
|
}
|
|
2237
2224
|
return true
|
|
2238
2225
|
})
|
|
2239
|
-
return equals(`all elements (${new Locator(locator)}) to have attributes ${JSON.stringify(attributes)}`).assert(
|
|
2240
|
-
chunked.length,
|
|
2241
|
-
elemAmount,
|
|
2242
|
-
)
|
|
2226
|
+
return equals(`all elements (${new Locator(locator)}) to have attributes ${JSON.stringify(attributes)}`).assert(chunked.length, elemAmount)
|
|
2243
2227
|
}
|
|
2244
2228
|
|
|
2245
2229
|
/**
|
|
@@ -2312,7 +2296,7 @@ class Playwright extends Helper {
|
|
|
2312
2296
|
const fullPageOption = fullPage || this.options.fullPageScreenshots
|
|
2313
2297
|
let outputFile = screenshotOutputFolder(fileName)
|
|
2314
2298
|
|
|
2315
|
-
this.
|
|
2299
|
+
this.debugSection('Screenshot', relativeDir(outputFile))
|
|
2316
2300
|
|
|
2317
2301
|
await this.page.screenshot({
|
|
2318
2302
|
path: outputFile,
|
|
@@ -2325,7 +2309,7 @@ class Playwright extends Helper {
|
|
|
2325
2309
|
const activeSessionPage = this.sessionPages[sessionName]
|
|
2326
2310
|
outputFile = screenshotOutputFolder(`${sessionName}_${fileName}`)
|
|
2327
2311
|
|
|
2328
|
-
this.
|
|
2312
|
+
this.debugSection('Screenshot', `${sessionName} - ${relativeDir(outputFile)}`)
|
|
2329
2313
|
|
|
2330
2314
|
if (activeSessionPage) {
|
|
2331
2315
|
await activeSessionPage.screenshot({
|
|
@@ -2359,9 +2343,7 @@ class Playwright extends Helper {
|
|
|
2359
2343
|
method = method.toLowerCase()
|
|
2360
2344
|
const allowedMethods = ['get', 'post', 'patch', 'head', 'fetch', 'delete']
|
|
2361
2345
|
if (!allowedMethods.includes(method)) {
|
|
2362
|
-
throw new Error(
|
|
2363
|
-
`Method ${method} is not allowed, use the one from a list ${allowedMethods} or switch to using REST helper`,
|
|
2364
|
-
)
|
|
2346
|
+
throw new Error(`Method ${method} is not allowed, use the one from a list ${allowedMethods} or switch to using REST helper`)
|
|
2365
2347
|
}
|
|
2366
2348
|
|
|
2367
2349
|
if (url.startsWith('/')) {
|
|
@@ -2398,21 +2380,19 @@ class Playwright extends Helper {
|
|
|
2398
2380
|
if (this.options.recordVideo && this.page && this.page.video()) {
|
|
2399
2381
|
test.artifacts.video = saveVideoForPage(this.page, `${test.title}.failed`)
|
|
2400
2382
|
for (const sessionName in this.sessionPages) {
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
`${test.title}_${sessionName}.failed`,
|
|
2404
|
-
)
|
|
2383
|
+
if (sessionName === '') continue
|
|
2384
|
+
test.artifacts[`video_${sessionName}`] = saveVideoForPage(this.sessionPages[sessionName], `${sessionName}_${test.title}.failed`)
|
|
2405
2385
|
}
|
|
2406
2386
|
}
|
|
2407
2387
|
|
|
2408
2388
|
if (this.options.trace) {
|
|
2409
2389
|
test.artifacts.trace = await saveTraceForContext(this.browserContext, `${test.title}.failed`)
|
|
2410
2390
|
for (const sessionName in this.sessionPages) {
|
|
2411
|
-
if (
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
)
|
|
2391
|
+
if (sessionName === '') continue
|
|
2392
|
+
const sessionPage = this.sessionPages[sessionName]
|
|
2393
|
+
const sessionContext = sessionPage.context()
|
|
2394
|
+
if (!sessionContext || !sessionContext.tracing) continue
|
|
2395
|
+
test.artifacts[`trace_${sessionName}`] = await saveTraceForContext(sessionContext, `${sessionName}_${test.title}.failed`)
|
|
2416
2396
|
}
|
|
2417
2397
|
}
|
|
2418
2398
|
|
|
@@ -2426,16 +2406,14 @@ class Playwright extends Helper {
|
|
|
2426
2406
|
if (this.options.keepVideoForPassedTests) {
|
|
2427
2407
|
test.artifacts.video = saveVideoForPage(this.page, `${test.title}.passed`)
|
|
2428
2408
|
for (const sessionName of Object.keys(this.sessionPages)) {
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
`${test.title}_${sessionName}.passed`,
|
|
2432
|
-
)
|
|
2409
|
+
if (sessionName === '') continue
|
|
2410
|
+
test.artifacts[`video_${sessionName}`] = saveVideoForPage(this.sessionPages[sessionName], `${sessionName}_${test.title}.passed`)
|
|
2433
2411
|
}
|
|
2434
2412
|
} else {
|
|
2435
2413
|
this.page
|
|
2436
2414
|
.video()
|
|
2437
2415
|
.delete()
|
|
2438
|
-
.catch(
|
|
2416
|
+
.catch(e => {})
|
|
2439
2417
|
}
|
|
2440
2418
|
}
|
|
2441
2419
|
|
|
@@ -2444,11 +2422,11 @@ class Playwright extends Helper {
|
|
|
2444
2422
|
if (this.options.trace) {
|
|
2445
2423
|
test.artifacts.trace = await saveTraceForContext(this.browserContext, `${test.title}.passed`)
|
|
2446
2424
|
for (const sessionName in this.sessionPages) {
|
|
2447
|
-
if (
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
)
|
|
2425
|
+
if (sessionName === '') continue
|
|
2426
|
+
const sessionPage = this.sessionPages[sessionName]
|
|
2427
|
+
const sessionContext = sessionPage.context()
|
|
2428
|
+
if (!sessionContext || !sessionContext.tracing) continue
|
|
2429
|
+
test.artifacts[`trace_${sessionName}`] = await saveTraceForContext(sessionContext, `${sessionName}_${test.title}.passed`)
|
|
2452
2430
|
}
|
|
2453
2431
|
}
|
|
2454
2432
|
} else {
|
|
@@ -2465,7 +2443,7 @@ class Playwright extends Helper {
|
|
|
2465
2443
|
* {{> wait }}
|
|
2466
2444
|
*/
|
|
2467
2445
|
async wait(sec) {
|
|
2468
|
-
return new Promise(
|
|
2446
|
+
return new Promise(done => {
|
|
2469
2447
|
setTimeout(done, sec * 1000)
|
|
2470
2448
|
})
|
|
2471
2449
|
}
|
|
@@ -2481,20 +2459,18 @@ class Playwright extends Helper {
|
|
|
2481
2459
|
const context = await this._getContext()
|
|
2482
2460
|
if (!locator.isXPath()) {
|
|
2483
2461
|
const valueFn = function ([locator]) {
|
|
2484
|
-
return Array.from(document.querySelectorAll(locator)).filter(
|
|
2462
|
+
return Array.from(document.querySelectorAll(locator)).filter(el => !el.disabled).length > 0
|
|
2485
2463
|
}
|
|
2486
2464
|
waiter = context.waitForFunction(valueFn, [locator.value], { timeout: waitTimeout })
|
|
2487
2465
|
} else {
|
|
2488
2466
|
const enabledFn = function ([locator, $XPath]) {
|
|
2489
|
-
eval($XPath)
|
|
2490
|
-
return $XPath(null, locator).filter(
|
|
2467
|
+
eval($XPath)
|
|
2468
|
+
return $XPath(null, locator).filter(el => !el.disabled).length > 0
|
|
2491
2469
|
}
|
|
2492
2470
|
waiter = context.waitForFunction(enabledFn, [locator.value, $XPath.toString()], { timeout: waitTimeout })
|
|
2493
2471
|
}
|
|
2494
|
-
return waiter.catch(
|
|
2495
|
-
throw new Error(
|
|
2496
|
-
`element (${locator.toString()}) still not enabled after ${waitTimeout / 1000} sec\n${err.message}`,
|
|
2497
|
-
)
|
|
2472
|
+
return waiter.catch(err => {
|
|
2473
|
+
throw new Error(`element (${locator.toString()}) still not enabled after ${waitTimeout / 1000} sec\n${err.message}`)
|
|
2498
2474
|
})
|
|
2499
2475
|
}
|
|
2500
2476
|
|
|
@@ -2509,20 +2485,18 @@ class Playwright extends Helper {
|
|
|
2509
2485
|
const context = await this._getContext()
|
|
2510
2486
|
if (!locator.isXPath()) {
|
|
2511
2487
|
const valueFn = function ([locator]) {
|
|
2512
|
-
return Array.from(document.querySelectorAll(locator)).filter(
|
|
2488
|
+
return Array.from(document.querySelectorAll(locator)).filter(el => el.disabled).length > 0
|
|
2513
2489
|
}
|
|
2514
2490
|
waiter = context.waitForFunction(valueFn, [locator.value], { timeout: waitTimeout })
|
|
2515
2491
|
} else {
|
|
2516
2492
|
const disabledFn = function ([locator, $XPath]) {
|
|
2517
|
-
eval($XPath)
|
|
2518
|
-
return $XPath(null, locator).filter(
|
|
2493
|
+
eval($XPath)
|
|
2494
|
+
return $XPath(null, locator).filter(el => el.disabled).length > 0
|
|
2519
2495
|
}
|
|
2520
2496
|
waiter = context.waitForFunction(disabledFn, [locator.value, $XPath.toString()], { timeout: waitTimeout })
|
|
2521
2497
|
}
|
|
2522
|
-
return waiter.catch(
|
|
2523
|
-
throw new Error(
|
|
2524
|
-
`element (${locator.toString()}) is still enabled after ${waitTimeout / 1000} sec\n${err.message}`,
|
|
2525
|
-
)
|
|
2498
|
+
return waiter.catch(err => {
|
|
2499
|
+
throw new Error(`element (${locator.toString()}) is still enabled after ${waitTimeout / 1000} sec\n${err.message}`)
|
|
2526
2500
|
})
|
|
2527
2501
|
}
|
|
2528
2502
|
|
|
@@ -2537,26 +2511,21 @@ class Playwright extends Helper {
|
|
|
2537
2511
|
const context = await this._getContext()
|
|
2538
2512
|
if (!locator.isXPath()) {
|
|
2539
2513
|
const valueFn = function ([locator, value]) {
|
|
2540
|
-
return (
|
|
2541
|
-
Array.from(document.querySelectorAll(locator)).filter((el) => (el.value || '').indexOf(value) !== -1).length >
|
|
2542
|
-
0
|
|
2543
|
-
)
|
|
2514
|
+
return Array.from(document.querySelectorAll(locator)).filter(el => (el.value || '').indexOf(value) !== -1).length > 0
|
|
2544
2515
|
}
|
|
2545
2516
|
waiter = context.waitForFunction(valueFn, [locator.value, value], { timeout: waitTimeout })
|
|
2546
2517
|
} else {
|
|
2547
2518
|
const valueFn = function ([locator, $XPath, value]) {
|
|
2548
|
-
eval($XPath)
|
|
2549
|
-
return $XPath(null, locator).filter(
|
|
2519
|
+
eval($XPath)
|
|
2520
|
+
return $XPath(null, locator).filter(el => (el.value || '').indexOf(value) !== -1).length > 0
|
|
2550
2521
|
}
|
|
2551
2522
|
waiter = context.waitForFunction(valueFn, [locator.value, $XPath.toString(), value], {
|
|
2552
2523
|
timeout: waitTimeout,
|
|
2553
2524
|
})
|
|
2554
2525
|
}
|
|
2555
|
-
return waiter.catch(
|
|
2526
|
+
return waiter.catch(err => {
|
|
2556
2527
|
const loc = locator.toString()
|
|
2557
|
-
throw new Error(
|
|
2558
|
-
`element (${loc}) is not in DOM or there is no element(${loc}) with value "${value}" after ${waitTimeout / 1000} sec\n${err.message}`,
|
|
2559
|
-
)
|
|
2528
|
+
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}`)
|
|
2560
2529
|
})
|
|
2561
2530
|
}
|
|
2562
2531
|
|
|
@@ -2576,22 +2545,20 @@ class Playwright extends Helper {
|
|
|
2576
2545
|
if (!els || els.length === 0) {
|
|
2577
2546
|
return false
|
|
2578
2547
|
}
|
|
2579
|
-
return Array.prototype.filter.call(els,
|
|
2548
|
+
return Array.prototype.filter.call(els, el => el.offsetParent !== null).length === num
|
|
2580
2549
|
}
|
|
2581
2550
|
waiter = context.waitForFunction(visibleFn, [locator.value, num], { timeout: waitTimeout })
|
|
2582
2551
|
} else {
|
|
2583
2552
|
const visibleFn = function ([locator, $XPath, num]) {
|
|
2584
|
-
eval($XPath)
|
|
2585
|
-
return $XPath(null, locator).filter(
|
|
2553
|
+
eval($XPath)
|
|
2554
|
+
return $XPath(null, locator).filter(el => el.offsetParent !== null).length === num
|
|
2586
2555
|
}
|
|
2587
2556
|
waiter = context.waitForFunction(visibleFn, [locator.value, $XPath.toString(), num], {
|
|
2588
2557
|
timeout: waitTimeout,
|
|
2589
2558
|
})
|
|
2590
2559
|
}
|
|
2591
|
-
return waiter.catch(
|
|
2592
|
-
throw new Error(
|
|
2593
|
-
`The number of elements (${locator.toString()}) is not ${num} after ${waitTimeout / 1000} sec\n${err.message}`,
|
|
2594
|
-
)
|
|
2560
|
+
return waiter.catch(err => {
|
|
2561
|
+
throw new Error(`The number of elements (${locator.toString()}) is not ${num} after ${waitTimeout / 1000} sec\n${err.message}`)
|
|
2595
2562
|
})
|
|
2596
2563
|
}
|
|
2597
2564
|
|
|
@@ -2599,9 +2566,7 @@ class Playwright extends Helper {
|
|
|
2599
2566
|
* {{> waitForClickable }}
|
|
2600
2567
|
*/
|
|
2601
2568
|
async waitForClickable(locator, waitTimeout) {
|
|
2602
|
-
console.log(
|
|
2603
|
-
'I.waitForClickable is DEPRECATED: This is no longer needed, Playwright automatically waits for element to be clickable',
|
|
2604
|
-
)
|
|
2569
|
+
console.log('I.waitForClickable is DEPRECATED: This is no longer needed, Playwright automatically waits for element to be clickable')
|
|
2605
2570
|
console.log('Remove usage of this function')
|
|
2606
2571
|
}
|
|
2607
2572
|
|
|
@@ -2617,9 +2582,7 @@ class Playwright extends Helper {
|
|
|
2617
2582
|
try {
|
|
2618
2583
|
await context.locator(buildLocatorString(locator)).first().waitFor({ timeout: waitTimeout, state: 'attached' })
|
|
2619
2584
|
} catch (e) {
|
|
2620
|
-
throw new Error(
|
|
2621
|
-
`element (${locator.toString()}) still not present on page after ${waitTimeout / 1000} sec\n${e.message}`,
|
|
2622
|
-
)
|
|
2585
|
+
throw new Error(`element (${locator.toString()}) still not present on page after ${waitTimeout / 1000} sec\n${e.message}`)
|
|
2623
2586
|
}
|
|
2624
2587
|
}
|
|
2625
2588
|
|
|
@@ -2711,10 +2674,8 @@ class Playwright extends Helper {
|
|
|
2711
2674
|
.locator(buildLocatorString(locator))
|
|
2712
2675
|
.first()
|
|
2713
2676
|
.waitFor({ timeout: waitTimeout, state: 'hidden' })
|
|
2714
|
-
.catch(
|
|
2715
|
-
throw new Error(
|
|
2716
|
-
`element (${locator.toString()}) still not hidden after ${waitTimeout / 1000} sec\n${err.message}`,
|
|
2717
|
-
)
|
|
2677
|
+
.catch(err => {
|
|
2678
|
+
throw new Error(`element (${locator.toString()}) still not hidden after ${waitTimeout / 1000} sec\n${err.message}`)
|
|
2718
2679
|
})
|
|
2719
2680
|
}
|
|
2720
2681
|
|
|
@@ -2737,9 +2698,12 @@ class Playwright extends Helper {
|
|
|
2737
2698
|
}
|
|
2738
2699
|
|
|
2739
2700
|
async _getContext() {
|
|
2740
|
-
if (this.context && this.context.constructor.name === 'FrameLocator') {
|
|
2701
|
+
if ((this.context && this.context.constructor.name === 'FrameLocator') || this.context) {
|
|
2741
2702
|
return this.context
|
|
2742
2703
|
}
|
|
2704
|
+
if (this.frame) {
|
|
2705
|
+
return this.frame
|
|
2706
|
+
}
|
|
2743
2707
|
return this.page
|
|
2744
2708
|
}
|
|
2745
2709
|
|
|
@@ -2751,14 +2715,14 @@ class Playwright extends Helper {
|
|
|
2751
2715
|
|
|
2752
2716
|
return this.page
|
|
2753
2717
|
.waitForFunction(
|
|
2754
|
-
|
|
2718
|
+
urlPart => {
|
|
2755
2719
|
const currUrl = decodeURIComponent(decodeURIComponent(decodeURIComponent(window.location.href)))
|
|
2756
2720
|
return currUrl.indexOf(urlPart) > -1
|
|
2757
2721
|
},
|
|
2758
2722
|
urlPart,
|
|
2759
2723
|
{ timeout: waitTimeout },
|
|
2760
2724
|
)
|
|
2761
|
-
.catch(async
|
|
2725
|
+
.catch(async e => {
|
|
2762
2726
|
const currUrl = await this._getPageUrl() // Required because the waitForFunction can't return data.
|
|
2763
2727
|
if (/Timeout/i.test(e.message)) {
|
|
2764
2728
|
throw new Error(`expected url to include ${urlPart}, but found ${currUrl}`)
|
|
@@ -2781,14 +2745,14 @@ class Playwright extends Helper {
|
|
|
2781
2745
|
|
|
2782
2746
|
return this.page
|
|
2783
2747
|
.waitForFunction(
|
|
2784
|
-
|
|
2748
|
+
urlPart => {
|
|
2785
2749
|
const currUrl = decodeURIComponent(decodeURIComponent(decodeURIComponent(window.location.href)))
|
|
2786
2750
|
return currUrl.indexOf(urlPart) > -1
|
|
2787
2751
|
},
|
|
2788
2752
|
urlPart,
|
|
2789
2753
|
{ timeout: waitTimeout },
|
|
2790
2754
|
)
|
|
2791
|
-
.catch(async
|
|
2755
|
+
.catch(async e => {
|
|
2792
2756
|
const currUrl = await this._getPageUrl() // Required because the waitForFunction can't return data.
|
|
2793
2757
|
if (/Timeout/i.test(e.message)) {
|
|
2794
2758
|
throw new Error(`expected url to be ${urlPart}, but found ${currUrl}`)
|
|
@@ -2804,53 +2768,74 @@ class Playwright extends Helper {
|
|
|
2804
2768
|
async waitForText(text, sec = null, context = null) {
|
|
2805
2769
|
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
2806
2770
|
const errorMessage = `Text "${text}" was not found on page after ${waitTimeout / 1000} sec.`
|
|
2807
|
-
let waiter
|
|
2808
2771
|
|
|
2809
2772
|
const contextObject = await this._getContext()
|
|
2810
2773
|
|
|
2811
2774
|
if (context) {
|
|
2812
2775
|
const locator = new Locator(context, 'css')
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2776
|
+
try {
|
|
2777
|
+
if (!locator.isXPath()) {
|
|
2778
|
+
return contextObject
|
|
2816
2779
|
.locator(`${locator.isCustom() ? `${locator.type}=${locator.value}` : locator.simplify()} >> text=${text}`)
|
|
2817
2780
|
.first()
|
|
2818
2781
|
.waitFor({ timeout: waitTimeout, state: 'visible' })
|
|
2819
|
-
|
|
2820
|
-
|
|
2782
|
+
.catch(e => {
|
|
2783
|
+
throw new Error(errorMessage)
|
|
2784
|
+
})
|
|
2821
2785
|
}
|
|
2822
|
-
}
|
|
2823
2786
|
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2787
|
+
if (locator.isXPath()) {
|
|
2788
|
+
return contextObject
|
|
2789
|
+
.waitForFunction(
|
|
2790
|
+
([locator, text, $XPath]) => {
|
|
2791
|
+
eval($XPath)
|
|
2792
|
+
const el = $XPath(null, locator)
|
|
2793
|
+
if (!el.length) return false
|
|
2794
|
+
return el[0].innerText.indexOf(text) > -1
|
|
2795
|
+
},
|
|
2796
|
+
[locator.value, text, $XPath.toString()],
|
|
2797
|
+
{ timeout: waitTimeout },
|
|
2798
|
+
)
|
|
2799
|
+
.catch(e => {
|
|
2800
|
+
throw new Error(errorMessage)
|
|
2801
|
+
})
|
|
2838
2802
|
}
|
|
2803
|
+
} catch (e) {
|
|
2804
|
+
throw new Error(`${errorMessage}\n${e.message}`)
|
|
2839
2805
|
}
|
|
2840
|
-
} else {
|
|
2841
|
-
// we have this as https://github.com/microsoft/playwright/issues/26829 is not yet implemented
|
|
2842
|
-
// eslint-disable-next-line no-lonely-if
|
|
2843
|
-
const _contextObject = this.frame ? this.frame : contextObject
|
|
2844
|
-
let count = 0
|
|
2845
|
-
do {
|
|
2846
|
-
waiter = await _contextObject.locator(`:has-text("${text}")`).first().isVisible()
|
|
2847
|
-
if (waiter) break
|
|
2848
|
-
await this.wait(1)
|
|
2849
|
-
count += 1000
|
|
2850
|
-
} while (count <= waitTimeout)
|
|
2851
|
-
|
|
2852
|
-
if (!waiter) throw new Error(`${errorMessage}`)
|
|
2853
2806
|
}
|
|
2807
|
+
|
|
2808
|
+
// Based on original implementation but fixed to check title text and remove problematic promiseRetry
|
|
2809
|
+
// Original used timeoutGap for waitForFunction to give it slightly more time than the locator
|
|
2810
|
+
const timeoutGap = waitTimeout + 1000
|
|
2811
|
+
|
|
2812
|
+
return Promise.race([
|
|
2813
|
+
// Strategy 1: waitForFunction that checks both body AND title text
|
|
2814
|
+
// Use this.page instead of contextObject because FrameLocator doesn't have waitForFunction
|
|
2815
|
+
// Original only checked document.body.innerText, missing title text like "TestEd"
|
|
2816
|
+
this.page.waitForFunction(
|
|
2817
|
+
function (text) {
|
|
2818
|
+
// Check body text (original behavior)
|
|
2819
|
+
if (document.body && document.body.innerText && document.body.innerText.indexOf(text) > -1) {
|
|
2820
|
+
return true
|
|
2821
|
+
}
|
|
2822
|
+
// Check document title (fixes the TestEd in title issue)
|
|
2823
|
+
if (document.title && document.title.indexOf(text) > -1) {
|
|
2824
|
+
return true
|
|
2825
|
+
}
|
|
2826
|
+
return false
|
|
2827
|
+
},
|
|
2828
|
+
text,
|
|
2829
|
+
{ timeout: timeoutGap },
|
|
2830
|
+
),
|
|
2831
|
+
// Strategy 2: Native Playwright text locator (replaces problematic promiseRetry)
|
|
2832
|
+
contextObject
|
|
2833
|
+
.locator(`:has-text(${JSON.stringify(text)})`)
|
|
2834
|
+
.first()
|
|
2835
|
+
.waitFor({ timeout: waitTimeout }),
|
|
2836
|
+
]).catch(err => {
|
|
2837
|
+
throw new Error(errorMessage)
|
|
2838
|
+
})
|
|
2854
2839
|
}
|
|
2855
2840
|
|
|
2856
2841
|
/**
|
|
@@ -3019,11 +3004,11 @@ class Playwright extends Helper {
|
|
|
3019
3004
|
}
|
|
3020
3005
|
} else {
|
|
3021
3006
|
const visibleFn = function ([locator, $XPath]) {
|
|
3022
|
-
eval($XPath)
|
|
3007
|
+
eval($XPath)
|
|
3023
3008
|
return $XPath(null, locator).length === 0
|
|
3024
3009
|
}
|
|
3025
3010
|
waiter = context.waitForFunction(visibleFn, [locator.value, $XPath.toString()], { timeout: waitTimeout })
|
|
3026
|
-
return waiter.catch(
|
|
3011
|
+
return waiter.catch(err => {
|
|
3027
3012
|
throw new Error(`element (${locator.toString()}) still on page after ${waitTimeout / 1000} sec\n${err.message}`)
|
|
3028
3013
|
})
|
|
3029
3014
|
}
|
|
@@ -3045,9 +3030,9 @@ class Playwright extends Helper {
|
|
|
3045
3030
|
|
|
3046
3031
|
return promiseRetry(
|
|
3047
3032
|
async (retry, number) => {
|
|
3048
|
-
const _grabCookie = async
|
|
3033
|
+
const _grabCookie = async name => {
|
|
3049
3034
|
const cookies = await this.browserContext.cookies()
|
|
3050
|
-
const cookie = cookies.filter(
|
|
3035
|
+
const cookie = cookies.filter(c => c.name === name)
|
|
3051
3036
|
if (cookie.length === 0) throw Error(`Cookie ${name} is not found after ${retries}s`)
|
|
3052
3037
|
}
|
|
3053
3038
|
|
|
@@ -3126,7 +3111,7 @@ class Playwright extends Helper {
|
|
|
3126
3111
|
this.recording = true
|
|
3127
3112
|
this.recordedAtLeastOnce = true
|
|
3128
3113
|
|
|
3129
|
-
this.page.on('requestfinished', async
|
|
3114
|
+
this.page.on('requestfinished', async request => {
|
|
3130
3115
|
const information = {
|
|
3131
3116
|
url: request.url(),
|
|
3132
3117
|
method: request.method(),
|
|
@@ -3165,20 +3150,20 @@ class Playwright extends Helper {
|
|
|
3165
3150
|
*/
|
|
3166
3151
|
blockTraffic(urls) {
|
|
3167
3152
|
if (Array.isArray(urls)) {
|
|
3168
|
-
urls.forEach(
|
|
3169
|
-
this.page.route(url,
|
|
3153
|
+
urls.forEach(url => {
|
|
3154
|
+
this.page.route(url, route => {
|
|
3170
3155
|
route
|
|
3171
3156
|
.abort()
|
|
3172
3157
|
// Sometimes it happens that browser has been closed in the meantime. It is ok to ignore error then.
|
|
3173
|
-
.catch(
|
|
3158
|
+
.catch(e => {})
|
|
3174
3159
|
})
|
|
3175
3160
|
})
|
|
3176
3161
|
} else {
|
|
3177
|
-
this.page.route(urls,
|
|
3162
|
+
this.page.route(urls, route => {
|
|
3178
3163
|
route
|
|
3179
3164
|
.abort()
|
|
3180
3165
|
// Sometimes it happens that browser has been closed in the meantime. It is ok to ignore error then.
|
|
3181
|
-
.catch(
|
|
3166
|
+
.catch(e => {})
|
|
3182
3167
|
})
|
|
3183
3168
|
}
|
|
3184
3169
|
}
|
|
@@ -3207,8 +3192,8 @@ class Playwright extends Helper {
|
|
|
3207
3192
|
urls = [urls]
|
|
3208
3193
|
}
|
|
3209
3194
|
|
|
3210
|
-
urls.forEach(
|
|
3211
|
-
this.page.route(url,
|
|
3195
|
+
urls.forEach(url => {
|
|
3196
|
+
this.page.route(url, route => {
|
|
3212
3197
|
if (this.page.isClosed()) {
|
|
3213
3198
|
// Sometimes it happens that browser has been closed in the meantime.
|
|
3214
3199
|
// In this case we just don't fulfill to prevent error in test scenario.
|
|
@@ -3254,13 +3239,10 @@ class Playwright extends Helper {
|
|
|
3254
3239
|
*/
|
|
3255
3240
|
grabTrafficUrl(urlMatch) {
|
|
3256
3241
|
if (!this.recordedAtLeastOnce) {
|
|
3257
|
-
throw new Error(
|
|
3258
|
-
'Failure in test automation. You use "I.grabTrafficUrl", but "I.startRecordingTraffic" was never called before.',
|
|
3259
|
-
)
|
|
3242
|
+
throw new Error('Failure in test automation. You use "I.grabTrafficUrl", but "I.startRecordingTraffic" was never called before.')
|
|
3260
3243
|
}
|
|
3261
3244
|
|
|
3262
3245
|
for (const i in this.requests) {
|
|
3263
|
-
// eslint-disable-next-line no-prototype-builtins
|
|
3264
3246
|
if (this.requests.hasOwnProperty(i)) {
|
|
3265
3247
|
const request = this.requests[i]
|
|
3266
3248
|
|
|
@@ -3310,15 +3292,15 @@ class Playwright extends Helper {
|
|
|
3310
3292
|
await this.cdpSession.send('Network.enable')
|
|
3311
3293
|
await this.cdpSession.send('Page.enable')
|
|
3312
3294
|
|
|
3313
|
-
this.cdpSession.on('Network.webSocketFrameReceived',
|
|
3295
|
+
this.cdpSession.on('Network.webSocketFrameReceived', payload => {
|
|
3314
3296
|
this._logWebsocketMessages(this._getWebSocketLog('RECEIVED', payload))
|
|
3315
3297
|
})
|
|
3316
3298
|
|
|
3317
|
-
this.cdpSession.on('Network.webSocketFrameSent',
|
|
3299
|
+
this.cdpSession.on('Network.webSocketFrameSent', payload => {
|
|
3318
3300
|
this._logWebsocketMessages(this._getWebSocketLog('SENT', payload))
|
|
3319
3301
|
})
|
|
3320
3302
|
|
|
3321
|
-
this.cdpSession.on('Network.webSocketFrameError',
|
|
3303
|
+
this.cdpSession.on('Network.webSocketFrameError', payload => {
|
|
3322
3304
|
this._logWebsocketMessages(this._getWebSocketLog('ERROR', payload))
|
|
3323
3305
|
})
|
|
3324
3306
|
}
|
|
@@ -3342,9 +3324,7 @@ class Playwright extends Helper {
|
|
|
3342
3324
|
grabWebSocketMessages() {
|
|
3343
3325
|
if (!this.recordingWebSocketMessages) {
|
|
3344
3326
|
if (!this.recordedWebSocketMessagesAtLeastOnce) {
|
|
3345
|
-
throw new Error(
|
|
3346
|
-
'Failure in test automation. You use "I.grabWebSocketMessages", but "I.startRecordingWebSocketMessages" was never called before.',
|
|
3347
|
-
)
|
|
3327
|
+
throw new Error('Failure in test automation. You use "I.grabWebSocketMessages", but "I.startRecordingWebSocketMessages" was never called before.')
|
|
3348
3328
|
}
|
|
3349
3329
|
}
|
|
3350
3330
|
return this.webSocketMessages
|
|
@@ -3491,17 +3471,13 @@ async function proceedClick(locator, context = null, options = {}) {
|
|
|
3491
3471
|
}
|
|
3492
3472
|
const els = await findClickable.call(this, matcher, locator)
|
|
3493
3473
|
if (context) {
|
|
3494
|
-
assertElementExists(
|
|
3495
|
-
els,
|
|
3496
|
-
locator,
|
|
3497
|
-
'Clickable element',
|
|
3498
|
-
`was not found inside element ${new Locator(context).toString()}`,
|
|
3499
|
-
)
|
|
3474
|
+
assertElementExists(els, locator, 'Clickable element', `was not found inside element ${new Locator(context).toString()}`)
|
|
3500
3475
|
} else {
|
|
3501
3476
|
assertElementExists(els, locator, 'Clickable element')
|
|
3502
3477
|
}
|
|
3503
3478
|
|
|
3504
3479
|
await highlightActiveElement.call(this, els[0])
|
|
3480
|
+
if (store.debugMode) this.debugSection('Clicked', await elToString(els[0], 1))
|
|
3505
3481
|
|
|
3506
3482
|
/*
|
|
3507
3483
|
using the force true options itself but instead dispatching a click
|
|
@@ -3563,16 +3539,18 @@ async function proceedSee(assertType, text, context, strict = false) {
|
|
|
3563
3539
|
description = `element ${locator.toString()}`
|
|
3564
3540
|
const els = await this._locate(locator)
|
|
3565
3541
|
assertElementExists(els, locator.toString())
|
|
3566
|
-
allText = await Promise.all(els.map(
|
|
3542
|
+
allText = await Promise.all(els.map(el => el.innerText()))
|
|
3543
|
+
}
|
|
3544
|
+
|
|
3545
|
+
if (store?.currentStep?.opts?.ignoreCase === true) {
|
|
3546
|
+
text = text.toLowerCase()
|
|
3547
|
+
allText = allText.map(elText => elText.toLowerCase())
|
|
3567
3548
|
}
|
|
3568
3549
|
|
|
3569
3550
|
if (strict) {
|
|
3570
|
-
return allText.map(
|
|
3551
|
+
return allText.map(elText => equals(description)[assertType](text, elText))
|
|
3571
3552
|
}
|
|
3572
|
-
return stringIncludes(description)[assertType](
|
|
3573
|
-
normalizeSpacesInString(text),
|
|
3574
|
-
normalizeSpacesInString(allText.join(' | ')),
|
|
3575
|
-
)
|
|
3553
|
+
return stringIncludes(description)[assertType](normalizeSpacesInString(text), normalizeSpacesInString(allText.join(' | ')))
|
|
3576
3554
|
}
|
|
3577
3555
|
|
|
3578
3556
|
async function findCheckable(locator, context) {
|
|
@@ -3602,7 +3580,7 @@ async function findCheckable(locator, context) {
|
|
|
3602
3580
|
async function proceedIsChecked(assertType, option) {
|
|
3603
3581
|
let els = await findCheckable.call(this, option)
|
|
3604
3582
|
assertElementExists(els, option, 'Checkable')
|
|
3605
|
-
els = await Promise.all(els.map(
|
|
3583
|
+
els = await Promise.all(els.map(el => el.isChecked()))
|
|
3606
3584
|
const selected = els.reduce((prev, cur) => prev || cur)
|
|
3607
3585
|
return truth(`checkable ${option}`, 'to be checked')[assertType](selected)
|
|
3608
3586
|
}
|
|
@@ -3634,10 +3612,10 @@ async function proceedSeeInField(assertType, field, value) {
|
|
|
3634
3612
|
const els = await findFields.call(this, field)
|
|
3635
3613
|
assertElementExists(els, field, 'Field')
|
|
3636
3614
|
const el = els[0]
|
|
3637
|
-
const tag = await el.evaluate(
|
|
3615
|
+
const tag = await el.evaluate(e => e.tagName)
|
|
3638
3616
|
const fieldType = await el.getAttribute('type')
|
|
3639
3617
|
|
|
3640
|
-
const proceedMultiple = async
|
|
3618
|
+
const proceedMultiple = async elements => {
|
|
3641
3619
|
const fields = Array.isArray(elements) ? elements : [elements]
|
|
3642
3620
|
|
|
3643
3621
|
const elementValues = []
|
|
@@ -3651,7 +3629,7 @@ async function proceedSeeInField(assertType, field, value) {
|
|
|
3651
3629
|
if (assertType === 'assert') {
|
|
3652
3630
|
equals(`select option by ${field}`)[assertType](true, elementValues.length > 0)
|
|
3653
3631
|
}
|
|
3654
|
-
elementValues.forEach(
|
|
3632
|
+
elementValues.forEach(val => stringIncludes(`fields by ${field}`)[assertType](value, val))
|
|
3655
3633
|
}
|
|
3656
3634
|
}
|
|
3657
3635
|
|
|
@@ -3738,6 +3716,8 @@ function isFrameLocator(locator) {
|
|
|
3738
3716
|
}
|
|
3739
3717
|
|
|
3740
3718
|
function assertElementExists(res, locator, prefix, suffix) {
|
|
3719
|
+
// if element text is an empty string, just exit this check
|
|
3720
|
+
if (typeof res === 'string' && res === '') return
|
|
3741
3721
|
if (!res || res.length === 0) {
|
|
3742
3722
|
throw new ElementNotFound(locator, prefix, suffix)
|
|
3743
3723
|
}
|
|
@@ -3774,12 +3754,9 @@ async function targetCreatedHandler(page) {
|
|
|
3774
3754
|
this.contextLocator = null
|
|
3775
3755
|
})
|
|
3776
3756
|
})
|
|
3777
|
-
page.on('console',
|
|
3757
|
+
page.on('console', msg => {
|
|
3778
3758
|
if (!consoleLogStore.includes(msg) && this.options.ignoreLog && !this.options.ignoreLog.includes(msg.type())) {
|
|
3779
|
-
this.debugSection(
|
|
3780
|
-
`Browser:${ucfirst(msg.type())}`,
|
|
3781
|
-
((msg.text && msg.text()) || msg._text || '') + msg.args().join(' '),
|
|
3782
|
-
)
|
|
3759
|
+
this.debugSection(`Browser:${ucfirst(msg.type())}`, ((msg.text && msg.text()) || msg._text || '') + msg.args().join(' '))
|
|
3783
3760
|
}
|
|
3784
3761
|
consoleLogStore.add(msg)
|
|
3785
3762
|
})
|
|
@@ -3813,7 +3790,6 @@ function parseWindowSize(windowSize) {
|
|
|
3813
3790
|
// List of key values to key definitions
|
|
3814
3791
|
// https://github.com/puppeteer/puppeteer/blob/v1.20.0/lib/USKeyboardLayout.js
|
|
3815
3792
|
const keyDefinitionMap = {
|
|
3816
|
-
/* eslint-disable quote-props */
|
|
3817
3793
|
0: 'Digit0',
|
|
3818
3794
|
1: 'Digit1',
|
|
3819
3795
|
2: 'Digit2',
|
|
@@ -3861,7 +3837,6 @@ const keyDefinitionMap = {
|
|
|
3861
3837
|
'\\': 'Backslash',
|
|
3862
3838
|
']': 'BracketRight',
|
|
3863
3839
|
"'": 'Quote',
|
|
3864
|
-
/* eslint-enable quote-props */
|
|
3865
3840
|
}
|
|
3866
3841
|
|
|
3867
3842
|
function getNormalizedKey(key) {
|
|
@@ -3889,7 +3864,7 @@ async function refreshContextSession() {
|
|
|
3889
3864
|
const contexts = await this.browser.contexts()
|
|
3890
3865
|
contexts.shift()
|
|
3891
3866
|
|
|
3892
|
-
await Promise.all(contexts.map(
|
|
3867
|
+
await Promise.all(contexts.map(c => c.close()))
|
|
3893
3868
|
} catch (e) {
|
|
3894
3869
|
console.log(e)
|
|
3895
3870
|
}
|
|
@@ -3908,10 +3883,10 @@ async function refreshContextSession() {
|
|
|
3908
3883
|
const currentUrl = await this.grabCurrentUrl()
|
|
3909
3884
|
|
|
3910
3885
|
if (currentUrl.startsWith('http')) {
|
|
3911
|
-
await this.executeScript('localStorage.clear();').catch(
|
|
3886
|
+
await this.executeScript('localStorage.clear();').catch(err => {
|
|
3912
3887
|
if (!(err.message.indexOf("Storage is disabled inside 'data:' URLs.") > -1)) throw err
|
|
3913
3888
|
})
|
|
3914
|
-
await this.executeScript('sessionStorage.clear();').catch(
|
|
3889
|
+
await this.executeScript('sessionStorage.clear();').catch(err => {
|
|
3915
3890
|
if (!(err.message.indexOf("Storage is disabled inside 'data:' URLs.") > -1)) throw err
|
|
3916
3891
|
})
|
|
3917
3892
|
}
|
|
@@ -3935,17 +3910,37 @@ function saveVideoForPage(page, name) {
|
|
|
3935
3910
|
async function saveTraceForContext(context, name) {
|
|
3936
3911
|
if (!context) return
|
|
3937
3912
|
if (!context.tracing) return
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3913
|
+
try {
|
|
3914
|
+
const fileName = `${`${global.output_dir}${pathSeparator}trace${pathSeparator}${uuidv4()}_${clearString(name)}`.slice(0, 245)}.zip`
|
|
3915
|
+
await context.tracing.stop({ path: fileName })
|
|
3916
|
+
return fileName
|
|
3917
|
+
} catch (err) {
|
|
3918
|
+
// Handle the case where tracing was not started or context is invalid
|
|
3919
|
+
if (err.message && err.message.includes('Must start tracing before stopping')) {
|
|
3920
|
+
// Tracing was never started on this context, silently skip
|
|
3921
|
+
return null
|
|
3922
|
+
}
|
|
3923
|
+
throw err
|
|
3924
|
+
}
|
|
3941
3925
|
}
|
|
3942
3926
|
|
|
3943
3927
|
async function highlightActiveElement(element) {
|
|
3944
|
-
if (this.options.highlightElement &&
|
|
3945
|
-
await element.evaluate(
|
|
3928
|
+
if ((this.options.highlightElement || store.onPause) && store.debugMode) {
|
|
3929
|
+
await element.evaluate(el => {
|
|
3946
3930
|
const prevStyle = el.style.boxShadow
|
|
3947
|
-
el.style.boxShadow = '0px 0px 4px 3px rgba(
|
|
3931
|
+
el.style.boxShadow = '0px 0px 4px 3px rgba(147, 51, 234, 0.8)' // Bright purple that works on both dark/light modes
|
|
3948
3932
|
setTimeout(() => (el.style.boxShadow = prevStyle), 2000)
|
|
3949
3933
|
})
|
|
3950
3934
|
}
|
|
3951
3935
|
}
|
|
3936
|
+
|
|
3937
|
+
async function elToString(el, numberOfElements) {
|
|
3938
|
+
const html = await el.evaluate(node => node.outerHTML)
|
|
3939
|
+
return (
|
|
3940
|
+
html
|
|
3941
|
+
.replace(/\n/g, '')
|
|
3942
|
+
.replace(/\s+/g, ' ')
|
|
3943
|
+
.substring(0, 100 / numberOfElements)
|
|
3944
|
+
.trim() + '...'
|
|
3945
|
+
)
|
|
3946
|
+
}
|