codeceptjs 3.7.6-beta.4 → 4.0.0-beta.10.esm-aria
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 +1 -3
- package/bin/codecept.js +51 -53
- package/bin/test-server.js +14 -3
- package/docs/webapi/click.mustache +5 -1
- package/lib/actor.js +15 -11
- package/lib/ai.js +72 -107
- package/lib/assert/empty.js +9 -8
- package/lib/assert/equal.js +15 -17
- package/lib/assert/error.js +2 -2
- package/lib/assert/include.js +9 -11
- package/lib/assert/throws.js +1 -1
- package/lib/assert/truth.js +8 -5
- package/lib/assert.js +18 -18
- package/lib/codecept.js +102 -75
- package/lib/colorUtils.js +48 -50
- package/lib/command/check.js +32 -27
- package/lib/command/configMigrate.js +11 -10
- package/lib/command/definitions.js +16 -10
- package/lib/command/dryRun.js +16 -16
- package/lib/command/generate.js +62 -27
- package/lib/command/gherkin/init.js +36 -38
- package/lib/command/gherkin/snippets.js +14 -14
- package/lib/command/gherkin/steps.js +21 -18
- package/lib/command/info.js +8 -8
- package/lib/command/init.js +36 -29
- package/lib/command/interactive.js +11 -10
- package/lib/command/list.js +10 -9
- package/lib/command/run-multiple/chunk.js +5 -5
- package/lib/command/run-multiple/collection.js +5 -5
- package/lib/command/run-multiple/run.js +3 -3
- package/lib/command/run-multiple.js +16 -13
- package/lib/command/run-rerun.js +6 -7
- package/lib/command/run-workers.js +24 -9
- package/lib/command/run.js +23 -8
- package/lib/command/utils.js +20 -18
- package/lib/command/workers/runTests.js +197 -114
- package/lib/config.js +124 -51
- package/lib/container.js +438 -87
- package/lib/data/context.js +6 -5
- package/lib/data/dataScenarioConfig.js +1 -1
- package/lib/data/dataTableArgument.js +1 -1
- package/lib/data/table.js +1 -1
- package/lib/effects.js +94 -10
- package/lib/element/WebElement.js +2 -2
- package/lib/els.js +11 -9
- package/lib/event.js +11 -10
- package/lib/globals.js +141 -0
- package/lib/heal.js +12 -12
- package/lib/helper/AI.js +11 -11
- package/lib/helper/ApiDataFactory.js +50 -19
- package/lib/helper/Appium.js +19 -27
- package/lib/helper/FileSystem.js +32 -12
- package/lib/helper/GraphQL.js +3 -3
- package/lib/helper/GraphQLDataFactory.js +4 -4
- package/lib/helper/JSONResponse.js +25 -29
- package/lib/helper/Mochawesome.js +7 -4
- package/lib/helper/Playwright.js +902 -164
- package/lib/helper/Puppeteer.js +383 -76
- package/lib/helper/REST.js +29 -12
- package/lib/helper/WebDriver.js +268 -61
- package/lib/helper/clientscripts/PollyWebDriverExt.js +1 -1
- 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 +18 -9
- package/lib/helper/extras/PlaywrightRestartOpts.js +34 -23
- package/lib/helper/extras/Popup.js +1 -1
- package/lib/helper/extras/React.js +29 -30
- package/lib/helper/network/actions.js +29 -44
- package/lib/helper/network/utils.js +76 -83
- package/lib/helper/scripts/blurElement.js +6 -6
- package/lib/helper/scripts/focusElement.js +6 -6
- package/lib/helper/scripts/highlightElement.js +9 -9
- package/lib/helper/scripts/isElementClickable.js +34 -34
- package/lib/helper.js +2 -1
- package/lib/history.js +23 -20
- package/lib/hooks.js +10 -10
- package/lib/html.js +90 -100
- package/lib/index.js +48 -21
- package/lib/listener/config.js +19 -12
- package/lib/listener/emptyRun.js +6 -7
- package/lib/listener/enhancedGlobalRetry.js +6 -6
- package/lib/listener/exit.js +4 -3
- package/lib/listener/globalRetry.js +5 -5
- package/lib/listener/globalTimeout.js +30 -14
- package/lib/listener/helpers.js +39 -14
- package/lib/listener/mocha.js +3 -4
- package/lib/listener/result.js +4 -5
- package/lib/listener/retryEnhancer.js +3 -3
- package/lib/listener/steps.js +8 -7
- package/lib/listener/store.js +3 -3
- package/lib/locator.js +213 -192
- package/lib/mocha/asyncWrapper.js +105 -62
- package/lib/mocha/bdd.js +99 -13
- package/lib/mocha/cli.js +59 -26
- package/lib/mocha/factory.js +78 -19
- package/lib/mocha/featureConfig.js +1 -1
- package/lib/mocha/gherkin.js +56 -24
- package/lib/mocha/hooks.js +12 -3
- package/lib/mocha/index.js +13 -4
- package/lib/mocha/inject.js +22 -5
- package/lib/mocha/scenarioConfig.js +2 -2
- package/lib/mocha/suite.js +9 -2
- package/lib/mocha/test.js +10 -7
- package/lib/mocha/ui.js +28 -18
- package/lib/output.js +10 -8
- package/lib/parser.js +44 -44
- package/lib/pause.js +15 -16
- package/lib/plugin/analyze.js +19 -12
- package/lib/plugin/auth.js +20 -21
- package/lib/plugin/autoDelay.js +12 -8
- package/lib/plugin/coverage.js +28 -11
- package/lib/plugin/customLocator.js +3 -3
- package/lib/plugin/customReporter.js +3 -2
- package/lib/plugin/enhancedRetryFailedStep.js +6 -6
- package/lib/plugin/heal.js +14 -9
- package/lib/plugin/htmlReporter.js +724 -99
- package/lib/plugin/pageInfo.js +10 -10
- package/lib/plugin/pauseOnFail.js +4 -3
- package/lib/plugin/retryFailedStep.js +48 -5
- package/lib/plugin/screenshotOnFail.js +75 -37
- package/lib/plugin/stepByStepReport.js +14 -14
- package/lib/plugin/stepTimeout.js +4 -3
- package/lib/plugin/subtitles.js +6 -5
- package/lib/recorder.js +33 -14
- package/lib/rerun.js +69 -26
- package/lib/result.js +4 -4
- package/lib/retryCoordinator.js +2 -2
- package/lib/secret.js +18 -17
- package/lib/session.js +95 -89
- package/lib/step/base.js +7 -7
- package/lib/step/comment.js +2 -2
- package/lib/step/config.js +1 -1
- package/lib/step/func.js +3 -3
- package/lib/step/helper.js +3 -3
- package/lib/step/meta.js +5 -5
- package/lib/step/record.js +11 -11
- package/lib/step/retry.js +3 -3
- package/lib/step/section.js +3 -3
- package/lib/step.js +7 -10
- package/lib/steps.js +9 -5
- package/lib/store.js +1 -1
- package/lib/template/heal.js +1 -1
- 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 +17 -6
- package/lib/timeout.js +1 -7
- package/lib/transform.js +8 -8
- package/lib/translation.js +32 -18
- package/lib/utils/mask_data.js +4 -10
- package/lib/utils.js +66 -64
- package/lib/workerStorage.js +17 -17
- package/lib/workers.js +214 -84
- package/package.json +41 -37
- package/translations/de-DE.js +2 -2
- package/translations/fr-FR.js +2 -2
- package/translations/index.js +23 -10
- package/translations/it-IT.js +2 -2
- package/translations/ja-JP.js +2 -2
- package/translations/nl-NL.js +2 -2
- package/translations/pl-PL.js +2 -2
- package/translations/pt-BR.js +2 -2
- package/translations/ru-RU.js +2 -2
- package/translations/utils.js +4 -3
- package/translations/zh-CN.js +2 -2
- package/translations/zh-TW.js +2 -2
- package/typings/index.d.ts +5 -3
- package/typings/promiseBasedTypes.d.ts +4 -0
- package/typings/types.d.ts +4 -0
- package/lib/helper/Nightmare.js +0 -1486
- package/lib/helper/Protractor.js +0 -1840
- package/lib/helper/TestCafe.js +0 -1391
- package/lib/helper/clientscripts/nightmare.js +0 -213
- package/lib/helper/testcafe/testControllerHolder.js +0 -42
- package/lib/helper/testcafe/testcafe-utils.js +0 -61
- package/lib/plugin/allure.js +0 -15
- package/lib/plugin/autoLogin.js +0 -5
- package/lib/plugin/commentStep.js +0 -141
- package/lib/plugin/eachElement.js +0 -127
- package/lib/plugin/fakerTransform.js +0 -49
- package/lib/plugin/retryTo.js +0 -16
- package/lib/plugin/selenoid.js +0 -364
- package/lib/plugin/standardActingHelpers.js +0 -6
- package/lib/plugin/tryTo.js +0 -16
- package/lib/plugin/wdio.js +0 -247
- package/lib/within.js +0 -90
package/lib/helper/REST.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const { beautify } = require('../utils')
|
|
1
|
+
import axios from 'axios'
|
|
2
|
+
import Helper from '@codeceptjs/helper'
|
|
3
|
+
import { Agent } from 'https'
|
|
4
|
+
import Secret from '../secret.js'
|
|
5
|
+
import { beautify } from '../utils.js'
|
|
7
6
|
|
|
8
7
|
/**
|
|
9
8
|
* ## Configuration
|
|
@@ -143,7 +142,9 @@ class REST extends Helper {
|
|
|
143
142
|
|
|
144
143
|
static _checkRequirements() {
|
|
145
144
|
try {
|
|
146
|
-
|
|
145
|
+
// In ESM, axios is already imported at the top, so no need to check
|
|
146
|
+
// The import will fail at module load time if axios is missing
|
|
147
|
+
return null
|
|
147
148
|
} catch (e) {
|
|
148
149
|
return ['axios']
|
|
149
150
|
}
|
|
@@ -218,10 +219,18 @@ class REST extends Helper {
|
|
|
218
219
|
await this.config.onRequest(request)
|
|
219
220
|
}
|
|
220
221
|
|
|
221
|
-
|
|
222
|
+
try {
|
|
223
|
+
this.options.prettyPrintJson ? this.debugSection('Request', beautify(JSON.stringify(_debugRequest))) : this.debugSection('Request', JSON.stringify(_debugRequest))
|
|
224
|
+
} catch (e) {
|
|
225
|
+
console.log('[REST] Request:', JSON.stringify(_debugRequest))
|
|
226
|
+
}
|
|
222
227
|
|
|
223
228
|
if (this.options.printCurl) {
|
|
224
|
-
|
|
229
|
+
try {
|
|
230
|
+
this.debugSection('CURL Request', curlize(request))
|
|
231
|
+
} catch (e) {
|
|
232
|
+
console.log('[REST] CURL Request:', curlize(request))
|
|
233
|
+
}
|
|
225
234
|
}
|
|
226
235
|
|
|
227
236
|
let response
|
|
@@ -229,13 +238,21 @@ class REST extends Helper {
|
|
|
229
238
|
response = await this.axios(request)
|
|
230
239
|
} catch (err) {
|
|
231
240
|
if (!err.response) throw err
|
|
232
|
-
|
|
241
|
+
try {
|
|
242
|
+
this.debugSection('Response', `Response error. Status code: ${err.response.status}`)
|
|
243
|
+
} catch (e) {
|
|
244
|
+
console.log('[REST] Response error. Status code:', err.response.status)
|
|
245
|
+
}
|
|
233
246
|
response = err.response
|
|
234
247
|
}
|
|
235
248
|
if (this.config.onResponse) {
|
|
236
249
|
await this.config.onResponse(response)
|
|
237
250
|
}
|
|
238
|
-
|
|
251
|
+
try {
|
|
252
|
+
this.options.prettyPrintJson ? this.debugSection('Response', beautify(JSON.stringify(response.data))) : this.debugSection('Response', JSON.stringify(response.data))
|
|
253
|
+
} catch (e) {
|
|
254
|
+
console.log('[REST] Response:', JSON.stringify(response.data))
|
|
255
|
+
}
|
|
239
256
|
return response
|
|
240
257
|
}
|
|
241
258
|
|
|
@@ -448,7 +465,7 @@ class REST extends Helper {
|
|
|
448
465
|
}
|
|
449
466
|
}
|
|
450
467
|
|
|
451
|
-
|
|
468
|
+
export { REST as default }
|
|
452
469
|
|
|
453
470
|
function curlize(request) {
|
|
454
471
|
if (request.data?.constructor.name.toLowerCase() === 'formdata') return 'cURL is not printed as the request body is not a JSON'
|
package/lib/helper/WebDriver.js
CHANGED
|
@@ -1,32 +1,47 @@
|
|
|
1
1
|
let webdriverio
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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'
|
|
25
27
|
|
|
26
28
|
const SHADOW = 'shadow'
|
|
27
29
|
const webRoot = 'body'
|
|
28
30
|
let browserLogs = []
|
|
29
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
|
+
}
|
|
44
|
+
|
|
30
45
|
/**
|
|
31
46
|
* ## Configuration
|
|
32
47
|
*
|
|
@@ -428,7 +443,7 @@ const config = {}
|
|
|
428
443
|
class WebDriver extends Helper {
|
|
429
444
|
constructor(config) {
|
|
430
445
|
super(config)
|
|
431
|
-
webdriverio
|
|
446
|
+
// webdriverio will be loaded dynamically in _init method
|
|
432
447
|
|
|
433
448
|
// set defaults
|
|
434
449
|
this.root = webRoot
|
|
@@ -534,12 +549,26 @@ class WebDriver extends Helper {
|
|
|
534
549
|
|
|
535
550
|
static _checkRequirements() {
|
|
536
551
|
try {
|
|
537
|
-
|
|
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
|
|
538
555
|
} catch (e) {
|
|
539
556
|
return ['webdriverio@^6.12.1']
|
|
540
557
|
}
|
|
541
558
|
}
|
|
542
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
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
543
572
|
static _config() {
|
|
544
573
|
return [
|
|
545
574
|
{
|
|
@@ -647,6 +676,7 @@ class WebDriver extends Helper {
|
|
|
647
676
|
}
|
|
648
677
|
|
|
649
678
|
async _before() {
|
|
679
|
+
if (!webdriverio) await this._init()
|
|
650
680
|
this.context = this.root
|
|
651
681
|
if (this.options.restart && !this.options.manualStart) return this._startBrowser()
|
|
652
682
|
if (!this.isRunning && !this.options.manualStart) return this._startBrowser()
|
|
@@ -845,7 +875,7 @@ class WebDriver extends Helper {
|
|
|
845
875
|
* @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
|
|
846
876
|
*/
|
|
847
877
|
async _locate(locator, smartWait = false) {
|
|
848
|
-
if (
|
|
878
|
+
if (store.debugMode) smartWait = false
|
|
849
879
|
|
|
850
880
|
// special locator type for Shadow DOM
|
|
851
881
|
if (this._isShadowLocator(locator)) {
|
|
@@ -865,6 +895,17 @@ class WebDriver extends Helper {
|
|
|
865
895
|
return els
|
|
866
896
|
}
|
|
867
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)
|
|
907
|
+
}
|
|
908
|
+
|
|
868
909
|
if (!this.options.smartWait || !smartWait) {
|
|
869
910
|
if (this._isCustomLocator(locator)) {
|
|
870
911
|
const locatorObj = new Locator(locator)
|
|
@@ -935,6 +976,34 @@ class WebDriver extends Helper {
|
|
|
935
976
|
return findFields.call(this, locator).then(res => res)
|
|
936
977
|
}
|
|
937
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
|
|
1005
|
+
}
|
|
1006
|
+
|
|
938
1007
|
/**
|
|
939
1008
|
* {{> grabWebElements }}
|
|
940
1009
|
*
|
|
@@ -1085,6 +1154,75 @@ class WebDriver extends Helper {
|
|
|
1085
1154
|
await this.browser.buttonDown(2)
|
|
1086
1155
|
}
|
|
1087
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()
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1088
1226
|
/**
|
|
1089
1227
|
* {{> forceRightClick }}
|
|
1090
1228
|
*
|
|
@@ -1122,7 +1260,17 @@ class WebDriver extends Helper {
|
|
|
1122
1260
|
assertElementExists(res, field, 'Field')
|
|
1123
1261
|
const elem = usingFirstElement(res)
|
|
1124
1262
|
highlightActiveElement.call(this, elem)
|
|
1125
|
-
|
|
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
|
+
}
|
|
1126
1274
|
await elem.setValue(value.toString())
|
|
1127
1275
|
}
|
|
1128
1276
|
|
|
@@ -1227,7 +1375,8 @@ class WebDriver extends Helper {
|
|
|
1227
1375
|
const elementId = getElementId(elem)
|
|
1228
1376
|
highlightActiveElement.call(this, elem)
|
|
1229
1377
|
|
|
1230
|
-
const isSelected = await this.browser
|
|
1378
|
+
const isSelected = await isElementChecked(this.browser, elementId)
|
|
1379
|
+
|
|
1231
1380
|
if (isSelected) return Promise.resolve(true)
|
|
1232
1381
|
return this.browser[clickMethod](elementId)
|
|
1233
1382
|
}
|
|
@@ -1247,7 +1396,8 @@ class WebDriver extends Helper {
|
|
|
1247
1396
|
const elementId = getElementId(elem)
|
|
1248
1397
|
highlightActiveElement.call(this, elem)
|
|
1249
1398
|
|
|
1250
|
-
const isSelected = await this.browser
|
|
1399
|
+
const isSelected = await isElementChecked(this.browser, elementId)
|
|
1400
|
+
|
|
1251
1401
|
if (!isSelected) return Promise.resolve(true)
|
|
1252
1402
|
return this.browser[clickMethod](elementId)
|
|
1253
1403
|
}
|
|
@@ -1776,7 +1926,7 @@ class WebDriver extends Helper {
|
|
|
1776
1926
|
try {
|
|
1777
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
|
|
|
@@ -2305,10 +2455,14 @@ class WebDriver extends Helper {
|
|
|
2305
2455
|
res = usingFirstElement(res)
|
|
2306
2456
|
assertElementExists(res, locator)
|
|
2307
2457
|
|
|
2308
|
-
return res
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
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
|
+
})
|
|
2312
2466
|
}
|
|
2313
2467
|
|
|
2314
2468
|
/**
|
|
@@ -2330,6 +2484,7 @@ class WebDriver extends Helper {
|
|
|
2330
2484
|
{ timeout: aSec * 1000 },
|
|
2331
2485
|
)
|
|
2332
2486
|
.catch(e => {
|
|
2487
|
+
e = wrapError(e)
|
|
2333
2488
|
if (e.message.indexOf('timeout')) {
|
|
2334
2489
|
throw new Error(`expected url to include ${urlPart}, but found ${currUrl}`)
|
|
2335
2490
|
}
|
|
@@ -2355,6 +2510,7 @@ class WebDriver extends Helper {
|
|
|
2355
2510
|
})
|
|
2356
2511
|
}, aSec * 1000)
|
|
2357
2512
|
.catch(e => {
|
|
2513
|
+
e = wrapError(e)
|
|
2358
2514
|
if (e.message.indexOf('timeout')) {
|
|
2359
2515
|
throw new Error(`expected url to be ${urlPart}, but found ${currUrl}`)
|
|
2360
2516
|
}
|
|
@@ -2441,21 +2597,25 @@ class WebDriver extends Helper {
|
|
|
2441
2597
|
async waitNumberOfVisibleElements(locator, num, sec = null) {
|
|
2442
2598
|
const aSec = sec || this.options.waitForTimeoutInSeconds
|
|
2443
2599
|
|
|
2444
|
-
return this.browser
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
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
|
+
})
|
|
2459
2619
|
}
|
|
2460
2620
|
|
|
2461
2621
|
/**
|
|
@@ -2612,7 +2772,6 @@ class WebDriver extends Helper {
|
|
|
2612
2772
|
*/
|
|
2613
2773
|
async openNewTab(url = 'about:blank', windowName = null) {
|
|
2614
2774
|
const client = this.browser
|
|
2615
|
-
const crypto = require('crypto')
|
|
2616
2775
|
if (windowName == null) {
|
|
2617
2776
|
windowName = crypto.randomBytes(32).toString('hex')
|
|
2618
2777
|
}
|
|
@@ -2809,6 +2968,7 @@ async function findClickable(locator, locateFn) {
|
|
|
2809
2968
|
}
|
|
2810
2969
|
|
|
2811
2970
|
if (locator.isAccessibilityId() && !this.isWeb) return locateFn(locator, true)
|
|
2971
|
+
if (locator.isRole()) return locateFn(locator, true)
|
|
2812
2972
|
if (!locator.isFuzzy()) return locateFn(locator, true)
|
|
2813
2973
|
|
|
2814
2974
|
let els
|
|
@@ -2817,13 +2977,21 @@ async function findClickable(locator, locateFn) {
|
|
|
2817
2977
|
els = await locateFn(Locator.clickable.narrow(literal))
|
|
2818
2978
|
if (els.length) return els
|
|
2819
2979
|
|
|
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
|
+
}
|
|
2987
|
+
|
|
2820
2988
|
els = await locateFn(Locator.clickable.wide(literal))
|
|
2821
2989
|
if (els.length) return els
|
|
2822
2990
|
|
|
2823
2991
|
els = await locateFn(Locator.clickable.self(literal))
|
|
2824
2992
|
if (els.length) return els
|
|
2825
2993
|
|
|
2826
|
-
return locateFn(locator.value) // by css or xpath
|
|
2994
|
+
return await locateFn(locator.value) // by css or xpath
|
|
2827
2995
|
}
|
|
2828
2996
|
|
|
2829
2997
|
async function findFields(locator) {
|
|
@@ -2834,6 +3002,7 @@ async function findFields(locator) {
|
|
|
2834
3002
|
}
|
|
2835
3003
|
|
|
2836
3004
|
if (locator.isAccessibilityId() && !this.isWeb) return this._locate(locator, true)
|
|
3005
|
+
if (locator.isRole()) return this._locate(locator, true)
|
|
2837
3006
|
if (!locator.isFuzzy()) return this._locate(locator, true)
|
|
2838
3007
|
|
|
2839
3008
|
const literal = xpathLocator.literal(locator.value)
|
|
@@ -2845,7 +3014,8 @@ async function findFields(locator) {
|
|
|
2845
3014
|
|
|
2846
3015
|
els = await this._locate(Locator.field.byName(literal))
|
|
2847
3016
|
if (els.length) return els
|
|
2848
|
-
|
|
3017
|
+
|
|
3018
|
+
return await this._locate(locator.value) // by css or xpath
|
|
2849
3019
|
}
|
|
2850
3020
|
|
|
2851
3021
|
async function proceedSeeField(assertType, field, value) {
|
|
@@ -2871,13 +3041,19 @@ async function proceedSeeField(assertType, field, value) {
|
|
|
2871
3041
|
}
|
|
2872
3042
|
}
|
|
2873
3043
|
|
|
2874
|
-
const proceedSingle = el =>
|
|
2875
|
-
el.getValue()
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
3044
|
+
const proceedSingle = async el => {
|
|
3045
|
+
let res = await el.getValue()
|
|
3046
|
+
|
|
3047
|
+
if (res === null) {
|
|
3048
|
+
res = await el.getText()
|
|
3049
|
+
}
|
|
3050
|
+
|
|
3051
|
+
if (res === null || res === undefined) {
|
|
3052
|
+
throw new Error(`Element ${el.selector} has no value attribute`)
|
|
3053
|
+
}
|
|
3054
|
+
|
|
3055
|
+
stringIncludes(`fields by ${field}`)[assertType](value, res)
|
|
3056
|
+
}
|
|
2881
3057
|
|
|
2882
3058
|
const filterBySelected = async elements => filterAsync(elements, async el => this.browser.isElementSelected(getElementId(el)))
|
|
2883
3059
|
|
|
@@ -2939,10 +3115,31 @@ async function proceedSeeCheckbox(assertType, field) {
|
|
|
2939
3115
|
const res = await findFields.call(this, field)
|
|
2940
3116
|
assertElementExists(res, field, 'Field')
|
|
2941
3117
|
|
|
2942
|
-
const selected = await forEachAsync(res, async el =>
|
|
3118
|
+
const selected = await forEachAsync(res, async el => {
|
|
3119
|
+
const elementId = getElementId(el)
|
|
3120
|
+
return isElementChecked(this.browser, elementId)
|
|
3121
|
+
})
|
|
3122
|
+
|
|
2943
3123
|
return truth(`checkable field "${field}"`, 'to be checked')[assertType](selected)
|
|
2944
3124
|
}
|
|
2945
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
|
+
}
|
|
3133
|
+
|
|
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
|
|
3141
|
+
}
|
|
3142
|
+
|
|
2946
3143
|
async function findCheckable(locator, locateFn) {
|
|
2947
3144
|
let els
|
|
2948
3145
|
locator = new Locator(locator)
|
|
@@ -2952,15 +3149,25 @@ async function findCheckable(locator, locateFn) {
|
|
|
2952
3149
|
}
|
|
2953
3150
|
|
|
2954
3151
|
if (locator.isAccessibilityId() && !this.isWeb) return locateFn(locator, true)
|
|
3152
|
+
if (locator.isRole()) return locateFn(locator, true)
|
|
2955
3153
|
if (!locator.isFuzzy()) return locateFn(locator, true)
|
|
2956
3154
|
|
|
2957
3155
|
const literal = xpathLocator.literal(locator.value)
|
|
2958
3156
|
els = await locateFn(Locator.checkable.byText(literal))
|
|
2959
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
|
+
}
|
|
3166
|
+
|
|
2960
3167
|
els = await locateFn(Locator.checkable.byName(literal))
|
|
2961
3168
|
if (els.length) return els
|
|
2962
3169
|
|
|
2963
|
-
return locateFn(locator.value) // by css or xpath
|
|
3170
|
+
return await locateFn(locator.value) // by css or xpath
|
|
2964
3171
|
}
|
|
2965
3172
|
|
|
2966
3173
|
function withStrictLocator(locator) {
|
|
@@ -3169,4 +3376,4 @@ function logEvents(event) {
|
|
|
3169
3376
|
browserLogs.push(event.text) // add log message to the array
|
|
3170
3377
|
}
|
|
3171
3378
|
|
|
3172
|
-
|
|
3379
|
+
export { WebDriver as default }
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
function ConnectionRefused(err) {
|
|
2
|
-
this.message = "Can't connect to WebDriver.\n"
|
|
3
|
-
this.message += `${err}\n\n
|
|
4
|
-
this.message += 'Please make sure Selenium Server is running and accessible'
|
|
5
|
-
this.stack = err.stack
|
|
2
|
+
this.message = "Can't connect to WebDriver.\n"
|
|
3
|
+
this.message += `${err}\n\n`
|
|
4
|
+
this.message += 'Please make sure Selenium Server is running and accessible'
|
|
5
|
+
this.stack = err.stack
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
ConnectionRefused.prototype = Object.create(Error.prototype)
|
|
8
|
+
ConnectionRefused.prototype = Object.create(Error.prototype)
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
export default ConnectionRefused
|
|
@@ -1,38 +1,33 @@
|
|
|
1
|
-
|
|
1
|
+
import Locator from '../../locator.js'
|
|
2
2
|
|
|
3
|
-
const prefixMessage = 'Element'
|
|
3
|
+
const prefixMessage = 'Element'
|
|
4
4
|
|
|
5
5
|
function seeElementError(locator) {
|
|
6
6
|
if (typeof locator === 'object') {
|
|
7
|
-
locator = JSON.stringify(locator)
|
|
7
|
+
locator = JSON.stringify(locator)
|
|
8
8
|
}
|
|
9
|
-
throw new Error(`${prefixMessage} "${
|
|
9
|
+
throw new Error(`${prefixMessage} "${new Locator(locator)}" is still visible on page.`)
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
function seeElementInDOMError(locator) {
|
|
13
13
|
if (typeof locator === 'object') {
|
|
14
|
-
locator = JSON.stringify(locator)
|
|
14
|
+
locator = JSON.stringify(locator)
|
|
15
15
|
}
|
|
16
|
-
throw new Error(`${prefixMessage} "${
|
|
16
|
+
throw new Error(`${prefixMessage} "${new Locator(locator)}" is still seen in DOM.`)
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
function dontSeeElementError(locator) {
|
|
20
20
|
if (typeof locator === 'object') {
|
|
21
|
-
locator = JSON.stringify(locator)
|
|
21
|
+
locator = JSON.stringify(locator)
|
|
22
22
|
}
|
|
23
|
-
throw new Error(`${prefixMessage} "${
|
|
23
|
+
throw new Error(`${prefixMessage} "${new Locator(locator)}" is not visible on page.`)
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
function dontSeeElementInDOMError(locator) {
|
|
27
27
|
if (typeof locator === 'object') {
|
|
28
|
-
locator = JSON.stringify(locator)
|
|
28
|
+
locator = JSON.stringify(locator)
|
|
29
29
|
}
|
|
30
|
-
throw new Error(`${prefixMessage} "${
|
|
30
|
+
throw new Error(`${prefixMessage} "${new Locator(locator)}" is not seen in DOM.`)
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
seeElementError,
|
|
35
|
-
dontSeeElementError,
|
|
36
|
-
seeElementInDOMError,
|
|
37
|
-
dontSeeElementInDOMError,
|
|
38
|
-
};
|
|
33
|
+
export { seeElementError, dontSeeElementError, seeElementInDOMError, dontSeeElementInDOMError }
|