lib/webdriver/actionsequence.js

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
18goog.provide('webdriver.ActionSequence');
19
20goog.require('goog.array');
21goog.require('webdriver.Button');
22goog.require('webdriver.Command');
23goog.require('webdriver.CommandName');
24goog.require('webdriver.Key');
25
26
27
28/**
29 * Class for defining sequences of complex user interactions. Each sequence
30 * will not be executed until {@link #perform} is called.
31 *
32 * Example:
33 *
34 * new webdriver.ActionSequence(driver).
35 * keyDown(webdriver.Key.SHIFT).
36 * click(element1).
37 * click(element2).
38 * dragAndDrop(element3, element4).
39 * keyUp(webdriver.Key.SHIFT).
40 * perform();
41 *
42 * @param {!webdriver.WebDriver} driver The driver instance to use.
43 * @constructor
44 */
45webdriver.ActionSequence = function(driver) {
46
47 /** @private {!webdriver.WebDriver} */
48 this.driver_ = driver;
49
50 /** @private {!Array.<{description: string, command: !webdriver.Command}>} */
51 this.actions_ = [];
52};
53
54
55/**
56 * Schedules an action to be executed each time {@link #perform} is called on
57 * this instance.
58 * @param {string} description A description of the command.
59 * @param {!webdriver.Command} command The command.
60 * @private
61 */
62webdriver.ActionSequence.prototype.schedule_ = function(description, command) {
63 this.actions_.push({
64 description: description,
65 command: command
66 });
67};
68
69
70/**
71 * Executes this action sequence.
72 * @return {!webdriver.promise.Promise} A promise that will be resolved once
73 * this sequence has completed.
74 */
75webdriver.ActionSequence.prototype.perform = function() {
76 // Make a protected copy of the scheduled actions. This will protect against
77 // users defining additional commands before this sequence is actually
78 // executed.
79 var actions = goog.array.clone(this.actions_);
80 var driver = this.driver_;
81 return driver.controlFlow().execute(function() {
82 goog.array.forEach(actions, function(action) {
83 driver.schedule(action.command, action.description);
84 });
85 }, 'ActionSequence.perform');
86};
87
88
89/**
90 * Moves the mouse. The location to move to may be specified in terms of the
91 * mouse's current location, an offset relative to the top-left corner of an
92 * element, or an element (in which case the middle of the element is used).
93 * @param {(!webdriver.WebElement|{x: number, y: number})} location The
94 * location to drag to, as either another WebElement or an offset in pixels.
95 * @param {{x: number, y: number}=} opt_offset If the target {@code location}
96 * is defined as a {@link webdriver.WebElement}, this parameter defines an
97 * offset within that element. The offset should be specified in pixels
98 * relative to the top-left corner of the element's bounding box. If
99 * omitted, the element's center will be used as the target offset.
100 * @return {!webdriver.ActionSequence} A self reference.
101 */
102webdriver.ActionSequence.prototype.mouseMove = function(location, opt_offset) {
103 var command = new webdriver.Command(webdriver.CommandName.MOVE_TO);
104
105 if (goog.isNumber(location.x)) {
106 setOffset(/** @type {{x: number, y: number}} */(location));
107 } else {
108 command.setParameter('element', location.getRawId());
109 if (opt_offset) {
110 setOffset(opt_offset);
111 }
112 }
113
114 this.schedule_('mouseMove', command);
115 return this;
116
117 /** @param {{x: number, y: number}} offset The offset to use. */
118 function setOffset(offset) {
119 command.setParameter('xoffset', offset.x || 0);
120 command.setParameter('yoffset', offset.y || 0);
121 }
122};
123
124
125/**
126 * Schedules a mouse action.
127 * @param {string} description A simple descriptive label for the scheduled
128 * action.
129 * @param {!webdriver.CommandName} commandName The name of the command.
130 * @param {(webdriver.WebElement|webdriver.Button)=} opt_elementOrButton Either
131 * the element to interact with or the button to click with.
132 * Defaults to {@link webdriver.Button.LEFT} if neither an element nor
133 * button is specified.
134 * @param {webdriver.Button=} opt_button The button to use. Defaults to
135 * {@link webdriver.Button.LEFT}. Ignored if the previous argument is
136 * provided as a button.
137 * @return {!webdriver.ActionSequence} A self reference.
138 * @private
139 */
140webdriver.ActionSequence.prototype.scheduleMouseAction_ = function(
141 description, commandName, opt_elementOrButton, opt_button) {
142 var button;
143 if (goog.isNumber(opt_elementOrButton)) {
144 button = opt_elementOrButton;
145 } else {
146 if (opt_elementOrButton) {
147 this.mouseMove(
148 /** @type {!webdriver.WebElement} */ (opt_elementOrButton));
149 }
150 button = goog.isDef(opt_button) ? opt_button : webdriver.Button.LEFT;
151 }
152
153 var command = new webdriver.Command(commandName).
154 setParameter('button', button);
155 this.schedule_(description, command);
156 return this;
157};
158
159
160/**
161 * Presses a mouse button. The mouse button will not be released until
162 * {@link #mouseUp} is called, regardless of whether that call is made in this
163 * sequence or another. The behavior for out-of-order events (e.g. mouseDown,
164 * click) is undefined.
165 *
166 * If an element is provided, the mouse will first be moved to the center
167 * of that element. This is equivalent to:
168 *
169 * sequence.mouseMove(element).mouseDown()
170 *
171 * Warning: this method currently only supports the left mouse button. See
172 * [issue 4047](http://code.google.com/p/selenium/issues/detail?id=4047).
173 *
174 * @param {(webdriver.WebElement|webdriver.Button)=} opt_elementOrButton Either
175 * the element to interact with or the button to click with.
176 * Defaults to {@link webdriver.Button.LEFT} if neither an element nor
177 * button is specified.
178 * @param {webdriver.Button=} opt_button The button to use. Defaults to
179 * {@link webdriver.Button.LEFT}. Ignored if a button is provided as the
180 * first argument.
181 * @return {!webdriver.ActionSequence} A self reference.
182 */
183webdriver.ActionSequence.prototype.mouseDown = function(opt_elementOrButton,
184 opt_button) {
185 return this.scheduleMouseAction_('mouseDown',
186 webdriver.CommandName.MOUSE_DOWN, opt_elementOrButton, opt_button);
187};
188
189
190/**
191 * Releases a mouse button. Behavior is undefined for calling this function
192 * without a previous call to {@link #mouseDown}.
193 *
194 * If an element is provided, the mouse will first be moved to the center
195 * of that element. This is equivalent to:
196 *
197 * sequence.mouseMove(element).mouseUp()
198 *
199 * Warning: this method currently only supports the left mouse button. See
200 * [issue 4047](http://code.google.com/p/selenium/issues/detail?id=4047).
201 *
202 * @param {(webdriver.WebElement|webdriver.Button)=} opt_elementOrButton Either
203 * the element to interact with or the button to click with.
204 * Defaults to {@link webdriver.Button.LEFT} if neither an element nor
205 * button is specified.
206 * @param {webdriver.Button=} opt_button The button to use. Defaults to
207 * {@link webdriver.Button.LEFT}. Ignored if a button is provided as the
208 * first argument.
209 * @return {!webdriver.ActionSequence} A self reference.
210 */
211webdriver.ActionSequence.prototype.mouseUp = function(opt_elementOrButton,
212 opt_button) {
213 return this.scheduleMouseAction_('mouseUp',
214 webdriver.CommandName.MOUSE_UP, opt_elementOrButton, opt_button);
215};
216
217
218/**
219 * Convenience function for performing a "drag and drop" manuever. The target
220 * element may be moved to the location of another element, or by an offset (in
221 * pixels).
222 * @param {!webdriver.WebElement} element The element to drag.
223 * @param {(!webdriver.WebElement|{x: number, y: number})} location The
224 * location to drag to, either as another WebElement or an offset in pixels.
225 * @return {!webdriver.ActionSequence} A self reference.
226 */
227webdriver.ActionSequence.prototype.dragAndDrop = function(element, location) {
228 return this.mouseDown(element).mouseMove(location).mouseUp();
229};
230
231
232/**
233 * Clicks a mouse button.
234 *
235 * If an element is provided, the mouse will first be moved to the center
236 * of that element. This is equivalent to:
237 *
238 * sequence.mouseMove(element).click()
239 *
240 * @param {(webdriver.WebElement|webdriver.Button)=} opt_elementOrButton Either
241 * the element to interact with or the button to click with.
242 * Defaults to {@link webdriver.Button.LEFT} if neither an element nor
243 * button is specified.
244 * @param {webdriver.Button=} opt_button The button to use. Defaults to
245 * {@link webdriver.Button.LEFT}. Ignored if a button is provided as the
246 * first argument.
247 * @return {!webdriver.ActionSequence} A self reference.
248 */
249webdriver.ActionSequence.prototype.click = function(opt_elementOrButton,
250 opt_button) {
251 return this.scheduleMouseAction_('click',
252 webdriver.CommandName.CLICK, opt_elementOrButton, opt_button);
253};
254
255
256/**
257 * Double-clicks a mouse button.
258 *
259 * If an element is provided, the mouse will first be moved to the center of
260 * that element. This is equivalent to:
261 *
262 * sequence.mouseMove(element).doubleClick()
263 *
264 * Warning: this method currently only supports the left mouse button. See
265 * [issue 4047](http://code.google.com/p/selenium/issues/detail?id=4047).
266 *
267 * @param {(webdriver.WebElement|webdriver.Button)=} opt_elementOrButton Either
268 * the element to interact with or the button to click with.
269 * Defaults to {@link webdriver.Button.LEFT} if neither an element nor
270 * button is specified.
271 * @param {webdriver.Button=} opt_button The button to use. Defaults to
272 * {@link webdriver.Button.LEFT}. Ignored if a button is provided as the
273 * first argument.
274 * @return {!webdriver.ActionSequence} A self reference.
275 */
276webdriver.ActionSequence.prototype.doubleClick = function(opt_elementOrButton,
277 opt_button) {
278 return this.scheduleMouseAction_('doubleClick',
279 webdriver.CommandName.DOUBLE_CLICK, opt_elementOrButton, opt_button);
280};
281
282
283/**
284 * Schedules a keyboard action.
285 * @param {string} description A simple descriptive label for the scheduled
286 * action.
287 * @param {!Array.<(string|!webdriver.Key)>} keys The keys to send.
288 * @return {!webdriver.ActionSequence} A self reference.
289 * @private
290 */
291webdriver.ActionSequence.prototype.scheduleKeyboardAction_ = function(
292 description, keys) {
293 var command =
294 new webdriver.Command(webdriver.CommandName.SEND_KEYS_TO_ACTIVE_ELEMENT).
295 setParameter('value', keys);
296 this.schedule_(description, command);
297 return this;
298};
299
300
301/**
302 * Checks that a key is a modifier key.
303 * @param {!webdriver.Key} key The key to check.
304 * @throws {Error} If the key is not a modifier key.
305 * @private
306 */
307webdriver.ActionSequence.checkModifierKey_ = function(key) {
308 if (key !== webdriver.Key.ALT && key !== webdriver.Key.CONTROL &&
309 key !== webdriver.Key.SHIFT && key !== webdriver.Key.COMMAND) {
310 throw Error('Not a modifier key');
311 }
312};
313
314
315/**
316 * Performs a modifier key press. The modifier key is <em>not released</em>
317 * until {@link #keyUp} or {@link #sendKeys} is called. The key press will be
318 * targetted at the currently focused element.
319 * @param {!webdriver.Key} key The modifier key to push. Must be one of
320 * {ALT, CONTROL, SHIFT, COMMAND, META}.
321 * @return {!webdriver.ActionSequence} A self reference.
322 * @throws {Error} If the key is not a valid modifier key.
323 */
324webdriver.ActionSequence.prototype.keyDown = function(key) {
325 webdriver.ActionSequence.checkModifierKey_(key);
326 return this.scheduleKeyboardAction_('keyDown', [key]);
327};
328
329
330/**
331 * Performs a modifier key release. The release is targetted at the currently
332 * focused element.
333 * @param {!webdriver.Key} key The modifier key to release. Must be one of
334 * {ALT, CONTROL, SHIFT, COMMAND, META}.
335 * @return {!webdriver.ActionSequence} A self reference.
336 * @throws {Error} If the key is not a valid modifier key.
337 */
338webdriver.ActionSequence.prototype.keyUp = function(key) {
339 webdriver.ActionSequence.checkModifierKey_(key);
340 return this.scheduleKeyboardAction_('keyUp', [key]);
341};
342
343
344/**
345 * Simulates typing multiple keys. Each modifier key encountered in the
346 * sequence will not be released until it is encountered again. All key events
347 * will be targetted at the currently focused element.
348 * @param {...(string|!webdriver.Key|!Array.<(string|!webdriver.Key)>)} var_args
349 * The keys to type.
350 * @return {!webdriver.ActionSequence} A self reference.
351 * @throws {Error} If the key is not a valid modifier key.
352 */
353webdriver.ActionSequence.prototype.sendKeys = function(var_args) {
354 var keys = goog.array.flatten(goog.array.slice(arguments, 0));
355 return this.scheduleKeyboardAction_('sendKeys', keys);
356};