# lib/webdriver/webdriver.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 | /**
19 | * @fileoverview The heart of the WebDriver JavaScript API.
20 | */
21 |
22 | goog.provide('webdriver.Alert');
23 | goog.provide('webdriver.AlertPromise');
24 | goog.provide('webdriver.FileDetector');
25 | goog.provide('webdriver.UnhandledAlertError');
26 | goog.provide('webdriver.WebDriver');
27 | goog.provide('webdriver.WebElement');
28 | goog.provide('webdriver.WebElementPromise');
29 |
30 | goog.require('bot.Error');
31 | goog.require('bot.ErrorCode');
32 | goog.require('bot.response');
33 | goog.require('goog.array');
34 | goog.require('goog.object');
35 | goog.require('webdriver.ActionSequence');
36 | goog.require('webdriver.Command');
37 | goog.require('webdriver.CommandName');
38 | goog.require('webdriver.Key');
39 | goog.require('webdriver.Locator');
40 | goog.require('webdriver.Serializable');
41 | goog.require('webdriver.Session');
42 | goog.require('webdriver.TouchSequence');
43 | goog.require('webdriver.logging');
44 | goog.require('webdriver.promise');
45 | goog.require('webdriver.until');
46 |
47 |
48 | //////////////////////////////////////////////////////////////////////////////
49 | //
50 | // webdriver.WebDriver
51 | //
52 | //////////////////////////////////////////////////////////////////////////////
53 |
54 |
55 |
56 | /**
57 | * Creates a new WebDriver client, which provides control over a browser.
58 | *
59 | * Every WebDriver command returns a {@code webdriver.promise.Promise} that
60 | * represents the result of that command. Callbacks may be registered on this
61 | * object to manipulate the command result or catch an expected error. Any
62 | * commands scheduled with a callback are considered sub-commands and will
63 | * execute before the next command in the current frame. For example:
64 | *
65 | * var message = [];
66 | * driver.call(message.push, message, 'a').then(function() {
67 | * driver.call(message.push, message, 'b');
68 | * });
69 | * driver.call(message.push, message, 'c');
70 | * driver.call(function() {
71 | * alert('message is abc? ' + (message.join('') == 'abc'));
72 | * });
73 | *
74 | * @param {!(webdriver.Session|webdriver.promise.Promise)} session Either a
75 | * known session or a promise that will be resolved to a session.
76 | * @param {!webdriver.CommandExecutor} executor The executor to use when
77 | * sending commands to the browser.
78 | * @param {webdriver.promise.ControlFlow=} opt_flow The flow to
79 | * schedule commands through. Defaults to the active flow object.
80 | * @constructor
81 | */
82 | webdriver.WebDriver = function(session, executor, opt_flow) {
83 |
84 | /** @private {!(webdriver.Session|webdriver.promise.Promise)} */
85 | this.session_ = session;
86 |
87 | /** @private {!webdriver.CommandExecutor} */
88 | this.executor_ = executor;
89 |
90 | /** @private {!webdriver.promise.ControlFlow} */
91 | this.flow_ = opt_flow || webdriver.promise.controlFlow();
92 |
93 | /** @private {webdriver.FileDetector} */
94 | this.fileDetector_ = null;
95 | };
96 |
97 |
98 | /**
99 | * Creates a new WebDriver client for an existing session.
100 | * @param {!webdriver.CommandExecutor} executor Command executor to use when
101 | * querying for session details.
102 | * @param {string} sessionId ID of the session to attach to.
103 | * @param {webdriver.promise.ControlFlow=} opt_flow The control flow all driver
104 | * commands should execute under. Defaults to the
105 | * {@link webdriver.promise.controlFlow() currently active} control flow.
106 | * @return {!webdriver.WebDriver} A new client for the specified session.
107 | */
108 | webdriver.WebDriver.attachToSession = function(executor, sessionId, opt_flow) {
109 | return webdriver.WebDriver.acquireSession_(executor,
110 | new webdriver.Command(webdriver.CommandName.DESCRIBE_SESSION).
111 | setParameter('sessionId', sessionId),
112 | 'WebDriver.attachToSession()',
113 | opt_flow);
114 | };
115 |
116 |
117 | /**
118 | * Creates a new WebDriver session.
119 | * @param {!webdriver.CommandExecutor} executor The executor to create the new
120 | * session with.
121 | * @param {!webdriver.Capabilities} desiredCapabilities The desired
122 | * capabilities for the new session.
123 | * @param {webdriver.promise.ControlFlow=} opt_flow The control flow all driver
124 | * commands should execute under, including the initial session creation.
125 | * Defaults to the {@link webdriver.promise.controlFlow() currently active}
126 | * control flow.
127 | * @return {!webdriver.WebDriver} The driver for the newly created session.
128 | */
129 | webdriver.WebDriver.createSession = function(
130 | executor, desiredCapabilities, opt_flow) {
131 | return webdriver.WebDriver.acquireSession_(executor,
132 | new webdriver.Command(webdriver.CommandName.NEW_SESSION).
133 | setParameter('desiredCapabilities', desiredCapabilities),
134 | 'WebDriver.createSession()',
135 | opt_flow);
136 | };
137 |
138 |
139 | /**
140 | * Sends a command to the server that is expected to return the details for a
141 | * {@link webdriver.Session}. This may either be an existing session, or a
142 | * newly created one.
143 | * @param {!webdriver.CommandExecutor} executor Command executor to use when
144 | * querying for session details.
145 | * @param {!webdriver.Command} command The command to send to fetch the session
146 | * details.
147 | * @param {string} description A descriptive debug label for this action.
148 | * @param {webdriver.promise.ControlFlow=} opt_flow The control flow all driver
149 | * commands should execute under. Defaults to the
150 | * {@link webdriver.promise.controlFlow() currently active} control flow.
151 | * @return {!webdriver.WebDriver} A new WebDriver client for the session.
152 | * @private
153 | */
154 | webdriver.WebDriver.acquireSession_ = function(
155 | executor, command, description, opt_flow) {
156 | var flow = opt_flow || webdriver.promise.controlFlow();
157 | var session = flow.execute(function() {
158 | return webdriver.WebDriver.executeCommand_(executor, command).
159 | then(function(response) {
160 | bot.response.checkResponse(response);
161 | return new webdriver.Session(response['sessionId'],
162 | response['value']);
163 | });
164 | }, description);
165 | return new webdriver.WebDriver(session, executor, flow);
166 | };
167 |
168 |
169 | /**
170 | * Converts an object to its JSON representation in the WebDriver wire protocol.
171 | * When converting values of type object, the following steps will be taken:
172 | * <ol>
173 | * <li>if the object is a WebElement, the return value will be the element's
174 | * server ID
175 | * <li>if the object is a Serializable, its
176 | * {@link webdriver.Serializable#serialize} function will be invoked and
177 | * this algorithm will recursively be applied to the result
178 | * <li>if the object provides a "toJSON" function, this algorithm will
179 | * recursively be applied to the result of that function
180 | * <li>otherwise, the value of each key will be recursively converted according
181 | * to the rules above.
182 | * </ol>
183 | *
184 | * @param {*} obj The object to convert.
185 | * @return {!webdriver.promise.Promise.<?>} A promise that will resolve to the
186 | * input value's JSON representation.
187 | * @private
188 | * @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol
189 | */
190 | webdriver.WebDriver.toWireValue_ = function(obj) {
191 | if (webdriver.promise.isPromise(obj)) {
192 | return obj.then(webdriver.WebDriver.toWireValue_);
193 | }
194 | return webdriver.promise.fulfilled(convertValue(obj));
195 |
196 | function convertValue(value) {
197 | switch (goog.typeOf(value)) {
198 | case 'array':
199 | return convertKeys(value, true);
200 | case 'object':
201 | // NB: WebElement is a Serializable, but we know its serialized form
202 | // is a promise for its wire format. This is a micro optimization to
203 | // avoid creating extra promises by recursing on the promised id.
204 | if (value instanceof webdriver.WebElement) {
205 | return value.getId();
206 | }
207 | if (value instanceof webdriver.Serializable) {
208 | return webdriver.WebDriver.toWireValue_(value.serialize());
209 | }
210 | if (goog.isFunction(value.toJSON)) {
211 | return webdriver.WebDriver.toWireValue_(value.toJSON());
212 | }
213 | if (goog.isNumber(value.nodeType) && goog.isString(value.nodeName)) {
214 | throw new TypeError(
215 | 'Invalid argument type: ' + value.nodeName +
216 | '(' + value.nodeType + ')');
217 | }
218 | return convertKeys(value, false);
219 | case 'function':
220 | return '' + value;
221 | case 'undefined':
222 | return null;
223 | default:
224 | return value;
225 | }
226 | }
227 |
228 | function convertKeys(obj, isArray) {
229 | var numKeys = isArray ? obj.length : goog.object.getCount(obj);
230 | var ret = isArray ? new Array(numKeys) : {};
231 | if (!numKeys) {
232 | return webdriver.promise.fulfilled(ret);
233 | }
234 |
235 | var numResolved = 0;
236 | var done = webdriver.promise.defer();
237 |
238 | // forEach will stop iteration at undefined, where we want to convert
239 | // these to null and keep iterating.
240 | var forEachKey = !isArray ? goog.object.forEach : function(arr, fn) {
241 | var n = arr.length;
242 | for (var i = 0; i < n; i++) {
243 | fn(arr[i], i);
244 | }
245 | };
246 |
247 | forEachKey(obj, function(value, key) {
248 | if (webdriver.promise.isPromise(value)) {
249 | value.then(webdriver.WebDriver.toWireValue_).
250 | then(setValue, done.reject);
251 | } else {
252 | webdriver.promise.asap(convertValue(value), setValue, done.reject);
253 | }
254 |
255 | function setValue(value) {
256 | ret[key] = value;
257 | maybeFulfill();
258 | }
259 | });
260 |
261 | return done.promise;
262 |
263 | function maybeFulfill() {
264 | if (++numResolved === numKeys) {
265 | done.fulfill(ret);
266 | }
267 | }
268 | }
269 | };
270 |
271 |
272 | /**
273 | * Converts a value from its JSON representation according to the WebDriver wire
274 | * protocol. Any JSON object containing a
275 | * {@code webdriver.WebElement.ELEMENT_KEY} key will be decoded to a
276 | * {@code webdriver.WebElement} object. All other values will be passed through
277 | * as is.
278 | * @param {!webdriver.WebDriver} driver The driver instance to use as the
279 | * parent of any unwrapped {@code webdriver.WebElement} values.
280 | * @param {*} value The value to convert.
281 | * @return {*} The converted value.
282 | * @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol
283 | * @private
284 | */
285 | webdriver.WebDriver.fromWireValue_ = function(driver, value) {
286 | if (goog.isArray(value)) {
287 | value = goog.array.map(/**@type {goog.array.ArrayLike}*/ (value),
288 | goog.partial(webdriver.WebDriver.fromWireValue_, driver));
289 | } else if (value && goog.isObject(value) && !goog.isFunction(value)) {
290 | if (webdriver.WebElement.ELEMENT_KEY in value) {
291 | value = new webdriver.WebElement(driver, value);
292 | } else {
293 | value = goog.object.map(/**@type {!Object}*/ (value),
294 | goog.partial(webdriver.WebDriver.fromWireValue_, driver));
295 | }
296 | }
297 | return value;
298 | };
299 |
300 |
301 | /**
302 | * Translates a command to its wire-protocol representation before passing it
303 | * to the given {@code executor} for execution.
304 | * @param {!webdriver.CommandExecutor} executor The executor to use.
305 | * @param {!webdriver.Command} command The command to execute.
306 | * @return {!webdriver.promise.Promise} A promise that will resolve with the
307 | * command response.
308 | * @private
309 | */
310 | webdriver.WebDriver.executeCommand_ = function(executor, command) {
311 | return webdriver.WebDriver.toWireValue_(command.getParameters()).
312 | then(function(parameters) {
313 | command.setParameters(parameters);
314 | return webdriver.promise.checkedNodeCall(
315 | goog.bind(executor.execute, executor, command));
316 | });
317 | };
318 |
319 |
320 | /**
321 | * @return {!webdriver.promise.ControlFlow} The control flow used by this
322 | * instance.
323 | */
324 | webdriver.WebDriver.prototype.controlFlow = function() {
325 | return this.flow_;
326 | };
327 |
328 |
329 | /**
330 | * Schedules a {@code webdriver.Command} to be executed by this driver's
331 | * {@code webdriver.CommandExecutor}.
332 | * @param {!webdriver.Command} command The command to schedule.
333 | * @param {string} description A description of the command for debugging.
334 | * @return {!webdriver.promise.Promise.<T>} A promise that will be resolved
335 | * with the command result.
336 | * @template T
337 | */
338 | webdriver.WebDriver.prototype.schedule = function(command, description) {
339 | var self = this;
340 |
341 | checkHasNotQuit();
342 | command.setParameter('sessionId', this.session_);
343 |
344 | // If any of the command parameters are rejected promises, those
345 | // rejections may be reported as unhandled before the control flow
346 | // attempts to execute the command. To ensure parameters errors
347 | // propagate through the command itself, we resolve all of the
348 | // command parameters now, but suppress any errors until the ControlFlow
349 | // actually executes the command. This addresses scenarios like catching
350 | // an element not found error in:
351 | //
352 | // driver.findElement(By.id('foo')).click().thenCatch(function(e) {
353 | // if (e.code === bot.ErrorCode.NO_SUCH_ELEMENT) {
354 | // // Do something.
355 | // }
356 | // });
357 | var prepCommand = webdriver.WebDriver.toWireValue_(command.getParameters());
358 | prepCommand.thenCatch(goog.nullFunction);
359 |
360 | var flow = this.flow_;
361 | var executor = this.executor_;
362 | return flow.execute(function() {
363 | // A call to WebDriver.quit() may have been scheduled in the same event
364 | // loop as this |command|, which would prevent us from detecting that the
365 | // driver has quit above. Therefore, we need to make another quick check.
366 | // We still check above so we can fail as early as possible.
367 | checkHasNotQuit();
368 |
369 | // Retrieve resolved command parameters; any previously suppressed errors
370 | // will now propagate up through the control flow as part of the command
371 | // execution.
372 | return prepCommand.then(function(parameters) {
373 | command.setParameters(parameters);
374 | return webdriver.promise.checkedNodeCall(
375 | goog.bind(executor.execute, executor, command));
376 | });
377 | }, description).then(function(response) {
378 | try {
379 | bot.response.checkResponse(response);
380 | } catch (ex) {
381 | var value = response['value'];
382 | if (ex.code === bot.ErrorCode.UNEXPECTED_ALERT_OPEN) {
383 | var text = value && value['alert'] ? value['alert']['text'] : '';
384 | throw new webdriver.UnhandledAlertError(ex.message, text,
385 | new webdriver.Alert(self, text));
386 | }
387 | throw ex;
388 | }
389 | return webdriver.WebDriver.fromWireValue_(self, response['value']);
390 | });
391 |
392 | function checkHasNotQuit() {
393 | if (!self.session_) {
394 | throw new Error('This driver instance does not have a valid session ID ' +
395 | '(did you call WebDriver.quit()?) and may no longer be ' +
396 | 'used.');
397 | }
398 | }
399 | };
400 |
401 |
402 | /**
403 | * Sets the {@linkplain webdriver.FileDetector file detector} that should be
404 | * used with this instance.
405 | * @param {webdriver.FileDetector} detector The detector to use or {@code null}.
406 | */
407 | webdriver.WebDriver.prototype.setFileDetector = function(detector) {
408 | this.fileDetector_ = detector;
409 | };
410 |
411 |
412 | // ----------------------------------------------------------------------------
413 | // Client command functions:
414 | // ----------------------------------------------------------------------------
415 |
416 |
417 | /**
418 | * @return {!webdriver.promise.Promise.<!webdriver.Session>} A promise for this
419 | * client's session.
420 | */
421 | webdriver.WebDriver.prototype.getSession = function() {
422 | return webdriver.promise.when(this.session_);
423 | };
424 |
425 |
426 | /**
427 | * @return {!webdriver.promise.Promise.<!webdriver.Capabilities>} A promise
428 | * that will resolve with the this instance's capabilities.
429 | */
430 | webdriver.WebDriver.prototype.getCapabilities = function() {
431 | return webdriver.promise.when(this.session_, function(session) {
432 | return session.getCapabilities();
433 | });
434 | };
435 |
436 |
437 | /**
438 | * Schedules a command to quit the current session. After calling quit, this
439 | * instance will be invalidated and may no longer be used to issue commands
440 | * against the browser.
441 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
442 | * when the command has completed.
443 | */
444 | webdriver.WebDriver.prototype.quit = function() {
445 | var result = this.schedule(
446 | new webdriver.Command(webdriver.CommandName.QUIT),
447 | 'WebDriver.quit()');
448 | // Delete our session ID when the quit command finishes; this will allow us to
449 | // throw an error when attemnpting to use a driver post-quit.
450 | return result.thenFinally(goog.bind(function() {
451 | delete this.session_;
452 | }, this));
453 | };
454 |
455 |
456 | /**
457 | * Creates a new action sequence using this driver. The sequence will not be
458 | * scheduled for execution until {@link webdriver.ActionSequence#perform} is
459 | * called. Example:
460 | *
461 | * driver.actions().
462 | * mouseDown(element1).
463 | * mouseMove(element2).
464 | * mouseUp().
465 | * perform();
466 | *
467 | * @return {!webdriver.ActionSequence} A new action sequence for this instance.
468 | */
469 | webdriver.WebDriver.prototype.actions = function() {
470 | return new webdriver.ActionSequence(this);
471 | };
472 |
473 |
474 | /**
475 | * Creates a new touch sequence using this driver. The sequence will not be
476 | * scheduled for execution until {@link webdriver.TouchSequence#perform} is
477 | * called. Example:
478 | *
479 | * driver.touchActions().
480 | * tap(element1).
481 | * doubleTap(element2).
482 | * perform();
483 | *
484 | * @return {!webdriver.TouchSequence} A new touch sequence for this instance.
485 | */
486 | webdriver.WebDriver.prototype.touchActions = function() {
487 | return new webdriver.TouchSequence(this);
488 | };
489 |
490 |
491 | /**
492 | * Schedules a command to execute JavaScript in the context of the currently
493 | * selected frame or window. The script fragment will be executed as the body
494 | * of an anonymous function. If the script is provided as a function object,
495 | * that function will be converted to a string for injection into the target
496 | * window.
497 | *
498 | * Any arguments provided in addition to the script will be included as script
499 | * arguments and may be referenced using the {@code arguments} object.
500 | * Arguments may be a boolean, number, string, or {@code webdriver.WebElement}.
501 | * Arrays and objects may also be used as script arguments as long as each item
502 | * adheres to the types previously mentioned.
503 | *
504 | * The script may refer to any variables accessible from the current window.
505 | * Furthermore, the script will execute in the window's context, thus
506 | * {@code document} may be used to refer to the current document. Any local
507 | * variables will not be available once the script has finished executing,
508 | * though global variables will persist.
509 | *
510 | * If the script has a return value (i.e. if the script contains a return
511 | * statement), then the following steps will be taken for resolving this
512 | * functions return value:
513 | *
514 | * - For a HTML element, the value will resolve to a
515 | * {@link webdriver.WebElement}
516 | * - Null and undefined return values will resolve to null</li>
517 | * - Booleans, numbers, and strings will resolve as is</li>
518 | * - Functions will resolve to their string representation</li>
519 | * - For arrays and objects, each member item will be converted according to
520 | * the rules above
521 | *
522 | * @param {!(string|Function)} script The script to execute.
523 | * @param {...*} var_args The arguments to pass to the script.
524 | * @return {!webdriver.promise.Promise.<T>} A promise that will resolve to the
525 | * scripts return value.
526 | * @template T
527 | */
528 | webdriver.WebDriver.prototype.executeScript = function(script, var_args) {
529 | if (goog.isFunction(script)) {
530 | script = 'return (' + script + ').apply(null, arguments);';
531 | }
532 | var args = arguments.length > 1 ? goog.array.slice(arguments, 1) : [];
533 | return this.schedule(
534 | new webdriver.Command(webdriver.CommandName.EXECUTE_SCRIPT).
535 | setParameter('script', script).
536 | setParameter('args', args),
537 | 'WebDriver.executeScript()');
538 | };
539 |
540 |
541 | /**
542 | * Schedules a command to execute asynchronous JavaScript in the context of the
543 | * currently selected frame or window. The script fragment will be executed as
544 | * the body of an anonymous function. If the script is provided as a function
545 | * object, that function will be converted to a string for injection into the
546 | * target window.
547 | *
548 | * Any arguments provided in addition to the script will be included as script
549 | * arguments and may be referenced using the {@code arguments} object.
550 | * Arguments may be a boolean, number, string, or {@code webdriver.WebElement}.
551 | * Arrays and objects may also be used as script arguments as long as each item
552 | * adheres to the types previously mentioned.
553 | *
554 | * Unlike executing synchronous JavaScript with {@link #executeScript},
555 | * scripts executed with this function must explicitly signal they are finished
556 | * by invoking the provided callback. This callback will always be injected
557 | * into the executed function as the last argument, and thus may be referenced
558 | * with {@code arguments[arguments.length - 1]}. The following steps will be
559 | * taken for resolving this functions return value against the first argument
560 | * to the script's callback function:
561 | *
562 | * - For a HTML element, the value will resolve to a
563 | * {@link webdriver.WebElement}
564 | * - Null and undefined return values will resolve to null
565 | * - Booleans, numbers, and strings will resolve as is
566 | * - Functions will resolve to their string representation
567 | * - For arrays and objects, each member item will be converted according to
568 | * the rules above
569 | *
570 | * __Example #1:__ Performing a sleep that is synchronized with the currently
571 | * selected window:
572 | *
573 | * var start = new Date().getTime();
574 | * driver.executeAsyncScript(
575 | * 'window.setTimeout(arguments[arguments.length - 1], 500);').
576 | * then(function() {
577 | * console.log(
578 | * 'Elapsed time: ' + (new Date().getTime() - start) + ' ms');
579 | * });
580 | *
581 | * __Example #2:__ Synchronizing a test with an AJAX application:
582 | *
583 | * var button = driver.findElement(By.id('compose-button'));
584 | * button.click();
585 | * driver.executeAsyncScript(
586 | * 'var callback = arguments[arguments.length - 1];' +
587 | * 'mailClient.getComposeWindowWidget().onload(callback);');
588 | * driver.switchTo().frame('composeWidget');
589 | * driver.findElement(By.id('to')).sendKeys('dog@example.com');
590 | *
591 | * __Example #3:__ Injecting a XMLHttpRequest and waiting for the result. In
592 | * this example, the inject script is specified with a function literal. When
593 | * using this format, the function is converted to a string for injection, so it
594 | * should not reference any symbols not defined in the scope of the page under
595 | * test.
596 | *
597 | * driver.executeAsyncScript(function() {
598 | * var callback = arguments[arguments.length - 1];
599 | * var xhr = new XMLHttpRequest();
600 | * xhr.open("GET", "/resource/data.json", true);
601 | * xhr.onreadystatechange = function() {
602 | * if (xhr.readyState == 4) {
603 | * callback(xhr.responseText);
604 | * }
605 | * }
606 | * xhr.send('');
607 | * }).then(function(str) {
608 | * console.log(JSON.parse(str)['food']);
609 | * });
610 | *
611 | * @param {!(string|Function)} script The script to execute.
612 | * @param {...*} var_args The arguments to pass to the script.
613 | * @return {!webdriver.promise.Promise.<T>} A promise that will resolve to the
614 | * scripts return value.
615 | * @template T
616 | */
617 | webdriver.WebDriver.prototype.executeAsyncScript = function(script, var_args) {
618 | if (goog.isFunction(script)) {
619 | script = 'return (' + script + ').apply(null, arguments);';
620 | }
621 | return this.schedule(
622 | new webdriver.Command(webdriver.CommandName.EXECUTE_ASYNC_SCRIPT).
623 | setParameter('script', script).
624 | setParameter('args', goog.array.slice(arguments, 1)),
625 | 'WebDriver.executeScript()');
626 | };
627 |
628 |
629 | /**
630 | * Schedules a command to execute a custom function.
631 | * @param {function(...): (T|webdriver.promise.Promise.<T>)} fn The function to
632 | * execute.
633 | * @param {Object=} opt_scope The object in whose scope to execute the function.
634 | * @param {...*} var_args Any arguments to pass to the function.
635 | * @return {!webdriver.promise.Promise.<T>} A promise that will be resolved'
636 | * with the function's result.
637 | * @template T
638 | */
639 | webdriver.WebDriver.prototype.call = function(fn, opt_scope, var_args) {
640 | var args = goog.array.slice(arguments, 2);
641 | var flow = this.flow_;
642 | return flow.execute(function() {
643 | return webdriver.promise.fullyResolved(args).then(function(args) {
644 | if (webdriver.promise.isGenerator(fn)) {
645 | args.unshift(fn, opt_scope);
646 | return webdriver.promise.consume.apply(null, args);
647 | }
648 | return fn.apply(opt_scope, args);
649 | });
650 | }, 'WebDriver.call(' + (fn.name || 'function') + ')');
651 | };
652 |
653 |
654 | /**
655 | * Schedules a command to wait for a condition to hold. The condition may be
656 | * specified by a {@link webdriver.until.Condition}, as a custom function, or
657 | * as a {@link webdriver.promise.Promise}.
658 | *
659 | * For a {@link webdriver.until.Condition} or function, the wait will repeatedly
660 | * evaluate the condition until it returns a truthy value. If any errors occur
661 | * while evaluating the condition, they will be allowed to propagate. In the
662 | * event a condition returns a {@link webdriver.promise.Promise promise}, the
663 | * polling loop will wait for it to be resolved and use the resolved value for
664 | * whether the condition has been satisified. Note the resolution time for
665 | * a promise is factored into whether a wait has timed out.
666 | *
667 | * *Example:* waiting up to 10 seconds for an element to be present and visible
668 | * on the page.
669 | *
670 | * var button = driver.wait(until.elementLocated(By.id('foo')), 10000);
671 | * button.click();
672 | *
673 | * This function may also be used to block the command flow on the resolution
674 | * of a {@link webdriver.promise.Promise promise}. When given a promise, the
675 | * command will simply wait for its resolution before completing. A timeout may
676 | * be provided to fail the command if the promise does not resolve before the
677 | * timeout expires.
678 | *
679 | * *Example:* Suppose you have a function, `startTestServer`, that returns a
680 | * promise for when a server is ready for requests. You can block a `WebDriver`
681 | * client on this promise with:
682 | *
683 | * var started = startTestServer();
684 | * driver.wait(started, 5 * 1000, 'Server should start within 5 seconds');
685 | * driver.get(getServerUrl());
686 | *
687 | * @param {!(webdriver.promise.Promise<T>|
688 | * webdriver.until.Condition<T>|
689 | * function(!webdriver.WebDriver): T)} condition The condition to
690 | * wait on, defined as a promise, condition object, or a function to
691 | * evaluate as a condition.
692 | * @param {number=} opt_timeout How long to wait for the condition to be true.
693 | * @param {string=} opt_message An optional message to use if the wait times
694 | * out.
695 | * @return {!webdriver.promise.Promise<T>} A promise that will be fulfilled
696 | * with the first truthy value returned by the condition function, or
697 | * rejected if the condition times out.
698 | * @template T
699 | */
700 | webdriver.WebDriver.prototype.wait = function(
701 | condition, opt_timeout, opt_message) {
702 | if (webdriver.promise.isPromise(condition)) {
703 | return this.flow_.wait(
704 | /** @type {!webdriver.promise.Promise} */(condition),
705 | opt_timeout, opt_message);
706 | }
707 |
708 | var message = opt_message;
709 | var fn = /** @type {!Function} */(condition);
710 | if (condition instanceof webdriver.until.Condition) {
711 | message = message || condition.description();
712 | fn = condition.fn;
713 | }
714 |
715 | var driver = this;
716 | return this.flow_.wait(function() {
717 | if (webdriver.promise.isGenerator(fn)) {
718 | return webdriver.promise.consume(fn, null, [driver]);
719 | }
720 | return fn(driver);
721 | }, opt_timeout, message);
722 | };
723 |
724 |
725 | /**
726 | * Schedules a command to make the driver sleep for the given amount of time.
727 | * @param {number} ms The amount of time, in milliseconds, to sleep.
728 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
729 | * when the sleep has finished.
730 | */
731 | webdriver.WebDriver.prototype.sleep = function(ms) {
732 | return this.flow_.timeout(ms, 'WebDriver.sleep(' + ms + ')');
733 | };
734 |
735 |
736 | /**
737 | * Schedules a command to retrieve they current window handle.
738 | * @return {!webdriver.promise.Promise.<string>} A promise that will be
739 | * resolved with the current window handle.
740 | */
741 | webdriver.WebDriver.prototype.getWindowHandle = function() {
742 | return this.schedule(
743 | new webdriver.Command(webdriver.CommandName.GET_CURRENT_WINDOW_HANDLE),
744 | 'WebDriver.getWindowHandle()');
745 | };
746 |
747 |
748 | /**
749 | * Schedules a command to retrieve the current list of available window handles.
750 | * @return {!webdriver.promise.Promise.<!Array.<string>>} A promise that will
751 | * be resolved with an array of window handles.
752 | */
753 | webdriver.WebDriver.prototype.getAllWindowHandles = function() {
754 | return this.schedule(
755 | new webdriver.Command(webdriver.CommandName.GET_WINDOW_HANDLES),
756 | 'WebDriver.getAllWindowHandles()');
757 | };
758 |
759 |
760 | /**
761 | * Schedules a command to retrieve the current page's source. The page source
762 | * returned is a representation of the underlying DOM: do not expect it to be
763 | * formatted or escaped in the same way as the response sent from the web
764 | * server.
765 | * @return {!webdriver.promise.Promise.<string>} A promise that will be
766 | * resolved with the current page source.
767 | */
768 | webdriver.WebDriver.prototype.getPageSource = function() {
769 | return this.schedule(
770 | new webdriver.Command(webdriver.CommandName.GET_PAGE_SOURCE),
771 | 'WebDriver.getAllWindowHandles()');
772 | };
773 |
774 |
775 | /**
776 | * Schedules a command to close the current window.
777 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
778 | * when this command has completed.
779 | */
780 | webdriver.WebDriver.prototype.close = function() {
781 | return this.schedule(new webdriver.Command(webdriver.CommandName.CLOSE),
782 | 'WebDriver.close()');
783 | };
784 |
785 |
786 | /**
787 | * Schedules a command to navigate to the given URL.
788 | * @param {string} url The fully qualified URL to open.
789 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
790 | * when the document has finished loading.
791 | */
792 | webdriver.WebDriver.prototype.get = function(url) {
793 | return this.navigate().to(url);
794 | };
795 |
796 |
797 | /**
798 | * Schedules a command to retrieve the URL of the current page.
799 | * @return {!webdriver.promise.Promise.<string>} A promise that will be
800 | * resolved with the current URL.
801 | */
802 | webdriver.WebDriver.prototype.getCurrentUrl = function() {
803 | return this.schedule(
804 | new webdriver.Command(webdriver.CommandName.GET_CURRENT_URL),
805 | 'WebDriver.getCurrentUrl()');
806 | };
807 |
808 |
809 | /**
810 | * Schedules a command to retrieve the current page's title.
811 | * @return {!webdriver.promise.Promise.<string>} A promise that will be
812 | * resolved with the current page's title.
813 | */
814 | webdriver.WebDriver.prototype.getTitle = function() {
815 | return this.schedule(new webdriver.Command(webdriver.CommandName.GET_TITLE),
816 | 'WebDriver.getTitle()');
817 | };
818 |
819 |
820 | /**
821 | * Schedule a command to find an element on the page. If the element cannot be
822 | * found, a {@link bot.ErrorCode.NO_SUCH_ELEMENT} result will be returned
823 | * by the driver. Unlike other commands, this error cannot be suppressed. In
824 | * other words, scheduling a command to find an element doubles as an assert
825 | * that the element is present on the page. To test whether an element is
826 | * present on the page, use {@link #isElementPresent} instead.
827 | *
828 | * The search criteria for an element may be defined using one of the
829 | * factories in the {@link webdriver.By} namespace, or as a short-hand
830 | * {@link webdriver.By.Hash} object. For example, the following two statements
831 | * are equivalent:
832 | *
833 | * var e1 = driver.findElement(By.id('foo'));
834 | * var e2 = driver.findElement({id:'foo'});
835 | *
836 | * You may also provide a custom locator function, which takes as input
837 | * this WebDriver instance and returns a {@link webdriver.WebElement}, or a
838 | * promise that will resolve to a WebElement. For example, to find the first
839 | * visible link on a page, you could write:
840 | *
841 | * var link = driver.findElement(firstVisibleLink);
842 | *
843 | * function firstVisibleLink(driver) {
844 | * var links = driver.findElements(By.tagName('a'));
845 | * return webdriver.promise.filter(links, function(link) {
846 | * return links.isDisplayed();
847 | * }).then(function(visibleLinks) {
848 | * return visibleLinks[0];
849 | * });
850 | * }
851 | *
852 | * When running in the browser, a WebDriver cannot manipulate DOM elements
853 | * directly; it may do so only through a {@link webdriver.WebElement} reference.
854 | * This function may be used to generate a WebElement from a DOM element. A
855 | * reference to the DOM element will be stored in a known location and this
856 | * driver will attempt to retrieve it through {@link #executeScript}. If the
857 | * element cannot be found (eg, it belongs to a different document than the
858 | * one this instance is currently focused on), a
859 | * {@link bot.ErrorCode.NO_SUCH_ELEMENT} error will be returned.
860 | *
861 | * @param {!(webdriver.Locator|webdriver.By.Hash|Element|Function)} locator The
862 | * locator to use.
863 | * @return {!webdriver.WebElement} A WebElement that can be used to issue
864 | * commands against the located element. If the element is not found, the
865 | * element will be invalidated and all scheduled commands aborted.
866 | */
867 | webdriver.WebDriver.prototype.findElement = function(locator) {
868 | var id;
869 | if ('nodeType' in locator && 'ownerDocument' in locator) {
870 | var element = /** @type {!Element} */ (locator);
871 | id = this.findDomElement_(element).then(function(element) {
872 | if (!element) {
873 | throw new bot.Error(bot.ErrorCode.NO_SUCH_ELEMENT,
874 | 'Unable to locate element. Is WebDriver focused on its ' +
875 | 'ownerDocument\'s frame?');
876 | }
877 | return element;
878 | });
879 | } else {
880 | locator = webdriver.Locator.checkLocator(locator);
881 | if (goog.isFunction(locator)) {
882 | id = this.findElementInternal_(locator, this);
883 | } else {
884 | var command = new webdriver.Command(webdriver.CommandName.FIND_ELEMENT).
885 | setParameter('using', locator.using).
886 | setParameter('value', locator.value);
887 | id = this.schedule(command, 'WebDriver.findElement(' + locator + ')');
888 | }
889 | }
890 | return new webdriver.WebElementPromise(this, id);
891 | };
892 |
893 |
894 | /**
895 | * @param {!Function} locatorFn The locator function to use.
896 | * @param {!(webdriver.WebDriver|webdriver.WebElement)} context The search
897 | * context.
898 | * @return {!webdriver.promise.Promise.<!webdriver.WebElement>} A
899 | * promise that will resolve to a list of WebElements.
900 | * @private
901 | */
902 | webdriver.WebDriver.prototype.findElementInternal_ = function(
903 | locatorFn, context) {
904 | return this.call(goog.partial(locatorFn, context)).then(function(result) {
905 | if (goog.isArray(result)) {
906 | result = result[0];
907 | }
908 | if (!(result instanceof webdriver.WebElement)) {
909 | throw new TypeError('Custom locator did not return a WebElement');
910 | }
911 | return result;
912 | });
913 | };
914 |
915 |
916 | /**
917 | * Locates a DOM element so that commands may be issued against it using the
918 | * {@link webdriver.WebElement} class. This is accomplished by storing a
919 | * reference to the element in an object on the element's ownerDocument.
920 | * {@link #executeScript} will then be used to create a WebElement from this
921 | * reference. This requires this driver to currently be focused on the
922 | * ownerDocument's window+frame.
923 |
924 | * @param {!Element} element The element to locate.
925 | * @return {!webdriver.promise.Promise.<webdriver.WebElement>} A promise that
926 | * will be fulfilled with the located element, or null if the element
927 | * could not be found.
928 | * @private
929 | */
930 | webdriver.WebDriver.prototype.findDomElement_ = function(element) {
931 | var doc = element.ownerDocument;
932 | var store = doc['$webdriver$'] = doc['$webdriver$'] || {};
933 | var id = Math.floor(Math.random() * goog.now()).toString(36);
934 | store[id] = element;
935 | element[id] = id;
936 |
937 | function cleanUp() {
938 | delete store[id];
939 | }
940 |
941 | function lookupElement(id) {
942 | var store = document['$webdriver$'];
943 | if (!store) {
944 | return null;
945 | }
946 |
947 | var element = store[id];
948 | if (!element || element[id] !== id) {
949 | return null;
950 | }
951 | return element;
952 | }
953 |
954 | /** @type {!webdriver.promise.Promise.<webdriver.WebElement>} */
955 | var foundElement = this.executeScript(lookupElement, id);
956 | foundElement.thenFinally(cleanUp);
957 | return foundElement;
958 | };
959 |
960 |
961 | /**
962 | * Schedules a command to test if an element is present on the page.
963 | *
964 | * If given a DOM element, this function will check if it belongs to the
965 | * document the driver is currently focused on. Otherwise, the function will
966 | * test if at least one element can be found with the given search criteria.
967 | *
968 | * @param {!(webdriver.Locator|webdriver.By.Hash|Element|
969 | * Function)} locatorOrElement The locator to use, or the actual
970 | * DOM element to be located by the server.
971 | * @return {!webdriver.promise.Promise.<boolean>} A promise that will resolve
972 | * with whether the element is present on the page.
973 | */
974 | webdriver.WebDriver.prototype.isElementPresent = function(locatorOrElement) {
975 | if ('nodeType' in locatorOrElement && 'ownerDocument' in locatorOrElement) {
976 | return this.findDomElement_(/** @type {!Element} */ (locatorOrElement)).
977 | then(function(result) { return !!result; });
978 | } else {
979 | return this.findElements.apply(this, arguments).then(function(result) {
980 | return !!result.length;
981 | });
982 | }
983 | };
984 |
985 |
986 | /**
987 | * Schedule a command to search for multiple elements on the page.
988 | *
989 | * @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator The locator
990 | * strategy to use when searching for the element.
991 | * @return {!webdriver.promise.Promise.<!Array.<!webdriver.WebElement>>} A
992 | * promise that will resolve to an array of WebElements.
993 | */
994 | webdriver.WebDriver.prototype.findElements = function(locator) {
995 | locator = webdriver.Locator.checkLocator(locator);
996 | if (goog.isFunction(locator)) {
997 | return this.findElementsInternal_(locator, this);
998 | } else {
999 | var command = new webdriver.Command(webdriver.CommandName.FIND_ELEMENTS).
1000 | setParameter('using', locator.using).
1001 | setParameter('value', locator.value);
1002 | return this.schedule(command, 'WebDriver.findElements(' + locator + ')');
1003 | }
1004 | };
1005 |
1006 |
1007 | /**
1008 | * @param {!Function} locatorFn The locator function to use.
1009 | * @param {!(webdriver.WebDriver|webdriver.WebElement)} context The search
1010 | * context.
1011 | * @return {!webdriver.promise.Promise.<!Array.<!webdriver.WebElement>>} A
1012 | * promise that will resolve to an array of WebElements.
1013 | * @private
1014 | */
1015 | webdriver.WebDriver.prototype.findElementsInternal_ = function(
1016 | locatorFn, context) {
1017 | return this.call(goog.partial(locatorFn, context)).then(function(result) {
1018 | if (result instanceof webdriver.WebElement) {
1019 | return [result];
1020 | }
1021 |
1022 | if (!goog.isArray(result)) {
1023 | return [];
1024 | }
1025 |
1026 | return goog.array.filter(result, function(item) {
1027 | return item instanceof webdriver.WebElement;
1028 | });
1029 | });
1030 | };
1031 |
1032 |
1033 | /**
1034 | * Schedule a command to take a screenshot. The driver makes a best effort to
1035 | * return a screenshot of the following, in order of preference:
1036 | * <ol>
1037 | * <li>Entire page
1038 | * <li>Current window
1039 | * <li>Visible portion of the current frame
1040 | * <li>The screenshot of the entire display containing the browser
1041 | * </ol>
1042 | *
1043 | * @return {!webdriver.promise.Promise.<string>} A promise that will be
1044 | * resolved to the screenshot as a base-64 encoded PNG.
1045 | */
1046 | webdriver.WebDriver.prototype.takeScreenshot = function() {
1047 | return this.schedule(new webdriver.Command(webdriver.CommandName.SCREENSHOT),
1048 | 'WebDriver.takeScreenshot()');
1049 | };
1050 |
1051 |
1052 | /**
1053 | * @return {!webdriver.WebDriver.Options} The options interface for this
1054 | * instance.
1055 | */
1056 | webdriver.WebDriver.prototype.manage = function() {
1057 | return new webdriver.WebDriver.Options(this);
1058 | };
1059 |
1060 |
1061 | /**
1062 | * @return {!webdriver.WebDriver.Navigation} The navigation interface for this
1063 | * instance.
1064 | */
1065 | webdriver.WebDriver.prototype.navigate = function() {
1066 | return new webdriver.WebDriver.Navigation(this);
1067 | };
1068 |
1069 |
1070 | /**
1071 | * @return {!webdriver.WebDriver.TargetLocator} The target locator interface for
1072 | * this instance.
1073 | */
1074 | webdriver.WebDriver.prototype.switchTo = function() {
1075 | return new webdriver.WebDriver.TargetLocator(this);
1076 | };
1077 |
1078 |
1079 |
1080 | /**
1081 | * Interface for navigating back and forth in the browser history.
1082 | * @param {!webdriver.WebDriver} driver The parent driver.
1083 | * @constructor
1084 | */
1085 | webdriver.WebDriver.Navigation = function(driver) {
1086 |
1087 | /** @private {!webdriver.WebDriver} */
1088 | this.driver_ = driver;
1089 | };
1090 |
1091 |
1092 | /**
1093 | * Schedules a command to navigate to a new URL.
1094 | * @param {string} url The URL to navigate to.
1095 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
1096 | * when the URL has been loaded.
1097 | */
1098 | webdriver.WebDriver.Navigation.prototype.to = function(url) {
1099 | return this.driver_.schedule(
1100 | new webdriver.Command(webdriver.CommandName.GET).
1101 | setParameter('url', url),
1102 | 'WebDriver.navigate().to(' + url + ')');
1103 | };
1104 |
1105 |
1106 | /**
1107 | * Schedules a command to move backwards in the browser history.
1108 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
1109 | * when the navigation event has completed.
1110 | */
1111 | webdriver.WebDriver.Navigation.prototype.back = function() {
1112 | return this.driver_.schedule(
1113 | new webdriver.Command(webdriver.CommandName.GO_BACK),
1114 | 'WebDriver.navigate().back()');
1115 | };
1116 |
1117 |
1118 | /**
1119 | * Schedules a command to move forwards in the browser history.
1120 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
1121 | * when the navigation event has completed.
1122 | */
1123 | webdriver.WebDriver.Navigation.prototype.forward = function() {
1124 | return this.driver_.schedule(
1125 | new webdriver.Command(webdriver.CommandName.GO_FORWARD),
1126 | 'WebDriver.navigate().forward()');
1127 | };
1128 |
1129 |
1130 | /**
1131 | * Schedules a command to refresh the current page.
1132 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
1133 | * when the navigation event has completed.
1134 | */
1135 | webdriver.WebDriver.Navigation.prototype.refresh = function() {
1136 | return this.driver_.schedule(
1137 | new webdriver.Command(webdriver.CommandName.REFRESH),
1138 | 'WebDriver.navigate().refresh()');
1139 | };
1140 |
1141 |
1142 |
1143 | /**
1144 | * Provides methods for managing browser and driver state.
1145 | * @param {!webdriver.WebDriver} driver The parent driver.
1146 | * @constructor
1147 | */
1148 | webdriver.WebDriver.Options = function(driver) {
1149 |
1150 | /** @private {!webdriver.WebDriver} */
1151 | this.driver_ = driver;
1152 | };
1153 |
1154 |
1155 | /**
1156 | * A JSON description of a browser cookie.
1157 | * @typedef {{
1158 | * name: string,
1159 | * value: string,
1160 | * path: (string|undefined),
1161 | * domain: (string|undefined),
1162 | * secure: (boolean|undefined),
1163 | * expiry: (number|undefined)
1164 | * }}
1165 | * @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#cookie-json-object
1166 | */
1167 | webdriver.WebDriver.Options.Cookie;
1168 |
1169 |
1170 | /**
1171 | * Schedules a command to add a cookie.
1172 | * @param {string} name The cookie name.
1173 | * @param {string} value The cookie value.
1174 | * @param {string=} opt_path The cookie path.
1175 | * @param {string=} opt_domain The cookie domain.
1176 | * @param {boolean=} opt_isSecure Whether the cookie is secure.
1177 | * @param {(number|!Date)=} opt_expiry When the cookie expires. If specified as
1178 | * a number, should be in milliseconds since midnight, January 1, 1970 UTC.
1179 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
1180 | * when the cookie has been added to the page.
1181 | */
1182 | webdriver.WebDriver.Options.prototype.addCookie = function(
1183 | name, value, opt_path, opt_domain, opt_isSecure, opt_expiry) {
1184 | // We do not allow '=' or ';' in the name.
1185 | if (/[;=]/.test(name)) {
1186 | throw Error('Invalid cookie name "' + name + '"');
1187 | }
1188 |
1189 | // We do not allow ';' in value.
1190 | if (/;/.test(value)) {
1191 | throw Error('Invalid cookie value "' + value + '"');
1192 | }
1193 |
1194 | var cookieString = name + '=' + value +
1195 | (opt_domain ? ';domain=' + opt_domain : '') +
1196 | (opt_path ? ';path=' + opt_path : '') +
1197 | (opt_isSecure ? ';secure' : '');
1198 |
1199 | var expiry;
1200 | if (goog.isDef(opt_expiry)) {
1201 | var expiryDate;
1202 | if (goog.isNumber(opt_expiry)) {
1203 | expiryDate = new Date(opt_expiry);
1204 | } else {
1205 | expiryDate = /** @type {!Date} */ (opt_expiry);
1206 | opt_expiry = expiryDate.getTime();
1207 | }
1208 | cookieString += ';expires=' + expiryDate.toUTCString();
1209 | // Convert from milliseconds to seconds.
1210 | expiry = Math.floor(/** @type {number} */ (opt_expiry) / 1000);
1211 | }
1212 |
1213 | return this.driver_.schedule(
1214 | new webdriver.Command(webdriver.CommandName.ADD_COOKIE).
1215 | setParameter('cookie', {
1216 | 'name': name,
1217 | 'value': value,
1218 | 'path': opt_path,
1219 | 'domain': opt_domain,
1220 | 'secure': !!opt_isSecure,
1221 | 'expiry': expiry
1222 | }),
1223 | 'WebDriver.manage().addCookie(' + cookieString + ')');
1224 | };
1225 |
1226 |
1227 | /**
1228 | * Schedules a command to delete all cookies visible to the current page.
1229 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
1230 | * when all cookies have been deleted.
1231 | */
1232 | webdriver.WebDriver.Options.prototype.deleteAllCookies = function() {
1233 | return this.driver_.schedule(
1234 | new webdriver.Command(webdriver.CommandName.DELETE_ALL_COOKIES),
1235 | 'WebDriver.manage().deleteAllCookies()');
1236 | };
1237 |
1238 |
1239 | /**
1240 | * Schedules a command to delete the cookie with the given name. This command is
1241 | * a no-op if there is no cookie with the given name visible to the current
1242 | * page.
1243 | * @param {string} name The name of the cookie to delete.
1244 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
1245 | * when the cookie has been deleted.
1246 | */
1247 | webdriver.WebDriver.Options.prototype.deleteCookie = function(name) {
1248 | return this.driver_.schedule(
1249 | new webdriver.Command(webdriver.CommandName.DELETE_COOKIE).
1250 | setParameter('name', name),
1251 | 'WebDriver.manage().deleteCookie(' + name + ')');
1252 | };
1253 |
1254 |
1255 | /**
1256 | * Schedules a command to retrieve all cookies visible to the current page.
1257 | * Each cookie will be returned as a JSON object as described by the WebDriver
1258 | * wire protocol.
1259 | * @return {!webdriver.promise.Promise.<
1260 | * !Array.<webdriver.WebDriver.Options.Cookie>>} A promise that will be
1261 | * resolved with the cookies visible to the current page.
1262 | * @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#cookie-json-object
1263 | */
1264 | webdriver.WebDriver.Options.prototype.getCookies = function() {
1265 | return this.driver_.schedule(
1266 | new webdriver.Command(webdriver.CommandName.GET_ALL_COOKIES),
1267 | 'WebDriver.manage().getCookies()');
1268 | };
1269 |
1270 |
1271 | /**
1272 | * Schedules a command to retrieve the cookie with the given name. Returns null
1273 | * if there is no such cookie. The cookie will be returned as a JSON object as
1274 | * described by the WebDriver wire protocol.
1275 | * @param {string} name The name of the cookie to retrieve.
1276 | * @return {!webdriver.promise.Promise.<?webdriver.WebDriver.Options.Cookie>} A
1277 | * promise that will be resolved with the named cookie, or {@code null}
1278 | * if there is no such cookie.
1279 | * @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#cookie-json-object
1280 | */
1281 | webdriver.WebDriver.Options.prototype.getCookie = function(name) {
1282 | return this.getCookies().then(function(cookies) {
1283 | return goog.array.find(cookies, function(cookie) {
1284 | return cookie && cookie['name'] == name;
1285 | });
1286 | });
1287 | };
1288 |
1289 |
1290 | /**
1291 | * @return {!webdriver.WebDriver.Logs} The interface for managing driver
1292 | * logs.
1293 | */
1294 | webdriver.WebDriver.Options.prototype.logs = function() {
1295 | return new webdriver.WebDriver.Logs(this.driver_);
1296 | };
1297 |
1298 |
1299 | /**
1300 | * @return {!webdriver.WebDriver.Timeouts} The interface for managing driver
1301 | * timeouts.
1302 | */
1303 | webdriver.WebDriver.Options.prototype.timeouts = function() {
1304 | return new webdriver.WebDriver.Timeouts(this.driver_);
1305 | };
1306 |
1307 |
1308 | /**
1309 | * @return {!webdriver.WebDriver.Window} The interface for managing the
1310 | * current window.
1311 | */
1312 | webdriver.WebDriver.Options.prototype.window = function() {
1313 | return new webdriver.WebDriver.Window(this.driver_);
1314 | };
1315 |
1316 |
1317 |
1318 | /**
1319 | * An interface for managing timeout behavior for WebDriver instances.
1320 | * @param {!webdriver.WebDriver} driver The parent driver.
1321 | * @constructor
1322 | */
1323 | webdriver.WebDriver.Timeouts = function(driver) {
1324 |
1325 | /** @private {!webdriver.WebDriver} */
1326 | this.driver_ = driver;
1327 | };
1328 |
1329 |
1330 | /**
1331 | * Specifies the amount of time the driver should wait when searching for an
1332 | * element if it is not immediately present.
1333 | *
1334 | * When searching for a single element, the driver should poll the page
1335 | * until the element has been found, or this timeout expires before failing
1336 | * with a {@link bot.ErrorCode.NO_SUCH_ELEMENT} error. When searching
1337 | * for multiple elements, the driver should poll the page until at least one
1338 | * element has been found or this timeout has expired.
1339 | *
1340 | * Setting the wait timeout to 0 (its default value), disables implicit
1341 | * waiting.
1342 | *
1343 | * Increasing the implicit wait timeout should be used judiciously as it
1344 | * will have an adverse effect on test run time, especially when used with
1345 | * slower location strategies like XPath.
1346 | *
1347 | * @param {number} ms The amount of time to wait, in milliseconds.
1348 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
1349 | * when the implicit wait timeout has been set.
1350 | */
1351 | webdriver.WebDriver.Timeouts.prototype.implicitlyWait = function(ms) {
1352 | return this.driver_.schedule(
1353 | new webdriver.Command(webdriver.CommandName.IMPLICITLY_WAIT).
1354 | setParameter('ms', ms < 0 ? 0 : ms),
1355 | 'WebDriver.manage().timeouts().implicitlyWait(' + ms + ')');
1356 | };
1357 |
1358 |
1359 | /**
1360 | * Sets the amount of time to wait, in milliseconds, for an asynchronous script
1361 | * to finish execution before returning an error. If the timeout is less than or
1362 | * equal to 0, the script will be allowed to run indefinitely.
1363 | *
1364 | * @param {number} ms The amount of time to wait, in milliseconds.
1365 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
1366 | * when the script timeout has been set.
1367 | */
1368 | webdriver.WebDriver.Timeouts.prototype.setScriptTimeout = function(ms) {
1369 | return this.driver_.schedule(
1370 | new webdriver.Command(webdriver.CommandName.SET_SCRIPT_TIMEOUT).
1371 | setParameter('ms', ms < 0 ? 0 : ms),
1372 | 'WebDriver.manage().timeouts().setScriptTimeout(' + ms + ')');
1373 | };
1374 |
1375 |
1376 | /**
1377 | * Sets the amount of time to wait for a page load to complete before returning
1378 | * an error. If the timeout is negative, page loads may be indefinite.
1379 | * @param {number} ms The amount of time to wait, in milliseconds.
1380 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
1381 | * when the timeout has been set.
1382 | */
1383 | webdriver.WebDriver.Timeouts.prototype.pageLoadTimeout = function(ms) {
1384 | return this.driver_.schedule(
1385 | new webdriver.Command(webdriver.CommandName.SET_TIMEOUT).
1386 | setParameter('type', 'page load').
1387 | setParameter('ms', ms),
1388 | 'WebDriver.manage().timeouts().pageLoadTimeout(' + ms + ')');
1389 | };
1390 |
1391 |
1392 |
1393 | /**
1394 | * An interface for managing the current window.
1395 | * @param {!webdriver.WebDriver} driver The parent driver.
1396 | * @constructor
1397 | */
1398 | webdriver.WebDriver.Window = function(driver) {
1399 |
1400 | /** @private {!webdriver.WebDriver} */
1401 | this.driver_ = driver;
1402 | };
1403 |
1404 |
1405 | /**
1406 | * Retrieves the window's current position, relative to the top left corner of
1407 | * the screen.
1408 | * @return {!webdriver.promise.Promise.<{x: number, y: number}>} A promise that
1409 | * will be resolved with the window's position in the form of a
1410 | * {x:number, y:number} object literal.
1411 | */
1412 | webdriver.WebDriver.Window.prototype.getPosition = function() {
1413 | return this.driver_.schedule(
1414 | new webdriver.Command(webdriver.CommandName.GET_WINDOW_POSITION).
1415 | setParameter('windowHandle', 'current'),
1416 | 'WebDriver.manage().window().getPosition()');
1417 | };
1418 |
1419 |
1420 | /**
1421 | * Repositions the current window.
1422 | * @param {number} x The desired horizontal position, relative to the left side
1423 | * of the screen.
1424 | * @param {number} y The desired vertical position, relative to the top of the
1425 | * of the screen.
1426 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
1427 | * when the command has completed.
1428 | */
1429 | webdriver.WebDriver.Window.prototype.setPosition = function(x, y) {
1430 | return this.driver_.schedule(
1431 | new webdriver.Command(webdriver.CommandName.SET_WINDOW_POSITION).
1432 | setParameter('windowHandle', 'current').
1433 | setParameter('x', x).
1434 | setParameter('y', y),
1435 | 'WebDriver.manage().window().setPosition(' + x + ', ' + y + ')');
1436 | };
1437 |
1438 |
1439 | /**
1440 | * Retrieves the window's current size.
1441 | * @return {!webdriver.promise.Promise.<{width: number, height: number}>} A
1442 | * promise that will be resolved with the window's size in the form of a
1443 | * {width:number, height:number} object literal.
1444 | */
1445 | webdriver.WebDriver.Window.prototype.getSize = function() {
1446 | return this.driver_.schedule(
1447 | new webdriver.Command(webdriver.CommandName.GET_WINDOW_SIZE).
1448 | setParameter('windowHandle', 'current'),
1449 | 'WebDriver.manage().window().getSize()');
1450 | };
1451 |
1452 |
1453 | /**
1454 | * Resizes the current window.
1455 | * @param {number} width The desired window width.
1456 | * @param {number} height The desired window height.
1457 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
1458 | * when the command has completed.
1459 | */
1460 | webdriver.WebDriver.Window.prototype.setSize = function(width, height) {
1461 | return this.driver_.schedule(
1462 | new webdriver.Command(webdriver.CommandName.SET_WINDOW_SIZE).
1463 | setParameter('windowHandle', 'current').
1464 | setParameter('width', width).
1465 | setParameter('height', height),
1466 | 'WebDriver.manage().window().setSize(' + width + ', ' + height + ')');
1467 | };
1468 |
1469 |
1470 | /**
1471 | * Maximizes the current window.
1472 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
1473 | * when the command has completed.
1474 | */
1475 | webdriver.WebDriver.Window.prototype.maximize = function() {
1476 | return this.driver_.schedule(
1477 | new webdriver.Command(webdriver.CommandName.MAXIMIZE_WINDOW).
1478 | setParameter('windowHandle', 'current'),
1479 | 'WebDriver.manage().window().maximize()');
1480 | };
1481 |
1482 |
1483 | /**
1484 | * Interface for managing WebDriver log records.
1485 | * @param {!webdriver.WebDriver} driver The parent driver.
1486 | * @constructor
1487 | */
1488 | webdriver.WebDriver.Logs = function(driver) {
1489 |
1490 | /** @private {!webdriver.WebDriver} */
1491 | this.driver_ = driver;
1492 | };
1493 |
1494 |
1495 | /**
1496 | * Fetches available log entries for the given type.
1497 | *
1498 | * Note that log buffers are reset after each call, meaning that available
1499 | * log entries correspond to those entries not yet returned for a given log
1500 | * type. In practice, this means that this call will return the available log
1501 | * entries since the last call, or from the start of the session.
1502 | *
1503 | * @param {!webdriver.logging.Type} type The desired log type.
1504 | * @return {!webdriver.promise.Promise.<!Array.<!webdriver.logging.Entry>>} A
1505 | * promise that will resolve to a list of log entries for the specified
1506 | * type.
1507 | */
1508 | webdriver.WebDriver.Logs.prototype.get = function(type) {
1509 | return this.driver_.schedule(
1510 | new webdriver.Command(webdriver.CommandName.GET_LOG).
1511 | setParameter('type', type),
1512 | 'WebDriver.manage().logs().get(' + type + ')').
1513 | then(function(entries) {
1514 | return goog.array.map(entries, function(entry) {
1515 | if (!(entry instanceof webdriver.logging.Entry)) {
1516 | return new webdriver.logging.Entry(
1517 | entry['level'], entry['message'], entry['timestamp'],
1518 | entry['type']);
1519 | }
1520 | return entry;
1521 | });
1522 | });
1523 | };
1524 |
1525 |
1526 | /**
1527 | * Retrieves the log types available to this driver.
1528 | * @return {!webdriver.promise.Promise.<!Array.<!webdriver.logging.Type>>} A
1529 | * promise that will resolve to a list of available log types.
1530 | */
1531 | webdriver.WebDriver.Logs.prototype.getAvailableLogTypes = function() {
1532 | return this.driver_.schedule(
1533 | new webdriver.Command(webdriver.CommandName.GET_AVAILABLE_LOG_TYPES),
1534 | 'WebDriver.manage().logs().getAvailableLogTypes()');
1535 | };
1536 |
1537 |
1538 |
1539 | /**
1540 | * An interface for changing the focus of the driver to another frame or window.
1541 | * @param {!webdriver.WebDriver} driver The parent driver.
1542 | * @constructor
1543 | */
1544 | webdriver.WebDriver.TargetLocator = function(driver) {
1545 |
1546 | /** @private {!webdriver.WebDriver} */
1547 | this.driver_ = driver;
1548 | };
1549 |
1550 |
1551 | /**
1552 | * Schedules a command retrieve the {@code document.activeElement} element on
1553 | * the current document, or {@code document.body} if activeElement is not
1554 | * available.
1555 | * @return {!webdriver.WebElementPromise} The active element.
1556 | */
1557 | webdriver.WebDriver.TargetLocator.prototype.activeElement = function() {
1558 | var id = this.driver_.schedule(
1559 | new webdriver.Command(webdriver.CommandName.GET_ACTIVE_ELEMENT),
1560 | 'WebDriver.switchTo().activeElement()');
1561 | return new webdriver.WebElementPromise(this.driver_, id);
1562 | };
1563 |
1564 |
1565 | /**
1566 | * Schedules a command to switch focus of all future commands to the first frame
1567 | * on the page.
1568 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
1569 | * when the driver has changed focus to the default content.
1570 | */
1571 | webdriver.WebDriver.TargetLocator.prototype.defaultContent = function() {
1572 | return this.driver_.schedule(
1573 | new webdriver.Command(webdriver.CommandName.SWITCH_TO_FRAME).
1574 | setParameter('id', null),
1575 | 'WebDriver.switchTo().defaultContent()');
1576 | };
1577 |
1578 |
1579 | /**
1580 | * Schedules a command to switch the focus of all future commands to another
1581 | * frame on the page.
1582 | *
1583 | * If the frame is specified by a number, the command will switch to the frame
1584 | * by its (zero-based) index into
1585 | * [window.frames](https://developer.mozilla.org/en-US/docs/Web/API/Window.frames).
1586 | *
1587 | * If the frame is specified by a string, the command will select the frame by
1588 | * its name or ID. To select sub-frames, simply separate the frame names/IDs by
1589 | * dots. As an example, "main.child" will select the frame with the name "main"
1590 | * and then its child "child".
1591 | *
1592 | * If the specified frame can not be found, the deferred result will errback
1593 | * with a {@link bot.ErrorCode.NO_SUCH_FRAME} error.
1594 | *
1595 | * @param {string|number} nameOrIndex The frame locator.
1596 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
1597 | * when the driver has changed focus to the specified frame.
1598 | */
1599 | webdriver.WebDriver.TargetLocator.prototype.frame = function(nameOrIndex) {
1600 | return this.driver_.schedule(
1601 | new webdriver.Command(webdriver.CommandName.SWITCH_TO_FRAME).
1602 | setParameter('id', nameOrIndex),
1603 | 'WebDriver.switchTo().frame(' + nameOrIndex + ')');
1604 | };
1605 |
1606 |
1607 | /**
1608 | * Schedules a command to switch the focus of all future commands to another
1609 | * window. Windows may be specified by their {@code window.name} attribute or
1610 | * by its handle (as returned by {@link webdriver.WebDriver#getWindowHandles}).
1611 | *
1612 | * If the specificed window can not be found, the deferred result will errback
1613 | * with a {@link bot.ErrorCode.NO_SUCH_WINDOW} error.
1614 | *
1615 | * @param {string} nameOrHandle The name or window handle of the window to
1616 | * switch focus to.
1617 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
1618 | * when the driver has changed focus to the specified window.
1619 | */
1620 | webdriver.WebDriver.TargetLocator.prototype.window = function(nameOrHandle) {
1621 | return this.driver_.schedule(
1622 | new webdriver.Command(webdriver.CommandName.SWITCH_TO_WINDOW).
1623 | setParameter('name', nameOrHandle),
1624 | 'WebDriver.switchTo().window(' + nameOrHandle + ')');
1625 | };
1626 |
1627 |
1628 | /**
1629 | * Schedules a command to change focus to the active alert dialog. This command
1630 | * will return a {@link bot.ErrorCode.NO_SUCH_ALERT} error if an alert dialog
1631 | * is not currently open.
1632 | * @return {!webdriver.AlertPromise} The open alert.
1633 | */
1634 | webdriver.WebDriver.TargetLocator.prototype.alert = function() {
1635 | var text = this.driver_.schedule(
1636 | new webdriver.Command(webdriver.CommandName.GET_ALERT_TEXT),
1637 | 'WebDriver.switchTo().alert()');
1638 | var driver = this.driver_;
1639 | return new webdriver.AlertPromise(driver, text.then(function(text) {
1640 | return new webdriver.Alert(driver, text);
1641 | }));
1642 | };
1643 |
1644 |
1645 | /**
1646 | * Simulate pressing many keys at once in a "chord". Takes a sequence of
1647 | * {@link webdriver.Key}s or strings, appends each of the values to a string,
1648 | * and adds the chord termination key ({@link webdriver.Key.NULL}) and returns
1649 | * the resultant string.
1650 | *
1651 | * Note: when the low-level webdriver key handlers see Keys.NULL, active
1652 | * modifier keys (CTRL/ALT/SHIFT/etc) release via a keyup event.
1653 | *
1654 | * @param {...string} var_args The key sequence to concatenate.
1655 | * @return {string} The null-terminated key sequence.
1656 | * @see http://code.google.com/p/webdriver/issues/detail?id=79
1657 | */
1658 | webdriver.Key.chord = function(var_args) {
1659 | var sequence = goog.array.reduce(
1660 | goog.array.slice(arguments, 0),
1661 | function(str, key) {
1662 | return str + key;
1663 | }, '');
1664 | sequence += webdriver.Key.NULL;
1665 | return sequence;
1666 | };
1667 |
1668 |
1669 | //////////////////////////////////////////////////////////////////////////////
1670 | //
1671 | // webdriver.WebElement
1672 | //
1673 | //////////////////////////////////////////////////////////////////////////////
1674 |
1675 |
1676 |
1677 | /**
1678 | * Represents a DOM element. WebElements can be found by searching from the
1679 | * document root using a {@link webdriver.WebDriver} instance, or by searching
1680 | * under another WebElement:
1681 | *
1682 | * driver.get('http://www.google.com');
1683 | * var searchForm = driver.findElement(By.tagName('form'));
1684 | * var searchBox = searchForm.findElement(By.name('q'));
1685 | * searchBox.sendKeys('webdriver');
1686 | *
1687 | * The WebElement is implemented as a promise for compatibility with the promise
1688 | * API. It will always resolve itself when its internal state has been fully
1689 | * resolved and commands may be issued against the element. This can be used to
1690 | * catch errors when an element cannot be located on the page:
1691 | *
1692 | * driver.findElement(By.id('not-there')).then(function(element) {
1693 | * alert('Found an element that was not expected to be there!');
1694 | * }, function(error) {
1695 | * alert('The element was not found, as expected');
1696 | * });
1697 | *
1698 | * @param {!webdriver.WebDriver} driver The parent WebDriver instance for this
1699 | * element.
1700 | * @param {!(webdriver.promise.Promise.<webdriver.WebElement.Id>|
1701 | * webdriver.WebElement.Id)} id The server-assigned opaque ID for the
1702 | * underlying DOM element.
1703 | * @constructor
1704 | * @extends {webdriver.Serializable.<webdriver.WebElement.Id>}
1705 | */
1706 | webdriver.WebElement = function(driver, id) {
1707 | webdriver.Serializable.call(this);
1708 |
1709 | /** @private {!webdriver.WebDriver} */
1710 | this.driver_ = driver;
1711 |
1712 | /** @private {!webdriver.promise.Promise.<webdriver.WebElement.Id>} */
1713 | this.id_ = id instanceof webdriver.promise.Promise ?
1714 | id : webdriver.promise.fulfilled(id);
1715 | };
1716 | goog.inherits(webdriver.WebElement, webdriver.Serializable);
1717 |
1718 |
1719 | /**
1720 | * Wire protocol definition of a WebElement ID.
1721 | * @typedef {{ELEMENT: string}}
1722 | * @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol
1723 | */
1724 | webdriver.WebElement.Id;
1725 |
1726 |
1727 | /**
1728 | * The property key used in the wire protocol to indicate that a JSON object
1729 | * contains the ID of a WebElement.
1730 | * @type {string}
1731 | * @const
1732 | */
1733 | webdriver.WebElement.ELEMENT_KEY = 'ELEMENT';
1734 |
1735 |
1736 | /**
1737 | * Compares to WebElements for equality.
1738 | * @param {!webdriver.WebElement} a A WebElement.
1739 | * @param {!webdriver.WebElement} b A WebElement.
1740 | * @return {!webdriver.promise.Promise.<boolean>} A promise that will be
1741 | * resolved to whether the two WebElements are equal.
1742 | */
1743 | webdriver.WebElement.equals = function(a, b) {
1744 | if (a == b) {
1745 | return webdriver.promise.fulfilled(true);
1746 | }
1747 | var ids = [a.getId(), b.getId()];
1748 | return webdriver.promise.all(ids).then(function(ids) {
1749 | // If the two element's have the same ID, they should be considered
1750 | // equal. Otherwise, they may still be equivalent, but we'll need to
1751 | // ask the server to check for us.
1752 | if (ids[0][webdriver.WebElement.ELEMENT_KEY] ==
1753 | ids[1][webdriver.WebElement.ELEMENT_KEY]) {
1754 | return true;
1755 | }
1756 |
1757 | var command = new webdriver.Command(webdriver.CommandName.ELEMENT_EQUALS);
1758 | command.setParameter('id', ids[0]);
1759 | command.setParameter('other', ids[1]);
1760 | return a.driver_.schedule(command, 'webdriver.WebElement.equals()');
1761 | });
1762 | };
1763 |
1764 |
1765 | /**
1766 | * @return {!webdriver.WebDriver} The parent driver for this instance.
1767 | */
1768 | webdriver.WebElement.prototype.getDriver = function() {
1769 | return this.driver_;
1770 | };
1771 |
1772 |
1773 | /**
1774 | * @return {!webdriver.promise.Promise.<webdriver.WebElement.Id>} A promise
1775 | * that resolves to this element's JSON representation as defined by the
1776 | * WebDriver wire protocol.
1777 | * @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol
1778 | */
1779 | webdriver.WebElement.prototype.getId = function() {
1780 | return this.id_;
1781 | };
1782 |
1783 |
1784 | /**
1785 | * Returns the raw ID string ID for this element.
1786 | * @return {!webdriver.promise.Promise<string>} A promise that resolves to this
1787 | * element's raw ID as a string value.
1788 | * @package
1789 | */
1790 | webdriver.WebElement.prototype.getRawId = function() {
1791 | return this.getId().then(function(value) {
1792 | return value['ELEMENT'];
1793 | });
1794 | };
1795 |
1796 |
1797 | /** @override */
1798 | webdriver.WebElement.prototype.serialize = function() {
1799 | return this.getId();
1800 | };
1801 |
1802 |
1803 | /**
1804 | * Schedules a command that targets this element with the parent WebDriver
1805 | * instance. Will ensure this element's ID is included in the command parameters
1806 | * under the "id" key.
1807 | * @param {!webdriver.Command} command The command to schedule.
1808 | * @param {string} description A description of the command for debugging.
1809 | * @return {!webdriver.promise.Promise.<T>} A promise that will be resolved
1810 | * with the command result.
1811 | * @template T
1812 | * @see webdriver.WebDriver.prototype.schedule
1813 | * @private
1814 | */
1815 | webdriver.WebElement.prototype.schedule_ = function(command, description) {
1816 | command.setParameter('id', this.getId());
1817 | return this.driver_.schedule(command, description);
1818 | };
1819 |
1820 |
1821 | /**
1822 | * Schedule a command to find a descendant of this element. If the element
1823 | * cannot be found, a {@link bot.ErrorCode.NO_SUCH_ELEMENT} result will
1824 | * be returned by the driver. Unlike other commands, this error cannot be
1825 | * suppressed. In other words, scheduling a command to find an element doubles
1826 | * as an assert that the element is present on the page. To test whether an
1827 | * element is present on the page, use {@link #isElementPresent} instead.
1828 | *
1829 | * The search criteria for an element may be defined using one of the
1830 | * factories in the {@link webdriver.By} namespace, or as a short-hand
1831 | * {@link webdriver.By.Hash} object. For example, the following two statements
1832 | * are equivalent:
1833 | *
1834 | * var e1 = element.findElement(By.id('foo'));
1835 | * var e2 = element.findElement({id:'foo'});
1836 | *
1837 | * You may also provide a custom locator function, which takes as input
1838 | * this WebDriver instance and returns a {@link webdriver.WebElement}, or a
1839 | * promise that will resolve to a WebElement. For example, to find the first
1840 | * visible link on a page, you could write:
1841 | *
1842 | * var link = element.findElement(firstVisibleLink);
1843 | *
1844 | * function firstVisibleLink(element) {
1845 | * var links = element.findElements(By.tagName('a'));
1846 | * return webdriver.promise.filter(links, function(link) {
1847 | * return links.isDisplayed();
1848 | * }).then(function(visibleLinks) {
1849 | * return visibleLinks[0];
1850 | * });
1851 | * }
1852 | *
1853 | * @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator The
1854 | * locator strategy to use when searching for the element.
1855 | * @return {!webdriver.WebElement} A WebElement that can be used to issue
1856 | * commands against the located element. If the element is not found, the
1857 | * element will be invalidated and all scheduled commands aborted.
1858 | */
1859 | webdriver.WebElement.prototype.findElement = function(locator) {
1860 | locator = webdriver.Locator.checkLocator(locator);
1861 | var id;
1862 | if (goog.isFunction(locator)) {
1863 | id = this.driver_.findElementInternal_(locator, this);
1864 | } else {
1865 | var command = new webdriver.Command(
1866 | webdriver.CommandName.FIND_CHILD_ELEMENT).
1867 | setParameter('using', locator.using).
1868 | setParameter('value', locator.value);
1869 | id = this.schedule_(command, 'WebElement.findElement(' + locator + ')');
1870 | }
1871 | return new webdriver.WebElementPromise(this.driver_, id);
1872 | };
1873 |
1874 |
1875 | /**
1876 | * Schedules a command to test if there is at least one descendant of this
1877 | * element that matches the given search criteria.
1878 | *
1879 | * @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator The
1880 | * locator strategy to use when searching for the element.
1881 | * @return {!webdriver.promise.Promise.<boolean>} A promise that will be
1882 | * resolved with whether an element could be located on the page.
1883 | */
1884 | webdriver.WebElement.prototype.isElementPresent = function(locator) {
1885 | return this.findElements(locator).then(function(result) {
1886 | return !!result.length;
1887 | });
1888 | };
1889 |
1890 |
1891 | /**
1892 | * Schedules a command to find all of the descendants of this element that
1893 | * match the given search criteria.
1894 | *
1895 | * @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator The
1896 | * locator strategy to use when searching for the elements.
1897 | * @return {!webdriver.promise.Promise.<!Array.<!webdriver.WebElement>>} A
1898 | * promise that will resolve to an array of WebElements.
1899 | */
1900 | webdriver.WebElement.prototype.findElements = function(locator) {
1901 | locator = webdriver.Locator.checkLocator(locator);
1902 | if (goog.isFunction(locator)) {
1903 | return this.driver_.findElementsInternal_(locator, this);
1904 | } else {
1905 | var command = new webdriver.Command(
1906 | webdriver.CommandName.FIND_CHILD_ELEMENTS).
1907 | setParameter('using', locator.using).
1908 | setParameter('value', locator.value);
1909 | return this.schedule_(command, 'WebElement.findElements(' + locator + ')');
1910 | }
1911 | };
1912 |
1913 |
1914 | /**
1915 | * Schedules a command to click on this element.
1916 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
1917 | * when the click command has completed.
1918 | */
1919 | webdriver.WebElement.prototype.click = function() {
1920 | return this.schedule_(
1921 | new webdriver.Command(webdriver.CommandName.CLICK_ELEMENT),
1922 | 'WebElement.click()');
1923 | };
1924 |
1925 |
1926 | /**
1927 | * Schedules a command to type a sequence on the DOM element represented by this
1928 | * instance.
1929 | *
1930 | * Modifier keys (SHIFT, CONTROL, ALT, META) are stateful; once a modifier is
1931 | * processed in the keysequence, that key state is toggled until one of the
1932 | * following occurs:
1933 | *
1934 | * - The modifier key is encountered again in the sequence. At this point the
1935 | * state of the key is toggled (along with the appropriate keyup/down events).
1936 | * - The {@link webdriver.Key.NULL} key is encountered in the sequence. When
1937 | * this key is encountered, all modifier keys current in the down state are
1938 | * released (with accompanying keyup events). The NULL key can be used to
1939 | * simulate common keyboard shortcuts:
1940 | *
1941 | * element.sendKeys("text was",
1942 | * webdriver.Key.CONTROL, "a", webdriver.Key.NULL,
1943 | * "now text is");
1944 | * // Alternatively:
1945 | * element.sendKeys("text was",
1946 | * webdriver.Key.chord(webdriver.Key.CONTROL, "a"),
1947 | * "now text is");
1948 | *
1949 | * - The end of the keysequence is encountered. When there are no more keys
1950 | * to type, all depressed modifier keys are released (with accompanying keyup
1951 | * events).
1952 | *
1953 | * If this element is a file input ({@code <input type="file">}), the
1954 | * specified key sequence should specify the path to the file to attach to
1955 | * the element. This is analgous to the user clicking "Browse..." and entering
1956 | * the path into the file select dialog.
1957 | *
1958 | * var form = driver.findElement(By.css('form'));
1959 | * var element = form.findElement(By.css('input[type=file]'));
1960 | * element.sendKeys('/path/to/file.txt');
1961 | * form.submit();
1962 | *
1963 | * For uploads to function correctly, the entered path must reference a file
1964 | * on the _browser's_ machine, not the local machine running this script. When
1965 | * running against a remote Selenium server, a {@link webdriver.FileDetector}
1966 | * may be used to transparently copy files to the remote machine before
1967 | * attempting to upload them in the browser.
1968 | *
1969 | * __Note:__ On browsers where native keyboard events are not supported
1970 | * (e.g. Firefox on OS X), key events will be synthesized. Special
1971 | * punctionation keys will be synthesized according to a standard QWERTY en-us
1972 | * keyboard layout.
1973 | *
1974 | * @param {...(string|!webdriver.promise.Promise<string>)} var_args The sequence
1975 | * of keys to type. All arguments will be joined into a single sequence.
1976 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
1977 | * when all keys have been typed.
1978 | */
1979 | webdriver.WebElement.prototype.sendKeys = function(var_args) {
1980 | // Coerce every argument to a string. This protects us from users that
1981 | // ignore the jsdoc and give us a number (which ends up causing problems on
1982 | // the server, which requires strings).
1983 | var keys = webdriver.promise.all(goog.array.slice(arguments, 0)).
1984 | then(function(keys) {
1985 | return goog.array.map(keys, String);
1986 | });
1987 | if (!this.driver_.fileDetector_) {
1988 | return this.schedule_(
1989 | new webdriver.Command(webdriver.CommandName.SEND_KEYS_TO_ELEMENT).
1990 | setParameter('value', keys),
1991 | 'WebElement.sendKeys()');
1992 | }
1993 |
1994 | // Suppress unhandled rejection errors until the flow executes the command.
1995 | keys.thenCatch(goog.nullFunction);
1996 |
1997 | var element = this;
1998 | return this.driver_.flow_.execute(function() {
1999 | return keys.then(function(keys) {
2000 | return element.driver_.fileDetector_
2001 | .handleFile(element.driver_, keys.join(''));
2002 | }).then(function(keys) {
2003 | return element.schedule_(
2004 | new webdriver.Command(webdriver.CommandName.SEND_KEYS_TO_ELEMENT).
2005 | setParameter('value', [keys]),
2006 | 'WebElement.sendKeys()');
2007 | });
2008 | }, 'WebElement.sendKeys()');
2009 | };
2010 |
2011 |
2012 | /**
2013 | * Schedules a command to query for the tag/node name of this element.
2014 | * @return {!webdriver.promise.Promise.<string>} A promise that will be
2015 | * resolved with the element's tag name.
2016 | */
2017 | webdriver.WebElement.prototype.getTagName = function() {
2018 | return this.schedule_(
2019 | new webdriver.Command(webdriver.CommandName.GET_ELEMENT_TAG_NAME),
2020 | 'WebElement.getTagName()');
2021 | };
2022 |
2023 |
2024 | /**
2025 | * Schedules a command to query for the computed style of the element
2026 | * represented by this instance. If the element inherits the named style from
2027 | * its parent, the parent will be queried for its value. Where possible, color
2028 | * values will be converted to their hex representation (e.g. #00ff00 instead of
2029 | * rgb(0, 255, 0)).
2030 | *
2031 | * _Warning:_ the value returned will be as the browser interprets it, so
2032 | * it may be tricky to form a proper assertion.
2033 | *
2034 | * @param {string} cssStyleProperty The name of the CSS style property to look
2035 | * up.
2036 | * @return {!webdriver.promise.Promise.<string>} A promise that will be
2037 | * resolved with the requested CSS value.
2038 | */
2039 | webdriver.WebElement.prototype.getCssValue = function(cssStyleProperty) {
2040 | var name = webdriver.CommandName.GET_ELEMENT_VALUE_OF_CSS_PROPERTY;
2041 | return this.schedule_(
2042 | new webdriver.Command(name).
2043 | setParameter('propertyName', cssStyleProperty),
2044 | 'WebElement.getCssValue(' + cssStyleProperty + ')');
2045 | };
2046 |
2047 |
2048 | /**
2049 | * Schedules a command to query for the value of the given attribute of the
2050 | * element. Will return the current value, even if it has been modified after
2051 | * the page has been loaded. More exactly, this method will return the value of
2052 | * the given attribute, unless that attribute is not present, in which case the
2053 | * value of the property with the same name is returned. If neither value is
2054 | * set, null is returned (for example, the "value" property of a textarea
2055 | * element). The "style" attribute is converted as best can be to a
2056 | * text representation with a trailing semi-colon. The following are deemed to
2057 | * be "boolean" attributes and will return either "true" or null:
2058 | *
2059 | * async, autofocus, autoplay, checked, compact, complete, controls, declare,
2060 | * defaultchecked, defaultselected, defer, disabled, draggable, ended,
2061 | * formnovalidate, hidden, indeterminate, iscontenteditable, ismap, itemscope,
2062 | * loop, multiple, muted, nohref, noresize, noshade, novalidate, nowrap, open,
2063 | * paused, pubdate, readonly, required, reversed, scoped, seamless, seeking,
2064 | * selected, spellcheck, truespeed, willvalidate
2065 | *
2066 | * Finally, the following commonly mis-capitalized attribute/property names
2067 | * are evaluated as expected:
2068 | *
2069 | * - "class"
2070 | * - "readonly"
2071 | *
2072 | * @param {string} attributeName The name of the attribute to query.
2073 | * @return {!webdriver.promise.Promise.<?string>} A promise that will be
2074 | * resolved with the attribute's value. The returned value will always be
2075 | * either a string or null.
2076 | */
2077 | webdriver.WebElement.prototype.getAttribute = function(attributeName) {
2078 | return this.schedule_(
2079 | new webdriver.Command(webdriver.CommandName.GET_ELEMENT_ATTRIBUTE).
2080 | setParameter('name', attributeName),
2081 | 'WebElement.getAttribute(' + attributeName + ')');
2082 | };
2083 |
2084 |
2085 | /**
2086 | * Get the visible (i.e. not hidden by CSS) innerText of this element, including
2087 | * sub-elements, without any leading or trailing whitespace.
2088 | * @return {!webdriver.promise.Promise.<string>} A promise that will be
2089 | * resolved with the element's visible text.
2090 | */
2091 | webdriver.WebElement.prototype.getText = function() {
2092 | return this.schedule_(
2093 | new webdriver.Command(webdriver.CommandName.GET_ELEMENT_TEXT),
2094 | 'WebElement.getText()');
2095 | };
2096 |
2097 |
2098 | /**
2099 | * Schedules a command to compute the size of this element's bounding box, in
2100 | * pixels.
2101 | * @return {!webdriver.promise.Promise.<{width: number, height: number}>} A
2102 | * promise that will be resolved with the element's size as a
2103 | * {@code {width:number, height:number}} object.
2104 | */
2105 | webdriver.WebElement.prototype.getSize = function() {
2106 | return this.schedule_(
2107 | new webdriver.Command(webdriver.CommandName.GET_ELEMENT_SIZE),
2108 | 'WebElement.getSize()');
2109 | };
2110 |
2111 |
2112 | /**
2113 | * Schedules a command to compute the location of this element in page space.
2114 | * @return {!webdriver.promise.Promise.<{x: number, y: number}>} A promise that
2115 | * will be resolved to the element's location as a
2116 | * {@code {x:number, y:number}} object.
2117 | */
2118 | webdriver.WebElement.prototype.getLocation = function() {
2119 | return this.schedule_(
2120 | new webdriver.Command(webdriver.CommandName.GET_ELEMENT_LOCATION),
2121 | 'WebElement.getLocation()');
2122 | };
2123 |
2124 |
2125 | /**
2126 | * Schedules a command to query whether the DOM element represented by this
2127 | * instance is enabled, as dicted by the {@code disabled} attribute.
2128 | * @return {!webdriver.promise.Promise.<boolean>} A promise that will be
2129 | * resolved with whether this element is currently enabled.
2130 | */
2131 | webdriver.WebElement.prototype.isEnabled = function() {
2132 | return this.schedule_(
2133 | new webdriver.Command(webdriver.CommandName.IS_ELEMENT_ENABLED),
2134 | 'WebElement.isEnabled()');
2135 | };
2136 |
2137 |
2138 | /**
2139 | * Schedules a command to query whether this element is selected.
2140 | * @return {!webdriver.promise.Promise.<boolean>} A promise that will be
2141 | * resolved with whether this element is currently selected.
2142 | */
2143 | webdriver.WebElement.prototype.isSelected = function() {
2144 | return this.schedule_(
2145 | new webdriver.Command(webdriver.CommandName.IS_ELEMENT_SELECTED),
2146 | 'WebElement.isSelected()');
2147 | };
2148 |
2149 |
2150 | /**
2151 | * Schedules a command to submit the form containing this element (or this
2152 | * element if it is a FORM element). This command is a no-op if the element is
2153 | * not contained in a form.
2154 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
2155 | * when the form has been submitted.
2156 | */
2157 | webdriver.WebElement.prototype.submit = function() {
2158 | return this.schedule_(
2159 | new webdriver.Command(webdriver.CommandName.SUBMIT_ELEMENT),
2160 | 'WebElement.submit()');
2161 | };
2162 |
2163 |
2164 | /**
2165 | * Schedules a command to clear the {@code value} of this element. This command
2166 | * has no effect if the underlying DOM element is neither a text INPUT element
2167 | * nor a TEXTAREA element.
2168 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
2169 | * when the element has been cleared.
2170 | */
2171 | webdriver.WebElement.prototype.clear = function() {
2172 | return this.schedule_(
2173 | new webdriver.Command(webdriver.CommandName.CLEAR_ELEMENT),
2174 | 'WebElement.clear()');
2175 | };
2176 |
2177 |
2178 | /**
2179 | * Schedules a command to test whether this element is currently displayed.
2180 | * @return {!webdriver.promise.Promise.<boolean>} A promise that will be
2181 | * resolved with whether this element is currently visible on the page.
2182 | */
2183 | webdriver.WebElement.prototype.isDisplayed = function() {
2184 | return this.schedule_(
2185 | new webdriver.Command(webdriver.CommandName.IS_ELEMENT_DISPLAYED),
2186 | 'WebElement.isDisplayed()');
2187 | };
2188 |
2189 |
2190 | /**
2191 | * Schedules a command to retrieve the outer HTML of this element.
2192 | * @return {!webdriver.promise.Promise.<string>} A promise that will be
2193 | * resolved with the element's outer HTML.
2194 | */
2195 | webdriver.WebElement.prototype.getOuterHtml = function() {
2196 | return this.driver_.executeScript(function() {
2197 | var element = arguments[0];
2198 | if ('outerHTML' in element) {
2199 | return element.outerHTML;
2200 | } else {
2201 | var div = element.ownerDocument.createElement('div');
2202 | div.appendChild(element.cloneNode(true));
2203 | return div.innerHTML;
2204 | }
2205 | }, this);
2206 | };
2207 |
2208 |
2209 | /**
2210 | * Schedules a command to retrieve the inner HTML of this element.
2211 | * @return {!webdriver.promise.Promise.<string>} A promise that will be
2212 | * resolved with the element's inner HTML.
2213 | */
2214 | webdriver.WebElement.prototype.getInnerHtml = function() {
2215 | return this.driver_.executeScript('return arguments[0].innerHTML', this);
2216 | };
2217 |
2218 |
2219 |
2220 | /**
2221 | * WebElementPromise is a promise that will be fulfilled with a WebElement.
2222 | * This serves as a forward proxy on WebElement, allowing calls to be
2223 | * scheduled without directly on this instance before the underlying
2224 | * WebElement has been fulfilled. In other words, the following two statements
2225 | * are equivalent:
2226 | *
2227 | * driver.findElement({id: 'my-button'}).click();
2228 | * driver.findElement({id: 'my-button'}).then(function(el) {
2229 | * return el.click();
2230 | * });
2231 | *
2232 | * @param {!webdriver.WebDriver} driver The parent WebDriver instance for this
2233 | * element.
2234 | * @param {!webdriver.promise.Promise.<!webdriver.WebElement>} el A promise
2235 | * that will resolve to the promised element.
2236 | * @constructor
2237 | * @extends {webdriver.WebElement}
2238 | * @implements {webdriver.promise.Thenable.<!webdriver.WebElement>}
2239 | * @final
2240 | */
2241 | webdriver.WebElementPromise = function(driver, el) {
2242 | webdriver.WebElement.call(this, driver, {'ELEMENT': 'unused'});
2243 |
2244 | /** @override */
2245 | this.cancel = goog.bind(el.cancel, el);
2246 |
2247 | /** @override */
2248 | this.isPending = goog.bind(el.isPending, el);
2249 |
2250 | /** @override */
2251 | this.then = goog.bind(el.then, el);
2252 |
2253 | /** @override */
2254 | this.thenCatch = goog.bind(el.thenCatch, el);
2255 |
2256 | /** @override */
2257 | this.thenFinally = goog.bind(el.thenFinally, el);
2258 |
2259 | /**
2260 | * Defers returning the element ID until the wrapped WebElement has been
2261 | * resolved.
2262 | * @override
2263 | */
2264 | this.getId = function() {
2265 | return el.then(function(el) {
2266 | return el.getId();
2267 | });
2268 | };
2269 | };
2270 | goog.inherits(webdriver.WebElementPromise, webdriver.WebElement);
2271 |
2272 |
2273 | /**
2274 | * Represents a modal dialog such as {@code alert}, {@code confirm}, or
2275 | * {@code prompt}. Provides functions to retrieve the message displayed with
2276 | * the alert, accept or dismiss the alert, and set the response text (in the
2277 | * case of {@code prompt}).
2278 | * @param {!webdriver.WebDriver} driver The driver controlling the browser this
2279 | * alert is attached to.
2280 | * @param {string} text The message text displayed with this alert.
2281 | * @constructor
2282 | */
2283 | webdriver.Alert = function(driver, text) {
2284 | /** @private {!webdriver.WebDriver} */
2285 | this.driver_ = driver;
2286 |
2287 | /** @private {!webdriver.promise.Promise.<string>} */
2288 | this.text_ = webdriver.promise.when(text);
2289 | };
2290 |
2291 |
2292 | /**
2293 | * Retrieves the message text displayed with this alert. For instance, if the
2294 | * alert were opened with alert("hello"), then this would return "hello".
2295 | * @return {!webdriver.promise.Promise.<string>} A promise that will be
2296 | * resolved to the text displayed with this alert.
2297 | */
2298 | webdriver.Alert.prototype.getText = function() {
2299 | return this.text_;
2300 | };
2301 |
2302 |
2303 | /**
2304 | * Accepts this alert.
2305 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
2306 | * when this command has completed.
2307 | */
2308 | webdriver.Alert.prototype.accept = function() {
2309 | return this.driver_.schedule(
2310 | new webdriver.Command(webdriver.CommandName.ACCEPT_ALERT),
2311 | 'WebDriver.switchTo().alert().accept()');
2312 | };
2313 |
2314 |
2315 | /**
2316 | * Dismisses this alert.
2317 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
2318 | * when this command has completed.
2319 | */
2320 | webdriver.Alert.prototype.dismiss = function() {
2321 | return this.driver_.schedule(
2322 | new webdriver.Command(webdriver.CommandName.DISMISS_ALERT),
2323 | 'WebDriver.switchTo().alert().dismiss()');
2324 | };
2325 |
2326 |
2327 | /**
2328 | * Sets the response text on this alert. This command will return an error if
2329 | * the underlying alert does not support response text (e.g. window.alert and
2330 | * window.confirm).
2331 | * @param {string} text The text to set.
2332 | * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
2333 | * when this command has completed.
2334 | */
2335 | webdriver.Alert.prototype.sendKeys = function(text) {
2336 | return this.driver_.schedule(
2337 | new webdriver.Command(webdriver.CommandName.SET_ALERT_TEXT).
2338 | setParameter('text', text),
2339 | 'WebDriver.switchTo().alert().sendKeys(' + text + ')');
2340 | };
2341 |
2342 |
2343 |
2344 | /**
2345 | * AlertPromise is a promise that will be fulfilled with an Alert. This promise
2346 | * serves as a forward proxy on an Alert, allowing calls to be scheduled
2347 | * directly on this instance before the underlying Alert has been fulfilled. In
2348 | * other words, the following two statements are equivalent:
2349 | *
2350 | * driver.switchTo().alert().dismiss();
2351 | * driver.switchTo().alert().then(function(alert) {
2352 | * return alert.dismiss();
2353 | * });
2354 | *
2355 | * @param {!webdriver.WebDriver} driver The driver controlling the browser this
2356 | * alert is attached to.
2357 | * @param {!webdriver.promise.Thenable.<!webdriver.Alert>} alert A thenable
2358 | * that will be fulfilled with the promised alert.
2359 | * @constructor
2360 | * @extends {webdriver.Alert}
2361 | * @implements {webdriver.promise.Thenable.<!webdriver.Alert>}
2362 | * @final
2363 | */
2364 | webdriver.AlertPromise = function(driver, alert) {
2365 | webdriver.Alert.call(this, driver, 'unused');
2366 |
2367 | /** @override */
2368 | this.cancel = goog.bind(alert.cancel, alert);
2369 |
2370 | /** @override */
2371 | this.isPending = goog.bind(alert.isPending, alert);
2372 |
2373 | /** @override */
2374 | this.then = goog.bind(alert.then, alert);
2375 |
2376 | /** @override */
2377 | this.thenCatch = goog.bind(alert.thenCatch, alert);
2378 |
2379 | /** @override */
2380 | this.thenFinally = goog.bind(alert.thenFinally, alert);
2381 |
2382 | /**
2383 | * Defer returning text until the promised alert has been resolved.
2384 | * @override
2385 | */
2386 | this.getText = function() {
2387 | return alert.then(function(alert) {
2388 | return alert.getText();
2389 | });
2390 | };
2391 |
2392 | /**
2393 | * Defers action until the alert has been located.
2394 | * @override
2395 | */
2396 | this.accept = function() {
2397 | return alert.then(function(alert) {
2398 | return alert.accept();
2399 | });
2400 | };
2401 |
2402 | /**
2403 | * Defers action until the alert has been located.
2404 | * @override
2405 | */
2406 | this.dismiss = function() {
2407 | return alert.then(function(alert) {
2408 | return alert.dismiss();
2409 | });
2410 | };
2411 |
2412 | /**
2413 | * Defers action until the alert has been located.
2414 | * @override
2415 | */
2416 | this.sendKeys = function(text) {
2417 | return alert.then(function(alert) {
2418 | return alert.sendKeys(text);
2419 | });
2420 | };
2421 | };
2422 | goog.inherits(webdriver.AlertPromise, webdriver.Alert);
2423 |
2424 |
2425 |
2426 | /**
2427 | * An error returned to indicate that there is an unhandled modal dialog on the
2428 | * current page.
2429 | * @param {string} message The error message.
2430 | * @param {string} text The text displayed with the unhandled alert.
2431 | * @param {!webdriver.Alert} alert The alert handle.
2432 | * @constructor
2433 | * @extends {bot.Error}
2434 | */
2435 | webdriver.UnhandledAlertError = function(message, text, alert) {
2436 | webdriver.UnhandledAlertError.base(
2437 | this, 'constructor', bot.ErrorCode.UNEXPECTED_ALERT_OPEN, message);
2438 |
2439 | /** @private {string} */
2440 | this.text_ = text;
2441 |
2442 | /** @private {!webdriver.Alert} */
2443 | this.alert_ = alert;
2444 | };
2445 | goog.inherits(webdriver.UnhandledAlertError, bot.Error);
2446 |
2447 |
2448 | /**
2449 | * @return {string} The text displayed with the unhandled alert.
2450 | */
2451 | webdriver.UnhandledAlertError.prototype.getAlertText = function() {
2452 | return this.text_;
2453 | };
2454 |
2455 |
2456 |
2457 | /**
2458 | * Used with {@link webdriver.WebElement#sendKeys WebElement#sendKeys} on file
2459 | * input elements ({@code <input type="file">}) to detect when the entered key
2460 | * sequence defines the path to a file.
2461 | *
2462 | * By default, {@linkplain webdriver.WebElement WebElement's} will enter all
2463 | * key sequences exactly as entered. You may set a
2464 | * {@linkplain webdriver.WebDriver#setFileDetector file detector} on the parent
2465 | * WebDriver instance to define custom behavior for handling file elements. Of
2466 | * particular note is the {@link selenium-webdriver/remote.FileDetector}, which
2467 | * should be used when running against a remote
2468 | * [Selenium Server](http://docs.seleniumhq.org/download/).
2469 | */
2470 | webdriver.FileDetector = goog.defineClass(null, {
2471 | /** @constructor */
2472 | constructor: function() {},
2473 |
2474 | /**
2475 | * Handles the file specified by the given path, preparing it for use with
2476 | * the current browser. If the path does not refer to a valid file, it will
2477 | * be returned unchanged, otherwisee a path suitable for use with the current
2478 | * browser will be returned.
2479 | *
2480 | * This default implementation is a no-op. Subtypes may override this
2481 | * function for custom tailored file handling.
2482 | *
2483 | * @param {!webdriver.WebDriver} driver The driver for the current browser.
2484 | * @param {string} path The path to process.
2485 | * @return {!webdriver.promise.Promise<string>} A promise for the processed
2486 | * file path.
2487 | * @package
2488 | */
2489 | handleFile: function(driver, path) {
2490 | return webdriver.promise.fulfilled(path);
2491 | }
2492 | });