codeceptjs 4.0.0-beta.4 → 4.0.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +139 -87
- package/lib/command/check.js +201 -0
- package/lib/command/configMigrate.js +2 -4
- package/lib/command/definitions.js +8 -26
- 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 +262 -220
- 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 +3 -6
- package/lib/helper/Appium.js +47 -51
- package/lib/helper/FileSystem.js +3 -3
- package/lib/helper/GraphQLDataFactory.js +3 -3
- package/lib/helper/JSONResponse.js +75 -37
- package/lib/helper/Mochawesome.js +31 -9
- package/lib/helper/Nightmare.js +35 -53
- package/lib/helper/Playwright.js +262 -267
- package/lib/helper/Protractor.js +54 -77
- package/lib/helper/Puppeteer.js +246 -260
- package/lib/helper/REST.js +5 -17
- package/lib/helper/TestCafe.js +21 -44
- package/lib/helper/WebDriver.js +151 -170
- package/lib/helper/extras/Popup.js +22 -22
- package/lib/helper/testcafe/testcafe-utils.js +26 -27
- 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/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 +82 -62
- 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 -19
- 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 -1
- 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 -276
- package/package.json +76 -70
- package/translations/de-DE.js +4 -3
- package/translations/fr-FR.js +4 -3
- 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 +188 -186
- package/typings/promiseBasedTypes.d.ts +18 -705
- package/typings/types.d.ts +301 -804
- package/lib/cli.js +0 -256
- package/lib/helper/ExpectHelper.js +0 -391
- 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
|
|
|
@@ -1287,7 +1268,22 @@ class Playwright extends Helper {
|
|
|
1287
1268
|
|
|
1288
1269
|
if (this.frame) return findElements(this.frame, locator)
|
|
1289
1270
|
|
|
1290
|
-
|
|
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
|
|
1291
1287
|
}
|
|
1292
1288
|
|
|
1293
1289
|
/**
|
|
@@ -1346,7 +1342,8 @@ class Playwright extends Helper {
|
|
|
1346
1342
|
*
|
|
1347
1343
|
*/
|
|
1348
1344
|
async grabWebElements(locator) {
|
|
1349
|
-
|
|
1345
|
+
const elements = await this._locate(locator)
|
|
1346
|
+
return elements.map(element => new WebElement(element, this))
|
|
1350
1347
|
}
|
|
1351
1348
|
|
|
1352
1349
|
/**
|
|
@@ -1354,7 +1351,8 @@ class Playwright extends Helper {
|
|
|
1354
1351
|
*
|
|
1355
1352
|
*/
|
|
1356
1353
|
async grabWebElement(locator) {
|
|
1357
|
-
|
|
1354
|
+
const element = await this._locateElement(locator)
|
|
1355
|
+
return new WebElement(element, this)
|
|
1358
1356
|
}
|
|
1359
1357
|
|
|
1360
1358
|
/**
|
|
@@ -1437,10 +1435,10 @@ class Playwright extends Helper {
|
|
|
1437
1435
|
*/
|
|
1438
1436
|
async closeOtherTabs() {
|
|
1439
1437
|
const pages = await this.browserContext.pages()
|
|
1440
|
-
const otherPages = pages.filter(
|
|
1438
|
+
const otherPages = pages.filter(page => page !== this.page)
|
|
1441
1439
|
if (otherPages.length) {
|
|
1442
1440
|
this.debug(`Closing ${otherPages.length} tabs`)
|
|
1443
|
-
return Promise.all(otherPages.map(
|
|
1441
|
+
return Promise.all(otherPages.map(p => p.close()))
|
|
1444
1442
|
}
|
|
1445
1443
|
return Promise.resolve()
|
|
1446
1444
|
}
|
|
@@ -1483,9 +1481,9 @@ class Playwright extends Helper {
|
|
|
1483
1481
|
*/
|
|
1484
1482
|
async seeElement(locator) {
|
|
1485
1483
|
let els = await this._locate(locator)
|
|
1486
|
-
els = await Promise.all(els.map(
|
|
1484
|
+
els = await Promise.all(els.map(el => el.isVisible()))
|
|
1487
1485
|
try {
|
|
1488
|
-
return empty('visible elements').negate(els.filter(
|
|
1486
|
+
return empty('visible elements').negate(els.filter(v => v).fill('ELEMENT'))
|
|
1489
1487
|
} catch (e) {
|
|
1490
1488
|
dontSeeElementError(locator)
|
|
1491
1489
|
}
|
|
@@ -1497,9 +1495,9 @@ class Playwright extends Helper {
|
|
|
1497
1495
|
*/
|
|
1498
1496
|
async dontSeeElement(locator) {
|
|
1499
1497
|
let els = await this._locate(locator)
|
|
1500
|
-
els = await Promise.all(els.map(
|
|
1498
|
+
els = await Promise.all(els.map(el => el.isVisible()))
|
|
1501
1499
|
try {
|
|
1502
|
-
return empty('visible elements').assert(els.filter(
|
|
1500
|
+
return empty('visible elements').assert(els.filter(v => v).fill('ELEMENT'))
|
|
1503
1501
|
} catch (e) {
|
|
1504
1502
|
seeElementError(locator)
|
|
1505
1503
|
}
|
|
@@ -1511,7 +1509,7 @@ class Playwright extends Helper {
|
|
|
1511
1509
|
async seeElementInDOM(locator) {
|
|
1512
1510
|
const els = await this._locate(locator)
|
|
1513
1511
|
try {
|
|
1514
|
-
return empty('elements on page').negate(els.filter(
|
|
1512
|
+
return empty('elements on page').negate(els.filter(v => v).fill('ELEMENT'))
|
|
1515
1513
|
} catch (e) {
|
|
1516
1514
|
dontSeeElementInDOMError(locator)
|
|
1517
1515
|
}
|
|
@@ -1523,7 +1521,7 @@ class Playwright extends Helper {
|
|
|
1523
1521
|
async dontSeeElementInDOM(locator) {
|
|
1524
1522
|
const els = await this._locate(locator)
|
|
1525
1523
|
try {
|
|
1526
|
-
return empty('elements on a page').assert(els.filter(
|
|
1524
|
+
return empty('elements on a page').assert(els.filter(v => v).fill('ELEMENT'))
|
|
1527
1525
|
} catch (e) {
|
|
1528
1526
|
seeElementInDOMError(locator)
|
|
1529
1527
|
}
|
|
@@ -1547,7 +1545,7 @@ class Playwright extends Helper {
|
|
|
1547
1545
|
* @return {Promise<void>}
|
|
1548
1546
|
*/
|
|
1549
1547
|
async handleDownloads(fileName) {
|
|
1550
|
-
this.page.waitForEvent('download').then(async
|
|
1548
|
+
this.page.waitForEvent('download').then(async download => {
|
|
1551
1549
|
const filePath = await download.path()
|
|
1552
1550
|
fileName = fileName || `downloads/${path.basename(filePath)}`
|
|
1553
1551
|
|
|
@@ -1741,6 +1739,7 @@ class Playwright extends Helper {
|
|
|
1741
1739
|
const el = els[0]
|
|
1742
1740
|
|
|
1743
1741
|
await el.clear()
|
|
1742
|
+
if (store.debugMode) this.debugSection('Focused', await elToString(el, 1))
|
|
1744
1743
|
|
|
1745
1744
|
await highlightActiveElement.call(this, el)
|
|
1746
1745
|
|
|
@@ -1852,8 +1851,8 @@ class Playwright extends Helper {
|
|
|
1852
1851
|
*/
|
|
1853
1852
|
async grabNumberOfVisibleElements(locator) {
|
|
1854
1853
|
let els = await this._locate(locator)
|
|
1855
|
-
els = await Promise.all(els.map(
|
|
1856
|
-
return els.filter(
|
|
1854
|
+
els = await Promise.all(els.map(el => el.isVisible()))
|
|
1855
|
+
return els.filter(v => v).length
|
|
1857
1856
|
}
|
|
1858
1857
|
|
|
1859
1858
|
/**
|
|
@@ -1963,9 +1962,7 @@ class Playwright extends Helper {
|
|
|
1963
1962
|
*/
|
|
1964
1963
|
async seeNumberOfElements(locator, num) {
|
|
1965
1964
|
const elements = await this._locate(locator)
|
|
1966
|
-
return equals(
|
|
1967
|
-
`expected number of elements (${new Locator(locator)}) is ${num}, but found ${elements.length}`,
|
|
1968
|
-
).assert(elements.length, num)
|
|
1965
|
+
return equals(`expected number of elements (${new Locator(locator)}) is ${num}, but found ${elements.length}`).assert(elements.length, num)
|
|
1969
1966
|
}
|
|
1970
1967
|
|
|
1971
1968
|
/**
|
|
@@ -1975,10 +1972,7 @@ class Playwright extends Helper {
|
|
|
1975
1972
|
*/
|
|
1976
1973
|
async seeNumberOfVisibleElements(locator, num) {
|
|
1977
1974
|
const res = await this.grabNumberOfVisibleElements(locator)
|
|
1978
|
-
return equals(`expected number of visible elements (${new Locator(locator)}) is ${num}, but found ${res}`).assert(
|
|
1979
|
-
res,
|
|
1980
|
-
num,
|
|
1981
|
-
)
|
|
1975
|
+
return equals(`expected number of visible elements (${new Locator(locator)}) is ${num}, but found ${res}`).assert(res, num)
|
|
1982
1976
|
}
|
|
1983
1977
|
|
|
1984
1978
|
/**
|
|
@@ -1997,7 +1991,7 @@ class Playwright extends Helper {
|
|
|
1997
1991
|
*/
|
|
1998
1992
|
async seeCookie(name) {
|
|
1999
1993
|
const cookies = await this.browserContext.cookies()
|
|
2000
|
-
empty(`cookie ${name} to be set`).negate(cookies.filter(
|
|
1994
|
+
empty(`cookie ${name} to be set`).negate(cookies.filter(c => c.name === name))
|
|
2001
1995
|
}
|
|
2002
1996
|
|
|
2003
1997
|
/**
|
|
@@ -2005,7 +1999,7 @@ class Playwright extends Helper {
|
|
|
2005
1999
|
*/
|
|
2006
2000
|
async dontSeeCookie(name) {
|
|
2007
2001
|
const cookies = await this.browserContext.cookies()
|
|
2008
|
-
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))
|
|
2009
2003
|
}
|
|
2010
2004
|
|
|
2011
2005
|
/**
|
|
@@ -2016,17 +2010,18 @@ class Playwright extends Helper {
|
|
|
2016
2010
|
async grabCookie(name) {
|
|
2017
2011
|
const cookies = await this.browserContext.cookies()
|
|
2018
2012
|
if (!name) return cookies
|
|
2019
|
-
const cookie = cookies.filter(
|
|
2013
|
+
const cookie = cookies.filter(c => c.name === name)
|
|
2020
2014
|
if (cookie[0]) return cookie[0]
|
|
2021
2015
|
}
|
|
2022
2016
|
|
|
2023
2017
|
/**
|
|
2024
2018
|
* {{> clearCookie }}
|
|
2025
2019
|
*/
|
|
2026
|
-
async clearCookie() {
|
|
2027
|
-
// Playwright currently doesn't support to delete a certain cookie
|
|
2028
|
-
// https://github.com/microsoft/playwright/blob/main/docs/src/api/class-browsercontext.md#async-method-browsercontextclearcookies
|
|
2020
|
+
async clearCookie(cookieName) {
|
|
2029
2021
|
if (!this.browserContext) return
|
|
2022
|
+
if (cookieName) {
|
|
2023
|
+
return this.browserContext.clearCookies({ name: cookieName })
|
|
2024
|
+
}
|
|
2030
2025
|
return this.browserContext.clearCookies()
|
|
2031
2026
|
}
|
|
2032
2027
|
|
|
@@ -2098,7 +2093,7 @@ class Playwright extends Helper {
|
|
|
2098
2093
|
const els = await this._locate(locator)
|
|
2099
2094
|
const texts = []
|
|
2100
2095
|
for (const el of els) {
|
|
2101
|
-
texts.push(await
|
|
2096
|
+
texts.push(await el.innerText())
|
|
2102
2097
|
}
|
|
2103
2098
|
this.debug(`Matched ${els.length} elements`)
|
|
2104
2099
|
return texts
|
|
@@ -2120,7 +2115,7 @@ class Playwright extends Helper {
|
|
|
2120
2115
|
async grabValueFromAll(locator) {
|
|
2121
2116
|
const els = await findFields.call(this, locator)
|
|
2122
2117
|
this.debug(`Matched ${els.length} elements`)
|
|
2123
|
-
return Promise.all(els.map(
|
|
2118
|
+
return Promise.all(els.map(el => el.inputValue()))
|
|
2124
2119
|
}
|
|
2125
2120
|
|
|
2126
2121
|
/**
|
|
@@ -2139,7 +2134,7 @@ class Playwright extends Helper {
|
|
|
2139
2134
|
async grabHTMLFromAll(locator) {
|
|
2140
2135
|
const els = await this._locate(locator)
|
|
2141
2136
|
this.debug(`Matched ${els.length} elements`)
|
|
2142
|
-
return Promise.all(els.map(
|
|
2137
|
+
return Promise.all(els.map(el => el.innerHTML()))
|
|
2143
2138
|
}
|
|
2144
2139
|
|
|
2145
2140
|
/**
|
|
@@ -2160,11 +2155,7 @@ class Playwright extends Helper {
|
|
|
2160
2155
|
async grabCssPropertyFromAll(locator, cssProperty) {
|
|
2161
2156
|
const els = await this._locate(locator)
|
|
2162
2157
|
this.debug(`Matched ${els.length} elements`)
|
|
2163
|
-
const cssValues = await Promise.all(
|
|
2164
|
-
els.map((el) =>
|
|
2165
|
-
el.evaluate((el, cssProperty) => getComputedStyle(el).getPropertyValue(cssProperty), cssProperty),
|
|
2166
|
-
),
|
|
2167
|
-
)
|
|
2158
|
+
const cssValues = await Promise.all(els.map(el => el.evaluate((el, cssProperty) => getComputedStyle(el).getPropertyValue(cssProperty), cssProperty)))
|
|
2168
2159
|
|
|
2169
2160
|
return cssValues
|
|
2170
2161
|
}
|
|
@@ -2192,19 +2183,16 @@ class Playwright extends Helper {
|
|
|
2192
2183
|
}
|
|
2193
2184
|
}
|
|
2194
2185
|
|
|
2195
|
-
const values = Object.keys(cssPropertiesCamelCase).map(
|
|
2186
|
+
const values = Object.keys(cssPropertiesCamelCase).map(key => cssPropertiesCamelCase[key])
|
|
2196
2187
|
if (!Array.isArray(props)) props = [props]
|
|
2197
2188
|
let chunked = chunkArray(props, values.length)
|
|
2198
|
-
chunked = chunked.filter(
|
|
2189
|
+
chunked = chunked.filter(val => {
|
|
2199
2190
|
for (let i = 0; i < val.length; ++i) {
|
|
2200
|
-
// eslint-disable-next-line eqeqeq
|
|
2201
2191
|
if (val[i] != values[i]) return false
|
|
2202
2192
|
}
|
|
2203
2193
|
return true
|
|
2204
2194
|
})
|
|
2205
|
-
return equals(
|
|
2206
|
-
`all elements (${new Locator(locator)}) to have CSS property ${JSON.stringify(cssProperties)}`,
|
|
2207
|
-
).assert(chunked.length, elemAmount)
|
|
2195
|
+
return equals(`all elements (${new Locator(locator)}) to have CSS property ${JSON.stringify(cssProperties)}`).assert(chunked.length, elemAmount)
|
|
2208
2196
|
}
|
|
2209
2197
|
|
|
2210
2198
|
/**
|
|
@@ -2217,16 +2205,16 @@ class Playwright extends Helper {
|
|
|
2217
2205
|
|
|
2218
2206
|
const elemAmount = res.length
|
|
2219
2207
|
const commands = []
|
|
2220
|
-
res.forEach(
|
|
2221
|
-
Object.keys(attributes).forEach(
|
|
2208
|
+
res.forEach(el => {
|
|
2209
|
+
Object.keys(attributes).forEach(prop => {
|
|
2222
2210
|
commands.push(el.evaluate((el, attr) => el[attr] || el.getAttribute(attr), prop))
|
|
2223
2211
|
})
|
|
2224
2212
|
})
|
|
2225
2213
|
let attrs = await Promise.all(commands)
|
|
2226
|
-
const values = Object.keys(attributes).map(
|
|
2214
|
+
const values = Object.keys(attributes).map(key => attributes[key])
|
|
2227
2215
|
if (!Array.isArray(attrs)) attrs = [attrs]
|
|
2228
2216
|
let chunked = chunkArray(attrs, values.length)
|
|
2229
|
-
chunked = chunked.filter(
|
|
2217
|
+
chunked = chunked.filter(val => {
|
|
2230
2218
|
for (let i = 0; i < val.length; ++i) {
|
|
2231
2219
|
// the attribute could be a boolean
|
|
2232
2220
|
if (typeof val[i] === 'boolean') return val[i] === values[i]
|
|
@@ -2235,10 +2223,7 @@ class Playwright extends Helper {
|
|
|
2235
2223
|
}
|
|
2236
2224
|
return true
|
|
2237
2225
|
})
|
|
2238
|
-
return equals(`all elements (${new Locator(locator)}) to have attributes ${JSON.stringify(attributes)}`).assert(
|
|
2239
|
-
chunked.length,
|
|
2240
|
-
elemAmount,
|
|
2241
|
-
)
|
|
2226
|
+
return equals(`all elements (${new Locator(locator)}) to have attributes ${JSON.stringify(attributes)}`).assert(chunked.length, elemAmount)
|
|
2242
2227
|
}
|
|
2243
2228
|
|
|
2244
2229
|
/**
|
|
@@ -2311,7 +2296,7 @@ class Playwright extends Helper {
|
|
|
2311
2296
|
const fullPageOption = fullPage || this.options.fullPageScreenshots
|
|
2312
2297
|
let outputFile = screenshotOutputFolder(fileName)
|
|
2313
2298
|
|
|
2314
|
-
this.
|
|
2299
|
+
this.debugSection('Screenshot', relativeDir(outputFile))
|
|
2315
2300
|
|
|
2316
2301
|
await this.page.screenshot({
|
|
2317
2302
|
path: outputFile,
|
|
@@ -2324,7 +2309,7 @@ class Playwright extends Helper {
|
|
|
2324
2309
|
const activeSessionPage = this.sessionPages[sessionName]
|
|
2325
2310
|
outputFile = screenshotOutputFolder(`${sessionName}_${fileName}`)
|
|
2326
2311
|
|
|
2327
|
-
this.
|
|
2312
|
+
this.debugSection('Screenshot', `${sessionName} - ${relativeDir(outputFile)}`)
|
|
2328
2313
|
|
|
2329
2314
|
if (activeSessionPage) {
|
|
2330
2315
|
await activeSessionPage.screenshot({
|
|
@@ -2358,9 +2343,7 @@ class Playwright extends Helper {
|
|
|
2358
2343
|
method = method.toLowerCase()
|
|
2359
2344
|
const allowedMethods = ['get', 'post', 'patch', 'head', 'fetch', 'delete']
|
|
2360
2345
|
if (!allowedMethods.includes(method)) {
|
|
2361
|
-
throw new Error(
|
|
2362
|
-
`Method ${method} is not allowed, use the one from a list ${allowedMethods} or switch to using REST helper`,
|
|
2363
|
-
)
|
|
2346
|
+
throw new Error(`Method ${method} is not allowed, use the one from a list ${allowedMethods} or switch to using REST helper`)
|
|
2364
2347
|
}
|
|
2365
2348
|
|
|
2366
2349
|
if (url.startsWith('/')) {
|
|
@@ -2397,21 +2380,19 @@ class Playwright extends Helper {
|
|
|
2397
2380
|
if (this.options.recordVideo && this.page && this.page.video()) {
|
|
2398
2381
|
test.artifacts.video = saveVideoForPage(this.page, `${test.title}.failed`)
|
|
2399
2382
|
for (const sessionName in this.sessionPages) {
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
`${test.title}_${sessionName}.failed`,
|
|
2403
|
-
)
|
|
2383
|
+
if (sessionName === '') continue
|
|
2384
|
+
test.artifacts[`video_${sessionName}`] = saveVideoForPage(this.sessionPages[sessionName], `${sessionName}_${test.title}.failed`)
|
|
2404
2385
|
}
|
|
2405
2386
|
}
|
|
2406
2387
|
|
|
2407
2388
|
if (this.options.trace) {
|
|
2408
2389
|
test.artifacts.trace = await saveTraceForContext(this.browserContext, `${test.title}.failed`)
|
|
2409
2390
|
for (const sessionName in this.sessionPages) {
|
|
2410
|
-
if (
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
)
|
|
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`)
|
|
2415
2396
|
}
|
|
2416
2397
|
}
|
|
2417
2398
|
|
|
@@ -2425,16 +2406,14 @@ class Playwright extends Helper {
|
|
|
2425
2406
|
if (this.options.keepVideoForPassedTests) {
|
|
2426
2407
|
test.artifacts.video = saveVideoForPage(this.page, `${test.title}.passed`)
|
|
2427
2408
|
for (const sessionName of Object.keys(this.sessionPages)) {
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
`${test.title}_${sessionName}.passed`,
|
|
2431
|
-
)
|
|
2409
|
+
if (sessionName === '') continue
|
|
2410
|
+
test.artifacts[`video_${sessionName}`] = saveVideoForPage(this.sessionPages[sessionName], `${sessionName}_${test.title}.passed`)
|
|
2432
2411
|
}
|
|
2433
2412
|
} else {
|
|
2434
2413
|
this.page
|
|
2435
2414
|
.video()
|
|
2436
2415
|
.delete()
|
|
2437
|
-
.catch(
|
|
2416
|
+
.catch(e => {})
|
|
2438
2417
|
}
|
|
2439
2418
|
}
|
|
2440
2419
|
|
|
@@ -2443,11 +2422,11 @@ class Playwright extends Helper {
|
|
|
2443
2422
|
if (this.options.trace) {
|
|
2444
2423
|
test.artifacts.trace = await saveTraceForContext(this.browserContext, `${test.title}.passed`)
|
|
2445
2424
|
for (const sessionName in this.sessionPages) {
|
|
2446
|
-
if (
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
)
|
|
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`)
|
|
2451
2430
|
}
|
|
2452
2431
|
}
|
|
2453
2432
|
} else {
|
|
@@ -2464,7 +2443,7 @@ class Playwright extends Helper {
|
|
|
2464
2443
|
* {{> wait }}
|
|
2465
2444
|
*/
|
|
2466
2445
|
async wait(sec) {
|
|
2467
|
-
return new Promise(
|
|
2446
|
+
return new Promise(done => {
|
|
2468
2447
|
setTimeout(done, sec * 1000)
|
|
2469
2448
|
})
|
|
2470
2449
|
}
|
|
@@ -2480,20 +2459,18 @@ class Playwright extends Helper {
|
|
|
2480
2459
|
const context = await this._getContext()
|
|
2481
2460
|
if (!locator.isXPath()) {
|
|
2482
2461
|
const valueFn = function ([locator]) {
|
|
2483
|
-
return Array.from(document.querySelectorAll(locator)).filter(
|
|
2462
|
+
return Array.from(document.querySelectorAll(locator)).filter(el => !el.disabled).length > 0
|
|
2484
2463
|
}
|
|
2485
2464
|
waiter = context.waitForFunction(valueFn, [locator.value], { timeout: waitTimeout })
|
|
2486
2465
|
} else {
|
|
2487
2466
|
const enabledFn = function ([locator, $XPath]) {
|
|
2488
|
-
eval($XPath)
|
|
2489
|
-
return $XPath(null, locator).filter(
|
|
2467
|
+
eval($XPath)
|
|
2468
|
+
return $XPath(null, locator).filter(el => !el.disabled).length > 0
|
|
2490
2469
|
}
|
|
2491
2470
|
waiter = context.waitForFunction(enabledFn, [locator.value, $XPath.toString()], { timeout: waitTimeout })
|
|
2492
2471
|
}
|
|
2493
|
-
return waiter.catch(
|
|
2494
|
-
throw new Error(
|
|
2495
|
-
`element (${locator.toString()}) still not enabled after ${waitTimeout / 1000} sec\n${err.message}`,
|
|
2496
|
-
)
|
|
2472
|
+
return waiter.catch(err => {
|
|
2473
|
+
throw new Error(`element (${locator.toString()}) still not enabled after ${waitTimeout / 1000} sec\n${err.message}`)
|
|
2497
2474
|
})
|
|
2498
2475
|
}
|
|
2499
2476
|
|
|
@@ -2508,20 +2485,18 @@ class Playwright extends Helper {
|
|
|
2508
2485
|
const context = await this._getContext()
|
|
2509
2486
|
if (!locator.isXPath()) {
|
|
2510
2487
|
const valueFn = function ([locator]) {
|
|
2511
|
-
return Array.from(document.querySelectorAll(locator)).filter(
|
|
2488
|
+
return Array.from(document.querySelectorAll(locator)).filter(el => el.disabled).length > 0
|
|
2512
2489
|
}
|
|
2513
2490
|
waiter = context.waitForFunction(valueFn, [locator.value], { timeout: waitTimeout })
|
|
2514
2491
|
} else {
|
|
2515
2492
|
const disabledFn = function ([locator, $XPath]) {
|
|
2516
|
-
eval($XPath)
|
|
2517
|
-
return $XPath(null, locator).filter(
|
|
2493
|
+
eval($XPath)
|
|
2494
|
+
return $XPath(null, locator).filter(el => el.disabled).length > 0
|
|
2518
2495
|
}
|
|
2519
2496
|
waiter = context.waitForFunction(disabledFn, [locator.value, $XPath.toString()], { timeout: waitTimeout })
|
|
2520
2497
|
}
|
|
2521
|
-
return waiter.catch(
|
|
2522
|
-
throw new Error(
|
|
2523
|
-
`element (${locator.toString()}) is still enabled after ${waitTimeout / 1000} sec\n${err.message}`,
|
|
2524
|
-
)
|
|
2498
|
+
return waiter.catch(err => {
|
|
2499
|
+
throw new Error(`element (${locator.toString()}) is still enabled after ${waitTimeout / 1000} sec\n${err.message}`)
|
|
2525
2500
|
})
|
|
2526
2501
|
}
|
|
2527
2502
|
|
|
@@ -2536,26 +2511,21 @@ class Playwright extends Helper {
|
|
|
2536
2511
|
const context = await this._getContext()
|
|
2537
2512
|
if (!locator.isXPath()) {
|
|
2538
2513
|
const valueFn = function ([locator, value]) {
|
|
2539
|
-
return (
|
|
2540
|
-
Array.from(document.querySelectorAll(locator)).filter((el) => (el.value || '').indexOf(value) !== -1).length >
|
|
2541
|
-
0
|
|
2542
|
-
)
|
|
2514
|
+
return Array.from(document.querySelectorAll(locator)).filter(el => (el.value || '').indexOf(value) !== -1).length > 0
|
|
2543
2515
|
}
|
|
2544
2516
|
waiter = context.waitForFunction(valueFn, [locator.value, value], { timeout: waitTimeout })
|
|
2545
2517
|
} else {
|
|
2546
2518
|
const valueFn = function ([locator, $XPath, value]) {
|
|
2547
|
-
eval($XPath)
|
|
2548
|
-
return $XPath(null, locator).filter(
|
|
2519
|
+
eval($XPath)
|
|
2520
|
+
return $XPath(null, locator).filter(el => (el.value || '').indexOf(value) !== -1).length > 0
|
|
2549
2521
|
}
|
|
2550
2522
|
waiter = context.waitForFunction(valueFn, [locator.value, $XPath.toString(), value], {
|
|
2551
2523
|
timeout: waitTimeout,
|
|
2552
2524
|
})
|
|
2553
2525
|
}
|
|
2554
|
-
return waiter.catch(
|
|
2526
|
+
return waiter.catch(err => {
|
|
2555
2527
|
const loc = locator.toString()
|
|
2556
|
-
throw new Error(
|
|
2557
|
-
`element (${loc}) is not in DOM or there is no element(${loc}) with value "${value}" after ${waitTimeout / 1000} sec\n${err.message}`,
|
|
2558
|
-
)
|
|
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}`)
|
|
2559
2529
|
})
|
|
2560
2530
|
}
|
|
2561
2531
|
|
|
@@ -2575,22 +2545,20 @@ class Playwright extends Helper {
|
|
|
2575
2545
|
if (!els || els.length === 0) {
|
|
2576
2546
|
return false
|
|
2577
2547
|
}
|
|
2578
|
-
return Array.prototype.filter.call(els,
|
|
2548
|
+
return Array.prototype.filter.call(els, el => el.offsetParent !== null).length === num
|
|
2579
2549
|
}
|
|
2580
2550
|
waiter = context.waitForFunction(visibleFn, [locator.value, num], { timeout: waitTimeout })
|
|
2581
2551
|
} else {
|
|
2582
2552
|
const visibleFn = function ([locator, $XPath, num]) {
|
|
2583
|
-
eval($XPath)
|
|
2584
|
-
return $XPath(null, locator).filter(
|
|
2553
|
+
eval($XPath)
|
|
2554
|
+
return $XPath(null, locator).filter(el => el.offsetParent !== null).length === num
|
|
2585
2555
|
}
|
|
2586
2556
|
waiter = context.waitForFunction(visibleFn, [locator.value, $XPath.toString(), num], {
|
|
2587
2557
|
timeout: waitTimeout,
|
|
2588
2558
|
})
|
|
2589
2559
|
}
|
|
2590
|
-
return waiter.catch(
|
|
2591
|
-
throw new Error(
|
|
2592
|
-
`The number of elements (${locator.toString()}) is not ${num} after ${waitTimeout / 1000} sec\n${err.message}`,
|
|
2593
|
-
)
|
|
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}`)
|
|
2594
2562
|
})
|
|
2595
2563
|
}
|
|
2596
2564
|
|
|
@@ -2598,9 +2566,7 @@ class Playwright extends Helper {
|
|
|
2598
2566
|
* {{> waitForClickable }}
|
|
2599
2567
|
*/
|
|
2600
2568
|
async waitForClickable(locator, waitTimeout) {
|
|
2601
|
-
console.log(
|
|
2602
|
-
'I.waitForClickable is DEPRECATED: This is no longer needed, Playwright automatically waits for element to be clickable',
|
|
2603
|
-
)
|
|
2569
|
+
console.log('I.waitForClickable is DEPRECATED: This is no longer needed, Playwright automatically waits for element to be clickable')
|
|
2604
2570
|
console.log('Remove usage of this function')
|
|
2605
2571
|
}
|
|
2606
2572
|
|
|
@@ -2616,9 +2582,7 @@ class Playwright extends Helper {
|
|
|
2616
2582
|
try {
|
|
2617
2583
|
await context.locator(buildLocatorString(locator)).first().waitFor({ timeout: waitTimeout, state: 'attached' })
|
|
2618
2584
|
} catch (e) {
|
|
2619
|
-
throw new Error(
|
|
2620
|
-
`element (${locator.toString()}) still not present on page after ${waitTimeout / 1000} sec\n${e.message}`,
|
|
2621
|
-
)
|
|
2585
|
+
throw new Error(`element (${locator.toString()}) still not present on page after ${waitTimeout / 1000} sec\n${e.message}`)
|
|
2622
2586
|
}
|
|
2623
2587
|
}
|
|
2624
2588
|
|
|
@@ -2710,10 +2674,8 @@ class Playwright extends Helper {
|
|
|
2710
2674
|
.locator(buildLocatorString(locator))
|
|
2711
2675
|
.first()
|
|
2712
2676
|
.waitFor({ timeout: waitTimeout, state: 'hidden' })
|
|
2713
|
-
.catch(
|
|
2714
|
-
throw new Error(
|
|
2715
|
-
`element (${locator.toString()}) still not hidden after ${waitTimeout / 1000} sec\n${err.message}`,
|
|
2716
|
-
)
|
|
2677
|
+
.catch(err => {
|
|
2678
|
+
throw new Error(`element (${locator.toString()}) still not hidden after ${waitTimeout / 1000} sec\n${err.message}`)
|
|
2717
2679
|
})
|
|
2718
2680
|
}
|
|
2719
2681
|
|
|
@@ -2739,6 +2701,9 @@ class Playwright extends Helper {
|
|
|
2739
2701
|
if ((this.context && this.context.constructor.name === 'FrameLocator') || this.context) {
|
|
2740
2702
|
return this.context
|
|
2741
2703
|
}
|
|
2704
|
+
if (this.frame) {
|
|
2705
|
+
return this.frame
|
|
2706
|
+
}
|
|
2742
2707
|
return this.page
|
|
2743
2708
|
}
|
|
2744
2709
|
|
|
@@ -2750,14 +2715,14 @@ class Playwright extends Helper {
|
|
|
2750
2715
|
|
|
2751
2716
|
return this.page
|
|
2752
2717
|
.waitForFunction(
|
|
2753
|
-
|
|
2718
|
+
urlPart => {
|
|
2754
2719
|
const currUrl = decodeURIComponent(decodeURIComponent(decodeURIComponent(window.location.href)))
|
|
2755
2720
|
return currUrl.indexOf(urlPart) > -1
|
|
2756
2721
|
},
|
|
2757
2722
|
urlPart,
|
|
2758
2723
|
{ timeout: waitTimeout },
|
|
2759
2724
|
)
|
|
2760
|
-
.catch(async
|
|
2725
|
+
.catch(async e => {
|
|
2761
2726
|
const currUrl = await this._getPageUrl() // Required because the waitForFunction can't return data.
|
|
2762
2727
|
if (/Timeout/i.test(e.message)) {
|
|
2763
2728
|
throw new Error(`expected url to include ${urlPart}, but found ${currUrl}`)
|
|
@@ -2780,14 +2745,14 @@ class Playwright extends Helper {
|
|
|
2780
2745
|
|
|
2781
2746
|
return this.page
|
|
2782
2747
|
.waitForFunction(
|
|
2783
|
-
|
|
2748
|
+
urlPart => {
|
|
2784
2749
|
const currUrl = decodeURIComponent(decodeURIComponent(decodeURIComponent(window.location.href)))
|
|
2785
2750
|
return currUrl.indexOf(urlPart) > -1
|
|
2786
2751
|
},
|
|
2787
2752
|
urlPart,
|
|
2788
2753
|
{ timeout: waitTimeout },
|
|
2789
2754
|
)
|
|
2790
|
-
.catch(async
|
|
2755
|
+
.catch(async e => {
|
|
2791
2756
|
const currUrl = await this._getPageUrl() // Required because the waitForFunction can't return data.
|
|
2792
2757
|
if (/Timeout/i.test(e.message)) {
|
|
2793
2758
|
throw new Error(`expected url to be ${urlPart}, but found ${currUrl}`)
|
|
@@ -2803,56 +2768,74 @@ class Playwright extends Helper {
|
|
|
2803
2768
|
async waitForText(text, sec = null, context = null) {
|
|
2804
2769
|
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
2805
2770
|
const errorMessage = `Text "${text}" was not found on page after ${waitTimeout / 1000} sec.`
|
|
2806
|
-
let waiter
|
|
2807
2771
|
|
|
2808
2772
|
const contextObject = await this._getContext()
|
|
2809
2773
|
|
|
2810
2774
|
if (context) {
|
|
2811
2775
|
const locator = new Locator(context, 'css')
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2776
|
+
try {
|
|
2777
|
+
if (!locator.isXPath()) {
|
|
2778
|
+
return contextObject
|
|
2815
2779
|
.locator(`${locator.isCustom() ? `${locator.type}=${locator.value}` : locator.simplify()} >> text=${text}`)
|
|
2816
2780
|
.first()
|
|
2817
2781
|
.waitFor({ timeout: waitTimeout, state: 'visible' })
|
|
2818
|
-
|
|
2819
|
-
|
|
2782
|
+
.catch(e => {
|
|
2783
|
+
throw new Error(errorMessage)
|
|
2784
|
+
})
|
|
2820
2785
|
}
|
|
2821
|
-
}
|
|
2822
2786
|
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
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
|
+
})
|
|
2837
2802
|
}
|
|
2803
|
+
} catch (e) {
|
|
2804
|
+
throw new Error(`${errorMessage}\n${e.message}`)
|
|
2838
2805
|
}
|
|
2839
|
-
}
|
|
2840
|
-
// we have this as https://github.com/microsoft/playwright/issues/26829 is not yet implemented
|
|
2806
|
+
}
|
|
2841
2807
|
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
waiter = await _contextObject
|
|
2846
|
-
.locator(`:has-text(${JSON.stringify(text)})`)
|
|
2847
|
-
.first()
|
|
2848
|
-
.isVisible()
|
|
2849
|
-
if (waiter) break
|
|
2850
|
-
await this.wait(1)
|
|
2851
|
-
count += 1000
|
|
2852
|
-
} while (count <= waitTimeout)
|
|
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
|
|
2853
2811
|
|
|
2854
|
-
|
|
2855
|
-
|
|
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
|
+
})
|
|
2856
2839
|
}
|
|
2857
2840
|
|
|
2858
2841
|
/**
|
|
@@ -3021,11 +3004,11 @@ class Playwright extends Helper {
|
|
|
3021
3004
|
}
|
|
3022
3005
|
} else {
|
|
3023
3006
|
const visibleFn = function ([locator, $XPath]) {
|
|
3024
|
-
eval($XPath)
|
|
3007
|
+
eval($XPath)
|
|
3025
3008
|
return $XPath(null, locator).length === 0
|
|
3026
3009
|
}
|
|
3027
3010
|
waiter = context.waitForFunction(visibleFn, [locator.value, $XPath.toString()], { timeout: waitTimeout })
|
|
3028
|
-
return waiter.catch(
|
|
3011
|
+
return waiter.catch(err => {
|
|
3029
3012
|
throw new Error(`element (${locator.toString()}) still on page after ${waitTimeout / 1000} sec\n${err.message}`)
|
|
3030
3013
|
})
|
|
3031
3014
|
}
|
|
@@ -3047,9 +3030,9 @@ class Playwright extends Helper {
|
|
|
3047
3030
|
|
|
3048
3031
|
return promiseRetry(
|
|
3049
3032
|
async (retry, number) => {
|
|
3050
|
-
const _grabCookie = async
|
|
3033
|
+
const _grabCookie = async name => {
|
|
3051
3034
|
const cookies = await this.browserContext.cookies()
|
|
3052
|
-
const cookie = cookies.filter(
|
|
3035
|
+
const cookie = cookies.filter(c => c.name === name)
|
|
3053
3036
|
if (cookie.length === 0) throw Error(`Cookie ${name} is not found after ${retries}s`)
|
|
3054
3037
|
}
|
|
3055
3038
|
|
|
@@ -3128,7 +3111,7 @@ class Playwright extends Helper {
|
|
|
3128
3111
|
this.recording = true
|
|
3129
3112
|
this.recordedAtLeastOnce = true
|
|
3130
3113
|
|
|
3131
|
-
this.page.on('requestfinished', async
|
|
3114
|
+
this.page.on('requestfinished', async request => {
|
|
3132
3115
|
const information = {
|
|
3133
3116
|
url: request.url(),
|
|
3134
3117
|
method: request.method(),
|
|
@@ -3167,20 +3150,20 @@ class Playwright extends Helper {
|
|
|
3167
3150
|
*/
|
|
3168
3151
|
blockTraffic(urls) {
|
|
3169
3152
|
if (Array.isArray(urls)) {
|
|
3170
|
-
urls.forEach(
|
|
3171
|
-
this.page.route(url,
|
|
3153
|
+
urls.forEach(url => {
|
|
3154
|
+
this.page.route(url, route => {
|
|
3172
3155
|
route
|
|
3173
3156
|
.abort()
|
|
3174
3157
|
// Sometimes it happens that browser has been closed in the meantime. It is ok to ignore error then.
|
|
3175
|
-
.catch(
|
|
3158
|
+
.catch(e => {})
|
|
3176
3159
|
})
|
|
3177
3160
|
})
|
|
3178
3161
|
} else {
|
|
3179
|
-
this.page.route(urls,
|
|
3162
|
+
this.page.route(urls, route => {
|
|
3180
3163
|
route
|
|
3181
3164
|
.abort()
|
|
3182
3165
|
// Sometimes it happens that browser has been closed in the meantime. It is ok to ignore error then.
|
|
3183
|
-
.catch(
|
|
3166
|
+
.catch(e => {})
|
|
3184
3167
|
})
|
|
3185
3168
|
}
|
|
3186
3169
|
}
|
|
@@ -3209,8 +3192,8 @@ class Playwright extends Helper {
|
|
|
3209
3192
|
urls = [urls]
|
|
3210
3193
|
}
|
|
3211
3194
|
|
|
3212
|
-
urls.forEach(
|
|
3213
|
-
this.page.route(url,
|
|
3195
|
+
urls.forEach(url => {
|
|
3196
|
+
this.page.route(url, route => {
|
|
3214
3197
|
if (this.page.isClosed()) {
|
|
3215
3198
|
// Sometimes it happens that browser has been closed in the meantime.
|
|
3216
3199
|
// In this case we just don't fulfill to prevent error in test scenario.
|
|
@@ -3256,13 +3239,10 @@ class Playwright extends Helper {
|
|
|
3256
3239
|
*/
|
|
3257
3240
|
grabTrafficUrl(urlMatch) {
|
|
3258
3241
|
if (!this.recordedAtLeastOnce) {
|
|
3259
|
-
throw new Error(
|
|
3260
|
-
'Failure in test automation. You use "I.grabTrafficUrl", but "I.startRecordingTraffic" was never called before.',
|
|
3261
|
-
)
|
|
3242
|
+
throw new Error('Failure in test automation. You use "I.grabTrafficUrl", but "I.startRecordingTraffic" was never called before.')
|
|
3262
3243
|
}
|
|
3263
3244
|
|
|
3264
3245
|
for (const i in this.requests) {
|
|
3265
|
-
// eslint-disable-next-line no-prototype-builtins
|
|
3266
3246
|
if (this.requests.hasOwnProperty(i)) {
|
|
3267
3247
|
const request = this.requests[i]
|
|
3268
3248
|
|
|
@@ -3312,15 +3292,15 @@ class Playwright extends Helper {
|
|
|
3312
3292
|
await this.cdpSession.send('Network.enable')
|
|
3313
3293
|
await this.cdpSession.send('Page.enable')
|
|
3314
3294
|
|
|
3315
|
-
this.cdpSession.on('Network.webSocketFrameReceived',
|
|
3295
|
+
this.cdpSession.on('Network.webSocketFrameReceived', payload => {
|
|
3316
3296
|
this._logWebsocketMessages(this._getWebSocketLog('RECEIVED', payload))
|
|
3317
3297
|
})
|
|
3318
3298
|
|
|
3319
|
-
this.cdpSession.on('Network.webSocketFrameSent',
|
|
3299
|
+
this.cdpSession.on('Network.webSocketFrameSent', payload => {
|
|
3320
3300
|
this._logWebsocketMessages(this._getWebSocketLog('SENT', payload))
|
|
3321
3301
|
})
|
|
3322
3302
|
|
|
3323
|
-
this.cdpSession.on('Network.webSocketFrameError',
|
|
3303
|
+
this.cdpSession.on('Network.webSocketFrameError', payload => {
|
|
3324
3304
|
this._logWebsocketMessages(this._getWebSocketLog('ERROR', payload))
|
|
3325
3305
|
})
|
|
3326
3306
|
}
|
|
@@ -3344,9 +3324,7 @@ class Playwright extends Helper {
|
|
|
3344
3324
|
grabWebSocketMessages() {
|
|
3345
3325
|
if (!this.recordingWebSocketMessages) {
|
|
3346
3326
|
if (!this.recordedWebSocketMessagesAtLeastOnce) {
|
|
3347
|
-
throw new Error(
|
|
3348
|
-
'Failure in test automation. You use "I.grabWebSocketMessages", but "I.startRecordingWebSocketMessages" was never called before.',
|
|
3349
|
-
)
|
|
3327
|
+
throw new Error('Failure in test automation. You use "I.grabWebSocketMessages", but "I.startRecordingWebSocketMessages" was never called before.')
|
|
3350
3328
|
}
|
|
3351
3329
|
}
|
|
3352
3330
|
return this.webSocketMessages
|
|
@@ -3493,17 +3471,13 @@ async function proceedClick(locator, context = null, options = {}) {
|
|
|
3493
3471
|
}
|
|
3494
3472
|
const els = await findClickable.call(this, matcher, locator)
|
|
3495
3473
|
if (context) {
|
|
3496
|
-
assertElementExists(
|
|
3497
|
-
els,
|
|
3498
|
-
locator,
|
|
3499
|
-
'Clickable element',
|
|
3500
|
-
`was not found inside element ${new Locator(context).toString()}`,
|
|
3501
|
-
)
|
|
3474
|
+
assertElementExists(els, locator, 'Clickable element', `was not found inside element ${new Locator(context).toString()}`)
|
|
3502
3475
|
} else {
|
|
3503
3476
|
assertElementExists(els, locator, 'Clickable element')
|
|
3504
3477
|
}
|
|
3505
3478
|
|
|
3506
3479
|
await highlightActiveElement.call(this, els[0])
|
|
3480
|
+
if (store.debugMode) this.debugSection('Clicked', await elToString(els[0], 1))
|
|
3507
3481
|
|
|
3508
3482
|
/*
|
|
3509
3483
|
using the force true options itself but instead dispatching a click
|
|
@@ -3565,16 +3539,18 @@ async function proceedSee(assertType, text, context, strict = false) {
|
|
|
3565
3539
|
description = `element ${locator.toString()}`
|
|
3566
3540
|
const els = await this._locate(locator)
|
|
3567
3541
|
assertElementExists(els, locator.toString())
|
|
3568
|
-
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())
|
|
3569
3548
|
}
|
|
3570
3549
|
|
|
3571
3550
|
if (strict) {
|
|
3572
|
-
return allText.map(
|
|
3551
|
+
return allText.map(elText => equals(description)[assertType](text, elText))
|
|
3573
3552
|
}
|
|
3574
|
-
return stringIncludes(description)[assertType](
|
|
3575
|
-
normalizeSpacesInString(text),
|
|
3576
|
-
normalizeSpacesInString(allText.join(' | ')),
|
|
3577
|
-
)
|
|
3553
|
+
return stringIncludes(description)[assertType](normalizeSpacesInString(text), normalizeSpacesInString(allText.join(' | ')))
|
|
3578
3554
|
}
|
|
3579
3555
|
|
|
3580
3556
|
async function findCheckable(locator, context) {
|
|
@@ -3604,7 +3580,7 @@ async function findCheckable(locator, context) {
|
|
|
3604
3580
|
async function proceedIsChecked(assertType, option) {
|
|
3605
3581
|
let els = await findCheckable.call(this, option)
|
|
3606
3582
|
assertElementExists(els, option, 'Checkable')
|
|
3607
|
-
els = await Promise.all(els.map(
|
|
3583
|
+
els = await Promise.all(els.map(el => el.isChecked()))
|
|
3608
3584
|
const selected = els.reduce((prev, cur) => prev || cur)
|
|
3609
3585
|
return truth(`checkable ${option}`, 'to be checked')[assertType](selected)
|
|
3610
3586
|
}
|
|
@@ -3636,10 +3612,10 @@ async function proceedSeeInField(assertType, field, value) {
|
|
|
3636
3612
|
const els = await findFields.call(this, field)
|
|
3637
3613
|
assertElementExists(els, field, 'Field')
|
|
3638
3614
|
const el = els[0]
|
|
3639
|
-
const tag = await el.evaluate(
|
|
3615
|
+
const tag = await el.evaluate(e => e.tagName)
|
|
3640
3616
|
const fieldType = await el.getAttribute('type')
|
|
3641
3617
|
|
|
3642
|
-
const proceedMultiple = async
|
|
3618
|
+
const proceedMultiple = async elements => {
|
|
3643
3619
|
const fields = Array.isArray(elements) ? elements : [elements]
|
|
3644
3620
|
|
|
3645
3621
|
const elementValues = []
|
|
@@ -3653,7 +3629,7 @@ async function proceedSeeInField(assertType, field, value) {
|
|
|
3653
3629
|
if (assertType === 'assert') {
|
|
3654
3630
|
equals(`select option by ${field}`)[assertType](true, elementValues.length > 0)
|
|
3655
3631
|
}
|
|
3656
|
-
elementValues.forEach(
|
|
3632
|
+
elementValues.forEach(val => stringIncludes(`fields by ${field}`)[assertType](value, val))
|
|
3657
3633
|
}
|
|
3658
3634
|
}
|
|
3659
3635
|
|
|
@@ -3740,6 +3716,8 @@ function isFrameLocator(locator) {
|
|
|
3740
3716
|
}
|
|
3741
3717
|
|
|
3742
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
|
|
3743
3721
|
if (!res || res.length === 0) {
|
|
3744
3722
|
throw new ElementNotFound(locator, prefix, suffix)
|
|
3745
3723
|
}
|
|
@@ -3776,12 +3754,9 @@ async function targetCreatedHandler(page) {
|
|
|
3776
3754
|
this.contextLocator = null
|
|
3777
3755
|
})
|
|
3778
3756
|
})
|
|
3779
|
-
page.on('console',
|
|
3757
|
+
page.on('console', msg => {
|
|
3780
3758
|
if (!consoleLogStore.includes(msg) && this.options.ignoreLog && !this.options.ignoreLog.includes(msg.type())) {
|
|
3781
|
-
this.debugSection(
|
|
3782
|
-
`Browser:${ucfirst(msg.type())}`,
|
|
3783
|
-
((msg.text && msg.text()) || msg._text || '') + msg.args().join(' '),
|
|
3784
|
-
)
|
|
3759
|
+
this.debugSection(`Browser:${ucfirst(msg.type())}`, ((msg.text && msg.text()) || msg._text || '') + msg.args().join(' '))
|
|
3785
3760
|
}
|
|
3786
3761
|
consoleLogStore.add(msg)
|
|
3787
3762
|
})
|
|
@@ -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
|
+
}
|