You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

249 lines
6.9 KiB

// Copyright 2013 Selenium committers
// Copyright 2013 Software Freedom Conservancy
//
// 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.
/**
* @fileoverview Provides wrappers around the following global functions from
* <a href="http://visionmedia.github.io/mocha/">Mocha's BDD interface</a>:
* <ul>
* <li>after
* <li>afterEach
* <li>before
* <li>beforeEach
* <li>it
* <li>it.only
* <li>it.skip
* <li>xit
* </ul>
*
* <p>The provided wrappers leverage the {@link webdriver.promise.ControlFlow}
* to simplify writing asynchronous tests:
* <pre><code>
* var webdriver = require('selenium-webdriver'),
* portprober = require('selenium-webdriver/net/portprober'),
* remote = require('selenium-webdriver/remote'),
* test = require('selenium-webdriver/testing');
*
* test.describe('Google Search', function() {
* var driver, server;
*
* test.before(function() {
* server = new remote.SeleniumServer(
* 'path/to/selenium-server-standalone.jar',
* {port: portprober.findFreePort()});
* server.start();
*
* driver = new webdriver.Builder().
* withCapabilities({'browserName': 'firefox'}).
* usingServer(server.address()).
* build();
* });
*
* test.after(function() {
* driver.quit();
* server.stop();
* });
*
* test.it('should append query to title', function() {
* driver.get('http://www.google.com');
* driver.findElement(webdriver.By.name('q')).sendKeys('webdriver');
* driver.findElement(webdriver.By.name('btnG')).click();
* driver.wait(function() {
* return driver.getTitle().then(function(title) {
* return 'webdriver - Google Search' === title;
* });
* }, 1000, 'Waiting for title to update');
* });
* });
* </code></pre>
*
* <p>You may conditionally suppress a test function using the exported
* "ignore" function. If the provided predicate returns true, the attached
* test case will be skipped:
* <pre><code>
* test.ignore(maybe()).it('is flaky', function() {
* if (Math.random() < 0.5) throw Error();
* });
*
* function maybe() { return Math.random() < 0.5; }
* </code></pre>
*/
var promise = require('..').promise;
var flow = promise.controlFlow();
/**
* Wraps a function so that all passed arguments are ignored.
* @param {!Function} fn The function to wrap.
* @return {!Function} The wrapped function.
*/
function seal(fn) {
return function() {
fn();
};
}
/**
* Wraps a function on Mocha's BDD interface so it runs inside a
* webdriver.promise.ControlFlow and waits for the flow to complete before
* continuing.
* @param {!Function} globalFn The function to wrap.
* @return {!Function} The new function.
*/
function wrapped(globalFn) {
return function() {
switch (arguments.length) {
case 1:
globalFn(asyncTestFn(arguments[0]));
break;
case 2:
globalFn(arguments[0], asyncTestFn(arguments[1]));
break;
default:
throw Error('Invalid # arguments: ' + arguments.length);
}
};
function asyncTestFn(fn) {
var ret = function(done) {
this.timeout(0);
var timeout = this.timeout;
this.timeout = undefined; // Do not let tests change the timeout.
try {
var testFn = fn.bind(this);
flow.execute(function() {
var done = promise.defer();
promise.asap(testFn(done.reject), done.fulfill, done.reject);
return done.promise;
}).then(seal(done), done);
} finally {
this.timeout = timeout;
}
};
ret.toString = function() {
return fn.toString();
};
return ret;
}
}
/**
* Ignores the test chained to this function if the provided predicate returns
* true.
* @param {function(): boolean} predicateFn A predicate to call to determine
* if the test should be suppressed. This function MUST be synchronous.
* @return {!Object} An object with wrapped versions of {@link #it()} and
* {@link #describe()} that ignore tests as indicated by the predicate.
*/
function ignore(predicateFn) {
var describe = wrap(exports.xdescribe, exports.describe);
describe.only = wrap(exports.xdescribe, exports.describe.only);
var it = wrap(exports.xit, exports.it);
it.only = wrap(exports.xit, exports.it.only);
return {
describe: describe,
it: it
};
function wrap(onSkip, onRun) {
return function(title, fn) {
if (predicateFn()) {
onSkip(title, fn);
} else {
onRun(title, fn);
}
};
}
}
// PUBLIC API
/**
* Registers a new test suite.
* @param {string} name The suite name.
* @param {function()=} fn The suite function, or {@code undefined} to define
* a pending test suite.
*/
exports.describe = global.describe;
/**
* Defines a suppressed test suite.
* @param {string} name The suite name.
* @param {function()=} fn The suite function, or {@code undefined} to define
* a pending test suite.
*/
exports.xdescribe = global.xdescribe;
exports.describe.skip = global.describe.skip;
/**
* Register a function to call after the current suite finishes.
* @param {function()} fn .
*/
exports.after = wrapped(global.after);
/**
* Register a function to call after each test in a suite.
* @param {function()} fn .
*/
exports.afterEach = wrapped(global.afterEach);
/**
* Register a function to call before the current suite starts.
* @param {function()} fn .
*/
exports.before = wrapped(global.before);
/**
* Register a function to call before each test in a suite.
* @param {function()} fn .
*/
exports.beforeEach = wrapped(global.beforeEach);
/**
* Add a test to the current suite.
* @param {string} name The test name.
* @param {function()=} fn The test function, or {@code undefined} to define
* a pending test case.
*/
exports.it = wrapped(global.it);
/**
* An alias for {@link #it()} that flags the test as the only one that should
* be run within the current suite.
* @param {string} name The test name.
* @param {function()=} fn The test function, or {@code undefined} to define
* a pending test case.
*/
exports.iit = exports.it.only = wrapped(global.it.only);
/**
* Adds a test to the current suite while suppressing it so it is not run.
* @param {string} name The test name.
* @param {function()=} fn The test function, or {@code undefined} to define
* a pending test case.
*/
exports.xit = exports.it.skip = wrapped(global.xit);
exports.ignore = ignore;