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/WebDriver.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
let webdriverio
|
|
2
2
|
|
|
3
|
+
import fs from 'fs'
|
|
3
4
|
import assert from 'assert'
|
|
4
5
|
import path from 'path'
|
|
5
6
|
import crypto from 'crypto'
|
|
@@ -9,21 +10,39 @@ import promiseRetry from 'promise-retry'
|
|
|
9
10
|
import { includes as stringIncludes } from '../assert/include.js'
|
|
10
11
|
import { urlEquals, equals } from '../assert/equal.js'
|
|
11
12
|
import store from '../store.js'
|
|
13
|
+
import { checkFocusBeforeType, checkFocusBeforePressKey } from './extras/focusCheck.js'
|
|
12
14
|
import output from '../output.js'
|
|
13
15
|
const { debug } = output
|
|
14
16
|
import { empty } from '../assert/empty.js'
|
|
15
17
|
import { truth } from '../assert/truth.js'
|
|
16
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
xpathLocator,
|
|
20
|
+
fileExists,
|
|
21
|
+
decodeUrl,
|
|
22
|
+
chunkArray,
|
|
23
|
+
convertCssPropertiesToCamelCase,
|
|
24
|
+
screenshotOutputFolder,
|
|
25
|
+
getNormalizedKeyAttributeValue,
|
|
26
|
+
modifierKeys,
|
|
27
|
+
normalizePath,
|
|
28
|
+
resolveUrl,
|
|
29
|
+
getMimeType,
|
|
30
|
+
base64EncodeFile,
|
|
31
|
+
} from '../utils.js'
|
|
17
32
|
import { isColorProperty, convertColorToRGBA } from '../colorUtils.js'
|
|
18
33
|
import ElementNotFound from './errors/ElementNotFound.js'
|
|
34
|
+
import MultipleElementsFound from './errors/MultipleElementsFound.js'
|
|
19
35
|
import ConnectionRefused from './errors/ConnectionRefused.js'
|
|
20
36
|
import Locator from '../locator.js'
|
|
21
37
|
import { highlightElement } from './scripts/highlightElement.js'
|
|
22
38
|
import { focusElement } from './scripts/focusElement.js'
|
|
23
39
|
import { blurElement } from './scripts/blurElement.js'
|
|
24
40
|
import { dontSeeElementError, seeElementError, seeElementInDOMError, dontSeeElementInDOMError } from './errors/ElementAssertion.js'
|
|
41
|
+
import { dropFile } from './scripts/dropFile.js'
|
|
25
42
|
import { dontSeeTraffic, seeTraffic, grabRecordedNetworkTraffics, stopRecordingTraffic, flushNetworkTraffics } from './network/actions.js'
|
|
26
43
|
import WebElement from '../element/WebElement.js'
|
|
44
|
+
import { selectElement } from './extras/elementSelection.js'
|
|
45
|
+
import { fillRichEditor } from './extras/richTextEditor.js'
|
|
27
46
|
|
|
28
47
|
const SHADOW = 'shadow'
|
|
29
48
|
const webRoot = 'body'
|
|
@@ -78,11 +97,7 @@ const config = {}
|
|
|
78
97
|
* WebDriver helper which wraps [webdriverio](http://webdriver.io/) library to
|
|
79
98
|
* manipulate browser using Selenium WebDriver or PhantomJS.
|
|
80
99
|
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
* With the release of WebdriverIO version v8.14.0, and onwards, all driver management hassles are now a thing of the past 🙌. Read more [here](https://webdriver.io/blog/2023/07/31/driver-management/).
|
|
84
|
-
* One of the significant advantages of this update is that you can now get rid of any driver services you previously had to manage, such as
|
|
85
|
-
* `wdio-chromedriver-service`, `wdio-geckodriver-service`, `wdio-edgedriver-service`, `wdio-safaridriver-service`, and even `@wdio/selenium-standalone-service`.
|
|
100
|
+
* No Selenium Server, ChromeDriver, or GeckoDriver to install or start. Since WebdriverIO 9, driver management is fully automatic — WebdriverIO downloads and starts the matching driver for you. Read more [here](https://webdriver.io/blog/2023/07/31/driver-management/). Please check [Testing with WebDriver](https://codecept.io/webdriver/#testing-with-webdriver) for more details.
|
|
86
101
|
*
|
|
87
102
|
* For those who require custom driver options, fear not; WebDriver Helper allows you to pass in driver options through custom WebDriver configuration.
|
|
88
103
|
* If you have a custom grid, use a cloud service, or prefer to run your own driver, there's no need to worry since WebDriver Helper will only start a driver when there are no other connection information settings like hostname or port specified.
|
|
@@ -489,6 +504,7 @@ class WebDriver extends Helper {
|
|
|
489
504
|
keepBrowserState: false,
|
|
490
505
|
deprecationWarnings: false,
|
|
491
506
|
highlightElement: false,
|
|
507
|
+
strict: false,
|
|
492
508
|
}
|
|
493
509
|
|
|
494
510
|
// override defaults with config
|
|
@@ -888,13 +904,6 @@ class WebDriver extends Helper {
|
|
|
888
904
|
return els
|
|
889
905
|
}
|
|
890
906
|
|
|
891
|
-
// special locator type for React
|
|
892
|
-
if (locator.react) {
|
|
893
|
-
const els = await this.browser.react$$(locator.react, locator.props || undefined, locator.state || undefined)
|
|
894
|
-
this.debugSection('Elements', `Found ${els.length} react components`)
|
|
895
|
-
return els
|
|
896
|
-
}
|
|
897
|
-
|
|
898
907
|
// special locator type for ARIA roles
|
|
899
908
|
if (locator.role) {
|
|
900
909
|
return this._locateByRole(locator)
|
|
@@ -1064,7 +1073,6 @@ class WebDriver extends Helper {
|
|
|
1064
1073
|
/**
|
|
1065
1074
|
* {{> click }}
|
|
1066
1075
|
*
|
|
1067
|
-
* {{ react }}
|
|
1068
1076
|
*/
|
|
1069
1077
|
async click(locator, context = null) {
|
|
1070
1078
|
const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick'
|
|
@@ -1076,7 +1084,7 @@ class WebDriver extends Helper {
|
|
|
1076
1084
|
} else {
|
|
1077
1085
|
assertElementExists(res, locator, 'Clickable element')
|
|
1078
1086
|
}
|
|
1079
|
-
const elem =
|
|
1087
|
+
const elem = selectElement(res, locator, this)
|
|
1080
1088
|
highlightActiveElement.call(this, elem)
|
|
1081
1089
|
return this.browser[clickMethod](getElementId(elem))
|
|
1082
1090
|
}
|
|
@@ -1084,7 +1092,6 @@ class WebDriver extends Helper {
|
|
|
1084
1092
|
/**
|
|
1085
1093
|
* {{> forceClick }}
|
|
1086
1094
|
*
|
|
1087
|
-
* {{ react }}
|
|
1088
1095
|
*/
|
|
1089
1096
|
async forceClick(locator, context = null) {
|
|
1090
1097
|
const locateFn = prepareLocateFn.call(this, context)
|
|
@@ -1095,7 +1102,7 @@ class WebDriver extends Helper {
|
|
|
1095
1102
|
} else {
|
|
1096
1103
|
assertElementExists(res, locator, 'Clickable element')
|
|
1097
1104
|
}
|
|
1098
|
-
const elem =
|
|
1105
|
+
const elem = selectElement(res, locator, this)
|
|
1099
1106
|
highlightActiveElement.call(this, elem)
|
|
1100
1107
|
|
|
1101
1108
|
return this.executeScript(el => {
|
|
@@ -1111,7 +1118,6 @@ class WebDriver extends Helper {
|
|
|
1111
1118
|
/**
|
|
1112
1119
|
* {{> doubleClick }}
|
|
1113
1120
|
*
|
|
1114
|
-
* {{ react }}
|
|
1115
1121
|
*/
|
|
1116
1122
|
async doubleClick(locator, context = null) {
|
|
1117
1123
|
const locateFn = prepareLocateFn.call(this, context)
|
|
@@ -1123,7 +1129,7 @@ class WebDriver extends Helper {
|
|
|
1123
1129
|
assertElementExists(res, locator, 'Clickable element')
|
|
1124
1130
|
}
|
|
1125
1131
|
|
|
1126
|
-
const elem =
|
|
1132
|
+
const elem = selectElement(res, locator, this)
|
|
1127
1133
|
highlightActiveElement.call(this, elem)
|
|
1128
1134
|
return elem.doubleClick()
|
|
1129
1135
|
}
|
|
@@ -1131,7 +1137,6 @@ class WebDriver extends Helper {
|
|
|
1131
1137
|
/**
|
|
1132
1138
|
* {{> rightClick }}
|
|
1133
1139
|
*
|
|
1134
|
-
* {{ react }}
|
|
1135
1140
|
*/
|
|
1136
1141
|
async rightClick(locator, context) {
|
|
1137
1142
|
const locateFn = prepareLocateFn.call(this, context)
|
|
@@ -1143,7 +1148,7 @@ class WebDriver extends Helper {
|
|
|
1143
1148
|
assertElementExists(res, locator, 'Clickable element')
|
|
1144
1149
|
}
|
|
1145
1150
|
|
|
1146
|
-
const el =
|
|
1151
|
+
const el = selectElement(res, locator, this)
|
|
1147
1152
|
|
|
1148
1153
|
await el.moveTo()
|
|
1149
1154
|
|
|
@@ -1226,7 +1231,6 @@ class WebDriver extends Helper {
|
|
|
1226
1231
|
/**
|
|
1227
1232
|
* {{> forceRightClick }}
|
|
1228
1233
|
*
|
|
1229
|
-
* {{ react }}
|
|
1230
1234
|
*/
|
|
1231
1235
|
async forceRightClick(locator, context = null) {
|
|
1232
1236
|
const locateFn = prepareLocateFn.call(this, context)
|
|
@@ -1251,15 +1255,19 @@ class WebDriver extends Helper {
|
|
|
1251
1255
|
|
|
1252
1256
|
/**
|
|
1253
1257
|
* {{> fillField }}
|
|
1254
|
-
* {{ react }}
|
|
1255
1258
|
* {{ custom }}
|
|
1256
1259
|
*
|
|
1257
1260
|
*/
|
|
1258
|
-
async fillField(field, value) {
|
|
1259
|
-
const res = await findFields.call(this, field)
|
|
1261
|
+
async fillField(field, value, context = null) {
|
|
1262
|
+
const res = await findFields.call(this, field, context)
|
|
1260
1263
|
assertElementExists(res, field, 'Field')
|
|
1261
|
-
const elem =
|
|
1264
|
+
const elem = selectElement(res, field, this)
|
|
1262
1265
|
highlightActiveElement.call(this, elem)
|
|
1266
|
+
|
|
1267
|
+
if (await fillRichEditor(this, elem, value)) {
|
|
1268
|
+
return
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1263
1271
|
try {
|
|
1264
1272
|
await elem.clearValue()
|
|
1265
1273
|
} catch (err) {
|
|
@@ -1276,12 +1284,11 @@ class WebDriver extends Helper {
|
|
|
1276
1284
|
|
|
1277
1285
|
/**
|
|
1278
1286
|
* {{> appendField }}
|
|
1279
|
-
* {{ react }}
|
|
1280
1287
|
*/
|
|
1281
|
-
async appendField(field, value) {
|
|
1282
|
-
const res = await findFields.call(this, field)
|
|
1288
|
+
async appendField(field, value, context = null) {
|
|
1289
|
+
const res = await findFields.call(this, field, context)
|
|
1283
1290
|
assertElementExists(res, field, 'Field')
|
|
1284
|
-
const elem =
|
|
1291
|
+
const elem = selectElement(res, field, this)
|
|
1285
1292
|
highlightActiveElement.call(this, elem)
|
|
1286
1293
|
return elem.addValue(value.toString())
|
|
1287
1294
|
}
|
|
@@ -1290,10 +1297,10 @@ class WebDriver extends Helper {
|
|
|
1290
1297
|
* {{> clearField }}
|
|
1291
1298
|
*
|
|
1292
1299
|
*/
|
|
1293
|
-
async clearField(field) {
|
|
1294
|
-
const res = await findFields.call(this, field)
|
|
1300
|
+
async clearField(field, context = null) {
|
|
1301
|
+
const res = await findFields.call(this, field, context)
|
|
1295
1302
|
assertElementExists(res, field, 'Field')
|
|
1296
|
-
const elem =
|
|
1303
|
+
const elem = selectElement(res, field, this)
|
|
1297
1304
|
highlightActiveElement.call(this, elem)
|
|
1298
1305
|
return elem.clearValue(getElementId(elem))
|
|
1299
1306
|
}
|
|
@@ -1301,34 +1308,31 @@ class WebDriver extends Helper {
|
|
|
1301
1308
|
/**
|
|
1302
1309
|
* {{> selectOption }}
|
|
1303
1310
|
*/
|
|
1304
|
-
async selectOption(select, option) {
|
|
1305
|
-
const
|
|
1306
|
-
|
|
1307
|
-
const elem = usingFirstElement(res)
|
|
1308
|
-
highlightActiveElement.call(this, elem)
|
|
1311
|
+
async selectOption(select, option, context = null) {
|
|
1312
|
+
const locateFn = prepareLocateFn.call(this, context)
|
|
1313
|
+
const matchedLocator = new Locator(select)
|
|
1309
1314
|
|
|
1310
|
-
|
|
1311
|
-
|
|
1315
|
+
// Strict locator
|
|
1316
|
+
if (!matchedLocator.isFuzzy()) {
|
|
1317
|
+
this.debugSection('SelectOption', `Strict: ${JSON.stringify(select)}`)
|
|
1318
|
+
const els = await locateFn(select)
|
|
1319
|
+
assertElementExists(els, select, 'Selectable element')
|
|
1320
|
+
return proceedSelectOption.call(this, selectElement(els, select, this), option)
|
|
1312
1321
|
}
|
|
1313
1322
|
|
|
1314
|
-
//
|
|
1315
|
-
|
|
1323
|
+
// Fuzzy: try combobox
|
|
1324
|
+
this.debugSection('SelectOption', `Fuzzy: "${matchedLocator.value}"`)
|
|
1325
|
+
let els = await this._locateByRole({ role: 'combobox', text: matchedLocator.value })
|
|
1326
|
+
if (els?.length) return proceedSelectOption.call(this, selectElement(els, select, this), option)
|
|
1316
1327
|
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
if (elementId) return this.browser.elementClick(elementId)
|
|
1321
|
-
}
|
|
1328
|
+
// Fuzzy: try listbox
|
|
1329
|
+
els = await this._locateByRole({ role: 'listbox', text: matchedLocator.value })
|
|
1330
|
+
if (els?.length) return proceedSelectOption.call(this, selectElement(els, select, this), option)
|
|
1322
1331
|
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
els = await forEachAsync(option, async opt => this.browser.findElementsFromElement(getElementId(elem), 'xpath', Locator.select.byValue(xpathLocator.literal(opt))))
|
|
1328
|
-
if (els.length === 0) {
|
|
1329
|
-
throw new ElementNotFound(select, `Option "${option}" in`, 'was not found neither by a visible text nor by a value')
|
|
1330
|
-
}
|
|
1331
|
-
return forEachAsync(els, clickOptionFn)
|
|
1332
|
+
// Fuzzy: try native select
|
|
1333
|
+
const res = await findFields.call(this, select, context)
|
|
1334
|
+
assertElementExists(res, select, 'Selectable field')
|
|
1335
|
+
return proceedSelectOption.call(this, selectElement(res, select, this), option)
|
|
1332
1336
|
}
|
|
1333
1337
|
|
|
1334
1338
|
/**
|
|
@@ -1336,28 +1340,41 @@ class WebDriver extends Helper {
|
|
|
1336
1340
|
*
|
|
1337
1341
|
* {{> attachFile }}
|
|
1338
1342
|
*/
|
|
1339
|
-
async attachFile(locator, pathToFile) {
|
|
1340
|
-
let file = path.join(
|
|
1343
|
+
async attachFile(locator, pathToFile, context = null) {
|
|
1344
|
+
let file = path.join(store.codeceptDir, pathToFile)
|
|
1341
1345
|
if (!fileExists(file)) {
|
|
1342
1346
|
throw new Error(`File at ${file} can not be found on local system`)
|
|
1343
1347
|
}
|
|
1344
1348
|
|
|
1345
|
-
const res = await findFields.call(this, locator)
|
|
1349
|
+
const res = await findFields.call(this, locator, context)
|
|
1346
1350
|
this.debug(`Uploading ${file}`)
|
|
1347
|
-
assertElementExists(res, locator, 'File field')
|
|
1348
|
-
const el = usingFirstElement(res)
|
|
1349
1351
|
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1352
|
+
if (res.length) {
|
|
1353
|
+
const el = selectElement(res, locator, this)
|
|
1354
|
+
const tag = await this.browser.execute(function (elem) { return elem.tagName }, el)
|
|
1355
|
+
const type = await this.browser.execute(function (elem) { return elem.type }, el)
|
|
1356
|
+
if (tag === 'INPUT' && type === 'file') {
|
|
1357
|
+
if (this.options.remoteFileUpload) {
|
|
1358
|
+
try {
|
|
1359
|
+
this.debugSection('File', 'Uploading file to remote server')
|
|
1360
|
+
file = await this.browser.uploadFile(file)
|
|
1361
|
+
} catch (err) {
|
|
1362
|
+
throw new Error(`File can't be transferred to remote server. Set \`remoteFileUpload: false\` in config to upload file locally.\n${err.message}`)
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
return el.addValue(file)
|
|
1357
1366
|
}
|
|
1358
1367
|
}
|
|
1359
1368
|
|
|
1360
|
-
|
|
1369
|
+
const targetRes = res.length ? res : await this._locate(locator)
|
|
1370
|
+
assertElementExists(targetRes, locator, 'Element')
|
|
1371
|
+
const targetEl = selectElement(targetRes, locator, this)
|
|
1372
|
+
const fileData = {
|
|
1373
|
+
base64Content: base64EncodeFile(file),
|
|
1374
|
+
fileName: path.basename(file),
|
|
1375
|
+
mimeType: getMimeType(path.basename(file)),
|
|
1376
|
+
}
|
|
1377
|
+
return this.browser.execute(dropFile, targetEl, fileData)
|
|
1361
1378
|
}
|
|
1362
1379
|
|
|
1363
1380
|
/**
|
|
@@ -1371,7 +1388,7 @@ class WebDriver extends Helper {
|
|
|
1371
1388
|
const res = await findCheckable.call(this, field, locateFn)
|
|
1372
1389
|
|
|
1373
1390
|
assertElementExists(res, field, 'Checkable')
|
|
1374
|
-
const elem =
|
|
1391
|
+
const elem = selectElement(res, field, this)
|
|
1375
1392
|
const elementId = getElementId(elem)
|
|
1376
1393
|
highlightActiveElement.call(this, elem)
|
|
1377
1394
|
|
|
@@ -1392,7 +1409,7 @@ class WebDriver extends Helper {
|
|
|
1392
1409
|
const res = await findCheckable.call(this, field, locateFn)
|
|
1393
1410
|
|
|
1394
1411
|
assertElementExists(res, field, 'Checkable')
|
|
1395
|
-
const elem =
|
|
1412
|
+
const elem = selectElement(res, field, this)
|
|
1396
1413
|
const elementId = getElementId(elem)
|
|
1397
1414
|
highlightActiveElement.call(this, elem)
|
|
1398
1415
|
|
|
@@ -1564,7 +1581,6 @@ class WebDriver extends Helper {
|
|
|
1564
1581
|
/**
|
|
1565
1582
|
* {{> see }}
|
|
1566
1583
|
*
|
|
1567
|
-
* {{ react }}
|
|
1568
1584
|
*/
|
|
1569
1585
|
async see(text, context = null) {
|
|
1570
1586
|
return proceedSee.call(this, 'assert', text, context)
|
|
@@ -1580,7 +1596,6 @@ class WebDriver extends Helper {
|
|
|
1580
1596
|
/**
|
|
1581
1597
|
* {{> dontSee }}
|
|
1582
1598
|
*
|
|
1583
|
-
* {{ react }}
|
|
1584
1599
|
*/
|
|
1585
1600
|
async dontSee(text, context = null) {
|
|
1586
1601
|
return proceedSee.call(this, 'negate', text, context)
|
|
@@ -1590,18 +1605,18 @@ class WebDriver extends Helper {
|
|
|
1590
1605
|
* {{> seeInField }}
|
|
1591
1606
|
*
|
|
1592
1607
|
*/
|
|
1593
|
-
async seeInField(field, value) {
|
|
1608
|
+
async seeInField(field, value, context = null) {
|
|
1594
1609
|
const _value = typeof value === 'boolean' ? value : value.toString()
|
|
1595
|
-
return proceedSeeField.call(this, 'assert', field, _value)
|
|
1610
|
+
return proceedSeeField.call(this, 'assert', field, _value, context)
|
|
1596
1611
|
}
|
|
1597
1612
|
|
|
1598
1613
|
/**
|
|
1599
1614
|
* {{> dontSeeInField }}
|
|
1600
1615
|
*
|
|
1601
1616
|
*/
|
|
1602
|
-
async dontSeeInField(field, value) {
|
|
1617
|
+
async dontSeeInField(field, value, context = null) {
|
|
1603
1618
|
const _value = typeof value === 'boolean' ? value : value.toString()
|
|
1604
|
-
return proceedSeeField.call(this, 'negate', field, _value)
|
|
1619
|
+
return proceedSeeField.call(this, 'negate', field, _value, context)
|
|
1605
1620
|
}
|
|
1606
1621
|
|
|
1607
1622
|
/**
|
|
@@ -1622,11 +1637,11 @@ class WebDriver extends Helper {
|
|
|
1622
1637
|
|
|
1623
1638
|
/**
|
|
1624
1639
|
* {{> seeElement }}
|
|
1625
|
-
* {{ react }}
|
|
1626
1640
|
*
|
|
1627
1641
|
*/
|
|
1628
|
-
async seeElement(locator) {
|
|
1629
|
-
const
|
|
1642
|
+
async seeElement(locator, context = null) {
|
|
1643
|
+
const locateFn = prepareLocateFn.call(this, context)
|
|
1644
|
+
const res = context ? await locateFn(locator) : await this._locate(locator, true)
|
|
1630
1645
|
assertElementExists(res, locator)
|
|
1631
1646
|
const selected = await forEachAsync(res, async el => el.isDisplayed())
|
|
1632
1647
|
try {
|
|
@@ -1638,10 +1653,10 @@ class WebDriver extends Helper {
|
|
|
1638
1653
|
|
|
1639
1654
|
/**
|
|
1640
1655
|
* {{> dontSeeElement }}
|
|
1641
|
-
* {{ react }}
|
|
1642
1656
|
*/
|
|
1643
|
-
async dontSeeElement(locator) {
|
|
1644
|
-
const
|
|
1657
|
+
async dontSeeElement(locator, context = null) {
|
|
1658
|
+
const locateFn = prepareLocateFn.call(this, context)
|
|
1659
|
+
const res = context ? await locateFn(locator) : await this._locate(locator, false)
|
|
1645
1660
|
if (!res || res.length === 0) {
|
|
1646
1661
|
return truth(`elements of ${new Locator(locator)}`, 'to be seen').negate(false)
|
|
1647
1662
|
}
|
|
@@ -1722,7 +1737,6 @@ class WebDriver extends Helper {
|
|
|
1722
1737
|
|
|
1723
1738
|
/**
|
|
1724
1739
|
* {{> seeNumberOfElements }}
|
|
1725
|
-
* {{ react }}
|
|
1726
1740
|
*/
|
|
1727
1741
|
async seeNumberOfElements(locator, num) {
|
|
1728
1742
|
const res = await this._locate(locator)
|
|
@@ -1731,7 +1745,6 @@ class WebDriver extends Helper {
|
|
|
1731
1745
|
|
|
1732
1746
|
/**
|
|
1733
1747
|
* {{> seeNumberOfVisibleElements }}
|
|
1734
|
-
* {{ react }}
|
|
1735
1748
|
*/
|
|
1736
1749
|
async seeNumberOfVisibleElements(locator, num) {
|
|
1737
1750
|
const res = await this.grabNumberOfVisibleElements(locator)
|
|
@@ -1848,6 +1861,26 @@ class WebDriver extends Helper {
|
|
|
1848
1861
|
return urlEquals(this.options.url).negate(url, decodeUrl(res))
|
|
1849
1862
|
}
|
|
1850
1863
|
|
|
1864
|
+
/**
|
|
1865
|
+
* {{> seeCurrentPathEquals }}
|
|
1866
|
+
*/
|
|
1867
|
+
async seeCurrentPathEquals(path) {
|
|
1868
|
+
const currentUrl = await this.browser.getUrl()
|
|
1869
|
+
const baseUrl = this.options.url || 'http://localhost'
|
|
1870
|
+
const actualPath = new URL(currentUrl, baseUrl).pathname
|
|
1871
|
+
return equals('url path').assert(normalizePath(path), normalizePath(actualPath))
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
/**
|
|
1875
|
+
* {{> dontSeeCurrentPathEquals }}
|
|
1876
|
+
*/
|
|
1877
|
+
async dontSeeCurrentPathEquals(path) {
|
|
1878
|
+
const currentUrl = await this.browser.getUrl()
|
|
1879
|
+
const baseUrl = this.options.url || 'http://localhost'
|
|
1880
|
+
const actualPath = new URL(currentUrl, baseUrl).pathname
|
|
1881
|
+
return equals('url path').negate(normalizePath(path), normalizePath(actualPath))
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1851
1884
|
/**
|
|
1852
1885
|
* Wraps [execute](http://webdriver.io/api/protocol/execute.html) command.
|
|
1853
1886
|
*
|
|
@@ -1920,8 +1953,22 @@ class WebDriver extends Helper {
|
|
|
1920
1953
|
* {{> moveCursorTo }}
|
|
1921
1954
|
*/
|
|
1922
1955
|
async moveCursorTo(locator, xOffset, yOffset) {
|
|
1923
|
-
|
|
1924
|
-
|
|
1956
|
+
let context = null
|
|
1957
|
+
if (typeof xOffset !== 'number' && xOffset !== undefined) {
|
|
1958
|
+
context = xOffset
|
|
1959
|
+
xOffset = undefined
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
let res
|
|
1963
|
+
if (context) {
|
|
1964
|
+
const contextRes = await this._locate(withStrictLocator(context), true)
|
|
1965
|
+
assertElementExists(contextRes, context, 'Context element')
|
|
1966
|
+
res = await contextRes[0].$$(withStrictLocator(locator))
|
|
1967
|
+
assertElementExists(res, locator)
|
|
1968
|
+
} else {
|
|
1969
|
+
res = await this._locate(withStrictLocator(locator), true)
|
|
1970
|
+
assertElementExists(res, locator)
|
|
1971
|
+
}
|
|
1925
1972
|
const elem = usingFirstElement(res)
|
|
1926
1973
|
try {
|
|
1927
1974
|
await elem.moveTo({ xOffset, yOffset })
|
|
@@ -2173,6 +2220,7 @@ class WebDriver extends Helper {
|
|
|
2173
2220
|
* {{> pressKeyWithKeyNormalization }}
|
|
2174
2221
|
*/
|
|
2175
2222
|
async pressKey(key) {
|
|
2223
|
+
await checkFocusBeforePressKey(this, key)
|
|
2176
2224
|
const modifiers = []
|
|
2177
2225
|
if (Array.isArray(key)) {
|
|
2178
2226
|
for (let k of key) {
|
|
@@ -2219,6 +2267,8 @@ class WebDriver extends Helper {
|
|
|
2219
2267
|
* {{> type }}
|
|
2220
2268
|
*/
|
|
2221
2269
|
async type(keys, delay = null) {
|
|
2270
|
+
await checkFocusBeforeType(this)
|
|
2271
|
+
|
|
2222
2272
|
if (!Array.isArray(keys)) {
|
|
2223
2273
|
keys = keys.toString()
|
|
2224
2274
|
keys = keys.split('')
|
|
@@ -2471,6 +2521,7 @@ class WebDriver extends Helper {
|
|
|
2471
2521
|
async waitInUrl(urlPart, sec = null) {
|
|
2472
2522
|
const client = this.browser
|
|
2473
2523
|
const aSec = sec || this.options.waitForTimeoutInSeconds
|
|
2524
|
+
const expectedUrl = resolveUrl(urlPart, this.options.url)
|
|
2474
2525
|
let currUrl = ''
|
|
2475
2526
|
|
|
2476
2527
|
return client
|
|
@@ -2478,7 +2529,7 @@ class WebDriver extends Helper {
|
|
|
2478
2529
|
function () {
|
|
2479
2530
|
return this.getUrl().then(res => {
|
|
2480
2531
|
currUrl = decodeUrl(res)
|
|
2481
|
-
return currUrl.indexOf(
|
|
2532
|
+
return currUrl.indexOf(expectedUrl) > -1
|
|
2482
2533
|
})
|
|
2483
2534
|
},
|
|
2484
2535
|
{ timeout: aSec * 1000 },
|
|
@@ -2486,7 +2537,7 @@ class WebDriver extends Helper {
|
|
|
2486
2537
|
.catch(e => {
|
|
2487
2538
|
e = wrapError(e)
|
|
2488
2539
|
if (e.message.indexOf('timeout')) {
|
|
2489
|
-
throw new Error(`expected url to include ${
|
|
2540
|
+
throw new Error(`expected url to include ${expectedUrl}, but found ${currUrl}`)
|
|
2490
2541
|
}
|
|
2491
2542
|
throw e
|
|
2492
2543
|
})
|
|
@@ -2497,22 +2548,47 @@ class WebDriver extends Helper {
|
|
|
2497
2548
|
*/
|
|
2498
2549
|
async waitUrlEquals(urlPart, sec = null) {
|
|
2499
2550
|
const aSec = sec || this.options.waitForTimeoutInSeconds
|
|
2500
|
-
const
|
|
2501
|
-
if (urlPart.indexOf('http') < 0) {
|
|
2502
|
-
urlPart = baseUrl + urlPart
|
|
2503
|
-
}
|
|
2551
|
+
const expectedUrl = resolveUrl(urlPart, this.options.url)
|
|
2504
2552
|
let currUrl = ''
|
|
2505
2553
|
return this.browser
|
|
2506
2554
|
.waitUntil(function () {
|
|
2507
2555
|
return this.getUrl().then(res => {
|
|
2508
2556
|
currUrl = decodeUrl(res)
|
|
2509
|
-
return currUrl ===
|
|
2557
|
+
return currUrl === expectedUrl
|
|
2510
2558
|
})
|
|
2511
2559
|
}, aSec * 1000)
|
|
2512
2560
|
.catch(e => {
|
|
2513
2561
|
e = wrapError(e)
|
|
2514
2562
|
if (e.message.indexOf('timeout')) {
|
|
2515
|
-
throw new Error(`expected url to be ${
|
|
2563
|
+
throw new Error(`expected url to be ${expectedUrl}, but found ${currUrl}`)
|
|
2564
|
+
}
|
|
2565
|
+
throw e
|
|
2566
|
+
})
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2569
|
+
/**
|
|
2570
|
+
* {{> waitCurrentPathEquals }}
|
|
2571
|
+
*/
|
|
2572
|
+
async waitCurrentPathEquals(path, sec = null) {
|
|
2573
|
+
const aSec = sec || this.options.waitForTimeoutInSeconds
|
|
2574
|
+
const normalizedPath = normalizePath(path)
|
|
2575
|
+
const baseUrl = this.options.url || 'http://localhost'
|
|
2576
|
+
let actualPath = ''
|
|
2577
|
+
|
|
2578
|
+
return this.browser
|
|
2579
|
+
.waitUntil(
|
|
2580
|
+
async () => {
|
|
2581
|
+
const currUrl = await this.browser.getUrl()
|
|
2582
|
+
const url = new URL(currUrl, baseUrl)
|
|
2583
|
+
actualPath = url.pathname
|
|
2584
|
+
return normalizePath(actualPath) === normalizedPath
|
|
2585
|
+
},
|
|
2586
|
+
{ timeout: aSec * 1000 },
|
|
2587
|
+
)
|
|
2588
|
+
.catch(e => {
|
|
2589
|
+
e = wrapError(e)
|
|
2590
|
+
if (e.message.indexOf('timeout')) {
|
|
2591
|
+
throw new Error(`expected path to be ${normalizedPath}, but found ${normalizePath(actualPath)}`)
|
|
2516
2592
|
}
|
|
2517
2593
|
throw e
|
|
2518
2594
|
})
|
|
@@ -2994,32 +3070,33 @@ async function findClickable(locator, locateFn) {
|
|
|
2994
3070
|
return await locateFn(locator.value) // by css or xpath
|
|
2995
3071
|
}
|
|
2996
3072
|
|
|
2997
|
-
async function findFields(locator) {
|
|
3073
|
+
async function findFields(locator, context = null) {
|
|
3074
|
+
const locateFn = prepareLocateFn.call(this, context)
|
|
2998
3075
|
locator = new Locator(locator)
|
|
2999
3076
|
|
|
3000
3077
|
if (this._isCustomLocator(locator)) {
|
|
3001
|
-
return
|
|
3078
|
+
return locateFn(locator)
|
|
3002
3079
|
}
|
|
3003
3080
|
|
|
3004
|
-
if (locator.isAccessibilityId() && !this.isWeb) return
|
|
3005
|
-
if (locator.isRole()) return
|
|
3006
|
-
if (!locator.isFuzzy()) return
|
|
3081
|
+
if (locator.isAccessibilityId() && !this.isWeb) return locateFn(locator)
|
|
3082
|
+
if (locator.isRole()) return locateFn(locator)
|
|
3083
|
+
if (!locator.isFuzzy()) return locateFn(locator)
|
|
3007
3084
|
|
|
3008
3085
|
const literal = xpathLocator.literal(locator.value)
|
|
3009
|
-
let els = await
|
|
3086
|
+
let els = await locateFn(Locator.field.labelEquals(literal))
|
|
3010
3087
|
if (els.length) return els
|
|
3011
3088
|
|
|
3012
|
-
els = await
|
|
3089
|
+
els = await locateFn(Locator.field.labelContains(literal))
|
|
3013
3090
|
if (els.length) return els
|
|
3014
3091
|
|
|
3015
|
-
els = await
|
|
3092
|
+
els = await locateFn(Locator.field.byName(literal))
|
|
3016
3093
|
if (els.length) return els
|
|
3017
3094
|
|
|
3018
|
-
return await
|
|
3095
|
+
return await locateFn(locator.value) // by css or xpath
|
|
3019
3096
|
}
|
|
3020
3097
|
|
|
3021
|
-
async function proceedSeeField(assertType, field, value) {
|
|
3022
|
-
const res = await findFields.call(this, field)
|
|
3098
|
+
async function proceedSeeField(assertType, field, value, context) {
|
|
3099
|
+
const res = await findFields.call(this, field, context)
|
|
3023
3100
|
assertElementExists(res, field, 'Field')
|
|
3024
3101
|
const elem = usingFirstElement(res)
|
|
3025
3102
|
const elemId = getElementId(elem)
|
|
@@ -3128,7 +3205,23 @@ async function getElementTextAttributes(element) {
|
|
|
3128
3205
|
const ariaLabel = await this.browser.getElementAttribute(elementId, 'aria-label').catch(() => '')
|
|
3129
3206
|
const placeholder = await this.browser.getElementAttribute(elementId, 'placeholder').catch(() => '')
|
|
3130
3207
|
const innerText = await this.browser.getElementText(elementId).catch(() => '')
|
|
3131
|
-
|
|
3208
|
+
|
|
3209
|
+
// Handle aria-labelledby
|
|
3210
|
+
const labelledBy = await this.browser.getElementAttribute(elementId, 'aria-labelledby').catch(() => '')
|
|
3211
|
+
let labelText = ''
|
|
3212
|
+
if (labelledBy) {
|
|
3213
|
+
try {
|
|
3214
|
+
const labelId = labelledBy.split(' ')[0]
|
|
3215
|
+
const labelEls = await this.browser.$$(`#${labelId}`)
|
|
3216
|
+
if (labelEls?.length) {
|
|
3217
|
+
labelText = await this.browser.getElementText(getElementId(labelEls[0])).catch(() => '')
|
|
3218
|
+
}
|
|
3219
|
+
} catch (e) {
|
|
3220
|
+
// Ignore errors when resolving aria-labelledby
|
|
3221
|
+
}
|
|
3222
|
+
}
|
|
3223
|
+
|
|
3224
|
+
return [ariaLabel, placeholder, innerText, labelText]
|
|
3132
3225
|
}
|
|
3133
3226
|
|
|
3134
3227
|
async function isElementChecked(browser, elementId) {
|
|
@@ -3188,10 +3281,30 @@ function assertElementExists(res, locator, prefix, suffix) {
|
|
|
3188
3281
|
}
|
|
3189
3282
|
|
|
3190
3283
|
function usingFirstElement(els) {
|
|
3284
|
+
const rawIndex = store.currentStep?.opts?.elementIndex
|
|
3285
|
+
if (rawIndex != null && els.length > 1) {
|
|
3286
|
+
let elementIndex = rawIndex
|
|
3287
|
+
if (elementIndex === 'first') elementIndex = 1
|
|
3288
|
+
if (elementIndex === 'last') elementIndex = -1
|
|
3289
|
+
if (Number.isInteger(elementIndex) && elementIndex !== 0) {
|
|
3290
|
+
const idx = elementIndex > 0 ? elementIndex - 1 : els.length + elementIndex
|
|
3291
|
+
if (idx >= 0 && idx < els.length) {
|
|
3292
|
+
debug(`[Elements] Using element #${rawIndex} out of ${els.length}`)
|
|
3293
|
+
return els[idx]
|
|
3294
|
+
}
|
|
3295
|
+
}
|
|
3296
|
+
}
|
|
3191
3297
|
if (els.length > 1) debug(`[Elements] Using first element out of ${els.length}`)
|
|
3192
3298
|
return els[0]
|
|
3193
3299
|
}
|
|
3194
3300
|
|
|
3301
|
+
function assertOnlyOneElement(elements, locator, helper) {
|
|
3302
|
+
if (elements.length > 1) {
|
|
3303
|
+
const webElements = Array.from(elements).map(el => new WebElement(el, helper))
|
|
3304
|
+
throw new MultipleElementsFound(locator, webElements)
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3307
|
+
|
|
3195
3308
|
function getElementId(el) {
|
|
3196
3309
|
// W3C WebDriver web element identifier
|
|
3197
3310
|
// https://w3c.github.io/webdriver/#dfn-web-element-identifier
|
|
@@ -3353,7 +3466,7 @@ function isModifierKey(key) {
|
|
|
3353
3466
|
}
|
|
3354
3467
|
|
|
3355
3468
|
function highlightActiveElement(element) {
|
|
3356
|
-
if (this.options.highlightElement &&
|
|
3469
|
+
if (this.options.highlightElement && store.debugMode) {
|
|
3357
3470
|
highlightElement(element, this.browser)
|
|
3358
3471
|
}
|
|
3359
3472
|
}
|
|
@@ -3364,9 +3477,6 @@ function prepareLocateFn(context) {
|
|
|
3364
3477
|
l = new Locator(l, 'css')
|
|
3365
3478
|
return this._locate(context, true).then(async res => {
|
|
3366
3479
|
assertElementExists(res, context, 'Context element')
|
|
3367
|
-
if (l.react) {
|
|
3368
|
-
return res[0].react$$(l.react, l.props || undefined)
|
|
3369
|
-
}
|
|
3370
3480
|
return res[0].$$(l.simplify())
|
|
3371
3481
|
})
|
|
3372
3482
|
}
|
|
@@ -3376,4 +3486,107 @@ function logEvents(event) {
|
|
|
3376
3486
|
browserLogs.push(event.text) // add log message to the array
|
|
3377
3487
|
}
|
|
3378
3488
|
|
|
3489
|
+
async function proceedSelectOption(elem, option) {
|
|
3490
|
+
const elementId = getElementId(elem)
|
|
3491
|
+
const role = await this.browser.getElementAttribute(elementId, 'role').catch(() => null)
|
|
3492
|
+
const options = Array.isArray(option) ? option : [option]
|
|
3493
|
+
|
|
3494
|
+
if (role === 'combobox') {
|
|
3495
|
+
this.debugSection('SelectOption', 'Expanding combobox')
|
|
3496
|
+
highlightActiveElement.call(this, elem)
|
|
3497
|
+
const ariaOwns = await this.browser.getElementAttribute(elementId, 'aria-owns').catch(() => null)
|
|
3498
|
+
const ariaControls = await this.browser.getElementAttribute(elementId, 'aria-controls').catch(() => null)
|
|
3499
|
+
const ariaLabelledBy = await this.browser.getElementAttribute(elementId, 'aria-labelledby').catch(() => null)
|
|
3500
|
+
await this.browser.elementClick(elementId)
|
|
3501
|
+
|
|
3502
|
+
const listboxId = ariaOwns || ariaControls
|
|
3503
|
+
let listbox = null
|
|
3504
|
+
if (listboxId) {
|
|
3505
|
+
const listboxEls = await this.browser.$$(`#${listboxId}`)
|
|
3506
|
+
if (listboxEls?.length) listbox = listboxEls[0]
|
|
3507
|
+
}
|
|
3508
|
+
if (!listbox && ariaLabelledBy) {
|
|
3509
|
+
// Find listbox with the same aria-labelledby
|
|
3510
|
+
const listboxEls = await this.browser.$$(`[role="listbox"][aria-labelledby="${ariaLabelledBy}"]`)
|
|
3511
|
+
if (listboxEls?.length) listbox = listboxEls[0]
|
|
3512
|
+
}
|
|
3513
|
+
if (!listbox) {
|
|
3514
|
+
// Fallback: find any listbox with the same label
|
|
3515
|
+
const allListboxes = await this.browser.$$(`[role="listbox"]`)
|
|
3516
|
+
for (const lb of allListboxes) {
|
|
3517
|
+
const lbLabelledBy = await this.browser.getElementAttribute(getElementId(lb), 'aria-labelledby').catch(() => '')
|
|
3518
|
+
if (lbLabelledBy === ariaLabelledBy) {
|
|
3519
|
+
listbox = lb
|
|
3520
|
+
break
|
|
3521
|
+
}
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3524
|
+
|
|
3525
|
+
if (listbox) {
|
|
3526
|
+
const listboxElementId = getElementId(listbox)
|
|
3527
|
+
for (const opt of options) {
|
|
3528
|
+
const optEls = await this.browser.findElementsFromElement(listboxElementId, 'xpath', `.//*[@role="option"]`)
|
|
3529
|
+
if (optEls?.length) {
|
|
3530
|
+
for (const optEl of optEls) {
|
|
3531
|
+
const optElId = getElementId(optEl)
|
|
3532
|
+
const text = await this.browser.getElementText(optElId).catch(() => '')
|
|
3533
|
+
if (text && text.includes(opt)) {
|
|
3534
|
+
this.debugSection('SelectOption', `Clicking: "${opt}"`)
|
|
3535
|
+
highlightActiveElement.call(this, optEl)
|
|
3536
|
+
await this.browser.elementClick(optElId)
|
|
3537
|
+
break
|
|
3538
|
+
}
|
|
3539
|
+
}
|
|
3540
|
+
}
|
|
3541
|
+
}
|
|
3542
|
+
}
|
|
3543
|
+
return
|
|
3544
|
+
}
|
|
3545
|
+
|
|
3546
|
+
if (role === 'listbox') {
|
|
3547
|
+
for (const opt of options) {
|
|
3548
|
+
const optEls = await this.browser.findElementsFromElement(elementId, 'xpath', `.//*[@role="option"]`)
|
|
3549
|
+
if (optEls?.length) {
|
|
3550
|
+
for (const optEl of optEls) {
|
|
3551
|
+
const optElId = getElementId(optEl)
|
|
3552
|
+
const text = await this.browser.getElementText(optElId).catch(() => '')
|
|
3553
|
+
if (text && text.includes(opt)) {
|
|
3554
|
+
this.debugSection('SelectOption', `Clicking: "${opt}"`)
|
|
3555
|
+
highlightActiveElement.call(this, optEl)
|
|
3556
|
+
await this.browser.elementClick(optElId)
|
|
3557
|
+
break
|
|
3558
|
+
}
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3561
|
+
}
|
|
3562
|
+
return
|
|
3563
|
+
}
|
|
3564
|
+
|
|
3565
|
+
// Native <select> element
|
|
3566
|
+
highlightActiveElement.call(this, elem)
|
|
3567
|
+
|
|
3568
|
+
if (!Array.isArray(option)) {
|
|
3569
|
+
option = [option]
|
|
3570
|
+
}
|
|
3571
|
+
|
|
3572
|
+
const clickOptionFn = async el => {
|
|
3573
|
+
if (el[0]) el = el[0]
|
|
3574
|
+
const elId = getElementId(el)
|
|
3575
|
+
if (elId) return this.browser.elementClick(elId)
|
|
3576
|
+
}
|
|
3577
|
+
|
|
3578
|
+
// select options by visible text
|
|
3579
|
+
let els = await forEachAsync(option, async opt => this.browser.findElementsFromElement(elementId, 'xpath', Locator.select.byVisibleText(xpathLocator.literal(opt))))
|
|
3580
|
+
|
|
3581
|
+
if (Array.isArray(els) && els.length) {
|
|
3582
|
+
return forEachAsync(els, clickOptionFn)
|
|
3583
|
+
}
|
|
3584
|
+
// select options by value
|
|
3585
|
+
els = await forEachAsync(option, async opt => this.browser.findElementsFromElement(elementId, 'xpath', Locator.select.byValue(xpathLocator.literal(opt))))
|
|
3586
|
+
if (els.length === 0) {
|
|
3587
|
+
throw new ElementNotFound(elem, `Option "${option}" in`, 'was not found neither by a visible text nor by a value')
|
|
3588
|
+
}
|
|
3589
|
+
return forEachAsync(els, clickOptionFn)
|
|
3590
|
+
}
|
|
3591
|
+
|
|
3379
3592
|
export { WebDriver as default }
|