diff options
author | Florian Dold <florian.dold@gmail.com> | 2016-11-03 01:33:53 +0100 |
---|---|---|
committer | Florian Dold <florian.dold@gmail.com> | 2016-11-03 01:33:53 +0100 |
commit | d1291f67551c58168af43698a359cb5ddfd266b0 (patch) | |
tree | 55a13ed29fe1915e3f42f1b1b7038dafa2e975a7 /node_modules/selenium-webdriver/lib/actions.js | |
parent | d0a0695fb5d34996850723f7d4b1b59c3df909c2 (diff) |
node_modules
Diffstat (limited to 'node_modules/selenium-webdriver/lib/actions.js')
-rw-r--r-- | node_modules/selenium-webdriver/lib/actions.js | 596 |
1 files changed, 596 insertions, 0 deletions
diff --git a/node_modules/selenium-webdriver/lib/actions.js b/node_modules/selenium-webdriver/lib/actions.js new file mode 100644 index 000000000..7200b08d6 --- /dev/null +++ b/node_modules/selenium-webdriver/lib/actions.js @@ -0,0 +1,596 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +'use strict'; + +const command = require('./command'); +const error = require('./error'); +const input = require('./input'); + + +/** + * @param {!IArrayLike} args . + * @return {!Array} . + */ +function flatten(args) { + let result = []; + for (let i = 0; i < args.length; i++) { + let element = args[i]; + if (Array.isArray(element)) { + result.push.apply(result, flatten(element)); + } else { + result.push(element); + } + } + return result; +} + + +const MODIFIER_KEYS = new Set([ + input.Key.ALT, + input.Key.CONTROL, + input.Key.SHIFT, + input.Key.COMMAND +]); + + +/** + * Checks that a key is a modifier key. + * @param {!input.Key} key The key to check. + * @throws {error.InvalidArgumentError} If the key is not a modifier key. + * @private + */ +function checkModifierKey(key) { + if (!MODIFIER_KEYS.has(key)) { + throw new error.InvalidArgumentError('Not a modifier key'); + } +} + + +/** + * Class for defining sequences of complex user interactions. Each sequence + * will not be executed until {@link #perform} is called. + * + * Example: + * + * new ActionSequence(driver). + * keyDown(Key.SHIFT). + * click(element1). + * click(element2). + * dragAndDrop(element3, element4). + * keyUp(Key.SHIFT). + * perform(); + * + */ +class ActionSequence { + /** + * @param {!./webdriver.WebDriver} driver The driver that should be used to + * perform this action sequence. + */ + constructor(driver) { + /** @private {!./webdriver.WebDriver} */ + this.driver_ = driver; + + /** @private {!Array<{description: string, command: !command.Command}>} */ + this.actions_ = []; + } + + /** + * Schedules an action to be executed each time {@link #perform} is called on + * this instance. + * + * @param {string} description A description of the command. + * @param {!command.Command} command The command. + * @private + */ + schedule_(description, command) { + this.actions_.push({ + description: description, + command: command + }); + } + + /** + * Executes this action sequence. + * + * @return {!./promise.Promise} A promise that will be resolved once + * this sequence has completed. + */ + perform() { + // Make a protected copy of the scheduled actions. This will protect against + // users defining additional commands before this sequence is actually + // executed. + let actions = this.actions_.concat(); + let driver = this.driver_; + return driver.controlFlow().execute(function() { + actions.forEach(function(action) { + driver.schedule(action.command, action.description); + }); + }, 'ActionSequence.perform'); + } + + /** + * Moves the mouse. The location to move to may be specified in terms of the + * mouse's current location, an offset relative to the top-left corner of an + * element, or an element (in which case the middle of the element is used). + * + * @param {(!./webdriver.WebElement|{x: number, y: number})} location The + * location to drag to, as either another WebElement or an offset in + * pixels. + * @param {{x: number, y: number}=} opt_offset If the target {@code location} + * is defined as a {@link ./webdriver.WebElement}, this parameter defines + * an offset within that element. The offset should be specified in pixels + * relative to the top-left corner of the element's bounding box. If + * omitted, the element's center will be used as the target offset. + * @return {!ActionSequence} A self reference. + */ + mouseMove(location, opt_offset) { + let cmd = new command.Command(command.Name.MOVE_TO); + + if (typeof location.x === 'number') { + setOffset(/** @type {{x: number, y: number}} */(location)); + } else { + cmd.setParameter('element', location.getId()); + if (opt_offset) { + setOffset(opt_offset); + } + } + + this.schedule_('mouseMove', cmd); + return this; + + /** @param {{x: number, y: number}} offset The offset to use. */ + function setOffset(offset) { + cmd.setParameter('xoffset', offset.x || 0); + cmd.setParameter('yoffset', offset.y || 0); + } + } + + /** + * Schedules a mouse action. + * @param {string} description A simple descriptive label for the scheduled + * action. + * @param {!command.Name} commandName The name of the command. + * @param {(./webdriver.WebElement|input.Button)=} opt_elementOrButton Either + * the element to interact with or the button to click with. + * Defaults to {@link input.Button.LEFT} if neither an element nor + * button is specified. + * @param {input.Button=} opt_button The button to use. Defaults to + * {@link input.Button.LEFT}. Ignored if the previous argument is + * provided as a button. + * @return {!ActionSequence} A self reference. + * @private + */ + scheduleMouseAction_( + description, commandName, opt_elementOrButton, opt_button) { + let button; + if (typeof opt_elementOrButton === 'number') { + button = opt_elementOrButton; + } else { + if (opt_elementOrButton) { + this.mouseMove( + /** @type {!./webdriver.WebElement} */ (opt_elementOrButton)); + } + button = opt_button !== void(0) ? opt_button : input.Button.LEFT; + } + + let cmd = new command.Command(commandName). + setParameter('button', button); + this.schedule_(description, cmd); + return this; + } + + /** + * Presses a mouse button. The mouse button will not be released until + * {@link #mouseUp} is called, regardless of whether that call is made in this + * sequence or another. The behavior for out-of-order events (e.g. mouseDown, + * click) is undefined. + * + * If an element is provided, the mouse will first be moved to the center + * of that element. This is equivalent to: + * + * sequence.mouseMove(element).mouseDown() + * + * Warning: this method currently only supports the left mouse button. See + * [issue 4047](http://code.google.com/p/selenium/issues/detail?id=4047). + * + * @param {(./webdriver.WebElement|input.Button)=} opt_elementOrButton Either + * the element to interact with or the button to click with. + * Defaults to {@link input.Button.LEFT} if neither an element nor + * button is specified. + * @param {input.Button=} opt_button The button to use. Defaults to + * {@link input.Button.LEFT}. Ignored if a button is provided as the + * first argument. + * @return {!ActionSequence} A self reference. + */ + mouseDown(opt_elementOrButton, opt_button) { + return this.scheduleMouseAction_('mouseDown', + command.Name.MOUSE_DOWN, opt_elementOrButton, opt_button); + } + + /** + * Releases a mouse button. Behavior is undefined for calling this function + * without a previous call to {@link #mouseDown}. + * + * If an element is provided, the mouse will first be moved to the center + * of that element. This is equivalent to: + * + * sequence.mouseMove(element).mouseUp() + * + * Warning: this method currently only supports the left mouse button. See + * [issue 4047](http://code.google.com/p/selenium/issues/detail?id=4047). + * + * @param {(./webdriver.WebElement|input.Button)=} opt_elementOrButton Either + * the element to interact with or the button to click with. + * Defaults to {@link input.Button.LEFT} if neither an element nor + * button is specified. + * @param {input.Button=} opt_button The button to use. Defaults to + * {@link input.Button.LEFT}. Ignored if a button is provided as the + * first argument. + * @return {!ActionSequence} A self reference. + */ + mouseUp(opt_elementOrButton, opt_button) { + return this.scheduleMouseAction_('mouseUp', + command.Name.MOUSE_UP, opt_elementOrButton, opt_button); + } + + /** + * Convenience function for performing a "drag and drop" manuever. The target + * element may be moved to the location of another element, or by an offset (in + * pixels). + * + * @param {!./webdriver.WebElement} element The element to drag. + * @param {(!./webdriver.WebElement|{x: number, y: number})} location The + * location to drag to, either as another WebElement or an offset in + * pixels. + * @return {!ActionSequence} A self reference. + */ + dragAndDrop(element, location) { + return this.mouseDown(element).mouseMove(location).mouseUp(); + } + + /** + * Clicks a mouse button. + * + * If an element is provided, the mouse will first be moved to the center + * of that element. This is equivalent to: + * + * sequence.mouseMove(element).click() + * + * @param {(./webdriver.WebElement|input.Button)=} opt_elementOrButton Either + * the element to interact with or the button to click with. + * Defaults to {@link input.Button.LEFT} if neither an element nor + * button is specified. + * @param {input.Button=} opt_button The button to use. Defaults to + * {@link input.Button.LEFT}. Ignored if a button is provided as the + * first argument. + * @return {!ActionSequence} A self reference. + */ + click(opt_elementOrButton, opt_button) { + return this.scheduleMouseAction_('click', + command.Name.CLICK, opt_elementOrButton, opt_button); + } + + /** + * Double-clicks a mouse button. + * + * If an element is provided, the mouse will first be moved to the center of + * that element. This is equivalent to: + * + * sequence.mouseMove(element).doubleClick() + * + * Warning: this method currently only supports the left mouse button. See + * [issue 4047](http://code.google.com/p/selenium/issues/detail?id=4047). + * + * @param {(./webdriver.WebElement|input.Button)=} opt_elementOrButton Either + * the element to interact with or the button to click with. + * Defaults to {@link input.Button.LEFT} if neither an element nor + * button is specified. + * @param {input.Button=} opt_button The button to use. Defaults to + * {@link input.Button.LEFT}. Ignored if a button is provided as the + * first argument. + * @return {!ActionSequence} A self reference. + */ + doubleClick(opt_elementOrButton, opt_button) { + return this.scheduleMouseAction_('doubleClick', + command.Name.DOUBLE_CLICK, opt_elementOrButton, opt_button); + } + + /** + * Schedules a keyboard action. + * + * @param {string} description A simple descriptive label for the scheduled + * action. + * @param {!Array<(string|!input.Key)>} keys The keys to send. + * @return {!ActionSequence} A self reference. + * @private + */ + scheduleKeyboardAction_(description, keys) { + let cmd = new command.Command(command.Name.SEND_KEYS_TO_ACTIVE_ELEMENT) + .setParameter('value', keys); + this.schedule_(description, cmd); + return this; + } + + /** + * Performs a modifier key press. The modifier key is <em>not released</em> + * until {@link #keyUp} or {@link #sendKeys} is called. The key press will be + * targetted at the currently focused element. + * + * @param {!input.Key} key The modifier key to push. Must be one of + * {ALT, CONTROL, SHIFT, COMMAND, META}. + * @return {!ActionSequence} A self reference. + * @throws {error.InvalidArgumentError} If the key is not a valid modifier + * key. + */ + keyDown(key) { + checkModifierKey(key); + return this.scheduleKeyboardAction_('keyDown', [key]); + } + + /** + * Performs a modifier key release. The release is targetted at the currently + * focused element. + * @param {!input.Key} key The modifier key to release. Must be one of + * {ALT, CONTROL, SHIFT, COMMAND, META}. + * @return {!ActionSequence} A self reference. + * @throws {error.InvalidArgumentError} If the key is not a valid modifier + * key. + */ + keyUp(key) { + checkModifierKey(key); + return this.scheduleKeyboardAction_('keyUp', [key]); + } + + /** + * Simulates typing multiple keys. Each modifier key encountered in the + * sequence will not be released until it is encountered again. All key events + * will be targetted at the currently focused element. + * + * @param {...(string|!input.Key|!Array<(string|!input.Key)>)} var_args + * The keys to type. + * @return {!ActionSequence} A self reference. + * @throws {Error} If the key is not a valid modifier key. + */ + sendKeys(var_args) { + let keys = flatten(arguments); + return this.scheduleKeyboardAction_('sendKeys', keys); + } +} + + +/** + * Class for defining sequences of user touch interactions. Each sequence + * will not be executed until {@link #perform} is called. + * + * Example: + * + * new TouchSequence(driver). + * tapAndHold({x: 0, y: 0}). + * move({x: 3, y: 4}). + * release({x: 10, y: 10}). + * perform(); + * + */ +class TouchSequence { + /** + * @param {!./webdriver.WebDriver} driver The driver that should be used to + * perform this action sequence. + */ + constructor(driver) { + /** @private {!./webdriver.WebDriver} */ + this.driver_ = driver; + + /** @private {!Array<{description: string, command: !command.Command}>} */ + this.actions_ = []; + } + + /** + * Schedules an action to be executed each time {@link #perform} is called on + * this instance. + * @param {string} description A description of the command. + * @param {!command.Command} command The command. + * @private + */ + schedule_(description, command) { + this.actions_.push({ + description: description, + command: command + }); + } + + /** + * Executes this action sequence. + * @return {!./promise.Promise} A promise that will be resolved once + * this sequence has completed. + */ + perform() { + // Make a protected copy of the scheduled actions. This will protect against + // users defining additional commands before this sequence is actually + // executed. + let actions = this.actions_.concat(); + let driver = this.driver_; + return driver.controlFlow().execute(function() { + actions.forEach(function(action) { + driver.schedule(action.command, action.description); + }); + }, 'TouchSequence.perform'); + } + + /** + * Taps an element. + * + * @param {!./webdriver.WebElement} elem The element to tap. + * @return {!TouchSequence} A self reference. + */ + tap(elem) { + let cmd = new command.Command(command.Name.TOUCH_SINGLE_TAP). + setParameter('element', elem.getId()); + + this.schedule_('tap', cmd); + return this; + } + + /** + * Double taps an element. + * + * @param {!./webdriver.WebElement} elem The element to double tap. + * @return {!TouchSequence} A self reference. + */ + doubleTap(elem) { + let cmd = new command.Command(command.Name.TOUCH_DOUBLE_TAP). + setParameter('element', elem.getId()); + + this.schedule_('doubleTap', cmd); + return this; + } + + /** + * Long press on an element. + * + * @param {!./webdriver.WebElement} elem The element to long press. + * @return {!TouchSequence} A self reference. + */ + longPress(elem) { + let cmd = new command.Command(command.Name.TOUCH_LONG_PRESS). + setParameter('element', elem.getId()); + + this.schedule_('longPress', cmd); + return this; + } + + /** + * Touch down at the given location. + * + * @param {{x: number, y: number}} location The location to touch down at. + * @return {!TouchSequence} A self reference. + */ + tapAndHold(location) { + let cmd = new command.Command(command.Name.TOUCH_DOWN). + setParameter('x', location.x). + setParameter('y', location.y); + + this.schedule_('tapAndHold', cmd); + return this; + } + + /** + * Move a held {@linkplain #tapAndHold touch} to the specified location. + * + * @param {{x: number, y: number}} location The location to move to. + * @return {!TouchSequence} A self reference. + */ + move(location) { + let cmd = new command.Command(command.Name.TOUCH_MOVE). + setParameter('x', location.x). + setParameter('y', location.y); + + this.schedule_('move', cmd); + return this; + } + + /** + * Release a held {@linkplain #tapAndHold touch} at the specified location. + * + * @param {{x: number, y: number}} location The location to release at. + * @return {!TouchSequence} A self reference. + */ + release(location) { + let cmd = new command.Command(command.Name.TOUCH_UP). + setParameter('x', location.x). + setParameter('y', location.y); + + this.schedule_('release', cmd); + return this; + } + + /** + * Scrolls the touch screen by the given offset. + * + * @param {{x: number, y: number}} offset The offset to scroll to. + * @return {!TouchSequence} A self reference. + */ + scroll(offset) { + let cmd = new command.Command(command.Name.TOUCH_SCROLL). + setParameter('xoffset', offset.x). + setParameter('yoffset', offset.y); + + this.schedule_('scroll', cmd); + return this; + } + + /** + * Scrolls the touch screen, starting on `elem` and moving by the specified + * offset. + * + * @param {!./webdriver.WebElement} elem The element where scroll starts. + * @param {{x: number, y: number}} offset The offset to scroll to. + * @return {!TouchSequence} A self reference. + */ + scrollFromElement(elem, offset) { + let cmd = new command.Command(command.Name.TOUCH_SCROLL). + setParameter('element', elem.getId()). + setParameter('xoffset', offset.x). + setParameter('yoffset', offset.y); + + this.schedule_('scrollFromElement', cmd); + return this; + } + + /** + * Flick, starting anywhere on the screen, at speed xspeed and yspeed. + * + * @param {{xspeed: number, yspeed: number}} speed The speed to flick in each + direction, in pixels per second. + * @return {!TouchSequence} A self reference. + */ + flick(speed) { + let cmd = new command.Command(command.Name.TOUCH_FLICK). + setParameter('xspeed', speed.xspeed). + setParameter('yspeed', speed.yspeed); + + this.schedule_('flick', cmd); + return this; + } + + /** + * Flick starting at elem and moving by x and y at specified speed. + * + * @param {!./webdriver.WebElement} elem The element where flick starts. + * @param {{x: number, y: number}} offset The offset to flick to. + * @param {number} speed The speed to flick at in pixels per second. + * @return {!TouchSequence} A self reference. + */ + flickElement(elem, offset, speed) { + let cmd = new command.Command(command.Name.TOUCH_FLICK). + setParameter('element', elem.getId()). + setParameter('xoffset', offset.x). + setParameter('yoffset', offset.y). + setParameter('speed', speed); + + this.schedule_('flickElement', cmd); + return this; + } +} + + +// PUBLIC API + +module.exports = { + ActionSequence: ActionSequence, + TouchSequence: TouchSequence, +}; |