codeceptjs 4.0.0-rc.9 → 4.0.0
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 +9 -10
- package/bin/codecept.js +15 -2
- package/bin/codeceptq.js +49 -0
- package/bin/mcp-server.js +751 -172
- package/docs/advanced.md +201 -0
- package/docs/agents.md +181 -0
- package/docs/ai.md +489 -0
- package/docs/aitrace.md +266 -0
- package/docs/api.md +332 -0
- package/docs/architecture.md +235 -0
- package/docs/assertions.md +415 -0
- package/docs/auth.md +318 -0
- package/docs/basics.md +424 -0
- package/docs/bdd.md +539 -0
- package/docs/best.md +240 -0
- package/docs/bootstrap.md +132 -0
- package/docs/commands.md +352 -0
- package/docs/community-helpers.md +63 -0
- package/docs/configuration.md +185 -0
- package/docs/continuous-integration.md +431 -0
- package/docs/custom-helpers.md +297 -0
- package/docs/data.md +448 -0
- package/docs/debugging.md +332 -0
- package/docs/detox.md +235 -0
- package/docs/docker.md +107 -0
- package/docs/effects.md +179 -0
- package/docs/element-based-testing.md +295 -0
- package/docs/element-selection.md +125 -0
- package/docs/els.md +328 -0
- package/docs/environment-variables.md +131 -0
- package/docs/examples.md +160 -0
- package/docs/heal.md +213 -0
- package/docs/helpers/ApiDataFactory.md +267 -0
- package/docs/helpers/Appium.md +1419 -0
- package/docs/helpers/Detox.md +665 -0
- package/docs/helpers/ExpectHelper.md +275 -0
- package/docs/helpers/FileSystem.md +152 -0
- package/docs/helpers/GraphQL.md +152 -0
- package/docs/helpers/GraphQLDataFactory.md +226 -0
- package/docs/helpers/JSONResponse.md +255 -0
- package/docs/helpers/MockRequest.md +377 -0
- package/docs/helpers/Playwright.md +2970 -0
- package/docs/helpers/Puppeteer-firefox.md +86 -0
- package/docs/helpers/Puppeteer.md +2583 -0
- package/docs/helpers/REST.md +289 -0
- package/docs/helpers/WebDriver.md +2639 -0
- package/docs/hooks.md +148 -0
- package/docs/index.md +111 -0
- package/docs/installation.md +121 -0
- package/docs/internal-test-server.md +89 -0
- package/docs/locators.md +355 -0
- package/docs/mcp.md +485 -0
- package/docs/migrate-from-cypress.md +98 -0
- package/docs/migrate-from-java.md +108 -0
- package/docs/migrate-from-protractor.md +101 -0
- package/docs/migrate-from-testcafe.md +99 -0
- package/docs/migration-4.md +743 -0
- package/docs/mobile.md +338 -0
- package/docs/pageobjects.md +399 -0
- package/docs/parallel.md +187 -0
- package/docs/playwright.md +714 -0
- package/docs/plugins/aiTrace.md +49 -0
- package/docs/plugins/analyze.md +66 -0
- package/docs/plugins/auth.md +241 -0
- package/docs/plugins/autoDelay.md +48 -0
- package/docs/plugins/browser.md +41 -0
- package/docs/plugins/coverage.md +39 -0
- package/docs/plugins/customLocator.md +119 -0
- package/docs/plugins/customReporter.md +16 -0
- package/docs/plugins/expose.md +75 -0
- package/docs/plugins/heal.md +44 -0
- package/docs/plugins/junitReporter.md +51 -0
- package/docs/plugins/pageInfo.md +34 -0
- package/docs/plugins/pause.md +43 -0
- package/docs/plugins/pauseOnFail.md +18 -0
- package/docs/plugins/retryFailedStep.md +75 -0
- package/docs/plugins/screencast.md +55 -0
- package/docs/plugins/screenshot.md +58 -0
- package/docs/plugins/screenshotOnFail.md +18 -0
- package/docs/plugins/stepTimeout.md +65 -0
- package/docs/plugins.md +87 -0
- package/docs/puppeteer.md +314 -0
- package/docs/quickstart.md +120 -0
- package/docs/reports.md +198 -0
- package/docs/retry.md +311 -0
- package/docs/secrets.md +150 -0
- package/docs/sessions.md +80 -0
- package/docs/shadow.md +68 -0
- package/docs/store.md +94 -0
- package/docs/test-structure.md +275 -0
- package/docs/timeouts.md +183 -0
- package/docs/translation.md +247 -0
- package/docs/tutorial.md +323 -0
- package/docs/typescript.md +159 -0
- package/docs/web-element.md +251 -0
- package/docs/webdriver.md +641 -0
- package/docs/within.md +55 -0
- package/lib/actor.js +1 -36
- package/lib/ai.js +3 -2
- package/lib/aria.js +260 -0
- package/lib/assertions.js +18 -0
- package/lib/codecept.js +7 -7
- package/lib/command/check.js +2 -1
- package/lib/command/dryRun.js +24 -5
- package/lib/command/generate.js +2 -0
- package/lib/command/gherkin/snippets.js +5 -4
- package/lib/command/init.js +248 -266
- package/lib/command/list.js +150 -10
- package/lib/command/query.js +218 -0
- package/lib/command/run-multiple.js +3 -2
- package/lib/command/run-workers.js +1 -14
- package/lib/command/run.js +3 -17
- package/lib/command/utils.js +14 -0
- package/lib/command/workers/runTests.js +11 -15
- package/lib/config.js +77 -4
- package/lib/container.js +97 -15
- package/lib/effects.js +17 -0
- package/lib/element/WebElement.js +194 -2
- package/lib/els.js +12 -6
- package/lib/globals.js +32 -19
- package/lib/heal.js +7 -4
- package/lib/helper/ApiDataFactory.js +2 -1
- package/lib/helper/FileSystem.js +3 -2
- package/lib/helper/GraphQLDataFactory.js +2 -1
- package/lib/helper/Playwright.js +63 -70
- package/lib/helper/Puppeteer.js +20 -109
- package/lib/helper/WebDriver.js +13 -30
- package/lib/helper/errors/NonFocusedType.js +8 -0
- package/lib/helper/extras/Download.js +45 -0
- package/lib/helper/extras/PlaywrightLocator.js +10 -0
- package/lib/helper/extras/elementSelection.js +10 -3
- package/lib/helper/extras/focusCheck.js +43 -0
- package/lib/helper/extras/richTextEditor.js +178 -0
- package/lib/history.js +3 -2
- package/lib/html.js +90 -16
- package/lib/index.js +9 -1
- package/lib/listener/config.js +6 -4
- package/lib/listener/emptyRun.js +2 -1
- package/lib/listener/helpers.js +4 -1
- package/lib/listener/mocha.js +2 -1
- package/lib/listener/pageobjects.js +43 -0
- package/lib/listener/result.js +3 -2
- package/lib/locator.js +126 -16
- package/lib/mocha/cli.js +4 -2
- package/lib/mocha/factory.js +7 -2
- package/lib/mocha/inject.js +1 -1
- package/lib/mocha/scenarioConfig.js +2 -1
- package/lib/mocha/ui.js +5 -6
- package/lib/parser.js +2 -2
- package/lib/pause.js +38 -4
- package/lib/plugin/aiTrace.js +96 -103
- package/lib/plugin/analyze.js +9 -9
- package/lib/plugin/auth.js +3 -3
- package/lib/plugin/browser.js +77 -0
- package/lib/plugin/expose.js +159 -0
- package/lib/plugin/heal.js +47 -3
- package/lib/plugin/junitReporter.js +303 -0
- package/lib/plugin/pageInfo.js +54 -52
- package/lib/plugin/pause.js +131 -0
- package/lib/plugin/pauseOnFail.js +11 -33
- package/lib/plugin/retryFailedStep.js +15 -13
- package/lib/plugin/screencast.js +289 -0
- package/lib/plugin/screenshot.js +558 -0
- package/lib/plugin/screenshotOnFail.js +9 -170
- package/lib/plugin/stepTimeout.js +3 -2
- package/lib/recorder.js +1 -1
- package/lib/rerun.js +2 -1
- package/lib/result.js +2 -1
- package/lib/step/base.js +10 -9
- package/lib/step/comment.js +2 -2
- package/lib/step/config.js +7 -0
- package/lib/step/helper.js +4 -4
- package/lib/step/meta.js +3 -3
- package/lib/step/record.js +5 -5
- package/lib/store.js +72 -3
- package/lib/translation.js +2 -1
- package/lib/utils/mask_data.js +2 -1
- package/lib/utils/pluginParser.js +151 -0
- package/lib/utils/trace.js +297 -0
- package/lib/utils.js +29 -3
- package/lib/workers.js +14 -22
- package/package.json +17 -14
- package/typings/index.d.ts +0 -5
- package/docs/webapi/amOnPage.mustache +0 -11
- package/docs/webapi/appendField.mustache +0 -16
- package/docs/webapi/attachFile.mustache +0 -24
- package/docs/webapi/blur.mustache +0 -18
- package/docs/webapi/checkOption.mustache +0 -13
- package/docs/webapi/clearCookie.mustache +0 -9
- package/docs/webapi/clearField.mustache +0 -14
- package/docs/webapi/click.mustache +0 -29
- package/docs/webapi/clickLink.mustache +0 -8
- package/docs/webapi/closeCurrentTab.mustache +0 -7
- package/docs/webapi/closeOtherTabs.mustache +0 -8
- package/docs/webapi/dontSee.mustache +0 -11
- package/docs/webapi/dontSeeCheckboxIsChecked.mustache +0 -10
- package/docs/webapi/dontSeeCookie.mustache +0 -8
- package/docs/webapi/dontSeeCurrentPathEquals.mustache +0 -10
- package/docs/webapi/dontSeeCurrentUrlEquals.mustache +0 -10
- package/docs/webapi/dontSeeElement.mustache +0 -12
- package/docs/webapi/dontSeeElementInDOM.mustache +0 -8
- package/docs/webapi/dontSeeInCurrentUrl.mustache +0 -4
- package/docs/webapi/dontSeeInField.mustache +0 -16
- package/docs/webapi/dontSeeInSource.mustache +0 -8
- package/docs/webapi/dontSeeInTitle.mustache +0 -8
- package/docs/webapi/dontSeeTraffic.mustache +0 -13
- package/docs/webapi/doubleClick.mustache +0 -13
- package/docs/webapi/downloadFile.mustache +0 -12
- package/docs/webapi/dragAndDrop.mustache +0 -9
- package/docs/webapi/dragSlider.mustache +0 -11
- package/docs/webapi/executeAsyncScript.mustache +0 -24
- package/docs/webapi/executeScript.mustache +0 -26
- package/docs/webapi/fillField.mustache +0 -21
- package/docs/webapi/flushNetworkTraffics.mustache +0 -5
- package/docs/webapi/focus.mustache +0 -13
- package/docs/webapi/forceClick.mustache +0 -28
- package/docs/webapi/forceRightClick.mustache +0 -18
- package/docs/webapi/grabAllWindowHandles.mustache +0 -7
- package/docs/webapi/grabAttributeFrom.mustache +0 -10
- package/docs/webapi/grabAttributeFromAll.mustache +0 -9
- package/docs/webapi/grabBrowserLogs.mustache +0 -9
- package/docs/webapi/grabCookie.mustache +0 -11
- package/docs/webapi/grabCssPropertyFrom.mustache +0 -11
- package/docs/webapi/grabCssPropertyFromAll.mustache +0 -10
- package/docs/webapi/grabCurrentUrl.mustache +0 -9
- package/docs/webapi/grabCurrentWindowHandle.mustache +0 -6
- package/docs/webapi/grabDataFromPerformanceTiming.mustache +0 -20
- package/docs/webapi/grabElementBoundingRect.mustache +0 -20
- package/docs/webapi/grabGeoLocation.mustache +0 -8
- package/docs/webapi/grabHTMLFrom.mustache +0 -10
- package/docs/webapi/grabHTMLFromAll.mustache +0 -9
- package/docs/webapi/grabNumberOfOpenTabs.mustache +0 -8
- package/docs/webapi/grabNumberOfVisibleElements.mustache +0 -9
- package/docs/webapi/grabPageScrollPosition.mustache +0 -8
- package/docs/webapi/grabPopupText.mustache +0 -5
- package/docs/webapi/grabRecordedNetworkTraffics.mustache +0 -10
- package/docs/webapi/grabSource.mustache +0 -8
- package/docs/webapi/grabTextFrom.mustache +0 -10
- package/docs/webapi/grabTextFromAll.mustache +0 -9
- package/docs/webapi/grabTitle.mustache +0 -8
- package/docs/webapi/grabValueFrom.mustache +0 -9
- package/docs/webapi/grabValueFromAll.mustache +0 -8
- package/docs/webapi/grabWebElement.mustache +0 -9
- package/docs/webapi/grabWebElements.mustache +0 -9
- package/docs/webapi/moveCursorTo.mustache +0 -16
- package/docs/webapi/openNewTab.mustache +0 -7
- package/docs/webapi/pressKey.mustache +0 -12
- package/docs/webapi/pressKeyDown.mustache +0 -12
- package/docs/webapi/pressKeyUp.mustache +0 -12
- package/docs/webapi/pressKeyWithKeyNormalization.mustache +0 -60
- package/docs/webapi/refreshPage.mustache +0 -6
- package/docs/webapi/resizeWindow.mustache +0 -6
- package/docs/webapi/rightClick.mustache +0 -14
- package/docs/webapi/saveElementScreenshot.mustache +0 -10
- package/docs/webapi/saveScreenshot.mustache +0 -12
- package/docs/webapi/say.mustache +0 -10
- package/docs/webapi/scrollIntoView.mustache +0 -11
- package/docs/webapi/scrollPageToBottom.mustache +0 -6
- package/docs/webapi/scrollPageToTop.mustache +0 -6
- package/docs/webapi/scrollTo.mustache +0 -12
- package/docs/webapi/see.mustache +0 -11
- package/docs/webapi/seeAttributesOnElements.mustache +0 -9
- package/docs/webapi/seeCheckboxIsChecked.mustache +0 -10
- package/docs/webapi/seeCookie.mustache +0 -8
- package/docs/webapi/seeCssPropertiesOnElements.mustache +0 -9
- package/docs/webapi/seeCurrentPathEquals.mustache +0 -10
- package/docs/webapi/seeCurrentUrlEquals.mustache +0 -11
- package/docs/webapi/seeElement.mustache +0 -12
- package/docs/webapi/seeElementInDOM.mustache +0 -8
- package/docs/webapi/seeInCurrentUrl.mustache +0 -8
- package/docs/webapi/seeInField.mustache +0 -17
- package/docs/webapi/seeInPopup.mustache +0 -8
- package/docs/webapi/seeInSource.mustache +0 -7
- package/docs/webapi/seeInTitle.mustache +0 -8
- package/docs/webapi/seeNumberOfElements.mustache +0 -11
- package/docs/webapi/seeNumberOfVisibleElements.mustache +0 -10
- package/docs/webapi/seeTextEquals.mustache +0 -9
- package/docs/webapi/seeTitleEquals.mustache +0 -8
- package/docs/webapi/seeTraffic.mustache +0 -36
- package/docs/webapi/selectOption.mustache +0 -26
- package/docs/webapi/setCookie.mustache +0 -16
- package/docs/webapi/setGeoLocation.mustache +0 -12
- package/docs/webapi/startRecordingTraffic.mustache +0 -8
- package/docs/webapi/startRecordingWebSocketMessages.mustache +0 -8
- package/docs/webapi/stopRecordingTraffic.mustache +0 -5
- package/docs/webapi/stopRecordingWebSocketMessages.mustache +0 -7
- package/docs/webapi/switchTo.mustache +0 -9
- package/docs/webapi/switchToNextTab.mustache +0 -10
- package/docs/webapi/switchToPreviousTab.mustache +0 -10
- package/docs/webapi/type.mustache +0 -21
- package/docs/webapi/uncheckOption.mustache +0 -13
- package/docs/webapi/wait.mustache +0 -8
- package/docs/webapi/waitForClickable.mustache +0 -11
- package/docs/webapi/waitForCookie.mustache +0 -9
- package/docs/webapi/waitForDetached.mustache +0 -10
- package/docs/webapi/waitForDisabled.mustache +0 -6
- package/docs/webapi/waitForElement.mustache +0 -11
- package/docs/webapi/waitForEnabled.mustache +0 -6
- package/docs/webapi/waitForFunction.mustache +0 -17
- package/docs/webapi/waitForInvisible.mustache +0 -10
- package/docs/webapi/waitForNumberOfTabs.mustache +0 -9
- package/docs/webapi/waitForText.mustache +0 -13
- package/docs/webapi/waitForValue.mustache +0 -10
- package/docs/webapi/waitForVisible.mustache +0 -10
- package/docs/webapi/waitInUrl.mustache +0 -9
- package/docs/webapi/waitNumberOfVisibleElements.mustache +0 -10
- package/docs/webapi/waitToHide.mustache +0 -10
- package/docs/webapi/waitUrlEquals.mustache +0 -10
- package/lib/helper/AI.js +0 -214
- package/lib/helper/Mochawesome.js +0 -96
- package/lib/helper/extras/PlaywrightReactVueLocator.js +0 -52
- package/lib/helper/extras/React.js +0 -65
- package/lib/plugin/stepByStepReport.js +0 -431
- package/lib/plugin/subtitles.js +0 -89
package/lib/els.js
CHANGED
|
@@ -6,10 +6,11 @@ import recordStep from './step/record.js'
|
|
|
6
6
|
import FuncStep from './step/func.js'
|
|
7
7
|
import { truth } from './assert/truth.js'
|
|
8
8
|
import { isAsyncFunction, humanizeFunction } from './utils.js'
|
|
9
|
+
import WebElement from './element/WebElement.js'
|
|
9
10
|
|
|
10
11
|
function element(purpose, locator, fn) {
|
|
11
12
|
let stepConfig
|
|
12
|
-
if (arguments[arguments.length - 1]
|
|
13
|
+
if (StepConfig.isStepConfig(arguments[arguments.length - 1])) {
|
|
13
14
|
stepConfig = arguments[arguments.length - 1]
|
|
14
15
|
}
|
|
15
16
|
|
|
@@ -28,7 +29,8 @@ function element(purpose, locator, fn) {
|
|
|
28
29
|
const els = await step.helper._locate(locator)
|
|
29
30
|
output.debug(`Found ${els.length} elements, using first element`)
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
const wrapped = new WebElement(els[0], step.helper)
|
|
33
|
+
return fn(wrapped)
|
|
32
34
|
},
|
|
33
35
|
stepConfig,
|
|
34
36
|
)
|
|
@@ -52,7 +54,8 @@ function eachElement(purpose, locator, fn) {
|
|
|
52
54
|
let i = 0
|
|
53
55
|
for (const el of els) {
|
|
54
56
|
try {
|
|
55
|
-
|
|
57
|
+
const wrapped = new WebElement(el, step.helper)
|
|
58
|
+
await fn(wrapped, i)
|
|
56
59
|
} catch (err) {
|
|
57
60
|
output.error(`eachElement: failed operation on element #${i} ${el}`)
|
|
58
61
|
errs.push(err)
|
|
@@ -74,7 +77,8 @@ function expectElement(locator, fn) {
|
|
|
74
77
|
const els = await step.helper._locate(locator)
|
|
75
78
|
output.debug(`Found ${els.length} elements, first will be used for assertion`)
|
|
76
79
|
|
|
77
|
-
const
|
|
80
|
+
const wrapped = new WebElement(els[0], step.helper)
|
|
81
|
+
const result = await fn(wrapped)
|
|
78
82
|
const assertion = truth(`element (${locator})`, fn.toString())
|
|
79
83
|
assertion.assert(result)
|
|
80
84
|
})
|
|
@@ -92,7 +96,8 @@ function expectAnyElement(locator, fn) {
|
|
|
92
96
|
|
|
93
97
|
let found = false
|
|
94
98
|
for (const el of els) {
|
|
95
|
-
const
|
|
99
|
+
const wrapped = new WebElement(el, step.helper)
|
|
100
|
+
const result = await fn(wrapped)
|
|
96
101
|
if (result) {
|
|
97
102
|
found = true
|
|
98
103
|
break
|
|
@@ -113,7 +118,8 @@ function expectAllElements(locator, fn) {
|
|
|
113
118
|
let i = 1
|
|
114
119
|
for (const el of els) {
|
|
115
120
|
output.debug(`checking element #${i}: ${el}`)
|
|
116
|
-
const
|
|
121
|
+
const wrapped = new WebElement(el, step.helper)
|
|
122
|
+
const result = await fn(wrapped)
|
|
117
123
|
const assertion = truth(`element #${i} of (${locator})`, humanizeFunction(fn))
|
|
118
124
|
assertion.assert(result)
|
|
119
125
|
i++
|
package/lib/globals.js
CHANGED
|
@@ -8,27 +8,47 @@ import fsPath from 'path'
|
|
|
8
8
|
import ActorFactory from './actor.js'
|
|
9
9
|
import output from './output.js'
|
|
10
10
|
import locator from './locator.js'
|
|
11
|
+
import store from './store.js'
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Initialize CodeceptJS core globals
|
|
14
15
|
* Called from Codecept.initGlobals()
|
|
15
16
|
*/
|
|
16
17
|
export async function initCodeceptGlobals(dir, config, container) {
|
|
18
|
+
store.initialize({
|
|
19
|
+
codeceptDir: dir,
|
|
20
|
+
outputDir: fsPath.resolve(dir, config.output),
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
store.noGlobals = config.noGlobals || false
|
|
24
|
+
store.maskSensitiveData = config.maskSensitiveData || false
|
|
25
|
+
|
|
26
|
+
// Keep globals for backward compat with external plugins
|
|
17
27
|
global.codecept_dir = dir
|
|
18
28
|
global.output_dir = fsPath.resolve(dir, config.output)
|
|
19
29
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return ActorFactory(obj, global.container || container)
|
|
24
|
-
}
|
|
25
|
-
global.Actor = global.actor
|
|
26
|
-
|
|
27
|
-
// Use dynamic imports for modules to avoid circular dependencies
|
|
30
|
+
// pause/inject/share stay global even under noGlobals — they're the everyday
|
|
31
|
+
// debugging/wiring entry points and have no useful import alternative for
|
|
32
|
+
// page-object code that runs before the container is available.
|
|
28
33
|
global.pause = async (...args) => {
|
|
29
34
|
const pauseModule = await import('./pause.js')
|
|
30
35
|
return (pauseModule.default || pauseModule)(...args)
|
|
31
36
|
}
|
|
37
|
+
global.inject = () => container.support()
|
|
38
|
+
global.share = container.share
|
|
39
|
+
|
|
40
|
+
if (config.noGlobals) return;
|
|
41
|
+
|
|
42
|
+
output.print(output.styles.debug('Global functions are deprecated. Use `import { Helper, within, session } from "codeceptjs"` instead. Set `noGlobals: true` in config to disable globals.'));
|
|
43
|
+
|
|
44
|
+
const HelperModule = await import('@codeceptjs/helper')
|
|
45
|
+
global.Helper = global.codecept_helper = HelperModule.default || HelperModule
|
|
46
|
+
|
|
47
|
+
// Set up actor global - will use container when available
|
|
48
|
+
global.actor = global.codecept_actor = (obj) => {
|
|
49
|
+
return ActorFactory(obj, container)
|
|
50
|
+
}
|
|
51
|
+
global.Actor = global.actor
|
|
32
52
|
|
|
33
53
|
global.within = async (...args) => {
|
|
34
54
|
return (await import('./effects.js')).within(...args)
|
|
@@ -46,14 +66,9 @@ export async function initCodeceptGlobals(dir, config, container) {
|
|
|
46
66
|
return locator.build(locatorQuery)
|
|
47
67
|
}
|
|
48
68
|
|
|
49
|
-
global.inject = () => container.support()
|
|
50
|
-
global.share = container.share
|
|
51
|
-
|
|
52
69
|
const secretModule = await import('./secret.js')
|
|
53
70
|
global.secret = secretModule.secret || (secretModule.default && secretModule.default.secret)
|
|
54
71
|
|
|
55
|
-
global.codecept_debug = output.debug
|
|
56
|
-
|
|
57
72
|
const codeceptjsModule = await import('./index.js') // load all objects
|
|
58
73
|
global.codeceptjs = codeceptjsModule.default || codeceptjsModule
|
|
59
74
|
|
|
@@ -65,9 +80,6 @@ export async function initCodeceptGlobals(dir, config, container) {
|
|
|
65
80
|
global.Then = stepDefinitions.Then
|
|
66
81
|
global.DefineParameterType = stepDefinitions.defineParameterType
|
|
67
82
|
|
|
68
|
-
// debug mode
|
|
69
|
-
global.debugMode = false
|
|
70
|
-
|
|
71
83
|
// mask sensitive data
|
|
72
84
|
global.maskSensitiveData = config.maskSensitiveData || false
|
|
73
85
|
|
|
@@ -78,6 +90,8 @@ export async function initCodeceptGlobals(dir, config, container) {
|
|
|
78
90
|
* Called from mocha/ui.js pre-require event
|
|
79
91
|
*/
|
|
80
92
|
export function initMochaGlobals(context) {
|
|
93
|
+
if (store.noGlobals) return;
|
|
94
|
+
|
|
81
95
|
// Mocha test framework globals
|
|
82
96
|
global.BeforeAll = context.BeforeAll
|
|
83
97
|
global.AfterAll = context.AfterAll
|
|
@@ -106,6 +120,8 @@ export function getGlobalNames() {
|
|
|
106
120
|
return [
|
|
107
121
|
'codecept_dir',
|
|
108
122
|
'output_dir',
|
|
123
|
+
'Helper',
|
|
124
|
+
'codecept_helper',
|
|
109
125
|
'actor',
|
|
110
126
|
'codecept_actor',
|
|
111
127
|
'Actor',
|
|
@@ -117,13 +133,11 @@ export function getGlobalNames() {
|
|
|
117
133
|
'inject',
|
|
118
134
|
'share',
|
|
119
135
|
'secret',
|
|
120
|
-
'codecept_debug',
|
|
121
136
|
'codeceptjs',
|
|
122
137
|
'Given',
|
|
123
138
|
'When',
|
|
124
139
|
'Then',
|
|
125
140
|
'DefineParameterType',
|
|
126
|
-
'debugMode',
|
|
127
141
|
'maskSensitiveData',
|
|
128
142
|
'BeforeAll',
|
|
129
143
|
'AfterAll',
|
|
@@ -136,6 +150,5 @@ export function getGlobalNames() {
|
|
|
136
150
|
'After',
|
|
137
151
|
'Scenario',
|
|
138
152
|
'xScenario',
|
|
139
|
-
'container'
|
|
140
153
|
]
|
|
141
154
|
}
|
package/lib/heal.js
CHANGED
|
@@ -4,6 +4,7 @@ import colors from 'chalk'
|
|
|
4
4
|
import recorder from './recorder.js'
|
|
5
5
|
import output from './output.js'
|
|
6
6
|
import event from './event.js'
|
|
7
|
+
import container from './container.js'
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* @class
|
|
@@ -48,12 +49,14 @@ class Heal {
|
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
hasCorrespondingRecipes(step) {
|
|
51
|
-
return matchRecipes(this.recipes, this.contextName).filter(r => !r.steps || r.steps.includes(step.
|
|
52
|
+
return matchRecipes(this.recipes, this.contextName).filter(r => !r.steps || r.steps.includes(step.title)).length > 0
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
async getCodeSuggestions(context) {
|
|
55
56
|
const suggestions = []
|
|
57
|
+
const stepName = context.step?.title
|
|
56
58
|
const recipes = matchRecipes(this.recipes, this.contextName)
|
|
59
|
+
.filter(r => !r.steps || !stepName || r.steps.includes(stepName))
|
|
57
60
|
|
|
58
61
|
debug('Recipes', recipes)
|
|
59
62
|
|
|
@@ -69,7 +72,7 @@ class Heal {
|
|
|
69
72
|
if (!prepareFn) continue
|
|
70
73
|
|
|
71
74
|
if (context[property]) continue
|
|
72
|
-
context[property] = await prepareFn(
|
|
75
|
+
context[property] = await prepareFn(container.support())
|
|
73
76
|
}
|
|
74
77
|
|
|
75
78
|
output.level(currentOutputLevel)
|
|
@@ -116,10 +119,10 @@ class Heal {
|
|
|
116
119
|
})
|
|
117
120
|
|
|
118
121
|
if (typeof codeSnippet === 'string') {
|
|
119
|
-
const I =
|
|
122
|
+
const I = container.support('I')
|
|
120
123
|
await eval(codeSnippet)
|
|
121
124
|
} else if (typeof codeSnippet === 'function') {
|
|
122
|
-
await codeSnippet(
|
|
125
|
+
await codeSnippet(container.support())
|
|
123
126
|
}
|
|
124
127
|
|
|
125
128
|
this.fixes.push({
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
2
|
import Helper from '@codeceptjs/helper'
|
|
3
3
|
import REST from './REST.js'
|
|
4
|
+
import store from '../store.js'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Helper for managing remote data using REST API.
|
|
@@ -324,7 +325,7 @@ class ApiDataFactory extends Helper {
|
|
|
324
325
|
await import.meta.resolve(modulePath)
|
|
325
326
|
} catch (e) {
|
|
326
327
|
// If not found, try relative to codecept_dir
|
|
327
|
-
modulePath = path.join(
|
|
328
|
+
modulePath = path.join(store.codeceptDir, modulePath)
|
|
328
329
|
}
|
|
329
330
|
// check if the new syntax `export default new Factory()` is used and loads the builder, otherwise loads the module that used old syntax `module.exports = new Factory()`.
|
|
330
331
|
const module = await import(modulePath)
|
package/lib/helper/FileSystem.js
CHANGED
|
@@ -4,6 +4,7 @@ import fs from 'fs'
|
|
|
4
4
|
|
|
5
5
|
import Helper from '@codeceptjs/helper'
|
|
6
6
|
import { fileExists } from '../utils.js'
|
|
7
|
+
import store from '../store.js'
|
|
7
8
|
import { fileIncludes } from '../assert/include.js'
|
|
8
9
|
import { fileEquals } from '../assert/equal.js'
|
|
9
10
|
|
|
@@ -33,7 +34,7 @@ import { fileEquals } from '../assert/equal.js'
|
|
|
33
34
|
class FileSystem extends Helper {
|
|
34
35
|
constructor() {
|
|
35
36
|
super()
|
|
36
|
-
this.dir =
|
|
37
|
+
this.dir = store.codeceptDir
|
|
37
38
|
this.file = ''
|
|
38
39
|
}
|
|
39
40
|
|
|
@@ -52,7 +53,7 @@ class FileSystem extends Helper {
|
|
|
52
53
|
* @param {string} openPath
|
|
53
54
|
*/
|
|
54
55
|
amInPath(openPath) {
|
|
55
|
-
this.dir = path.join(
|
|
56
|
+
this.dir = path.join(store.codeceptDir, openPath)
|
|
56
57
|
try {
|
|
57
58
|
this.debugSection('Dir', this.dir)
|
|
58
59
|
} catch (e) {
|
|
@@ -2,6 +2,7 @@ import path from 'path'
|
|
|
2
2
|
|
|
3
3
|
import HelperModule from '@codeceptjs/helper'
|
|
4
4
|
import GraphQL from './GraphQL.js'
|
|
5
|
+
import store from '../store.js'
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Helper for managing remote data using GraphQL queries.
|
|
@@ -251,7 +252,7 @@ class GraphQLDataFactory extends Helper {
|
|
|
251
252
|
try {
|
|
252
253
|
require.resolve(modulePath)
|
|
253
254
|
} catch (e) {
|
|
254
|
-
modulePath = path.join(
|
|
255
|
+
modulePath = path.join(store.codeceptDir, modulePath)
|
|
255
256
|
}
|
|
256
257
|
const builder = require(modulePath)
|
|
257
258
|
return builder.build(data)
|
package/lib/helper/Playwright.js
CHANGED
|
@@ -7,6 +7,7 @@ import promiseRetry from 'promise-retry'
|
|
|
7
7
|
import Locator from '../locator.js'
|
|
8
8
|
import recorder from '../recorder.js'
|
|
9
9
|
import store from '../store.js'
|
|
10
|
+
import { checkFocusBeforeType, checkFocusBeforePressKey } from './extras/focusCheck.js'
|
|
10
11
|
import { includes as stringIncludes } from '../assert/include.js'
|
|
11
12
|
import { urlEquals, equals } from '../assert/equal.js'
|
|
12
13
|
import { empty } from '../assert/empty.js'
|
|
@@ -35,19 +36,16 @@ import MultipleElementsFound from './errors/MultipleElementsFound.js'
|
|
|
35
36
|
import RemoteBrowserConnectionRefused from './errors/RemoteBrowserConnectionRefused.js'
|
|
36
37
|
import Popup from './extras/Popup.js'
|
|
37
38
|
import Console from './extras/Console.js'
|
|
38
|
-
import {
|
|
39
|
+
import { findByPlaywrightLocator } from './extras/PlaywrightLocator.js'
|
|
39
40
|
import { dropFile } from './scripts/dropFile.js'
|
|
40
41
|
import WebElement from '../element/WebElement.js'
|
|
41
42
|
import { selectElement } from './extras/elementSelection.js'
|
|
43
|
+
import { fillRichEditor } from './extras/richTextEditor.js'
|
|
42
44
|
|
|
43
45
|
let playwright
|
|
44
46
|
let perfTiming
|
|
45
47
|
let defaultSelectorEnginesInitialized = false
|
|
46
48
|
|
|
47
|
-
// Use global object to track selector registration across workers
|
|
48
|
-
if (typeof global.__playwrightSelectorsRegistered === 'undefined') {
|
|
49
|
-
global.__playwrightSelectorsRegistered = false
|
|
50
|
-
}
|
|
51
49
|
|
|
52
50
|
const popupStore = new Popup()
|
|
53
51
|
const consoleLogStore = new Console()
|
|
@@ -448,7 +446,7 @@ class Playwright extends Helper {
|
|
|
448
446
|
this.options.recordVideo = { size }
|
|
449
447
|
}
|
|
450
448
|
if (this.options.recordVideo && !this.options.recordVideo.dir) {
|
|
451
|
-
this.options.recordVideo.dir = `${
|
|
449
|
+
this.options.recordVideo.dir = `${store.outputDir}/videos/`
|
|
452
450
|
}
|
|
453
451
|
this.isRemoteBrowser = !!this.playwrightOptions.browserWSEndpoint
|
|
454
452
|
this.isElectron = this.options.browser === 'electron'
|
|
@@ -510,18 +508,18 @@ class Playwright extends Helper {
|
|
|
510
508
|
try {
|
|
511
509
|
// Always wrap in try-catch since selectors might be registered globally across workers
|
|
512
510
|
// Check global flag to avoid re-registration in worker processes
|
|
513
|
-
if (!
|
|
511
|
+
if (!defaultSelectorEnginesInitialized) {
|
|
514
512
|
try {
|
|
515
513
|
await playwright.selectors.register('__value', createValueEngine)
|
|
516
514
|
await playwright.selectors.register('__disabled', createDisabledEngine)
|
|
517
|
-
|
|
515
|
+
defaultSelectorEnginesInitialized = true
|
|
518
516
|
defaultSelectorEnginesInitialized = true
|
|
519
517
|
} catch (e) {
|
|
520
518
|
if (!e.message.includes('already registered')) {
|
|
521
519
|
throw e
|
|
522
520
|
}
|
|
523
521
|
// Selector already registered globally by another worker
|
|
524
|
-
|
|
522
|
+
defaultSelectorEnginesInitialized = true
|
|
525
523
|
defaultSelectorEnginesInitialized = true
|
|
526
524
|
}
|
|
527
525
|
} else {
|
|
@@ -614,7 +612,7 @@ class Playwright extends Helper {
|
|
|
614
612
|
if (this.options.recordVideo) contextOptions.recordVideo = this.options.recordVideo
|
|
615
613
|
if (this.options.recordHar) {
|
|
616
614
|
const harExt = this.options.recordHar.content && this.options.recordHar.content === 'attach' ? 'zip' : 'har'
|
|
617
|
-
const fileName = `${`${
|
|
615
|
+
const fileName = `${`${store.outputDir}${path.sep}har${path.sep}${uuidv4()}_${clearString(this.currentRunningTest.title)}`.slice(0, 245)}.${harExt}`
|
|
618
616
|
const dir = path.dirname(fileName)
|
|
619
617
|
if (!fileExists(dir)) fs.mkdirSync(dir)
|
|
620
618
|
this.options.recordHar.path = fileName
|
|
@@ -757,6 +755,11 @@ class Playwright extends Helper {
|
|
|
757
755
|
}
|
|
758
756
|
|
|
759
757
|
async _afterSuite() {
|
|
758
|
+
// Reset leftover test-level cleanup state (e.g. screenshot failures)
|
|
759
|
+
// so only errors from this suite teardown are evaluated below.
|
|
760
|
+
this.hasCleanupError = false
|
|
761
|
+
this.testFailures = []
|
|
762
|
+
|
|
760
763
|
// Stop browser after suite completes
|
|
761
764
|
// For restart strategies: stop after each suite
|
|
762
765
|
// For session mode (restart:false): stop after the last suite
|
|
@@ -1636,7 +1639,7 @@ class Playwright extends Helper {
|
|
|
1636
1639
|
* @returns Promise<void>
|
|
1637
1640
|
*/
|
|
1638
1641
|
async replayFromHar(harFilePath, opts) {
|
|
1639
|
-
const file = path.join(
|
|
1642
|
+
const file = path.join(store.codeceptDir, harFilePath)
|
|
1640
1643
|
|
|
1641
1644
|
if (!fileExists(file)) {
|
|
1642
1645
|
throw new Error(`File at ${file} cannot be found on local system`)
|
|
@@ -2047,7 +2050,7 @@ class Playwright extends Helper {
|
|
|
2047
2050
|
const filePath = await download.path()
|
|
2048
2051
|
fileName = fileName || `downloads/${path.basename(filePath)}`
|
|
2049
2052
|
|
|
2050
|
-
const downloadPath = path.join(
|
|
2053
|
+
const downloadPath = path.join(store.outputDir, fileName)
|
|
2051
2054
|
if (!fs.existsSync(path.dirname(downloadPath))) {
|
|
2052
2055
|
fs.mkdirSync(path.dirname(downloadPath), '0777')
|
|
2053
2056
|
}
|
|
@@ -2078,15 +2081,6 @@ class Playwright extends Helper {
|
|
|
2078
2081
|
return proceedClick.call(this, locator, context, options)
|
|
2079
2082
|
}
|
|
2080
2083
|
|
|
2081
|
-
/**
|
|
2082
|
-
* Clicks link and waits for navigation (deprecated)
|
|
2083
|
-
*/
|
|
2084
|
-
async clickLink(locator, context = null) {
|
|
2085
|
-
console.log('clickLink deprecated: Playwright automatically waits for navigation to happen.')
|
|
2086
|
-
console.log('Replace I.clickLink with I.click')
|
|
2087
|
-
return this.click(locator, context)
|
|
2088
|
-
}
|
|
2089
|
-
|
|
2090
2084
|
/**
|
|
2091
2085
|
* {{> forceClick }}
|
|
2092
2086
|
*/
|
|
@@ -2231,6 +2225,7 @@ class Playwright extends Helper {
|
|
|
2231
2225
|
* {{> pressKeyWithKeyNormalization }}
|
|
2232
2226
|
*/
|
|
2233
2227
|
async pressKey(key) {
|
|
2228
|
+
await checkFocusBeforePressKey(this, key)
|
|
2234
2229
|
const modifiers = []
|
|
2235
2230
|
if (Array.isArray(key)) {
|
|
2236
2231
|
for (let k of key) {
|
|
@@ -2259,6 +2254,8 @@ class Playwright extends Helper {
|
|
|
2259
2254
|
* {{> type }}
|
|
2260
2255
|
*/
|
|
2261
2256
|
async type(keys, delay = null) {
|
|
2257
|
+
await checkFocusBeforeType(this)
|
|
2258
|
+
|
|
2262
2259
|
// Always use page.keyboard.type for any string (including single character and national characters).
|
|
2263
2260
|
if (!Array.isArray(keys)) {
|
|
2264
2261
|
keys = keys.toString()
|
|
@@ -2283,11 +2280,15 @@ class Playwright extends Helper {
|
|
|
2283
2280
|
assertElementExists(els, field, 'Field')
|
|
2284
2281
|
const el = selectElement(els, field, this)
|
|
2285
2282
|
|
|
2283
|
+
await highlightActiveElement.call(this, el)
|
|
2284
|
+
|
|
2285
|
+
if (await fillRichEditor(this, el, value)) {
|
|
2286
|
+
return this._waitForAction()
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2286
2289
|
await el.clear()
|
|
2287
2290
|
if (store.debugMode) this.debugSection('Focused', await elToString(el, 1))
|
|
2288
2291
|
|
|
2289
|
-
await highlightActiveElement.call(this, el)
|
|
2290
|
-
|
|
2291
2292
|
await el.type(value.toString(), { delay: this.options.pressKeyDelay })
|
|
2292
2293
|
|
|
2293
2294
|
return this._waitForAction()
|
|
@@ -2343,7 +2344,7 @@ class Playwright extends Helper {
|
|
|
2343
2344
|
*
|
|
2344
2345
|
*/
|
|
2345
2346
|
async attachFile(locator, pathToFile, context = null) {
|
|
2346
|
-
const file = path.join(
|
|
2347
|
+
const file = path.join(store.codeceptDir, pathToFile)
|
|
2347
2348
|
|
|
2348
2349
|
if (!fileExists(file)) {
|
|
2349
2350
|
throw new Error(`File at ${file} can not be found on local system`)
|
|
@@ -2669,8 +2670,11 @@ class Playwright extends Helper {
|
|
|
2669
2670
|
* @returns {Promise<any>}
|
|
2670
2671
|
*/
|
|
2671
2672
|
async executeScript(fn, arg) {
|
|
2672
|
-
if (
|
|
2673
|
-
|
|
2673
|
+
if (arg && typeof arg.getNativeElement === 'function') arg = arg.getNativeElement()
|
|
2674
|
+
if (arg && typeof arg.evaluate === 'function' && typeof arg.locator === 'function') {
|
|
2675
|
+
return arg.evaluate(fn)
|
|
2676
|
+
}
|
|
2677
|
+
if (this.context && typeof this.context.url !== 'function' && typeof this.context.innerText !== 'function') {
|
|
2674
2678
|
return this.context.locator(':root').evaluate(fn, arg)
|
|
2675
2679
|
}
|
|
2676
2680
|
return this.page.evaluate.apply(this.page, [fn, arg])
|
|
@@ -2700,15 +2704,12 @@ class Playwright extends Helper {
|
|
|
2700
2704
|
*
|
|
2701
2705
|
*/
|
|
2702
2706
|
async grabTextFrom(locator) {
|
|
2703
|
-
|
|
2704
|
-
if (
|
|
2705
|
-
const
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
this.debugSection('Text', text)
|
|
2710
|
-
return text
|
|
2711
|
-
}
|
|
2707
|
+
const roleElements = await handleRoleLocator(this.page, locator)
|
|
2708
|
+
if (roleElements && roleElements.length > 0) {
|
|
2709
|
+
const text = await roleElements[0].textContent()
|
|
2710
|
+
assertElementExists(text, JSON.stringify(locator))
|
|
2711
|
+
this.debugSection('Text', text)
|
|
2712
|
+
return text
|
|
2712
2713
|
}
|
|
2713
2714
|
|
|
2714
2715
|
const locatorObj = new Locator(locator, 'css')
|
|
@@ -2936,7 +2937,7 @@ class Playwright extends Helper {
|
|
|
2936
2937
|
const els = await this._locate(matchedLocator)
|
|
2937
2938
|
assertElementExists(els, locator)
|
|
2938
2939
|
const snapshot = await els[0].ariaSnapshot()
|
|
2939
|
-
this.debugSection('Aria Snapshot', snapshot)
|
|
2940
|
+
this.debugSection('Aria Snapshot', `${snapshot.split('\n').length} lines`)
|
|
2940
2941
|
return snapshot
|
|
2941
2942
|
}
|
|
2942
2943
|
|
|
@@ -3296,8 +3297,6 @@ class Playwright extends Helper {
|
|
|
3296
3297
|
}
|
|
3297
3298
|
|
|
3298
3299
|
/**
|
|
3299
|
-
* This method accepts [React selectors](https://codecept.io/react).
|
|
3300
|
-
*
|
|
3301
3300
|
* {{> waitForVisible }}
|
|
3302
3301
|
*/
|
|
3303
3302
|
async waitForVisible(locator, sec) {
|
|
@@ -3410,7 +3409,7 @@ class Playwright extends Helper {
|
|
|
3410
3409
|
}
|
|
3411
3410
|
|
|
3412
3411
|
async _getContext() {
|
|
3413
|
-
if (
|
|
3412
|
+
if (this.context) {
|
|
3414
3413
|
return this.context
|
|
3415
3414
|
}
|
|
3416
3415
|
if (this.frame) {
|
|
@@ -4194,25 +4193,22 @@ export function buildLocatorString(locator) {
|
|
|
4194
4193
|
return locator.simplify()
|
|
4195
4194
|
}
|
|
4196
4195
|
|
|
4197
|
-
/**
|
|
4198
|
-
* Checks if a locator is a role locator object (e.g., {role: 'button', text: 'Submit', exact: true})
|
|
4199
|
-
*/
|
|
4200
|
-
function isRoleLocatorObject(locator) {
|
|
4201
|
-
return locator && typeof locator === 'object' && locator.role && !locator.type
|
|
4202
|
-
}
|
|
4203
|
-
|
|
4204
4196
|
/**
|
|
4205
4197
|
* Handles role locator objects by converting them to Playwright's getByRole() API
|
|
4198
|
+
* Accepts both raw objects ({role: 'button', text: 'Submit'}) and Locator-wrapped role objects.
|
|
4206
4199
|
* Returns elements array if role locator, null otherwise
|
|
4207
4200
|
*/
|
|
4208
4201
|
async function handleRoleLocator(context, locator) {
|
|
4209
|
-
|
|
4202
|
+
const loc = new Locator(locator)
|
|
4203
|
+
if (!loc.isRole()) return null
|
|
4210
4204
|
|
|
4205
|
+
const roleObj = loc.locator || {}
|
|
4211
4206
|
const options = {}
|
|
4212
|
-
if (
|
|
4213
|
-
if (
|
|
4207
|
+
if (roleObj.text) options.name = roleObj.text
|
|
4208
|
+
if (roleObj.name) options.name = roleObj.name
|
|
4209
|
+
if (roleObj.exact !== undefined) options.exact = roleObj.exact
|
|
4214
4210
|
|
|
4215
|
-
return context.getByRole(
|
|
4211
|
+
return context.getByRole(roleObj.role, Object.keys(options).length > 0 ? options : undefined).all()
|
|
4216
4212
|
}
|
|
4217
4213
|
|
|
4218
4214
|
async function findByRole(context, locator) {
|
|
@@ -4224,13 +4220,8 @@ async function findByRole(context, locator) {
|
|
|
4224
4220
|
}
|
|
4225
4221
|
|
|
4226
4222
|
async function findElements(matcher, locator) {
|
|
4227
|
-
// Check if locator is a Locator object with react/vue type, or a raw object with react/vue property
|
|
4228
|
-
const isReactLocator = locator.type === 'react' || (locator.locator && locator.locator.react) || locator.react
|
|
4229
|
-
const isVueLocator = locator.type === 'vue' || (locator.locator && locator.locator.vue) || locator.vue
|
|
4230
4223
|
const isPwLocator = locator.type === 'pw' || (locator.locator && locator.locator.pw) || locator.pw
|
|
4231
4224
|
|
|
4232
|
-
if (isReactLocator) return findReact(matcher, locator)
|
|
4233
|
-
if (isVueLocator) return findVue(matcher, locator)
|
|
4234
4225
|
if (isPwLocator) return findByPlaywrightLocator.call(this, matcher, locator)
|
|
4235
4226
|
|
|
4236
4227
|
// Handle role locators with text/exact options (e.g., {role: 'button', text: 'Submit', exact: true})
|
|
@@ -4245,8 +4236,6 @@ async function findElements(matcher, locator) {
|
|
|
4245
4236
|
}
|
|
4246
4237
|
|
|
4247
4238
|
async function findElement(matcher, locator) {
|
|
4248
|
-
if (locator.react) return findReact(matcher, locator)
|
|
4249
|
-
if (locator.vue) return findVue(matcher, locator)
|
|
4250
4239
|
if (locator.pw) return findByPlaywrightLocator.call(this, matcher, locator)
|
|
4251
4240
|
|
|
4252
4241
|
locator = new Locator(locator, 'css')
|
|
@@ -4281,12 +4270,13 @@ async function proceedClick(locator, context = null, options = {}) {
|
|
|
4281
4270
|
assertElementExists(els, locator, 'Clickable element')
|
|
4282
4271
|
}
|
|
4283
4272
|
|
|
4284
|
-
const
|
|
4273
|
+
const opts = store.currentStep?.opts
|
|
4285
4274
|
let element
|
|
4286
|
-
if (elementIndex != null) {
|
|
4275
|
+
if (opts?.elementIndex != null) {
|
|
4287
4276
|
element = selectElement(els, locator, this)
|
|
4288
4277
|
} else {
|
|
4289
|
-
|
|
4278
|
+
const strict = (opts?.exact === false || opts?.strictMode === false) ? false : (this.options.strict || opts?.exact === true || opts?.strictMode === true)
|
|
4279
|
+
if (strict) assertOnlyOneElement(els, locator, this)
|
|
4290
4280
|
element = els.length > 1 ? (await getVisibleElements(els))[0] : els[0]
|
|
4291
4281
|
}
|
|
4292
4282
|
|
|
@@ -4355,7 +4345,9 @@ async function proceedSee(assertType, text, context, strict = false) {
|
|
|
4355
4345
|
if (!context) {
|
|
4356
4346
|
const el = await this.context
|
|
4357
4347
|
|
|
4358
|
-
allText = el.
|
|
4348
|
+
allText = typeof el.url !== 'function' && typeof el.innerText === 'function'
|
|
4349
|
+
? [await el.innerText()]
|
|
4350
|
+
: [await el.locator('body').innerText()]
|
|
4359
4351
|
|
|
4360
4352
|
description = 'web application'
|
|
4361
4353
|
} else {
|
|
@@ -4425,12 +4417,9 @@ async function findFields(locator, context = null) {
|
|
|
4425
4417
|
? loc => findElements.call(this, contextEl, loc)
|
|
4426
4418
|
: loc => this._locate(loc)
|
|
4427
4419
|
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
const roleElements = await handleRoleLocator(matcher, locator)
|
|
4432
|
-
if (roleElements) return roleElements
|
|
4433
|
-
}
|
|
4420
|
+
const matcher = contextEl || (await this.page)
|
|
4421
|
+
const roleElements = await handleRoleLocator(matcher, locator)
|
|
4422
|
+
if (roleElements) return roleElements
|
|
4434
4423
|
|
|
4435
4424
|
const matchedLocator = new Locator(locator)
|
|
4436
4425
|
if (!matchedLocator.isFuzzy()) {
|
|
@@ -4641,12 +4630,16 @@ async function targetCreatedHandler(page) {
|
|
|
4641
4630
|
.catch(() => null)
|
|
4642
4631
|
.then(async () => {
|
|
4643
4632
|
if (this.context && this.context._type === 'Frame') {
|
|
4644
|
-
// we are inside iframe
|
|
4633
|
+
// we are inside iframe via Frame object — refresh handle
|
|
4645
4634
|
const frameEl = await this.context.frameElement()
|
|
4646
4635
|
this.context = await frameEl.contentFrame()
|
|
4647
4636
|
this.contextLocator = null
|
|
4648
4637
|
return
|
|
4649
4638
|
}
|
|
4639
|
+
if (this.context && this.context.constructor && this.context.constructor.name === 'FrameLocator') {
|
|
4640
|
+
// we are inside iframe via FrameLocator — keep it across load events
|
|
4641
|
+
return
|
|
4642
|
+
}
|
|
4650
4643
|
// if context element was in iframe - keep it
|
|
4651
4644
|
// if (await this.context.ownerFrame()) return;
|
|
4652
4645
|
this.context = page
|
|
@@ -4827,7 +4820,7 @@ async function refreshContextSession() {
|
|
|
4827
4820
|
|
|
4828
4821
|
function saveVideoForPage(page, name) {
|
|
4829
4822
|
if (!page.video()) return null
|
|
4830
|
-
const fileName = `${`${
|
|
4823
|
+
const fileName = `${`${store.outputDir}${pathSeparator}videos${pathSeparator}${uuidv4()}_${clearString(name)}`.slice(0, 245)}.webm`
|
|
4831
4824
|
page
|
|
4832
4825
|
.video()
|
|
4833
4826
|
.saveAs(fileName)
|
|
@@ -4844,7 +4837,7 @@ async function saveTraceForContext(context, name) {
|
|
|
4844
4837
|
if (!context) return
|
|
4845
4838
|
if (!context.tracing) return
|
|
4846
4839
|
try {
|
|
4847
|
-
const fileName = `${`${
|
|
4840
|
+
const fileName = `${`${store.outputDir}${pathSeparator}trace${pathSeparator}${uuidv4()}_${clearString(name)}`.slice(0, 245)}.zip`
|
|
4848
4841
|
await context.tracing.stop({ path: fileName })
|
|
4849
4842
|
return fileName
|
|
4850
4843
|
} catch (err) {
|