codeceptjs 4.0.0-beta.2 → 4.0.0-beta.21
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 +133 -120
- package/bin/codecept.js +107 -96
- package/bin/test-server.js +64 -0
- package/docs/webapi/clearCookie.mustache +1 -1
- package/docs/webapi/click.mustache +5 -1
- package/lib/actor.js +73 -103
- package/lib/ai.js +159 -188
- package/lib/assert/empty.js +22 -24
- package/lib/assert/equal.js +30 -37
- package/lib/assert/error.js +14 -14
- package/lib/assert/include.js +43 -48
- package/lib/assert/throws.js +11 -11
- package/lib/assert/truth.js +22 -22
- package/lib/assert.js +20 -18
- package/lib/codecept.js +262 -162
- package/lib/colorUtils.js +50 -52
- package/lib/command/check.js +206 -0
- package/lib/command/configMigrate.js +56 -51
- package/lib/command/definitions.js +96 -109
- package/lib/command/dryRun.js +77 -79
- package/lib/command/generate.js +234 -194
- package/lib/command/gherkin/init.js +42 -33
- package/lib/command/gherkin/snippets.js +76 -74
- package/lib/command/gherkin/steps.js +20 -17
- package/lib/command/info.js +74 -38
- package/lib/command/init.js +301 -290
- package/lib/command/interactive.js +41 -32
- package/lib/command/list.js +28 -27
- package/lib/command/run-multiple/chunk.js +51 -48
- package/lib/command/run-multiple/collection.js +5 -5
- package/lib/command/run-multiple/run.js +5 -1
- package/lib/command/run-multiple.js +97 -97
- package/lib/command/run-rerun.js +19 -25
- package/lib/command/run-workers.js +68 -92
- package/lib/command/run.js +39 -27
- package/lib/command/utils.js +80 -64
- package/lib/command/workers/runTests.js +388 -226
- package/lib/config.js +109 -50
- package/lib/container.js +765 -261
- package/lib/data/context.js +60 -61
- package/lib/data/dataScenarioConfig.js +47 -47
- package/lib/data/dataTableArgument.js +32 -32
- package/lib/data/table.js +22 -22
- package/lib/effects.js +307 -0
- package/lib/element/WebElement.js +327 -0
- package/lib/els.js +160 -0
- package/lib/event.js +173 -163
- package/lib/globals.js +141 -0
- package/lib/heal.js +89 -85
- package/lib/helper/AI.js +131 -41
- package/lib/helper/ApiDataFactory.js +107 -75
- package/lib/helper/Appium.js +542 -404
- package/lib/helper/FileSystem.js +100 -79
- package/lib/helper/GraphQL.js +44 -43
- package/lib/helper/GraphQLDataFactory.js +52 -52
- package/lib/helper/JSONResponse.js +126 -88
- package/lib/helper/Mochawesome.js +54 -29
- package/lib/helper/Playwright.js +2547 -1316
- package/lib/helper/Puppeteer.js +1578 -1181
- package/lib/helper/REST.js +209 -68
- package/lib/helper/WebDriver.js +1482 -1342
- package/lib/helper/errors/ConnectionRefused.js +6 -6
- package/lib/helper/errors/ElementAssertion.js +11 -16
- package/lib/helper/errors/ElementNotFound.js +5 -9
- package/lib/helper/errors/RemoteBrowserConnectionRefused.js +5 -5
- package/lib/helper/extras/Console.js +11 -11
- package/lib/helper/extras/PlaywrightLocator.js +110 -0
- package/lib/helper/extras/PlaywrightPropEngine.js +18 -18
- package/lib/helper/extras/PlaywrightReactVueLocator.js +17 -8
- package/lib/helper/extras/PlaywrightRestartOpts.js +25 -11
- package/lib/helper/extras/Popup.js +22 -22
- package/lib/helper/extras/React.js +27 -28
- package/lib/helper/network/actions.js +36 -42
- package/lib/helper/network/utils.js +78 -84
- package/lib/helper/scripts/blurElement.js +5 -5
- package/lib/helper/scripts/focusElement.js +5 -5
- package/lib/helper/scripts/highlightElement.js +8 -8
- package/lib/helper/scripts/isElementClickable.js +34 -34
- package/lib/helper.js +2 -3
- package/lib/history.js +23 -19
- package/lib/hooks.js +8 -8
- package/lib/html.js +94 -104
- package/lib/index.js +38 -27
- package/lib/listener/config.js +30 -23
- package/lib/listener/emptyRun.js +54 -0
- package/lib/listener/enhancedGlobalRetry.js +110 -0
- package/lib/listener/exit.js +16 -18
- package/lib/listener/globalRetry.js +70 -0
- package/lib/listener/globalTimeout.js +181 -0
- package/lib/listener/helpers.js +76 -51
- package/lib/listener/mocha.js +10 -11
- package/lib/listener/result.js +11 -0
- package/lib/listener/retryEnhancer.js +85 -0
- package/lib/listener/steps.js +71 -59
- package/lib/listener/store.js +20 -0
- package/lib/locator.js +214 -197
- package/lib/mocha/asyncWrapper.js +274 -0
- package/lib/mocha/bdd.js +167 -0
- package/lib/mocha/cli.js +341 -0
- package/lib/mocha/factory.js +163 -0
- package/lib/mocha/featureConfig.js +89 -0
- package/lib/mocha/gherkin.js +231 -0
- package/lib/mocha/hooks.js +121 -0
- package/lib/mocha/index.js +21 -0
- package/lib/mocha/inject.js +46 -0
- package/lib/{interfaces → mocha}/scenarioConfig.js +58 -34
- package/lib/mocha/suite.js +89 -0
- package/lib/mocha/test.js +184 -0
- package/lib/mocha/types.d.ts +42 -0
- package/lib/mocha/ui.js +242 -0
- package/lib/output.js +141 -71
- package/lib/parser.js +54 -44
- package/lib/pause.js +173 -145
- package/lib/plugin/analyze.js +403 -0
- package/lib/plugin/{autoLogin.js → auth.js} +178 -79
- package/lib/plugin/autoDelay.js +36 -40
- package/lib/plugin/coverage.js +131 -78
- package/lib/plugin/customLocator.js +22 -21
- package/lib/plugin/customReporter.js +53 -0
- package/lib/plugin/enhancedRetryFailedStep.js +99 -0
- package/lib/plugin/heal.js +101 -110
- package/lib/plugin/htmlReporter.js +3648 -0
- package/lib/plugin/pageInfo.js +140 -0
- package/lib/plugin/pauseOnFail.js +12 -11
- package/lib/plugin/retryFailedStep.js +82 -47
- package/lib/plugin/screenshotOnFail.js +111 -92
- package/lib/plugin/stepByStepReport.js +159 -101
- package/lib/plugin/stepTimeout.js +20 -25
- package/lib/plugin/subtitles.js +38 -38
- package/lib/recorder.js +193 -130
- package/lib/rerun.js +94 -49
- package/lib/result.js +238 -0
- package/lib/retryCoordinator.js +207 -0
- package/lib/secret.js +20 -18
- package/lib/session.js +95 -89
- package/lib/step/base.js +239 -0
- package/lib/step/comment.js +10 -0
- package/lib/step/config.js +50 -0
- package/lib/step/func.js +46 -0
- package/lib/step/helper.js +50 -0
- package/lib/step/meta.js +99 -0
- package/lib/step/record.js +74 -0
- package/lib/step/retry.js +11 -0
- package/lib/step/section.js +55 -0
- package/lib/step.js +18 -329
- package/lib/steps.js +54 -0
- package/lib/store.js +38 -7
- package/lib/template/heal.js +3 -12
- package/lib/template/prompts/generatePageObject.js +31 -0
- package/lib/template/prompts/healStep.js +13 -0
- package/lib/template/prompts/writeStep.js +9 -0
- package/lib/test-server.js +334 -0
- package/lib/timeout.js +60 -0
- package/lib/transform.js +8 -8
- package/lib/translation.js +34 -21
- package/lib/utils/loaderCheck.js +124 -0
- package/lib/utils/mask_data.js +47 -0
- package/lib/utils/typescript.js +237 -0
- package/lib/utils.js +411 -228
- package/lib/workerStorage.js +37 -34
- package/lib/workers.js +532 -296
- package/package.json +124 -95
- package/translations/de-DE.js +5 -3
- package/translations/fr-FR.js +5 -4
- package/translations/index.js +22 -12
- package/translations/it-IT.js +4 -3
- package/translations/ja-JP.js +4 -3
- package/translations/nl-NL.js +76 -0
- package/translations/pl-PL.js +4 -3
- package/translations/pt-BR.js +4 -3
- package/translations/ru-RU.js +4 -3
- package/translations/utils.js +10 -0
- package/translations/zh-CN.js +4 -3
- package/translations/zh-TW.js +4 -3
- package/typings/index.d.ts +546 -185
- package/typings/promiseBasedTypes.d.ts +150 -875
- package/typings/types.d.ts +547 -992
- package/lib/cli.js +0 -249
- package/lib/dirname.js +0 -5
- package/lib/helper/Expect.js +0 -425
- package/lib/helper/ExpectHelper.js +0 -399
- package/lib/helper/MockServer.js +0 -223
- package/lib/helper/Nightmare.js +0 -1411
- package/lib/helper/Protractor.js +0 -1835
- package/lib/helper/SoftExpectHelper.js +0 -381
- package/lib/helper/TestCafe.js +0 -1410
- package/lib/helper/clientscripts/nightmare.js +0 -213
- package/lib/helper/testcafe/testControllerHolder.js +0 -42
- package/lib/helper/testcafe/testcafe-utils.js +0 -63
- package/lib/interfaces/bdd.js +0 -98
- package/lib/interfaces/featureConfig.js +0 -69
- package/lib/interfaces/gherkin.js +0 -195
- package/lib/listener/artifacts.js +0 -19
- package/lib/listener/retry.js +0 -68
- package/lib/listener/timeout.js +0 -109
- package/lib/mochaFactory.js +0 -110
- package/lib/plugin/allure.js +0 -15
- package/lib/plugin/commentStep.js +0 -136
- package/lib/plugin/debugErrors.js +0 -67
- package/lib/plugin/eachElement.js +0 -127
- package/lib/plugin/fakerTransform.js +0 -49
- package/lib/plugin/retryTo.js +0 -121
- package/lib/plugin/selenoid.js +0 -371
- package/lib/plugin/standardActingHelpers.js +0 -9
- package/lib/plugin/tryTo.js +0 -105
- package/lib/plugin/wdio.js +0 -246
- package/lib/scenario.js +0 -222
- package/lib/ui.js +0 -238
- package/lib/within.js +0 -70
package/lib/helper/WebDriver.js
CHANGED
|
@@ -1,48 +1,46 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
import
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
} from '
|
|
22
|
-
|
|
23
|
-
import {
|
|
24
|
-
import
|
|
25
|
-
import
|
|
26
|
-
import
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const SHADOW = 'shadow';
|
|
45
|
-
const webRoot = 'body';
|
|
1
|
+
let webdriverio
|
|
2
|
+
|
|
3
|
+
import assert from 'assert'
|
|
4
|
+
import path from 'path'
|
|
5
|
+
import crypto from 'crypto'
|
|
6
|
+
|
|
7
|
+
import Helper from '@codeceptjs/helper'
|
|
8
|
+
import promiseRetry from 'promise-retry'
|
|
9
|
+
import { includes as stringIncludes } from '../assert/include.js'
|
|
10
|
+
import { urlEquals, equals } from '../assert/equal.js'
|
|
11
|
+
import store from '../store.js'
|
|
12
|
+
import output from '../output.js'
|
|
13
|
+
const { debug } = output
|
|
14
|
+
import { empty } from '../assert/empty.js'
|
|
15
|
+
import { truth } from '../assert/truth.js'
|
|
16
|
+
import { xpathLocator, fileExists, decodeUrl, chunkArray, convertCssPropertiesToCamelCase, screenshotOutputFolder, getNormalizedKeyAttributeValue, modifierKeys } from '../utils.js'
|
|
17
|
+
import { isColorProperty, convertColorToRGBA } from '../colorUtils.js'
|
|
18
|
+
import ElementNotFound from './errors/ElementNotFound.js'
|
|
19
|
+
import ConnectionRefused from './errors/ConnectionRefused.js'
|
|
20
|
+
import Locator from '../locator.js'
|
|
21
|
+
import { highlightElement } from './scripts/highlightElement.js'
|
|
22
|
+
import { focusElement } from './scripts/focusElement.js'
|
|
23
|
+
import { blurElement } from './scripts/blurElement.js'
|
|
24
|
+
import { dontSeeElementError, seeElementError, seeElementInDOMError, dontSeeElementInDOMError } from './errors/ElementAssertion.js'
|
|
25
|
+
import { dontSeeTraffic, seeTraffic, grabRecordedNetworkTraffics, stopRecordingTraffic, flushNetworkTraffics } from './network/actions.js'
|
|
26
|
+
import WebElement from '../element/WebElement.js'
|
|
27
|
+
|
|
28
|
+
const SHADOW = 'shadow'
|
|
29
|
+
const webRoot = 'body'
|
|
30
|
+
let browserLogs = []
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Wraps error objects that don't have a proper message property
|
|
34
|
+
* This is needed for ESM compatibility with WebdriverIO error handling
|
|
35
|
+
*/
|
|
36
|
+
function wrapError(e) {
|
|
37
|
+
if (e && typeof e === 'object' && !e.message) {
|
|
38
|
+
const err = new Error(e.error || e.timeoutMsg || String(e))
|
|
39
|
+
err.stack = e.stack
|
|
40
|
+
return err
|
|
41
|
+
}
|
|
42
|
+
return e
|
|
43
|
+
}
|
|
46
44
|
|
|
47
45
|
/**
|
|
48
46
|
* ## Configuration
|
|
@@ -53,6 +51,7 @@ const webRoot = 'body';
|
|
|
53
51
|
* @type {object}
|
|
54
52
|
* @prop {string} url - base url of website to be tested.
|
|
55
53
|
* @prop {string} browser - Browser in which to perform testing.
|
|
54
|
+
* @prop {boolean} [bidiProtocol=false] - WebDriver Bidi Protocol. Default: false. More info: https://webdriver.io/docs/api/webdriverBidi/
|
|
56
55
|
* @prop {string} [basicAuth] - (optional) the basic authentication to pass to base url. Example: {username: 'username', password: 'password'}
|
|
57
56
|
* @prop {string} [host=localhost] - WebDriver host to connect.
|
|
58
57
|
* @prop {number} [port=4444] - WebDriver port to connect.
|
|
@@ -72,9 +71,8 @@ const webRoot = 'body';
|
|
|
72
71
|
* @prop {object} [timeouts] [WebDriver timeouts](http://webdriver.io/docs/timeouts.html) defined as hash.
|
|
73
72
|
* @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose).
|
|
74
73
|
* @prop {string} [logLevel=silent] - level of logging verbosity. Default: silent. Options: trace | debug | info | warn | error | silent. More info: https://webdriver.io/docs/configuration/#loglevel
|
|
75
|
-
* @prop {boolean} [devtoolsProtocol=false] - enable devtools protocol. Default: false. More info: https://webdriver.io/docs/automationProtocols/#devtools-protocol.
|
|
76
74
|
*/
|
|
77
|
-
const config = {}
|
|
75
|
+
const config = {}
|
|
78
76
|
|
|
79
77
|
/**
|
|
80
78
|
* WebDriver helper which wraps [webdriverio](http://webdriver.io/) library to
|
|
@@ -180,7 +178,6 @@ const config = {};
|
|
|
180
178
|
* WebDriver : {
|
|
181
179
|
* url: "http://localhost",
|
|
182
180
|
* browser: "chrome",
|
|
183
|
-
* devtoolsProtocol: true,
|
|
184
181
|
* desiredCapabilities: {
|
|
185
182
|
* chromeOptions: {
|
|
186
183
|
* args: [ "--headless", "--disable-gpu", "--no-sandbox" ]
|
|
@@ -445,34 +442,34 @@ const config = {};
|
|
|
445
442
|
*/
|
|
446
443
|
class WebDriver extends Helper {
|
|
447
444
|
constructor(config) {
|
|
448
|
-
super(config)
|
|
449
|
-
webdriverio
|
|
445
|
+
super(config)
|
|
446
|
+
// webdriverio will be loaded dynamically in _init method
|
|
450
447
|
|
|
451
448
|
// set defaults
|
|
452
|
-
this.root = webRoot
|
|
453
|
-
this.isWeb = true
|
|
454
|
-
this.isRunning = false
|
|
455
|
-
this.sessionWindows = {}
|
|
456
|
-
this.activeSessionName = ''
|
|
457
|
-
this.customLocatorStrategies = config.customLocatorStrategies
|
|
449
|
+
this.root = webRoot
|
|
450
|
+
this.isWeb = true
|
|
451
|
+
this.isRunning = false
|
|
452
|
+
this.sessionWindows = {}
|
|
453
|
+
this.activeSessionName = ''
|
|
454
|
+
this.customLocatorStrategies = config.customLocatorStrategies
|
|
458
455
|
|
|
459
456
|
// for network stuff
|
|
460
|
-
this.requests = []
|
|
461
|
-
this.recording = false
|
|
462
|
-
this.recordedAtLeastOnce = false
|
|
457
|
+
this.requests = []
|
|
458
|
+
this.recording = false
|
|
459
|
+
this.recordedAtLeastOnce = false
|
|
463
460
|
|
|
464
|
-
this._setConfig(config)
|
|
461
|
+
this._setConfig(config)
|
|
465
462
|
|
|
466
463
|
Locator.addFilter((locator, result) => {
|
|
467
464
|
if (typeof locator === 'string' && locator.indexOf('~') === 0) {
|
|
468
465
|
// accessibility locator
|
|
469
466
|
if (this.isWeb) {
|
|
470
|
-
result.value = `[aria-label="${locator.slice(1)}"]
|
|
471
|
-
result.type = 'css'
|
|
472
|
-
result.output = `aria-label=${locator.slice(1)}
|
|
467
|
+
result.value = `[aria-label="${locator.slice(1)}"]`
|
|
468
|
+
result.type = 'css'
|
|
469
|
+
result.output = `aria-label=${locator.slice(1)}`
|
|
473
470
|
}
|
|
474
471
|
}
|
|
475
|
-
})
|
|
472
|
+
})
|
|
476
473
|
}
|
|
477
474
|
|
|
478
475
|
_validateConfig(config) {
|
|
@@ -492,41 +489,45 @@ class WebDriver extends Helper {
|
|
|
492
489
|
keepBrowserState: false,
|
|
493
490
|
deprecationWarnings: false,
|
|
494
491
|
highlightElement: false,
|
|
495
|
-
}
|
|
492
|
+
}
|
|
496
493
|
|
|
497
494
|
// override defaults with config
|
|
498
|
-
config = Object.assign(defaults, config)
|
|
495
|
+
config = Object.assign(defaults, config)
|
|
499
496
|
|
|
500
497
|
if (config.host) {
|
|
501
498
|
// webdriverio spec
|
|
502
|
-
config.hostname = config.host
|
|
503
|
-
config.path = config.path ? config.path : '/wd/hub'
|
|
499
|
+
config.hostname = config.host
|
|
500
|
+
config.path = config.path ? config.path : '/wd/hub'
|
|
504
501
|
}
|
|
505
502
|
|
|
506
|
-
config.baseUrl = config.url || config.baseUrl
|
|
503
|
+
config.baseUrl = config.url || config.baseUrl
|
|
507
504
|
if (config.desiredCapabilities && Object.keys(config.desiredCapabilities).length) {
|
|
508
|
-
config.capabilities = config.desiredCapabilities
|
|
505
|
+
config.capabilities = config.desiredCapabilities
|
|
509
506
|
}
|
|
510
|
-
config.capabilities.browserName = config.browser || config.capabilities.browserName
|
|
511
|
-
|
|
507
|
+
config.capabilities.browserName = config.browser || config.capabilities.browserName
|
|
508
|
+
|
|
509
|
+
// WebDriver Bidi Protocol. Default: true
|
|
510
|
+
config.capabilities.webSocketUrl = config.bidiProtocol ?? config.capabilities.webSocketUrl ?? true
|
|
511
|
+
|
|
512
|
+
config.capabilities.browserVersion = config.browserVersion || config.capabilities.browserVersion
|
|
512
513
|
if (config.capabilities.chromeOptions) {
|
|
513
|
-
config.capabilities['goog:chromeOptions'] = config.capabilities.chromeOptions
|
|
514
|
-
delete config.capabilities.chromeOptions
|
|
514
|
+
config.capabilities['goog:chromeOptions'] = config.capabilities.chromeOptions
|
|
515
|
+
delete config.capabilities.chromeOptions
|
|
515
516
|
}
|
|
516
517
|
if (config.capabilities.firefoxOptions) {
|
|
517
|
-
config.capabilities['moz:firefoxOptions'] = config.capabilities.firefoxOptions
|
|
518
|
-
delete config.capabilities.firefoxOptions
|
|
518
|
+
config.capabilities['moz:firefoxOptions'] = config.capabilities.firefoxOptions
|
|
519
|
+
delete config.capabilities.firefoxOptions
|
|
519
520
|
}
|
|
520
521
|
if (config.capabilities.ieOptions) {
|
|
521
|
-
config.capabilities['se:ieOptions'] = config.capabilities.ieOptions
|
|
522
|
-
delete config.capabilities.ieOptions
|
|
522
|
+
config.capabilities['se:ieOptions'] = config.capabilities.ieOptions
|
|
523
|
+
delete config.capabilities.ieOptions
|
|
523
524
|
}
|
|
524
525
|
if (config.capabilities.selenoidOptions) {
|
|
525
|
-
config.capabilities['selenoid:options'] = config.capabilities.selenoidOptions
|
|
526
|
-
delete config.capabilities.selenoidOptions
|
|
526
|
+
config.capabilities['selenoid:options'] = config.capabilities.selenoidOptions
|
|
527
|
+
delete config.capabilities.selenoidOptions
|
|
527
528
|
}
|
|
528
529
|
|
|
529
|
-
config.waitForTimeoutInSeconds = config.waitForTimeout / 1000
|
|
530
|
+
config.waitForTimeoutInSeconds = config.waitForTimeout / 1000 // convert to seconds
|
|
530
531
|
|
|
531
532
|
if (!config.capabilities.platformName && (!config.url || !config.browser)) {
|
|
532
533
|
throw new Error(`
|
|
@@ -540,196 +541,212 @@ class WebDriver extends Helper {
|
|
|
540
541
|
}
|
|
541
542
|
}
|
|
542
543
|
}
|
|
543
|
-
`)
|
|
544
|
+
`)
|
|
544
545
|
}
|
|
545
546
|
|
|
546
|
-
return config
|
|
547
|
+
return config
|
|
547
548
|
}
|
|
548
549
|
|
|
549
550
|
static _checkRequirements() {
|
|
550
551
|
try {
|
|
551
|
-
|
|
552
|
+
// In ESM, webdriverio will be checked via dynamic import in _init
|
|
553
|
+
// The import will fail at module load time if webdriverio is missing
|
|
554
|
+
return null
|
|
552
555
|
} catch (e) {
|
|
553
|
-
return ['webdriverio@^6.12.1']
|
|
556
|
+
return ['webdriverio@^6.12.1']
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
async _init() {
|
|
561
|
+
// Load webdriverio dynamically
|
|
562
|
+
if (!webdriverio) {
|
|
563
|
+
try {
|
|
564
|
+
webdriverio = await import('webdriverio')
|
|
565
|
+
webdriverio = webdriverio.default || webdriverio
|
|
566
|
+
} catch (e) {
|
|
567
|
+
throw new Error('webdriverio could not be loaded. Please install webdriverio.')
|
|
568
|
+
}
|
|
554
569
|
}
|
|
555
570
|
}
|
|
556
571
|
|
|
557
572
|
static _config() {
|
|
558
|
-
return [
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
573
|
+
return [
|
|
574
|
+
{
|
|
575
|
+
name: 'url',
|
|
576
|
+
message: 'Base url of site to be tested',
|
|
577
|
+
default: 'http://localhost',
|
|
578
|
+
},
|
|
579
|
+
{
|
|
580
|
+
name: 'browser',
|
|
581
|
+
message: 'Browser in which testing will be performed',
|
|
582
|
+
default: 'chrome',
|
|
583
|
+
},
|
|
584
|
+
]
|
|
567
585
|
}
|
|
568
586
|
|
|
569
587
|
_beforeSuite() {
|
|
570
588
|
if (!this.options.restart && !this.options.manualStart && !this.isRunning) {
|
|
571
|
-
this.debugSection('Session', 'Starting singleton browser session')
|
|
572
|
-
return this._startBrowser()
|
|
589
|
+
this.debugSection('Session', 'Starting singleton browser session')
|
|
590
|
+
return this._startBrowser()
|
|
573
591
|
}
|
|
574
592
|
}
|
|
575
593
|
|
|
576
594
|
_lookupCustomLocator(customStrategy) {
|
|
577
|
-
if (typeof
|
|
578
|
-
return null
|
|
595
|
+
if (typeof this.customLocatorStrategies !== 'object') {
|
|
596
|
+
return null
|
|
579
597
|
}
|
|
580
|
-
const strategy = this.customLocatorStrategies[customStrategy]
|
|
581
|
-
return typeof
|
|
598
|
+
const strategy = this.customLocatorStrategies[customStrategy]
|
|
599
|
+
return typeof strategy === 'function' ? strategy : null
|
|
582
600
|
}
|
|
583
601
|
|
|
584
602
|
_isCustomLocator(locator) {
|
|
585
|
-
const locatorObj = new Locator(locator)
|
|
603
|
+
const locatorObj = new Locator(locator)
|
|
586
604
|
if (locatorObj.isCustom()) {
|
|
587
|
-
const customLocator = this._lookupCustomLocator(locatorObj.type)
|
|
605
|
+
const customLocator = this._lookupCustomLocator(locatorObj.type)
|
|
588
606
|
if (customLocator) {
|
|
589
|
-
return true
|
|
607
|
+
return true
|
|
590
608
|
}
|
|
591
|
-
throw new Error('Please define "customLocatorStrategies" as an Object and the Locator Strategy as a "function".')
|
|
609
|
+
throw new Error('Please define "customLocatorStrategies" as an Object and the Locator Strategy as a "function".')
|
|
592
610
|
}
|
|
593
|
-
return false
|
|
611
|
+
return false
|
|
594
612
|
}
|
|
595
613
|
|
|
596
614
|
async _res(locator) {
|
|
597
|
-
const res =
|
|
598
|
-
|
|
599
|
-
: await this.$$(withStrictLocator(locator));
|
|
600
|
-
return res;
|
|
615
|
+
const res = this._isShadowLocator(locator) || this._isCustomLocator(locator) ? await this._locate(locator) : await this.$$(withStrictLocator(locator))
|
|
616
|
+
return res
|
|
601
617
|
}
|
|
602
618
|
|
|
603
619
|
async _startBrowser() {
|
|
604
620
|
try {
|
|
605
621
|
if (this.options.multiremote) {
|
|
606
|
-
this.browser = await webdriverio.multiremote(this.options.multiremote)
|
|
622
|
+
this.browser = await webdriverio.multiremote(this.options.multiremote)
|
|
607
623
|
} else {
|
|
608
624
|
// remove non w3c capabilities
|
|
609
|
-
delete this.options.capabilities.protocol
|
|
610
|
-
delete this.options.capabilities.hostname
|
|
611
|
-
delete this.options.capabilities.port
|
|
612
|
-
delete this.options.capabilities.path
|
|
613
|
-
|
|
614
|
-
if (!['chrome', 'chromium'].includes(this.options.browser.toLowerCase())) throw Error('The devtools protocol is only working with Chrome or Chromium');
|
|
615
|
-
this.options.automationProtocol = 'devtools';
|
|
616
|
-
}
|
|
617
|
-
this.browser = await webdriverio.remote(this.options);
|
|
625
|
+
delete this.options.capabilities.protocol
|
|
626
|
+
delete this.options.capabilities.hostname
|
|
627
|
+
delete this.options.capabilities.port
|
|
628
|
+
delete this.options.capabilities.path
|
|
629
|
+
this.browser = await webdriverio.remote(this.options)
|
|
618
630
|
}
|
|
619
631
|
} catch (err) {
|
|
620
632
|
if (err.toString().indexOf('ECONNREFUSED')) {
|
|
621
|
-
throw new ConnectionRefused(err)
|
|
633
|
+
throw new ConnectionRefused(err)
|
|
622
634
|
}
|
|
623
|
-
throw err
|
|
635
|
+
throw err
|
|
624
636
|
}
|
|
625
637
|
|
|
626
|
-
this.isRunning = true
|
|
638
|
+
this.isRunning = true
|
|
627
639
|
if (this.options.timeouts && this.isWeb) {
|
|
628
|
-
await this.defineTimeout(this.options.timeouts)
|
|
640
|
+
await this.defineTimeout(this.options.timeouts)
|
|
629
641
|
}
|
|
630
642
|
|
|
631
|
-
await this._resizeWindowIfNeeded(this.browser, this.options.windowSize)
|
|
643
|
+
await this._resizeWindowIfNeeded(this.browser, this.options.windowSize)
|
|
632
644
|
|
|
633
|
-
this.$$ = this.browser.$$.bind(this.browser)
|
|
645
|
+
this.$$ = this.browser.$$.bind(this.browser)
|
|
634
646
|
|
|
635
647
|
if (this._isCustomLocatorStrategyDefined()) {
|
|
636
|
-
Object.keys(this.customLocatorStrategies).forEach(async
|
|
637
|
-
this.debugSection('Weddriver', `adding custom locator strategy: ${customLocator}`)
|
|
638
|
-
const locatorFunction = this._lookupCustomLocator(customLocator)
|
|
639
|
-
this.browser.addLocatorStrategy(customLocator, locatorFunction)
|
|
640
|
-
})
|
|
648
|
+
Object.keys(this.customLocatorStrategies).forEach(async customLocator => {
|
|
649
|
+
this.debugSection('Weddriver', `adding custom locator strategy: ${customLocator}`)
|
|
650
|
+
const locatorFunction = this._lookupCustomLocator(customLocator)
|
|
651
|
+
this.browser.addLocatorStrategy(customLocator, locatorFunction)
|
|
652
|
+
})
|
|
641
653
|
}
|
|
642
654
|
|
|
643
655
|
if (this.browser.capabilities && this.browser.capabilities.platformName) {
|
|
644
|
-
this.browser.capabilities.platformName = this.browser.capabilities.platformName.toLowerCase()
|
|
656
|
+
this.browser.capabilities.platformName = this.browser.capabilities.platformName.toLowerCase()
|
|
645
657
|
}
|
|
646
658
|
|
|
647
|
-
|
|
648
|
-
|
|
659
|
+
this.browser.on('dialog', () => {})
|
|
660
|
+
|
|
661
|
+
// Check for Bidi, because "sessionSubscribe" is an exclusive Bidi protocol feature. Otherwise, error will be thrown.
|
|
662
|
+
if (this.browser.capabilities && this.browser.capabilities.webSocketUrl) {
|
|
663
|
+
await this.browser.sessionSubscribe({ events: ['log.entryAdded'] })
|
|
664
|
+
this.browser.on('log.entryAdded', logEvents)
|
|
649
665
|
}
|
|
650
666
|
|
|
651
|
-
return this.browser
|
|
667
|
+
return this.browser
|
|
652
668
|
}
|
|
653
669
|
|
|
654
670
|
_isCustomLocatorStrategyDefined() {
|
|
655
|
-
return this.customLocatorStrategies && Object.keys(this.customLocatorStrategies).length
|
|
671
|
+
return this.customLocatorStrategies && Object.keys(this.customLocatorStrategies).length
|
|
656
672
|
}
|
|
657
673
|
|
|
658
674
|
async _stopBrowser() {
|
|
659
|
-
if (this.browser && this.isRunning) await this.browser.deleteSession()
|
|
675
|
+
if (this.browser && this.isRunning) await this.browser.deleteSession()
|
|
660
676
|
}
|
|
661
677
|
|
|
662
678
|
async _before() {
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
if (
|
|
666
|
-
if (this.
|
|
667
|
-
|
|
679
|
+
if (!webdriverio) await this._init()
|
|
680
|
+
this.context = this.root
|
|
681
|
+
if (this.options.restart && !this.options.manualStart) return this._startBrowser()
|
|
682
|
+
if (!this.isRunning && !this.options.manualStart) return this._startBrowser()
|
|
683
|
+
if (this.browser) this.$$ = this.browser.$$.bind(this.browser)
|
|
684
|
+
return this.browser
|
|
668
685
|
}
|
|
669
686
|
|
|
670
687
|
async _after() {
|
|
671
|
-
if (!this.isRunning) return
|
|
688
|
+
if (!this.isRunning) return
|
|
672
689
|
if (this.options.restart) {
|
|
673
|
-
this.isRunning = false
|
|
674
|
-
return this.browser.deleteSession()
|
|
690
|
+
this.isRunning = false
|
|
691
|
+
return this.browser.deleteSession()
|
|
675
692
|
}
|
|
676
|
-
if (this.browser.isInsideFrame) await this.browser.
|
|
693
|
+
if (this.browser.isInsideFrame) await this.browser.switchFrame(null)
|
|
677
694
|
|
|
678
|
-
if (this.options.keepBrowserState) return
|
|
695
|
+
if (this.options.keepBrowserState) return
|
|
679
696
|
|
|
680
697
|
if (!this.options.keepCookies && this.options.capabilities.browserName) {
|
|
681
|
-
this.debugSection('Session', 'cleaning cookies and localStorage')
|
|
682
|
-
await this.browser.deleteCookies()
|
|
698
|
+
this.debugSection('Session', 'cleaning cookies and localStorage')
|
|
699
|
+
await this.browser.deleteCookies()
|
|
683
700
|
}
|
|
684
|
-
await this.browser.execute('localStorage.clear();').catch(
|
|
685
|
-
if (!(err.message.indexOf("Storage is disabled inside 'data:' URLs.") > -1)) throw err
|
|
686
|
-
})
|
|
687
|
-
await this.closeOtherTabs()
|
|
688
|
-
|
|
701
|
+
await this.browser.execute('localStorage.clear();').catch(err => {
|
|
702
|
+
if (!(err.message.indexOf("Storage is disabled inside 'data:' URLs.") > -1)) throw err
|
|
703
|
+
})
|
|
704
|
+
await this.closeOtherTabs()
|
|
705
|
+
browserLogs = []
|
|
706
|
+
return this.browser
|
|
689
707
|
}
|
|
690
708
|
|
|
691
|
-
_afterSuite() {
|
|
692
|
-
}
|
|
709
|
+
_afterSuite() {}
|
|
693
710
|
|
|
694
711
|
_finishTest() {
|
|
695
|
-
if (!this.options.restart && this.isRunning) return this._stopBrowser()
|
|
712
|
+
if (!this.options.restart && this.isRunning) return this._stopBrowser()
|
|
696
713
|
}
|
|
697
714
|
|
|
698
715
|
_session() {
|
|
699
|
-
const defaultSession = this.browser
|
|
716
|
+
const defaultSession = this.browser
|
|
700
717
|
return {
|
|
701
718
|
start: async (sessionName, opts) => {
|
|
702
719
|
// opts.disableScreenshots = true; // screenshots cant be saved as session will be already closed
|
|
703
|
-
opts = this._validateConfig(Object.assign(this.options, opts))
|
|
704
|
-
this.debugSection('New Browser', JSON.stringify(opts))
|
|
705
|
-
const browser = await webdriverio.remote(opts)
|
|
706
|
-
this.activeSessionName = sessionName
|
|
720
|
+
opts = this._validateConfig(Object.assign(this.options, opts))
|
|
721
|
+
this.debugSection('New Browser', JSON.stringify(opts))
|
|
722
|
+
const browser = await webdriverio.remote(opts)
|
|
723
|
+
this.activeSessionName = sessionName
|
|
707
724
|
if (opts.timeouts && this.isWeb) {
|
|
708
|
-
await this._defineBrowserTimeout(browser, opts.timeouts)
|
|
725
|
+
await this._defineBrowserTimeout(browser, opts.timeouts)
|
|
709
726
|
}
|
|
710
727
|
|
|
711
|
-
await this._resizeWindowIfNeeded(browser, opts.windowSize)
|
|
728
|
+
await this._resizeWindowIfNeeded(browser, opts.windowSize)
|
|
712
729
|
|
|
713
|
-
return browser
|
|
730
|
+
return browser
|
|
714
731
|
},
|
|
715
|
-
stop: async
|
|
716
|
-
if (!browser) return
|
|
717
|
-
return browser.deleteSession()
|
|
732
|
+
stop: async browser => {
|
|
733
|
+
if (!browser) return
|
|
734
|
+
return browser.deleteSession()
|
|
718
735
|
},
|
|
719
|
-
loadVars: async
|
|
720
|
-
if (this.context !== this.root) throw new Error(
|
|
721
|
-
this.browser = browser
|
|
722
|
-
this.$$ = this.browser.$$.bind(this.browser)
|
|
723
|
-
this.sessionWindows[this.activeSessionName] = browser
|
|
736
|
+
loadVars: async browser => {
|
|
737
|
+
if (this.context !== this.root) throw new Error("Can't start session inside within block")
|
|
738
|
+
this.browser = browser
|
|
739
|
+
this.$$ = this.browser.$$.bind(this.browser)
|
|
740
|
+
this.sessionWindows[this.activeSessionName] = browser
|
|
724
741
|
},
|
|
725
|
-
restoreVars: async
|
|
742
|
+
restoreVars: async session => {
|
|
726
743
|
if (!session) {
|
|
727
|
-
this.activeSessionName = ''
|
|
744
|
+
this.activeSessionName = ''
|
|
728
745
|
}
|
|
729
|
-
this.browser = defaultSession
|
|
730
|
-
this.$$ = this.browser.$$.bind(this.browser)
|
|
746
|
+
this.browser = defaultSession
|
|
747
|
+
this.$$ = this.browser.$$.bind(this.browser)
|
|
731
748
|
},
|
|
732
|
-
}
|
|
749
|
+
}
|
|
733
750
|
}
|
|
734
751
|
|
|
735
752
|
/**
|
|
@@ -751,41 +768,41 @@ class WebDriver extends Helper {
|
|
|
751
768
|
* @param {function} fn async functuion that executed with WebDriver helper as argument
|
|
752
769
|
*/
|
|
753
770
|
useWebDriverTo(description, fn) {
|
|
754
|
-
return this._useTo(...arguments)
|
|
771
|
+
return this._useTo(...arguments)
|
|
755
772
|
}
|
|
756
773
|
|
|
757
774
|
async _failed() {
|
|
758
|
-
if (this.context !== this.root) await this._withinEnd()
|
|
775
|
+
if (this.context !== this.root) await this._withinEnd()
|
|
759
776
|
}
|
|
760
777
|
|
|
761
778
|
async _withinBegin(locator) {
|
|
762
|
-
const frame = isFrameLocator(locator)
|
|
779
|
+
const frame = isFrameLocator(locator)
|
|
763
780
|
if (frame) {
|
|
764
|
-
this.browser.isInsideFrame = true
|
|
781
|
+
this.browser.isInsideFrame = true
|
|
765
782
|
if (Array.isArray(frame)) {
|
|
766
783
|
// this.switchTo(null);
|
|
767
|
-
await forEachAsync(frame, async f => this.switchTo(f))
|
|
768
|
-
return
|
|
784
|
+
await forEachAsync(frame, async f => this.switchTo(f))
|
|
785
|
+
return
|
|
769
786
|
}
|
|
770
|
-
await this.switchTo(frame)
|
|
771
|
-
return
|
|
787
|
+
await this.switchTo(frame)
|
|
788
|
+
return
|
|
772
789
|
}
|
|
773
|
-
this.context = locator
|
|
790
|
+
this.context = locator
|
|
774
791
|
|
|
775
|
-
let res = await this.browser.$$(withStrictLocator(locator))
|
|
776
|
-
assertElementExists(res, locator)
|
|
777
|
-
res = usingFirstElement(res)
|
|
778
|
-
this.context = res.selector
|
|
779
|
-
this.$$ = res.$$.bind(res)
|
|
792
|
+
let res = await this.browser.$$(withStrictLocator(locator))
|
|
793
|
+
assertElementExists(res, locator)
|
|
794
|
+
res = usingFirstElement(res)
|
|
795
|
+
this.context = res.selector
|
|
796
|
+
this.$$ = res.$$.bind(res)
|
|
780
797
|
}
|
|
781
798
|
|
|
782
799
|
async _withinEnd() {
|
|
783
800
|
if (this.browser.isInsideFrame) {
|
|
784
|
-
this.browser.isInsideFrame = false
|
|
785
|
-
return this.switchTo(null)
|
|
801
|
+
this.browser.isInsideFrame = false
|
|
802
|
+
return this.switchTo(null)
|
|
786
803
|
}
|
|
787
|
-
this.context = this.root
|
|
788
|
-
this.$$ = this.browser.$$.bind(this.browser)
|
|
804
|
+
this.context = this.root
|
|
805
|
+
this.$$ = this.browser.$$.bind(this.browser)
|
|
789
806
|
}
|
|
790
807
|
|
|
791
808
|
/**
|
|
@@ -794,7 +811,7 @@ class WebDriver extends Helper {
|
|
|
794
811
|
* @param {object} locator
|
|
795
812
|
*/
|
|
796
813
|
_isShadowLocator(locator) {
|
|
797
|
-
return locator.type === SHADOW || locator[SHADOW]
|
|
814
|
+
return locator.type === SHADOW || locator[SHADOW]
|
|
798
815
|
}
|
|
799
816
|
|
|
800
817
|
/**
|
|
@@ -803,35 +820,37 @@ class WebDriver extends Helper {
|
|
|
803
820
|
* @param {object} locator
|
|
804
821
|
*/
|
|
805
822
|
async _locateShadow(locator) {
|
|
806
|
-
const shadow = locator.value ? locator.value : locator[SHADOW]
|
|
807
|
-
const shadowSequence = []
|
|
808
|
-
let elements
|
|
823
|
+
const shadow = locator.value ? locator.value : locator[SHADOW]
|
|
824
|
+
const shadowSequence = []
|
|
825
|
+
let elements
|
|
809
826
|
|
|
810
827
|
if (!Array.isArray(shadow)) {
|
|
811
|
-
throw new Error(`Shadow '${shadow}' should be defined as an Array of elements.`)
|
|
828
|
+
throw new Error(`Shadow '${shadow}' should be defined as an Array of elements.`)
|
|
812
829
|
}
|
|
813
830
|
|
|
814
831
|
// traverse through the Shadow locators in sequence
|
|
815
832
|
for (let index = 0; index < shadow.length; index++) {
|
|
816
|
-
const shadowElement = shadow[index]
|
|
817
|
-
shadowSequence.push(shadowElement)
|
|
833
|
+
const shadowElement = shadow[index]
|
|
834
|
+
shadowSequence.push(shadowElement)
|
|
818
835
|
|
|
819
836
|
if (!elements) {
|
|
820
|
-
elements = await
|
|
837
|
+
elements = await this.browser.$$(shadowElement)
|
|
821
838
|
} else if (Array.isArray(elements)) {
|
|
822
|
-
elements = await elements[0].shadow$$(shadowElement)
|
|
839
|
+
elements = await elements[0].shadow$$(shadowElement)
|
|
823
840
|
} else if (elements) {
|
|
824
|
-
elements = await elements.shadow$$(shadowElement)
|
|
841
|
+
elements = await elements.shadow$$(shadowElement)
|
|
825
842
|
}
|
|
826
843
|
|
|
827
844
|
if (!elements || !elements[0]) {
|
|
828
|
-
throw new Error(
|
|
845
|
+
throw new Error(
|
|
846
|
+
`Shadow Element '${shadowElement}' is not found. It is possible the element is incorrect or elements sequence is incorrect. Please verify the sequence '${shadowSequence.join('>')}' is correctly chained.`,
|
|
847
|
+
)
|
|
829
848
|
}
|
|
830
849
|
}
|
|
831
850
|
|
|
832
|
-
this.debugSection('Elements', `Found ${elements.length} '${SHADOW}' elements`)
|
|
851
|
+
this.debugSection('Elements', `Found ${elements.length} '${SHADOW}' elements`)
|
|
833
852
|
|
|
834
|
-
return elements
|
|
853
|
+
return elements
|
|
835
854
|
}
|
|
836
855
|
|
|
837
856
|
/**
|
|
@@ -840,8 +859,8 @@ class WebDriver extends Helper {
|
|
|
840
859
|
* @param {object} locator
|
|
841
860
|
*/
|
|
842
861
|
async _smartWait(locator) {
|
|
843
|
-
this.debugSection(`SmartWait (${this.options.smartWait}ms)`, `Locating ${JSON.stringify(locator)} in ${this.options.smartWait}`)
|
|
844
|
-
await this.defineTimeout({ implicit: this.options.smartWait })
|
|
862
|
+
this.debugSection(`SmartWait (${this.options.smartWait}ms)`, `Locating ${JSON.stringify(locator)} in ${this.options.smartWait}`)
|
|
863
|
+
await this.defineTimeout({ implicit: this.options.smartWait })
|
|
845
864
|
}
|
|
846
865
|
|
|
847
866
|
/**
|
|
@@ -856,53 +875,64 @@ class WebDriver extends Helper {
|
|
|
856
875
|
* @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
|
|
857
876
|
*/
|
|
858
877
|
async _locate(locator, smartWait = false) {
|
|
859
|
-
if (
|
|
878
|
+
if (store.debugMode) smartWait = false
|
|
860
879
|
|
|
861
880
|
// special locator type for Shadow DOM
|
|
862
881
|
if (this._isShadowLocator(locator)) {
|
|
863
882
|
if (!this.options.smartWait || !smartWait) {
|
|
864
|
-
const els = await this._locateShadow(locator)
|
|
865
|
-
return els
|
|
883
|
+
const els = await this._locateShadow(locator)
|
|
884
|
+
return els
|
|
866
885
|
}
|
|
867
886
|
|
|
868
|
-
const els = await this._locateShadow(locator)
|
|
869
|
-
return els
|
|
887
|
+
const els = await this._locateShadow(locator)
|
|
888
|
+
return els
|
|
870
889
|
}
|
|
871
890
|
|
|
872
891
|
// special locator type for React
|
|
873
892
|
if (locator.react) {
|
|
874
|
-
const els = await this.browser.react$$(locator.react, locator.props || undefined, locator.state || undefined)
|
|
875
|
-
this.debugSection('Elements', `Found ${els.length} react components`)
|
|
876
|
-
return els
|
|
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
|
+
// special locator type for ARIA roles
|
|
899
|
+
if (locator.role) {
|
|
900
|
+
return this._locateByRole(locator)
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// Handle role locators passed as Locator instances
|
|
904
|
+
const matchedLocator = new Locator(locator)
|
|
905
|
+
if (matchedLocator.isRole()) {
|
|
906
|
+
return this._locateByRole(matchedLocator.locator)
|
|
877
907
|
}
|
|
878
908
|
|
|
879
909
|
if (!this.options.smartWait || !smartWait) {
|
|
880
910
|
if (this._isCustomLocator(locator)) {
|
|
881
|
-
const locatorObj = new Locator(locator)
|
|
882
|
-
return this.browser.custom$$(locatorObj.type, locatorObj.value)
|
|
911
|
+
const locatorObj = new Locator(locator)
|
|
912
|
+
return this.browser.custom$$(locatorObj.type, locatorObj.value)
|
|
883
913
|
}
|
|
884
914
|
|
|
885
|
-
const els = await this.$$(withStrictLocator(locator))
|
|
886
|
-
return els
|
|
915
|
+
const els = await this.$$(withStrictLocator(locator))
|
|
916
|
+
return els
|
|
887
917
|
}
|
|
888
918
|
|
|
889
|
-
await this._smartWait(locator)
|
|
919
|
+
await this._smartWait(locator)
|
|
890
920
|
|
|
891
921
|
if (this._isCustomLocator(locator)) {
|
|
892
|
-
const locatorObj = new Locator(locator)
|
|
893
|
-
return this.browser.custom$$(locatorObj.type, locatorObj.value)
|
|
922
|
+
const locatorObj = new Locator(locator)
|
|
923
|
+
return this.browser.custom$$(locatorObj.type, locatorObj.value)
|
|
894
924
|
}
|
|
895
925
|
|
|
896
|
-
const els = await this.$$(withStrictLocator(locator))
|
|
897
|
-
await this.defineTimeout({ implicit: 0 })
|
|
898
|
-
return els
|
|
926
|
+
const els = await this.$$(withStrictLocator(locator))
|
|
927
|
+
await this.defineTimeout({ implicit: 0 })
|
|
928
|
+
return els
|
|
899
929
|
}
|
|
900
930
|
|
|
901
931
|
_grabCustomLocator(locator) {
|
|
902
932
|
if (typeof locator === 'string') {
|
|
903
|
-
locator = new Locator(locator)
|
|
933
|
+
locator = new Locator(locator)
|
|
904
934
|
}
|
|
905
|
-
return locator.value ? locator.value : locator.custom
|
|
935
|
+
return locator.value ? locator.value : locator.custom
|
|
906
936
|
}
|
|
907
937
|
|
|
908
938
|
/**
|
|
@@ -915,7 +945,7 @@ class WebDriver extends Helper {
|
|
|
915
945
|
* @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
|
|
916
946
|
*/
|
|
917
947
|
async _locateCheckable(locator) {
|
|
918
|
-
return findCheckable.call(this, locator, this.$$.bind(this)).then(res => res)
|
|
948
|
+
return findCheckable.call(this, locator, this.$$.bind(this)).then(res => res)
|
|
919
949
|
}
|
|
920
950
|
|
|
921
951
|
/**
|
|
@@ -929,8 +959,8 @@ class WebDriver extends Helper {
|
|
|
929
959
|
* @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
|
|
930
960
|
*/
|
|
931
961
|
async _locateClickable(locator, context) {
|
|
932
|
-
const locateFn = prepareLocateFn.call(this, context)
|
|
933
|
-
return findClickable.call(this, locator, locateFn)
|
|
962
|
+
const locateFn = prepareLocateFn.call(this, context)
|
|
963
|
+
return findClickable.call(this, locator, locateFn)
|
|
934
964
|
}
|
|
935
965
|
|
|
936
966
|
/**
|
|
@@ -943,7 +973,35 @@ class WebDriver extends Helper {
|
|
|
943
973
|
* @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
|
|
944
974
|
*/
|
|
945
975
|
async _locateFields(locator) {
|
|
946
|
-
return findFields.call(this, locator).then(res => res)
|
|
976
|
+
return findFields.call(this, locator).then(res => res)
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
/**
|
|
980
|
+
* Locate elements by ARIA role using WebdriverIO accessibility selectors
|
|
981
|
+
*
|
|
982
|
+
* @param {object} locator - role locator object { role: string, text?: string, exact?: boolean }
|
|
983
|
+
*/
|
|
984
|
+
async _locateByRole(locator) {
|
|
985
|
+
const role = locator.role
|
|
986
|
+
|
|
987
|
+
if (!locator.text) {
|
|
988
|
+
return this.browser.$$(`[role="${role}"]`)
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
const elements = await this.browser.$$(`[role="${role}"]`)
|
|
992
|
+
const filteredElements = []
|
|
993
|
+
const matchFn = locator.exact === true
|
|
994
|
+
? t => t === locator.text
|
|
995
|
+
: t => t && t.includes(locator.text)
|
|
996
|
+
|
|
997
|
+
for (const element of elements) {
|
|
998
|
+
const texts = await getElementTextAttributes.call(this, element)
|
|
999
|
+
if (texts.some(matchFn)) {
|
|
1000
|
+
filteredElements.push(element)
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
return filteredElements
|
|
947
1005
|
}
|
|
948
1006
|
|
|
949
1007
|
/**
|
|
@@ -951,7 +1009,20 @@ class WebDriver extends Helper {
|
|
|
951
1009
|
*
|
|
952
1010
|
*/
|
|
953
1011
|
async grabWebElements(locator) {
|
|
954
|
-
|
|
1012
|
+
const elements = await this._locate(locator)
|
|
1013
|
+
return elements.map(element => new WebElement(element, this))
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
/**
|
|
1017
|
+
* {{> grabWebElement }}
|
|
1018
|
+
*
|
|
1019
|
+
*/
|
|
1020
|
+
async grabWebElement(locator) {
|
|
1021
|
+
const elements = await this._locate(locator)
|
|
1022
|
+
if (elements.length === 0) {
|
|
1023
|
+
throw new ElementNotFound(locator, 'Element')
|
|
1024
|
+
}
|
|
1025
|
+
return new WebElement(elements[0], this)
|
|
955
1026
|
}
|
|
956
1027
|
|
|
957
1028
|
/**
|
|
@@ -967,11 +1038,11 @@ class WebDriver extends Helper {
|
|
|
967
1038
|
* @param {*} timeouts WebDriver timeouts object.
|
|
968
1039
|
*/
|
|
969
1040
|
defineTimeout(timeouts) {
|
|
970
|
-
return this._defineBrowserTimeout(this.browser, timeouts)
|
|
1041
|
+
return this._defineBrowserTimeout(this.browser, timeouts)
|
|
971
1042
|
}
|
|
972
1043
|
|
|
973
1044
|
_defineBrowserTimeout(browser, timeouts) {
|
|
974
|
-
return browser.setTimeout(timeouts)
|
|
1045
|
+
return browser.setTimeout(timeouts)
|
|
975
1046
|
}
|
|
976
1047
|
|
|
977
1048
|
/**
|
|
@@ -979,15 +1050,15 @@ class WebDriver extends Helper {
|
|
|
979
1050
|
*
|
|
980
1051
|
*/
|
|
981
1052
|
amOnPage(url) {
|
|
982
|
-
let split_url
|
|
1053
|
+
let split_url
|
|
983
1054
|
if (this.options.basicAuth) {
|
|
984
1055
|
if (url.startsWith('/')) {
|
|
985
|
-
url = this.options.url + url
|
|
1056
|
+
url = this.options.url + url
|
|
986
1057
|
}
|
|
987
|
-
split_url = url.split('//')
|
|
988
|
-
url = `${split_url[0]}//${this.options.basicAuth.username}:${this.options.basicAuth.password}@${split_url[1]}
|
|
1058
|
+
split_url = url.split('//')
|
|
1059
|
+
url = `${split_url[0]}//${this.options.basicAuth.username}:${this.options.basicAuth.password}@${split_url[1]}`
|
|
989
1060
|
}
|
|
990
|
-
return this.browser.url(url)
|
|
1061
|
+
return this.browser.url(url)
|
|
991
1062
|
}
|
|
992
1063
|
|
|
993
1064
|
/**
|
|
@@ -996,18 +1067,18 @@ class WebDriver extends Helper {
|
|
|
996
1067
|
* {{ react }}
|
|
997
1068
|
*/
|
|
998
1069
|
async click(locator, context = null) {
|
|
999
|
-
const clickMethod = this.browser.isMobile &&
|
|
1000
|
-
const locateFn = prepareLocateFn.call(this, context)
|
|
1070
|
+
const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick'
|
|
1071
|
+
const locateFn = prepareLocateFn.call(this, context)
|
|
1001
1072
|
|
|
1002
|
-
const res = await findClickable.call(this, locator, locateFn)
|
|
1073
|
+
const res = await findClickable.call(this, locator, locateFn)
|
|
1003
1074
|
if (context) {
|
|
1004
|
-
assertElementExists(res, locator, 'Clickable element', `was not found inside element ${new Locator(context)}`)
|
|
1075
|
+
assertElementExists(res, locator, 'Clickable element', `was not found inside element ${new Locator(context)}`)
|
|
1005
1076
|
} else {
|
|
1006
|
-
assertElementExists(res, locator, 'Clickable element')
|
|
1077
|
+
assertElementExists(res, locator, 'Clickable element')
|
|
1007
1078
|
}
|
|
1008
|
-
const elem = usingFirstElement(res)
|
|
1009
|
-
highlightActiveElement.call(this, elem)
|
|
1010
|
-
return this.browser[clickMethod](getElementId(elem))
|
|
1079
|
+
const elem = usingFirstElement(res)
|
|
1080
|
+
highlightActiveElement.call(this, elem)
|
|
1081
|
+
return this.browser[clickMethod](getElementId(elem))
|
|
1011
1082
|
}
|
|
1012
1083
|
|
|
1013
1084
|
/**
|
|
@@ -1016,25 +1087,25 @@ class WebDriver extends Helper {
|
|
|
1016
1087
|
* {{ react }}
|
|
1017
1088
|
*/
|
|
1018
1089
|
async forceClick(locator, context = null) {
|
|
1019
|
-
const locateFn = prepareLocateFn.call(this, context)
|
|
1090
|
+
const locateFn = prepareLocateFn.call(this, context)
|
|
1020
1091
|
|
|
1021
|
-
const res = await findClickable.call(this, locator, locateFn)
|
|
1092
|
+
const res = await findClickable.call(this, locator, locateFn)
|
|
1022
1093
|
if (context) {
|
|
1023
|
-
assertElementExists(res, locator, 'Clickable element', `was not found inside element ${new Locator(context)}`)
|
|
1094
|
+
assertElementExists(res, locator, 'Clickable element', `was not found inside element ${new Locator(context)}`)
|
|
1024
1095
|
} else {
|
|
1025
|
-
assertElementExists(res, locator, 'Clickable element')
|
|
1096
|
+
assertElementExists(res, locator, 'Clickable element')
|
|
1026
1097
|
}
|
|
1027
|
-
const elem = usingFirstElement(res)
|
|
1028
|
-
highlightActiveElement.call(this, elem)
|
|
1098
|
+
const elem = usingFirstElement(res)
|
|
1099
|
+
highlightActiveElement.call(this, elem)
|
|
1029
1100
|
|
|
1030
|
-
return this.executeScript(
|
|
1101
|
+
return this.executeScript(el => {
|
|
1031
1102
|
if (document.activeElement instanceof HTMLElement) {
|
|
1032
|
-
document.activeElement.blur()
|
|
1103
|
+
document.activeElement.blur()
|
|
1033
1104
|
}
|
|
1034
|
-
const event = document.createEvent('MouseEvent')
|
|
1035
|
-
event.initEvent('click', true, true)
|
|
1036
|
-
return el.dispatchEvent(event)
|
|
1037
|
-
}, elem)
|
|
1105
|
+
const event = document.createEvent('MouseEvent')
|
|
1106
|
+
event.initEvent('click', true, true)
|
|
1107
|
+
return el.dispatchEvent(event)
|
|
1108
|
+
}, elem)
|
|
1038
1109
|
}
|
|
1039
1110
|
|
|
1040
1111
|
/**
|
|
@@ -1043,18 +1114,18 @@ class WebDriver extends Helper {
|
|
|
1043
1114
|
* {{ react }}
|
|
1044
1115
|
*/
|
|
1045
1116
|
async doubleClick(locator, context = null) {
|
|
1046
|
-
const locateFn = prepareLocateFn.call(this, context)
|
|
1117
|
+
const locateFn = prepareLocateFn.call(this, context)
|
|
1047
1118
|
|
|
1048
|
-
const res = await findClickable.call(this, locator, locateFn)
|
|
1119
|
+
const res = await findClickable.call(this, locator, locateFn)
|
|
1049
1120
|
if (context) {
|
|
1050
|
-
assertElementExists(res, locator, 'Clickable element', `was not found inside element ${new Locator(context)}`)
|
|
1121
|
+
assertElementExists(res, locator, 'Clickable element', `was not found inside element ${new Locator(context)}`)
|
|
1051
1122
|
} else {
|
|
1052
|
-
assertElementExists(res, locator, 'Clickable element')
|
|
1123
|
+
assertElementExists(res, locator, 'Clickable element')
|
|
1053
1124
|
}
|
|
1054
1125
|
|
|
1055
|
-
const elem = usingFirstElement(res)
|
|
1056
|
-
highlightActiveElement.call(this, elem)
|
|
1057
|
-
return elem.doubleClick()
|
|
1126
|
+
const elem = usingFirstElement(res)
|
|
1127
|
+
highlightActiveElement.call(this, elem)
|
|
1128
|
+
return elem.doubleClick()
|
|
1058
1129
|
}
|
|
1059
1130
|
|
|
1060
1131
|
/**
|
|
@@ -1063,24 +1134,93 @@ class WebDriver extends Helper {
|
|
|
1063
1134
|
* {{ react }}
|
|
1064
1135
|
*/
|
|
1065
1136
|
async rightClick(locator, context) {
|
|
1066
|
-
const locateFn = prepareLocateFn.call(this, context)
|
|
1137
|
+
const locateFn = prepareLocateFn.call(this, context)
|
|
1067
1138
|
|
|
1068
|
-
const res = await findClickable.call(this, locator, locateFn)
|
|
1139
|
+
const res = await findClickable.call(this, locator, locateFn)
|
|
1069
1140
|
if (context) {
|
|
1070
|
-
assertElementExists(res, locator, 'Clickable element', `was not found inside element ${new Locator(context)}`)
|
|
1141
|
+
assertElementExists(res, locator, 'Clickable element', `was not found inside element ${new Locator(context)}`)
|
|
1071
1142
|
} else {
|
|
1072
|
-
assertElementExists(res, locator, 'Clickable element')
|
|
1143
|
+
assertElementExists(res, locator, 'Clickable element')
|
|
1073
1144
|
}
|
|
1074
1145
|
|
|
1075
|
-
const el = usingFirstElement(res)
|
|
1146
|
+
const el = usingFirstElement(res)
|
|
1076
1147
|
|
|
1077
|
-
await el.moveTo()
|
|
1148
|
+
await el.moveTo()
|
|
1078
1149
|
|
|
1079
1150
|
if (this.browser.isW3C) {
|
|
1080
|
-
return el.click({ button: 'right' })
|
|
1151
|
+
return el.click({ button: 'right' })
|
|
1081
1152
|
}
|
|
1082
1153
|
// JSON Wire version
|
|
1083
|
-
await this.browser.buttonDown(2)
|
|
1154
|
+
await this.browser.buttonDown(2)
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
/**
|
|
1158
|
+
* Performs click at specific coordinates.
|
|
1159
|
+
* If locator is provided, the coordinates are relative to the element's top-left corner.
|
|
1160
|
+
* If locator is not provided, the coordinates are relative to the body element.
|
|
1161
|
+
*
|
|
1162
|
+
* ```js
|
|
1163
|
+
* // Click at coordinates (100, 200) relative to body
|
|
1164
|
+
* I.clickXY(100, 200);
|
|
1165
|
+
*
|
|
1166
|
+
* // Click at coordinates (50, 30) relative to element's top-left corner
|
|
1167
|
+
* I.clickXY('#someElement', 50, 30);
|
|
1168
|
+
* ```
|
|
1169
|
+
*
|
|
1170
|
+
* @param {CodeceptJS.LocatorOrString|number} locator Element to click on or X coordinate if no element.
|
|
1171
|
+
* @param {number} [x] X coordinate relative to element's top-left, or Y coordinate if locator is a number.
|
|
1172
|
+
* @param {number} [y] Y coordinate relative to element's top-left.
|
|
1173
|
+
* @returns {Promise<void>}
|
|
1174
|
+
*/
|
|
1175
|
+
async clickXY(locator, x, y) {
|
|
1176
|
+
// If locator is a number, treat it as X coordinate and use body as base
|
|
1177
|
+
if (typeof locator === 'number') {
|
|
1178
|
+
const globalX = locator
|
|
1179
|
+
const globalY = x
|
|
1180
|
+
locator = '//body'
|
|
1181
|
+
x = globalX
|
|
1182
|
+
y = globalY
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
// Locate the base element
|
|
1186
|
+
const res = await this._locate(withStrictLocator(locator), true)
|
|
1187
|
+
assertElementExists(res, locator, 'Element to click')
|
|
1188
|
+
const el = usingFirstElement(res)
|
|
1189
|
+
|
|
1190
|
+
// Get element position and size to calculate top-left corner
|
|
1191
|
+
const location = await el.getLocation()
|
|
1192
|
+
const size = await el.getSize()
|
|
1193
|
+
|
|
1194
|
+
// WebDriver clicks at center by default, so we need to offset from center to top-left
|
|
1195
|
+
// then add our desired x, y coordinates
|
|
1196
|
+
const offsetX = -(size.width / 2) + x
|
|
1197
|
+
const offsetY = -(size.height / 2) + y
|
|
1198
|
+
|
|
1199
|
+
if (this.browser.isW3C) {
|
|
1200
|
+
// Use performActions for W3C WebDriver
|
|
1201
|
+
return this.browser.performActions([
|
|
1202
|
+
{
|
|
1203
|
+
type: 'pointer',
|
|
1204
|
+
id: 'pointer1',
|
|
1205
|
+
parameters: { pointerType: 'mouse' },
|
|
1206
|
+
actions: [
|
|
1207
|
+
{
|
|
1208
|
+
type: 'pointerMove',
|
|
1209
|
+
origin: el,
|
|
1210
|
+
duration: 0,
|
|
1211
|
+
x: Math.round(offsetX),
|
|
1212
|
+
y: Math.round(offsetY),
|
|
1213
|
+
},
|
|
1214
|
+
{ type: 'pointerDown', button: 0 },
|
|
1215
|
+
{ type: 'pointerUp', button: 0 },
|
|
1216
|
+
],
|
|
1217
|
+
},
|
|
1218
|
+
])
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
// Fallback for non-W3C browsers
|
|
1222
|
+
await el.moveTo({ xOffset: Math.round(offsetX), yOffset: Math.round(offsetY) })
|
|
1223
|
+
return el.click()
|
|
1084
1224
|
}
|
|
1085
1225
|
|
|
1086
1226
|
/**
|
|
@@ -1089,24 +1229,24 @@ class WebDriver extends Helper {
|
|
|
1089
1229
|
* {{ react }}
|
|
1090
1230
|
*/
|
|
1091
1231
|
async forceRightClick(locator, context = null) {
|
|
1092
|
-
const locateFn = prepareLocateFn.call(this, context)
|
|
1232
|
+
const locateFn = prepareLocateFn.call(this, context)
|
|
1093
1233
|
|
|
1094
|
-
const res = await findClickable.call(this, locator, locateFn)
|
|
1234
|
+
const res = await findClickable.call(this, locator, locateFn)
|
|
1095
1235
|
if (context) {
|
|
1096
|
-
assertElementExists(res, locator, 'Clickable element', `was not found inside element ${new Locator(context)}`)
|
|
1236
|
+
assertElementExists(res, locator, 'Clickable element', `was not found inside element ${new Locator(context)}`)
|
|
1097
1237
|
} else {
|
|
1098
|
-
assertElementExists(res, locator, 'Clickable element')
|
|
1238
|
+
assertElementExists(res, locator, 'Clickable element')
|
|
1099
1239
|
}
|
|
1100
|
-
const elem = usingFirstElement(res)
|
|
1240
|
+
const elem = usingFirstElement(res)
|
|
1101
1241
|
|
|
1102
|
-
return this.executeScript(
|
|
1242
|
+
return this.executeScript(el => {
|
|
1103
1243
|
if (document.activeElement instanceof HTMLElement) {
|
|
1104
|
-
document.activeElement.blur()
|
|
1244
|
+
document.activeElement.blur()
|
|
1105
1245
|
}
|
|
1106
|
-
const event = document.createEvent('MouseEvent')
|
|
1107
|
-
event.initEvent('contextmenu', true, true)
|
|
1108
|
-
return el.dispatchEvent(event)
|
|
1109
|
-
}, elem)
|
|
1246
|
+
const event = document.createEvent('MouseEvent')
|
|
1247
|
+
event.initEvent('contextmenu', true, true)
|
|
1248
|
+
return el.dispatchEvent(event)
|
|
1249
|
+
}, elem)
|
|
1110
1250
|
}
|
|
1111
1251
|
|
|
1112
1252
|
/**
|
|
@@ -1116,12 +1256,22 @@ class WebDriver extends Helper {
|
|
|
1116
1256
|
*
|
|
1117
1257
|
*/
|
|
1118
1258
|
async fillField(field, value) {
|
|
1119
|
-
const res = await findFields.call(this, field)
|
|
1120
|
-
assertElementExists(res, field, 'Field')
|
|
1121
|
-
const elem = usingFirstElement(res)
|
|
1122
|
-
highlightActiveElement.call(this, elem)
|
|
1123
|
-
|
|
1124
|
-
|
|
1259
|
+
const res = await findFields.call(this, field)
|
|
1260
|
+
assertElementExists(res, field, 'Field')
|
|
1261
|
+
const elem = usingFirstElement(res)
|
|
1262
|
+
highlightActiveElement.call(this, elem)
|
|
1263
|
+
try {
|
|
1264
|
+
await elem.clearValue()
|
|
1265
|
+
} catch (err) {
|
|
1266
|
+
if (err.message && err.message.includes('invalid element state')) {
|
|
1267
|
+
await this.executeScript(el => {
|
|
1268
|
+
el.value = ''
|
|
1269
|
+
}, elem)
|
|
1270
|
+
} else {
|
|
1271
|
+
throw err
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
await elem.setValue(value.toString())
|
|
1125
1275
|
}
|
|
1126
1276
|
|
|
1127
1277
|
/**
|
|
@@ -1129,15 +1279,11 @@ class WebDriver extends Helper {
|
|
|
1129
1279
|
* {{ react }}
|
|
1130
1280
|
*/
|
|
1131
1281
|
async appendField(field, value) {
|
|
1132
|
-
const res = await findFields.call(this, field)
|
|
1133
|
-
assertElementExists(res, field, 'Field')
|
|
1134
|
-
const elem = usingFirstElement(res)
|
|
1135
|
-
highlightActiveElement.call(this, elem)
|
|
1136
|
-
|
|
1137
|
-
const curentValue = await elem.getValue();
|
|
1138
|
-
return elem.setValue(curentValue + value.toString());
|
|
1139
|
-
}
|
|
1140
|
-
return elem.addValue(value.toString());
|
|
1282
|
+
const res = await findFields.call(this, field)
|
|
1283
|
+
assertElementExists(res, field, 'Field')
|
|
1284
|
+
const elem = usingFirstElement(res)
|
|
1285
|
+
highlightActiveElement.call(this, elem)
|
|
1286
|
+
return elem.addValue(value.toString())
|
|
1141
1287
|
}
|
|
1142
1288
|
|
|
1143
1289
|
/**
|
|
@@ -1145,47 +1291,44 @@ class WebDriver extends Helper {
|
|
|
1145
1291
|
*
|
|
1146
1292
|
*/
|
|
1147
1293
|
async clearField(field) {
|
|
1148
|
-
const res = await findFields.call(this, field)
|
|
1149
|
-
assertElementExists(res, field, 'Field')
|
|
1150
|
-
const elem = usingFirstElement(res)
|
|
1151
|
-
highlightActiveElement.call(this, elem)
|
|
1152
|
-
|
|
1153
|
-
return elem.setValue('');
|
|
1154
|
-
}
|
|
1155
|
-
return elem.clearValue(getElementId(elem));
|
|
1294
|
+
const res = await findFields.call(this, field)
|
|
1295
|
+
assertElementExists(res, field, 'Field')
|
|
1296
|
+
const elem = usingFirstElement(res)
|
|
1297
|
+
highlightActiveElement.call(this, elem)
|
|
1298
|
+
return elem.clearValue(getElementId(elem))
|
|
1156
1299
|
}
|
|
1157
1300
|
|
|
1158
1301
|
/**
|
|
1159
1302
|
* {{> selectOption }}
|
|
1160
1303
|
*/
|
|
1161
1304
|
async selectOption(select, option) {
|
|
1162
|
-
const res = await findFields.call(this, select)
|
|
1163
|
-
assertElementExists(res, select, 'Selectable field')
|
|
1164
|
-
const elem = usingFirstElement(res)
|
|
1165
|
-
highlightActiveElement.call(this, elem)
|
|
1305
|
+
const res = await findFields.call(this, select)
|
|
1306
|
+
assertElementExists(res, select, 'Selectable field')
|
|
1307
|
+
const elem = usingFirstElement(res)
|
|
1308
|
+
highlightActiveElement.call(this, elem)
|
|
1166
1309
|
|
|
1167
1310
|
if (!Array.isArray(option)) {
|
|
1168
|
-
option = [option]
|
|
1311
|
+
option = [option]
|
|
1169
1312
|
}
|
|
1170
1313
|
|
|
1171
1314
|
// select options by visible text
|
|
1172
|
-
let els = await forEachAsync(option, async opt => this.browser.findElementsFromElement(getElementId(elem), 'xpath', Locator.select.byVisibleText(xpathLocator.literal(opt))))
|
|
1315
|
+
let els = await forEachAsync(option, async opt => this.browser.findElementsFromElement(getElementId(elem), 'xpath', Locator.select.byVisibleText(xpathLocator.literal(opt))))
|
|
1173
1316
|
|
|
1174
|
-
const clickOptionFn = async
|
|
1175
|
-
if (el[0]) el = el[0]
|
|
1176
|
-
const elementId = getElementId(el)
|
|
1177
|
-
if (elementId) return this.browser.elementClick(elementId)
|
|
1178
|
-
}
|
|
1317
|
+
const clickOptionFn = async el => {
|
|
1318
|
+
if (el[0]) el = el[0]
|
|
1319
|
+
const elementId = getElementId(el)
|
|
1320
|
+
if (elementId) return this.browser.elementClick(elementId)
|
|
1321
|
+
}
|
|
1179
1322
|
|
|
1180
1323
|
if (Array.isArray(els) && els.length) {
|
|
1181
|
-
return forEachAsync(els, clickOptionFn)
|
|
1324
|
+
return forEachAsync(els, clickOptionFn)
|
|
1182
1325
|
}
|
|
1183
1326
|
// select options by value
|
|
1184
|
-
els = await forEachAsync(option, async opt => this.browser.findElementsFromElement(getElementId(elem), 'xpath', Locator.select.byValue(xpathLocator.literal(opt))))
|
|
1327
|
+
els = await forEachAsync(option, async opt => this.browser.findElementsFromElement(getElementId(elem), 'xpath', Locator.select.byValue(xpathLocator.literal(opt))))
|
|
1185
1328
|
if (els.length === 0) {
|
|
1186
|
-
throw new ElementNotFound(select, `Option "${option}" in`, 'was not found neither by a visible text nor by a value')
|
|
1329
|
+
throw new ElementNotFound(select, `Option "${option}" in`, 'was not found neither by a visible text nor by a value')
|
|
1187
1330
|
}
|
|
1188
|
-
return forEachAsync(els, clickOptionFn)
|
|
1331
|
+
return forEachAsync(els, clickOptionFn)
|
|
1189
1332
|
}
|
|
1190
1333
|
|
|
1191
1334
|
/**
|
|
@@ -1194,27 +1337,27 @@ class WebDriver extends Helper {
|
|
|
1194
1337
|
* {{> attachFile }}
|
|
1195
1338
|
*/
|
|
1196
1339
|
async attachFile(locator, pathToFile) {
|
|
1197
|
-
let file = path.join(global.codecept_dir, pathToFile)
|
|
1340
|
+
let file = path.join(global.codecept_dir, pathToFile)
|
|
1198
1341
|
if (!fileExists(file)) {
|
|
1199
|
-
throw new Error(`File at ${file} can not be found on local system`)
|
|
1342
|
+
throw new Error(`File at ${file} can not be found on local system`)
|
|
1200
1343
|
}
|
|
1201
1344
|
|
|
1202
|
-
const res = await findFields.call(this, locator)
|
|
1203
|
-
this.debug(`Uploading ${file}`)
|
|
1204
|
-
assertElementExists(res, locator, 'File field')
|
|
1205
|
-
const el = usingFirstElement(res)
|
|
1345
|
+
const res = await findFields.call(this, locator)
|
|
1346
|
+
this.debug(`Uploading ${file}`)
|
|
1347
|
+
assertElementExists(res, locator, 'File field')
|
|
1348
|
+
const el = usingFirstElement(res)
|
|
1206
1349
|
|
|
1207
1350
|
// Remote Upload (when running Selenium Server)
|
|
1208
|
-
if (this.options.remoteFileUpload
|
|
1351
|
+
if (this.options.remoteFileUpload) {
|
|
1209
1352
|
try {
|
|
1210
|
-
this.debugSection('File', 'Uploading file to remote server')
|
|
1211
|
-
file = await this.browser.uploadFile(file)
|
|
1353
|
+
this.debugSection('File', 'Uploading file to remote server')
|
|
1354
|
+
file = await this.browser.uploadFile(file)
|
|
1212
1355
|
} catch (err) {
|
|
1213
|
-
throw new Error(`File can't be transferred to remote server. Set \`remoteFileUpload: false\` in config to upload file locally.\n${err.message}`)
|
|
1356
|
+
throw new Error(`File can't be transferred to remote server. Set \`remoteFileUpload: false\` in config to upload file locally.\n${err.message}`)
|
|
1214
1357
|
}
|
|
1215
1358
|
}
|
|
1216
1359
|
|
|
1217
|
-
return el.addValue(file)
|
|
1360
|
+
return el.addValue(file)
|
|
1218
1361
|
}
|
|
1219
1362
|
|
|
1220
1363
|
/**
|
|
@@ -1222,19 +1365,20 @@ class WebDriver extends Helper {
|
|
|
1222
1365
|
* {{> checkOption }}
|
|
1223
1366
|
*/
|
|
1224
1367
|
async checkOption(field, context = null) {
|
|
1225
|
-
const clickMethod = this.browser.isMobile &&
|
|
1226
|
-
const locateFn = prepareLocateFn.call(this, context)
|
|
1368
|
+
const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick'
|
|
1369
|
+
const locateFn = prepareLocateFn.call(this, context)
|
|
1370
|
+
|
|
1371
|
+
const res = await findCheckable.call(this, field, locateFn)
|
|
1227
1372
|
|
|
1228
|
-
|
|
1373
|
+
assertElementExists(res, field, 'Checkable')
|
|
1374
|
+
const elem = usingFirstElement(res)
|
|
1375
|
+
const elementId = getElementId(elem)
|
|
1376
|
+
highlightActiveElement.call(this, elem)
|
|
1229
1377
|
|
|
1230
|
-
|
|
1231
|
-
const elem = usingFirstElement(res);
|
|
1232
|
-
const elementId = getElementId(elem);
|
|
1233
|
-
highlightActiveElement.call(this, elem);
|
|
1378
|
+
const isSelected = await isElementChecked(this.browser, elementId)
|
|
1234
1379
|
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
return this.browser[clickMethod](elementId);
|
|
1380
|
+
if (isSelected) return Promise.resolve(true)
|
|
1381
|
+
return this.browser[clickMethod](elementId)
|
|
1238
1382
|
}
|
|
1239
1383
|
|
|
1240
1384
|
/**
|
|
@@ -1242,19 +1386,20 @@ class WebDriver extends Helper {
|
|
|
1242
1386
|
* {{> uncheckOption }}
|
|
1243
1387
|
*/
|
|
1244
1388
|
async uncheckOption(field, context = null) {
|
|
1245
|
-
const clickMethod = this.browser.isMobile &&
|
|
1246
|
-
const locateFn = prepareLocateFn.call(this, context)
|
|
1389
|
+
const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick'
|
|
1390
|
+
const locateFn = prepareLocateFn.call(this, context)
|
|
1247
1391
|
|
|
1248
|
-
const res = await findCheckable.call(this, field, locateFn)
|
|
1392
|
+
const res = await findCheckable.call(this, field, locateFn)
|
|
1249
1393
|
|
|
1250
|
-
assertElementExists(res, field, 'Checkable')
|
|
1251
|
-
const elem = usingFirstElement(res)
|
|
1252
|
-
const elementId = getElementId(elem)
|
|
1253
|
-
highlightActiveElement.call(this, elem)
|
|
1394
|
+
assertElementExists(res, field, 'Checkable')
|
|
1395
|
+
const elem = usingFirstElement(res)
|
|
1396
|
+
const elementId = getElementId(elem)
|
|
1397
|
+
highlightActiveElement.call(this, elem)
|
|
1254
1398
|
|
|
1255
|
-
const isSelected = await this.browser
|
|
1256
|
-
|
|
1257
|
-
return
|
|
1399
|
+
const isSelected = await isElementChecked(this.browser, elementId)
|
|
1400
|
+
|
|
1401
|
+
if (!isSelected) return Promise.resolve(true)
|
|
1402
|
+
return this.browser[clickMethod](elementId)
|
|
1258
1403
|
}
|
|
1259
1404
|
|
|
1260
1405
|
/**
|
|
@@ -1262,10 +1407,14 @@ class WebDriver extends Helper {
|
|
|
1262
1407
|
*
|
|
1263
1408
|
*/
|
|
1264
1409
|
async grabTextFromAll(locator) {
|
|
1265
|
-
const res = await this._locate(locator, true)
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1410
|
+
const res = await this._locate(locator, true)
|
|
1411
|
+
let val = []
|
|
1412
|
+
await forEachAsync(res, async el => {
|
|
1413
|
+
const text = await this.browser.getElementText(getElementId(el))
|
|
1414
|
+
val.push(text)
|
|
1415
|
+
})
|
|
1416
|
+
this.debugSection('GrabText', String(val))
|
|
1417
|
+
return val
|
|
1269
1418
|
}
|
|
1270
1419
|
|
|
1271
1420
|
/**
|
|
@@ -1273,13 +1422,13 @@ class WebDriver extends Helper {
|
|
|
1273
1422
|
*
|
|
1274
1423
|
*/
|
|
1275
1424
|
async grabTextFrom(locator) {
|
|
1276
|
-
const texts = await this.grabTextFromAll(locator)
|
|
1277
|
-
assertElementExists(texts, locator)
|
|
1425
|
+
const texts = await this.grabTextFromAll(locator)
|
|
1426
|
+
assertElementExists(texts, locator)
|
|
1278
1427
|
if (texts.length > 1) {
|
|
1279
|
-
this.debugSection('GrabText', `Using first element out of ${texts.length}`)
|
|
1428
|
+
this.debugSection('GrabText', `Using first element out of ${texts.length}`)
|
|
1280
1429
|
}
|
|
1281
1430
|
|
|
1282
|
-
return texts[0]
|
|
1431
|
+
return texts[0]
|
|
1283
1432
|
}
|
|
1284
1433
|
|
|
1285
1434
|
/**
|
|
@@ -1287,10 +1436,10 @@ class WebDriver extends Helper {
|
|
|
1287
1436
|
*
|
|
1288
1437
|
*/
|
|
1289
1438
|
async grabHTMLFromAll(locator) {
|
|
1290
|
-
const elems = await this._locate(locator, true)
|
|
1291
|
-
const html = await forEachAsync(elems, elem => elem.getHTML(false))
|
|
1292
|
-
this.debugSection('GrabHTML', String(html))
|
|
1293
|
-
return html
|
|
1439
|
+
const elems = await this._locate(locator, true)
|
|
1440
|
+
const html = await forEachAsync(elems, elem => elem.getHTML(false))
|
|
1441
|
+
this.debugSection('GrabHTML', String(html))
|
|
1442
|
+
return html
|
|
1294
1443
|
}
|
|
1295
1444
|
|
|
1296
1445
|
/**
|
|
@@ -1298,13 +1447,13 @@ class WebDriver extends Helper {
|
|
|
1298
1447
|
*
|
|
1299
1448
|
*/
|
|
1300
1449
|
async grabHTMLFrom(locator) {
|
|
1301
|
-
const html = await this.grabHTMLFromAll(locator)
|
|
1302
|
-
assertElementExists(html, locator)
|
|
1450
|
+
const html = await this.grabHTMLFromAll(locator)
|
|
1451
|
+
assertElementExists(html, locator)
|
|
1303
1452
|
if (html.length > 1) {
|
|
1304
|
-
this.debugSection('GrabHTML', `Using first element out of ${html.length}`)
|
|
1453
|
+
this.debugSection('GrabHTML', `Using first element out of ${html.length}`)
|
|
1305
1454
|
}
|
|
1306
1455
|
|
|
1307
|
-
return html[0]
|
|
1456
|
+
return html[0]
|
|
1308
1457
|
}
|
|
1309
1458
|
|
|
1310
1459
|
/**
|
|
@@ -1312,11 +1461,11 @@ class WebDriver extends Helper {
|
|
|
1312
1461
|
*
|
|
1313
1462
|
*/
|
|
1314
1463
|
async grabValueFromAll(locator) {
|
|
1315
|
-
const res = await this._locate(locator, true)
|
|
1316
|
-
const val = await forEachAsync(res, el => el.getValue())
|
|
1317
|
-
this.debugSection('GrabValue', String(val))
|
|
1464
|
+
const res = await this._locate(locator, true)
|
|
1465
|
+
const val = await forEachAsync(res, el => el.getValue())
|
|
1466
|
+
this.debugSection('GrabValue', String(val))
|
|
1318
1467
|
|
|
1319
|
-
return val
|
|
1468
|
+
return val
|
|
1320
1469
|
}
|
|
1321
1470
|
|
|
1322
1471
|
/**
|
|
@@ -1324,92 +1473,92 @@ class WebDriver extends Helper {
|
|
|
1324
1473
|
*
|
|
1325
1474
|
*/
|
|
1326
1475
|
async grabValueFrom(locator) {
|
|
1327
|
-
const values = await this.grabValueFromAll(locator)
|
|
1328
|
-
assertElementExists(values, locator)
|
|
1476
|
+
const values = await this.grabValueFromAll(locator)
|
|
1477
|
+
assertElementExists(values, locator)
|
|
1329
1478
|
if (values.length > 1) {
|
|
1330
|
-
this.debugSection('GrabValue', `Using first element out of ${values.length}`)
|
|
1479
|
+
this.debugSection('GrabValue', `Using first element out of ${values.length}`)
|
|
1331
1480
|
}
|
|
1332
1481
|
|
|
1333
|
-
return values[0]
|
|
1482
|
+
return values[0]
|
|
1334
1483
|
}
|
|
1335
1484
|
|
|
1336
1485
|
/**
|
|
1337
1486
|
* {{> grabCssPropertyFromAll }}
|
|
1338
1487
|
*/
|
|
1339
1488
|
async grabCssPropertyFromAll(locator, cssProperty) {
|
|
1340
|
-
const res = await this._locate(locator, true)
|
|
1341
|
-
const val = await forEachAsync(res, async el => this.browser.getElementCSSValue(getElementId(el), cssProperty))
|
|
1342
|
-
this.debugSection('Grab', String(val))
|
|
1343
|
-
return val
|
|
1489
|
+
const res = await this._locate(locator, true)
|
|
1490
|
+
const val = await forEachAsync(res, async el => this.browser.getElementCSSValue(getElementId(el), cssProperty))
|
|
1491
|
+
this.debugSection('Grab', String(val))
|
|
1492
|
+
return val
|
|
1344
1493
|
}
|
|
1345
1494
|
|
|
1346
1495
|
/**
|
|
1347
1496
|
* {{> grabCssPropertyFrom }}
|
|
1348
1497
|
*/
|
|
1349
1498
|
async grabCssPropertyFrom(locator, cssProperty) {
|
|
1350
|
-
const cssValues = await this.grabCssPropertyFromAll(locator, cssProperty)
|
|
1351
|
-
assertElementExists(cssValues, locator)
|
|
1499
|
+
const cssValues = await this.grabCssPropertyFromAll(locator, cssProperty)
|
|
1500
|
+
assertElementExists(cssValues, locator)
|
|
1352
1501
|
|
|
1353
1502
|
if (cssValues.length > 1) {
|
|
1354
|
-
this.debugSection('GrabCSS', `Using first element out of ${cssValues.length}`)
|
|
1503
|
+
this.debugSection('GrabCSS', `Using first element out of ${cssValues.length}`)
|
|
1355
1504
|
}
|
|
1356
1505
|
|
|
1357
|
-
return cssValues[0]
|
|
1506
|
+
return cssValues[0]
|
|
1358
1507
|
}
|
|
1359
1508
|
|
|
1360
1509
|
/**
|
|
1361
1510
|
* {{> grabAttributeFromAll }}
|
|
1362
1511
|
*/
|
|
1363
1512
|
async grabAttributeFromAll(locator, attr) {
|
|
1364
|
-
const res = await this._locate(locator, true)
|
|
1365
|
-
const val = await forEachAsync(res, async el => el.getAttribute(attr))
|
|
1366
|
-
this.debugSection('GrabAttribute', String(val))
|
|
1367
|
-
return val
|
|
1513
|
+
const res = await this._locate(locator, true)
|
|
1514
|
+
const val = await forEachAsync(res, async el => el.getAttribute(attr))
|
|
1515
|
+
this.debugSection('GrabAttribute', String(val))
|
|
1516
|
+
return val
|
|
1368
1517
|
}
|
|
1369
1518
|
|
|
1370
1519
|
/**
|
|
1371
1520
|
* {{> grabAttributeFrom }}
|
|
1372
1521
|
*/
|
|
1373
1522
|
async grabAttributeFrom(locator, attr) {
|
|
1374
|
-
const attrs = await this.grabAttributeFromAll(locator, attr)
|
|
1375
|
-
assertElementExists(attrs, locator)
|
|
1523
|
+
const attrs = await this.grabAttributeFromAll(locator, attr)
|
|
1524
|
+
assertElementExists(attrs, locator)
|
|
1376
1525
|
if (attrs.length > 1) {
|
|
1377
|
-
this.debugSection('GrabAttribute', `Using first element out of ${attrs.length}`)
|
|
1526
|
+
this.debugSection('GrabAttribute', `Using first element out of ${attrs.length}`)
|
|
1378
1527
|
}
|
|
1379
|
-
return attrs[0]
|
|
1528
|
+
return attrs[0]
|
|
1380
1529
|
}
|
|
1381
1530
|
|
|
1382
1531
|
/**
|
|
1383
1532
|
* {{> seeInTitle }}
|
|
1384
1533
|
*/
|
|
1385
1534
|
async seeInTitle(text) {
|
|
1386
|
-
const title = await this.browser.getTitle()
|
|
1387
|
-
return stringIncludes('web page title').assert(text, title)
|
|
1535
|
+
const title = await this.browser.getTitle()
|
|
1536
|
+
return stringIncludes('web page title').assert(text, title)
|
|
1388
1537
|
}
|
|
1389
1538
|
|
|
1390
1539
|
/**
|
|
1391
1540
|
* {{> seeTitleEquals }}
|
|
1392
1541
|
*/
|
|
1393
1542
|
async seeTitleEquals(text) {
|
|
1394
|
-
const title = await this.browser.getTitle()
|
|
1395
|
-
return assert.equal(title, text, `expected web page title to be ${text}, but found ${title}`)
|
|
1543
|
+
const title = await this.browser.getTitle()
|
|
1544
|
+
return assert.equal(title, text, `expected web page title to be ${text}, but found ${title}`)
|
|
1396
1545
|
}
|
|
1397
1546
|
|
|
1398
1547
|
/**
|
|
1399
1548
|
* {{> dontSeeInTitle }}
|
|
1400
1549
|
*/
|
|
1401
1550
|
async dontSeeInTitle(text) {
|
|
1402
|
-
const title = await this.browser.getTitle()
|
|
1403
|
-
return stringIncludes('web page title').negate(text, title)
|
|
1551
|
+
const title = await this.browser.getTitle()
|
|
1552
|
+
return stringIncludes('web page title').negate(text, title)
|
|
1404
1553
|
}
|
|
1405
1554
|
|
|
1406
1555
|
/**
|
|
1407
1556
|
* {{> grabTitle }}
|
|
1408
1557
|
*/
|
|
1409
1558
|
async grabTitle() {
|
|
1410
|
-
const title = await this.browser.getTitle()
|
|
1411
|
-
this.debugSection('Title', title)
|
|
1412
|
-
return title
|
|
1559
|
+
const title = await this.browser.getTitle()
|
|
1560
|
+
this.debugSection('Title', title)
|
|
1561
|
+
return title
|
|
1413
1562
|
}
|
|
1414
1563
|
|
|
1415
1564
|
/**
|
|
@@ -1418,14 +1567,14 @@ class WebDriver extends Helper {
|
|
|
1418
1567
|
* {{ react }}
|
|
1419
1568
|
*/
|
|
1420
1569
|
async see(text, context = null) {
|
|
1421
|
-
return proceedSee.call(this, 'assert', text, context)
|
|
1570
|
+
return proceedSee.call(this, 'assert', text, context)
|
|
1422
1571
|
}
|
|
1423
1572
|
|
|
1424
1573
|
/**
|
|
1425
1574
|
* {{> seeTextEquals }}
|
|
1426
1575
|
*/
|
|
1427
1576
|
async seeTextEquals(text, context = null) {
|
|
1428
|
-
return proceedSee.call(this, 'assert', text, context, true)
|
|
1577
|
+
return proceedSee.call(this, 'assert', text, context, true)
|
|
1429
1578
|
}
|
|
1430
1579
|
|
|
1431
1580
|
/**
|
|
@@ -1434,7 +1583,7 @@ class WebDriver extends Helper {
|
|
|
1434
1583
|
* {{ react }}
|
|
1435
1584
|
*/
|
|
1436
1585
|
async dontSee(text, context = null) {
|
|
1437
|
-
return proceedSee.call(this, 'negate', text, context)
|
|
1586
|
+
return proceedSee.call(this, 'negate', text, context)
|
|
1438
1587
|
}
|
|
1439
1588
|
|
|
1440
1589
|
/**
|
|
@@ -1442,8 +1591,8 @@ class WebDriver extends Helper {
|
|
|
1442
1591
|
*
|
|
1443
1592
|
*/
|
|
1444
1593
|
async seeInField(field, value) {
|
|
1445
|
-
const _value =
|
|
1446
|
-
return proceedSeeField.call(this, 'assert', field, _value)
|
|
1594
|
+
const _value = typeof value === 'boolean' ? value : value.toString()
|
|
1595
|
+
return proceedSeeField.call(this, 'assert', field, _value)
|
|
1447
1596
|
}
|
|
1448
1597
|
|
|
1449
1598
|
/**
|
|
@@ -1451,8 +1600,8 @@ class WebDriver extends Helper {
|
|
|
1451
1600
|
*
|
|
1452
1601
|
*/
|
|
1453
1602
|
async dontSeeInField(field, value) {
|
|
1454
|
-
const _value =
|
|
1455
|
-
return proceedSeeField.call(this, 'negate', field, _value)
|
|
1603
|
+
const _value = typeof value === 'boolean' ? value : value.toString()
|
|
1604
|
+
return proceedSeeField.call(this, 'negate', field, _value)
|
|
1456
1605
|
}
|
|
1457
1606
|
|
|
1458
1607
|
/**
|
|
@@ -1460,7 +1609,7 @@ class WebDriver extends Helper {
|
|
|
1460
1609
|
* {{> seeCheckboxIsChecked }}
|
|
1461
1610
|
*/
|
|
1462
1611
|
async seeCheckboxIsChecked(field) {
|
|
1463
|
-
return proceedSeeCheckbox.call(this, 'assert', field)
|
|
1612
|
+
return proceedSeeCheckbox.call(this, 'assert', field)
|
|
1464
1613
|
}
|
|
1465
1614
|
|
|
1466
1615
|
/**
|
|
@@ -1468,7 +1617,7 @@ class WebDriver extends Helper {
|
|
|
1468
1617
|
* {{> dontSeeCheckboxIsChecked }}
|
|
1469
1618
|
*/
|
|
1470
1619
|
async dontSeeCheckboxIsChecked(field) {
|
|
1471
|
-
return proceedSeeCheckbox.call(this, 'negate', field)
|
|
1620
|
+
return proceedSeeCheckbox.call(this, 'negate', field)
|
|
1472
1621
|
}
|
|
1473
1622
|
|
|
1474
1623
|
/**
|
|
@@ -1477,13 +1626,13 @@ class WebDriver extends Helper {
|
|
|
1477
1626
|
*
|
|
1478
1627
|
*/
|
|
1479
1628
|
async seeElement(locator) {
|
|
1480
|
-
const res = await this._locate(locator, true)
|
|
1481
|
-
assertElementExists(res, locator)
|
|
1482
|
-
const selected = await forEachAsync(res, async el => el.isDisplayed())
|
|
1629
|
+
const res = await this._locate(locator, true)
|
|
1630
|
+
assertElementExists(res, locator)
|
|
1631
|
+
const selected = await forEachAsync(res, async el => el.isDisplayed())
|
|
1483
1632
|
try {
|
|
1484
|
-
return truth(`elements of ${
|
|
1633
|
+
return truth(`elements of ${new Locator(locator)}`, 'to be seen').assert(selected)
|
|
1485
1634
|
} catch (e) {
|
|
1486
|
-
dontSeeElementError(locator)
|
|
1635
|
+
dontSeeElementError(locator)
|
|
1487
1636
|
}
|
|
1488
1637
|
}
|
|
1489
1638
|
|
|
@@ -1492,15 +1641,15 @@ class WebDriver extends Helper {
|
|
|
1492
1641
|
* {{ react }}
|
|
1493
1642
|
*/
|
|
1494
1643
|
async dontSeeElement(locator) {
|
|
1495
|
-
const res = await this._locate(locator, false)
|
|
1644
|
+
const res = await this._locate(locator, false)
|
|
1496
1645
|
if (!res || res.length === 0) {
|
|
1497
|
-
return truth(`elements of ${
|
|
1646
|
+
return truth(`elements of ${new Locator(locator)}`, 'to be seen').negate(false)
|
|
1498
1647
|
}
|
|
1499
|
-
const selected = await forEachAsync(res, async el => el.isDisplayed())
|
|
1648
|
+
const selected = await forEachAsync(res, async el => el.isDisplayed())
|
|
1500
1649
|
try {
|
|
1501
|
-
return truth(`elements of ${
|
|
1650
|
+
return truth(`elements of ${new Locator(locator)}`, 'to be seen').negate(selected)
|
|
1502
1651
|
} catch (e) {
|
|
1503
|
-
seeElementError(locator)
|
|
1652
|
+
seeElementError(locator)
|
|
1504
1653
|
}
|
|
1505
1654
|
}
|
|
1506
1655
|
|
|
@@ -1509,11 +1658,11 @@ class WebDriver extends Helper {
|
|
|
1509
1658
|
*
|
|
1510
1659
|
*/
|
|
1511
1660
|
async seeElementInDOM(locator) {
|
|
1512
|
-
const res = await this._res(locator)
|
|
1661
|
+
const res = await this._res(locator)
|
|
1513
1662
|
try {
|
|
1514
|
-
return empty('elements').negate(res)
|
|
1663
|
+
return empty('elements').negate(res)
|
|
1515
1664
|
} catch (e) {
|
|
1516
|
-
dontSeeElementInDOMError(locator)
|
|
1665
|
+
dontSeeElementInDOMError(locator)
|
|
1517
1666
|
}
|
|
1518
1667
|
}
|
|
1519
1668
|
|
|
@@ -1522,11 +1671,11 @@ class WebDriver extends Helper {
|
|
|
1522
1671
|
*
|
|
1523
1672
|
*/
|
|
1524
1673
|
async dontSeeElementInDOM(locator) {
|
|
1525
|
-
const res = await this._res(locator)
|
|
1674
|
+
const res = await this._res(locator)
|
|
1526
1675
|
try {
|
|
1527
|
-
return empty('elements').assert(res)
|
|
1676
|
+
return empty('elements').assert(res)
|
|
1528
1677
|
} catch (e) {
|
|
1529
|
-
seeElementInDOMError(locator)
|
|
1678
|
+
seeElementInDOMError(locator)
|
|
1530
1679
|
}
|
|
1531
1680
|
}
|
|
1532
1681
|
|
|
@@ -1535,8 +1684,8 @@ class WebDriver extends Helper {
|
|
|
1535
1684
|
*
|
|
1536
1685
|
*/
|
|
1537
1686
|
async seeInSource(text) {
|
|
1538
|
-
const source = await this.browser.getPageSource()
|
|
1539
|
-
return stringIncludes('HTML source of a page').assert(text, source)
|
|
1687
|
+
const source = await this.browser.getPageSource()
|
|
1688
|
+
return stringIncludes('HTML source of a page').assert(text, source)
|
|
1540
1689
|
}
|
|
1541
1690
|
|
|
1542
1691
|
/**
|
|
@@ -1544,35 +1693,31 @@ class WebDriver extends Helper {
|
|
|
1544
1693
|
*
|
|
1545
1694
|
*/
|
|
1546
1695
|
async grabSource() {
|
|
1547
|
-
return this.browser.getPageSource()
|
|
1696
|
+
return this.browser.getPageSource()
|
|
1548
1697
|
}
|
|
1549
1698
|
|
|
1550
1699
|
/**
|
|
1551
1700
|
* {{> grabBrowserLogs }}
|
|
1552
1701
|
*/
|
|
1553
1702
|
async grabBrowserLogs() {
|
|
1554
|
-
|
|
1555
|
-
this.debug('Logs not available in W3C specification');
|
|
1556
|
-
return;
|
|
1557
|
-
}
|
|
1558
|
-
return this.browser.getLogs('browser');
|
|
1703
|
+
return browserLogs
|
|
1559
1704
|
}
|
|
1560
1705
|
|
|
1561
1706
|
/**
|
|
1562
1707
|
* {{> grabCurrentUrl }}
|
|
1563
1708
|
*/
|
|
1564
1709
|
async grabCurrentUrl() {
|
|
1565
|
-
const res = await this.browser.getUrl()
|
|
1566
|
-
this.debugSection('Url', res)
|
|
1567
|
-
return res
|
|
1710
|
+
const res = await this.browser.getUrl()
|
|
1711
|
+
this.debugSection('Url', res)
|
|
1712
|
+
return res
|
|
1568
1713
|
}
|
|
1569
1714
|
|
|
1570
1715
|
/**
|
|
1571
1716
|
* {{> dontSeeInSource }}
|
|
1572
1717
|
*/
|
|
1573
1718
|
async dontSeeInSource(text) {
|
|
1574
|
-
const source = await this.browser.getPageSource()
|
|
1575
|
-
return stringIncludes('HTML source of a page').negate(text, source)
|
|
1719
|
+
const source = await this.browser.getPageSource()
|
|
1720
|
+
return stringIncludes('HTML source of a page').negate(text, source)
|
|
1576
1721
|
}
|
|
1577
1722
|
|
|
1578
1723
|
/**
|
|
@@ -1580,8 +1725,8 @@ class WebDriver extends Helper {
|
|
|
1580
1725
|
* {{ react }}
|
|
1581
1726
|
*/
|
|
1582
1727
|
async seeNumberOfElements(locator, num) {
|
|
1583
|
-
const res = await this._locate(locator)
|
|
1584
|
-
return assert.equal(res.length, num, `expected number of elements (${
|
|
1728
|
+
const res = await this._locate(locator)
|
|
1729
|
+
return assert.equal(res.length, num, `expected number of elements (${new Locator(locator)}) is ${num}, but found ${res.length}`)
|
|
1585
1730
|
}
|
|
1586
1731
|
|
|
1587
1732
|
/**
|
|
@@ -1589,86 +1734,82 @@ class WebDriver extends Helper {
|
|
|
1589
1734
|
* {{ react }}
|
|
1590
1735
|
*/
|
|
1591
1736
|
async seeNumberOfVisibleElements(locator, num) {
|
|
1592
|
-
const res = await this.grabNumberOfVisibleElements(locator)
|
|
1593
|
-
return assert.equal(res, num, `expected number of visible elements (${
|
|
1737
|
+
const res = await this.grabNumberOfVisibleElements(locator)
|
|
1738
|
+
return assert.equal(res, num, `expected number of visible elements (${new Locator(locator)}) is ${num}, but found ${res}`)
|
|
1594
1739
|
}
|
|
1595
1740
|
|
|
1596
1741
|
/**
|
|
1597
1742
|
* {{> seeCssPropertiesOnElements }}
|
|
1598
1743
|
*/
|
|
1599
1744
|
async seeCssPropertiesOnElements(locator, cssProperties) {
|
|
1600
|
-
const res = await this._locate(locator)
|
|
1601
|
-
assertElementExists(res, locator)
|
|
1745
|
+
const res = await this._locate(locator)
|
|
1746
|
+
assertElementExists(res, locator)
|
|
1602
1747
|
|
|
1603
|
-
const cssPropertiesCamelCase = convertCssPropertiesToCamelCase(cssProperties)
|
|
1604
|
-
const elemAmount = res.length
|
|
1605
|
-
let props = []
|
|
1748
|
+
const cssPropertiesCamelCase = convertCssPropertiesToCamelCase(cssProperties)
|
|
1749
|
+
const elemAmount = res.length
|
|
1750
|
+
let props = []
|
|
1606
1751
|
|
|
1607
1752
|
for (const element of res) {
|
|
1608
1753
|
for (const prop of Object.keys(cssProperties)) {
|
|
1609
|
-
const cssProp = await this.grabCssPropertyFrom(locator, prop)
|
|
1754
|
+
const cssProp = await this.grabCssPropertyFrom(locator, prop)
|
|
1610
1755
|
if (isColorProperty(prop)) {
|
|
1611
|
-
props.push(convertColorToRGBA(cssProp))
|
|
1756
|
+
props.push(convertColorToRGBA(cssProp))
|
|
1612
1757
|
} else {
|
|
1613
|
-
props.push(cssProp)
|
|
1758
|
+
props.push(cssProp)
|
|
1614
1759
|
}
|
|
1615
1760
|
}
|
|
1616
1761
|
}
|
|
1617
1762
|
|
|
1618
|
-
const values = Object.keys(cssPropertiesCamelCase).map(key => cssPropertiesCamelCase[key])
|
|
1619
|
-
if (!Array.isArray(props)) props = [props]
|
|
1620
|
-
let chunked = chunkArray(props, values.length)
|
|
1621
|
-
chunked = chunked.filter(
|
|
1763
|
+
const values = Object.keys(cssPropertiesCamelCase).map(key => cssPropertiesCamelCase[key])
|
|
1764
|
+
if (!Array.isArray(props)) props = [props]
|
|
1765
|
+
let chunked = chunkArray(props, values.length)
|
|
1766
|
+
chunked = chunked.filter(val => {
|
|
1622
1767
|
for (let i = 0; i < val.length; ++i) {
|
|
1623
|
-
|
|
1624
|
-
if (val[i] != values[i]) return false;
|
|
1768
|
+
if (val[i] != values[i]) return false
|
|
1625
1769
|
}
|
|
1626
|
-
return true
|
|
1627
|
-
})
|
|
1628
|
-
return equals(`all elements (${
|
|
1770
|
+
return true
|
|
1771
|
+
})
|
|
1772
|
+
return equals(`all elements (${new Locator(locator)}) to have CSS property ${JSON.stringify(cssProperties)}`).assert(chunked.length, elemAmount)
|
|
1629
1773
|
}
|
|
1630
1774
|
|
|
1631
1775
|
/**
|
|
1632
1776
|
* {{> seeAttributesOnElements }}
|
|
1633
1777
|
*/
|
|
1634
1778
|
async seeAttributesOnElements(locator, attributes) {
|
|
1635
|
-
const res = await this._locate(locator)
|
|
1636
|
-
assertElementExists(res, locator)
|
|
1637
|
-
const elemAmount = res.length
|
|
1638
|
-
|
|
1639
|
-
let attrs = await forEachAsync(res, async
|
|
1640
|
-
return forEachAsync(Object.keys(attributes), async attr => el.getAttribute(attr))
|
|
1641
|
-
})
|
|
1642
|
-
|
|
1643
|
-
const values = Object.keys(attributes).map(key => attributes[key])
|
|
1644
|
-
if (!Array.isArray(attrs)) attrs = [attrs]
|
|
1645
|
-
let chunked = chunkArray(attrs, values.length)
|
|
1646
|
-
chunked = chunked.filter(
|
|
1779
|
+
const res = await this._locate(locator)
|
|
1780
|
+
assertElementExists(res, locator)
|
|
1781
|
+
const elemAmount = res.length
|
|
1782
|
+
|
|
1783
|
+
let attrs = await forEachAsync(res, async el => {
|
|
1784
|
+
return forEachAsync(Object.keys(attributes), async attr => el.getAttribute(attr))
|
|
1785
|
+
})
|
|
1786
|
+
|
|
1787
|
+
const values = Object.keys(attributes).map(key => attributes[key])
|
|
1788
|
+
if (!Array.isArray(attrs)) attrs = [attrs]
|
|
1789
|
+
let chunked = chunkArray(attrs, values.length)
|
|
1790
|
+
chunked = chunked.filter(val => {
|
|
1647
1791
|
for (let i = 0; i < val.length; ++i) {
|
|
1648
|
-
const _actual = Number.isNaN(val[i]) ||
|
|
1649
|
-
const _expected = Number.isNaN(values[i]) ||
|
|
1792
|
+
const _actual = Number.isNaN(val[i]) || typeof values[i] === 'string' ? val[i] : Number.parseInt(val[i], 10)
|
|
1793
|
+
const _expected = Number.isNaN(values[i]) || typeof values[i] === 'string' ? values[i] : Number.parseInt(values[i], 10)
|
|
1650
1794
|
// the attribute could be a boolean
|
|
1651
|
-
if (typeof _actual === 'boolean') return _actual === _expected
|
|
1652
|
-
if (_actual !== _expected) return false
|
|
1795
|
+
if (typeof _actual === 'boolean') return _actual === _expected
|
|
1796
|
+
if (_actual !== _expected) return false
|
|
1653
1797
|
}
|
|
1654
|
-
return true
|
|
1655
|
-
})
|
|
1656
|
-
return assert.ok(
|
|
1657
|
-
chunked.length === elemAmount,
|
|
1658
|
-
`expected all elements (${(new Locator(locator))}) to have attributes ${JSON.stringify(attributes)}`,
|
|
1659
|
-
);
|
|
1798
|
+
return true
|
|
1799
|
+
})
|
|
1800
|
+
return assert.ok(chunked.length === elemAmount, `expected all elements (${new Locator(locator)}) to have attributes ${JSON.stringify(attributes)}`)
|
|
1660
1801
|
}
|
|
1661
1802
|
|
|
1662
1803
|
/**
|
|
1663
1804
|
* {{> grabNumberOfVisibleElements }}
|
|
1664
1805
|
*/
|
|
1665
1806
|
async grabNumberOfVisibleElements(locator) {
|
|
1666
|
-
const res = await this._locate(locator)
|
|
1807
|
+
const res = await this._locate(locator)
|
|
1667
1808
|
|
|
1668
|
-
let selected = await forEachAsync(res, async el => el.isDisplayed())
|
|
1669
|
-
if (!Array.isArray(selected)) selected = [selected]
|
|
1670
|
-
selected = selected.filter(val => val === true)
|
|
1671
|
-
return selected.length
|
|
1809
|
+
let selected = await forEachAsync(res, async el => el.isDisplayed())
|
|
1810
|
+
if (!Array.isArray(selected)) selected = [selected]
|
|
1811
|
+
selected = selected.filter(val => val === true)
|
|
1812
|
+
return selected.length
|
|
1672
1813
|
}
|
|
1673
1814
|
|
|
1674
1815
|
/**
|
|
@@ -1676,8 +1817,8 @@ class WebDriver extends Helper {
|
|
|
1676
1817
|
*
|
|
1677
1818
|
*/
|
|
1678
1819
|
async seeInCurrentUrl(url) {
|
|
1679
|
-
const res = await this.browser.getUrl()
|
|
1680
|
-
return stringIncludes('url').assert(url, decodeUrl(res))
|
|
1820
|
+
const res = await this.browser.getUrl()
|
|
1821
|
+
return stringIncludes('url').assert(url, decodeUrl(res))
|
|
1681
1822
|
}
|
|
1682
1823
|
|
|
1683
1824
|
/**
|
|
@@ -1685,8 +1826,8 @@ class WebDriver extends Helper {
|
|
|
1685
1826
|
*
|
|
1686
1827
|
*/
|
|
1687
1828
|
async dontSeeInCurrentUrl(url) {
|
|
1688
|
-
const res = await this.browser.getUrl()
|
|
1689
|
-
return stringIncludes('url').negate(url, decodeUrl(res))
|
|
1829
|
+
const res = await this.browser.getUrl()
|
|
1830
|
+
return stringIncludes('url').negate(url, decodeUrl(res))
|
|
1690
1831
|
}
|
|
1691
1832
|
|
|
1692
1833
|
/**
|
|
@@ -1694,8 +1835,8 @@ class WebDriver extends Helper {
|
|
|
1694
1835
|
*
|
|
1695
1836
|
*/
|
|
1696
1837
|
async seeCurrentUrlEquals(url) {
|
|
1697
|
-
const res = await this.browser.getUrl()
|
|
1698
|
-
return urlEquals(this.options.url).assert(url, decodeUrl(res))
|
|
1838
|
+
const res = await this.browser.getUrl()
|
|
1839
|
+
return urlEquals(this.options.url).assert(url, decodeUrl(res))
|
|
1699
1840
|
}
|
|
1700
1841
|
|
|
1701
1842
|
/**
|
|
@@ -1703,8 +1844,8 @@ class WebDriver extends Helper {
|
|
|
1703
1844
|
*
|
|
1704
1845
|
*/
|
|
1705
1846
|
async dontSeeCurrentUrlEquals(url) {
|
|
1706
|
-
const res = await this.browser.getUrl()
|
|
1707
|
-
return urlEquals(this.options.url).negate(url, decodeUrl(res))
|
|
1847
|
+
const res = await this.browser.getUrl()
|
|
1848
|
+
return urlEquals(this.options.url).negate(url, decodeUrl(res))
|
|
1708
1849
|
}
|
|
1709
1850
|
|
|
1710
1851
|
/**
|
|
@@ -1713,7 +1854,7 @@ class WebDriver extends Helper {
|
|
|
1713
1854
|
* {{> executeScript }}
|
|
1714
1855
|
*/
|
|
1715
1856
|
executeScript(...args) {
|
|
1716
|
-
return this.browser.execute.apply(this.browser, args)
|
|
1857
|
+
return this.browser.execute.apply(this.browser, args)
|
|
1717
1858
|
}
|
|
1718
1859
|
|
|
1719
1860
|
/**
|
|
@@ -1721,7 +1862,7 @@ class WebDriver extends Helper {
|
|
|
1721
1862
|
*
|
|
1722
1863
|
*/
|
|
1723
1864
|
executeAsyncScript(...args) {
|
|
1724
|
-
return this.browser.executeAsync.apply(this.browser, args)
|
|
1865
|
+
return this.browser.executeAsync.apply(this.browser, args)
|
|
1725
1866
|
}
|
|
1726
1867
|
|
|
1727
1868
|
/**
|
|
@@ -1729,10 +1870,10 @@ class WebDriver extends Helper {
|
|
|
1729
1870
|
*
|
|
1730
1871
|
*/
|
|
1731
1872
|
async scrollIntoView(locator, scrollIntoViewOptions) {
|
|
1732
|
-
const res = await this._locate(withStrictLocator(locator), true)
|
|
1733
|
-
assertElementExists(res, locator)
|
|
1734
|
-
const elem = usingFirstElement(res)
|
|
1735
|
-
return elem.scrollIntoView(scrollIntoViewOptions)
|
|
1873
|
+
const res = await this._locate(withStrictLocator(locator), true)
|
|
1874
|
+
assertElementExists(res, locator)
|
|
1875
|
+
const elem = usingFirstElement(res)
|
|
1876
|
+
return elem.scrollIntoView(scrollIntoViewOptions)
|
|
1736
1877
|
}
|
|
1737
1878
|
|
|
1738
1879
|
/**
|
|
@@ -1741,42 +1882,51 @@ class WebDriver extends Helper {
|
|
|
1741
1882
|
*/
|
|
1742
1883
|
async scrollTo(locator, offsetX = 0, offsetY = 0) {
|
|
1743
1884
|
if (typeof locator === 'number' && typeof offsetX === 'number') {
|
|
1744
|
-
offsetY = offsetX
|
|
1745
|
-
offsetX = locator
|
|
1746
|
-
locator = null
|
|
1885
|
+
offsetY = offsetX
|
|
1886
|
+
offsetX = locator
|
|
1887
|
+
locator = null
|
|
1747
1888
|
}
|
|
1748
1889
|
|
|
1749
1890
|
if (locator) {
|
|
1750
|
-
const res = await this._locate(withStrictLocator(locator), true)
|
|
1751
|
-
assertElementExists(res, locator)
|
|
1752
|
-
const elem = usingFirstElement(res)
|
|
1753
|
-
const elementId = getElementId(elem)
|
|
1754
|
-
if (this.browser.isMobile && !this.browser.isW3C) return this.browser.touchScroll(offsetX, offsetY, elementId)
|
|
1755
|
-
const location = await elem.getLocation()
|
|
1756
|
-
assertElementExists(location, locator, 'Failed to receive', 'location')
|
|
1757
|
-
|
|
1758
|
-
return this.browser.execute(
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1891
|
+
const res = await this._locate(withStrictLocator(locator), true)
|
|
1892
|
+
assertElementExists(res, locator)
|
|
1893
|
+
const elem = usingFirstElement(res)
|
|
1894
|
+
const elementId = getElementId(elem)
|
|
1895
|
+
if (this.browser.isMobile && !this.browser.isW3C) return this.browser.touchScroll(offsetX, offsetY, elementId)
|
|
1896
|
+
const location = await elem.getLocation()
|
|
1897
|
+
assertElementExists(location, locator, 'Failed to receive', 'location')
|
|
1898
|
+
|
|
1899
|
+
return this.browser.execute(
|
|
1900
|
+
function (x, y) {
|
|
1901
|
+
return window.scrollTo(x, y)
|
|
1902
|
+
},
|
|
1903
|
+
location.x + offsetX,
|
|
1904
|
+
location.y + offsetY,
|
|
1905
|
+
)
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
if (this.browser.isMobile && !this.browser.isW3C) return this.browser.touchScroll(locator, offsetX, offsetY)
|
|
1909
|
+
|
|
1910
|
+
return this.browser.execute(
|
|
1911
|
+
function (x, y) {
|
|
1912
|
+
return window.scrollTo(x, y)
|
|
1913
|
+
},
|
|
1914
|
+
offsetX,
|
|
1915
|
+
offsetY,
|
|
1916
|
+
)
|
|
1767
1917
|
}
|
|
1768
1918
|
|
|
1769
1919
|
/**
|
|
1770
1920
|
* {{> moveCursorTo }}
|
|
1771
1921
|
*/
|
|
1772
1922
|
async moveCursorTo(locator, xOffset, yOffset) {
|
|
1773
|
-
const res = await this._locate(withStrictLocator(locator), true)
|
|
1774
|
-
assertElementExists(res, locator)
|
|
1775
|
-
const elem = usingFirstElement(res)
|
|
1923
|
+
const res = await this._locate(withStrictLocator(locator), true)
|
|
1924
|
+
assertElementExists(res, locator)
|
|
1925
|
+
const elem = usingFirstElement(res)
|
|
1776
1926
|
try {
|
|
1777
|
-
await elem.moveTo({ xOffset, yOffset })
|
|
1927
|
+
await elem.moveTo({ xOffset, yOffset })
|
|
1778
1928
|
} catch (e) {
|
|
1779
|
-
debug(e.message)
|
|
1929
|
+
output.debug(e.message)
|
|
1780
1930
|
}
|
|
1781
1931
|
}
|
|
1782
1932
|
|
|
@@ -1785,54 +1935,68 @@ class WebDriver extends Helper {
|
|
|
1785
1935
|
*
|
|
1786
1936
|
*/
|
|
1787
1937
|
async saveElementScreenshot(locator, fileName) {
|
|
1788
|
-
const outputFile = screenshotOutputFolder(fileName)
|
|
1938
|
+
const outputFile = screenshotOutputFolder(fileName)
|
|
1789
1939
|
|
|
1790
|
-
const res = await this._locate(withStrictLocator(locator), true)
|
|
1791
|
-
assertElementExists(res, locator)
|
|
1792
|
-
const elem = usingFirstElement(res)
|
|
1940
|
+
const res = await this._locate(withStrictLocator(locator), true)
|
|
1941
|
+
assertElementExists(res, locator)
|
|
1942
|
+
const elem = usingFirstElement(res)
|
|
1793
1943
|
|
|
1794
|
-
this.debug(`Screenshot of ${
|
|
1795
|
-
return elem.saveScreenshot(outputFile)
|
|
1944
|
+
this.debug(`Screenshot of ${new Locator(locator)} element has been saved to ${outputFile}`)
|
|
1945
|
+
return elem.saveScreenshot(outputFile)
|
|
1796
1946
|
}
|
|
1797
1947
|
|
|
1798
1948
|
/**
|
|
1799
1949
|
* {{> saveScreenshot }}
|
|
1800
1950
|
*/
|
|
1801
1951
|
async saveScreenshot(fileName, fullPage = false) {
|
|
1802
|
-
|
|
1952
|
+
let outputFile = screenshotOutputFolder(fileName)
|
|
1803
1953
|
|
|
1804
1954
|
if (this.activeSessionName) {
|
|
1805
|
-
const browser = this.sessionWindows[this.activeSessionName]
|
|
1955
|
+
const browser = this.sessionWindows[this.activeSessionName]
|
|
1956
|
+
|
|
1957
|
+
for (const sessionName in this.sessionWindows) {
|
|
1958
|
+
const activeSessionPage = this.sessionWindows[sessionName]
|
|
1959
|
+
outputFile = screenshotOutputFolder(`${sessionName}_${fileName}`)
|
|
1960
|
+
|
|
1961
|
+
this.debug(`${sessionName} - Screenshot is saving to ${outputFile}`)
|
|
1806
1962
|
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1963
|
+
if (browser) {
|
|
1964
|
+
this.debug(`Screenshot of ${sessionName} session has been saved to ${outputFile}`)
|
|
1965
|
+
await browser.saveScreenshot(outputFile)
|
|
1966
|
+
}
|
|
1810
1967
|
}
|
|
1811
1968
|
}
|
|
1812
1969
|
|
|
1813
1970
|
if (!fullPage) {
|
|
1814
|
-
this.debug(`Screenshot has been saved to ${outputFile}`)
|
|
1815
|
-
|
|
1971
|
+
this.debug(`Screenshot has been saved to ${outputFile}`)
|
|
1972
|
+
await this.browser.saveScreenshot(outputFile)
|
|
1816
1973
|
}
|
|
1817
1974
|
|
|
1818
|
-
|
|
1819
|
-
const originalWindowSize = await this.browser.getWindowSize();
|
|
1975
|
+
const originalWindowSize = await this.browser.getWindowSize()
|
|
1820
1976
|
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
}
|
|
1977
|
+
// this case running on device, so we could not set the windowSize
|
|
1978
|
+
if (this.browser.isMobile) {
|
|
1979
|
+
this.debug(`Screenshot has been saved to ${outputFile}, size: ${originalWindowSize.width}x${originalWindowSize.height}`)
|
|
1980
|
+
const buffer = await this.browser.saveScreenshot(outputFile)
|
|
1981
|
+
return buffer
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
let { width, height } = await this.browser
|
|
1985
|
+
.execute(function () {
|
|
1986
|
+
return {
|
|
1987
|
+
height: document.body.scrollHeight,
|
|
1988
|
+
width: document.body.scrollWidth,
|
|
1989
|
+
}
|
|
1990
|
+
})
|
|
1991
|
+
.then(res => res)
|
|
1827
1992
|
|
|
1828
|
-
if (height < 100) height = 500
|
|
1829
|
-
/* eslint-enable */
|
|
1993
|
+
if (height < 100) height = 500 // errors for very small height
|
|
1830
1994
|
|
|
1831
|
-
await this.browser.setWindowSize(width, height)
|
|
1832
|
-
this.debug(`Screenshot has been saved to ${outputFile}, size: ${width}x${height}`)
|
|
1833
|
-
const buffer = await this.browser.saveScreenshot(outputFile)
|
|
1834
|
-
await this.browser.setWindowSize(originalWindowSize.width, originalWindowSize.height)
|
|
1835
|
-
return buffer
|
|
1995
|
+
await this.browser.setWindowSize(width, height)
|
|
1996
|
+
this.debug(`Screenshot has been saved to ${outputFile}, size: ${width}x${height}`)
|
|
1997
|
+
const buffer = await this.browser.saveScreenshot(outputFile)
|
|
1998
|
+
await this.browser.setWindowSize(originalWindowSize.width, originalWindowSize.height)
|
|
1999
|
+
return buffer
|
|
1836
2000
|
}
|
|
1837
2001
|
|
|
1838
2002
|
/**
|
|
@@ -1840,40 +2004,40 @@ class WebDriver extends Helper {
|
|
|
1840
2004
|
* {{> setCookie }}
|
|
1841
2005
|
*/
|
|
1842
2006
|
async setCookie(cookie) {
|
|
1843
|
-
return this.browser.setCookies(cookie)
|
|
2007
|
+
return this.browser.setCookies(cookie)
|
|
1844
2008
|
}
|
|
1845
2009
|
|
|
1846
2010
|
/**
|
|
1847
2011
|
* {{> clearCookie }}
|
|
1848
2012
|
*/
|
|
1849
2013
|
async clearCookie(cookie) {
|
|
1850
|
-
return this.browser.deleteCookies(cookie)
|
|
2014
|
+
return this.browser.deleteCookies(cookie)
|
|
1851
2015
|
}
|
|
1852
2016
|
|
|
1853
2017
|
/**
|
|
1854
2018
|
* {{> seeCookie }}
|
|
1855
2019
|
*/
|
|
1856
2020
|
async seeCookie(name) {
|
|
1857
|
-
const cookie = await this.browser.getCookies([name])
|
|
1858
|
-
return truth(`cookie ${name}`, 'to be set').assert(cookie)
|
|
2021
|
+
const cookie = await this.browser.getCookies([name])
|
|
2022
|
+
return truth(`cookie ${name}`, 'to be set').assert(cookie)
|
|
1859
2023
|
}
|
|
1860
2024
|
|
|
1861
2025
|
/**
|
|
1862
2026
|
* {{> dontSeeCookie }}
|
|
1863
2027
|
*/
|
|
1864
2028
|
async dontSeeCookie(name) {
|
|
1865
|
-
const cookie = await this.browser.getCookies([name])
|
|
1866
|
-
return truth(`cookie ${name}`, 'to be set').negate(cookie)
|
|
2029
|
+
const cookie = await this.browser.getCookies([name])
|
|
2030
|
+
return truth(`cookie ${name}`, 'to be set').negate(cookie)
|
|
1867
2031
|
}
|
|
1868
2032
|
|
|
1869
2033
|
/**
|
|
1870
2034
|
* {{> grabCookie }}
|
|
1871
2035
|
*/
|
|
1872
2036
|
async grabCookie(name) {
|
|
1873
|
-
if (!name) return this.browser.getCookies()
|
|
1874
|
-
const cookie = await this.browser.getCookies([name])
|
|
1875
|
-
this.debugSection('Cookie', JSON.stringify(cookie))
|
|
1876
|
-
return cookie[0]
|
|
2037
|
+
if (!name) return this.browser.getCookies()
|
|
2038
|
+
const cookie = await this.browser.getCookies([name])
|
|
2039
|
+
this.debugSection('Cookie', JSON.stringify(cookie))
|
|
2040
|
+
return cookie[0]
|
|
1877
2041
|
}
|
|
1878
2042
|
|
|
1879
2043
|
/**
|
|
@@ -1881,30 +2045,33 @@ class WebDriver extends Helper {
|
|
|
1881
2045
|
*/
|
|
1882
2046
|
async waitForCookie(name, sec) {
|
|
1883
2047
|
// by default, we will retry 3 times
|
|
1884
|
-
let retries = 3
|
|
1885
|
-
const waitTimeout = sec || this.options.waitForTimeoutInSeconds
|
|
2048
|
+
let retries = 3
|
|
2049
|
+
const waitTimeout = sec || this.options.waitForTimeoutInSeconds
|
|
1886
2050
|
|
|
1887
2051
|
if (sec) {
|
|
1888
|
-
retries = sec
|
|
2052
|
+
retries = sec
|
|
1889
2053
|
} else {
|
|
1890
|
-
retries = waitTimeout - 1
|
|
2054
|
+
retries = waitTimeout - 1
|
|
1891
2055
|
}
|
|
1892
2056
|
|
|
1893
|
-
return promiseRetry(
|
|
1894
|
-
|
|
1895
|
-
const
|
|
1896
|
-
|
|
1897
|
-
|
|
2057
|
+
return promiseRetry(
|
|
2058
|
+
async (retry, number) => {
|
|
2059
|
+
const _grabCookie = async name => {
|
|
2060
|
+
const cookie = await this.browser.getCookies([name])
|
|
2061
|
+
if (cookie.length === 0) throw Error(`Cookie ${name} is not found after ${retries}s`)
|
|
2062
|
+
}
|
|
1898
2063
|
|
|
1899
|
-
|
|
1900
|
-
|
|
2064
|
+
this.debugSection('Wait for cookie: ', name)
|
|
2065
|
+
if (number > 1) this.debugSection('Retrying... Attempt #', number)
|
|
1901
2066
|
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
2067
|
+
try {
|
|
2068
|
+
await _grabCookie(name)
|
|
2069
|
+
} catch (e) {
|
|
2070
|
+
retry(e)
|
|
2071
|
+
}
|
|
2072
|
+
},
|
|
2073
|
+
{ retries, maxTimeout: 1000 },
|
|
2074
|
+
)
|
|
1908
2075
|
}
|
|
1909
2076
|
|
|
1910
2077
|
/**
|
|
@@ -1913,11 +2080,10 @@ class WebDriver extends Helper {
|
|
|
1913
2080
|
* libraries](http://jster.net/category/windows-modals-popups).
|
|
1914
2081
|
*/
|
|
1915
2082
|
async acceptPopup() {
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
});
|
|
2083
|
+
const text = await this.browser.getAlertText()
|
|
2084
|
+
if (text) {
|
|
2085
|
+
return await this.browser.acceptAlert()
|
|
2086
|
+
}
|
|
1921
2087
|
}
|
|
1922
2088
|
|
|
1923
2089
|
/**
|
|
@@ -1925,11 +2091,10 @@ class WebDriver extends Helper {
|
|
|
1925
2091
|
*
|
|
1926
2092
|
*/
|
|
1927
2093
|
async cancelPopup() {
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
});
|
|
2094
|
+
const text = await this.browser.getAlertText()
|
|
2095
|
+
if (text) {
|
|
2096
|
+
return await this.browser.dismissAlert()
|
|
2097
|
+
}
|
|
1933
2098
|
}
|
|
1934
2099
|
|
|
1935
2100
|
/**
|
|
@@ -1939,12 +2104,12 @@ class WebDriver extends Helper {
|
|
|
1939
2104
|
* @param {string} text value to check.
|
|
1940
2105
|
*/
|
|
1941
2106
|
async seeInPopup(text) {
|
|
1942
|
-
return this.browser.getAlertText().then(
|
|
2107
|
+
return await this.browser.getAlertText().then(res => {
|
|
1943
2108
|
if (res === null) {
|
|
1944
|
-
throw new Error('Popup is not opened')
|
|
2109
|
+
throw new Error('Popup is not opened')
|
|
1945
2110
|
}
|
|
1946
|
-
stringIncludes('text in popup').assert(text, res)
|
|
1947
|
-
})
|
|
2111
|
+
stringIncludes('text in popup').assert(text, res)
|
|
2112
|
+
})
|
|
1948
2113
|
}
|
|
1949
2114
|
|
|
1950
2115
|
/**
|
|
@@ -1952,9 +2117,9 @@ class WebDriver extends Helper {
|
|
|
1952
2117
|
*/
|
|
1953
2118
|
async grabPopupText() {
|
|
1954
2119
|
try {
|
|
1955
|
-
return await this.browser.getAlertText()
|
|
2120
|
+
return await this.browser.getAlertText()
|
|
1956
2121
|
} catch (err) {
|
|
1957
|
-
this.debugSection('Popup', 'Error getting text from popup')
|
|
2122
|
+
this.debugSection('Popup', 'Error getting text from popup')
|
|
1958
2123
|
}
|
|
1959
2124
|
}
|
|
1960
2125
|
|
|
@@ -1962,36 +2127,44 @@ class WebDriver extends Helper {
|
|
|
1962
2127
|
* {{> pressKeyDown }}
|
|
1963
2128
|
*/
|
|
1964
2129
|
async pressKeyDown(key) {
|
|
1965
|
-
key = getNormalizedKey.call(this, key)
|
|
2130
|
+
key = getNormalizedKey.call(this, key)
|
|
1966
2131
|
if (!this.browser.isW3C) {
|
|
1967
|
-
return this.browser.sendKeys([key])
|
|
2132
|
+
return this.browser.sendKeys([key])
|
|
1968
2133
|
}
|
|
1969
|
-
return this.browser.performActions([
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
2134
|
+
return this.browser.performActions([
|
|
2135
|
+
{
|
|
2136
|
+
type: 'key',
|
|
2137
|
+
id: 'keyboard',
|
|
2138
|
+
actions: [
|
|
2139
|
+
{
|
|
2140
|
+
type: 'keyDown',
|
|
2141
|
+
value: key,
|
|
2142
|
+
},
|
|
2143
|
+
],
|
|
2144
|
+
},
|
|
2145
|
+
])
|
|
1977
2146
|
}
|
|
1978
2147
|
|
|
1979
2148
|
/**
|
|
1980
2149
|
* {{> pressKeyUp }}
|
|
1981
2150
|
*/
|
|
1982
2151
|
async pressKeyUp(key) {
|
|
1983
|
-
key = getNormalizedKey.call(this, key)
|
|
2152
|
+
key = getNormalizedKey.call(this, key)
|
|
1984
2153
|
if (!this.browser.isW3C) {
|
|
1985
|
-
return this.browser.sendKeys([key])
|
|
2154
|
+
return this.browser.sendKeys([key])
|
|
1986
2155
|
}
|
|
1987
|
-
return this.browser.performActions([
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
2156
|
+
return this.browser.performActions([
|
|
2157
|
+
{
|
|
2158
|
+
type: 'key',
|
|
2159
|
+
id: 'keyboard',
|
|
2160
|
+
actions: [
|
|
2161
|
+
{
|
|
2162
|
+
type: 'keyUp',
|
|
2163
|
+
value: key,
|
|
2164
|
+
},
|
|
2165
|
+
],
|
|
2166
|
+
},
|
|
2167
|
+
])
|
|
1995
2168
|
}
|
|
1996
2169
|
|
|
1997
2170
|
/**
|
|
@@ -2000,40 +2173,45 @@ class WebDriver extends Helper {
|
|
|
2000
2173
|
* {{> pressKeyWithKeyNormalization }}
|
|
2001
2174
|
*/
|
|
2002
2175
|
async pressKey(key) {
|
|
2003
|
-
const modifiers = []
|
|
2176
|
+
const modifiers = []
|
|
2004
2177
|
if (Array.isArray(key)) {
|
|
2005
2178
|
for (let k of key) {
|
|
2006
|
-
k = getNormalizedKey.call(this, k)
|
|
2179
|
+
k = getNormalizedKey.call(this, k)
|
|
2007
2180
|
if (isModifierKey(k)) {
|
|
2008
|
-
modifiers.push(k)
|
|
2181
|
+
modifiers.push(k)
|
|
2009
2182
|
} else {
|
|
2010
|
-
key = k
|
|
2011
|
-
break
|
|
2183
|
+
key = k
|
|
2184
|
+
break
|
|
2012
2185
|
}
|
|
2013
2186
|
}
|
|
2014
2187
|
} else {
|
|
2015
|
-
key = getNormalizedKey.call(this, key)
|
|
2188
|
+
key = getNormalizedKey.call(this, key)
|
|
2016
2189
|
}
|
|
2017
2190
|
for (const modifier of modifiers) {
|
|
2018
|
-
await this.pressKeyDown(modifier)
|
|
2191
|
+
await this.pressKeyDown(modifier)
|
|
2019
2192
|
}
|
|
2020
2193
|
if (!this.browser.isW3C) {
|
|
2021
|
-
await this.browser.sendKeys([key])
|
|
2194
|
+
await this.browser.sendKeys([key])
|
|
2022
2195
|
} else {
|
|
2023
|
-
await this.browser.performActions([
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2196
|
+
await this.browser.performActions([
|
|
2197
|
+
{
|
|
2198
|
+
type: 'key',
|
|
2199
|
+
id: 'keyboard',
|
|
2200
|
+
actions: [
|
|
2201
|
+
{
|
|
2202
|
+
type: 'keyDown',
|
|
2203
|
+
value: key,
|
|
2204
|
+
},
|
|
2205
|
+
{
|
|
2206
|
+
type: 'keyUp',
|
|
2207
|
+
value: key,
|
|
2208
|
+
},
|
|
2209
|
+
],
|
|
2210
|
+
},
|
|
2211
|
+
])
|
|
2034
2212
|
}
|
|
2035
2213
|
for (const modifier of modifiers) {
|
|
2036
|
-
await this.pressKeyUp(modifier)
|
|
2214
|
+
await this.pressKeyUp(modifier)
|
|
2037
2215
|
}
|
|
2038
2216
|
}
|
|
2039
2217
|
|
|
@@ -2042,17 +2220,17 @@ class WebDriver extends Helper {
|
|
|
2042
2220
|
*/
|
|
2043
2221
|
async type(keys, delay = null) {
|
|
2044
2222
|
if (!Array.isArray(keys)) {
|
|
2045
|
-
keys = keys.toString()
|
|
2046
|
-
keys = keys.split('')
|
|
2223
|
+
keys = keys.toString()
|
|
2224
|
+
keys = keys.split('')
|
|
2047
2225
|
}
|
|
2048
2226
|
if (delay) {
|
|
2049
2227
|
for (const key of keys) {
|
|
2050
|
-
await this.browser.keys(key)
|
|
2051
|
-
await this.wait(delay / 1000)
|
|
2228
|
+
await this.browser.keys(key)
|
|
2229
|
+
await this.wait(delay / 1000)
|
|
2052
2230
|
}
|
|
2053
|
-
return
|
|
2231
|
+
return
|
|
2054
2232
|
}
|
|
2055
|
-
await this.browser.keys(keys)
|
|
2233
|
+
await this.browser.keys(keys)
|
|
2056
2234
|
}
|
|
2057
2235
|
|
|
2058
2236
|
/**
|
|
@@ -2061,27 +2239,27 @@ class WebDriver extends Helper {
|
|
|
2061
2239
|
* {{> resizeWindow }}
|
|
2062
2240
|
*/
|
|
2063
2241
|
async resizeWindow(width, height) {
|
|
2064
|
-
return this.browser.setWindowSize(width, height)
|
|
2242
|
+
return this.browser.setWindowSize(width, height)
|
|
2065
2243
|
}
|
|
2066
2244
|
|
|
2067
2245
|
async _resizeBrowserWindow(browser, width, height) {
|
|
2068
2246
|
if (width === 'maximize') {
|
|
2069
|
-
const size = await browser.maximizeWindow()
|
|
2070
|
-
this.debugSection('Window Size', size)
|
|
2071
|
-
return
|
|
2247
|
+
const size = await browser.maximizeWindow()
|
|
2248
|
+
this.debugSection('Window Size', size)
|
|
2249
|
+
return
|
|
2072
2250
|
}
|
|
2073
2251
|
if (browser.isW3C) {
|
|
2074
|
-
return browser.setWindowRect(null, null, parseInt(width, 10), parseInt(height, 10))
|
|
2252
|
+
return browser.setWindowRect(null, null, parseInt(width, 10), parseInt(height, 10))
|
|
2075
2253
|
}
|
|
2076
|
-
return browser.setWindowSize(parseInt(width, 10), parseInt(height, 10))
|
|
2254
|
+
return browser.setWindowSize(parseInt(width, 10), parseInt(height, 10))
|
|
2077
2255
|
}
|
|
2078
2256
|
|
|
2079
2257
|
async _resizeWindowIfNeeded(browser, windowSize) {
|
|
2080
2258
|
if (this.isWeb && windowSize === 'maximize') {
|
|
2081
|
-
await this._resizeBrowserWindow(browser, 'maximize')
|
|
2259
|
+
await this._resizeBrowserWindow(browser, 'maximize')
|
|
2082
2260
|
} else if (this.isWeb && windowSize && windowSize.indexOf('x') > 0) {
|
|
2083
|
-
const dimensions = windowSize.split('x')
|
|
2084
|
-
await this._resizeBrowserWindow(browser, dimensions[0], dimensions[1])
|
|
2261
|
+
const dimensions = windowSize.split('x')
|
|
2262
|
+
await this._resizeBrowserWindow(browser, dimensions[0], dimensions[1])
|
|
2085
2263
|
}
|
|
2086
2264
|
}
|
|
2087
2265
|
|
|
@@ -2090,11 +2268,11 @@ class WebDriver extends Helper {
|
|
|
2090
2268
|
*
|
|
2091
2269
|
*/
|
|
2092
2270
|
async focus(locator) {
|
|
2093
|
-
const els = await this._locate(locator)
|
|
2094
|
-
assertElementExists(els, locator, 'Element to focus')
|
|
2095
|
-
const el = usingFirstElement(els)
|
|
2271
|
+
const els = await this._locate(locator)
|
|
2272
|
+
assertElementExists(els, locator, 'Element to focus')
|
|
2273
|
+
const el = usingFirstElement(els)
|
|
2096
2274
|
|
|
2097
|
-
await focusElement(el, this.browser)
|
|
2275
|
+
await focusElement(el, this.browser)
|
|
2098
2276
|
}
|
|
2099
2277
|
|
|
2100
2278
|
/**
|
|
@@ -2102,11 +2280,11 @@ class WebDriver extends Helper {
|
|
|
2102
2280
|
*
|
|
2103
2281
|
*/
|
|
2104
2282
|
async blur(locator) {
|
|
2105
|
-
const els = await this._locate(locator)
|
|
2106
|
-
assertElementExists(els, locator, 'Element to blur')
|
|
2107
|
-
const el = usingFirstElement(els)
|
|
2283
|
+
const els = await this._locate(locator)
|
|
2284
|
+
assertElementExists(els, locator, 'Element to blur')
|
|
2285
|
+
const el = usingFirstElement(els)
|
|
2108
2286
|
|
|
2109
|
-
await blurElement(el, this.browser)
|
|
2287
|
+
await blurElement(el, this.browser)
|
|
2110
2288
|
}
|
|
2111
2289
|
|
|
2112
2290
|
/**
|
|
@@ -2114,64 +2292,73 @@ class WebDriver extends Helper {
|
|
|
2114
2292
|
* {{> dragAndDrop }}
|
|
2115
2293
|
*/
|
|
2116
2294
|
async dragAndDrop(srcElement, destElement) {
|
|
2117
|
-
let sourceEl = await this._locate(srcElement)
|
|
2118
|
-
assertElementExists(sourceEl, srcElement)
|
|
2119
|
-
sourceEl = usingFirstElement(sourceEl)
|
|
2295
|
+
let sourceEl = await this._locate(srcElement)
|
|
2296
|
+
assertElementExists(sourceEl, srcElement)
|
|
2297
|
+
sourceEl = usingFirstElement(sourceEl)
|
|
2120
2298
|
|
|
2121
|
-
let destEl = await this._locate(destElement)
|
|
2122
|
-
assertElementExists(destEl, destElement)
|
|
2123
|
-
destEl = usingFirstElement(destEl)
|
|
2299
|
+
let destEl = await this._locate(destElement)
|
|
2300
|
+
assertElementExists(destEl, destElement)
|
|
2301
|
+
destEl = usingFirstElement(destEl)
|
|
2124
2302
|
|
|
2125
|
-
return sourceEl.dragAndDrop(destEl)
|
|
2303
|
+
return sourceEl.dragAndDrop(destEl)
|
|
2126
2304
|
}
|
|
2127
2305
|
|
|
2128
2306
|
/**
|
|
2129
2307
|
* {{> dragSlider }}
|
|
2130
2308
|
*/
|
|
2131
2309
|
async dragSlider(locator, offsetX = 0) {
|
|
2132
|
-
const browser = this.browser
|
|
2133
|
-
await this.moveCursorTo(locator)
|
|
2310
|
+
const browser = this.browser
|
|
2311
|
+
await this.moveCursorTo(locator)
|
|
2134
2312
|
|
|
2135
2313
|
// for chrome
|
|
2136
2314
|
if (browser.isW3C) {
|
|
2137
|
-
const xOffset = await this.grabElementBoundingRect(locator, 'x')
|
|
2138
|
-
const yOffset = await this.grabElementBoundingRect(locator, 'y')
|
|
2139
|
-
|
|
2140
|
-
return browser.performActions([
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2315
|
+
const xOffset = await this.grabElementBoundingRect(locator, 'x')
|
|
2316
|
+
const yOffset = await this.grabElementBoundingRect(locator, 'y')
|
|
2317
|
+
|
|
2318
|
+
return browser.performActions([
|
|
2319
|
+
{
|
|
2320
|
+
type: 'pointer',
|
|
2321
|
+
id: 'pointer1',
|
|
2322
|
+
parameters: { pointerType: 'mouse' },
|
|
2323
|
+
actions: [
|
|
2324
|
+
{
|
|
2325
|
+
type: 'pointerMove',
|
|
2326
|
+
origin: 'pointer',
|
|
2327
|
+
duration: 1000,
|
|
2328
|
+
x: xOffset,
|
|
2329
|
+
y: yOffset,
|
|
2330
|
+
},
|
|
2331
|
+
{ type: 'pointerDown', button: 0 },
|
|
2332
|
+
{
|
|
2333
|
+
type: 'pointerMove',
|
|
2334
|
+
origin: 'pointer',
|
|
2335
|
+
duration: 1000,
|
|
2336
|
+
x: offsetX,
|
|
2337
|
+
y: 0,
|
|
2338
|
+
},
|
|
2339
|
+
{ type: 'pointerUp', button: 0 },
|
|
2340
|
+
],
|
|
2341
|
+
},
|
|
2342
|
+
])
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
await browser.buttonDown(0)
|
|
2346
|
+
await browser.moveToElement(null, offsetX, 0)
|
|
2347
|
+
await browser.buttonUp(0)
|
|
2161
2348
|
}
|
|
2162
2349
|
|
|
2163
2350
|
/**
|
|
2164
2351
|
* {{> grabAllWindowHandles }}
|
|
2165
2352
|
*/
|
|
2166
2353
|
async grabAllWindowHandles() {
|
|
2167
|
-
return this.browser.getWindowHandles()
|
|
2354
|
+
return this.browser.getWindowHandles()
|
|
2168
2355
|
}
|
|
2169
2356
|
|
|
2170
2357
|
/**
|
|
2171
2358
|
* {{> grabCurrentWindowHandle }}
|
|
2172
2359
|
*/
|
|
2173
2360
|
async grabCurrentWindowHandle() {
|
|
2174
|
-
return this.browser.getWindowHandle()
|
|
2361
|
+
return this.browser.getWindowHandle()
|
|
2175
2362
|
}
|
|
2176
2363
|
|
|
2177
2364
|
/**
|
|
@@ -2189,22 +2376,22 @@ class WebDriver extends Helper {
|
|
|
2189
2376
|
* @param {string} window name of window handle.
|
|
2190
2377
|
*/
|
|
2191
2378
|
async switchToWindow(window) {
|
|
2192
|
-
await this.browser.switchToWindow(window)
|
|
2379
|
+
await this.browser.switchToWindow(window)
|
|
2193
2380
|
}
|
|
2194
2381
|
|
|
2195
2382
|
/**
|
|
2196
2383
|
* {{> closeOtherTabs }}
|
|
2197
2384
|
*/
|
|
2198
2385
|
async closeOtherTabs() {
|
|
2199
|
-
const handles = await this.browser.getWindowHandles()
|
|
2200
|
-
const currentHandle = await this.browser.getWindowHandle()
|
|
2201
|
-
const otherHandles = handles.filter(handle => handle !== currentHandle)
|
|
2386
|
+
const handles = await this.browser.getWindowHandles()
|
|
2387
|
+
const currentHandle = await this.browser.getWindowHandle()
|
|
2388
|
+
const otherHandles = handles.filter(handle => handle !== currentHandle)
|
|
2202
2389
|
|
|
2203
|
-
await forEachAsync(otherHandles, async
|
|
2204
|
-
await this.browser.switchToWindow(handle)
|
|
2205
|
-
await this.browser.closeWindow()
|
|
2206
|
-
})
|
|
2207
|
-
await this.browser.switchToWindow(currentHandle)
|
|
2390
|
+
await forEachAsync(otherHandles, async handle => {
|
|
2391
|
+
await this.browser.switchToWindow(handle)
|
|
2392
|
+
await this.browser.closeWindow()
|
|
2393
|
+
})
|
|
2394
|
+
await this.browser.switchToWindow(currentHandle)
|
|
2208
2395
|
}
|
|
2209
2396
|
|
|
2210
2397
|
/**
|
|
@@ -2212,102 +2399,123 @@ class WebDriver extends Helper {
|
|
|
2212
2399
|
*/
|
|
2213
2400
|
async wait(sec) {
|
|
2214
2401
|
return new Promise(resolve => {
|
|
2215
|
-
setTimeout(resolve, sec * 1000)
|
|
2216
|
-
})
|
|
2402
|
+
setTimeout(resolve, sec * 1000)
|
|
2403
|
+
})
|
|
2217
2404
|
}
|
|
2218
2405
|
|
|
2219
2406
|
/**
|
|
2220
2407
|
* {{> waitForEnabled }}
|
|
2221
2408
|
*/
|
|
2222
2409
|
async waitForEnabled(locator, sec = null) {
|
|
2223
|
-
const aSec = sec || this.options.waitForTimeoutInSeconds
|
|
2410
|
+
const aSec = sec || this.options.waitForTimeoutInSeconds
|
|
2224
2411
|
|
|
2225
|
-
return this.browser.waitUntil(
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2412
|
+
return this.browser.waitUntil(
|
|
2413
|
+
async () => {
|
|
2414
|
+
const res = await this._res(locator)
|
|
2415
|
+
if (!res || res.length === 0) {
|
|
2416
|
+
return false
|
|
2417
|
+
}
|
|
2418
|
+
const selected = await forEachAsync(res, async el => this.browser.isElementEnabled(getElementId(el)))
|
|
2419
|
+
if (Array.isArray(selected)) {
|
|
2420
|
+
return selected.filter(val => val === true).length > 0
|
|
2421
|
+
}
|
|
2422
|
+
return selected
|
|
2423
|
+
},
|
|
2424
|
+
{
|
|
2425
|
+
timeout: aSec * 1000,
|
|
2426
|
+
timeoutMsg: `element (${new Locator(locator)}) still not enabled after ${aSec} sec`,
|
|
2427
|
+
},
|
|
2428
|
+
)
|
|
2239
2429
|
}
|
|
2240
2430
|
|
|
2241
2431
|
/**
|
|
2242
2432
|
* {{> waitForElement }}
|
|
2243
2433
|
*/
|
|
2244
2434
|
async waitForElement(locator, sec = null) {
|
|
2245
|
-
const aSec = sec || this.options.waitForTimeoutInSeconds
|
|
2435
|
+
const aSec = sec || this.options.waitForTimeoutInSeconds
|
|
2246
2436
|
|
|
2247
|
-
return this.browser.waitUntil(
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2437
|
+
return this.browser.waitUntil(
|
|
2438
|
+
async () => {
|
|
2439
|
+
const res = await this._res(locator)
|
|
2440
|
+
return res && res.length
|
|
2441
|
+
},
|
|
2442
|
+
{
|
|
2443
|
+
timeout: aSec * 1000,
|
|
2444
|
+
timeoutMsg: `element (${new Locator(locator)}) still not present on page after ${aSec} sec`,
|
|
2445
|
+
},
|
|
2446
|
+
)
|
|
2251
2447
|
}
|
|
2252
2448
|
|
|
2253
2449
|
/**
|
|
2254
2450
|
* {{> waitForClickable }}
|
|
2255
2451
|
*/
|
|
2256
2452
|
async waitForClickable(locator, waitTimeout) {
|
|
2257
|
-
waitTimeout = waitTimeout || this.options.waitForTimeoutInSeconds
|
|
2258
|
-
let res = await this._locate(locator)
|
|
2259
|
-
res = usingFirstElement(res)
|
|
2260
|
-
assertElementExists(res, locator)
|
|
2453
|
+
waitTimeout = waitTimeout || this.options.waitForTimeoutInSeconds
|
|
2454
|
+
let res = await this._locate(locator)
|
|
2455
|
+
res = usingFirstElement(res)
|
|
2456
|
+
assertElementExists(res, locator)
|
|
2261
2457
|
|
|
2262
|
-
return res
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2458
|
+
return res
|
|
2459
|
+
.waitForClickable({
|
|
2460
|
+
timeout: waitTimeout * 1000,
|
|
2461
|
+
timeoutMsg: `element ${res.selector} still not clickable after ${waitTimeout} sec`,
|
|
2462
|
+
})
|
|
2463
|
+
.catch(e => {
|
|
2464
|
+
throw wrapError(e)
|
|
2465
|
+
})
|
|
2266
2466
|
}
|
|
2267
2467
|
|
|
2268
2468
|
/**
|
|
2269
2469
|
* {{> waitInUrl }}
|
|
2270
2470
|
*/
|
|
2271
2471
|
async waitInUrl(urlPart, sec = null) {
|
|
2272
|
-
const client = this.browser
|
|
2273
|
-
const aSec = sec || this.options.waitForTimeoutInSeconds
|
|
2274
|
-
let currUrl = ''
|
|
2472
|
+
const client = this.browser
|
|
2473
|
+
const aSec = sec || this.options.waitForTimeoutInSeconds
|
|
2474
|
+
let currUrl = ''
|
|
2275
2475
|
|
|
2276
2476
|
return client
|
|
2277
|
-
.waitUntil(
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2477
|
+
.waitUntil(
|
|
2478
|
+
function () {
|
|
2479
|
+
return this.getUrl().then(res => {
|
|
2480
|
+
currUrl = decodeUrl(res)
|
|
2481
|
+
return currUrl.indexOf(urlPart) > -1
|
|
2482
|
+
})
|
|
2483
|
+
},
|
|
2484
|
+
{ timeout: aSec * 1000 },
|
|
2485
|
+
)
|
|
2486
|
+
.catch(e => {
|
|
2487
|
+
e = wrapError(e)
|
|
2283
2488
|
if (e.message.indexOf('timeout')) {
|
|
2284
|
-
throw new Error(`expected url to include ${urlPart}, but found ${currUrl}`)
|
|
2489
|
+
throw new Error(`expected url to include ${urlPart}, but found ${currUrl}`)
|
|
2285
2490
|
}
|
|
2286
|
-
throw e
|
|
2287
|
-
})
|
|
2491
|
+
throw e
|
|
2492
|
+
})
|
|
2288
2493
|
}
|
|
2289
2494
|
|
|
2290
2495
|
/**
|
|
2291
2496
|
* {{> waitUrlEquals }}
|
|
2292
2497
|
*/
|
|
2293
2498
|
async waitUrlEquals(urlPart, sec = null) {
|
|
2294
|
-
const aSec = sec || this.options.waitForTimeoutInSeconds
|
|
2295
|
-
const baseUrl = this.options.url
|
|
2499
|
+
const aSec = sec || this.options.waitForTimeoutInSeconds
|
|
2500
|
+
const baseUrl = this.options.url
|
|
2296
2501
|
if (urlPart.indexOf('http') < 0) {
|
|
2297
|
-
urlPart = baseUrl + urlPart
|
|
2298
|
-
}
|
|
2299
|
-
let currUrl = ''
|
|
2300
|
-
return this.browser
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2502
|
+
urlPart = baseUrl + urlPart
|
|
2503
|
+
}
|
|
2504
|
+
let currUrl = ''
|
|
2505
|
+
return this.browser
|
|
2506
|
+
.waitUntil(function () {
|
|
2507
|
+
return this.getUrl().then(res => {
|
|
2508
|
+
currUrl = decodeUrl(res)
|
|
2509
|
+
return currUrl === urlPart
|
|
2510
|
+
})
|
|
2511
|
+
}, aSec * 1000)
|
|
2512
|
+
.catch(e => {
|
|
2513
|
+
e = wrapError(e)
|
|
2514
|
+
if (e.message.indexOf('timeout')) {
|
|
2515
|
+
throw new Error(`expected url to be ${urlPart}, but found ${currUrl}`)
|
|
2516
|
+
}
|
|
2517
|
+
throw e
|
|
2518
|
+
})
|
|
2311
2519
|
}
|
|
2312
2520
|
|
|
2313
2521
|
/**
|
|
@@ -2315,42 +2523,48 @@ class WebDriver extends Helper {
|
|
|
2315
2523
|
*
|
|
2316
2524
|
*/
|
|
2317
2525
|
async waitForText(text, sec = null, context = null) {
|
|
2318
|
-
const aSec = sec || this.options.waitForTimeoutInSeconds
|
|
2319
|
-
const _context = context || this.root
|
|
2320
|
-
|
|
2321
|
-
return this.browser.waitUntil(
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2526
|
+
const aSec = sec || this.options.waitForTimeoutInSeconds
|
|
2527
|
+
const _context = context || this.root
|
|
2528
|
+
|
|
2529
|
+
return this.browser.waitUntil(
|
|
2530
|
+
async () => {
|
|
2531
|
+
const res = await this.$$(withStrictLocator.call(this, _context))
|
|
2532
|
+
if (!res || res.length === 0) return false
|
|
2533
|
+
const selected = await forEachAsync(res, async el => this.browser.getElementText(getElementId(el)))
|
|
2534
|
+
if (Array.isArray(selected)) {
|
|
2535
|
+
return selected.filter(part => part.indexOf(text) >= 0).length > 0
|
|
2536
|
+
}
|
|
2537
|
+
return selected.indexOf(text) >= 0
|
|
2538
|
+
},
|
|
2539
|
+
{
|
|
2540
|
+
timeout: aSec * 1000,
|
|
2541
|
+
timeoutMsg: `element (${_context}) is not in DOM or there is no element(${_context}) with text "${text}" after ${aSec} sec`,
|
|
2542
|
+
},
|
|
2543
|
+
)
|
|
2333
2544
|
}
|
|
2334
2545
|
|
|
2335
2546
|
/**
|
|
2336
2547
|
* {{> waitForValue }}
|
|
2337
2548
|
*/
|
|
2338
2549
|
async waitForValue(field, value, sec = null) {
|
|
2339
|
-
const client = this.browser
|
|
2340
|
-
const aSec = sec || this.options.waitForTimeoutInSeconds
|
|
2341
|
-
|
|
2342
|
-
return client.waitUntil(
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2550
|
+
const client = this.browser
|
|
2551
|
+
const aSec = sec || this.options.waitForTimeoutInSeconds
|
|
2552
|
+
|
|
2553
|
+
return client.waitUntil(
|
|
2554
|
+
async () => {
|
|
2555
|
+
const res = await findFields.call(this, field)
|
|
2556
|
+
if (!res || res.length === 0) return false
|
|
2557
|
+
const selected = await forEachAsync(res, async el => el.getValue())
|
|
2558
|
+
if (Array.isArray(selected)) {
|
|
2559
|
+
return selected.filter(part => part.indexOf(value) >= 0).length > 0
|
|
2560
|
+
}
|
|
2561
|
+
return selected.indexOf(value) >= 0
|
|
2562
|
+
},
|
|
2563
|
+
{
|
|
2564
|
+
timeout: aSec * 1000,
|
|
2565
|
+
timeoutMsg: `element (${field}) is not in DOM or there is no element(${field}) with value "${value}" after ${aSec} sec`,
|
|
2566
|
+
},
|
|
2567
|
+
)
|
|
2354
2568
|
}
|
|
2355
2569
|
|
|
2356
2570
|
/**
|
|
@@ -2358,304 +2572,281 @@ class WebDriver extends Helper {
|
|
|
2358
2572
|
*
|
|
2359
2573
|
*/
|
|
2360
2574
|
async waitForVisible(locator, sec = null) {
|
|
2361
|
-
const aSec = sec || this.options.waitForTimeoutInSeconds
|
|
2362
|
-
|
|
2363
|
-
return this.browser.waitUntil(
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2575
|
+
const aSec = sec || this.options.waitForTimeoutInSeconds
|
|
2576
|
+
|
|
2577
|
+
return this.browser.waitUntil(
|
|
2578
|
+
async () => {
|
|
2579
|
+
const res = await this._res(locator)
|
|
2580
|
+
if (!res || res.length === 0) return false
|
|
2581
|
+
const selected = await forEachAsync(res, async el => el.isDisplayed())
|
|
2582
|
+
if (Array.isArray(selected)) {
|
|
2583
|
+
return selected.filter(val => val === true).length > 0
|
|
2584
|
+
}
|
|
2585
|
+
return selected
|
|
2586
|
+
},
|
|
2587
|
+
{
|
|
2588
|
+
timeout: aSec * 1000,
|
|
2589
|
+
timeoutMsg: `element (${new Locator(locator)}) still not visible after ${aSec} sec`,
|
|
2590
|
+
},
|
|
2591
|
+
)
|
|
2372
2592
|
}
|
|
2373
2593
|
|
|
2374
2594
|
/**
|
|
2375
2595
|
* {{> waitNumberOfVisibleElements }}
|
|
2376
2596
|
*/
|
|
2377
2597
|
async waitNumberOfVisibleElements(locator, num, sec = null) {
|
|
2378
|
-
const aSec = sec || this.options.waitForTimeoutInSeconds
|
|
2379
|
-
|
|
2380
|
-
return this.browser
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2598
|
+
const aSec = sec || this.options.waitForTimeoutInSeconds
|
|
2599
|
+
|
|
2600
|
+
return this.browser
|
|
2601
|
+
.waitUntil(
|
|
2602
|
+
async () => {
|
|
2603
|
+
const res = await this._res(locator)
|
|
2604
|
+
if (!res || res.length === 0) return false
|
|
2605
|
+
let selected = await forEachAsync(res, async el => el.isDisplayed())
|
|
2606
|
+
|
|
2607
|
+
if (!Array.isArray(selected)) selected = [selected]
|
|
2608
|
+
selected = selected.filter(val => val === true)
|
|
2609
|
+
return selected.length === num
|
|
2610
|
+
},
|
|
2611
|
+
{
|
|
2612
|
+
timeout: aSec * 1000,
|
|
2613
|
+
timeoutMsg: `The number of elements (${new Locator(locator)}) is not ${num} after ${aSec} sec`,
|
|
2614
|
+
},
|
|
2615
|
+
)
|
|
2616
|
+
.catch(e => {
|
|
2617
|
+
throw wrapError(e)
|
|
2618
|
+
})
|
|
2389
2619
|
}
|
|
2390
2620
|
|
|
2391
2621
|
/**
|
|
2392
2622
|
* {{> waitForInvisible }}
|
|
2393
2623
|
*/
|
|
2394
2624
|
async waitForInvisible(locator, sec = null) {
|
|
2395
|
-
const aSec = sec || this.options.waitForTimeoutInSeconds
|
|
2396
|
-
|
|
2397
|
-
return this.browser.waitUntil(
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2625
|
+
const aSec = sec || this.options.waitForTimeoutInSeconds
|
|
2626
|
+
|
|
2627
|
+
return this.browser.waitUntil(
|
|
2628
|
+
async () => {
|
|
2629
|
+
const res = await this._res(locator)
|
|
2630
|
+
if (!res || res.length === 0) return true
|
|
2631
|
+
const selected = await forEachAsync(res, async el => el.isDisplayed())
|
|
2632
|
+
return !selected.length
|
|
2633
|
+
},
|
|
2634
|
+
{ timeout: aSec * 1000, timeoutMsg: `element (${new Locator(locator)}) still visible after ${aSec} sec` },
|
|
2635
|
+
)
|
|
2403
2636
|
}
|
|
2404
2637
|
|
|
2405
2638
|
/**
|
|
2406
2639
|
* {{> waitToHide }}
|
|
2407
2640
|
*/
|
|
2408
2641
|
async waitToHide(locator, sec = null) {
|
|
2409
|
-
return this.waitForInvisible(locator, sec)
|
|
2642
|
+
return this.waitForInvisible(locator, sec)
|
|
2410
2643
|
}
|
|
2411
2644
|
|
|
2412
2645
|
/**
|
|
2413
2646
|
* {{> waitForDetached }}
|
|
2414
2647
|
*/
|
|
2415
2648
|
async waitForDetached(locator, sec = null) {
|
|
2416
|
-
const aSec = sec || this.options.waitForTimeoutInSeconds
|
|
2649
|
+
const aSec = sec || this.options.waitForTimeoutInSeconds
|
|
2417
2650
|
|
|
2418
|
-
return this.browser.waitUntil(
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2651
|
+
return this.browser.waitUntil(
|
|
2652
|
+
async () => {
|
|
2653
|
+
const res = await this._res(locator)
|
|
2654
|
+
if (!res || res.length === 0) {
|
|
2655
|
+
return true
|
|
2656
|
+
}
|
|
2657
|
+
return false
|
|
2658
|
+
},
|
|
2659
|
+
{ timeout: aSec * 1000, timeoutMsg: `element (${new Locator(locator)}) still on page after ${aSec} sec` },
|
|
2660
|
+
)
|
|
2425
2661
|
}
|
|
2426
2662
|
|
|
2427
2663
|
/**
|
|
2428
2664
|
* {{> waitForFunction }}
|
|
2429
2665
|
*/
|
|
2430
2666
|
async waitForFunction(fn, argsOrSec = null, sec = null) {
|
|
2431
|
-
let args = []
|
|
2667
|
+
let args = []
|
|
2432
2668
|
if (argsOrSec) {
|
|
2433
2669
|
if (Array.isArray(argsOrSec)) {
|
|
2434
|
-
args = argsOrSec
|
|
2670
|
+
args = argsOrSec
|
|
2435
2671
|
} else if (typeof argsOrSec === 'number') {
|
|
2436
|
-
sec = argsOrSec
|
|
2672
|
+
sec = argsOrSec
|
|
2437
2673
|
}
|
|
2438
2674
|
}
|
|
2439
2675
|
|
|
2440
|
-
const aSec = sec || this.options.waitForTimeoutInSeconds
|
|
2676
|
+
const aSec = sec || this.options.waitForTimeoutInSeconds
|
|
2441
2677
|
|
|
2442
|
-
return this.browser.waitUntil(async () => this.browser.execute(fn, ...args), {
|
|
2678
|
+
return this.browser.waitUntil(async () => this.browser.execute(fn, ...args), {
|
|
2679
|
+
timeout: aSec * 1000,
|
|
2680
|
+
timeoutMsg: '',
|
|
2681
|
+
})
|
|
2443
2682
|
}
|
|
2444
2683
|
|
|
2445
2684
|
/**
|
|
2446
2685
|
* {{> waitForNumberOfTabs }}
|
|
2447
2686
|
*/
|
|
2448
2687
|
async waitForNumberOfTabs(expectedTabs, sec) {
|
|
2449
|
-
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeoutInSeconds
|
|
2450
|
-
let currentTabs
|
|
2451
|
-
let count = 0
|
|
2688
|
+
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeoutInSeconds
|
|
2689
|
+
let currentTabs
|
|
2690
|
+
let count = 0
|
|
2452
2691
|
|
|
2453
2692
|
do {
|
|
2454
|
-
currentTabs = await this.grabNumberOfOpenTabs()
|
|
2455
|
-
await this.wait(1)
|
|
2456
|
-
count += 1000
|
|
2457
|
-
if (currentTabs >= expectedTabs) return
|
|
2458
|
-
} while (count <= waitTimeout)
|
|
2693
|
+
currentTabs = await this.grabNumberOfOpenTabs()
|
|
2694
|
+
await this.wait(1)
|
|
2695
|
+
count += 1000
|
|
2696
|
+
if (currentTabs >= expectedTabs) return
|
|
2697
|
+
} while (count <= waitTimeout)
|
|
2459
2698
|
|
|
2460
|
-
throw new Error(`Expected ${expectedTabs} tabs are not met after ${waitTimeout / 1000} sec.`)
|
|
2699
|
+
throw new Error(`Expected ${expectedTabs} tabs are not met after ${waitTimeout / 1000} sec.`)
|
|
2461
2700
|
}
|
|
2462
2701
|
|
|
2463
2702
|
/**
|
|
2464
2703
|
* {{> switchTo }}
|
|
2465
2704
|
*/
|
|
2466
2705
|
async switchTo(locator) {
|
|
2467
|
-
this.browser.isInsideFrame = true
|
|
2468
|
-
if (Number.isInteger(locator)) {
|
|
2469
|
-
if (this.options.automationProtocol) {
|
|
2470
|
-
return this.browser.switchToFrame(locator + 1);
|
|
2471
|
-
}
|
|
2472
|
-
return this.browser.switchToFrame(locator);
|
|
2473
|
-
}
|
|
2706
|
+
this.browser.isInsideFrame = true
|
|
2474
2707
|
if (!locator) {
|
|
2475
|
-
return this.browser.
|
|
2708
|
+
return this.browser.switchFrame(null)
|
|
2476
2709
|
}
|
|
2477
2710
|
|
|
2478
|
-
let res = await this._locate(locator, true)
|
|
2479
|
-
assertElementExists(res, locator)
|
|
2480
|
-
res = usingFirstElement(res)
|
|
2481
|
-
return this.browser.
|
|
2711
|
+
let res = await this._locate(locator, true)
|
|
2712
|
+
assertElementExists(res, locator)
|
|
2713
|
+
res = usingFirstElement(res)
|
|
2714
|
+
return this.browser.switchFrame(res)
|
|
2482
2715
|
}
|
|
2483
2716
|
|
|
2484
2717
|
/**
|
|
2485
2718
|
* {{> switchToNextTab }}
|
|
2486
2719
|
*/
|
|
2487
2720
|
async switchToNextTab(num = 1, sec = null) {
|
|
2488
|
-
const aSec = sec || this.options.waitForTimeoutInSeconds
|
|
2489
|
-
let target
|
|
2490
|
-
const current = await this.browser.getWindowHandle()
|
|
2491
|
-
|
|
2492
|
-
await this.browser.waitUntil(
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2721
|
+
const aSec = sec || this.options.waitForTimeoutInSeconds
|
|
2722
|
+
let target
|
|
2723
|
+
const current = await this.browser.getWindowHandle()
|
|
2724
|
+
|
|
2725
|
+
await this.browser.waitUntil(
|
|
2726
|
+
async () => {
|
|
2727
|
+
await this.browser.getWindowHandles().then(handles => {
|
|
2728
|
+
if (handles.indexOf(current) + num + 1 <= handles.length) {
|
|
2729
|
+
target = handles[handles.indexOf(current) + num]
|
|
2730
|
+
}
|
|
2731
|
+
})
|
|
2732
|
+
return target
|
|
2733
|
+
},
|
|
2734
|
+
{ timeout: aSec * 1000, timeoutMsg: `There is no ability to switch to next tab with offset ${num}` },
|
|
2735
|
+
)
|
|
2736
|
+
return this.browser.switchToWindow(target)
|
|
2501
2737
|
}
|
|
2502
2738
|
|
|
2503
2739
|
/**
|
|
2504
2740
|
* {{> switchToPreviousTab }}
|
|
2505
2741
|
*/
|
|
2506
2742
|
async switchToPreviousTab(num = 1, sec = null) {
|
|
2507
|
-
const aSec = sec || this.options.waitForTimeoutInSeconds
|
|
2508
|
-
const current = await this.browser.getWindowHandle()
|
|
2509
|
-
let target
|
|
2510
|
-
|
|
2511
|
-
await this.browser.waitUntil(
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2743
|
+
const aSec = sec || this.options.waitForTimeoutInSeconds
|
|
2744
|
+
const current = await this.browser.getWindowHandle()
|
|
2745
|
+
let target
|
|
2746
|
+
|
|
2747
|
+
await this.browser.waitUntil(
|
|
2748
|
+
async () => {
|
|
2749
|
+
await this.browser.getWindowHandles().then(handles => {
|
|
2750
|
+
if (handles.indexOf(current) - num > -1) {
|
|
2751
|
+
target = handles[handles.indexOf(current) - num]
|
|
2752
|
+
}
|
|
2753
|
+
})
|
|
2754
|
+
return target
|
|
2755
|
+
},
|
|
2756
|
+
{ timeout: aSec * 1000, timeoutMsg: `There is no ability to switch to previous tab with offset ${num}` },
|
|
2757
|
+
)
|
|
2758
|
+
return this.browser.switchToWindow(target)
|
|
2520
2759
|
}
|
|
2521
2760
|
|
|
2522
2761
|
/**
|
|
2523
2762
|
* {{> closeCurrentTab }}
|
|
2524
2763
|
*/
|
|
2525
2764
|
async closeCurrentTab() {
|
|
2526
|
-
await this.browser.closeWindow()
|
|
2527
|
-
const handles = await this.browser.getWindowHandles()
|
|
2528
|
-
if (handles[0]) await this.browser.switchToWindow(handles[0])
|
|
2765
|
+
await this.browser.closeWindow()
|
|
2766
|
+
const handles = await this.browser.getWindowHandles()
|
|
2767
|
+
if (handles[0]) await this.browser.switchToWindow(handles[0])
|
|
2529
2768
|
}
|
|
2530
2769
|
|
|
2531
2770
|
/**
|
|
2532
2771
|
* {{> openNewTab }}
|
|
2533
2772
|
*/
|
|
2534
2773
|
async openNewTab(url = 'about:blank', windowName = null) {
|
|
2535
|
-
const client = this.browser
|
|
2536
|
-
const crypto = require('crypto');
|
|
2774
|
+
const client = this.browser
|
|
2537
2775
|
if (windowName == null) {
|
|
2538
|
-
windowName = crypto.randomBytes(32).toString('hex')
|
|
2776
|
+
windowName = crypto.randomBytes(32).toString('hex')
|
|
2539
2777
|
}
|
|
2540
|
-
return client.newWindow(url, windowName)
|
|
2778
|
+
return client.newWindow(url, windowName)
|
|
2541
2779
|
}
|
|
2542
2780
|
|
|
2543
2781
|
/**
|
|
2544
2782
|
* {{> grabNumberOfOpenTabs }}
|
|
2545
2783
|
*/
|
|
2546
2784
|
async grabNumberOfOpenTabs() {
|
|
2547
|
-
const pages = await this.browser.getWindowHandles()
|
|
2548
|
-
this.debugSection('Tabs', `Total ${pages.length}`)
|
|
2549
|
-
return pages.length
|
|
2785
|
+
const pages = await this.browser.getWindowHandles()
|
|
2786
|
+
this.debugSection('Tabs', `Total ${pages.length}`)
|
|
2787
|
+
return pages.length
|
|
2550
2788
|
}
|
|
2551
2789
|
|
|
2552
2790
|
/**
|
|
2553
2791
|
* {{> refreshPage }}
|
|
2554
2792
|
*/
|
|
2555
2793
|
async refreshPage() {
|
|
2556
|
-
const client = this.browser
|
|
2557
|
-
return client.refresh()
|
|
2794
|
+
const client = this.browser
|
|
2795
|
+
return client.refresh()
|
|
2558
2796
|
}
|
|
2559
2797
|
|
|
2560
2798
|
/**
|
|
2561
2799
|
* {{> scrollPageToTop }}
|
|
2562
2800
|
*/
|
|
2563
2801
|
scrollPageToTop() {
|
|
2564
|
-
const client = this.browser
|
|
2565
|
-
|
|
2802
|
+
const client = this.browser
|
|
2803
|
+
|
|
2566
2804
|
return client.execute(function () {
|
|
2567
|
-
window.scrollTo(0, 0)
|
|
2568
|
-
})
|
|
2569
|
-
/* eslint-enable */
|
|
2805
|
+
window.scrollTo(0, 0)
|
|
2806
|
+
})
|
|
2570
2807
|
}
|
|
2571
2808
|
|
|
2572
2809
|
/**
|
|
2573
2810
|
* {{> scrollPageToBottom }}
|
|
2574
2811
|
*/
|
|
2575
2812
|
scrollPageToBottom() {
|
|
2576
|
-
const client = this.browser
|
|
2577
|
-
|
|
2813
|
+
const client = this.browser
|
|
2814
|
+
|
|
2578
2815
|
return client.execute(function () {
|
|
2579
|
-
const body = document.body
|
|
2580
|
-
const html = document.documentElement
|
|
2581
|
-
window.scrollTo(0, Math.max(
|
|
2582
|
-
|
|
2583
|
-
body.offsetHeight,
|
|
2584
|
-
html.clientHeight,
|
|
2585
|
-
html.scrollHeight,
|
|
2586
|
-
html.offsetHeight
|
|
2587
|
-
));
|
|
2588
|
-
});
|
|
2589
|
-
/* eslint-enable */
|
|
2816
|
+
const body = document.body
|
|
2817
|
+
const html = document.documentElement
|
|
2818
|
+
window.scrollTo(0, Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight))
|
|
2819
|
+
})
|
|
2590
2820
|
}
|
|
2591
2821
|
|
|
2592
2822
|
/**
|
|
2593
2823
|
* {{> grabPageScrollPosition }}
|
|
2594
2824
|
*/
|
|
2595
2825
|
async grabPageScrollPosition() {
|
|
2596
|
-
/* eslint-disable comma-dangle */
|
|
2597
2826
|
function getScrollPosition() {
|
|
2598
2827
|
return {
|
|
2599
2828
|
x: window.pageXOffset,
|
|
2600
|
-
y: window.pageYOffset
|
|
2601
|
-
}
|
|
2602
|
-
}
|
|
2603
|
-
/* eslint-enable comma-dangle */
|
|
2604
|
-
return this.executeScript(getScrollPosition);
|
|
2605
|
-
}
|
|
2606
|
-
|
|
2607
|
-
/**
|
|
2608
|
-
* This method is **deprecated**.
|
|
2609
|
-
*
|
|
2610
|
-
*
|
|
2611
|
-
* {{> setGeoLocation }}
|
|
2612
|
-
*/
|
|
2613
|
-
async setGeoLocation(latitude, longitude) {
|
|
2614
|
-
if (!this.options.automationProtocol) {
|
|
2615
|
-
console.log(`setGeoLocation deprecated:
|
|
2616
|
-
* This command is deprecated due to using deprecated JSON Wire Protocol command. More info: https://webdriver.io/docs/api/jsonwp/#setgeolocation
|
|
2617
|
-
* Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration`);
|
|
2618
|
-
return;
|
|
2829
|
+
y: window.pageYOffset,
|
|
2830
|
+
}
|
|
2619
2831
|
}
|
|
2620
|
-
this.geoLocation = { latitude, longitude };
|
|
2621
2832
|
|
|
2622
|
-
|
|
2623
|
-
const pages = await this.puppeteerBrowser.pages();
|
|
2624
|
-
await pages[0].setGeolocation({ latitude, longitude });
|
|
2625
|
-
});
|
|
2626
|
-
}
|
|
2627
|
-
|
|
2628
|
-
/**
|
|
2629
|
-
* This method is **deprecated**.
|
|
2630
|
-
*
|
|
2631
|
-
* {{> grabGeoLocation }}
|
|
2632
|
-
*
|
|
2633
|
-
*/
|
|
2634
|
-
async grabGeoLocation() {
|
|
2635
|
-
if (!this.options.automationProtocol) {
|
|
2636
|
-
console.log(`grabGeoLocation deprecated:
|
|
2637
|
-
* This command is deprecated due to using deprecated JSON Wire Protocol command. More info: https://webdriver.io/docs/api/jsonwp/#getgeolocation
|
|
2638
|
-
* Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration`);
|
|
2639
|
-
return;
|
|
2640
|
-
}
|
|
2641
|
-
if (!this.geoLocation) return 'No GeoLocation is set!';
|
|
2642
|
-
return this.geoLocation;
|
|
2833
|
+
return this.executeScript(getScrollPosition)
|
|
2643
2834
|
}
|
|
2644
2835
|
|
|
2645
2836
|
/**
|
|
2646
2837
|
* {{> grabElementBoundingRect }}
|
|
2647
2838
|
*/
|
|
2648
2839
|
async grabElementBoundingRect(locator, prop) {
|
|
2649
|
-
const res = await this._locate(locator, true)
|
|
2650
|
-
assertElementExists(res, locator)
|
|
2651
|
-
const el = usingFirstElement(res)
|
|
2840
|
+
const res = await this._locate(locator, true)
|
|
2841
|
+
assertElementExists(res, locator)
|
|
2842
|
+
const el = usingFirstElement(res)
|
|
2652
2843
|
|
|
2653
2844
|
const rect = {
|
|
2654
2845
|
...(await el.getLocation()),
|
|
2655
2846
|
...(await el.getSize()),
|
|
2656
|
-
}
|
|
2657
|
-
if (prop) return rect[prop]
|
|
2658
|
-
return rect
|
|
2847
|
+
}
|
|
2848
|
+
if (prop) return rect[prop]
|
|
2849
|
+
return rect
|
|
2659
2850
|
}
|
|
2660
2851
|
|
|
2661
2852
|
/**
|
|
@@ -2663,164 +2854,56 @@ class WebDriver extends Helper {
|
|
|
2663
2854
|
* @param {*} caps
|
|
2664
2855
|
* @param {*} fn
|
|
2665
2856
|
*/
|
|
2666
|
-
|
|
2667
|
-
runOnIOS(caps, fn) {
|
|
2668
|
-
}
|
|
2857
|
+
|
|
2858
|
+
runOnIOS(caps, fn) {}
|
|
2669
2859
|
|
|
2670
2860
|
/**
|
|
2671
2861
|
* Placeholder for ~ locator only test case write once run on both Appium and WebDriver.
|
|
2672
2862
|
* @param {*} caps
|
|
2673
2863
|
* @param {*} fn
|
|
2674
2864
|
*/
|
|
2675
|
-
runOnAndroid(caps, fn) {
|
|
2676
|
-
}
|
|
2677
|
-
/* eslint-enable */
|
|
2865
|
+
runOnAndroid(caps, fn) {}
|
|
2678
2866
|
|
|
2679
2867
|
/**
|
|
2680
2868
|
* Placeholder for ~ locator only test case write once run on both Appium and WebDriver.
|
|
2681
2869
|
*/
|
|
2682
|
-
runInWeb(fn) {
|
|
2683
|
-
return fn()
|
|
2684
|
-
}
|
|
2685
|
-
|
|
2686
|
-
/**
|
|
2687
|
-
*
|
|
2688
|
-
* _Note:_ Only works when devtoolsProtocol is enabled.
|
|
2689
|
-
*
|
|
2690
|
-
* {{> flushNetworkTraffics }}
|
|
2691
|
-
*/
|
|
2692
|
-
flushNetworkTraffics() {
|
|
2693
|
-
if (!this.options.automationProtocol) {
|
|
2694
|
-
console.log('* Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration');
|
|
2695
|
-
return;
|
|
2696
|
-
}
|
|
2697
|
-
this.requests = [];
|
|
2698
|
-
}
|
|
2699
|
-
|
|
2700
|
-
/**
|
|
2701
|
-
*
|
|
2702
|
-
* _Note:_ Only works when devtoolsProtocol is enabled.
|
|
2703
|
-
*
|
|
2704
|
-
* {{> stopRecordingTraffic }}
|
|
2705
|
-
*/
|
|
2706
|
-
stopRecordingTraffic() {
|
|
2707
|
-
if (!this.options.automationProtocol) {
|
|
2708
|
-
console.log('* Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration');
|
|
2709
|
-
return;
|
|
2710
|
-
}
|
|
2711
|
-
this.page.removeAllListeners('request');
|
|
2712
|
-
this.recording = false;
|
|
2713
|
-
}
|
|
2714
|
-
|
|
2715
|
-
/**
|
|
2716
|
-
*
|
|
2717
|
-
* _Note:_ Only works when devtoolsProtocol is enabled.
|
|
2718
|
-
*
|
|
2719
|
-
* {{> startRecordingTraffic }}
|
|
2720
|
-
*
|
|
2721
|
-
*/
|
|
2722
|
-
async startRecordingTraffic() {
|
|
2723
|
-
if (!this.options.automationProtocol) {
|
|
2724
|
-
console.log('* Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration');
|
|
2725
|
-
return;
|
|
2726
|
-
}
|
|
2727
|
-
this.flushNetworkTraffics();
|
|
2728
|
-
this.recording = true;
|
|
2729
|
-
this.recordedAtLeastOnce = true;
|
|
2730
|
-
|
|
2731
|
-
this.page = (await this.puppeteerBrowser.pages())[0];
|
|
2732
|
-
await this.page.setRequestInterception(true);
|
|
2733
|
-
|
|
2734
|
-
this.page.on('request', (request) => {
|
|
2735
|
-
const information = {
|
|
2736
|
-
url: request.url(),
|
|
2737
|
-
method: request.method(),
|
|
2738
|
-
requestHeaders: request.headers(),
|
|
2739
|
-
requestPostData: request.postData(),
|
|
2740
|
-
response: request.response(),
|
|
2741
|
-
};
|
|
2742
|
-
|
|
2743
|
-
this.debugSection('REQUEST: ', JSON.stringify(information));
|
|
2744
|
-
|
|
2745
|
-
if (typeof information.requestPostData === 'object') {
|
|
2746
|
-
information.requestPostData = JSON.parse(information.requestPostData);
|
|
2747
|
-
}
|
|
2748
|
-
request.continue();
|
|
2749
|
-
this.requests.push(information);
|
|
2750
|
-
});
|
|
2751
|
-
}
|
|
2752
|
-
|
|
2753
|
-
/**
|
|
2754
|
-
*
|
|
2755
|
-
* _Note:_ Only works when devtoolsProtocol is enabled.
|
|
2756
|
-
*
|
|
2757
|
-
* {{> grabRecordedNetworkTraffics }}
|
|
2758
|
-
*/
|
|
2759
|
-
async grabRecordedNetworkTraffics() {
|
|
2760
|
-
if (!this.options.automationProtocol) {
|
|
2761
|
-
console.log('* Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration');
|
|
2762
|
-
return;
|
|
2763
|
-
}
|
|
2764
|
-
return grabRecordedNetworkTraffics.call(this);
|
|
2765
|
-
}
|
|
2766
|
-
|
|
2767
|
-
/**
|
|
2768
|
-
*
|
|
2769
|
-
* _Note:_ Only works when devtoolsProtocol is enabled.
|
|
2770
|
-
*
|
|
2771
|
-
* {{> seeTraffic }}
|
|
2772
|
-
*/
|
|
2773
|
-
async seeTraffic({
|
|
2774
|
-
name, url, parameters, requestPostData, timeout = 10,
|
|
2775
|
-
}) {
|
|
2776
|
-
if (!this.options.automationProtocol) {
|
|
2777
|
-
console.log('* Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration');
|
|
2778
|
-
return;
|
|
2779
|
-
}
|
|
2780
|
-
await seeTraffic.call(this, ...arguments);
|
|
2781
|
-
}
|
|
2782
|
-
|
|
2783
|
-
/**
|
|
2784
|
-
*
|
|
2785
|
-
* _Note:_ Only works when devtoolsProtocol is enabled.
|
|
2786
|
-
*
|
|
2787
|
-
* {{> dontSeeTraffic }}
|
|
2788
|
-
*
|
|
2789
|
-
*/
|
|
2790
|
-
dontSeeTraffic({ name, url }) {
|
|
2791
|
-
if (!this.options.automationProtocol) {
|
|
2792
|
-
console.log('* Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration');
|
|
2793
|
-
return;
|
|
2794
|
-
}
|
|
2795
|
-
dontSeeTraffic.call(this, ...arguments);
|
|
2870
|
+
async runInWeb(fn) {
|
|
2871
|
+
return fn()
|
|
2796
2872
|
}
|
|
2797
2873
|
}
|
|
2798
2874
|
|
|
2799
2875
|
async function proceedSee(assertType, text, context, strict = false) {
|
|
2800
|
-
let description
|
|
2876
|
+
let description
|
|
2801
2877
|
if (!context) {
|
|
2802
2878
|
if (this.context === webRoot) {
|
|
2803
|
-
context = this.context
|
|
2804
|
-
description = 'web page'
|
|
2879
|
+
context = this.context
|
|
2880
|
+
description = 'web page'
|
|
2805
2881
|
} else {
|
|
2806
|
-
description = `current context ${this.context}
|
|
2807
|
-
context = './/*'
|
|
2882
|
+
description = `current context ${this.context}`
|
|
2883
|
+
context = './/*'
|
|
2808
2884
|
}
|
|
2809
2885
|
} else {
|
|
2810
|
-
description = `element ${context}
|
|
2886
|
+
description = `element ${context}`
|
|
2887
|
+
}
|
|
2888
|
+
|
|
2889
|
+
const smartWaitEnabled = assertType === 'assert'
|
|
2890
|
+
const res = await this._locate(withStrictLocator(context), smartWaitEnabled)
|
|
2891
|
+
assertElementExists(res, context)
|
|
2892
|
+
let selected = await forEachAsync(res, async el => this.browser.getElementText(getElementId(el)))
|
|
2893
|
+
|
|
2894
|
+
// apply ignoreCase option
|
|
2895
|
+
if (store?.currentStep?.opts?.ignoreCase === true) {
|
|
2896
|
+
text = text.toLowerCase()
|
|
2897
|
+
selected = selected.map(elText => elText.toLowerCase())
|
|
2811
2898
|
}
|
|
2812
2899
|
|
|
2813
|
-
const smartWaitEnabled = assertType === 'assert';
|
|
2814
|
-
const res = await this._locate(withStrictLocator(context), smartWaitEnabled);
|
|
2815
|
-
assertElementExists(res, context);
|
|
2816
|
-
const selected = await forEachAsync(res, async el => this.browser.getElementText(getElementId(el)));
|
|
2817
2900
|
if (strict) {
|
|
2818
2901
|
if (Array.isArray(selected) && selected.length !== 0) {
|
|
2819
|
-
return selected.map(elText => equals(description)[assertType](text, elText))
|
|
2902
|
+
return selected.map(elText => equals(description)[assertType](text, elText))
|
|
2820
2903
|
}
|
|
2821
|
-
return equals(description)[assertType](text, selected)
|
|
2904
|
+
return equals(description)[assertType](text, selected)
|
|
2822
2905
|
}
|
|
2823
|
-
return stringIncludes(description)[assertType](text, selected)
|
|
2906
|
+
return stringIncludes(description)[assertType](text, selected)
|
|
2824
2907
|
}
|
|
2825
2908
|
|
|
2826
2909
|
/**
|
|
@@ -2837,21 +2920,19 @@ async function proceedSee(assertType, text, context, strict = false) {
|
|
|
2837
2920
|
* @return {Promise<Array>} - Array of values.
|
|
2838
2921
|
*/
|
|
2839
2922
|
async function forEachAsync(array, callback, options = { expandArrayResults: true }) {
|
|
2840
|
-
const {
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
const inputArray = Array.isArray(array) ? array : [array];
|
|
2844
|
-
const values = [];
|
|
2923
|
+
const { expandArrayResults = true } = options
|
|
2924
|
+
const inputArray = Array.isArray(array) ? array : [array]
|
|
2925
|
+
const values = []
|
|
2845
2926
|
for (let index = 0; index < inputArray.length; index++) {
|
|
2846
|
-
const res = await callback(inputArray[index], index, inputArray)
|
|
2927
|
+
const res = await callback(inputArray[index], index, inputArray)
|
|
2847
2928
|
|
|
2848
2929
|
if (Array.isArray(res) && expandArrayResults) {
|
|
2849
|
-
res.forEach(val => values.push(val))
|
|
2930
|
+
res.forEach(val => values.push(val))
|
|
2850
2931
|
} else if (res) {
|
|
2851
|
-
values.push(res)
|
|
2932
|
+
values.push(res)
|
|
2852
2933
|
}
|
|
2853
2934
|
}
|
|
2854
|
-
return values
|
|
2935
|
+
return values
|
|
2855
2936
|
}
|
|
2856
2937
|
|
|
2857
2938
|
/**
|
|
@@ -2866,374 +2947,433 @@ async function forEachAsync(array, callback, options = { expandArrayResults: tru
|
|
|
2866
2947
|
* @return {Promise<Array>} - Array of values.
|
|
2867
2948
|
*/
|
|
2868
2949
|
async function filterAsync(array, callback) {
|
|
2869
|
-
const inputArray = Array.isArray(array) ? array : [array]
|
|
2870
|
-
const values = []
|
|
2950
|
+
const inputArray = Array.isArray(array) ? array : [array]
|
|
2951
|
+
const values = []
|
|
2871
2952
|
for (let index = 0; index < inputArray.length; index++) {
|
|
2872
|
-
const res = await callback(inputArray[index], index, inputArray)
|
|
2873
|
-
const value = Array.isArray(res) ? res[0] : res
|
|
2953
|
+
const res = await callback(inputArray[index], index, inputArray)
|
|
2954
|
+
const value = Array.isArray(res) ? res[0] : res
|
|
2874
2955
|
|
|
2875
2956
|
if (value) {
|
|
2876
|
-
values.push(inputArray[index])
|
|
2957
|
+
values.push(inputArray[index])
|
|
2877
2958
|
}
|
|
2878
2959
|
}
|
|
2879
|
-
return values
|
|
2960
|
+
return values
|
|
2880
2961
|
}
|
|
2881
2962
|
|
|
2882
2963
|
async function findClickable(locator, locateFn) {
|
|
2883
|
-
locator = new Locator(locator)
|
|
2964
|
+
locator = new Locator(locator)
|
|
2884
2965
|
|
|
2885
2966
|
if (this._isCustomLocator(locator)) {
|
|
2886
|
-
return locateFn(locator.value)
|
|
2967
|
+
return locateFn(locator.value)
|
|
2887
2968
|
}
|
|
2888
2969
|
|
|
2889
|
-
if (locator.isAccessibilityId() && !this.isWeb) return locateFn(locator, true)
|
|
2890
|
-
if (
|
|
2970
|
+
if (locator.isAccessibilityId() && !this.isWeb) return locateFn(locator, true)
|
|
2971
|
+
if (locator.isRole()) return locateFn(locator, true)
|
|
2972
|
+
if (!locator.isFuzzy()) return locateFn(locator, true)
|
|
2973
|
+
|
|
2974
|
+
let els
|
|
2975
|
+
const literal = xpathLocator.literal(locator.value)
|
|
2891
2976
|
|
|
2892
|
-
|
|
2893
|
-
|
|
2977
|
+
els = await locateFn(Locator.clickable.narrow(literal))
|
|
2978
|
+
if (els.length) return els
|
|
2894
2979
|
|
|
2895
|
-
|
|
2896
|
-
|
|
2980
|
+
// Try ARIA selector for accessible name
|
|
2981
|
+
try {
|
|
2982
|
+
els = await locateFn(`aria/${locator.value}`)
|
|
2983
|
+
if (els.length) return els
|
|
2984
|
+
} catch (e) {
|
|
2985
|
+
// ARIA selector not supported or failed
|
|
2986
|
+
}
|
|
2897
2987
|
|
|
2898
|
-
els = await locateFn(Locator.clickable.wide(literal))
|
|
2899
|
-
if (els.length) return els
|
|
2988
|
+
els = await locateFn(Locator.clickable.wide(literal))
|
|
2989
|
+
if (els.length) return els
|
|
2900
2990
|
|
|
2901
|
-
els = await locateFn(Locator.clickable.self(literal))
|
|
2902
|
-
if (els.length) return els
|
|
2991
|
+
els = await locateFn(Locator.clickable.self(literal))
|
|
2992
|
+
if (els.length) return els
|
|
2903
2993
|
|
|
2904
|
-
return locateFn(locator.value)
|
|
2994
|
+
return await locateFn(locator.value) // by css or xpath
|
|
2905
2995
|
}
|
|
2906
2996
|
|
|
2907
2997
|
async function findFields(locator) {
|
|
2908
|
-
locator = new Locator(locator)
|
|
2998
|
+
locator = new Locator(locator)
|
|
2909
2999
|
|
|
2910
3000
|
if (this._isCustomLocator(locator)) {
|
|
2911
|
-
return this._locate(locator)
|
|
3001
|
+
return this._locate(locator)
|
|
2912
3002
|
}
|
|
2913
3003
|
|
|
2914
|
-
if (locator.isAccessibilityId() && !this.isWeb) return this._locate(locator, true)
|
|
2915
|
-
if (
|
|
3004
|
+
if (locator.isAccessibilityId() && !this.isWeb) return this._locate(locator, true)
|
|
3005
|
+
if (locator.isRole()) return this._locate(locator, true)
|
|
3006
|
+
if (!locator.isFuzzy()) return this._locate(locator, true)
|
|
3007
|
+
|
|
3008
|
+
const literal = xpathLocator.literal(locator.value)
|
|
3009
|
+
let els = await this._locate(Locator.field.labelEquals(literal))
|
|
3010
|
+
if (els.length) return els
|
|
2916
3011
|
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
if (els.length) return els;
|
|
3012
|
+
els = await this._locate(Locator.field.labelContains(literal))
|
|
3013
|
+
if (els.length) return els
|
|
2920
3014
|
|
|
2921
|
-
els = await this._locate(Locator.field.
|
|
2922
|
-
if (els.length) return els
|
|
3015
|
+
els = await this._locate(Locator.field.byName(literal))
|
|
3016
|
+
if (els.length) return els
|
|
2923
3017
|
|
|
2924
|
-
|
|
2925
|
-
if (els.length) return els;
|
|
2926
|
-
return this._locate(locator.value); // by css or xpath
|
|
3018
|
+
return await this._locate(locator.value) // by css or xpath
|
|
2927
3019
|
}
|
|
2928
3020
|
|
|
2929
3021
|
async function proceedSeeField(assertType, field, value) {
|
|
2930
|
-
const res = await findFields.call(this, field)
|
|
2931
|
-
assertElementExists(res, field, 'Field')
|
|
2932
|
-
const elem = usingFirstElement(res)
|
|
2933
|
-
const elemId = getElementId(elem)
|
|
2934
|
-
|
|
2935
|
-
const proceedMultiple = async
|
|
2936
|
-
const fieldResults = toArray(
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
3022
|
+
const res = await findFields.call(this, field)
|
|
3023
|
+
assertElementExists(res, field, 'Field')
|
|
3024
|
+
const elem = usingFirstElement(res)
|
|
3025
|
+
const elemId = getElementId(elem)
|
|
3026
|
+
|
|
3027
|
+
const proceedMultiple = async fields => {
|
|
3028
|
+
const fieldResults = toArray(
|
|
3029
|
+
await forEachAsync(fields, async el => {
|
|
3030
|
+
const elementId = getElementId(el)
|
|
3031
|
+
return this.browser.getElementAttribute(elementId, 'value')
|
|
3032
|
+
}),
|
|
3033
|
+
)
|
|
2940
3034
|
|
|
2941
3035
|
if (typeof value === 'boolean') {
|
|
2942
|
-
equals(`no. of items matching > 0: ${field}`)[assertType](value, !!fieldResults.length)
|
|
3036
|
+
equals(`no. of items matching > 0: ${field}`)[assertType](value, !!fieldResults.length)
|
|
2943
3037
|
} else {
|
|
2944
3038
|
// Assert that results were found so the forEach assert does not silently pass
|
|
2945
|
-
equals(`no. of items matching > 0: ${field}`)[assertType](true, !!fieldResults.length)
|
|
2946
|
-
fieldResults.forEach(val => stringIncludes(`fields by ${field}`)[assertType](value, val))
|
|
3039
|
+
equals(`no. of items matching > 0: ${field}`)[assertType](true, !!fieldResults.length)
|
|
3040
|
+
fieldResults.forEach(val => stringIncludes(`fields by ${field}`)[assertType](value, val))
|
|
2947
3041
|
}
|
|
2948
|
-
}
|
|
3042
|
+
}
|
|
3043
|
+
|
|
3044
|
+
const proceedSingle = async el => {
|
|
3045
|
+
let res = await el.getValue()
|
|
2949
3046
|
|
|
2950
|
-
const proceedSingle = el => el.getValue().then((res) => {
|
|
2951
3047
|
if (res === null) {
|
|
2952
|
-
|
|
3048
|
+
res = await el.getText()
|
|
3049
|
+
}
|
|
3050
|
+
|
|
3051
|
+
if (res === null || res === undefined) {
|
|
3052
|
+
throw new Error(`Element ${el.selector} has no value attribute`)
|
|
2953
3053
|
}
|
|
2954
|
-
stringIncludes(`fields by ${field}`)[assertType](value, res);
|
|
2955
|
-
});
|
|
2956
3054
|
|
|
2957
|
-
|
|
3055
|
+
stringIncludes(`fields by ${field}`)[assertType](value, res)
|
|
3056
|
+
}
|
|
3057
|
+
|
|
3058
|
+
const filterBySelected = async elements => filterAsync(elements, async el => this.browser.isElementSelected(getElementId(el)))
|
|
2958
3059
|
|
|
2959
3060
|
const filterSelectedByValue = async (elements, value) => {
|
|
2960
|
-
return filterAsync(elements, async
|
|
2961
|
-
const elementId = getElementId(el)
|
|
2962
|
-
const currentValue =
|
|
2963
|
-
const isSelected = await this.browser.isElementSelected(elementId)
|
|
2964
|
-
return currentValue === value && isSelected
|
|
2965
|
-
})
|
|
2966
|
-
}
|
|
2967
|
-
|
|
2968
|
-
const tag = await elem.getTagName()
|
|
3061
|
+
return filterAsync(elements, async el => {
|
|
3062
|
+
const elementId = getElementId(el)
|
|
3063
|
+
const currentValue = await this.browser.getElementAttribute(elementId, 'value')
|
|
3064
|
+
const isSelected = await this.browser.isElementSelected(elementId)
|
|
3065
|
+
return currentValue === value && isSelected
|
|
3066
|
+
})
|
|
3067
|
+
}
|
|
3068
|
+
|
|
3069
|
+
const tag = await elem.getTagName()
|
|
2969
3070
|
if (tag === 'select') {
|
|
2970
|
-
|
|
3071
|
+
let subOptions
|
|
3072
|
+
|
|
3073
|
+
try {
|
|
3074
|
+
subOptions = await this.browser.findElementsFromElement(elemId, 'css', 'option')
|
|
3075
|
+
} catch (e) {
|
|
3076
|
+
subOptions = await this.browser.findElementsFromElement(elemId, 'xpath', 'option')
|
|
3077
|
+
}
|
|
2971
3078
|
|
|
2972
3079
|
if (value === '') {
|
|
2973
3080
|
// Don't filter by value
|
|
2974
|
-
const selectedOptions = await filterBySelected(subOptions)
|
|
2975
|
-
return proceedMultiple(selectedOptions)
|
|
3081
|
+
const selectedOptions = await filterBySelected(subOptions)
|
|
3082
|
+
return proceedMultiple(selectedOptions)
|
|
2976
3083
|
}
|
|
2977
3084
|
|
|
2978
|
-
const options = await filterSelectedByValue(subOptions, value)
|
|
2979
|
-
return proceedMultiple(options)
|
|
3085
|
+
const options = await filterSelectedByValue(subOptions, value)
|
|
3086
|
+
return proceedMultiple(options)
|
|
2980
3087
|
}
|
|
2981
3088
|
|
|
2982
3089
|
if (tag === 'input') {
|
|
2983
|
-
const fieldType = await elem.getAttribute('type')
|
|
3090
|
+
const fieldType = await elem.getAttribute('type')
|
|
2984
3091
|
|
|
2985
3092
|
if (fieldType === 'checkbox' || fieldType === 'radio') {
|
|
2986
3093
|
if (typeof value === 'boolean') {
|
|
2987
3094
|
// Support boolean values
|
|
2988
|
-
const options = await filterBySelected(res)
|
|
2989
|
-
return proceedMultiple(options)
|
|
3095
|
+
const options = await filterBySelected(res)
|
|
3096
|
+
return proceedMultiple(options)
|
|
2990
3097
|
}
|
|
2991
3098
|
|
|
2992
|
-
const options = await filterSelectedByValue(res, value)
|
|
2993
|
-
return proceedMultiple(options)
|
|
3099
|
+
const options = await filterSelectedByValue(res, value)
|
|
3100
|
+
return proceedMultiple(options)
|
|
2994
3101
|
}
|
|
2995
|
-
return proceedSingle(elem)
|
|
3102
|
+
return proceedSingle(elem)
|
|
2996
3103
|
}
|
|
2997
|
-
return proceedSingle(elem)
|
|
3104
|
+
return proceedSingle(elem)
|
|
2998
3105
|
}
|
|
2999
3106
|
|
|
3000
3107
|
function toArray(item) {
|
|
3001
3108
|
if (!Array.isArray(item)) {
|
|
3002
|
-
return [item]
|
|
3109
|
+
return [item]
|
|
3003
3110
|
}
|
|
3004
|
-
return item
|
|
3111
|
+
return item
|
|
3005
3112
|
}
|
|
3006
3113
|
|
|
3007
3114
|
async function proceedSeeCheckbox(assertType, field) {
|
|
3008
|
-
const res = await findFields.call(this, field)
|
|
3009
|
-
assertElementExists(res, field, 'Field')
|
|
3115
|
+
const res = await findFields.call(this, field)
|
|
3116
|
+
assertElementExists(res, field, 'Field')
|
|
3117
|
+
|
|
3118
|
+
const selected = await forEachAsync(res, async el => {
|
|
3119
|
+
const elementId = getElementId(el)
|
|
3120
|
+
return isElementChecked(this.browser, elementId)
|
|
3121
|
+
})
|
|
3122
|
+
|
|
3123
|
+
return truth(`checkable field "${field}"`, 'to be checked')[assertType](selected)
|
|
3124
|
+
}
|
|
3125
|
+
|
|
3126
|
+
async function getElementTextAttributes(element) {
|
|
3127
|
+
const elementId = getElementId(element)
|
|
3128
|
+
const ariaLabel = await this.browser.getElementAttribute(elementId, 'aria-label').catch(() => '')
|
|
3129
|
+
const placeholder = await this.browser.getElementAttribute(elementId, 'placeholder').catch(() => '')
|
|
3130
|
+
const innerText = await this.browser.getElementText(elementId).catch(() => '')
|
|
3131
|
+
return [ariaLabel, placeholder, innerText]
|
|
3132
|
+
}
|
|
3010
3133
|
|
|
3011
|
-
|
|
3012
|
-
|
|
3134
|
+
async function isElementChecked(browser, elementId) {
|
|
3135
|
+
let isChecked = await browser.isElementSelected(elementId)
|
|
3136
|
+
if (!isChecked) {
|
|
3137
|
+
const ariaChecked = await browser.getElementAttribute(elementId, 'aria-checked')
|
|
3138
|
+
isChecked = ariaChecked === 'true'
|
|
3139
|
+
}
|
|
3140
|
+
return isChecked
|
|
3013
3141
|
}
|
|
3014
3142
|
|
|
3015
3143
|
async function findCheckable(locator, locateFn) {
|
|
3016
|
-
let els
|
|
3017
|
-
locator = new Locator(locator)
|
|
3144
|
+
let els
|
|
3145
|
+
locator = new Locator(locator)
|
|
3018
3146
|
|
|
3019
3147
|
if (this._isCustomLocator(locator)) {
|
|
3020
|
-
return locateFn(locator.value)
|
|
3148
|
+
return locateFn(locator.value)
|
|
3021
3149
|
}
|
|
3022
3150
|
|
|
3023
|
-
if (locator.isAccessibilityId() && !this.isWeb) return locateFn(locator, true)
|
|
3024
|
-
if (
|
|
3151
|
+
if (locator.isAccessibilityId() && !this.isWeb) return locateFn(locator, true)
|
|
3152
|
+
if (locator.isRole()) return locateFn(locator, true)
|
|
3153
|
+
if (!locator.isFuzzy()) return locateFn(locator, true)
|
|
3154
|
+
|
|
3155
|
+
const literal = xpathLocator.literal(locator.value)
|
|
3156
|
+
els = await locateFn(Locator.checkable.byText(literal))
|
|
3157
|
+
if (els.length) return els
|
|
3158
|
+
|
|
3159
|
+
// Try ARIA selector for accessible name
|
|
3160
|
+
try {
|
|
3161
|
+
els = await locateFn(`aria/${locator.value}`)
|
|
3162
|
+
if (els.length) return els
|
|
3163
|
+
} catch (e) {
|
|
3164
|
+
// ARIA selector not supported or failed
|
|
3165
|
+
}
|
|
3025
3166
|
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
if (els.length) return els;
|
|
3029
|
-
els = await locateFn(Locator.checkable.byName(literal));
|
|
3030
|
-
if (els.length) return els;
|
|
3167
|
+
els = await locateFn(Locator.checkable.byName(literal))
|
|
3168
|
+
if (els.length) return els
|
|
3031
3169
|
|
|
3032
|
-
return locateFn(locator.value)
|
|
3170
|
+
return await locateFn(locator.value) // by css or xpath
|
|
3033
3171
|
}
|
|
3034
3172
|
|
|
3035
3173
|
function withStrictLocator(locator) {
|
|
3036
|
-
locator = new Locator(locator)
|
|
3037
|
-
return locator.simplify()
|
|
3174
|
+
locator = new Locator(locator)
|
|
3175
|
+
return locator.simplify()
|
|
3038
3176
|
}
|
|
3039
3177
|
|
|
3040
3178
|
function isFrameLocator(locator) {
|
|
3041
|
-
locator = new Locator(locator)
|
|
3042
|
-
if (locator.isFrame()) return locator.value
|
|
3043
|
-
return false
|
|
3179
|
+
locator = new Locator(locator)
|
|
3180
|
+
if (locator.isFrame()) return locator.value
|
|
3181
|
+
return false
|
|
3044
3182
|
}
|
|
3045
3183
|
|
|
3046
3184
|
function assertElementExists(res, locator, prefix, suffix) {
|
|
3047
3185
|
if (!res || res.length === 0) {
|
|
3048
|
-
throw new ElementNotFound(locator, prefix, suffix)
|
|
3186
|
+
throw new ElementNotFound(locator, prefix, suffix)
|
|
3049
3187
|
}
|
|
3050
3188
|
}
|
|
3051
3189
|
|
|
3052
3190
|
function usingFirstElement(els) {
|
|
3053
|
-
if (els.length > 1) debug(`[Elements] Using first element out of ${els.length}`)
|
|
3054
|
-
return els[0]
|
|
3191
|
+
if (els.length > 1) debug(`[Elements] Using first element out of ${els.length}`)
|
|
3192
|
+
return els[0]
|
|
3055
3193
|
}
|
|
3056
3194
|
|
|
3057
3195
|
function getElementId(el) {
|
|
3058
3196
|
// W3C WebDriver web element identifier
|
|
3059
3197
|
// https://w3c.github.io/webdriver/#dfn-web-element-identifier
|
|
3060
3198
|
if (el['element-6066-11e4-a52e-4f735466cecf']) {
|
|
3061
|
-
return el['element-6066-11e4-a52e-4f735466cecf']
|
|
3199
|
+
return el['element-6066-11e4-a52e-4f735466cecf']
|
|
3062
3200
|
}
|
|
3063
3201
|
// (deprecated) JsonWireProtocol identifier
|
|
3064
3202
|
// https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#webelement-json-object
|
|
3065
3203
|
if (el.ELEMENT) {
|
|
3066
|
-
return el.ELEMENT
|
|
3204
|
+
return el.ELEMENT
|
|
3067
3205
|
}
|
|
3068
3206
|
|
|
3069
|
-
return null
|
|
3207
|
+
return null
|
|
3070
3208
|
}
|
|
3071
3209
|
|
|
3072
3210
|
// List of known key values to unicode code points
|
|
3073
3211
|
// https://www.w3.org/TR/webdriver/#keyboard-actions
|
|
3074
3212
|
const keyUnicodeMap = {
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
'Space': '\uE00D',
|
|
3213
|
+
Unidentified: '\uE000',
|
|
3214
|
+
Cancel: '\uE001',
|
|
3215
|
+
Clear: '\uE005',
|
|
3216
|
+
Help: '\uE002',
|
|
3217
|
+
Pause: '\uE00B',
|
|
3218
|
+
Backspace: '\uE003',
|
|
3219
|
+
Return: '\uE006',
|
|
3220
|
+
Enter: '\uE007',
|
|
3221
|
+
Escape: '\uE00C',
|
|
3222
|
+
Alt: '\uE00A',
|
|
3223
|
+
AltLeft: '\uE00A',
|
|
3224
|
+
AltRight: '\uE052',
|
|
3225
|
+
Control: '\uE009',
|
|
3226
|
+
ControlLeft: '\uE009',
|
|
3227
|
+
ControlRight: '\uE051',
|
|
3228
|
+
Meta: '\uE03D',
|
|
3229
|
+
MetaLeft: '\uE03D',
|
|
3230
|
+
MetaRight: '\uE053',
|
|
3231
|
+
Shift: '\uE008',
|
|
3232
|
+
ShiftLeft: '\uE008',
|
|
3233
|
+
ShiftRight: '\uE050',
|
|
3234
|
+
Space: '\uE00D',
|
|
3098
3235
|
' ': '\uE00D',
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
};
|
|
3236
|
+
Tab: '\uE004',
|
|
3237
|
+
Insert: '\uE016',
|
|
3238
|
+
Delete: '\uE017',
|
|
3239
|
+
End: '\uE010',
|
|
3240
|
+
Home: '\uE011',
|
|
3241
|
+
PageUp: '\uE00E',
|
|
3242
|
+
PageDown: '\uE00F',
|
|
3243
|
+
ArrowDown: '\uE015',
|
|
3244
|
+
ArrowLeft: '\uE012',
|
|
3245
|
+
ArrowRight: '\uE014',
|
|
3246
|
+
ArrowUp: '\uE013',
|
|
3247
|
+
F1: '\uE031',
|
|
3248
|
+
F2: '\uE032',
|
|
3249
|
+
F3: '\uE033',
|
|
3250
|
+
F4: '\uE034',
|
|
3251
|
+
F5: '\uE035',
|
|
3252
|
+
F6: '\uE036',
|
|
3253
|
+
F7: '\uE037',
|
|
3254
|
+
F8: '\uE038',
|
|
3255
|
+
F9: '\uE039',
|
|
3256
|
+
F10: '\uE03A',
|
|
3257
|
+
F11: '\uE03B',
|
|
3258
|
+
F12: '\uE03C',
|
|
3259
|
+
Numpad0: '\uE01A',
|
|
3260
|
+
Numpad1: '\uE01B',
|
|
3261
|
+
Numpad2: '\uE01C',
|
|
3262
|
+
Numpad3: '\uE01D',
|
|
3263
|
+
Numpad4: '\uE01E',
|
|
3264
|
+
Numpad5: '\uE01F',
|
|
3265
|
+
Numpad6: '\uE020',
|
|
3266
|
+
Numpad7: '\uE021',
|
|
3267
|
+
Numpad8: '\uE022',
|
|
3268
|
+
Numpad9: '\uE023',
|
|
3269
|
+
NumpadMultiply: '\uE024',
|
|
3270
|
+
NumpadAdd: '\uE025',
|
|
3271
|
+
NumpadSubtract: '\uE027',
|
|
3272
|
+
NumpadDecimal: '\uE028',
|
|
3273
|
+
NumpadDivide: '\uE029',
|
|
3274
|
+
NumpadEnter: '\uE007',
|
|
3275
|
+
NumpadInsert: '\uE05C', // 'Numpad0' alternate (when NumLock off)
|
|
3276
|
+
NumpadDelete: '\uE05D', // 'NumpadDecimal' alternate (when NumLock off)
|
|
3277
|
+
NumpadEnd: '\uE056', // 'Numpad1' alternate (when NumLock off)
|
|
3278
|
+
NumpadHome: '\uE057', // 'Numpad7' alternate (when NumLock off)
|
|
3279
|
+
NumpadPageDown: '\uE055', // 'Numpad3' alternate (when NumLock off)
|
|
3280
|
+
NumpadPageUp: '\uE054', // 'Numpad9' alternate (when NumLock off)
|
|
3281
|
+
NumpadArrowDown: '\uE05B', // 'Numpad2' alternate (when NumLock off)
|
|
3282
|
+
NumpadArrowLeft: '\uE058', // 'Numpad4' alternate (when NumLock off)
|
|
3283
|
+
NumpadArrowRight: '\uE05A', // 'Numpad6' alternate (when NumLock off)
|
|
3284
|
+
NumpadArrowUp: '\uE059', // 'Numpad8' alternate (when NumLock off)
|
|
3285
|
+
Comma: '\uE026', // ',' alias
|
|
3286
|
+
Digit0: '0', // '0' alias
|
|
3287
|
+
Digit1: '1', // '1' alias
|
|
3288
|
+
Digit2: '2', // '2' alias
|
|
3289
|
+
Digit3: '3', // '3' alias
|
|
3290
|
+
Digit4: '4', // '4' alias
|
|
3291
|
+
Digit5: '5', // '5' alias
|
|
3292
|
+
Digit6: '6', // '6' alias
|
|
3293
|
+
Digit7: '7', // '7' alias
|
|
3294
|
+
Digit8: '8', // '8' alias
|
|
3295
|
+
Digit9: '9', // '9' alias
|
|
3296
|
+
Equal: '\uE019', // '=' alias
|
|
3297
|
+
KeyA: 'a', // 'a' alias
|
|
3298
|
+
KeyB: 'b', // 'b' alias
|
|
3299
|
+
KeyC: 'c', // 'c' alias
|
|
3300
|
+
KeyD: 'd', // 'd' alias
|
|
3301
|
+
KeyE: 'e', // 'e' alias
|
|
3302
|
+
KeyF: 'f', // 'f' alias
|
|
3303
|
+
KeyG: 'g', // 'g' alias
|
|
3304
|
+
KeyH: 'h', // 'h' alias
|
|
3305
|
+
KeyI: 'i', // 'i' alias
|
|
3306
|
+
KeyJ: 'j', // 'j' alias
|
|
3307
|
+
KeyK: 'k', // 'k' alias
|
|
3308
|
+
KeyL: 'l', // 'l' alias
|
|
3309
|
+
KeyM: 'm', // 'm' alias
|
|
3310
|
+
KeyN: 'n', // 'n' alias
|
|
3311
|
+
KeyO: 'o', // 'o' alias
|
|
3312
|
+
KeyP: 'p', // 'p' alias
|
|
3313
|
+
KeyQ: 'q', // 'q' alias
|
|
3314
|
+
KeyR: 'r', // 'r' alias
|
|
3315
|
+
KeyS: 's', // 's' alias
|
|
3316
|
+
KeyT: 't', // 't' alias
|
|
3317
|
+
KeyU: 'u', // 'u' alias
|
|
3318
|
+
KeyV: 'v', // 'v' alias
|
|
3319
|
+
KeyW: 'w', // 'w' alias
|
|
3320
|
+
KeyX: 'x', // 'x' alias
|
|
3321
|
+
KeyY: 'y', // 'y' alias
|
|
3322
|
+
KeyZ: 'z', // 'z' alias
|
|
3323
|
+
Period: '.', // '.' alias
|
|
3324
|
+
Semicolon: '\uE018', // ';' alias
|
|
3325
|
+
Slash: '/', // '/' alias
|
|
3326
|
+
ZenkakuHankaku: '\uE040',
|
|
3327
|
+
}
|
|
3192
3328
|
|
|
3193
3329
|
function convertKeyToRawKey(key) {
|
|
3194
3330
|
if (Object.prototype.hasOwnProperty.call(keyUnicodeMap, key)) {
|
|
3195
|
-
return keyUnicodeMap[key]
|
|
3331
|
+
return keyUnicodeMap[key]
|
|
3196
3332
|
}
|
|
3197
3333
|
// Key is raw key when no representative unicode code point for value
|
|
3198
|
-
return key
|
|
3334
|
+
return key
|
|
3199
3335
|
}
|
|
3200
3336
|
|
|
3201
3337
|
function getNormalizedKey(key) {
|
|
3202
|
-
let normalizedKey = getNormalizedKeyAttributeValue(key)
|
|
3338
|
+
let normalizedKey = getNormalizedKeyAttributeValue(key)
|
|
3203
3339
|
// Always use "left" modifier keys for non-W3C sessions,
|
|
3204
3340
|
// as JsonWireProtocol does not support "right" modifier keys
|
|
3205
3341
|
if (!this.browser.isW3C) {
|
|
3206
|
-
normalizedKey = normalizedKey.replace(/^(Alt|Control|Meta|Shift)Right$/, '$1')
|
|
3342
|
+
normalizedKey = normalizedKey.replace(/^(Alt|Control|Meta|Shift)Right$/, '$1')
|
|
3207
3343
|
}
|
|
3208
3344
|
if (key !== normalizedKey) {
|
|
3209
|
-
this.debugSection('Input', `Mapping key '${key}' to '${normalizedKey}'`)
|
|
3345
|
+
this.debugSection('Input', `Mapping key '${key}' to '${normalizedKey}'`)
|
|
3210
3346
|
}
|
|
3211
|
-
return convertKeyToRawKey(normalizedKey)
|
|
3347
|
+
return convertKeyToRawKey(normalizedKey)
|
|
3212
3348
|
}
|
|
3213
3349
|
|
|
3214
|
-
const unicodeModifierKeys = modifierKeys.map(k => convertKeyToRawKey(k))
|
|
3350
|
+
const unicodeModifierKeys = modifierKeys.map(k => convertKeyToRawKey(k))
|
|
3215
3351
|
function isModifierKey(key) {
|
|
3216
|
-
return unicodeModifierKeys.includes(key)
|
|
3352
|
+
return unicodeModifierKeys.includes(key)
|
|
3217
3353
|
}
|
|
3218
3354
|
|
|
3219
3355
|
function highlightActiveElement(element) {
|
|
3220
3356
|
if (this.options.highlightElement && global.debugMode) {
|
|
3221
|
-
highlightElement(element, this.browser)
|
|
3357
|
+
highlightElement(element, this.browser)
|
|
3222
3358
|
}
|
|
3223
3359
|
}
|
|
3224
3360
|
|
|
3225
3361
|
function prepareLocateFn(context) {
|
|
3226
|
-
if (!context) return this._locate.bind(this)
|
|
3227
|
-
return
|
|
3228
|
-
l = new Locator(l, 'css')
|
|
3229
|
-
return this._locate(context, true).then(async
|
|
3230
|
-
assertElementExists(res, context, 'Context element')
|
|
3362
|
+
if (!context) return this._locate.bind(this)
|
|
3363
|
+
return l => {
|
|
3364
|
+
l = new Locator(l, 'css')
|
|
3365
|
+
return this._locate(context, true).then(async res => {
|
|
3366
|
+
assertElementExists(res, context, 'Context element')
|
|
3231
3367
|
if (l.react) {
|
|
3232
|
-
return res[0].react$$(l.react, l.props || undefined)
|
|
3368
|
+
return res[0].react$$(l.react, l.props || undefined)
|
|
3233
3369
|
}
|
|
3234
|
-
return res[0].$$(l.simplify())
|
|
3235
|
-
})
|
|
3236
|
-
}
|
|
3370
|
+
return res[0].$$(l.simplify())
|
|
3371
|
+
})
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3374
|
+
|
|
3375
|
+
function logEvents(event) {
|
|
3376
|
+
browserLogs.push(event.text) // add log message to the array
|
|
3237
3377
|
}
|
|
3238
3378
|
|
|
3239
|
-
export default
|
|
3379
|
+
export { WebDriver as default }
|