| // Copyright 2011 Software Freedom Conservancy. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| goog.provide('webdriver.test.testutil'); |
| |
| goog.require('goog.array'); |
| goog.require('goog.json'); |
| goog.require('goog.string'); |
| goog.require('goog.testing.MockClock'); |
| goog.require('goog.testing.recordFunction'); |
| |
| |
| /** @type {?goog.testing.MockClock} */ |
| webdriver.test.testutil.clock = null; |
| |
| /** @type {Array.<!string>} */ |
| webdriver.test.testutil.messages = []; |
| |
| /** @type {!Error} */ |
| webdriver.test.testutil.STUB_ERROR = new Error('ouch'); |
| webdriver.test.testutil.STUB_ERROR.stack = '(stub error; stack irrelevant)'; |
| |
| webdriver.test.testutil.throwStubError = function() { |
| throw webdriver.test.testutil.STUB_ERROR; |
| }; |
| |
| webdriver.test.testutil.assertIsStubError = function(error) { |
| assertEquals(webdriver.test.testutil.STUB_ERROR, error); |
| }; |
| |
| webdriver.test.testutil.createMockClock = function() { |
| webdriver.test.testutil.clock = new goog.testing.MockClock(true); |
| |
| /* Patch to work around the following bug with mock clock: |
| * function testNewZeroBasedTimeoutsRunInNextEventLoopAfterExistingTasks() { |
| * var events = []; |
| * setInterval(function() { events.push('a'); }, 1); |
| * setTimeout(function() { events.push('b'); }, 0); |
| * clock.tick(); |
| * assertEquals('ab', events.join('')); |
| * } |
| */ |
| goog.testing.MockClock.insert_ = function(timeout, queue) { |
| if (timeout.runAtMillis === goog.now() && timeout.millis === 0) { |
| timeout.runAtMillis += 1; |
| } |
| // Original goog.testing.MockClock.insert_ follows. |
| for (var i = queue.length; i != 0; i--) { |
| if (queue[i - 1].runAtMillis > timeout.runAtMillis) { |
| break; |
| } |
| queue[i] = queue[i - 1]; |
| } |
| queue[i] = timeout; |
| }; |
| |
| /* Patch to work around the following bug with mock clock: |
| * function testZeroBasedTimeoutsRunInNextEventLoop() { |
| * var count = 0; |
| * setTimeout(function() { |
| * count += 1; |
| * setTimeout(function() { count += 1; }, 0); |
| * setTimeout(function() { count += 1; }, 0); |
| * }, 0); |
| * clock.tick(); |
| * assertEquals(1, count); // Fails; count == 3 |
| * clock.tick(); |
| * assertEquals(3, count); |
| * } |
| */ |
| webdriver.test.testutil.clock.runFunctionsWithinRange_ = function(endTime) { |
| var adjustedEndTime = endTime - this.timeoutDelay_; |
| |
| // Repeatedly pop off the last item since the queue is always sorted. |
| // Stop once we've collected all timeouts that should run. |
| var timeouts = []; |
| while (this.queue_.length && |
| this.queue_[this.queue_.length - 1].runAtMillis <= adjustedEndTime) { |
| timeouts.push(this.queue_.pop()); |
| } |
| |
| // Now run all timeouts that are within range. |
| while (timeouts.length) { |
| var timeout = timeouts.shift(); |
| |
| if (!(timeout.timeoutKey in this.deletedKeys_)) { |
| // Only move time forwards. |
| this.nowMillis_ = Math.max(this.nowMillis_, |
| timeout.runAtMillis + this.timeoutDelay_); |
| // Call timeout in global scope and pass the timeout key as |
| // the argument. |
| timeout.funcToCall.call(goog.global, timeout.timeoutKey); |
| // In case the interval was cleared in the funcToCall |
| if (timeout.recurring) { |
| this.scheduleFunction_( |
| timeout.timeoutKey, timeout.funcToCall, timeout.millis, true); |
| } |
| } |
| } |
| }; |
| |
| return webdriver.test.testutil.clock; |
| }; |
| |
| |
| /** |
| * Advances the clock by one tick. |
| * @param {number=} opt_n The number of ticks to advance the clock. If not |
| * specified, will advance the clock once for every timeout made. |
| * Assumes all timeouts are 0-based. |
| */ |
| webdriver.test.testutil.consumeTimeouts = function(opt_n) { |
| // webdriver.promise and webdriver.application only schedule 0 timeouts to |
| // yield until the next available event loop. |
| for (var i = 0; |
| i < (opt_n || webdriver.test.testutil.clock.getTimeoutsMade()); i++) { |
| webdriver.test.testutil.clock.tick(); |
| } |
| }; |
| |
| |
| /** |
| * Asserts the contents of the {@link webdriver.test.testutil.messages} array |
| * are as expected. |
| * @param {...*} var_args The expected contents. |
| */ |
| webdriver.test.testutil.assertMessages = function(var_args) { |
| var args = Array.prototype.slice.call(arguments, 0); |
| assertArrayEquals(args, webdriver.test.testutil.messages); |
| }; |
| |
| |
| /** |
| * Wraps a call to {@link webdriver.test.testutil.assertMessages} so it can |
| * be passed as a callback. |
| * @param {...*} var_args The expected contents. |
| * @return {!Function} The wrapped function. |
| */ |
| webdriver.test.testutil.assertingMessages = function(var_args) { |
| var args = goog.array.slice(arguments, 0); |
| return function() { |
| return webdriver.test.testutil.assertMessages.apply(null, args); |
| }; |
| }; |
| |
| |
| /** |
| * Asserts an object is a promise. |
| * @param {*} obj The object to check. |
| */ |
| webdriver.test.testutil.assertIsPromise = function(obj) { |
| assertTrue('Value is not a promise: ' + goog.typeOf(obj), |
| webdriver.promise.isPromise(obj)); |
| }; |
| |
| |
| /** |
| * Asserts an object is not a promise. |
| * @param {*} obj The object to check. |
| */ |
| webdriver.test.testutil.assertNotPromise = function(obj) { |
| assertFalse(webdriver.promise.isPromise(obj)); |
| }; |
| |
| /** |
| * Wraps a function. The wrapped function will have several utility functions: |
| * <ul> |
| * <li>assertCalled: Asserts that the function was called. |
| * <li>assertNotCalled: Asserts that the function was not called. |
| * </ul> |
| * @param {Function=} opt_fn The function to wrap; defaults to |
| * goog.nullFunction. |
| * @return {!Function} The wrapped function. |
| * @see goog.testing.recordFunction |
| */ |
| webdriver.test.testutil.callbackHelper = function(opt_fn) { |
| var callback = goog.testing.recordFunction(opt_fn); |
| |
| callback.getExpectedCallCountMessage = function(n, opt_prefix, opt_noJoin) { |
| var message = []; |
| if (opt_prefix) message.push(opt_prefix); |
| |
| var calls = callback.getCalls(); |
| message.push( |
| 'Expected to be called ' + n + ' times.', |
| ' was called ' + calls.length + ' times:'); |
| message = goog.array.concat(message, |
| goog.array.map(calls, function(call, i) { |
| var e = call.getError(); |
| if (e) { |
| throw e; |
| } |
| return goog.string.repeat(' ', 4) + 'args(call #' + i + '): ' + |
| goog.json.serialize(call.getArguments()); |
| })); |
| return opt_noJoin ? message : message.join('\n'); |
| }; |
| |
| callback.assertCalled = function(opt_message) { |
| assertEquals(callback.getExpectedCallCountMessage(1, opt_message), |
| 1, callback.getCallCount()); |
| }; |
| |
| callback.assertNotCalled = function(opt_message) { |
| assertEquals(callback.getExpectedCallCountMessage(0, opt_message), |
| 0, callback.getCallCount()); |
| }; |
| |
| return callback; |
| }; |
| |
| |
| /** |
| * Creates a utility for managing a pair of callbacks, capable of asserting only |
| * one of the pair was ever called. |
| * |
| * @param {Function=} opt_callback The callback to manage. |
| * @param {Function=} opt_errback The errback to manage. |
| */ |
| webdriver.test.testutil.callbackPair = function(opt_callback, opt_errback) { |
| var pair = { |
| callback: webdriver.test.testutil.callbackHelper(opt_callback), |
| errback: webdriver.test.testutil.callbackHelper(opt_errback) |
| }; |
| |
| pair.assertEither = function(opt_message) { |
| if (!pair.callback.getCallCount() && |
| !pair.errback.getCallCount()) { |
| var message = ['Neither callback nor errback has been called']; |
| if (opt_message) goog.array.insertAt(message, opt_message); |
| fail(message.join('\n')); |
| } |
| }; |
| |
| pair.assertNeither = function(opt_message) { |
| var message = [opt_message || 'Unexpected callback results:']; |
| if (pair.callback.getCallCount()) { |
| message = goog.array.concat(message, |
| pair.callback.getExpectedCallCountMessage(0, |
| 'Did not expect callback to be called.', true)); |
| } |
| if (pair.errback.getCallCount()) { |
| message = goog.array.concat(message, |
| pair.errback.getExpectedCallCountMessage(0, |
| 'Did not expect errback to be called.', true)); |
| } |
| if (message.length > 1) { |
| fail(message.join('\n -- ')); |
| } |
| }; |
| |
| pair.assertCallback = function(opt_message, opt_count) { |
| assertCalls(pair.callback, 'callback', pair.errback, 'errback', |
| opt_message, opt_count); |
| }; |
| |
| pair.assertErrback = function(opt_message, opt_count) { |
| assertCalls(pair.errback, 'errback', pair.callback, 'callback', |
| opt_message, opt_count); |
| }; |
| |
| pair.reset = function() { |
| pair.callback.reset(); |
| pair.errback.reset(); |
| }; |
| |
| return pair; |
| |
| function assertCalls(expectedFn, expectedName, unexpectedFn, unexpectedName, |
| opt_message, opt_count) { |
| var count = opt_count || 1; |
| var message = [opt_message || 'Unexpected callback results:']; |
| if (expectedFn.getCallCount() != count) { |
| message = goog.array.concat(message, |
| expectedFn.getExpectedCallCountMessage(count, |
| 'Unexpected call pattern for ' + expectedName, true)); |
| } |
| |
| if (unexpectedFn.getCallCount()) { |
| message = goog.array.concat(message, |
| unexpectedFn.getExpectedCallCountMessage(0, |
| 'Expected ' + unexpectedName + ' to never be called', true)); |
| } |
| |
| if (message.length > 1) { |
| fail(message.join('\n -- ')); |
| } |
| } |
| }; |
| |
| |
| webdriver.test.testutil.assertObjectEquals = function(expected, actual) { |
| assertObjectEquals( |
| 'Expected: ' + goog.json.serialize(expected) + '\n' + |
| 'Actual: ' + goog.json.serialize(actual), |
| expected, actual); |
| }; |