selenium-webdriver 4.0.0-beta.2 → 4.0.0-beta.3

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 CHANGED
@@ -1,3 +1,13 @@
1
+ ## v4.0.0-beta.3
2
+
3
+ * Removed support for legacy actions.
4
+ This will also remove "Bride Mode" support, which was introduced to translate of action class to legacy API.
5
+ * Remove devtools support for v86,v87 and adding support for v89, v90, v91
6
+ * make isPromise() return boolean value for null and undefined cases (#6785)
7
+ * code cleanup (#9158)
8
+ * replacing asserts equal with strictEqual and deepEqual with deepStrictEqual
9
+
10
+
1
11
  ## v4.0.0-beta.2
2
12
 
3
13
  * Removed support for Edge legacy.
package/io/exec.js CHANGED
@@ -121,9 +121,9 @@ class Command {
121
121
  * @return {!Command} The launched command.
122
122
  */
123
123
  module.exports = function exec(command, opt_options) {
124
- var options = opt_options || {}
124
+ const options = opt_options || {}
125
125
 
126
- var proc = childProcess.spawn(command, options.args || [], {
126
+ let proc = childProcess.spawn(command, options.args || [], {
127
127
  env: options.env || process.env,
128
128
  stdio: options.stdio || 'ignore',
129
129
  })
@@ -133,7 +133,7 @@ module.exports = function exec(command, opt_options) {
133
133
  proc.unref()
134
134
  process.once('exit', onProcessExit)
135
135
 
136
- let result = new Promise((resolve) => {
136
+ const result = new Promise((resolve) => {
137
137
  proc.once('exit', (code, signal) => {
138
138
  proc = null
139
139
  process.removeListener('exit', onProcessExit)
package/io/index.js CHANGED
@@ -204,13 +204,12 @@ exports.tmpDir = function () {
204
204
  */
205
205
  exports.tmpFile = function (opt_options) {
206
206
  return checkedCall((callback) => {
207
- // |tmp.file| checks arguments length to detect options rather than doing a
208
- // truthy check, so we must only pass options if there are some to pass.
209
- if (opt_options) {
210
- tmp.file(opt_options, callback)
211
- } else {
212
- tmp.file(callback)
213
- }
207
+ /** check fixed in v > 0.2.1 if
208
+ * (typeof options === 'function') {
209
+ * return [{}, options];
210
+ * }
211
+ */
212
+ tmp.file(opt_options, callback)
214
213
  })
215
214
  }
216
215
 
@@ -224,7 +223,7 @@ exports.tmpFile = function (opt_options) {
224
223
  * not be found.
225
224
  */
226
225
  exports.findInPath = function (file, opt_checkCwd) {
227
- let dirs = []
226
+ const dirs = []
228
227
  if (opt_checkCwd) {
229
228
  dirs.push(process.cwd())
230
229
  }
@@ -329,7 +328,7 @@ exports.mkdirp = function mkdirp(dir) {
329
328
  * will be relative to `rootPath`.
330
329
  */
331
330
  exports.walkDir = function (rootPath) {
332
- let seen = []
331
+ const seen = []
333
332
  return (function walk(dir) {
334
333
  return checkedCall((callback) => fs.readdir(dir, callback)).then((files) =>
335
334
  Promise.all(
package/io/zip.js CHANGED
@@ -180,8 +180,8 @@ function load(path) {
180
180
  */
181
181
  function unzip(src, dst) {
182
182
  return load(src).then((zip) => {
183
- let promisedDirs = new Map()
184
- let promises = []
183
+ const promisedDirs = new Map()
184
+ const promises = []
185
185
 
186
186
  zip.z_.forEach((relPath, file) => {
187
187
  let p
@@ -218,7 +218,6 @@ function unzip(src, dst) {
218
218
  }
219
219
 
220
220
  // PUBLIC API
221
-
222
- exports.Zip = Zip
223
- exports.load = load
224
- exports.unzip = unzip
221
+ module.exports.Zip = Zip
222
+ module.exports.load = load
223
+ module.exports.unzip = unzip
package/lib/command.js CHANGED
@@ -177,21 +177,6 @@ const Name = {
177
177
 
178
178
  ACTIONS: 'actions',
179
179
  CLEAR_ACTIONS: 'clearActions',
180
-
181
- LEGACY_ACTION_CLICK: 'legacyAction:click',
182
- LEGACY_ACTION_DOUBLE_CLICK: 'legacyAction:doubleclick',
183
- LEGACY_ACTION_MOUSE_DOWN: 'legacyAction:mouseDown',
184
- LEGACY_ACTION_MOUSE_UP: 'legacyAction:mouseUp',
185
- LEGACY_ACTION_MOUSE_MOVE: 'legacyAction:mouseMove',
186
- LEGACY_ACTION_SEND_KEYS: 'legacyAction:sendKeys',
187
- LEGACY_ACTION_TOUCH_DOWN: 'legacyAction:touchDown',
188
- LEGACY_ACTION_TOUCH_UP: 'legacyAction:touchUp',
189
- LEGACY_ACTION_TOUCH_MOVE: 'legacyAction:touchMove',
190
- LEGACY_ACTION_TOUCH_SCROLL: 'legacyAction:touchScroll',
191
- LEGACY_ACTION_TOUCH_LONG_PRESS: 'legacyAction:touchLongPress',
192
- LEGACY_ACTION_TOUCH_FLICK: 'legacyAction:touchFlick',
193
- LEGACY_ACTION_TOUCH_SINGLE_TAP: 'legacyAction:singleTap',
194
- LEGACY_ACTION_TOUCH_DOUBLE_TAP: 'legacyAction:doubleTap',
195
180
  }
196
181
 
197
182
  /**
package/lib/http.js CHANGED
@@ -305,35 +305,6 @@ const COMMAND_MAP = new Map([
305
305
  [cmd.Name.GET_AVAILABLE_LOG_TYPES, get('/session/:sessionId/log/types')],
306
306
  [cmd.Name.GET_SESSION_LOGS, post('/logs')],
307
307
  [cmd.Name.UPLOAD_FILE, post('/session/:sessionId/se/file')],
308
- [cmd.Name.LEGACY_ACTION_CLICK, post('/session/:sessionId/click')],
309
- [
310
- cmd.Name.LEGACY_ACTION_DOUBLE_CLICK,
311
- post('/session/:sessionId/doubleclick'),
312
- ],
313
- [cmd.Name.LEGACY_ACTION_MOUSE_DOWN, post('/session/:sessionId/buttondown')],
314
- [cmd.Name.LEGACY_ACTION_MOUSE_UP, post('/session/:sessionId/buttonup')],
315
- [cmd.Name.LEGACY_ACTION_MOUSE_MOVE, post('/session/:sessionId/moveto')],
316
- [cmd.Name.LEGACY_ACTION_SEND_KEYS, post('/session/:sessionId/keys')],
317
- [cmd.Name.LEGACY_ACTION_TOUCH_DOWN, post('/session/:sessionId/touch/down')],
318
- [cmd.Name.LEGACY_ACTION_TOUCH_UP, post('/session/:sessionId/touch/up')],
319
- [cmd.Name.LEGACY_ACTION_TOUCH_MOVE, post('/session/:sessionId/touch/move')],
320
- [
321
- cmd.Name.LEGACY_ACTION_TOUCH_SCROLL,
322
- post('/session/:sessionId/touch/scroll'),
323
- ],
324
- [
325
- cmd.Name.LEGACY_ACTION_TOUCH_LONG_PRESS,
326
- post('/session/:sessionId/touch/longclick'),
327
- ],
328
- [cmd.Name.LEGACY_ACTION_TOUCH_FLICK, post('/session/:sessionId/touch/flick')],
329
- [
330
- cmd.Name.LEGACY_ACTION_TOUCH_SINGLE_TAP,
331
- post('/session/:sessionId/touch/click'),
332
- ],
333
- [
334
- cmd.Name.LEGACY_ACTION_TOUCH_DOUBLE_TAP,
335
- post('/session/:sessionId/touch/doubleclick'),
336
- ],
337
308
  ])
338
309
 
339
310
  /** @const {!Map<string, (CommandSpec|CommandTransformer)>} */
package/lib/input.js CHANGED
@@ -489,67 +489,6 @@ Pointer.Type = {
489
489
  * | Keyboard | pause(100) | keyDown(SHIFT) |
490
490
  * | Mouse | move({duration: 300}) | |
491
491
  *
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
492
  * [client rect]: https://developer.mozilla.org/en-US/docs/Web/API/Element/getClientRects
554
493
  * [bounding client rect]: https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect
555
494
  *
@@ -560,16 +499,10 @@ class Actions {
560
499
  /**
561
500
  * @param {!Executor} executor The object to execute the configured
562
501
  * actions with.
563
- * @param {{async: (boolean|undefined),
564
- * bridge: (boolean|undefined)}} options Options for this action
502
+ * @param {{async: (boolean|undefined)}} options Options for this action
565
503
  * sequence (see class description for details).
566
504
  */
567
- constructor(executor, { async = false, bridge = false } = {}) {
568
- if (async && bridge) {
569
- throw new InvalidArgumentError(
570
- 'Async sequences not supported with bridge mode'
571
- )
572
- }
505
+ constructor(executor, { async = false } = {}) {
573
506
 
574
507
  /** @private @const */
575
508
  this.executor_ = executor
@@ -577,9 +510,6 @@ class Actions {
577
510
  /** @private @const */
578
511
  this.sync_ = !async
579
512
 
580
- /** @private @const */
581
- this.bridge_ = !!bridge
582
-
583
513
  /** @private @const */
584
514
  this.keyboard_ = new Keyboard('default keyboard')
585
515
 
@@ -957,13 +887,6 @@ class Actions {
957
887
  new Command(Name.ACTIONS).setParameter('actions', _actions)
958
888
  )
959
889
  } 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
890
  throw ex
968
891
  }
969
892
  }
@@ -980,213 +903,6 @@ function isIdle(actions) {
980
903
  )
981
904
  }
982
905
 
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
906
  /**
1191
907
  * Script used to compute the offset from the center of a DOM element's first
1192
908
  * 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
- try {
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
- let value = arr[i]
203
- let include = await fn.call(self, value, i, arr)
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "selenium-webdriver",
3
- "version": "4.0.0-beta.2",
3
+ "version": "4.0.0-beta.3",
4
4
  "description": "The official WebDriver JavaScript bindings from the Selenium project",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
package/remote/index.js CHANGED
@@ -210,7 +210,7 @@ class DriverService {
210
210
  }
211
211
 
212
212
  return resolveCommandLineFlags(this.args_).then((args) => {
213
- var command = exec(self.executable_, {
213
+ const command = exec(self.executable_, {
214
214
  args: args,
215
215
  env: self.env_,
216
216
  stdio: self.stdio_,
@@ -218,8 +218,8 @@ class DriverService {
218
218
 
219
219
  resolveCommand(command)
220
220
 
221
- var earlyTermination = command.result().then(function (result) {
222
- var error =
221
+ const earlyTermination = command.result().then(function (result) {
222
+ const error =
223
223
  result.code == null
224
224
  ? Error('Server was killed with ' + result.signal)
225
225
  : Error('Server terminated early with status ' + result.code)
@@ -229,7 +229,7 @@ class DriverService {
229
229
  throw error
230
230
  })
231
231
 
232
- var hostname = self.hostname_
232
+ let hostname = self.hostname_
233
233
  if (!hostname) {
234
234
  hostname =
235
235
  (!self.loopbackOnly_ && net.getAddress()) ||
@@ -338,9 +338,8 @@ DriverService.Builder = class {
338
338
  * @this {THIS}
339
339
  * @template THIS
340
340
  */
341
- addArguments(var_args) { // eslint-disable-line
342
- let args = Array.prototype.slice.call(arguments, 0)
343
- this.options_.args = this.options_.args.concat(args)
341
+ addArguments(...arguments_) {
342
+ this.options_.args = this.options_.args.concat(arguments_)
344
343
  return this
345
344
  }
346
345
 
package/lib/actions.js DELETED
@@ -1,627 +0,0 @@
1
- // Licensed to the Software Freedom Conservancy (SFC) under one
2
- // or more contributor license agreements. See the NOTICE file
3
- // distributed with this work for additional information
4
- // regarding copyright ownership. The SFC licenses this file
5
- // to you under the Apache License, Version 2.0 (the
6
- // "License"); you may not use this file except in compliance
7
- // with the License. You may obtain a copy of the License at
8
- //
9
- // http://www.apache.org/licenses/LICENSE-2.0
10
- //
11
- // Unless required by applicable law or agreed to in writing,
12
- // software distributed under the License is distributed on an
13
- // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
- // KIND, either express or implied. See the License for the
15
- // specific language governing permissions and limitations
16
- // under the License.
17
-
18
- 'use strict'
19
-
20
- const command = require('./command')
21
- const error = require('./error')
22
- const input = require('./input')
23
-
24
- /**
25
- * @param {!IArrayLike} args .
26
- * @return {!Array} .
27
- */
28
- function flatten(args) {
29
- const result = []
30
- for (let i = 0; i < args.length; i++) {
31
- const element = args[i]
32
- if (Array.isArray(element)) {
33
- result.push.apply(result, flatten(element))
34
- } else {
35
- result.push(element)
36
- }
37
- }
38
- return result
39
- }
40
-
41
- const MODIFIER_KEYS = new Set([
42
- input.Key.ALT,
43
- input.Key.CONTROL,
44
- input.Key.SHIFT,
45
- input.Key.COMMAND,
46
- ])
47
-
48
- /**
49
- * Checks that a key is a modifier key.
50
- * @param {!input.Key} key The key to check.
51
- * @throws {error.InvalidArgumentError} If the key is not a modifier key.
52
- * @private
53
- */
54
- function checkModifierKey(key) {
55
- if (!MODIFIER_KEYS.has(key)) {
56
- throw new error.InvalidArgumentError('Not a modifier key')
57
- }
58
- }
59
-
60
- /**
61
- * Class for defining sequences of complex user interactions. Each sequence
62
- * will not be executed until {@link #perform} is called.
63
- *
64
- * This class should not be instantiated directly. Instead, obtain an instance
65
- * using {@link ./webdriver.WebDriver#actions() WebDriver.actions()}.
66
- *
67
- * Sample usage:
68
- *
69
- * new LegacyActions(driver).
70
- * keyDown(Key.SHIFT).
71
- * click(element1).
72
- * click(element2).
73
- * dragAndDrop(element3, element4).
74
- * keyUp(Key.SHIFT).
75
- * perform();
76
- *
77
- * @deprecated This class is strongly deprecated and will be removed once
78
- * [Google's Chrome][Chrome] and [Microsoft's Edge][Edge] browsers
79
- * support the new action sequence API.
80
- *
81
- * [Chrome]: https://chromium.googlesource.com/chromium/src/+/master/docs/chromedriver_status.md
82
- * [Edge]: https://docs.microsoft.com/en-us/microsoft-edge/webdriver
83
- */
84
- class LegacyActionSequence {
85
- /**
86
- * @param {!./webdriver.WebDriver} driver The driver that should be used to
87
- * perform this action sequence.
88
- */
89
- constructor(driver) {
90
- /** @private {!./webdriver.WebDriver} */
91
- this.driver_ = driver
92
-
93
- /** @private {!Array<{description: string, command: !command.Command}>} */
94
- this.actions_ = []
95
- }
96
-
97
- /**
98
- * Schedules an action to be executed each time {@link #perform} is called on
99
- * this instance.
100
- *
101
- * @param {string} description A description of the command.
102
- * @param {!command.Command} command The command.
103
- * @private
104
- */
105
- schedule_(description, command) {
106
- this.actions_.push({
107
- description: description,
108
- command: command,
109
- })
110
- }
111
-
112
- /**
113
- * Executes this action sequence.
114
- *
115
- * @return {!Promise} A promise that will be resolved once this sequence has
116
- * completed.
117
- */
118
- async perform() {
119
- // Make a protected copy of the scheduled actions. This will protect against
120
- // users defining additional commands before this sequence is actually
121
- // executed.
122
- const actions = this.actions_.concat()
123
- for (const action of actions) {
124
- await this.driver_.execute(action.command)
125
- }
126
- }
127
-
128
- /**
129
- * Moves the mouse. The location to move to may be specified in terms of the
130
- * mouse's current location, an offset relative to the top-left corner of an
131
- * element, or an element (in which case the middle of the element is used).
132
- *
133
- * @param {(!./webdriver.WebElement|{x: number, y: number})} location The
134
- * location to drag to, as either another WebElement or an offset in
135
- * pixels.
136
- * @param {{x: number, y: number}=} opt_offset If the target {@code location}
137
- * is defined as a {@link ./webdriver.WebElement}, this parameter defines
138
- * an offset within that element. The offset should be specified in pixels
139
- * relative to the top-left corner of the element's bounding box. If
140
- * omitted, the element's center will be used as the target offset.
141
- * @return {!LegacyActionSequence} A self reference.
142
- */
143
- mouseMove(location, opt_offset) {
144
- const cmd = new command.Command(command.Name.LEGACY_ACTION_MOUSE_MOVE)
145
-
146
- if (typeof location.x === 'number') {
147
- setOffset(/** @type {{x: number, y: number}} */ (location))
148
- } else {
149
- cmd.setParameter('element', location.getId())
150
- if (opt_offset) {
151
- setOffset(opt_offset)
152
- }
153
- }
154
-
155
- this.schedule_('mouseMove', cmd)
156
- return this
157
-
158
- /** @param {{x: number, y: number}} offset The offset to use. */
159
- function setOffset(offset) {
160
- cmd.setParameter('xoffset', offset.x || 0)
161
- cmd.setParameter('yoffset', offset.y || 0)
162
- }
163
- }
164
-
165
- /**
166
- * Schedules a mouse action.
167
- * @param {string} description A simple descriptive label for the scheduled
168
- * action.
169
- * @param {!command.Name} commandName The name of the command.
170
- * @param {(./webdriver.WebElement|input.Button)=} opt_elementOrButton Either
171
- * the element to interact with or the button to click with.
172
- * Defaults to {@link input.Button.LEFT} if neither an element nor
173
- * button is specified.
174
- * @param {input.Button=} opt_button The button to use. Defaults to
175
- * {@link input.Button.LEFT}. Ignored if the previous argument is
176
- * provided as a button.
177
- * @return {!LegacyActionSequence} A self reference.
178
- * @private
179
- */
180
- scheduleMouseAction_(
181
- description,
182
- commandName,
183
- opt_elementOrButton,
184
- opt_button
185
- ) {
186
- let button
187
- if (typeof opt_elementOrButton === 'number') {
188
- button = opt_elementOrButton
189
- } else {
190
- if (opt_elementOrButton) {
191
- this.mouseMove(
192
- /** @type {!./webdriver.WebElement} */ (opt_elementOrButton)
193
- )
194
- }
195
- button = opt_button !== void 0 ? opt_button : input.Button.LEFT
196
- }
197
-
198
- let cmd = new command.Command(commandName).setParameter('button', button)
199
- this.schedule_(description, cmd)
200
- return this
201
- }
202
-
203
- /**
204
- * Presses a mouse button. The mouse button will not be released until
205
- * {@link #mouseUp} is called, regardless of whether that call is made in this
206
- * sequence or another. The behavior for out-of-order events (e.g. mouseDown,
207
- * click) is undefined.
208
- *
209
- * If an element is provided, the mouse will first be moved to the center
210
- * of that element. This is equivalent to:
211
- *
212
- * sequence.mouseMove(element).mouseDown()
213
- *
214
- * Warning: this method currently only supports the left mouse button. See
215
- * [issue 4047](http://code.google.com/p/selenium/issues/detail?id=4047).
216
- *
217
- * @param {(./webdriver.WebElement|input.Button)=} opt_elementOrButton Either
218
- * the element to interact with or the button to click with.
219
- * Defaults to {@link input.Button.LEFT} if neither an element nor
220
- * button is specified.
221
- * @param {input.Button=} opt_button The button to use. Defaults to
222
- * {@link input.Button.LEFT}. Ignored if a button is provided as the
223
- * first argument.
224
- * @return {!LegacyActionSequence} A self reference.
225
- */
226
- mouseDown(opt_elementOrButton, opt_button) {
227
- return this.scheduleMouseAction_(
228
- 'mouseDown',
229
- command.Name.LEGACY_ACTION_MOUSE_DOWN,
230
- opt_elementOrButton,
231
- opt_button
232
- )
233
- }
234
-
235
- /**
236
- * Releases a mouse button. Behavior is undefined for calling this function
237
- * without a previous call to {@link #mouseDown}.
238
- *
239
- * If an element is provided, the mouse will first be moved to the center
240
- * of that element. This is equivalent to:
241
- *
242
- * sequence.mouseMove(element).mouseUp()
243
- *
244
- * Warning: this method currently only supports the left mouse button. See
245
- * [issue 4047](http://code.google.com/p/selenium/issues/detail?id=4047).
246
- *
247
- * @param {(./webdriver.WebElement|input.Button)=} opt_elementOrButton Either
248
- * the element to interact with or the button to click with.
249
- * Defaults to {@link input.Button.LEFT} if neither an element nor
250
- * button is specified.
251
- * @param {input.Button=} opt_button The button to use. Defaults to
252
- * {@link input.Button.LEFT}. Ignored if a button is provided as the
253
- * first argument.
254
- * @return {!LegacyActionSequence} A self reference.
255
- */
256
- mouseUp(opt_elementOrButton, opt_button) {
257
- return this.scheduleMouseAction_(
258
- 'mouseUp',
259
- command.Name.LEGACY_ACTION_MOUSE_UP,
260
- opt_elementOrButton,
261
- opt_button
262
- )
263
- }
264
-
265
- /**
266
- * Convenience function for performing a "drag and drop" maneuver. The target
267
- * element may be moved to the location of another element, or by an offset (in
268
- * pixels).
269
- *
270
- * @param {!./webdriver.WebElement} element The element to drag.
271
- * @param {(!./webdriver.WebElement|{x: number, y: number})} location The
272
- * location to drag to, either as another WebElement or an offset in
273
- * pixels.
274
- * @return {!LegacyActionSequence} A self reference.
275
- */
276
- dragAndDrop(element, location) {
277
- return this.mouseDown(element).mouseMove(location).mouseUp()
278
- }
279
-
280
- /**
281
- * Clicks a mouse button.
282
- *
283
- * If an element is provided, the mouse will first be moved to the center
284
- * of that element. This is equivalent to:
285
- *
286
- * sequence.mouseMove(element).click()
287
- *
288
- * @param {(./webdriver.WebElement|input.Button)=} opt_elementOrButton Either
289
- * the element to interact with or the button to click with.
290
- * Defaults to {@link input.Button.LEFT} if neither an element nor
291
- * button is specified.
292
- * @param {input.Button=} opt_button The button to use. Defaults to
293
- * {@link input.Button.LEFT}. Ignored if a button is provided as the
294
- * first argument.
295
- * @return {!LegacyActionSequence} A self reference.
296
- */
297
- click(opt_elementOrButton, opt_button) {
298
- return this.scheduleMouseAction_(
299
- 'click',
300
- command.Name.LEGACY_ACTION_CLICK,
301
- opt_elementOrButton,
302
- opt_button
303
- )
304
- }
305
-
306
- /**
307
- * Double-clicks a mouse button.
308
- *
309
- * If an element is provided, the mouse will first be moved to the center of
310
- * that element. This is equivalent to:
311
- *
312
- * sequence.mouseMove(element).doubleClick()
313
- *
314
- * Warning: this method currently only supports the left mouse button. See
315
- * [issue 4047](http://code.google.com/p/selenium/issues/detail?id=4047).
316
- *
317
- * @param {(./webdriver.WebElement|input.Button)=} opt_elementOrButton Either
318
- * the element to interact with or the button to click with.
319
- * Defaults to {@link input.Button.LEFT} if neither an element nor
320
- * button is specified.
321
- * @param {input.Button=} opt_button The button to use. Defaults to
322
- * {@link input.Button.LEFT}. Ignored if a button is provided as the
323
- * first argument.
324
- * @return {!LegacyActionSequence} A self reference.
325
- */
326
- doubleClick(opt_elementOrButton, opt_button) {
327
- return this.scheduleMouseAction_(
328
- 'doubleClick',
329
- command.Name.LEGACY_ACTION_DOUBLE_CLICK,
330
- opt_elementOrButton,
331
- opt_button
332
- )
333
- }
334
-
335
- /**
336
- * Schedules a keyboard action.
337
- *
338
- * @param {string} description A simple descriptive label for the scheduled
339
- * action.
340
- * @param {!Array<(string|!input.Key)>} keys The keys to send.
341
- * @return {!LegacyActionSequence} A self reference.
342
- * @private
343
- */
344
- scheduleKeyboardAction_(description, keys) {
345
- let cmd = new command.Command(
346
- command.Name.LEGACY_ACTION_SEND_KEYS
347
- ).setParameter('value', keys)
348
- this.schedule_(description, cmd)
349
- return this
350
- }
351
-
352
- /**
353
- * Performs a modifier key press. The modifier key is <em>not released</em>
354
- * until {@link #keyUp} or {@link #sendKeys} is called. The key press will be
355
- * targeted at the currently focused element.
356
- *
357
- * @param {!input.Key} key The modifier key to push. Must be one of
358
- * {ALT, CONTROL, SHIFT, COMMAND, META}.
359
- * @return {!LegacyActionSequence} A self reference.
360
- * @throws {error.InvalidArgumentError} If the key is not a valid modifier
361
- * key.
362
- */
363
- keyDown(key) {
364
- checkModifierKey(key)
365
- return this.scheduleKeyboardAction_('keyDown', [key])
366
- }
367
-
368
- /**
369
- * Performs a modifier key release. The release is targeted at the currently
370
- * focused element.
371
- * @param {!input.Key} key The modifier key to release. Must be one of
372
- * {ALT, CONTROL, SHIFT, COMMAND, META}.
373
- * @return {!LegacyActionSequence} A self reference.
374
- * @throws {error.InvalidArgumentError} If the key is not a valid modifier
375
- * key.
376
- */
377
- keyUp(key) {
378
- checkModifierKey(key)
379
- return this.scheduleKeyboardAction_('keyUp', [key])
380
- }
381
-
382
- /**
383
- * Simulates typing multiple keys. Each modifier key encountered in the
384
- * sequence will not be released until it is encountered again. All key events
385
- * will be targeted at the currently focused element.
386
- *
387
- * @param {...(string|!input.Key|!Array<(string|!input.Key)>)} var_args
388
- * The keys to type.
389
- * @return {!LegacyActionSequence} A self reference.
390
- * @throws {Error} If the key is not a valid modifier key.
391
- */
392
- sendKeys(_var_args) {
393
- // eslint-disable-line
394
- const keys = flatten(arguments)
395
- return this.scheduleKeyboardAction_('sendKeys', keys)
396
- }
397
- }
398
-
399
- /**
400
- * Class for defining sequences of user touch interactions. Each sequence
401
- * will not be executed until {@link #perform} is called.
402
- *
403
- * This class should not be instantiated directly. Instead, obtain an instance
404
- * using {@link ./webdriver.WebDriver#touchActions() WebDriver.touchActions()}.
405
- *
406
- * Sample usage:
407
- *
408
- * new LegacyActions(driver).
409
- * tapAndHold({x: 0, y: 0}).
410
- * move({x: 3, y: 4}).
411
- * release({x: 10, y: 10}).
412
- * perform();
413
- *
414
- * @deprecated This class is strongly deprecated and will be removed once
415
- * [Google's Chrome][Chrome] and [Microsoft's Edge][Edge] browsers
416
- * support the new action sequence API.
417
- *
418
- * [Chrome]: https://chromium.googlesource.com/chromium/src/+/master/docs/chromedriver_status.md
419
- * [Edge]: https://docs.microsoft.com/en-us/microsoft-edge/webdriver
420
- */
421
- class LegacyTouchSequence {
422
- /**
423
- * @param {!./webdriver.WebDriver} driver The driver that should be used to
424
- * perform this action sequence.
425
- */
426
- constructor(driver) {
427
- /** @private {!./webdriver.WebDriver} */
428
- this.driver_ = driver
429
-
430
- /** @private {!Array<{description: string, command: !command.Command}>} */
431
- this.actions_ = []
432
- }
433
-
434
- /**
435
- * Schedules an action to be executed each time {@link #perform} is called on
436
- * this instance.
437
- * @param {string} description A description of the command.
438
- * @param {!command.Command} command The command.
439
- * @private
440
- */
441
- schedule_(description, command) {
442
- this.actions_.push({
443
- description: description,
444
- command: command,
445
- })
446
- }
447
-
448
- /**
449
- * Executes this action sequence.
450
- * @return {!Promise} A promise that will be resolved once this sequence has
451
- * completed.
452
- */
453
- async perform() {
454
- // Make a protected copy of the scheduled actions. This will protect against
455
- // users defining additional commands before this sequence is actually
456
- // executed.
457
- let actions = this.actions_.concat()
458
- for (let action of actions) {
459
- await this.driver_.execute(action.command)
460
- }
461
- }
462
-
463
- /**
464
- * Taps an element.
465
- *
466
- * @param {!./webdriver.WebElement} elem The element to tap.
467
- * @return {!LegacyTouchSequence} A self reference.
468
- */
469
- tap(elem) {
470
- let cmd = new command.Command(
471
- command.Name.LEGACY_ACTION_TOUCH_SINGLE_TAP
472
- ).setParameter('element', elem.getId())
473
-
474
- this.schedule_('tap', cmd)
475
- return this
476
- }
477
-
478
- /**
479
- * Double taps an element.
480
- *
481
- * @param {!./webdriver.WebElement} elem The element to double tap.
482
- * @return {!LegacyTouchSequence} A self reference.
483
- */
484
- doubleTap(elem) {
485
- const cmd = new command.Command(
486
- command.Name.LEGACY_ACTION_TOUCH_DOUBLE_TAP
487
- ).setParameter('element', elem.getId())
488
-
489
- this.schedule_('doubleTap', cmd)
490
- return this
491
- }
492
-
493
- /**
494
- * Long press on an element.
495
- *
496
- * @param {!./webdriver.WebElement} elem The element to long press.
497
- * @return {!LegacyTouchSequence} A self reference.
498
- */
499
- longPress(elem) {
500
- const cmd = new command.Command(
501
- command.Name.LEGACY_ACTION_TOUCH_LONG_PRESS
502
- ).setParameter('element', elem.getId())
503
-
504
- this.schedule_('longPress', cmd)
505
- return this
506
- }
507
-
508
- /**
509
- * Touch down at the given location.
510
- *
511
- * @param {{x: number, y: number}} location The location to touch down at.
512
- * @return {!LegacyTouchSequence} A self reference.
513
- */
514
- tapAndHold(location) {
515
- const cmd = new command.Command(command.Name.LEGACY_ACTION_TOUCH_DOWN)
516
- .setParameter('x', location.x)
517
- .setParameter('y', location.y)
518
-
519
- this.schedule_('tapAndHold', cmd)
520
- return this
521
- }
522
-
523
- /**
524
- * Move a held {@linkplain #tapAndHold touch} to the specified location.
525
- *
526
- * @param {{x: number, y: number}} location The location to move to.
527
- * @return {!LegacyTouchSequence} A self reference.
528
- */
529
- move(location) {
530
- const cmd = new command.Command(command.Name.LEGACY_ACTION_TOUCH_MOVE)
531
- .setParameter('x', location.x)
532
- .setParameter('y', location.y)
533
-
534
- this.schedule_('move', cmd)
535
- return this
536
- }
537
-
538
- /**
539
- * Release a held {@linkplain #tapAndHold touch} at the specified location.
540
- *
541
- * @param {{x: number, y: number}} location The location to release at.
542
- * @return {!LegacyTouchSequence} A self reference.
543
- */
544
- release(location) {
545
- let cmd = new command.Command(command.Name.LEGACY_ACTION_TOUCH_UP)
546
- .setParameter('x', location.x)
547
- .setParameter('y', location.y)
548
-
549
- this.schedule_('release', cmd)
550
- return this
551
- }
552
-
553
- /**
554
- * Scrolls the touch screen by the given offset.
555
- *
556
- * @param {{x: number, y: number}} offset The offset to scroll to.
557
- * @return {!LegacyTouchSequence} A self reference.
558
- */
559
- scroll(offset) {
560
- const cmd = new command.Command(command.Name.LEGACY_ACTION_TOUCH_SCROLL)
561
- .setParameter('xoffset', offset.x)
562
- .setParameter('yoffset', offset.y)
563
-
564
- this.schedule_('scroll', cmd)
565
- return this
566
- }
567
-
568
- /**
569
- * Scrolls the touch screen, starting on `elem` and moving by the specified
570
- * offset.
571
- *
572
- * @param {!./webdriver.WebElement} elem The element where scroll starts.
573
- * @param {{x: number, y: number}} offset The offset to scroll to.
574
- * @return {!LegacyTouchSequence} A self reference.
575
- */
576
- scrollFromElement(elem, offset) {
577
- const cmd = new command.Command(command.Name.LEGACY_ACTION_TOUCH_SCROLL)
578
- .setParameter('element', elem.getId())
579
- .setParameter('xoffset', offset.x)
580
- .setParameter('yoffset', offset.y)
581
-
582
- this.schedule_('scrollFromElement', cmd)
583
- return this
584
- }
585
-
586
- /**
587
- * Flick, starting anywhere on the screen, at speed xspeed and yspeed.
588
- *
589
- * @param {{xspeed: number, yspeed: number}} speed The speed to flick in each
590
- direction, in pixels per second.
591
- * @return {!LegacyTouchSequence} A self reference.
592
- */
593
- flick(speed) {
594
- const cmd = new command.Command(command.Name.LEGACY_ACTION_TOUCH_FLICK)
595
- .setParameter('xspeed', speed.xspeed)
596
- .setParameter('yspeed', speed.yspeed)
597
-
598
- this.schedule_('flick', cmd)
599
- return this
600
- }
601
-
602
- /**
603
- * Flick starting at elem and moving by x and y at specified speed.
604
- *
605
- * @param {!./webdriver.WebElement} elem The element where flick starts.
606
- * @param {{x: number, y: number}} offset The offset to flick to.
607
- * @param {number} speed The speed to flick at in pixels per second.
608
- * @return {!LegacyTouchSequence} A self reference.
609
- */
610
- flickElement(elem, offset, speed) {
611
- const cmd = new command.Command(command.Name.LEGACY_ACTION_TOUCH_FLICK)
612
- .setParameter('element', elem.getId())
613
- .setParameter('xoffset', offset.x)
614
- .setParameter('yoffset', offset.y)
615
- .setParameter('speed', speed)
616
-
617
- this.schedule_('flickElement', cmd)
618
- return this
619
- }
620
- }
621
-
622
- // PUBLIC API
623
-
624
- module.exports = {
625
- LegacyActionSequence,
626
- LegacyTouchSequence,
627
- }