codeceptjs 4.0.2-beta.8 → 4.0.2
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 +39 -28
- package/bin/codecept.js +15 -2
- package/bin/codeceptq.js +49 -0
- package/bin/mcp-server.js +1189 -0
- 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 +745 -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 +195 -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 +34 -25
- package/lib/command/check.js +2 -1
- package/lib/command/definitions.js +6 -7
- package/lib/command/dryRun.js +24 -5
- package/lib/command/generate.js +3 -1
- package/lib/command/gherkin/snippets.js +5 -4
- package/lib/command/init.js +249 -270
- package/lib/command/list.js +150 -10
- package/lib/command/query.js +218 -0
- package/lib/command/run-multiple.js +3 -1
- package/lib/command/run-workers.js +2 -14
- package/lib/command/run.js +3 -17
- package/lib/command/utils.js +14 -0
- package/lib/command/workers/runTests.js +91 -37
- package/lib/config.js +96 -18
- package/lib/container.js +115 -17
- package/lib/effects.js +17 -0
- package/lib/element/WebElement.js +246 -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/Appium.js +8 -8
- package/lib/helper/FileSystem.js +3 -2
- package/lib/helper/GraphQLDataFactory.js +2 -1
- package/lib/helper/Playwright.js +358 -467
- package/lib/helper/Puppeteer.js +335 -192
- package/lib/helper/WebDriver.js +324 -111
- package/lib/helper/errors/ElementNotFound.js +5 -2
- package/lib/helper/errors/MultipleElementsFound.js +52 -0
- package/lib/helper/errors/NonFocusedType.js +8 -0
- package/lib/helper/extras/Download.js +45 -0
- package/lib/helper/extras/PlaywrightLocator.js +7 -107
- 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/helper/scripts/dropFile.js +11 -0
- package/lib/history.js +3 -2
- package/lib/html.js +103 -16
- package/lib/index.js +9 -1
- package/lib/listener/config.js +6 -4
- package/lib/listener/emptyRun.js +2 -1
- package/lib/listener/globalRetry.js +32 -6
- 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 +158 -16
- package/lib/mocha/cli.js +19 -1
- package/lib/mocha/factory.js +11 -1
- 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 +457 -0
- package/lib/plugin/analyze.js +9 -9
- package/lib/plugin/auth.js +5 -4
- 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 +43 -32
- 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/loaderCheck.js +28 -0
- 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/typescript.js +188 -23
- package/lib/utils.js +77 -3
- package/lib/workers.js +65 -40
- package/package.json +35 -30
- package/typings/index.d.ts +119 -8
- package/typings/promiseBasedTypes.d.ts +3158 -6065
- package/typings/types.d.ts +3453 -6494
- package/docs/webapi/amOnPage.mustache +0 -11
- package/docs/webapi/appendField.mustache +0 -11
- package/docs/webapi/attachFile.mustache +0 -12
- 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 -9
- 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/dontSeeCurrentUrlEquals.mustache +0 -10
- package/docs/webapi/dontSeeElement.mustache +0 -8
- package/docs/webapi/dontSeeElementInDOM.mustache +0 -8
- package/docs/webapi/dontSeeInCurrentUrl.mustache +0 -4
- package/docs/webapi/dontSeeInField.mustache +0 -11
- 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 -16
- 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 -12
- 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/seeCurrentUrlEquals.mustache +0 -11
- package/docs/webapi/seeElement.mustache +0 -8
- package/docs/webapi/seeElementInDOM.mustache +0 -8
- package/docs/webapi/seeInCurrentUrl.mustache +0 -8
- package/docs/webapi/seeInField.mustache +0 -12
- 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 -21
- 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/listener/enhancedGlobalRetry.js +0 -110
- package/lib/plugin/enhancedRetryFailedStep.js +0 -99
- package/lib/plugin/htmlReporter.js +0 -3648
- package/lib/plugin/stepByStepReport.js +0 -427
- package/lib/plugin/subtitles.js +0 -89
- package/lib/retryCoordinator.js +0 -207
package/lib/helper/Puppeteer.js
CHANGED
|
@@ -8,6 +8,7 @@ import promiseRetry from 'promise-retry'
|
|
|
8
8
|
import Locator from '../locator.js'
|
|
9
9
|
import recorder from '../recorder.js'
|
|
10
10
|
import store from '../store.js'
|
|
11
|
+
import { checkFocusBeforeType, checkFocusBeforePressKey } from './extras/focusCheck.js'
|
|
11
12
|
import { includes as stringIncludes } from '../assert/include.js'
|
|
12
13
|
import { urlEquals, equals } from '../assert/equal.js'
|
|
13
14
|
import { empty } from '../assert/empty.js'
|
|
@@ -26,17 +27,25 @@ import {
|
|
|
26
27
|
isModifierKey,
|
|
27
28
|
requireWithFallback,
|
|
28
29
|
normalizeSpacesInString,
|
|
30
|
+
normalizePath,
|
|
31
|
+
resolveUrl,
|
|
32
|
+
getMimeType,
|
|
33
|
+
base64EncodeFile,
|
|
29
34
|
} from '../utils.js'
|
|
30
35
|
import { isColorProperty, convertColorToRGBA } from '../colorUtils.js'
|
|
31
36
|
import ElementNotFound from './errors/ElementNotFound.js'
|
|
37
|
+
import MultipleElementsFound from './errors/MultipleElementsFound.js'
|
|
32
38
|
import RemoteBrowserConnectionRefused from './errors/RemoteBrowserConnectionRefused.js'
|
|
33
39
|
import Popup from './extras/Popup.js'
|
|
34
40
|
import Console from './extras/Console.js'
|
|
35
41
|
import { highlightElement } from './scripts/highlightElement.js'
|
|
36
42
|
import { blurElement } from './scripts/blurElement.js'
|
|
43
|
+
import { dropFile } from './scripts/dropFile.js'
|
|
37
44
|
import { dontSeeElementError, seeElementError, dontSeeElementInDOMError, seeElementInDOMError } from './errors/ElementAssertion.js'
|
|
38
45
|
import { dontSeeTraffic, seeTraffic, grabRecordedNetworkTraffics, stopRecordingTraffic, flushNetworkTraffics } from './network/actions.js'
|
|
39
46
|
import WebElement from '../element/WebElement.js'
|
|
47
|
+
import { selectElement } from './extras/elementSelection.js'
|
|
48
|
+
import { fillRichEditor } from './extras/richTextEditor.js'
|
|
40
49
|
|
|
41
50
|
let puppeteer
|
|
42
51
|
|
|
@@ -266,6 +275,7 @@ class Puppeteer extends Helper {
|
|
|
266
275
|
show: false,
|
|
267
276
|
defaultPopupAction: 'accept',
|
|
268
277
|
highlightElement: false,
|
|
278
|
+
strict: false,
|
|
269
279
|
}
|
|
270
280
|
|
|
271
281
|
return Object.assign(defaults, config)
|
|
@@ -743,7 +753,7 @@ class Puppeteer extends Helper {
|
|
|
743
753
|
}
|
|
744
754
|
|
|
745
755
|
if (this.options.trace) {
|
|
746
|
-
const fileName = `${`${
|
|
756
|
+
const fileName = `${`${store.outputDir}${path.sep}trace${path.sep}${uuidv4()}_${clearString(this.currentRunningTest.title)}`.slice(0, 245)}.json`
|
|
747
757
|
const dir = path.dirname(fileName)
|
|
748
758
|
if (!fileExists(dir)) fs.mkdirSync(dir)
|
|
749
759
|
await this.page.tracing.start({ screenshots: true, path: fileName })
|
|
@@ -811,12 +821,28 @@ class Puppeteer extends Helper {
|
|
|
811
821
|
|
|
812
822
|
/**
|
|
813
823
|
* {{> moveCursorTo }}
|
|
814
|
-
* {{ react }}
|
|
815
824
|
*/
|
|
816
825
|
async moveCursorTo(locator, offsetX = 0, offsetY = 0) {
|
|
817
|
-
|
|
818
|
-
if (
|
|
819
|
-
|
|
826
|
+
let context = null
|
|
827
|
+
if (typeof offsetX !== 'number') {
|
|
828
|
+
context = offsetX
|
|
829
|
+
offsetX = 0
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
let el
|
|
833
|
+
if (context) {
|
|
834
|
+
const contextEls = await findElements.call(this, this.page, context)
|
|
835
|
+
assertElementExists(contextEls, context, 'Context element')
|
|
836
|
+
const els = await findElements.call(this, contextEls[0], locator)
|
|
837
|
+
if (!els || els.length === 0) {
|
|
838
|
+
throw new ElementNotFound(locator, 'Element to move cursor to')
|
|
839
|
+
}
|
|
840
|
+
el = els[0]
|
|
841
|
+
} else {
|
|
842
|
+
el = await this._locateElement(locator)
|
|
843
|
+
if (!el) {
|
|
844
|
+
throw new ElementNotFound(locator, 'Element to move cursor to')
|
|
845
|
+
}
|
|
820
846
|
}
|
|
821
847
|
|
|
822
848
|
// Use manual mouse.move instead of .hover() so the offset can be added to the coordinates
|
|
@@ -965,7 +991,6 @@ class Puppeteer extends Helper {
|
|
|
965
991
|
* const elements = await this.helpers['Puppeteer']._locate({name: 'password'});
|
|
966
992
|
* ```
|
|
967
993
|
*
|
|
968
|
-
* {{ react }}
|
|
969
994
|
*/
|
|
970
995
|
async _locate(locator) {
|
|
971
996
|
const context = await this.context
|
|
@@ -980,10 +1005,17 @@ class Puppeteer extends Helper {
|
|
|
980
1005
|
* const element = await this.helpers['Puppeteer']._locateElement({name: 'password'});
|
|
981
1006
|
* ```
|
|
982
1007
|
*
|
|
983
|
-
* {{ react }}
|
|
984
1008
|
*/
|
|
985
1009
|
async _locateElement(locator) {
|
|
986
1010
|
const context = await this.context
|
|
1011
|
+
const elementIndex = store.currentStep?.opts?.elementIndex
|
|
1012
|
+
if (this.options.strict || elementIndex) {
|
|
1013
|
+
const elements = await findElements.call(this, context, locator)
|
|
1014
|
+
if (elements.length === 0) {
|
|
1015
|
+
throw new ElementNotFound(locator, 'Element', 'was not found')
|
|
1016
|
+
}
|
|
1017
|
+
return selectElement(elements, locator, this)
|
|
1018
|
+
}
|
|
987
1019
|
return findElement.call(this, context, locator)
|
|
988
1020
|
}
|
|
989
1021
|
|
|
@@ -1001,7 +1033,7 @@ class Puppeteer extends Helper {
|
|
|
1001
1033
|
if (!els || els.length === 0) {
|
|
1002
1034
|
throw new ElementNotFound(locator, 'Checkbox or radio')
|
|
1003
1035
|
}
|
|
1004
|
-
return els
|
|
1036
|
+
return selectElement(els, locator, this)
|
|
1005
1037
|
}
|
|
1006
1038
|
|
|
1007
1039
|
/**
|
|
@@ -1156,10 +1188,17 @@ class Puppeteer extends Helper {
|
|
|
1156
1188
|
|
|
1157
1189
|
/**
|
|
1158
1190
|
* {{> seeElement }}
|
|
1159
|
-
* {{ react }}
|
|
1160
1191
|
*/
|
|
1161
|
-
async seeElement(locator) {
|
|
1162
|
-
let els
|
|
1192
|
+
async seeElement(locator, context = null) {
|
|
1193
|
+
let els
|
|
1194
|
+
if (context) {
|
|
1195
|
+
const contextPage = await this.context
|
|
1196
|
+
const contextEls = await findElements.call(this, contextPage, context)
|
|
1197
|
+
assertElementExists(contextEls, context, 'Context element')
|
|
1198
|
+
els = await findElements.call(this, contextEls[0], locator)
|
|
1199
|
+
} else {
|
|
1200
|
+
els = await this._locate(locator)
|
|
1201
|
+
}
|
|
1163
1202
|
els = (await Promise.all(els.map(el => el.boundingBox() && el))).filter(v => v)
|
|
1164
1203
|
// Puppeteer visibility was ignored? | Remove when Puppeteer is fixed
|
|
1165
1204
|
els = await Promise.all(els.map(async el => (await el.evaluate(node => window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none')) && el))
|
|
@@ -1172,10 +1211,17 @@ class Puppeteer extends Helper {
|
|
|
1172
1211
|
|
|
1173
1212
|
/**
|
|
1174
1213
|
* {{> dontSeeElement }}
|
|
1175
|
-
* {{ react }}
|
|
1176
1214
|
*/
|
|
1177
|
-
async dontSeeElement(locator) {
|
|
1178
|
-
let els
|
|
1215
|
+
async dontSeeElement(locator, context = null) {
|
|
1216
|
+
let els
|
|
1217
|
+
if (context) {
|
|
1218
|
+
const contextPage = await this.context
|
|
1219
|
+
const contextEls = await findElements.call(this, contextPage, context)
|
|
1220
|
+
assertElementExists(contextEls, context, 'Context element')
|
|
1221
|
+
els = await findElements.call(this, contextEls[0], locator)
|
|
1222
|
+
} else {
|
|
1223
|
+
els = await this._locate(locator)
|
|
1224
|
+
}
|
|
1179
1225
|
els = (await Promise.all(els.map(el => el.boundingBox() && el))).filter(v => v)
|
|
1180
1226
|
// Puppeteer visibility was ignored? | Remove when Puppeteer is fixed
|
|
1181
1227
|
els = await Promise.all(els.map(async el => (await el.evaluate(node => window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none')) && el))
|
|
@@ -1213,7 +1259,6 @@ class Puppeteer extends Helper {
|
|
|
1213
1259
|
/**
|
|
1214
1260
|
* {{> click }}
|
|
1215
1261
|
*
|
|
1216
|
-
* {{ react }}
|
|
1217
1262
|
*/
|
|
1218
1263
|
async click(locator = '//body', context = null) {
|
|
1219
1264
|
return proceedClick.call(this, locator, context)
|
|
@@ -1222,7 +1267,6 @@ class Puppeteer extends Helper {
|
|
|
1222
1267
|
/**
|
|
1223
1268
|
* {{> forceClick }}
|
|
1224
1269
|
*
|
|
1225
|
-
* {{ react }}
|
|
1226
1270
|
*/
|
|
1227
1271
|
async forceClick(locator, context = null) {
|
|
1228
1272
|
let matcher = await this.context
|
|
@@ -1252,7 +1296,6 @@ class Puppeteer extends Helper {
|
|
|
1252
1296
|
/**
|
|
1253
1297
|
* {{> clickLink }}
|
|
1254
1298
|
*
|
|
1255
|
-
* {{ react }}
|
|
1256
1299
|
*/
|
|
1257
1300
|
async clickLink(locator, context = null) {
|
|
1258
1301
|
return proceedClick.call(this, locator, context, { waitForNavigation: true })
|
|
@@ -1276,7 +1319,7 @@ class Puppeteer extends Helper {
|
|
|
1276
1319
|
* @param {string} [downloadPath='downloads'] change this parameter to set another directory for saving
|
|
1277
1320
|
*/
|
|
1278
1321
|
async handleDownloads(downloadPath = 'downloads') {
|
|
1279
|
-
downloadPath = path.join(
|
|
1322
|
+
downloadPath = path.join(store.outputDir, downloadPath)
|
|
1280
1323
|
if (!fs.existsSync(downloadPath)) {
|
|
1281
1324
|
fs.mkdirSync(downloadPath, '0777')
|
|
1282
1325
|
}
|
|
@@ -1338,7 +1381,7 @@ class Puppeteer extends Helper {
|
|
|
1338
1381
|
},
|
|
1339
1382
|
})
|
|
1340
1383
|
|
|
1341
|
-
const outputFile = path.join(`${
|
|
1384
|
+
const outputFile = path.join(`${store.outputDir}/${fileName}`)
|
|
1342
1385
|
|
|
1343
1386
|
try {
|
|
1344
1387
|
await new Promise((resolve, reject) => {
|
|
@@ -1360,7 +1403,6 @@ class Puppeteer extends Helper {
|
|
|
1360
1403
|
/**
|
|
1361
1404
|
* {{> doubleClick }}
|
|
1362
1405
|
*
|
|
1363
|
-
* {{ react }}
|
|
1364
1406
|
*/
|
|
1365
1407
|
async doubleClick(locator, context = null) {
|
|
1366
1408
|
return proceedClick.call(this, locator, context, { clickCount: 2 })
|
|
@@ -1369,7 +1411,6 @@ class Puppeteer extends Helper {
|
|
|
1369
1411
|
/**
|
|
1370
1412
|
* {{> rightClick }}
|
|
1371
1413
|
*
|
|
1372
|
-
* {{ react }}
|
|
1373
1414
|
*/
|
|
1374
1415
|
async rightClick(locator, context = null) {
|
|
1375
1416
|
return proceedClick.call(this, locator, context, { button: 'right' })
|
|
@@ -1498,6 +1539,7 @@ class Puppeteer extends Helper {
|
|
|
1498
1539
|
* {{> pressKeyWithKeyNormalization }}
|
|
1499
1540
|
*/
|
|
1500
1541
|
async pressKey(key) {
|
|
1542
|
+
await checkFocusBeforePressKey(this, key)
|
|
1501
1543
|
const modifiers = []
|
|
1502
1544
|
if (Array.isArray(key)) {
|
|
1503
1545
|
for (let k of key) {
|
|
@@ -1526,6 +1568,8 @@ class Puppeteer extends Helper {
|
|
|
1526
1568
|
* {{> type }}
|
|
1527
1569
|
*/
|
|
1528
1570
|
async type(keys, delay = null) {
|
|
1571
|
+
await checkFocusBeforeType(this)
|
|
1572
|
+
|
|
1529
1573
|
if (!Array.isArray(keys)) {
|
|
1530
1574
|
keys = keys.toString()
|
|
1531
1575
|
keys = keys.split('')
|
|
@@ -1539,12 +1583,20 @@ class Puppeteer extends Helper {
|
|
|
1539
1583
|
|
|
1540
1584
|
/**
|
|
1541
1585
|
* {{> fillField }}
|
|
1542
|
-
* {{ react }}
|
|
1543
1586
|
*/
|
|
1544
|
-
async fillField(field, value) {
|
|
1545
|
-
|
|
1587
|
+
async fillField(field, value, context = null) {
|
|
1588
|
+
let els = await findVisibleFields.call(this, field, context)
|
|
1589
|
+
if (!els.length) {
|
|
1590
|
+
els = await findFields.call(this, field, context)
|
|
1591
|
+
}
|
|
1546
1592
|
assertElementExists(els, field, 'Field')
|
|
1547
|
-
const el = els
|
|
1593
|
+
const el = selectElement(els, field, this)
|
|
1594
|
+
|
|
1595
|
+
if (await fillRichEditor(this, el, value)) {
|
|
1596
|
+
highlightActiveElement.call(this, el, await this._getContext())
|
|
1597
|
+
return this._waitForAction()
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1548
1600
|
const tag = await el.getProperty('tagName').then(el => el.jsonValue())
|
|
1549
1601
|
const editable = await el.getProperty('contenteditable').then(el => el.jsonValue())
|
|
1550
1602
|
if (tag === 'INPUT' || tag === 'TEXTAREA') {
|
|
@@ -1562,38 +1614,38 @@ class Puppeteer extends Helper {
|
|
|
1562
1614
|
/**
|
|
1563
1615
|
* {{> clearField }}
|
|
1564
1616
|
*/
|
|
1565
|
-
async clearField(field) {
|
|
1566
|
-
return this.fillField(field, '')
|
|
1617
|
+
async clearField(field, context = null) {
|
|
1618
|
+
return this.fillField(field, '', context)
|
|
1567
1619
|
}
|
|
1568
1620
|
|
|
1569
1621
|
/**
|
|
1570
1622
|
* {{> appendField }}
|
|
1571
1623
|
*
|
|
1572
|
-
* {{ react }}
|
|
1573
1624
|
*/
|
|
1574
|
-
async appendField(field, value) {
|
|
1575
|
-
const els = await findVisibleFields.call(this, field)
|
|
1625
|
+
async appendField(field, value, context = null) {
|
|
1626
|
+
const els = await findVisibleFields.call(this, field, context)
|
|
1576
1627
|
assertElementExists(els, field, 'Field')
|
|
1577
|
-
|
|
1578
|
-
await
|
|
1579
|
-
await
|
|
1628
|
+
const el = selectElement(els, field, this)
|
|
1629
|
+
highlightActiveElement.call(this, el, await this._getContext())
|
|
1630
|
+
await el.press('End')
|
|
1631
|
+
await el.type(value.toString(), { delay: this.options.pressKeyDelay })
|
|
1580
1632
|
return this._waitForAction()
|
|
1581
1633
|
}
|
|
1582
1634
|
|
|
1583
1635
|
/**
|
|
1584
1636
|
* {{> seeInField }}
|
|
1585
1637
|
*/
|
|
1586
|
-
async seeInField(field, value) {
|
|
1638
|
+
async seeInField(field, value, context = null) {
|
|
1587
1639
|
const _value = typeof value === 'boolean' ? value : value.toString()
|
|
1588
|
-
return proceedSeeInField.call(this, 'assert', field, _value)
|
|
1640
|
+
return proceedSeeInField.call(this, 'assert', field, _value, context)
|
|
1589
1641
|
}
|
|
1590
1642
|
|
|
1591
1643
|
/**
|
|
1592
1644
|
* {{> dontSeeInField }}
|
|
1593
1645
|
*/
|
|
1594
|
-
async dontSeeInField(field, value) {
|
|
1646
|
+
async dontSeeInField(field, value, context = null) {
|
|
1595
1647
|
const _value = typeof value === 'boolean' ? value : value.toString()
|
|
1596
|
-
return proceedSeeInField.call(this, 'negate', field, _value)
|
|
1648
|
+
return proceedSeeInField.call(this, 'negate', field, _value, context)
|
|
1597
1649
|
}
|
|
1598
1650
|
|
|
1599
1651
|
/**
|
|
@@ -1601,54 +1653,75 @@ class Puppeteer extends Helper {
|
|
|
1601
1653
|
*
|
|
1602
1654
|
* {{> attachFile }}
|
|
1603
1655
|
*/
|
|
1604
|
-
async attachFile(locator, pathToFile) {
|
|
1605
|
-
const file = path.join(
|
|
1656
|
+
async attachFile(locator, pathToFile, context = null) {
|
|
1657
|
+
const file = path.join(store.codeceptDir, pathToFile)
|
|
1606
1658
|
|
|
1607
1659
|
if (!fileExists(file)) {
|
|
1608
1660
|
throw new Error(`File at ${file} can not be found on local system`)
|
|
1609
1661
|
}
|
|
1610
|
-
const els = await findFields.call(this, locator)
|
|
1611
|
-
|
|
1612
|
-
|
|
1662
|
+
const els = await findFields.call(this, locator, context)
|
|
1663
|
+
if (els.length) {
|
|
1664
|
+
const el = selectElement(els, locator, this)
|
|
1665
|
+
const tag = await el.evaluate(el => el.tagName)
|
|
1666
|
+
const type = await el.evaluate(el => el.type)
|
|
1667
|
+
if (tag === 'INPUT' && type === 'file') {
|
|
1668
|
+
await el.uploadFile(file)
|
|
1669
|
+
return this._waitForAction()
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
const targetEls = els.length ? els : await this._locate(locator)
|
|
1674
|
+
assertElementExists(targetEls, locator, 'Element')
|
|
1675
|
+
const el = selectElement(targetEls, locator, this)
|
|
1676
|
+
const fileData = {
|
|
1677
|
+
base64Content: base64EncodeFile(file),
|
|
1678
|
+
fileName: path.basename(file),
|
|
1679
|
+
mimeType: getMimeType(path.basename(file)),
|
|
1680
|
+
}
|
|
1681
|
+
await el.evaluate(dropFile, fileData)
|
|
1613
1682
|
return this._waitForAction()
|
|
1614
1683
|
}
|
|
1615
1684
|
|
|
1616
1685
|
/**
|
|
1617
1686
|
* {{> selectOption }}
|
|
1618
1687
|
*/
|
|
1619
|
-
async selectOption(select, option) {
|
|
1620
|
-
const
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
for (const key in option) {
|
|
1630
|
-
const opt = xpathLocator.literal(option[key])
|
|
1631
|
-
let optEl = await findElements.call(this, el, { xpath: Locator.select.byVisibleText(opt) })
|
|
1632
|
-
if (optEl.length) {
|
|
1633
|
-
this._evaluateHandeInContext(el => (el.selected = true), optEl[0])
|
|
1634
|
-
continue
|
|
1635
|
-
}
|
|
1636
|
-
optEl = await findElements.call(this, el, { xpath: Locator.select.byValue(opt) })
|
|
1637
|
-
if (optEl.length) {
|
|
1638
|
-
this._evaluateHandeInContext(el => (el.selected = true), optEl[0])
|
|
1639
|
-
}
|
|
1688
|
+
async selectOption(select, option, context = null) {
|
|
1689
|
+
const pageContext = await this._getContext()
|
|
1690
|
+
const matchedLocator = new Locator(select)
|
|
1691
|
+
|
|
1692
|
+
let contextEl
|
|
1693
|
+
if (context) {
|
|
1694
|
+
const contextEls = await findElements.call(this, pageContext, context)
|
|
1695
|
+
assertElementExists(contextEls, context, 'Context element')
|
|
1696
|
+
contextEl = contextEls[0]
|
|
1640
1697
|
}
|
|
1641
|
-
await this._evaluateHandeInContext(element => {
|
|
1642
|
-
element.dispatchEvent(new Event('input', { bubbles: true }))
|
|
1643
|
-
element.dispatchEvent(new Event('change', { bubbles: true }))
|
|
1644
|
-
}, el)
|
|
1645
1698
|
|
|
1646
|
-
|
|
1699
|
+
// Strict locator
|
|
1700
|
+
if (!matchedLocator.isFuzzy()) {
|
|
1701
|
+
this.debugSection('SelectOption', `Strict: ${JSON.stringify(select)}`)
|
|
1702
|
+
const els = contextEl ? await findElements.call(this, contextEl, select) : await this._locate(select)
|
|
1703
|
+
assertElementExists(els, select, 'Selectable element')
|
|
1704
|
+
return proceedSelect.call(this, pageContext, selectElement(els, select, this), option)
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
// Fuzzy: try combobox
|
|
1708
|
+
this.debugSection('SelectOption', `Fuzzy: "${matchedLocator.value}"`)
|
|
1709
|
+
const comboboxSearchCtx = contextEl || pageContext
|
|
1710
|
+
let els = await findByRole(comboboxSearchCtx, { role: 'combobox', name: matchedLocator.value })
|
|
1711
|
+
if (els?.length) return proceedSelect.call(this, pageContext, selectElement(els, select, this), option)
|
|
1712
|
+
|
|
1713
|
+
// Fuzzy: try listbox
|
|
1714
|
+
els = await findByRole(comboboxSearchCtx, { role: 'listbox', name: matchedLocator.value })
|
|
1715
|
+
if (els?.length) return proceedSelect.call(this, pageContext, selectElement(els, select, this), option)
|
|
1716
|
+
|
|
1717
|
+
// Fuzzy: try native select
|
|
1718
|
+
const visibleEls = await findVisibleFields.call(this, select, context)
|
|
1719
|
+
assertElementExists(visibleEls, select, 'Selectable field')
|
|
1720
|
+
return proceedSelect.call(this, pageContext, selectElement(visibleEls, select, this), option)
|
|
1647
1721
|
}
|
|
1648
1722
|
|
|
1649
1723
|
/**
|
|
1650
1724
|
* {{> grabNumberOfVisibleElements }}
|
|
1651
|
-
* {{ react }}
|
|
1652
1725
|
*/
|
|
1653
1726
|
async grabNumberOfVisibleElements(locator) {
|
|
1654
1727
|
let els = await this._locate(locator)
|
|
@@ -1687,10 +1760,29 @@ class Puppeteer extends Helper {
|
|
|
1687
1760
|
urlEquals(this.options.url).negate(url, await this._getPageUrl())
|
|
1688
1761
|
}
|
|
1689
1762
|
|
|
1763
|
+
/**
|
|
1764
|
+
* {{> seeCurrentPathEquals }}
|
|
1765
|
+
*/
|
|
1766
|
+
async seeCurrentPathEquals(path) {
|
|
1767
|
+
const currentUrl = await this._getPageUrl()
|
|
1768
|
+
const baseUrl = this.options.url || 'http://localhost'
|
|
1769
|
+
const actualPath = new URL(currentUrl, baseUrl).pathname
|
|
1770
|
+
return equals('url path').assert(normalizePath(path), normalizePath(actualPath))
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
/**
|
|
1774
|
+
* {{> dontSeeCurrentPathEquals }}
|
|
1775
|
+
*/
|
|
1776
|
+
async dontSeeCurrentPathEquals(path) {
|
|
1777
|
+
const currentUrl = await this._getPageUrl()
|
|
1778
|
+
const baseUrl = this.options.url || 'http://localhost'
|
|
1779
|
+
const actualPath = new URL(currentUrl, baseUrl).pathname
|
|
1780
|
+
return equals('url path').negate(normalizePath(path), normalizePath(actualPath))
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1690
1783
|
/**
|
|
1691
1784
|
* {{> see }}
|
|
1692
1785
|
*
|
|
1693
|
-
* {{ react }}
|
|
1694
1786
|
*/
|
|
1695
1787
|
async see(text, context = null) {
|
|
1696
1788
|
return proceedSee.call(this, 'assert', text, context)
|
|
@@ -1706,7 +1798,6 @@ class Puppeteer extends Helper {
|
|
|
1706
1798
|
/**
|
|
1707
1799
|
* {{> dontSee }}
|
|
1708
1800
|
*
|
|
1709
|
-
* {{ react }}
|
|
1710
1801
|
*/
|
|
1711
1802
|
async dontSee(text, context = null) {
|
|
1712
1803
|
return proceedSee.call(this, 'negate', text, context)
|
|
@@ -1760,7 +1851,6 @@ class Puppeteer extends Helper {
|
|
|
1760
1851
|
/**
|
|
1761
1852
|
* {{> seeNumberOfElements }}
|
|
1762
1853
|
*
|
|
1763
|
-
* {{ react }}
|
|
1764
1854
|
*/
|
|
1765
1855
|
async seeNumberOfElements(locator, num) {
|
|
1766
1856
|
const elements = await this._locate(locator)
|
|
@@ -1770,7 +1860,6 @@ class Puppeteer extends Helper {
|
|
|
1770
1860
|
/**
|
|
1771
1861
|
* {{> seeNumberOfVisibleElements }}
|
|
1772
1862
|
*
|
|
1773
|
-
* {{ react }}
|
|
1774
1863
|
*/
|
|
1775
1864
|
async seeNumberOfVisibleElements(locator, num) {
|
|
1776
1865
|
const res = await this.grabNumberOfVisibleElements(locator)
|
|
@@ -1897,7 +1986,6 @@ class Puppeteer extends Helper {
|
|
|
1897
1986
|
|
|
1898
1987
|
/**
|
|
1899
1988
|
* {{> grabTextFromAll }}
|
|
1900
|
-
* {{ react }}
|
|
1901
1989
|
*/
|
|
1902
1990
|
async grabTextFromAll(locator) {
|
|
1903
1991
|
const els = await this._locate(locator)
|
|
@@ -1910,7 +1998,6 @@ class Puppeteer extends Helper {
|
|
|
1910
1998
|
|
|
1911
1999
|
/**
|
|
1912
2000
|
* {{> grabTextFrom }}
|
|
1913
|
-
* {{ react }}
|
|
1914
2001
|
*/
|
|
1915
2002
|
async grabTextFrom(locator) {
|
|
1916
2003
|
const texts = await this.grabTextFromAll(locator)
|
|
@@ -1971,7 +2058,6 @@ class Puppeteer extends Helper {
|
|
|
1971
2058
|
|
|
1972
2059
|
/**
|
|
1973
2060
|
* {{> grabCssPropertyFromAll }}
|
|
1974
|
-
* {{ react }}
|
|
1975
2061
|
*/
|
|
1976
2062
|
async grabCssPropertyFromAll(locator, cssProperty) {
|
|
1977
2063
|
const els = await this._locate(locator)
|
|
@@ -1983,7 +2069,6 @@ class Puppeteer extends Helper {
|
|
|
1983
2069
|
|
|
1984
2070
|
/**
|
|
1985
2071
|
* {{> grabCssPropertyFrom }}
|
|
1986
|
-
* {{ react }}
|
|
1987
2072
|
*/
|
|
1988
2073
|
async grabCssPropertyFrom(locator, cssProperty) {
|
|
1989
2074
|
const cssValues = await this.grabCssPropertyFromAll(locator, cssProperty)
|
|
@@ -1998,7 +2083,6 @@ class Puppeteer extends Helper {
|
|
|
1998
2083
|
|
|
1999
2084
|
/**
|
|
2000
2085
|
* {{> seeCssPropertiesOnElements }}
|
|
2001
|
-
* {{ react }}
|
|
2002
2086
|
*/
|
|
2003
2087
|
async seeCssPropertiesOnElements(locator, cssProperties) {
|
|
2004
2088
|
const res = await this._locate(locator)
|
|
@@ -2033,7 +2117,6 @@ class Puppeteer extends Helper {
|
|
|
2033
2117
|
|
|
2034
2118
|
/**
|
|
2035
2119
|
* {{> seeAttributesOnElements }}
|
|
2036
|
-
* {{ react }}
|
|
2037
2120
|
*/
|
|
2038
2121
|
async seeAttributesOnElements(locator, attributes) {
|
|
2039
2122
|
const elements = await this._locate(locator)
|
|
@@ -2071,7 +2154,6 @@ class Puppeteer extends Helper {
|
|
|
2071
2154
|
|
|
2072
2155
|
/**
|
|
2073
2156
|
* {{> dragSlider }}
|
|
2074
|
-
* {{ react }}
|
|
2075
2157
|
*/
|
|
2076
2158
|
async dragSlider(locator, offsetX = 0) {
|
|
2077
2159
|
const src = await this._locate(locator)
|
|
@@ -2093,7 +2175,6 @@ class Puppeteer extends Helper {
|
|
|
2093
2175
|
|
|
2094
2176
|
/**
|
|
2095
2177
|
* {{> grabAttributeFromAll }}
|
|
2096
|
-
* {{ react }}
|
|
2097
2178
|
*/
|
|
2098
2179
|
async grabAttributeFromAll(locator, attr) {
|
|
2099
2180
|
const els = await this._locate(locator)
|
|
@@ -2107,7 +2188,6 @@ class Puppeteer extends Helper {
|
|
|
2107
2188
|
|
|
2108
2189
|
/**
|
|
2109
2190
|
* {{> grabAttributeFrom }}
|
|
2110
|
-
* {{ react }}
|
|
2111
2191
|
*/
|
|
2112
2192
|
async grabAttributeFrom(locator, attr) {
|
|
2113
2193
|
const attrs = await this.grabAttributeFromAll(locator, attr)
|
|
@@ -2270,7 +2350,6 @@ class Puppeteer extends Helper {
|
|
|
2270
2350
|
|
|
2271
2351
|
/**
|
|
2272
2352
|
* {{> waitNumberOfVisibleElements }}
|
|
2273
|
-
* {{ react }}
|
|
2274
2353
|
*/
|
|
2275
2354
|
async waitNumberOfVisibleElements(locator, num, sec) {
|
|
2276
2355
|
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
@@ -2318,7 +2397,6 @@ class Puppeteer extends Helper {
|
|
|
2318
2397
|
|
|
2319
2398
|
/**
|
|
2320
2399
|
* {{> waitForElement }}
|
|
2321
|
-
* {{ react }}
|
|
2322
2400
|
*/
|
|
2323
2401
|
async waitForElement(locator, sec) {
|
|
2324
2402
|
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
@@ -2339,7 +2417,6 @@ class Puppeteer extends Helper {
|
|
|
2339
2417
|
/**
|
|
2340
2418
|
* {{> waitForVisible }}
|
|
2341
2419
|
*
|
|
2342
|
-
* {{ react }}
|
|
2343
2420
|
*/
|
|
2344
2421
|
async waitForVisible(locator, sec) {
|
|
2345
2422
|
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
@@ -2424,6 +2501,7 @@ class Puppeteer extends Helper {
|
|
|
2424
2501
|
*/
|
|
2425
2502
|
async waitInUrl(urlPart, sec = null) {
|
|
2426
2503
|
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
2504
|
+
const expectedUrl = resolveUrl(urlPart, this.options.url)
|
|
2427
2505
|
|
|
2428
2506
|
return this.page
|
|
2429
2507
|
.waitForFunction(
|
|
@@ -2432,12 +2510,12 @@ class Puppeteer extends Helper {
|
|
|
2432
2510
|
return currUrl.indexOf(urlPart) > -1
|
|
2433
2511
|
},
|
|
2434
2512
|
{ timeout: waitTimeout },
|
|
2435
|
-
|
|
2513
|
+
expectedUrl,
|
|
2436
2514
|
)
|
|
2437
2515
|
.catch(async e => {
|
|
2438
|
-
const currUrl = await this._getPageUrl()
|
|
2516
|
+
const currUrl = await this._getPageUrl()
|
|
2439
2517
|
if (/Waiting failed:/i.test(e.message) || /failed: timeout/i.test(e.message)) {
|
|
2440
|
-
throw new Error(`expected url to include ${
|
|
2518
|
+
throw new Error(`expected url to include ${expectedUrl}, but found ${currUrl}`)
|
|
2441
2519
|
} else {
|
|
2442
2520
|
throw e
|
|
2443
2521
|
}
|
|
@@ -2449,25 +2527,50 @@ class Puppeteer extends Helper {
|
|
|
2449
2527
|
*/
|
|
2450
2528
|
async waitUrlEquals(urlPart, sec = null) {
|
|
2451
2529
|
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
2530
|
+
const expectedUrl = resolveUrl(urlPart, this.options.url)
|
|
2452
2531
|
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2532
|
+
return this.page
|
|
2533
|
+
.waitForFunction(
|
|
2534
|
+
url => {
|
|
2535
|
+
const currUrl = decodeURIComponent(window.location.href)
|
|
2536
|
+
return currUrl === url
|
|
2537
|
+
},
|
|
2538
|
+
{ timeout: waitTimeout },
|
|
2539
|
+
expectedUrl,
|
|
2540
|
+
)
|
|
2541
|
+
.catch(async e => {
|
|
2542
|
+
const currUrl = await this._getPageUrl()
|
|
2543
|
+
if (/Waiting failed/i.test(e.message) || /failed: timeout/i.test(e.message)) {
|
|
2544
|
+
throw new Error(`expected url to be ${expectedUrl}, but found ${currUrl}`)
|
|
2545
|
+
} else {
|
|
2546
|
+
throw e
|
|
2547
|
+
}
|
|
2548
|
+
})
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2551
|
+
/**
|
|
2552
|
+
* {{> waitCurrentPathEquals }}
|
|
2553
|
+
*/
|
|
2554
|
+
async waitCurrentPathEquals(path, sec = null) {
|
|
2555
|
+
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
|
|
2556
|
+
const normalizedPath = normalizePath(path)
|
|
2457
2557
|
|
|
2458
2558
|
return this.page
|
|
2459
2559
|
.waitForFunction(
|
|
2460
|
-
|
|
2461
|
-
const
|
|
2462
|
-
|
|
2560
|
+
expectedPath => {
|
|
2561
|
+
const actualPath = window.location.pathname
|
|
2562
|
+
const normalizePath = p => (p === '' || p === '/' ? '/' : p.replace(/\/+/g, '/').replace(/\/$/, '') || '/')
|
|
2563
|
+
return normalizePath(actualPath) === expectedPath
|
|
2463
2564
|
},
|
|
2464
2565
|
{ timeout: waitTimeout },
|
|
2465
|
-
|
|
2566
|
+
normalizedPath,
|
|
2466
2567
|
)
|
|
2467
2568
|
.catch(async e => {
|
|
2468
|
-
const currUrl = await this._getPageUrl()
|
|
2569
|
+
const currUrl = await this._getPageUrl()
|
|
2570
|
+
const baseUrl = this.options.url || 'http://localhost'
|
|
2571
|
+
const actualPath = new URL(currUrl, baseUrl).pathname
|
|
2469
2572
|
if (/Waiting failed/i.test(e.message) || /failed: timeout/i.test(e.message)) {
|
|
2470
|
-
throw new Error(`expected
|
|
2573
|
+
throw new Error(`expected path to be ${normalizedPath}, but found ${normalizePath(actualPath)}`)
|
|
2471
2574
|
} else {
|
|
2472
2575
|
throw e
|
|
2473
2576
|
}
|
|
@@ -2890,15 +2993,18 @@ export default Puppeteer
|
|
|
2890
2993
|
* @returns {Promise<Array>} Array of ElementHandle objects
|
|
2891
2994
|
*/
|
|
2892
2995
|
async function findElements(matcher, locator) {
|
|
2893
|
-
// Check if locator is a Locator object with react type, or a raw object with react property
|
|
2894
|
-
const isReactLocator = locator.type === 'react' || (locator.locator && locator.locator.react) || locator.react
|
|
2895
|
-
if (isReactLocator) return findReactElements.call(this, locator)
|
|
2896
|
-
|
|
2897
2996
|
locator = new Locator(locator, 'css')
|
|
2898
|
-
|
|
2997
|
+
|
|
2899
2998
|
// Check if locator is a role locator and call findByRole
|
|
2900
2999
|
if (locator.isRole()) return findByRole.call(this, matcher, locator)
|
|
2901
3000
|
|
|
3001
|
+
// Handle shadow DOM locators with >>> deep descendant combinator
|
|
3002
|
+
// { shadow: ['my-app', 'recipe-hello', 'button'] } => 'my-app >>> recipe-hello >>> button'
|
|
3003
|
+
if (locator.isShadow()) {
|
|
3004
|
+
const shadowSelector = locator.value.join(' >>> ')
|
|
3005
|
+
return matcher.$$(shadowSelector)
|
|
3006
|
+
}
|
|
3007
|
+
|
|
2902
3008
|
// Use proven legacy approach - Puppeteer Locator API doesn't have .all() method
|
|
2903
3009
|
if (!locator.isXPath()) return matcher.$$(locator.simplify())
|
|
2904
3010
|
|
|
@@ -2953,7 +3059,6 @@ async function findElements(matcher, locator) {
|
|
|
2953
3059
|
* @returns {Promise<Object>} Single ElementHandle object
|
|
2954
3060
|
*/
|
|
2955
3061
|
async function findElement(matcher, locator) {
|
|
2956
|
-
if (locator.react) return findReactElements.call(this, locator)
|
|
2957
3062
|
locator = new Locator(locator, 'css')
|
|
2958
3063
|
|
|
2959
3064
|
// Check if locator is a role locator and call findByRole
|
|
@@ -2962,6 +3067,13 @@ async function findElement(matcher, locator) {
|
|
|
2962
3067
|
return elements[0]
|
|
2963
3068
|
}
|
|
2964
3069
|
|
|
3070
|
+
// Handle shadow DOM locators with >>> deep descendant combinator
|
|
3071
|
+
if (locator.isShadow()) {
|
|
3072
|
+
const shadowSelector = locator.value.join(' >>> ')
|
|
3073
|
+
const elements = await matcher.$$(shadowSelector)
|
|
3074
|
+
return elements[0]
|
|
3075
|
+
}
|
|
3076
|
+
|
|
2965
3077
|
// Use proven legacy approach - Puppeteer Locator API doesn't have .first() method
|
|
2966
3078
|
if (!locator.isXPath()) {
|
|
2967
3079
|
const elements = await matcher.$$(locator.simplify())
|
|
@@ -2990,10 +3102,11 @@ async function proceedClick(locator, context = null, options = {}) {
|
|
|
2990
3102
|
} else {
|
|
2991
3103
|
assertElementExists(els, locator, 'Clickable element')
|
|
2992
3104
|
}
|
|
3105
|
+
const el = selectElement(els, locator, this)
|
|
2993
3106
|
|
|
2994
|
-
highlightActiveElement.call(this,
|
|
3107
|
+
highlightActiveElement.call(this, el, await this._getContext())
|
|
2995
3108
|
|
|
2996
|
-
await
|
|
3109
|
+
await el.click(options)
|
|
2997
3110
|
const promises = []
|
|
2998
3111
|
if (options.waitForNavigation) {
|
|
2999
3112
|
promises.push(this.waitForNavigation())
|
|
@@ -3124,43 +3237,57 @@ async function proceedIsChecked(assertType, option) {
|
|
|
3124
3237
|
return truth(`checkable ${option}`, 'to be checked')[assertType](selected)
|
|
3125
3238
|
}
|
|
3126
3239
|
|
|
3127
|
-
async function findVisibleFields(locator) {
|
|
3128
|
-
const els = await findFields.call(this, locator)
|
|
3240
|
+
async function findVisibleFields(locator, context = null) {
|
|
3241
|
+
const els = await findFields.call(this, locator, context)
|
|
3129
3242
|
const visible = await Promise.all(els.map(el => el.boundingBox()))
|
|
3130
3243
|
return els.filter((el, index) => visible[index])
|
|
3131
3244
|
}
|
|
3132
3245
|
|
|
3133
|
-
async function findFields(locator) {
|
|
3246
|
+
async function findFields(locator, context = null) {
|
|
3247
|
+
let contextEl
|
|
3248
|
+
if (context) {
|
|
3249
|
+
const contextPage = await this.context
|
|
3250
|
+
const contextEls = await findElements.call(this, contextPage, context)
|
|
3251
|
+
assertElementExists(contextEls, context, 'Context element')
|
|
3252
|
+
contextEl = contextEls[0]
|
|
3253
|
+
}
|
|
3254
|
+
|
|
3255
|
+
const locateFn = contextEl
|
|
3256
|
+
? loc => findElements.call(this, contextEl, loc)
|
|
3257
|
+
: loc => this._locate(loc)
|
|
3258
|
+
|
|
3134
3259
|
const matchedLocator = new Locator(locator)
|
|
3135
3260
|
if (!matchedLocator.isFuzzy()) {
|
|
3136
|
-
return
|
|
3261
|
+
return locateFn(matchedLocator)
|
|
3137
3262
|
}
|
|
3138
3263
|
const literal = xpathLocator.literal(matchedLocator.value)
|
|
3139
3264
|
|
|
3140
|
-
let els = await
|
|
3265
|
+
let els = await locateFn({ xpath: Locator.field.labelEquals(literal) })
|
|
3141
3266
|
if (els.length) {
|
|
3142
3267
|
return els
|
|
3143
3268
|
}
|
|
3144
3269
|
|
|
3145
|
-
els = await
|
|
3270
|
+
els = await locateFn({ xpath: Locator.field.labelContains(literal) })
|
|
3146
3271
|
if (els.length) {
|
|
3147
3272
|
return els
|
|
3148
3273
|
}
|
|
3149
|
-
els = await
|
|
3274
|
+
els = await locateFn({ xpath: Locator.field.byName(literal) })
|
|
3150
3275
|
if (els.length) {
|
|
3151
3276
|
return els
|
|
3152
3277
|
}
|
|
3153
3278
|
|
|
3154
3279
|
// Try ARIA selector for accessible name
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3280
|
+
if (!contextEl) {
|
|
3281
|
+
try {
|
|
3282
|
+
const page = await this.context
|
|
3283
|
+
els = await page.$$(`::-p-aria(${matchedLocator.value})`)
|
|
3284
|
+
if (els.length) return els
|
|
3285
|
+
} catch (err) {
|
|
3286
|
+
// ARIA selector not supported or failed
|
|
3287
|
+
}
|
|
3161
3288
|
}
|
|
3162
3289
|
|
|
3163
|
-
return
|
|
3290
|
+
return locateFn({ css: matchedLocator.value })
|
|
3164
3291
|
}
|
|
3165
3292
|
|
|
3166
3293
|
async function proceedDragAndDrop(sourceLocator, destinationLocator) {
|
|
@@ -3189,8 +3316,8 @@ async function proceedDragAndDrop(sourceLocator, destinationLocator) {
|
|
|
3189
3316
|
await this._waitForAction()
|
|
3190
3317
|
}
|
|
3191
3318
|
|
|
3192
|
-
async function proceedSeeInField(assertType, field, value) {
|
|
3193
|
-
const els = await findVisibleFields.call(this, field)
|
|
3319
|
+
async function proceedSeeInField(assertType, field, value, context) {
|
|
3320
|
+
const els = await findVisibleFields.call(this, field, context)
|
|
3194
3321
|
assertElementExists(els, field, 'Field')
|
|
3195
3322
|
const el = els[0]
|
|
3196
3323
|
const tag = await el.getProperty('tagName').then(el => el.jsonValue())
|
|
@@ -3303,6 +3430,13 @@ function assertElementExists(res, locator, prefix, suffix) {
|
|
|
3303
3430
|
}
|
|
3304
3431
|
}
|
|
3305
3432
|
|
|
3433
|
+
function assertOnlyOneElement(elements, locator, helper) {
|
|
3434
|
+
if (elements.length > 1) {
|
|
3435
|
+
const webElements = elements.map(el => new WebElement(el, helper))
|
|
3436
|
+
throw new MultipleElementsFound(locator, webElements)
|
|
3437
|
+
}
|
|
3438
|
+
}
|
|
3439
|
+
|
|
3306
3440
|
function $XPath(element, selector) {
|
|
3307
3441
|
const found = document.evaluate(selector, element || document.body, null, 5, null)
|
|
3308
3442
|
const res = []
|
|
@@ -3410,7 +3544,7 @@ function getNormalizedKey(key) {
|
|
|
3410
3544
|
}
|
|
3411
3545
|
|
|
3412
3546
|
function highlightActiveElement(element, context) {
|
|
3413
|
-
if (this.options.highlightElement &&
|
|
3547
|
+
if (this.options.highlightElement && store.debugMode) {
|
|
3414
3548
|
highlightElement(element, context)
|
|
3415
3549
|
}
|
|
3416
3550
|
}
|
|
@@ -3423,75 +3557,6 @@ function _waitForElement(locator, options) {
|
|
|
3423
3557
|
}
|
|
3424
3558
|
}
|
|
3425
3559
|
|
|
3426
|
-
async function findReactElements(locator) {
|
|
3427
|
-
// Handle both Locator objects and raw locator objects
|
|
3428
|
-
const resolved = locator.locator ? locator.locator : toLocatorConfig(locator, 'react')
|
|
3429
|
-
this.debug(`Finding React elements: ${JSON.stringify(resolved)}`)
|
|
3430
|
-
|
|
3431
|
-
// Use createRequire to access require.resolve in ESM
|
|
3432
|
-
const { createRequire } = await import('module')
|
|
3433
|
-
const require = createRequire(import.meta.url)
|
|
3434
|
-
const resqScript = await fs.promises.readFile(require.resolve('resq'), 'utf-8')
|
|
3435
|
-
await this.page.evaluate(resqScript.toString())
|
|
3436
|
-
|
|
3437
|
-
await this.page.evaluate(() => window.resq.waitToLoadReact())
|
|
3438
|
-
const arrayHandle = await this.page.evaluateHandle(
|
|
3439
|
-
obj => {
|
|
3440
|
-
const { selector, props, state } = obj
|
|
3441
|
-
let elements = window.resq.resq$$(selector)
|
|
3442
|
-
if (Object.keys(props).length) {
|
|
3443
|
-
elements = elements.byProps(props)
|
|
3444
|
-
}
|
|
3445
|
-
if (Object.keys(state).length) {
|
|
3446
|
-
elements = elements.byState(state)
|
|
3447
|
-
}
|
|
3448
|
-
|
|
3449
|
-
if (!elements.length) {
|
|
3450
|
-
return []
|
|
3451
|
-
}
|
|
3452
|
-
|
|
3453
|
-
// resq returns an array of HTMLElements if the React component is a fragment
|
|
3454
|
-
// this avoids having nested arrays of nodes which the driver does not understand
|
|
3455
|
-
// [[div, div], [div, div]] => [div, div, div, div]
|
|
3456
|
-
let nodes = []
|
|
3457
|
-
|
|
3458
|
-
elements.forEach(element => {
|
|
3459
|
-
let { node, isFragment } = element
|
|
3460
|
-
|
|
3461
|
-
if (!node) {
|
|
3462
|
-
isFragment = true
|
|
3463
|
-
node = element.children
|
|
3464
|
-
}
|
|
3465
|
-
|
|
3466
|
-
if (isFragment) {
|
|
3467
|
-
nodes = nodes.concat(node)
|
|
3468
|
-
} else {
|
|
3469
|
-
nodes.push(node)
|
|
3470
|
-
}
|
|
3471
|
-
})
|
|
3472
|
-
|
|
3473
|
-
return [...nodes]
|
|
3474
|
-
},
|
|
3475
|
-
{
|
|
3476
|
-
selector: resolved.react,
|
|
3477
|
-
props: resolved.props || {},
|
|
3478
|
-
state: resolved.state || {},
|
|
3479
|
-
},
|
|
3480
|
-
)
|
|
3481
|
-
|
|
3482
|
-
const properties = await arrayHandle.getProperties()
|
|
3483
|
-
const result = []
|
|
3484
|
-
for (const property of properties.values()) {
|
|
3485
|
-
const elementHandle = property.asElement()
|
|
3486
|
-
if (elementHandle) {
|
|
3487
|
-
result.push(elementHandle)
|
|
3488
|
-
}
|
|
3489
|
-
}
|
|
3490
|
-
|
|
3491
|
-
await arrayHandle.dispose()
|
|
3492
|
-
return result
|
|
3493
|
-
}
|
|
3494
|
-
|
|
3495
3560
|
async function findByRole(matcher, locator) {
|
|
3496
3561
|
const resolved = toLocatorConfig(locator, 'role')
|
|
3497
3562
|
const roleSelector = buildRoleSelector(resolved)
|
|
@@ -3541,3 +3606,81 @@ function createRoleTextMatcher(expected, exactMatch) {
|
|
|
3541
3606
|
return value => typeof value === 'string' && value.includes(target)
|
|
3542
3607
|
}
|
|
3543
3608
|
|
|
3609
|
+
async function proceedSelect(context, el, option) {
|
|
3610
|
+
const role = await el.evaluate(e => e.getAttribute('role'))
|
|
3611
|
+
const options = Array.isArray(option) ? option : [option]
|
|
3612
|
+
|
|
3613
|
+
if (role === 'combobox') {
|
|
3614
|
+
this.debugSection('SelectOption', 'Expanding combobox')
|
|
3615
|
+
highlightActiveElement.call(this, el, context)
|
|
3616
|
+
const ariaOwns = await el.evaluate(e => e.getAttribute('aria-owns'))
|
|
3617
|
+
const ariaControls = await el.evaluate(e => e.getAttribute('aria-controls'))
|
|
3618
|
+
await el.click()
|
|
3619
|
+
await this._waitForAction()
|
|
3620
|
+
|
|
3621
|
+
const listboxId = ariaOwns || ariaControls
|
|
3622
|
+
let listbox = null
|
|
3623
|
+
if (listboxId) {
|
|
3624
|
+
const listboxEls = await context.$$( `#${listboxId}`)
|
|
3625
|
+
if (listboxEls.length) listbox = listboxEls[0]
|
|
3626
|
+
}
|
|
3627
|
+
if (!listbox) {
|
|
3628
|
+
const listboxEls = await findByRole.call(this, context, { role: 'listbox' })
|
|
3629
|
+
if (listboxEls?.length) listbox = listboxEls[0]
|
|
3630
|
+
}
|
|
3631
|
+
|
|
3632
|
+
if (listbox) {
|
|
3633
|
+
for (const opt of options) {
|
|
3634
|
+
const optEls = await findByRole.call(this, listbox, { role: 'option', name: opt })
|
|
3635
|
+
if (optEls?.length) {
|
|
3636
|
+
const optEl = optEls[0]
|
|
3637
|
+
this.debugSection('SelectOption', `Clicking: "${opt}"`)
|
|
3638
|
+
highlightActiveElement.call(this, optEl, context)
|
|
3639
|
+
await optEl.click()
|
|
3640
|
+
}
|
|
3641
|
+
}
|
|
3642
|
+
}
|
|
3643
|
+
return this._waitForAction()
|
|
3644
|
+
}
|
|
3645
|
+
|
|
3646
|
+
if (role === 'listbox') {
|
|
3647
|
+
for (const opt of options) {
|
|
3648
|
+
const optEls = await findByRole.call(this, el, { role: 'option', name: opt })
|
|
3649
|
+
if (optEls?.length) {
|
|
3650
|
+
const optEl = optEls[0]
|
|
3651
|
+
this.debugSection('SelectOption', `Clicking: "${opt}"`)
|
|
3652
|
+
highlightActiveElement.call(this, optEl, context)
|
|
3653
|
+
await optEl.click()
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3656
|
+
return this._waitForAction()
|
|
3657
|
+
}
|
|
3658
|
+
|
|
3659
|
+
// Native <select> element
|
|
3660
|
+
const tagName = await el.evaluate(e => e.tagName)
|
|
3661
|
+
if (tagName !== 'SELECT') {
|
|
3662
|
+
throw new Error('Element is not <select>')
|
|
3663
|
+
}
|
|
3664
|
+
highlightActiveElement.call(this, el, context)
|
|
3665
|
+
const optionArray = Array.isArray(option) ? option : [option]
|
|
3666
|
+
|
|
3667
|
+
for (const key in optionArray) {
|
|
3668
|
+
const opt = xpathLocator.literal(optionArray[key])
|
|
3669
|
+
let optEl = await findElements.call(this, el, { xpath: Locator.select.byVisibleText(opt) })
|
|
3670
|
+
if (optEl.length) {
|
|
3671
|
+
this._evaluateHandeInContext(el => (el.selected = true), optEl[0])
|
|
3672
|
+
continue
|
|
3673
|
+
}
|
|
3674
|
+
optEl = await findElements.call(this, el, { xpath: Locator.select.byValue(opt) })
|
|
3675
|
+
if (optEl.length) {
|
|
3676
|
+
this._evaluateHandeInContext(el => (el.selected = true), optEl[0])
|
|
3677
|
+
}
|
|
3678
|
+
}
|
|
3679
|
+
await this._evaluateHandeInContext(element => {
|
|
3680
|
+
element.dispatchEvent(new Event('input', { bubbles: true }))
|
|
3681
|
+
element.dispatchEvent(new Event('change', { bubbles: true }))
|
|
3682
|
+
}, el)
|
|
3683
|
+
|
|
3684
|
+
return this._waitForAction()
|
|
3685
|
+
}
|
|
3686
|
+
|