selenium-webdriver 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
  };