# 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 |
18 | goog.provide('webdriver.ActionSequence');
19 |
20 | goog.require('goog.array');
21 | goog.require('webdriver.Button');
22 | goog.require('webdriver.Command');
23 | goog.require('webdriver.CommandName');
24 | goog.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 | */
45 | webdriver.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 | */
62 | webdriver.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 | */
75 | webdriver.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 | */
102 | webdriver.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 | */
140 | webdriver.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 | */
183 | webdriver.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 | */
211 | webdriver.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 | */
227 | webdriver.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 | */
249 | webdriver.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 | */
276 | webdriver.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 | */
291 | webdriver.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 | */
307 | webdriver.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 | */
324 | webdriver.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 | */
338 | webdriver.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 | */
353 | webdriver.ActionSequence.prototype.sendKeys = function(var_args) {
354 | var keys = goog.array.flatten(goog.array.slice(arguments, 0));
355 | return this.scheduleKeyboardAction_('sendKeys', keys);
356 | };