selenium-webdriver 0.0.7 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -131,7 +131,6 @@ webdriver.LocalCommandProcessor.onResponse_ = function(command, e) {
131
131
  rawResponse['isError'],
132
132
  webdriver.Context.fromString(rawResponse['context']),
133
133
  rawResponse['response']);
134
- response.extraData['resultType'] = rawResponse['resultType'];
135
134
 
136
135
  // Only code in this file should be dispatching command events and listening
137
136
  // for response events, so this is safe. If someone else decided to attach a
@@ -230,12 +230,13 @@ webdriver.TestRunner.SINGLETON = null;
230
230
 
231
231
 
232
232
  webdriver.TestRunner.start = function(factoryFn) {
233
- if (webdriver.TestRunner.SINGLETON) {
234
- throw new Error('Singleton already initialized');
233
+ if (!webdriver.TestRunner.SINGLETON) {
234
+ webdriver.TestRunner.SINGLETON = new webdriver.TestRunner(factoryFn);
235
+ webdriver.TestRunner.SINGLETON.go();
235
236
  }
236
- webdriver.TestRunner.SINGLETON = new webdriver.TestRunner(factoryFn);
237
- webdriver.TestRunner.SINGLETON.go();
237
+ return webdriver.TestRunner.SINGLETON;
238
238
  };
239
+ goog.exportSymbol('WD_getTestRunner', webdriver.TestRunner.start);
239
240
 
240
241
 
241
242
  /**
@@ -585,21 +586,13 @@ webdriver.TestRunner.prototype.tearDown_ = function(result, driver) {
585
586
  */
586
587
  webdriver.TestRunner.prototype.handleDriverError_ = function(result, e) {
587
588
  result.passed = false;
588
- var failingCommand = e.target.getPendingCommand();
589
- var response = failingCommand ? failingCommand.response : null;
589
+ var failingCommand = e.target;
590
+ var response = failingCommand.getResponse();
590
591
  if (response) {
591
- result.errMsg = [];
592
- if (response.value) {
593
- result.errMsg.push(' ' + response.value);
594
- }
595
- goog.array.extend(
596
- result.errMsg, goog.array.map(response.errors, function(error) {
597
- return error.message + (error.stack ? ('\n' + error.stack) : '');
598
- }));
599
- result.errMsg = result.errMsg.join('\n');
592
+ result.errMsg = response.getErrorMessage();
600
593
  } else {
601
594
  // Should never happen, but just in case.
602
595
  result.errMsg = 'Unknown error!';
603
596
  }
604
- this.reportResult_(result, e.target);
597
+ this.reportResult_(result, failingCommand.getDriver());
605
598
  };
@@ -22,15 +22,14 @@ limitations under the License.
22
22
 
23
23
  goog.provide('webdriver.WebDriver');
24
24
  goog.provide('webdriver.WebDriver.EventType');
25
+ goog.provide('webdriver.WebDriver.Speed');
25
26
 
26
27
  goog.require('goog.events');
27
28
  goog.require('goog.events.EventTarget');
28
29
  goog.require('webdriver.Command');
29
30
  goog.require('webdriver.CommandName');
30
31
  goog.require('webdriver.Context');
31
- goog.require('webdriver.Future');
32
32
  goog.require('webdriver.Response');
33
- goog.require('webdriver.Wait');
34
33
  goog.require('webdriver.WebElement');
35
34
  goog.require('webdriver.logging');
36
35
  goog.require('webdriver.timing');
@@ -77,31 +76,23 @@ webdriver.WebDriver = function(commandProcessor) {
77
76
  this.commandProcessor_ = commandProcessor;
78
77
 
79
78
  /**
80
- * List of commands that have been sent to the command processor and are
81
- * await results. This array should only ever have three sizes:
82
- * 0: there are no pending commands with the command processor
83
- * 1: a single command is pending with the command processor
84
- * 2: a command within a wait condition is pending with the command
85
- * processor
86
- * @type {Array.<webdriver.Command>}
87
- * @private
88
- */
89
- this.pendingCommands_ = [];
90
-
91
- /**
92
- * A stack of frames for managing batched command execution order.
79
+ * A stack of frames for queued commands. The list of commands at index 0
80
+ * are global commands. When the stack has more than 1 frame, the commands
81
+ * in the list at the top of the stack are the remaining subcommands for the
82
+ * command at the top of the {@code pendingCommands_} stack.
93
83
  * @type {Array.<Array.<webdriver.Command>>}
94
84
  * @private
95
85
  */
96
- this.frames_ = [[]];
86
+ this.queuedCommands_ = [[]];
97
87
 
98
88
  /**
99
- * A list of commands that have been successfully completed since the last
100
- * reset.
89
+ * A list of commands that are currently being executed. The command at index
90
+ * N+1 is a subcommand to the command at index N. It will always be the case
91
+ * that {@code queuedCommands_.length == pendingCommands_.length + 1;}.
101
92
  * @type {Array.<webdriver.Command>}
102
- * @priate
93
+ * @private
103
94
  */
104
- this.commandHistory_ = [];
95
+ this.pendingCommands_ = [];
105
96
 
106
97
  /**
107
98
  * Whether this instance is paused. When paused, commands can still be issued,
@@ -188,10 +179,18 @@ webdriver.WebDriver.prototype.disposeInternal = function() {
188
179
  this.commandProcessor_.dispose();
189
180
  webdriver.timing.clearInterval(this.commandInterval_);
190
181
 
182
+ goog.array.forEach(this.pendingCommands_, function(command) {
183
+ command.dispose();
184
+ });
185
+ goog.array.forEach(this.queuedCommands_, function(frame) {
186
+ goog.array.forEach(frame, function(command) {
187
+ command.dispose();
188
+ });
189
+ });
190
+
191
191
  delete this.commandProcessor_;
192
192
  delete this.pendingCommands_;
193
- delete this.frames_;
194
- delete this.commandHistory_;
193
+ delete this.queuedCommands_;
195
194
  delete this.isPaused_;
196
195
  delete this.context_;
197
196
  delete this.sessionLocked_;
@@ -204,44 +203,60 @@ webdriver.WebDriver.prototype.disposeInternal = function() {
204
203
 
205
204
  /**
206
205
  * Queues a command to execute.
207
- * @param {webdriver.Command} command The command to execute.
208
- * @param {boolean} opt_addToFront Whether to add the command to the front or
209
- * back of the queue. Defaults to false.
206
+ * @param {webdriver.CommandName} name The name of the command to execute.
207
+ * @param {webdriver.WebElement} opt_element The element that is the target
208
+ * of the new command.
209
+ * @return {webdriver.Command} The new command.
210
210
  * @protected
211
211
  */
212
- webdriver.WebDriver.prototype.addCommand = function(command, opt_addToFront) {
213
- if (!(command instanceof webdriver.Command)) {
214
- throw new Error(
215
- 'IllegalArgument: command not an instanceof webdriver.Command');
216
- }
217
- var frame = goog.array.peek(this.frames_);
218
- if (opt_addToFront) {
219
- goog.array.insertAt(frame, command, 0);
220
- } else {
221
- frame.push(command);
222
- }
212
+ webdriver.WebDriver.prototype.addCommand = function(name, opt_element) {
213
+ var command = new webdriver.Command(this, name, opt_element);
214
+ goog.array.peek(this.queuedCommands_).push(command);
215
+ return command;
223
216
  };
224
217
 
225
218
 
226
219
  /**
227
- * @return {webdriver.Command} The command currently being executed or
228
- * {@code undefined}.
220
+ * @return {boolean} Whether this driver is idle (there are no pending
221
+ * commands).
229
222
  */
230
- webdriver.WebDriver.prototype.getPendingCommand = function() {
231
- return goog.array.peek(this.pendingCommands_);
223
+ webdriver.WebDriver.prototype.isIdle = function() {
224
+ // If there is a finished command on the pending command queue, but it
225
+ // failed, then the failure hasn't been dealt with yet and the driver will
226
+ // not process any more commands, so we consider this idle.
227
+ var pendingCommand = goog.array.peek(this.pendingCommands_);
228
+ if (pendingCommand && pendingCommand.isFinished() &&
229
+ pendingCommand.getResponse().isFailure) {
230
+ return true;
231
+ }
232
+ return !pendingCommand && this.queuedCommands_.length == 1 &&
233
+ !this.queuedCommands_[0].length;
232
234
  };
233
235
 
234
236
 
235
237
  /**
236
- * Aborts the pending command, if any. If the pending command is part of a
237
- * {@code #wait()}, then the entire wait operation will be aborted.
238
+ * Aborts the specified command and all of its pending subcommands.
239
+ * @param {webdriver.Command} command The command to abort.
240
+ * @return {number} The total number of commands aborted. A value of 0
241
+ * indicates that the given command was not a pending command.
238
242
  */
239
- webdriver.WebDriver.prototype.abortPendingCommand = function() {
240
- goog.array.forEach(this.pendingCommands_, function(command) {
241
- command.abort = true;
243
+ webdriver.WebDriver.prototype.abortCommand = function(command) {
244
+ var index = goog.array.findIndexRight(this.pendingCommands_, function(cmd) {
245
+ return cmd == command;
242
246
  });
243
- this.pendingCommands_ = [];
244
- this.waitFrame_ = null;
247
+ if (index >= 0) {
248
+ var numAborted = this.pendingCommands_.length - index;
249
+ var totalNumAborted = numAborted;
250
+ for (var i = 0; i < numAborted; i++) {
251
+ this.pendingCommands_.pop().dispose();
252
+ goog.array.forEach(this.queuedCommands_.pop(), function(subCommand) {
253
+ totalNumAborted += 1;
254
+ subCommand.dispose();
255
+ });
256
+ }
257
+ return totalNumAborted;
258
+ }
259
+ return 0;
245
260
  };
246
261
 
247
262
 
@@ -273,62 +288,38 @@ webdriver.WebDriver.prototype.resume = function() {
273
288
  * @private
274
289
  */
275
290
  webdriver.WebDriver.prototype.processCommands_ = function() {
276
- if (this.isPaused_) {
291
+ var pendingCommand = goog.array.peek(this.pendingCommands_);
292
+ if (this.isPaused_ || (pendingCommand && !pendingCommand.isFinished())) {
277
293
  return;
278
294
  }
279
295
 
280
- var pendingCommand = this.getPendingCommand();
281
- if (pendingCommand && webdriver.CommandName.WAIT != pendingCommand.name) {
296
+ if (pendingCommand && pendingCommand.getResponse().isFailure) {
297
+ // Or should we be throwing this to be caught by window.onerror?
298
+ webdriver.logging.error(
299
+ 'Unhandled command failure; halting command processing:\n' +
300
+ pendingCommand.getResponse().getErrorMessage());
282
301
  return;
283
302
  }
284
303
 
285
- var currentFrame = goog.array.peek(this.frames_);
304
+ var currentFrame = goog.array.peek(this.queuedCommands_);
286
305
  var nextCommand = currentFrame.shift();
306
+ while (!nextCommand && this.queuedCommands_.length > 1) {
307
+ this.queuedCommands_.pop();
308
+ this.pendingCommands_.pop();
309
+ currentFrame = goog.array.peek(this.queuedCommands_);
310
+ nextCommand = currentFrame.shift();
311
+ }
312
+
287
313
  if (nextCommand) {
314
+ var parentTarget = goog.array.peek(this.pendingCommands_) || this;
315
+ nextCommand.setParentEventTarget(parentTarget);
288
316
  this.pendingCommands_.push(nextCommand);
289
- if (nextCommand.name == webdriver.CommandName.FUNCTION) {
290
- this.frames_.push([]);
291
- } else if (nextCommand.name == webdriver.CommandName.WAIT) {
292
- this.waitFrame_ = [];
293
- this.frames_.push(this.waitFrame_);
294
- }
295
-
296
- nextCommand.setCompleteCallback(this.onCommandComplete_, this);
317
+ this.queuedCommands_.push([]);
297
318
  this.commandProcessor_.execute(nextCommand, this.sessionId_, this.context_);
298
- } else if (this.frames_.length > 1) {
299
- if (currentFrame !== this.waitFrame_) {
300
- this.frames_.pop();
301
- }
302
- }
303
- };
304
-
305
-
306
- /**
307
- * Callback for when a pending {@code webdriver.Command} is finished.
308
- * @private
309
- */
310
- webdriver.WebDriver.prototype.onCommandComplete_ = function(command) {
311
- this.commandHistory_.push(command);
312
- if (command.response.isFailure || command.response.errors.length) {
313
- if (webdriver.CommandName.WAIT == command.name) {
314
- // The wait terminated early. Abort all other commands issued inside the
315
- // wait condition.
316
- for (var i = 1; i < this.pendingCommands_.length; i++) {
317
- this.pendingCommands_[i].abort = true;
318
- }
319
- this.pendingCommands_ = [this.pendingCommands_[0]];
320
- }
321
- this.dispatchEvent(webdriver.WebDriver.EventType.ERROR);
322
- } else {
323
- this.pendingCommands_.pop();
324
- if (webdriver.CommandName.WAIT == command.name) {
325
- this.waitFrame_ = null;
326
- }
327
319
  }
328
320
  };
329
321
 
330
322
 
331
-
332
323
  /**
333
324
  * @return {?string} This instance's current session ID or {@code null} if it
334
325
  * does not have one yet.
@@ -346,6 +337,15 @@ webdriver.WebDriver.prototype.getContext = function() {
346
337
  };
347
338
 
348
339
 
340
+ /**
341
+ * Sets this driver's context.
342
+ * @param {webdriver.Context} context The new context.
343
+ */
344
+ webdriver.WebDriver.prototype.setContext = function(context) {
345
+ return this.context_ = context;
346
+ };
347
+
348
+
349
349
  // ----------------------------------------------------------------------------
350
350
  // Client command functions:
351
351
  // ----------------------------------------------------------------------------
@@ -361,66 +361,32 @@ webdriver.WebDriver.prototype.getContext = function() {
361
361
  */
362
362
  webdriver.WebDriver.prototype.catchExpectedError = function(opt_errorMsg,
363
363
  opt_handlerFn) {
364
- var currentFrame = goog.array.peek(this.frames_);
365
- var previousCommand = currentFrame.pop();
364
+ var currentFrame = goog.array.peek(this.queuedCommands_);
365
+ var previousCommand = goog.array.peek(currentFrame);
366
366
  if (!previousCommand) {
367
367
  throw new Error('No commands in the queue to expect an error from');
368
368
  }
369
369
 
370
- var listener =
371
- goog.events.getListener(this, webdriver.WebDriver.EventType.ERROR, true);
372
- if (listener) {
373
- throw new Error('IllegalState: Driver already has a registered ' +
374
- 'expected error handler');
375
- }
376
-
377
- var caughtError = false;
378
- var handleError = function(e) {
379
- caughtError = true;
380
- e.stopPropagation();
381
- e.preventDefault();
382
- if (goog.isFunction(opt_handlerFn)) {
383
- opt_handlerFn(e.target.getPendingCommand());
384
- }
385
- goog.events.removeAll(
386
- e.target, webdriver.WebDriver.EventType.ERROR, /*capture=*/true);
387
-
388
- // Errors cause the pending command to hang. Go ahead and abort that command
389
- // so we can proceed.
390
- this.abortPendingCommand();
391
- var frame = goog.array.peek(this.frames_);
392
- while (frame !== currentFrame) {
393
- this.frames_.pop();
394
- frame = goog.array.peek(this.frames_);
370
+ var failedCommand = null;
371
+ var key = goog.events.listenOnce(previousCommand,
372
+ webdriver.Command.ERROR_EVENT, function(e) {
373
+ failedCommand = e.target;
374
+ this.abortCommand(e.currentTarget);
375
+ e.preventDefault();
376
+ e.stopPropagation();
377
+ return false;
378
+ }, /*capture phase*/true, this);
379
+
380
+ this.callFunction(function() {
381
+ if (null == failedCommand) {
382
+ goog.events.unlistenByKey(key);
383
+ throw new Error(
384
+ (opt_errorMsg ? (opt_errorMsg + '\n') : '') +
385
+ 'Expected an error but none were raised.');
386
+ } else if (goog.isFunction(opt_handlerFn)) {
387
+ opt_handlerFn(failedCommand);
395
388
  }
396
- return false;
397
- };
398
-
399
- // Surround the last command with two new commands. The first enables our
400
- // error listener which cancels any errors. The second verifies that we
401
- // caught an error. If not, it fails the test.
402
- var catchExpected = new webdriver.Command(webdriver.CommandName.FUNCTION).
403
- setParameters(goog.bind(function() {
404
- goog.events.listenOnce(this, webdriver.WebDriver.EventType.ERROR,
405
- handleError, /*capture=*/true);
406
- }, this));
407
-
408
- var cleanupCatch = new webdriver.Command(webdriver.CommandName.FUNCTION).
409
- setParameters(goog.bind(function() {
410
- // Need to unlisten for error events so the error below doesn't get
411
- // blocked.
412
- goog.events.unlisten(this, webdriver.WebDriver.EventType.ERROR,
413
- handleError, /*capture=*/true);
414
- if (!caughtError) {
415
- throw new Error(
416
- (opt_errorMsg ? (opt_errorMsg + '\n') : '') +
417
- 'Expected an error but none were raised.');
418
- }
419
- }, this));
420
-
421
- currentFrame.push(catchExpected);
422
- currentFrame.push(previousCommand);
423
- currentFrame.push(cleanupCatch);
389
+ });
424
390
  };
425
391
 
426
392
 
@@ -441,8 +407,7 @@ webdriver.WebDriver.prototype.pause = function() {
441
407
  * sleep.
442
408
  */
443
409
  webdriver.WebDriver.prototype.sleep = function(ms) {
444
- this.addCommand(new webdriver.Command(webdriver.CommandName.SLEEP).
445
- setParameters(ms));
410
+ this.addCommand(webdriver.CommandName.SLEEP).setParameters(ms);
446
411
  };
447
412
 
448
413
 
@@ -453,24 +418,27 @@ webdriver.WebDriver.prototype.sleep = function(ms) {
453
418
  * {@code webdriver.Response} and passed to any subsequent function commands.
454
419
  * @param {function} fn The function to call; should take a single
455
420
  * {@code webdriver.Response} object.
421
+ * @return {webdriver.Future} The result of the function wrapped in a future.
456
422
  */
457
423
  webdriver.WebDriver.prototype.callFunction = function(fn, opt_selfObj,
458
424
  var_args) {
459
425
  var args = goog.array.slice(arguments, 2);
426
+ var frame = goog.array.peek(this.queuedCommands_);
427
+ var previousCommand = goog.array.peek(frame);
460
428
  var wrappedFunction = goog.bind(function() {
461
- var lastCommand = goog.array.peek(this.commandHistory_);
462
- args.push(lastCommand ? lastCommand.response :null);
463
- fn.apply(opt_selfObj, args);
429
+ args.push(previousCommand ? previousCommand.getResponse() : null);
430
+ return fn.apply(opt_selfObj, args);
464
431
  }, this);
465
- this.addCommand(new webdriver.Command(webdriver.CommandName.FUNCTION).
466
- setParameters(wrappedFunction));
432
+ return this.addCommand(webdriver.CommandName.FUNCTION).
433
+ setParameters(wrappedFunction).
434
+ getFutureResult();
467
435
  };
468
436
 
469
437
 
470
438
  /**
471
439
  * Waits for a condition to be true before executing the next command. If the
472
440
  * condition does not hold after the given {@code timeout}, an error will be
473
- * raised. Only one wait may be performed at a time (e.g. no nesting).
441
+ * raised.
474
442
  * Example:
475
443
  * <code>
476
444
  * driver.get('http://www.google.com');
@@ -481,23 +449,46 @@ webdriver.WebDriver.prototype.callFunction = function(fn, opt_selfObj,
481
449
  * @param {number} timeout The maximum amount of time to wait, in milliseconds.
482
450
  * @param {Object} opt_self (Optional) The object in whose context to execute
483
451
  * the {@code conditionFn}.
484
- * @throws If this driver is currently executing another wait command.
485
- * @see webdriver.Wait
486
- */
487
- webdriver.WebDriver.prototype.wait = function(conditionFn, timeout, opt_self) {
488
- if (this.pendingCommands_.length) {
489
- var command = this.pendingCommands_[0];
490
- if (webdriver.CommandName.WAIT == command.name) {
491
- throw new Error('Nested waits are not supported');
452
+ * @param {boolean} opt_waitNot (Optional) Whether to wait for the inverse of
453
+ * the {@code conditionFn}.
454
+ */
455
+ webdriver.WebDriver.prototype.wait = function(conditionFn, timeout, opt_self,
456
+ opt_waitNot) {
457
+ conditionFn = goog.bind(conditionFn, opt_self);
458
+ var waitOnInverse = !!opt_waitNot;
459
+ var callFunction = goog.bind(this.callFunction, this);
460
+
461
+ function pollFunction(opt_startTime, opt_future) {
462
+ var startTime = opt_startTime || goog.now();
463
+
464
+ function checkValue(value) {
465
+ var pendingFuture = null;
466
+ if (value instanceof webdriver.Future) {
467
+ if (value.isSet()) {
468
+ value = value.getValue();
469
+ } else {
470
+ pendingFuture = value;
471
+ value = null;
472
+ }
473
+ }
474
+
475
+ var done = !pendingFuture && (waitOnInverse != !!value);
476
+ if (!done) {
477
+ var ellapsed = goog.now() - startTime;
478
+ if (ellapsed > timeout) {
479
+ throw Error('Wait timed out after ' + ellapsed + 'ms');
480
+ }
481
+ callFunction(pollFunction, null, startTime, pendingFuture);
482
+ }
492
483
  }
493
- }
494
484
 
495
- if (opt_self) {
496
- conditionFn = goog.bind(conditionFn, opt_self);
485
+ var result = opt_future || conditionFn();
486
+ checkValue(result);
497
487
  }
498
- var waitOp = new webdriver.Wait(conditionFn, timeout);
499
- this.addCommand(new webdriver.Command(webdriver.CommandName.WAIT).
500
- setParameters(waitOp));
488
+
489
+ // Binding pollFunction for our initial values.
490
+ var initialPoll = goog.bind(pollFunction, null, 0, null);
491
+ this.addCommand(webdriver.CommandName.WAIT).setParameters(initialPoll);
501
492
  };
502
493
 
503
494
 
@@ -514,17 +505,10 @@ webdriver.WebDriver.prototype.wait = function(conditionFn, timeout, opt_self) {
514
505
  * @param {number} timeout The maximum amount of time to wait, in milliseconds.
515
506
  * @param {Object} opt_self (Optional) The object in whose context to execute
516
507
  * the {@code conditionFn}.
517
- * @see webdriver.Wait
518
508
  */
519
- webdriver.WebDriver.prototype.waitNot = function(conditionFn, timeout, opt_self,
520
- opt_interval) {
521
- if (opt_self) {
522
- conditionFn = goog.bind(conditionFn, opt_self);
523
- }
524
- var waitOp = new webdriver.Wait(conditionFn, timeout);
525
- waitOp.waitOnInverse(true);
526
- this.addCommand(new webdriver.Command(webdriver.CommandName.WAIT).
527
- setParameters(waitOp));
509
+ webdriver.WebDriver.prototype.waitNot = function(conditionFn, timeout,
510
+ opt_self) {
511
+ this.wait(conditionFn, timeout, opt_self, true);
528
512
  };
529
513
 
530
514
 
@@ -537,12 +521,12 @@ webdriver.WebDriver.prototype.waitNot = function(conditionFn, timeout, opt_self,
537
521
  */
538
522
  webdriver.WebDriver.prototype.newSession = function(lockSession) {
539
523
  if (lockSession) {
540
- this.addCommand(new webdriver.Command(webdriver.CommandName.NEW_SESSION).
524
+ this.addCommand(webdriver.CommandName.NEW_SESSION).
541
525
  setSuccessCallback(function(response) {
542
526
  this.sessionLocked_ = lockSession;
543
527
  this.sessionId_ = response.value;
544
528
  this.context_ = response.context;
545
- }, this));
529
+ }, this);
546
530
  } else {
547
531
  webdriver.logging.warn(
548
532
  'Cannot start new session; driver is locked into current session');
@@ -558,11 +542,11 @@ webdriver.WebDriver.prototype.newSession = function(lockSession) {
558
542
  * {@code #getWindowHandle()} or {@code #getAllWindowHandles()}.
559
543
  */
560
544
  webdriver.WebDriver.prototype.switchToWindow = function(name) {
561
- this.addCommand(new webdriver.Command(webdriver.CommandName.SWITCH_TO_WINDOW).
545
+ this.addCommand(webdriver.CommandName.SWITCH_TO_WINDOW).
562
546
  setParameters(name).
563
547
  setSuccessCallback(function(response) {
564
548
  this.context_ = response.value;
565
- }, this));
549
+ }, this);
566
550
  };
567
551
 
568
552
 
@@ -580,13 +564,13 @@ webdriver.WebDriver.prototype.switchToFrame = function(frame) {
580
564
  var commandName = webdriver.CommandName.SWITCH_TO_FRAME;
581
565
  var command;
582
566
  if (goog.isString(frame) || goog.isNumber(frame)) {
583
- command = new webdriver.Command(commandName).setParameters(frame);
567
+ command = this.addCommand(commandName).setParameters(frame);
584
568
  } else {
585
- command = new webdriver.Command(commandName, frame);
569
+ command = this.addCommand(commandName, frame);
586
570
  }
587
- this.addCommand(command.setSuccessCallback(function(response) {
571
+ command.setSuccessCallback(function(response) {
588
572
  this.context_ = response.context;
589
- }, this));
573
+ }, this);
590
574
  };
591
575
 
592
576
 
@@ -595,13 +579,11 @@ webdriver.WebDriver.prototype.switchToFrame = function(frame) {
595
579
  * contains iframes.
596
580
  */
597
581
  webdriver.WebDriver.prototype.switchToDefaultContent = function() {
598
- var command =
599
- new webdriver.Command(webdriver.CommandName.SWITCH_TO_DEFAULT_CONTENT).
600
- setParameters(null).
601
- setSuccessCallback(function(response) {
602
- this.context_ = response.context;
603
- }, this);
604
- this.addCommand(command);
582
+ this.addCommand(webdriver.CommandName.SWITCH_TO_DEFAULT_CONTENT).
583
+ setParameters(null).
584
+ setSuccessCallback(function(response) {
585
+ this.context_ = response.context;
586
+ }, this);
605
587
  };
606
588
 
607
589
 
@@ -610,12 +592,8 @@ webdriver.WebDriver.prototype.switchToDefaultContent = function() {
610
592
  * @return {webdriver.Future} The current handle wrapped in a Future.
611
593
  */
612
594
  webdriver.WebDriver.prototype.getWindowHandle = function() {
613
- var handle = new webdriver.Future(this);
614
- var command =
615
- new webdriver.Command(webdriver.CommandName.GET_CURRENT_WINDOW_HANDLE).
616
- setSuccessCallback(handle.setValueFromResponse, handle);
617
- this.addCommand(command);
618
- return handle;
595
+ return this.addCommand(webdriver.CommandName.GET_CURRENT_WINDOW_HANDLE).
596
+ getFutureResult();
619
597
  };
620
598
 
621
599
 
@@ -623,12 +601,7 @@ webdriver.WebDriver.prototype.getWindowHandle = function() {
623
601
  * Retrieves the handles for all known windows.
624
602
  */
625
603
  webdriver.WebDriver.prototype.getAllWindowHandles = function() {
626
- var command =
627
- new webdriver.Command(webdriver.CommandName.GET_WINDOW_HANDLES).
628
- setSuccessCallback(function(response) {
629
- response.value = response.value.split(',');
630
- });
631
- this.addCommand(command);
604
+ this.addCommand(webdriver.CommandName.GET_WINDOW_HANDLES);
632
605
  };
633
606
 
634
607
 
@@ -637,11 +610,8 @@ webdriver.WebDriver.prototype.getAllWindowHandles = function() {
637
610
  * @return {webdriver.Future} The page source wrapped in a Future.
638
611
  */
639
612
  webdriver.WebDriver.prototype.getPageSource = function() {
640
- var source = new webdriver.Future(this);
641
- var command = new webdriver.Command(webdriver.CommandName.GET_PAGE_SOURCE).
642
- setSuccessCallback(source.setValueFromResponse, source);
643
- this.addCommand(command);
644
- return source;
613
+ return this.addCommand(webdriver.CommandName.GET_PAGE_SOURCE).
614
+ getFutureResult();
645
615
  };
646
616
 
647
617
 
@@ -651,7 +621,7 @@ webdriver.WebDriver.prototype.getPageSource = function() {
651
621
  * script window (e.g. the window sending commands to the driver)</strong>
652
622
  */
653
623
  webdriver.WebDriver.prototype.close = function() {
654
- this.addCommand(new webdriver.Command(webdriver.CommandName.CLOSE));
624
+ this.addCommand(webdriver.CommandName.CLOSE);
655
625
  };
656
626
 
657
627
 
@@ -664,7 +634,7 @@ webdriver.WebDriver.prototype.close = function() {
664
634
  * @see {webdriver.WebDriver.prototype.executeScript}
665
635
  * @private
666
636
  */
667
- webdriver.WebDriver.mapToExecuteScriptArgument_ = function(arg) {
637
+ webdriver.WebDriver.wrapScriptArgument_ = function(arg) {
668
638
  var type, value;
669
639
  if (arg instanceof webdriver.WebElement) {
670
640
  type = 'ELEMENT';
@@ -674,6 +644,9 @@ webdriver.WebDriver.mapToExecuteScriptArgument_ = function(arg) {
674
644
  goog.isString(arg)) {
675
645
  type = goog.typeOf(arg).toUpperCase();
676
646
  value = arg;
647
+ } else if (goog.isArray(arg)) {
648
+ type = goog.typeOf(arg).toUpperCase();
649
+ value = goog.array.map(arg, webdriver.WebDriver.wrapScriptArgument_);
677
650
  } else {
678
651
  throw new Error('Invalid script argument type: ' + goog.typeOf(arg));
679
652
  }
@@ -681,6 +654,30 @@ webdriver.WebDriver.mapToExecuteScriptArgument_ = function(arg) {
681
654
  };
682
655
 
683
656
 
657
+ /**
658
+ * Helper function for unwrapping an executeScript result.
659
+ * @param {{type:string,value:*}|Array.<{type:string,value:*}>} result The
660
+ * result to unwrap.
661
+ * @return {*} The unwrapped result.
662
+ * @private
663
+ */
664
+ webdriver.WebDriver.prototype.unwrapScriptResult_ = function(result) {
665
+ switch (result.type) {
666
+ case 'ELEMENT':
667
+ var element = new webdriver.WebElement(this);
668
+ element.getId().setValue(result.value);
669
+ return element;
670
+
671
+ case 'ARRAY':
672
+ return goog.array.map(result.value, goog.bind(
673
+ this.unwrapScriptResult_, this));
674
+
675
+ default:
676
+ return result.value;
677
+ }
678
+ };
679
+
680
+
684
681
  /**
685
682
  * Adds a command to execute a JavaScript snippet in the window of the page
686
683
  * currently under test.
@@ -692,29 +689,13 @@ webdriver.WebDriver.mapToExecuteScriptArgument_ = function(arg) {
692
689
  webdriver.WebDriver.prototype.executeScript = function(script, var_args) {
693
690
  var args = goog.array.map(
694
691
  goog.array.slice(arguments, 1),
695
- webdriver.WebDriver.mapToExecuteScriptArgument_);
696
- var result = new webdriver.Future(this);
697
- this.addCommand(new webdriver.Command(webdriver.CommandName.EXECUTE_SCRIPT).
692
+ webdriver.WebDriver.wrapScriptArgument_);
693
+ return this.addCommand(webdriver.CommandName.EXECUTE_SCRIPT).
698
694
  setParameters(script, args).
699
695
  setSuccessCallback(function(response) {
700
- switch(response.extraData['resultType']) {
701
- case 'NULL':
702
- response.value = null;
703
- break;
704
-
705
- case 'ELEMENT':
706
- var id = response.value;
707
- response.value = new webdriver.WebElement(this);
708
- response.value.getId().setValue(id);
709
- break;
710
-
711
- case 'OTHER': // Fall-through
712
- default:
713
- break;
714
- }
715
- result.setValue(response.value);
716
- }, this));
717
- return result;
696
+ response.value = this.unwrapScriptResult_(response.value);
697
+ }, this).
698
+ getFutureResult();
718
699
  };
719
700
 
720
701
 
@@ -723,11 +704,11 @@ webdriver.WebDriver.prototype.executeScript = function(script, var_args) {
723
704
  * @param {goog.Uri|string} url The URL to fetch.
724
705
  */
725
706
  webdriver.WebDriver.prototype.get = function(url) {
726
- this.addCommand(new webdriver.Command(webdriver.CommandName.GET).
707
+ this.addCommand(webdriver.CommandName.GET).
727
708
  setParameters(url.toString()).
728
709
  setSuccessCallback(function(response) {
729
710
  this.context_ = response.context;
730
- }, this));
711
+ }, this);
731
712
  };
732
713
 
733
714
 
@@ -735,7 +716,7 @@ webdriver.WebDriver.prototype.get = function(url) {
735
716
  * Navigate backwards in the current browser window's history.
736
717
  */
737
718
  webdriver.WebDriver.prototype.back = function() {
738
- this.addCommand(new webdriver.Command(webdriver.CommandName.BACK));
719
+ this.addCommand(webdriver.CommandName.BACK);
739
720
  };
740
721
 
741
722
 
@@ -743,7 +724,7 @@ webdriver.WebDriver.prototype.back = function() {
743
724
  * Navigate forwards in the current browser window's history.
744
725
  */
745
726
  webdriver.WebDriver.prototype.forward = function() {
746
- this.addCommand(new webdriver.Command(webdriver.CommandName.FORWARD));
727
+ this.addCommand(webdriver.CommandName.FORWARD);
747
728
  };
748
729
 
749
730
 
@@ -751,7 +732,7 @@ webdriver.WebDriver.prototype.forward = function() {
751
732
  * Refresh the current page.
752
733
  */
753
734
  webdriver.WebDriver.prototype.refresh = function() {
754
- this.addCommand(new webdriver.Command(webdriver.CommandName.REFRESH));
735
+ this.addCommand(webdriver.CommandName.REFRESH);
755
736
  };
756
737
 
757
738
 
@@ -760,10 +741,8 @@ webdriver.WebDriver.prototype.refresh = function() {
760
741
  * @return {webdriver.Future} The current URL in a webdriver.Future.
761
742
  */
762
743
  webdriver.WebDriver.prototype.getCurrentUrl = function() {
763
- var url = new webdriver.Future(this);
764
- this.addCommand(new webdriver.Command(webdriver.CommandName.GET_CURRENT_URL).
765
- setSuccessCallback(url.setValueFromResponse, url));
766
- return url;
744
+ return this.addCommand(webdriver.CommandName.GET_CURRENT_URL).
745
+ getFutureResult();
767
746
  };
768
747
 
769
748
 
@@ -772,10 +751,8 @@ webdriver.WebDriver.prototype.getCurrentUrl = function() {
772
751
  * @return {webdriver.Future} The current page title.
773
752
  */
774
753
  webdriver.WebDriver.prototype.getTitle = function() {
775
- var title = new webdriver.Future(this);
776
- this.addCommand(new webdriver.Command(webdriver.CommandName.GET_TITLE).
777
- setSuccessCallback(title.setValueFromResponse, title));
778
- return title;
754
+ return this.addCommand(webdriver.CommandName.GET_TITLE).
755
+ getFutureResult();
779
756
  };
780
757
 
781
758
 
@@ -831,8 +808,8 @@ webdriver.WebDriver.prototype.findElements = function(by) {
831
808
  * @param {webdriver.WebDriver.Speed} speed The new speed setting.
832
809
  */
833
810
  webdriver.WebDriver.prototype.setMouseSpeed = function(speed) {
834
- this.addCommand(new webdriver.Command(webdriver.CommandName.SET_MOUSE_SPEED).
835
- setParameters(speed));
811
+ this.addCommand(webdriver.CommandName.SET_MOUSE_SPEED).
812
+ setParameters(speed);
836
813
  };
837
814
 
838
815
 
@@ -842,12 +819,6 @@ webdriver.WebDriver.prototype.setMouseSpeed = function(speed) {
842
819
  * when the query command completes.
843
820
  */
844
821
  webdriver.WebDriver.prototype.getMouseSpeed = function() {
845
- var speed = new webdriver.Future(this);
846
- this.addCommand(
847
- new webdriver.Command(webdriver.CommandName.GET_MOUSE_SPEED).
848
- setSuccessCallback(function(response) {
849
- response.value = Number(response.value);
850
- speed.setValue(response.value);
851
- }));
852
- return speed;
822
+ return this.addCommand(webdriver.CommandName.GET_MOUSE_SPEED).
823
+ getFutureResult();
853
824
  };