codeceptjs 4.0.0-rc.8 → 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 +195 -3
- 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 +96 -115
- package/lib/helper/Puppeteer.js +43 -131
- package/lib/helper/WebDriver.js +42 -52
- 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 +58 -0
- 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 +15 -2
- 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 +19 -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/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,18 +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'
|
|
42
|
+
import { selectElement } from './extras/elementSelection.js'
|
|
43
|
+
import { fillRichEditor } from './extras/richTextEditor.js'
|
|
41
44
|
|
|
42
45
|
let playwright
|
|
43
46
|
let perfTiming
|
|
44
47
|
let defaultSelectorEnginesInitialized = false
|
|
45
48
|
|
|
46
|
-
// Use global object to track selector registration across workers
|
|
47
|
-
if (typeof global.__playwrightSelectorsRegistered === 'undefined') {
|
|
48
|
-
global.__playwrightSelectorsRegistered = false
|
|
49
|
-
}
|
|
50
49
|
|
|
51
50
|
const popupStore = new Popup()
|
|
52
51
|
const consoleLogStore = new Console()
|
|
@@ -447,7 +446,7 @@ class Playwright extends Helper {
|
|
|
447
446
|
this.options.recordVideo = { size }
|
|
448
447
|
}
|
|
449
448
|
if (this.options.recordVideo && !this.options.recordVideo.dir) {
|
|
450
|
-
this.options.recordVideo.dir = `${
|
|
449
|
+
this.options.recordVideo.dir = `${store.outputDir}/videos/`
|
|
451
450
|
}
|
|
452
451
|
this.isRemoteBrowser = !!this.playwrightOptions.browserWSEndpoint
|
|
453
452
|
this.isElectron = this.options.browser === 'electron'
|
|
@@ -509,18 +508,18 @@ class Playwright extends Helper {
|
|
|
509
508
|
try {
|
|
510
509
|
// Always wrap in try-catch since selectors might be registered globally across workers
|
|
511
510
|
// Check global flag to avoid re-registration in worker processes
|
|
512
|
-
if (!
|
|
511
|
+
if (!defaultSelectorEnginesInitialized) {
|
|
513
512
|
try {
|
|
514
513
|
await playwright.selectors.register('__value', createValueEngine)
|
|
515
514
|
await playwright.selectors.register('__disabled', createDisabledEngine)
|
|
516
|
-
|
|
515
|
+
defaultSelectorEnginesInitialized = true
|
|
517
516
|
defaultSelectorEnginesInitialized = true
|
|
518
517
|
} catch (e) {
|
|
519
518
|
if (!e.message.includes('already registered')) {
|
|
520
519
|
throw e
|
|
521
520
|
}
|
|
522
521
|
// Selector already registered globally by another worker
|
|
523
|
-
|
|
522
|
+
defaultSelectorEnginesInitialized = true
|
|
524
523
|
defaultSelectorEnginesInitialized = true
|
|
525
524
|
}
|
|
526
525
|
} else {
|
|
@@ -613,7 +612,7 @@ class Playwright extends Helper {
|
|
|
613
612
|
if (this.options.recordVideo) contextOptions.recordVideo = this.options.recordVideo
|
|
614
613
|
if (this.options.recordHar) {
|
|
615
614
|
const harExt = this.options.recordHar.content && this.options.recordHar.content === 'attach' ? 'zip' : 'har'
|
|
616
|
-
const fileName = `${`${
|
|
615
|
+
const fileName = `${`${store.outputDir}${path.sep}har${path.sep}${uuidv4()}_${clearString(this.currentRunningTest.title)}`.slice(0, 245)}.${harExt}`
|
|
617
616
|
const dir = path.dirname(fileName)
|
|
618
617
|
if (!fileExists(dir)) fs.mkdirSync(dir)
|
|
619
618
|
this.options.recordHar.path = fileName
|
|
@@ -756,6 +755,11 @@ class Playwright extends Helper {
|
|
|
756
755
|
}
|
|
757
756
|
|
|
758
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
|
+
|
|
759
763
|
// Stop browser after suite completes
|
|
760
764
|
// For restart strategies: stop after each suite
|
|
761
765
|
// For session mode (restart:false): stop after the last suite
|
|
@@ -1635,7 +1639,7 @@ class Playwright extends Helper {
|
|
|
1635
1639
|
* @returns Promise<void>
|
|
1636
1640
|
*/
|
|
1637
1641
|
async replayFromHar(harFilePath, opts) {
|
|
1638
|
-
const file = path.join(
|
|
1642
|
+
const file = path.join(store.codeceptDir, harFilePath)
|
|
1639
1643
|
|
|
1640
1644
|
if (!fileExists(file)) {
|
|
1641
1645
|
throw new Error(`File at ${file} cannot be found on local system`)
|
|
@@ -1779,8 +1783,7 @@ class Playwright extends Helper {
|
|
|
1779
1783
|
if (elements.length === 0) {
|
|
1780
1784
|
throw new ElementNotFound(locator, 'Element', 'was not found')
|
|
1781
1785
|
}
|
|
1782
|
-
|
|
1783
|
-
return elements[0]
|
|
1786
|
+
return selectElement(elements, locator, this)
|
|
1784
1787
|
}
|
|
1785
1788
|
|
|
1786
1789
|
/**
|
|
@@ -1795,8 +1798,7 @@ class Playwright extends Helper {
|
|
|
1795
1798
|
const context = providedContext || (await this._getContext())
|
|
1796
1799
|
const els = await findCheckable.call(this, locator, context)
|
|
1797
1800
|
assertElementExists(els[0], locator, 'Checkbox or radio')
|
|
1798
|
-
|
|
1799
|
-
return els[0]
|
|
1801
|
+
return selectElement(els, locator, this)
|
|
1800
1802
|
}
|
|
1801
1803
|
|
|
1802
1804
|
/**
|
|
@@ -2048,7 +2050,7 @@ class Playwright extends Helper {
|
|
|
2048
2050
|
const filePath = await download.path()
|
|
2049
2051
|
fileName = fileName || `downloads/${path.basename(filePath)}`
|
|
2050
2052
|
|
|
2051
|
-
const downloadPath = path.join(
|
|
2053
|
+
const downloadPath = path.join(store.outputDir, fileName)
|
|
2052
2054
|
if (!fs.existsSync(path.dirname(downloadPath))) {
|
|
2053
2055
|
fs.mkdirSync(path.dirname(downloadPath), '0777')
|
|
2054
2056
|
}
|
|
@@ -2079,15 +2081,6 @@ class Playwright extends Helper {
|
|
|
2079
2081
|
return proceedClick.call(this, locator, context, options)
|
|
2080
2082
|
}
|
|
2081
2083
|
|
|
2082
|
-
/**
|
|
2083
|
-
* Clicks link and waits for navigation (deprecated)
|
|
2084
|
-
*/
|
|
2085
|
-
async clickLink(locator, context = null) {
|
|
2086
|
-
console.log('clickLink deprecated: Playwright automatically waits for navigation to happen.')
|
|
2087
|
-
console.log('Replace I.clickLink with I.click')
|
|
2088
|
-
return this.click(locator, context)
|
|
2089
|
-
}
|
|
2090
|
-
|
|
2091
2084
|
/**
|
|
2092
2085
|
* {{> forceClick }}
|
|
2093
2086
|
*/
|
|
@@ -2232,6 +2225,7 @@ class Playwright extends Helper {
|
|
|
2232
2225
|
* {{> pressKeyWithKeyNormalization }}
|
|
2233
2226
|
*/
|
|
2234
2227
|
async pressKey(key) {
|
|
2228
|
+
await checkFocusBeforePressKey(this, key)
|
|
2235
2229
|
const modifiers = []
|
|
2236
2230
|
if (Array.isArray(key)) {
|
|
2237
2231
|
for (let k of key) {
|
|
@@ -2260,6 +2254,8 @@ class Playwright extends Helper {
|
|
|
2260
2254
|
* {{> type }}
|
|
2261
2255
|
*/
|
|
2262
2256
|
async type(keys, delay = null) {
|
|
2257
|
+
await checkFocusBeforeType(this)
|
|
2258
|
+
|
|
2263
2259
|
// Always use page.keyboard.type for any string (including single character and national characters).
|
|
2264
2260
|
if (!Array.isArray(keys)) {
|
|
2265
2261
|
keys = keys.toString()
|
|
@@ -2282,14 +2278,17 @@ class Playwright extends Helper {
|
|
|
2282
2278
|
async fillField(field, value, context = null) {
|
|
2283
2279
|
const els = await findFields.call(this, field, context)
|
|
2284
2280
|
assertElementExists(els, field, 'Field')
|
|
2285
|
-
|
|
2286
|
-
|
|
2281
|
+
const el = selectElement(els, field, this)
|
|
2282
|
+
|
|
2283
|
+
await highlightActiveElement.call(this, el)
|
|
2284
|
+
|
|
2285
|
+
if (await fillRichEditor(this, el, value)) {
|
|
2286
|
+
return this._waitForAction()
|
|
2287
|
+
}
|
|
2287
2288
|
|
|
2288
2289
|
await el.clear()
|
|
2289
2290
|
if (store.debugMode) this.debugSection('Focused', await elToString(el, 1))
|
|
2290
2291
|
|
|
2291
|
-
await highlightActiveElement.call(this, el)
|
|
2292
|
-
|
|
2293
2292
|
await el.type(value.toString(), { delay: this.options.pressKeyDelay })
|
|
2294
2293
|
|
|
2295
2294
|
return this._waitForAction()
|
|
@@ -2301,9 +2300,8 @@ class Playwright extends Helper {
|
|
|
2301
2300
|
async clearField(locator, context = null) {
|
|
2302
2301
|
const els = await findFields.call(this, locator, context)
|
|
2303
2302
|
assertElementExists(els, locator, 'Field to clear')
|
|
2304
|
-
if (this.options.strict) assertOnlyOneElement(els, locator, this)
|
|
2305
2303
|
|
|
2306
|
-
const el = els
|
|
2304
|
+
const el = selectElement(els, locator, this)
|
|
2307
2305
|
|
|
2308
2306
|
await highlightActiveElement.call(this, el)
|
|
2309
2307
|
|
|
@@ -2318,10 +2316,10 @@ class Playwright extends Helper {
|
|
|
2318
2316
|
async appendField(field, value, context = null) {
|
|
2319
2317
|
const els = await findFields.call(this, field, context)
|
|
2320
2318
|
assertElementExists(els, field, 'Field')
|
|
2321
|
-
|
|
2322
|
-
await highlightActiveElement.call(this,
|
|
2323
|
-
await
|
|
2324
|
-
await
|
|
2319
|
+
const el = selectElement(els, field, this)
|
|
2320
|
+
await highlightActiveElement.call(this, el)
|
|
2321
|
+
await el.press('End')
|
|
2322
|
+
await el.type(value.toString(), { delay: this.options.pressKeyDelay })
|
|
2325
2323
|
return this._waitForAction()
|
|
2326
2324
|
}
|
|
2327
2325
|
|
|
@@ -2346,29 +2344,31 @@ class Playwright extends Helper {
|
|
|
2346
2344
|
*
|
|
2347
2345
|
*/
|
|
2348
2346
|
async attachFile(locator, pathToFile, context = null) {
|
|
2349
|
-
const file = path.join(
|
|
2347
|
+
const file = path.join(store.codeceptDir, pathToFile)
|
|
2350
2348
|
|
|
2351
2349
|
if (!fileExists(file)) {
|
|
2352
2350
|
throw new Error(`File at ${file} can not be found on local system`)
|
|
2353
2351
|
}
|
|
2354
2352
|
const els = await findFields.call(this, locator, context)
|
|
2355
2353
|
if (els.length) {
|
|
2356
|
-
const
|
|
2357
|
-
const
|
|
2354
|
+
const el = selectElement(els, locator, this)
|
|
2355
|
+
const tag = await el.evaluate(el => el.tagName)
|
|
2356
|
+
const type = await el.evaluate(el => el.type)
|
|
2358
2357
|
if (tag === 'INPUT' && type === 'file') {
|
|
2359
|
-
await
|
|
2358
|
+
await el.setInputFiles(file)
|
|
2360
2359
|
return this._waitForAction()
|
|
2361
2360
|
}
|
|
2362
2361
|
}
|
|
2363
2362
|
|
|
2364
2363
|
const targetEls = els.length ? els : await this._locate(locator)
|
|
2365
2364
|
assertElementExists(targetEls, locator, 'Element')
|
|
2365
|
+
const el = selectElement(targetEls, locator, this)
|
|
2366
2366
|
const fileData = {
|
|
2367
2367
|
base64Content: base64EncodeFile(file),
|
|
2368
2368
|
fileName: path.basename(file),
|
|
2369
2369
|
mimeType: getMimeType(path.basename(file)),
|
|
2370
2370
|
}
|
|
2371
|
-
await
|
|
2371
|
+
await el.evaluate(dropFile, fileData)
|
|
2372
2372
|
return this._waitForAction()
|
|
2373
2373
|
}
|
|
2374
2374
|
|
|
@@ -2391,23 +2391,23 @@ class Playwright extends Helper {
|
|
|
2391
2391
|
this.debugSection('SelectOption', `Strict: ${JSON.stringify(select)}`)
|
|
2392
2392
|
const els = contextEl ? await findElements.call(this, contextEl, matchedLocator) : await this._locate(matchedLocator)
|
|
2393
2393
|
assertElementExists(els, select, 'Selectable element')
|
|
2394
|
-
return proceedSelect.call(this, pageContext, els
|
|
2394
|
+
return proceedSelect.call(this, pageContext, selectElement(els, select, this), option)
|
|
2395
2395
|
}
|
|
2396
2396
|
|
|
2397
2397
|
// Fuzzy: try combobox
|
|
2398
2398
|
this.debugSection('SelectOption', `Fuzzy: "${matchedLocator.value}"`)
|
|
2399
2399
|
const comboboxSearchCtx = contextEl || pageContext
|
|
2400
2400
|
let els = await findByRole(comboboxSearchCtx, { role: 'combobox', name: matchedLocator.value })
|
|
2401
|
-
if (els?.length) return proceedSelect.call(this, pageContext, els
|
|
2401
|
+
if (els?.length) return proceedSelect.call(this, pageContext, selectElement(els, select, this), option)
|
|
2402
2402
|
|
|
2403
2403
|
// Fuzzy: try listbox
|
|
2404
2404
|
els = await findByRole(comboboxSearchCtx, { role: 'listbox', name: matchedLocator.value })
|
|
2405
|
-
if (els?.length) return proceedSelect.call(this, pageContext, els
|
|
2405
|
+
if (els?.length) return proceedSelect.call(this, pageContext, selectElement(els, select, this), option)
|
|
2406
2406
|
|
|
2407
2407
|
// Fuzzy: try native select
|
|
2408
2408
|
els = await findFields.call(this, select, context)
|
|
2409
2409
|
assertElementExists(els, select, 'Selectable element')
|
|
2410
|
-
return proceedSelect.call(this, pageContext, els
|
|
2410
|
+
return proceedSelect.call(this, pageContext, selectElement(els, select, this), option)
|
|
2411
2411
|
}
|
|
2412
2412
|
|
|
2413
2413
|
/**
|
|
@@ -2670,8 +2670,11 @@ class Playwright extends Helper {
|
|
|
2670
2670
|
* @returns {Promise<any>}
|
|
2671
2671
|
*/
|
|
2672
2672
|
async executeScript(fn, arg) {
|
|
2673
|
-
if (
|
|
2674
|
-
|
|
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') {
|
|
2675
2678
|
return this.context.locator(':root').evaluate(fn, arg)
|
|
2676
2679
|
}
|
|
2677
2680
|
return this.page.evaluate.apply(this.page, [fn, arg])
|
|
@@ -2701,15 +2704,12 @@ class Playwright extends Helper {
|
|
|
2701
2704
|
*
|
|
2702
2705
|
*/
|
|
2703
2706
|
async grabTextFrom(locator) {
|
|
2704
|
-
|
|
2705
|
-
if (
|
|
2706
|
-
const
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
this.debugSection('Text', text)
|
|
2711
|
-
return text
|
|
2712
|
-
}
|
|
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
|
|
2713
2713
|
}
|
|
2714
2714
|
|
|
2715
2715
|
const locatorObj = new Locator(locator, 'css')
|
|
@@ -2937,7 +2937,7 @@ class Playwright extends Helper {
|
|
|
2937
2937
|
const els = await this._locate(matchedLocator)
|
|
2938
2938
|
assertElementExists(els, locator)
|
|
2939
2939
|
const snapshot = await els[0].ariaSnapshot()
|
|
2940
|
-
this.debugSection('Aria Snapshot', snapshot)
|
|
2940
|
+
this.debugSection('Aria Snapshot', `${snapshot.split('\n').length} lines`)
|
|
2941
2941
|
return snapshot
|
|
2942
2942
|
}
|
|
2943
2943
|
|
|
@@ -3297,8 +3297,6 @@ class Playwright extends Helper {
|
|
|
3297
3297
|
}
|
|
3298
3298
|
|
|
3299
3299
|
/**
|
|
3300
|
-
* This method accepts [React selectors](https://codecept.io/react).
|
|
3301
|
-
*
|
|
3302
3300
|
* {{> waitForVisible }}
|
|
3303
3301
|
*/
|
|
3304
3302
|
async waitForVisible(locator, sec) {
|
|
@@ -3411,7 +3409,7 @@ class Playwright extends Helper {
|
|
|
3411
3409
|
}
|
|
3412
3410
|
|
|
3413
3411
|
async _getContext() {
|
|
3414
|
-
if (
|
|
3412
|
+
if (this.context) {
|
|
3415
3413
|
return this.context
|
|
3416
3414
|
}
|
|
3417
3415
|
if (this.frame) {
|
|
@@ -4195,25 +4193,22 @@ export function buildLocatorString(locator) {
|
|
|
4195
4193
|
return locator.simplify()
|
|
4196
4194
|
}
|
|
4197
4195
|
|
|
4198
|
-
/**
|
|
4199
|
-
* Checks if a locator is a role locator object (e.g., {role: 'button', text: 'Submit', exact: true})
|
|
4200
|
-
*/
|
|
4201
|
-
function isRoleLocatorObject(locator) {
|
|
4202
|
-
return locator && typeof locator === 'object' && locator.role && !locator.type
|
|
4203
|
-
}
|
|
4204
|
-
|
|
4205
4196
|
/**
|
|
4206
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.
|
|
4207
4199
|
* Returns elements array if role locator, null otherwise
|
|
4208
4200
|
*/
|
|
4209
4201
|
async function handleRoleLocator(context, locator) {
|
|
4210
|
-
|
|
4202
|
+
const loc = new Locator(locator)
|
|
4203
|
+
if (!loc.isRole()) return null
|
|
4211
4204
|
|
|
4205
|
+
const roleObj = loc.locator || {}
|
|
4212
4206
|
const options = {}
|
|
4213
|
-
if (
|
|
4214
|
-
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
|
|
4215
4210
|
|
|
4216
|
-
return context.getByRole(
|
|
4211
|
+
return context.getByRole(roleObj.role, Object.keys(options).length > 0 ? options : undefined).all()
|
|
4217
4212
|
}
|
|
4218
4213
|
|
|
4219
4214
|
async function findByRole(context, locator) {
|
|
@@ -4225,13 +4220,8 @@ async function findByRole(context, locator) {
|
|
|
4225
4220
|
}
|
|
4226
4221
|
|
|
4227
4222
|
async function findElements(matcher, locator) {
|
|
4228
|
-
// Check if locator is a Locator object with react/vue type, or a raw object with react/vue property
|
|
4229
|
-
const isReactLocator = locator.type === 'react' || (locator.locator && locator.locator.react) || locator.react
|
|
4230
|
-
const isVueLocator = locator.type === 'vue' || (locator.locator && locator.locator.vue) || locator.vue
|
|
4231
4223
|
const isPwLocator = locator.type === 'pw' || (locator.locator && locator.locator.pw) || locator.pw
|
|
4232
4224
|
|
|
4233
|
-
if (isReactLocator) return findReact(matcher, locator)
|
|
4234
|
-
if (isVueLocator) return findVue(matcher, locator)
|
|
4235
4225
|
if (isPwLocator) return findByPlaywrightLocator.call(this, matcher, locator)
|
|
4236
4226
|
|
|
4237
4227
|
// Handle role locators with text/exact options (e.g., {role: 'button', text: 'Submit', exact: true})
|
|
@@ -4246,8 +4236,6 @@ async function findElements(matcher, locator) {
|
|
|
4246
4236
|
}
|
|
4247
4237
|
|
|
4248
4238
|
async function findElement(matcher, locator) {
|
|
4249
|
-
if (locator.react) return findReact(matcher, locator)
|
|
4250
|
-
if (locator.vue) return findVue(matcher, locator)
|
|
4251
4239
|
if (locator.pw) return findByPlaywrightLocator.call(this, matcher, locator)
|
|
4252
4240
|
|
|
4253
4241
|
locator = new Locator(locator, 'css')
|
|
@@ -4282,16 +4270,22 @@ async function proceedClick(locator, context = null, options = {}) {
|
|
|
4282
4270
|
assertElementExists(els, locator, 'Clickable element')
|
|
4283
4271
|
}
|
|
4284
4272
|
|
|
4285
|
-
|
|
4286
|
-
|
|
4273
|
+
const opts = store.currentStep?.opts
|
|
4274
|
+
let element
|
|
4275
|
+
if (opts?.elementIndex != null) {
|
|
4276
|
+
element = selectElement(els, locator, this)
|
|
4277
|
+
} else {
|
|
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)
|
|
4280
|
+
element = els.length > 1 ? (await getVisibleElements(els))[0] : els[0]
|
|
4281
|
+
}
|
|
4282
|
+
|
|
4283
|
+
await highlightActiveElement.call(this, element)
|
|
4284
|
+
if (store.debugMode) this.debugSection('Clicked', await elToString(element, 1))
|
|
4287
4285
|
|
|
4288
|
-
/*
|
|
4289
|
-
using the force true options itself but instead dispatching a click
|
|
4290
|
-
*/
|
|
4291
4286
|
if (options.force) {
|
|
4292
|
-
await
|
|
4287
|
+
await element.dispatchEvent('click')
|
|
4293
4288
|
} else {
|
|
4294
|
-
const element = els.length > 1 ? (await getVisibleElements(els))[0] : els[0]
|
|
4295
4289
|
await element.click(options)
|
|
4296
4290
|
}
|
|
4297
4291
|
const promises = []
|
|
@@ -4308,7 +4302,6 @@ async function findClickable(matcher, locator) {
|
|
|
4308
4302
|
|
|
4309
4303
|
if (!matchedLocator.isFuzzy()) {
|
|
4310
4304
|
const els = await findElements.call(this, matcher, matchedLocator)
|
|
4311
|
-
if (this.options.strict) assertOnlyOneElement(els, locator, this)
|
|
4312
4305
|
return els
|
|
4313
4306
|
}
|
|
4314
4307
|
|
|
@@ -4317,42 +4310,27 @@ async function findClickable(matcher, locator) {
|
|
|
4317
4310
|
|
|
4318
4311
|
try {
|
|
4319
4312
|
els = await matcher.getByRole('button', { name: matchedLocator.value }).all()
|
|
4320
|
-
if (els.length)
|
|
4321
|
-
if (this.options.strict) assertOnlyOneElement(els, locator, this)
|
|
4322
|
-
return els
|
|
4323
|
-
}
|
|
4313
|
+
if (els.length) return els
|
|
4324
4314
|
} catch (err) {
|
|
4325
4315
|
// getByRole not supported or failed
|
|
4326
4316
|
}
|
|
4327
4317
|
|
|
4328
4318
|
try {
|
|
4329
4319
|
els = await matcher.getByRole('link', { name: matchedLocator.value }).all()
|
|
4330
|
-
if (els.length)
|
|
4331
|
-
if (this.options.strict) assertOnlyOneElement(els, locator, this)
|
|
4332
|
-
return els
|
|
4333
|
-
}
|
|
4320
|
+
if (els.length) return els
|
|
4334
4321
|
} catch (err) {
|
|
4335
4322
|
// getByRole not supported or failed
|
|
4336
4323
|
}
|
|
4337
4324
|
|
|
4338
4325
|
els = await findElements.call(this, matcher, Locator.clickable.narrow(literal))
|
|
4339
|
-
if (els.length)
|
|
4340
|
-
if (this.options.strict) assertOnlyOneElement(els, locator, this)
|
|
4341
|
-
return els
|
|
4342
|
-
}
|
|
4326
|
+
if (els.length) return els
|
|
4343
4327
|
|
|
4344
4328
|
els = await findElements.call(this, matcher, Locator.clickable.wide(literal))
|
|
4345
|
-
if (els.length)
|
|
4346
|
-
if (this.options.strict) assertOnlyOneElement(els, locator, this)
|
|
4347
|
-
return els
|
|
4348
|
-
}
|
|
4329
|
+
if (els.length) return els
|
|
4349
4330
|
|
|
4350
4331
|
try {
|
|
4351
4332
|
els = await findElements.call(this, matcher, Locator.clickable.self(literal))
|
|
4352
|
-
if (els.length)
|
|
4353
|
-
if (this.options.strict) assertOnlyOneElement(els, locator, this)
|
|
4354
|
-
return els
|
|
4355
|
-
}
|
|
4333
|
+
if (els.length) return els
|
|
4356
4334
|
} catch (err) {
|
|
4357
4335
|
// Do nothing
|
|
4358
4336
|
}
|
|
@@ -4367,7 +4345,9 @@ async function proceedSee(assertType, text, context, strict = false) {
|
|
|
4367
4345
|
if (!context) {
|
|
4368
4346
|
const el = await this.context
|
|
4369
4347
|
|
|
4370
|
-
allText = el.
|
|
4348
|
+
allText = typeof el.url !== 'function' && typeof el.innerText === 'function'
|
|
4349
|
+
? [await el.innerText()]
|
|
4350
|
+
: [await el.locator('body').innerText()]
|
|
4371
4351
|
|
|
4372
4352
|
description = 'web application'
|
|
4373
4353
|
} else {
|
|
@@ -4437,12 +4417,9 @@ async function findFields(locator, context = null) {
|
|
|
4437
4417
|
? loc => findElements.call(this, contextEl, loc)
|
|
4438
4418
|
: loc => this._locate(loc)
|
|
4439
4419
|
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
const roleElements = await handleRoleLocator(matcher, locator)
|
|
4444
|
-
if (roleElements) return roleElements
|
|
4445
|
-
}
|
|
4420
|
+
const matcher = contextEl || (await this.page)
|
|
4421
|
+
const roleElements = await handleRoleLocator(matcher, locator)
|
|
4422
|
+
if (roleElements) return roleElements
|
|
4446
4423
|
|
|
4447
4424
|
const matchedLocator = new Locator(locator)
|
|
4448
4425
|
if (!matchedLocator.isFuzzy()) {
|
|
@@ -4653,12 +4630,16 @@ async function targetCreatedHandler(page) {
|
|
|
4653
4630
|
.catch(() => null)
|
|
4654
4631
|
.then(async () => {
|
|
4655
4632
|
if (this.context && this.context._type === 'Frame') {
|
|
4656
|
-
// we are inside iframe
|
|
4633
|
+
// we are inside iframe via Frame object — refresh handle
|
|
4657
4634
|
const frameEl = await this.context.frameElement()
|
|
4658
4635
|
this.context = await frameEl.contentFrame()
|
|
4659
4636
|
this.contextLocator = null
|
|
4660
4637
|
return
|
|
4661
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
|
+
}
|
|
4662
4643
|
// if context element was in iframe - keep it
|
|
4663
4644
|
// if (await this.context.ownerFrame()) return;
|
|
4664
4645
|
this.context = page
|
|
@@ -4839,7 +4820,7 @@ async function refreshContextSession() {
|
|
|
4839
4820
|
|
|
4840
4821
|
function saveVideoForPage(page, name) {
|
|
4841
4822
|
if (!page.video()) return null
|
|
4842
|
-
const fileName = `${`${
|
|
4823
|
+
const fileName = `${`${store.outputDir}${pathSeparator}videos${pathSeparator}${uuidv4()}_${clearString(name)}`.slice(0, 245)}.webm`
|
|
4843
4824
|
page
|
|
4844
4825
|
.video()
|
|
4845
4826
|
.saveAs(fileName)
|
|
@@ -4856,7 +4837,7 @@ async function saveTraceForContext(context, name) {
|
|
|
4856
4837
|
if (!context) return
|
|
4857
4838
|
if (!context.tracing) return
|
|
4858
4839
|
try {
|
|
4859
|
-
const fileName = `${`${
|
|
4840
|
+
const fileName = `${`${store.outputDir}${pathSeparator}trace${pathSeparator}${uuidv4()}_${clearString(name)}`.slice(0, 245)}.zip`
|
|
4860
4841
|
await context.tracing.stop({ path: fileName })
|
|
4861
4842
|
return fileName
|
|
4862
4843
|
} catch (err) {
|