selenium-webdriver 4.0.0-beta.2 → 4.0.0-rc-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGES.md +45 -0
- package/README.md +4 -2
- package/chrome.js +3 -4
- package/chromium.js +38 -2
- package/devtools/networkinterceptor.js +89 -0
- package/edge.js +14 -20
- package/firefox.js +18 -1
- package/http/index.js +25 -3
- package/ie.js +8 -8
- package/index.js +24 -8
- package/io/exec.js +5 -4
- package/io/index.js +8 -9
- package/io/zip.js +5 -6
- package/lib/atoms/get-attribute.js +25 -25
- package/lib/by.js +39 -20
- package/lib/capabilities.js +10 -1
- package/lib/command.js +0 -15
- package/lib/http.js +2 -30
- package/lib/input.js +6 -301
- package/lib/promise.js +4 -16
- package/lib/until.js +14 -16
- package/lib/webdriver.js +128 -44
- package/opera.js +3 -3
- package/package.json +11 -11
- package/remote/index.js +8 -9
- package/safari.js +3 -3
- package/testing/index.js +7 -6
- package/lib/actions.js +0 -627
package/lib/input.js
CHANGED
|
@@ -20,14 +20,8 @@
|
|
|
20
20
|
/**
|
|
21
21
|
* @fileoverview Defines types related to user input with the WebDriver API.
|
|
22
22
|
*/
|
|
23
|
-
|
|
24
|
-
const promise = require('./promise')
|
|
25
23
|
const { Command, Name } = require('./command')
|
|
26
|
-
const {
|
|
27
|
-
InvalidArgumentError,
|
|
28
|
-
UnknownCommandError,
|
|
29
|
-
UnsupportedOperationError,
|
|
30
|
-
} = require('./error')
|
|
24
|
+
const { InvalidArgumentError } = require('./error')
|
|
31
25
|
|
|
32
26
|
/**
|
|
33
27
|
* Enumeration of the buttons used in the advanced interactions API.
|
|
@@ -489,67 +483,6 @@ Pointer.Type = {
|
|
|
489
483
|
* | Keyboard | pause(100) | keyDown(SHIFT) |
|
|
490
484
|
* | Mouse | move({duration: 300}) | |
|
|
491
485
|
*
|
|
492
|
-
*
|
|
493
|
-
* #### Bridge Mode
|
|
494
|
-
*
|
|
495
|
-
* As of January 2018, only Firefox natively supports this API. For other
|
|
496
|
-
* browsers, you may either use the
|
|
497
|
-
* {@link ./actions.LegacyActionSequence LegacyActionSequence} class, _or_ you
|
|
498
|
-
* can put the Actions class into bridge mode by passing `{bridge: true}` on
|
|
499
|
-
* creation:
|
|
500
|
-
*
|
|
501
|
-
* const actions = driver.actions({bridge: true});
|
|
502
|
-
* await actions.click(element).sendKeys('abc').perform();
|
|
503
|
-
*
|
|
504
|
-
* In bridge mode, {@link #perform perform()} will first attempt to execute the
|
|
505
|
-
* configured action sequence using the W3C action protocol. If this is rejected
|
|
506
|
-
* by the remote end, the sequence will be translated to and executed against
|
|
507
|
-
* the legacy protocol.
|
|
508
|
-
*
|
|
509
|
-
* Bridge mode __is not enabled by default__ as there are several notable
|
|
510
|
-
* differences between W3C-specified and legacy protocols. Care must be
|
|
511
|
-
* taken to configure your action sequences to account for these differences:
|
|
512
|
-
*
|
|
513
|
-
* 1. For W3C actions, the entire action sequence is executed in a single
|
|
514
|
-
* call to the remote end. For legacy sequences, multiple calls must be
|
|
515
|
-
* made for each step in the sequence. This introduces additional latency
|
|
516
|
-
* which may impact how the browser responds to the emulated user actions.
|
|
517
|
-
*
|
|
518
|
-
* 2. For the legacy actions, {@linkplain #pause pauses} are handled _locally_.
|
|
519
|
-
*
|
|
520
|
-
* 3. For legacy actions, a {@linkplain #keyDown keyDown()} for a
|
|
521
|
-
* _non-modifier key_ **must** be followed by a {@linkplain #keyUP keyUp()}
|
|
522
|
-
* for the same key. This will be handled for you if you use the
|
|
523
|
-
* {@linkplain #sendKeys sendKeys()} method.
|
|
524
|
-
*
|
|
525
|
-
* 4. Mouse movements may not be specified relative to
|
|
526
|
-
* {@linkplain ./input.Origin.VIEWPORT Origin.VIEWPORT}.
|
|
527
|
-
* All movements must be relative to an element or the mouse's current
|
|
528
|
-
* position ({@linkplain ./input.Origin.POINTER Origin.POINTER}).
|
|
529
|
-
* The {@linkplain #move move()} method defaults to viewport relative
|
|
530
|
-
* offsets, so you must always specify an appropriate origin in bridge mode:
|
|
531
|
-
*
|
|
532
|
-
* driver.actions({bridge: true})
|
|
533
|
-
* .move({x: 0, y: 0, origin: Origin.POINTER})
|
|
534
|
-
* .perform();
|
|
535
|
-
* driver.actions({bridge: true})
|
|
536
|
-
* .move({x: 0, y: 0, origin: someWebElement})
|
|
537
|
-
* .perform();
|
|
538
|
-
*
|
|
539
|
-
* 5. The legacy protocol does not support specifying the duration of a
|
|
540
|
-
* {@linkplain #move mouse movement}; any specified duration _is ignored_
|
|
541
|
-
* when translating actions to the legacy protocol.
|
|
542
|
-
*
|
|
543
|
-
* 6. For W3C actions, move offsets relative to a
|
|
544
|
-
* {@linkplain ./webdriver.WebElement WebElement} are interpreted relative
|
|
545
|
-
* to the center of an element's _first_ [client rect] in the viewport. For
|
|
546
|
-
* legacy actions, element offsets are relative to the top-left corner of
|
|
547
|
-
* the element's [bounding client rect]. When translating actions to the
|
|
548
|
-
* legacy protocol in bridge mode, an extra command must be inserted to
|
|
549
|
-
* translate move offsets from one frame of reference to the other. This
|
|
550
|
-
* extra command contributes to the overall latency issue outlined in
|
|
551
|
-
* point 1.
|
|
552
|
-
*
|
|
553
486
|
* [client rect]: https://developer.mozilla.org/en-US/docs/Web/API/Element/getClientRects
|
|
554
487
|
* [bounding client rect]: https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect
|
|
555
488
|
*
|
|
@@ -560,26 +493,16 @@ class Actions {
|
|
|
560
493
|
/**
|
|
561
494
|
* @param {!Executor} executor The object to execute the configured
|
|
562
495
|
* actions with.
|
|
563
|
-
* @param {{async: (boolean|undefined)
|
|
564
|
-
* bridge: (boolean|undefined)}} options Options for this action
|
|
496
|
+
* @param {{async: (boolean|undefined)}} options Options for this action
|
|
565
497
|
* sequence (see class description for details).
|
|
566
498
|
*/
|
|
567
|
-
constructor(executor, { async = false
|
|
568
|
-
if (async && bridge) {
|
|
569
|
-
throw new InvalidArgumentError(
|
|
570
|
-
'Async sequences not supported with bridge mode'
|
|
571
|
-
)
|
|
572
|
-
}
|
|
573
|
-
|
|
499
|
+
constructor(executor, { async = false } = {}) {
|
|
574
500
|
/** @private @const */
|
|
575
501
|
this.executor_ = executor
|
|
576
502
|
|
|
577
503
|
/** @private @const */
|
|
578
504
|
this.sync_ = !async
|
|
579
505
|
|
|
580
|
-
/** @private @const */
|
|
581
|
-
this.bridge_ = !!bridge
|
|
582
|
-
|
|
583
506
|
/** @private @const */
|
|
584
507
|
this.keyboard_ = new Keyboard('default keyboard')
|
|
585
508
|
|
|
@@ -952,20 +875,9 @@ class Actions {
|
|
|
952
875
|
return Promise.resolve()
|
|
953
876
|
}
|
|
954
877
|
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
)
|
|
959
|
-
} catch (ex) {
|
|
960
|
-
if (
|
|
961
|
-
this.bridge_ &&
|
|
962
|
-
(ex instanceof UnknownCommandError ||
|
|
963
|
-
ex instanceof UnsupportedOperationError)
|
|
964
|
-
) {
|
|
965
|
-
return executeLegacy(this.executor_, this.sequences_)
|
|
966
|
-
}
|
|
967
|
-
throw ex
|
|
968
|
-
}
|
|
878
|
+
await this.executor_.execute(
|
|
879
|
+
new Command(Name.ACTIONS).setParameter('actions', _actions)
|
|
880
|
+
)
|
|
969
881
|
}
|
|
970
882
|
}
|
|
971
883
|
|
|
@@ -980,213 +892,6 @@ function isIdle(actions) {
|
|
|
980
892
|
)
|
|
981
893
|
}
|
|
982
894
|
|
|
983
|
-
const MODIFIER_KEYS = new Set([Key.ALT, Key.CONTROL, Key.SHIFT, Key.COMMAND])
|
|
984
|
-
|
|
985
|
-
/**
|
|
986
|
-
* @param {!Executor} executor
|
|
987
|
-
* @param {!Map<!Device, !Array<!Action>>} sequences
|
|
988
|
-
* @return {!Promise<void>}
|
|
989
|
-
* @suppress {deprecated} Ignore warnings about using LegacyActionSequence.
|
|
990
|
-
*/
|
|
991
|
-
async function executeLegacy(executor, sequences) {
|
|
992
|
-
let maxLength = 0
|
|
993
|
-
sequences.forEach((seq) => (maxLength = Math.max(maxLength, seq.length)))
|
|
994
|
-
|
|
995
|
-
const actions = []
|
|
996
|
-
for (let i = 0; i < maxLength; i++) {
|
|
997
|
-
let next
|
|
998
|
-
for (const device of sequences.keys()) {
|
|
999
|
-
const seq = sequences.get(device)
|
|
1000
|
-
|
|
1001
|
-
if (
|
|
1002
|
-
device instanceof Pointer &&
|
|
1003
|
-
device.pointerType_ !== Pointer.Type.MOUSE
|
|
1004
|
-
) {
|
|
1005
|
-
throw new UnsupportedOperationError(
|
|
1006
|
-
`${device.pointerType_} pointer not supported in bridge mode`
|
|
1007
|
-
)
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
const action = seq[i]
|
|
1011
|
-
if (!action || (action.type === Action.Type.PAUSE && !action.duration)) {
|
|
1012
|
-
continue
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
// - If we've already found an action for this tick:
|
|
1016
|
-
// - If we have two pauses, use the one with a longer duration
|
|
1017
|
-
// - If one is a pause and the other isn't, use the non-pause
|
|
1018
|
-
// - Otherwise, two non-pauses is an error.
|
|
1019
|
-
// - Otherwise, we haven't selected an action yet.
|
|
1020
|
-
if (next) {
|
|
1021
|
-
if (next.type === Action.Type.PAUSE) {
|
|
1022
|
-
if (action.type === Action.Type.PAUSE) {
|
|
1023
|
-
next = next.duration > action.duration ? next : action
|
|
1024
|
-
} else {
|
|
1025
|
-
next = action
|
|
1026
|
-
}
|
|
1027
|
-
} else if (action.type !== Action.Type.PAUSE) {
|
|
1028
|
-
throw new UnsupportedOperationError(
|
|
1029
|
-
'parallel actions not supported in bridge mode'
|
|
1030
|
-
)
|
|
1031
|
-
}
|
|
1032
|
-
} else {
|
|
1033
|
-
next = action
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
if (action.type === Action.Type.KEY_DOWN) {
|
|
1037
|
-
// If this action is a keydown for a non-modifier key, the next action
|
|
1038
|
-
// must be a keyup for the same key, otherwise it cannot be translated
|
|
1039
|
-
// to the legacy action API.
|
|
1040
|
-
if (!MODIFIER_KEYS.has(action.value)) {
|
|
1041
|
-
const nextAction = seq[i + 1]
|
|
1042
|
-
if (
|
|
1043
|
-
!nextAction ||
|
|
1044
|
-
nextAction.type !== Action.Type.KEY_UP ||
|
|
1045
|
-
nextAction.value !== action.value
|
|
1046
|
-
) {
|
|
1047
|
-
throw new UnsupportedOperationError(
|
|
1048
|
-
`in bridge mode, keydown for <${action.value}> must be followed` +
|
|
1049
|
-
' by a keyup for the same key'
|
|
1050
|
-
)
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
} else if (
|
|
1054
|
-
action.type === Action.Type.KEY_UP &&
|
|
1055
|
-
!MODIFIER_KEYS.has(action.value)
|
|
1056
|
-
) {
|
|
1057
|
-
next = null
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
if (next) {
|
|
1062
|
-
actions.push(next)
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
|
-
for (let i = 0; i < actions.length; i++) {
|
|
1067
|
-
const action = actions[i]
|
|
1068
|
-
switch (action.type) {
|
|
1069
|
-
case Action.Type.PAUSE: {
|
|
1070
|
-
await promise.delayed(action.duration || 0)
|
|
1071
|
-
break
|
|
1072
|
-
}
|
|
1073
|
-
case Action.Type.KEY_DOWN: {
|
|
1074
|
-
const keys = [action.value]
|
|
1075
|
-
if (!MODIFIER_KEYS.has(action.value)) {
|
|
1076
|
-
// eslint-disable-next-line no-inner-declarations
|
|
1077
|
-
function nextIsKeyDown() {
|
|
1078
|
-
const next = actions[i + 1]
|
|
1079
|
-
return (
|
|
1080
|
-
!!next &&
|
|
1081
|
-
next.type === Action.Type.KEY_DOWN &&
|
|
1082
|
-
!MODIFIER_KEYS.has(next.value)
|
|
1083
|
-
)
|
|
1084
|
-
}
|
|
1085
|
-
for (; nextIsKeyDown(); i++) {
|
|
1086
|
-
keys.push(actions[i + 1].value)
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
|
-
await executor.execute(
|
|
1090
|
-
new Command(Name.LEGACY_ACTION_SEND_KEYS).setParameter('value', keys)
|
|
1091
|
-
)
|
|
1092
|
-
break
|
|
1093
|
-
}
|
|
1094
|
-
case Action.Type.KEY_UP: {
|
|
1095
|
-
await executor.execute(
|
|
1096
|
-
new Command(Name.LEGACY_ACTION_SEND_KEYS).setParameter('value', [
|
|
1097
|
-
action.value,
|
|
1098
|
-
])
|
|
1099
|
-
)
|
|
1100
|
-
break
|
|
1101
|
-
}
|
|
1102
|
-
case Action.Type.POINTER_DOWN: {
|
|
1103
|
-
// eslint-disable-next-line no-inner-declarations
|
|
1104
|
-
function isClick(startAt) {
|
|
1105
|
-
const first = actions[startAt]
|
|
1106
|
-
const second = actions[startAt + 1]
|
|
1107
|
-
return (
|
|
1108
|
-
!!first &&
|
|
1109
|
-
!!second &&
|
|
1110
|
-
first.type === Action.Type.POINTER_DOWN &&
|
|
1111
|
-
second.type === Action.Type.POINTER_UP &&
|
|
1112
|
-
first.button === second.button
|
|
1113
|
-
)
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
// eslint-disable-next-line no-inner-declarations
|
|
1117
|
-
function isDoubleClick(startAt) {
|
|
1118
|
-
return (
|
|
1119
|
-
isClick(startAt) &&
|
|
1120
|
-
isClick(startAt + 2) &&
|
|
1121
|
-
actions[startAt].button === actions[(startAt = 2)].button
|
|
1122
|
-
)
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
let cmd
|
|
1126
|
-
if (isDoubleClick(i)) {
|
|
1127
|
-
i += 3 // Consume the pointer up/down/up.
|
|
1128
|
-
cmd = Name.LEGACY_ACTION_DOUBLE_CLICK
|
|
1129
|
-
} else if (isClick(i)) {
|
|
1130
|
-
i++ // Consume the pointer-up.
|
|
1131
|
-
cmd = Name.LEGACY_ACTION_CLICK
|
|
1132
|
-
} else {
|
|
1133
|
-
cmd = Name.LEGACY_ACTION_MOUSE_DOWN
|
|
1134
|
-
}
|
|
1135
|
-
await executor.execute(
|
|
1136
|
-
new Command(cmd).setParameter('button', action.button)
|
|
1137
|
-
)
|
|
1138
|
-
break
|
|
1139
|
-
}
|
|
1140
|
-
case Action.Type.POINTER_UP: {
|
|
1141
|
-
await executor.execute(
|
|
1142
|
-
new Command(Name.LEGACY_ACTION_MOUSE_UP).setParameter(
|
|
1143
|
-
'button',
|
|
1144
|
-
action.button
|
|
1145
|
-
)
|
|
1146
|
-
)
|
|
1147
|
-
break
|
|
1148
|
-
}
|
|
1149
|
-
case Action.Type.POINTER_MOVE: {
|
|
1150
|
-
if (action.origin === Origin.VIEWPORT) {
|
|
1151
|
-
throw new UnsupportedOperationError(
|
|
1152
|
-
`pointer movements relative to ${Origin.VIEWPORT} are not` +
|
|
1153
|
-
' supported in bridge mode'
|
|
1154
|
-
)
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
let x = action.x
|
|
1158
|
-
let y = action.y
|
|
1159
|
-
const cmd = new Command(Name.LEGACY_ACTION_MOUSE_MOVE)
|
|
1160
|
-
if (action.origin && action.origin !== Origin.POINTER) {
|
|
1161
|
-
const el = /** @type {!./webdriver.WebElement} */ (action.origin)
|
|
1162
|
-
|
|
1163
|
-
// Need to translate frame of reference from center of element's first
|
|
1164
|
-
// client rect to the top-left of its bounding client rect. See:
|
|
1165
|
-
// https://w3c.github.io/webdriver/webdriver-spec.html#dfn-center-point
|
|
1166
|
-
let diff = await executor.execute(
|
|
1167
|
-
new Command(Name.EXECUTE_SCRIPT)
|
|
1168
|
-
.setParameter('script', INTERNAL_COMPUTE_OFFSET_SCRIPT)
|
|
1169
|
-
.setParameter('args', [el])
|
|
1170
|
-
)
|
|
1171
|
-
x += diff[0]
|
|
1172
|
-
y += diff[1]
|
|
1173
|
-
|
|
1174
|
-
const id = await el.getId()
|
|
1175
|
-
cmd.setParameter('element', id)
|
|
1176
|
-
}
|
|
1177
|
-
cmd.setParameter('xoffset', x).setParameter('yoffset', y)
|
|
1178
|
-
await executor.execute(cmd)
|
|
1179
|
-
break
|
|
1180
|
-
}
|
|
1181
|
-
default: {
|
|
1182
|
-
throw new UnsupportedOperationError(
|
|
1183
|
-
`${action.type} actions not supported in bridge mode`
|
|
1184
|
-
)
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
1189
|
-
|
|
1190
895
|
/**
|
|
1191
896
|
* Script used to compute the offset from the center of a DOM element's first
|
|
1192
897
|
* client rect from the top-left corner of the element's bounding client rect.
|
package/lib/promise.js
CHANGED
|
@@ -30,17 +30,7 @@
|
|
|
30
30
|
* @return {boolean} Whether the value is a promise.
|
|
31
31
|
*/
|
|
32
32
|
function isPromise(value) {
|
|
33
|
-
|
|
34
|
-
// Use array notation so the Closure compiler does not obfuscate away our
|
|
35
|
-
// contract.
|
|
36
|
-
return (
|
|
37
|
-
value &&
|
|
38
|
-
(typeof value === 'object' || typeof value === 'function') &&
|
|
39
|
-
typeof value['then'] === 'function'
|
|
40
|
-
)
|
|
41
|
-
} catch (ex) {
|
|
42
|
-
return false
|
|
43
|
-
}
|
|
33
|
+
return Object.prototype.toString.call(value) === '[object Promise]'
|
|
44
34
|
}
|
|
45
35
|
|
|
46
36
|
/**
|
|
@@ -50,9 +40,7 @@ function isPromise(value) {
|
|
|
50
40
|
* @return {!Promise<void>} The promise.
|
|
51
41
|
*/
|
|
52
42
|
function delayed(ms) {
|
|
53
|
-
return new Promise((resolve) =>
|
|
54
|
-
setTimeout(resolve, ms)
|
|
55
|
-
})
|
|
43
|
+
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
56
44
|
}
|
|
57
45
|
|
|
58
46
|
/**
|
|
@@ -199,8 +187,8 @@ async function filter(array, fn, self = undefined) {
|
|
|
199
187
|
|
|
200
188
|
for (let i = 0; i < n; i++) {
|
|
201
189
|
if (i in arr) {
|
|
202
|
-
|
|
203
|
-
|
|
190
|
+
const value = arr[i]
|
|
191
|
+
const include = await fn.call(self, value, i, arr)
|
|
204
192
|
if (include) {
|
|
205
193
|
values[valuesLength++] = value
|
|
206
194
|
}
|
package/lib/until.js
CHANGED
|
@@ -137,14 +137,13 @@ exports.alertIsPresent = function alertIsPresent() {
|
|
|
137
137
|
* @return {!Condition<boolean>} The new condition.
|
|
138
138
|
*/
|
|
139
139
|
exports.titleIs = function titleIs(title) {
|
|
140
|
-
return new Condition(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
)
|
|
140
|
+
return new Condition('for title to be ' + JSON.stringify(title), function (
|
|
141
|
+
driver
|
|
142
|
+
) {
|
|
143
|
+
return driver.getTitle().then(function (t) {
|
|
144
|
+
return t === title
|
|
145
|
+
})
|
|
146
|
+
})
|
|
148
147
|
}
|
|
149
148
|
|
|
150
149
|
/**
|
|
@@ -189,14 +188,13 @@ exports.titleMatches = function titleMatches(regex) {
|
|
|
189
188
|
* @return {!Condition<boolean>} The new condition.
|
|
190
189
|
*/
|
|
191
190
|
exports.urlIs = function urlIs(url) {
|
|
192
|
-
return new Condition(
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
)
|
|
191
|
+
return new Condition('for URL to be ' + JSON.stringify(url), function (
|
|
192
|
+
driver
|
|
193
|
+
) {
|
|
194
|
+
return driver.getCurrentUrl().then(function (u) {
|
|
195
|
+
return u === url
|
|
196
|
+
})
|
|
197
|
+
})
|
|
200
198
|
}
|
|
201
199
|
|
|
202
200
|
/**
|
package/lib/webdriver.js
CHANGED
|
@@ -36,19 +36,21 @@ const http = require('../http/index')
|
|
|
36
36
|
const fs = require('fs')
|
|
37
37
|
const { Capabilities } = require('./capabilities')
|
|
38
38
|
const path = require('path')
|
|
39
|
+
const { NoSuchElementError } = require('./error')
|
|
39
40
|
|
|
40
41
|
// Capability names that are defined in the W3C spec.
|
|
41
42
|
const W3C_CAPABILITY_NAMES = new Set([
|
|
42
43
|
'acceptInsecureCerts',
|
|
43
44
|
'browserName',
|
|
44
45
|
'browserVersion',
|
|
45
|
-
'platformName',
|
|
46
46
|
'pageLoadStrategy',
|
|
47
|
+
'platformName',
|
|
47
48
|
'proxy',
|
|
48
49
|
'setWindowRect',
|
|
49
|
-
'timeouts',
|
|
50
50
|
'strictFileInteractability',
|
|
51
|
+
'timeouts',
|
|
51
52
|
'unhandledPromptBehavior',
|
|
53
|
+
'webSocketUrl'
|
|
52
54
|
])
|
|
53
55
|
|
|
54
56
|
/**
|
|
@@ -649,7 +651,6 @@ function filterNonW3CCaps(capabilities) {
|
|
|
649
651
|
* @implements {IWebDriver}
|
|
650
652
|
*/
|
|
651
653
|
class WebDriver {
|
|
652
|
-
|
|
653
654
|
/**
|
|
654
655
|
* @param {!(./session.Session|IThenable<!./session.Session>)} session Either
|
|
655
656
|
* a known session or a promise that will be resolved to a session.
|
|
@@ -972,16 +973,46 @@ class WebDriver {
|
|
|
972
973
|
/** @override */
|
|
973
974
|
findElement(locator) {
|
|
974
975
|
let id
|
|
975
|
-
|
|
976
|
+
let cmd = null
|
|
977
|
+
|
|
978
|
+
if (locator instanceof RelativeBy) {
|
|
979
|
+
cmd = new command.Command(
|
|
980
|
+
command.Name.FIND_ELEMENTS_RELATIVE
|
|
981
|
+
).setParameter('args', locator.marshall())
|
|
982
|
+
} else {
|
|
983
|
+
locator = by.checkedLocator(locator)
|
|
984
|
+
}
|
|
985
|
+
|
|
976
986
|
if (typeof locator === 'function') {
|
|
977
987
|
id = this.findElementInternal_(locator, this)
|
|
978
|
-
|
|
979
|
-
|
|
988
|
+
return new WebElementPromise(this, id)
|
|
989
|
+
} else if (cmd === null) {
|
|
990
|
+
cmd = new command.Command(command.Name.FIND_ELEMENT)
|
|
980
991
|
.setParameter('using', locator.using)
|
|
981
992
|
.setParameter('value', locator.value)
|
|
982
|
-
id = this.execute(cmd)
|
|
983
993
|
}
|
|
984
|
-
|
|
994
|
+
|
|
995
|
+
id = this.execute(cmd)
|
|
996
|
+
if (locator instanceof RelativeBy) {
|
|
997
|
+
return this.normalize_(id)
|
|
998
|
+
} else {
|
|
999
|
+
return new WebElementPromise(this, id)
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
/**
|
|
1004
|
+
* @param {!Function} webElementPromise The webElement in unresolved state
|
|
1005
|
+
* @return {!Promise<!WebElement>} First single WebElement from array of resolved promises
|
|
1006
|
+
*/
|
|
1007
|
+
async normalize_(webElementPromise) {
|
|
1008
|
+
let result = await webElementPromise
|
|
1009
|
+
if (result.length === 0) {
|
|
1010
|
+
throw new NoSuchElementError(
|
|
1011
|
+
'Cannot locate an element with provided parameters'
|
|
1012
|
+
)
|
|
1013
|
+
} else {
|
|
1014
|
+
return result[0]
|
|
1015
|
+
}
|
|
985
1016
|
}
|
|
986
1017
|
|
|
987
1018
|
/**
|
|
@@ -1166,7 +1197,7 @@ class WebDriver {
|
|
|
1166
1197
|
caps['map_'].get(this.VENDOR_COMMAND_PREFIX + ':chromeOptions') ||
|
|
1167
1198
|
caps['map_'].get(this.VENDOR_CAPABILITY_PREFIX + ':edgeOptions') ||
|
|
1168
1199
|
caps['map_'].get('moz:debuggerAddress') ||
|
|
1169
|
-
new Map()
|
|
1200
|
+
new Map()
|
|
1170
1201
|
const debuggerUrl = seCdp || vendorInfo['debuggerAddress'] || vendorInfo
|
|
1171
1202
|
this._wsUrl = await this.getWsUrl(debuggerUrl, target)
|
|
1172
1203
|
|
|
@@ -1199,23 +1230,25 @@ class WebDriver {
|
|
|
1199
1230
|
if (target && cdpTargets.indexOf(target.toLowerCase()) === -1) {
|
|
1200
1231
|
throw new error.InvalidArgumentError('invalid target value')
|
|
1201
1232
|
}
|
|
1202
|
-
|
|
1203
|
-
if (
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1233
|
+
|
|
1234
|
+
if (debuggerAddress.match(/\/se\/cdp/)) {
|
|
1235
|
+
if (debuggerAddress.match("ws:\/\/", "http:\/\/")) {
|
|
1236
|
+
return debuggerAddress.replace("ws:\/\/", "http:\/\/")
|
|
1237
|
+
}
|
|
1238
|
+
else if (debuggerAddress.match("wss:\/\/", "https:\/\/")) {
|
|
1239
|
+
return debuggerAddress.replace("wss:\/\/", "https:\/\/")
|
|
1240
|
+
}
|
|
1241
|
+
else {
|
|
1242
|
+
return debuggerAddress
|
|
1243
|
+
}
|
|
1207
1244
|
}
|
|
1245
|
+
|
|
1246
|
+
const path = '/json/version'
|
|
1208
1247
|
let request = new http.Request('GET', path)
|
|
1209
1248
|
let client = new http.HttpClient('http://' + debuggerAddress)
|
|
1210
1249
|
let response = await client.send(request)
|
|
1211
|
-
let url
|
|
1212
|
-
if (target.toLowerCase() === 'page') {
|
|
1213
|
-
url = JSON.parse(response.body)[0]['webSocketDebuggerUrl']
|
|
1214
|
-
} else {
|
|
1215
|
-
url = JSON.parse(response.body)['webSocketDebuggerUrl']
|
|
1216
|
-
}
|
|
1217
1250
|
|
|
1218
|
-
return
|
|
1251
|
+
return JSON.parse(response.body)['webSocketDebuggerUrl']
|
|
1219
1252
|
}
|
|
1220
1253
|
|
|
1221
1254
|
/**
|
|
@@ -1227,14 +1260,6 @@ class WebDriver {
|
|
|
1227
1260
|
* @param connection CDP Connection
|
|
1228
1261
|
*/
|
|
1229
1262
|
async register(username, password, connection) {
|
|
1230
|
-
await connection.execute(
|
|
1231
|
-
'Network.setCacheDisabled',
|
|
1232
|
-
this.getRandomNumber(1, 10),
|
|
1233
|
-
{
|
|
1234
|
-
cacheDisabled: true,
|
|
1235
|
-
},
|
|
1236
|
-
null
|
|
1237
|
-
)
|
|
1238
1263
|
|
|
1239
1264
|
this._wsConnection.on('message', (message) => {
|
|
1240
1265
|
const params = JSON.parse(message)
|
|
@@ -1273,8 +1298,69 @@ class WebDriver {
|
|
|
1273
1298
|
},
|
|
1274
1299
|
null
|
|
1275
1300
|
)
|
|
1301
|
+
await connection.execute(
|
|
1302
|
+
'Network.setCacheDisabled',
|
|
1303
|
+
this.getRandomNumber(1, 10),
|
|
1304
|
+
{
|
|
1305
|
+
cacheDisabled: true,
|
|
1306
|
+
},
|
|
1307
|
+
null
|
|
1308
|
+
)
|
|
1276
1309
|
}
|
|
1277
1310
|
|
|
1311
|
+
/**
|
|
1312
|
+
* Handle Network interception requests
|
|
1313
|
+
* @param connection WebSocket connection to the browser
|
|
1314
|
+
* @param httpResponse Object representing what we are intercepting
|
|
1315
|
+
* as well as what should be returned.
|
|
1316
|
+
* @param callback callback called when we intercept requests.
|
|
1317
|
+
*/
|
|
1318
|
+
async onIntercept(connection, httpResponse, callback) {
|
|
1319
|
+
|
|
1320
|
+
this._wsConnection.on('message', (message) => {
|
|
1321
|
+
const params = JSON.parse(message)
|
|
1322
|
+
if (params.method === 'Fetch.requestPaused') {
|
|
1323
|
+
const requestPausedParams = params['params']
|
|
1324
|
+
if (requestPausedParams.request.url == httpResponse.urlToIntercept) {
|
|
1325
|
+
connection.execute(
|
|
1326
|
+
'Fetch.continueRequest',
|
|
1327
|
+
this.getRandomNumber(1, 10),
|
|
1328
|
+
{
|
|
1329
|
+
requestId: requestPausedParams['requestId'],
|
|
1330
|
+
url: httpResponse.urlToIntercept,
|
|
1331
|
+
method: httpResponse.method,
|
|
1332
|
+
headers: httpResponse.headers,
|
|
1333
|
+
postData: httpResponse.body
|
|
1334
|
+
}
|
|
1335
|
+
)
|
|
1336
|
+
callback()
|
|
1337
|
+
} else {
|
|
1338
|
+
connection.execute(
|
|
1339
|
+
'Fetch.continueRequest',
|
|
1340
|
+
this.getRandomNumber(1, 10),
|
|
1341
|
+
{
|
|
1342
|
+
requestId: requestPausedParams['requestId'],
|
|
1343
|
+
}
|
|
1344
|
+
)
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
})
|
|
1348
|
+
|
|
1349
|
+
await connection.execute(
|
|
1350
|
+
'Fetch.enable',
|
|
1351
|
+
1,
|
|
1352
|
+
{},
|
|
1353
|
+
null
|
|
1354
|
+
)
|
|
1355
|
+
await connection.execute(
|
|
1356
|
+
'Network.setCacheDisabled',
|
|
1357
|
+
this.getRandomNumber(1, 10),
|
|
1358
|
+
{
|
|
1359
|
+
cacheDisabled: true,
|
|
1360
|
+
},
|
|
1361
|
+
null
|
|
1362
|
+
)
|
|
1363
|
+
}
|
|
1278
1364
|
/**
|
|
1279
1365
|
*
|
|
1280
1366
|
* @param connection
|
|
@@ -1282,12 +1368,7 @@ class WebDriver {
|
|
|
1282
1368
|
* @returns {Promise<void>}
|
|
1283
1369
|
*/
|
|
1284
1370
|
async onLogEvent(connection, callback) {
|
|
1285
|
-
|
|
1286
|
-
'Runtime.enable',
|
|
1287
|
-
this.getRandomNumber(1, 10),
|
|
1288
|
-
{},
|
|
1289
|
-
null
|
|
1290
|
-
)
|
|
1371
|
+
|
|
1291
1372
|
|
|
1292
1373
|
this._wsConnection.on('message', (message) => {
|
|
1293
1374
|
const params = JSON.parse(message)
|
|
@@ -1303,6 +1384,12 @@ class WebDriver {
|
|
|
1303
1384
|
callback(event)
|
|
1304
1385
|
}
|
|
1305
1386
|
})
|
|
1387
|
+
await connection.execute(
|
|
1388
|
+
'Runtime.enable',
|
|
1389
|
+
this.getRandomNumber(1, 10),
|
|
1390
|
+
{},
|
|
1391
|
+
null
|
|
1392
|
+
)
|
|
1306
1393
|
}
|
|
1307
1394
|
|
|
1308
1395
|
/**
|
|
@@ -1374,7 +1461,10 @@ class WebDriver {
|
|
|
1374
1461
|
.toString()
|
|
1375
1462
|
} catch {
|
|
1376
1463
|
mutationListener = fs
|
|
1377
|
-
.readFileSync(
|
|
1464
|
+
.readFileSync(
|
|
1465
|
+
path.resolve(__dirname, './atoms/mutation-listener.js'),
|
|
1466
|
+
'utf-8'
|
|
1467
|
+
)
|
|
1378
1468
|
.toString()
|
|
1379
1469
|
}
|
|
1380
1470
|
|
|
@@ -2692,18 +2782,12 @@ class WebElement {
|
|
|
2692
2782
|
* Take a screenshot of the visible region encompassed by this element's
|
|
2693
2783
|
* bounding rectangle.
|
|
2694
2784
|
*
|
|
2695
|
-
* @param {boolean=} scroll Optional argument that indicates whether the
|
|
2696
|
-
* element should be scrolled into view before taking a screenshot.
|
|
2697
|
-
* Defaults to false.
|
|
2698
2785
|
* @return {!Promise<string>} A promise that will be
|
|
2699
2786
|
* resolved to the screenshot as a base-64 encoded PNG.
|
|
2700
2787
|
*/
|
|
2701
|
-
takeScreenshot(
|
|
2788
|
+
takeScreenshot() {
|
|
2702
2789
|
return this.execute_(
|
|
2703
|
-
new command.Command(command.Name.TAKE_ELEMENT_SCREENSHOT)
|
|
2704
|
-
'scroll',
|
|
2705
|
-
scroll
|
|
2706
|
-
)
|
|
2790
|
+
new command.Command(command.Name.TAKE_ELEMENT_SCREENSHOT)
|
|
2707
2791
|
)
|
|
2708
2792
|
}
|
|
2709
2793
|
}
|