# 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 |
16 | goog.provide('webdriver.ActionSequence');
17 |
18 | goog.require('goog.array');
19 | goog.require('webdriver.Button');
20 | goog.require('webdriver.Command');
21 | goog.require('webdriver.CommandName');
22 | goog.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 | */
43 | webdriver.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 | */
60 | webdriver.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 | */
73 | webdriver.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 | */
100 | webdriver.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 | */
138 | webdriver.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 | */
181 | webdriver.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 | */
209 | webdriver.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 | */
225 | webdriver.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 | */
247 | webdriver.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 | */
274 | webdriver.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 | */
289 | webdriver.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 | */
305 | webdriver.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 | */
322 | webdriver.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 | */
336 | webdriver.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 | */
351 | webdriver.ActionSequence.prototype.sendKeys = function(var_args) {
352 | var keys = goog.array.flatten(goog.array.slice(arguments, 0));
353 | return this.scheduleKeyboardAction_('sendKeys', keys);
354 | };