lib/webdriver/actionsequence.js

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