2016-11-03 01:33:53 +01:00
|
|
|
// Licensed to the Software Freedom Conservancy (SFC) under one
|
|
|
|
// or more contributor license agreements. See the NOTICE file
|
|
|
|
// distributed with this work for additional information
|
|
|
|
// regarding copyright ownership. The SFC licenses this file
|
|
|
|
// to you 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
|
|
|
|
* [Mocha's BDD interface](https://github.com/mochajs/mocha):
|
|
|
|
*
|
|
|
|
* - after
|
|
|
|
* - afterEach
|
|
|
|
* - before
|
|
|
|
* - beforeEach
|
|
|
|
* - it
|
|
|
|
* - it.only
|
|
|
|
* - it.skip
|
|
|
|
* - xit
|
|
|
|
*
|
2016-11-16 01:59:39 +01:00
|
|
|
* Each of the wrapped functions support generator functions. If the generator
|
|
|
|
* {@linkplain ../lib/promise.consume yields a promise}, the test will wait
|
|
|
|
* for that promise to resolve before invoking the next iteration of the
|
|
|
|
* generator:
|
|
|
|
*
|
|
|
|
* test.it('generators', function*() {
|
|
|
|
* let x = yield Promise.resolve(1);
|
|
|
|
* assert.equal(x, 1);
|
|
|
|
* });
|
|
|
|
*
|
2016-11-03 01:33:53 +01:00
|
|
|
* The provided wrappers leverage the {@link webdriver.promise.ControlFlow}
|
|
|
|
* to simplify writing asynchronous tests:
|
|
|
|
*
|
|
|
|
* var By = require('selenium-webdriver').By,
|
|
|
|
* until = require('selenium-webdriver').until,
|
|
|
|
* firefox = require('selenium-webdriver/firefox'),
|
|
|
|
* test = require('selenium-webdriver/testing');
|
|
|
|
*
|
|
|
|
* test.describe('Google Search', function() {
|
|
|
|
* var driver;
|
|
|
|
*
|
|
|
|
* test.before(function() {
|
|
|
|
* driver = new firefox.Driver();
|
|
|
|
* });
|
|
|
|
*
|
|
|
|
* test.after(function() {
|
|
|
|
* driver.quit();
|
|
|
|
* });
|
|
|
|
*
|
|
|
|
* test.it('should append query to title', function() {
|
|
|
|
* driver.get('http://www.google.com/ncr');
|
|
|
|
* driver.findElement(By.name('q')).sendKeys('webdriver');
|
|
|
|
* driver.findElement(By.name('btnG')).click();
|
|
|
|
* driver.wait(until.titleIs('webdriver - Google Search'), 1000);
|
|
|
|
* });
|
|
|
|
* });
|
|
|
|
*
|
|
|
|
* 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:
|
|
|
|
*
|
|
|
|
* test.ignore(maybe()).it('is flaky', function() {
|
|
|
|
* if (Math.random() < 0.5) throw Error();
|
|
|
|
* });
|
|
|
|
*
|
|
|
|
* function maybe() { return Math.random() < 0.5; }
|
|
|
|
*/
|
|
|
|
|
2016-11-16 01:59:39 +01:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
const promise = require('..').promise;
|
|
|
|
const flow = (function() {
|
|
|
|
const initial = process.env['SELENIUM_PROMISE_MANAGER'];
|
|
|
|
try {
|
|
|
|
process.env['SELENIUM_PROMISE_MANAGER'] = '1';
|
|
|
|
return promise.controlFlow();
|
|
|
|
} finally {
|
|
|
|
if (initial === undefined) {
|
|
|
|
delete process.env['SELENIUM_PROMISE_MANAGER'];
|
|
|
|
} else {
|
|
|
|
process.env['SELENIUM_PROMISE_MANAGER'] = initial;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})();
|
2016-11-03 01:33:53 +01:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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() {
|
|
|
|
if (arguments.length === 1) {
|
|
|
|
return globalFn(wrapArgument(arguments[0]));
|
|
|
|
|
|
|
|
} else if (arguments.length === 2) {
|
|
|
|
return globalFn(arguments[0], wrapArgument(arguments[1]));
|
|
|
|
|
|
|
|
} else {
|
|
|
|
throw Error('Invalid # arguments: ' + arguments.length);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function wrapArgument(value) {
|
|
|
|
if (typeof value === 'function') {
|
|
|
|
return makeAsyncTestFn(value);
|
|
|
|
}
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Make a wrapper to invoke caller's test function, fn. Run the test function
|
|
|
|
* within a ControlFlow.
|
|
|
|
*
|
|
|
|
* Should preserve the semantics of Mocha's Runnable.prototype.run (See
|
|
|
|
* https://github.com/mochajs/mocha/blob/master/lib/runnable.js#L192)
|
|
|
|
*
|
2016-11-16 01:59:39 +01:00
|
|
|
* @param {!Function} fn
|
|
|
|
* @return {!Function}
|
2016-11-03 01:33:53 +01:00
|
|
|
*/
|
|
|
|
function makeAsyncTestFn(fn) {
|
2016-11-16 01:59:39 +01:00
|
|
|
const isAsync = fn.length > 0;
|
|
|
|
const isGenerator = promise.isGenerator(fn);
|
|
|
|
if (isAsync && isGenerator) {
|
|
|
|
throw TypeError(
|
|
|
|
'generator-based tests must not take a callback; for async testing,'
|
|
|
|
+ ' return a promise (or yield on a promise)');
|
|
|
|
}
|
2016-11-03 01:33:53 +01:00
|
|
|
|
|
|
|
var ret = /** @type {function(this: mocha.Context)}*/ (function(done) {
|
2016-11-16 01:59:39 +01:00
|
|
|
const runTest = (resolve, reject) => {
|
|
|
|
try {
|
|
|
|
if (isAsync) {
|
|
|
|
fn.call(this, err => err ? reject(err) : resolve());
|
|
|
|
} else if (isGenerator) {
|
|
|
|
resolve(promise.consume(fn, this));
|
|
|
|
} else {
|
|
|
|
resolve(fn.call(this));
|
|
|
|
}
|
|
|
|
} catch (ex) {
|
|
|
|
reject(ex);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
if (!promise.USE_PROMISE_MANAGER) {
|
|
|
|
new Promise(runTest).then(seal(done), done);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-11-03 01:33:53 +01:00
|
|
|
var runnable = this.runnable();
|
|
|
|
var mochaCallback = runnable.callback;
|
|
|
|
runnable.callback = function() {
|
|
|
|
flow.reset();
|
|
|
|
return mochaCallback.apply(this, arguments);
|
|
|
|
};
|
|
|
|
|
|
|
|
flow.execute(function controlFlowExecute() {
|
|
|
|
return new promise.Promise(function(fulfill, reject) {
|
2016-11-16 01:59:39 +01:00
|
|
|
return runTest(fulfill, reject);
|
2016-11-03 01:33:53 +01:00
|
|
|
}, flow);
|
|
|
|
}, runnable.fullTitle()).then(seal(done), done);
|
|
|
|
});
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-11-16 01:59:39 +01:00
|
|
|
/**
|
|
|
|
* @param {string} name
|
|
|
|
* @return {!Function}
|
|
|
|
* @throws {TypeError}
|
|
|
|
*/
|
|
|
|
function getMochaGlobal(name) {
|
|
|
|
let fn = global[name];
|
|
|
|
let type = typeof fn;
|
|
|
|
if (type !== 'function') {
|
|
|
|
throw TypeError(
|
|
|
|
`Expected global.${name} to be a function, but is ${type}. `
|
|
|
|
+ 'This can happen if you try using this module when running '
|
|
|
|
+ 'with node directly instead of using the mocha executable');
|
|
|
|
}
|
|
|
|
return fn;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const WRAPPED = {
|
|
|
|
after: null,
|
|
|
|
afterEach: null,
|
|
|
|
before: null,
|
|
|
|
beforeEach: null,
|
|
|
|
it: null,
|
|
|
|
itOnly: null,
|
|
|
|
xit: null
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
function wrapIt() {
|
|
|
|
if (!WRAPPED.it) {
|
|
|
|
let it = getMochaGlobal('it');
|
|
|
|
WRAPPED.it = wrapped(it);
|
|
|
|
WRAPPED.itOnly = wrapped(it.only);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-11-03 01:33:53 +01:00
|
|
|
// PUBLIC API
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return {!promise.ControlFlow} the control flow instance used by this module
|
|
|
|
* to coordinate test actions.
|
|
|
|
*/
|
|
|
|
exports.controlFlow = function(){
|
|
|
|
return flow;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Registers a new test suite.
|
|
|
|
* @param {string} name The suite name.
|
2016-11-16 01:59:39 +01:00
|
|
|
* @param {function()=} opt_fn The suite function, or `undefined` to define
|
2016-11-03 01:33:53 +01:00
|
|
|
* a pending test suite.
|
|
|
|
*/
|
2016-11-16 01:59:39 +01:00
|
|
|
exports.describe = function(name, opt_fn) {
|
|
|
|
let fn = getMochaGlobal('describe');
|
|
|
|
return opt_fn ? fn(name, opt_fn) : fn(name);
|
|
|
|
};
|
|
|
|
|
2016-11-03 01:33:53 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Defines a suppressed test suite.
|
|
|
|
* @param {string} name The suite name.
|
2016-11-16 01:59:39 +01:00
|
|
|
* @param {function()=} opt_fn The suite function, or `undefined` to define
|
2016-11-03 01:33:53 +01:00
|
|
|
* a pending test suite.
|
|
|
|
*/
|
2016-11-16 01:59:39 +01:00
|
|
|
exports.describe.skip = function(name, opt_fn) {
|
|
|
|
let fn = getMochaGlobal('describe');
|
|
|
|
return opt_fn ? fn.skip(name, opt_fn) : fn.skip(name);
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Defines a suppressed test suite.
|
|
|
|
* @param {string} name The suite name.
|
|
|
|
* @param {function()=} opt_fn The suite function, or `undefined` to define
|
|
|
|
* a pending test suite.
|
|
|
|
*/
|
|
|
|
exports.xdescribe = function(name, opt_fn) {
|
|
|
|
let fn = getMochaGlobal('xdescribe');
|
|
|
|
return opt_fn ? fn(name, opt_fn) : fn(name);
|
|
|
|
};
|
|
|
|
|
2016-11-03 01:33:53 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Register a function to call after the current suite finishes.
|
|
|
|
* @param {function()} fn .
|
|
|
|
*/
|
2016-11-16 01:59:39 +01:00
|
|
|
exports.after = function(fn) {
|
|
|
|
if (!WRAPPED.after) {
|
|
|
|
WRAPPED.after = wrapped(getMochaGlobal('after'));
|
|
|
|
}
|
|
|
|
WRAPPED.after(fn);
|
|
|
|
};
|
|
|
|
|
2016-11-03 01:33:53 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Register a function to call after each test in a suite.
|
|
|
|
* @param {function()} fn .
|
|
|
|
*/
|
2016-11-16 01:59:39 +01:00
|
|
|
exports.afterEach = function(fn) {
|
|
|
|
if (!WRAPPED.afterEach) {
|
|
|
|
WRAPPED.afterEach = wrapped(getMochaGlobal('afterEach'));
|
|
|
|
}
|
|
|
|
WRAPPED.afterEach(fn);
|
|
|
|
};
|
|
|
|
|
2016-11-03 01:33:53 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Register a function to call before the current suite starts.
|
|
|
|
* @param {function()} fn .
|
|
|
|
*/
|
2016-11-16 01:59:39 +01:00
|
|
|
exports.before = function(fn) {
|
|
|
|
if (!WRAPPED.before) {
|
|
|
|
WRAPPED.before = wrapped(getMochaGlobal('before'));
|
|
|
|
}
|
|
|
|
WRAPPED.before(fn);
|
|
|
|
};
|
2016-11-03 01:33:53 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Register a function to call before each test in a suite.
|
|
|
|
* @param {function()} fn .
|
|
|
|
*/
|
2016-11-16 01:59:39 +01:00
|
|
|
exports.beforeEach = function(fn) {
|
|
|
|
if (!WRAPPED.beforeEach) {
|
|
|
|
WRAPPED.beforeEach = wrapped(getMochaGlobal('beforeEach'));
|
|
|
|
}
|
|
|
|
WRAPPED.beforeEach(fn);
|
|
|
|
};
|
2016-11-03 01:33:53 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Add a test to the current suite.
|
|
|
|
* @param {string} name The test name.
|
2016-11-16 01:59:39 +01:00
|
|
|
* @param {function()=} opt_fn The test function, or `undefined` to define
|
2016-11-03 01:33:53 +01:00
|
|
|
* a pending test case.
|
|
|
|
*/
|
2016-11-16 01:59:39 +01:00
|
|
|
exports.it = function(name, opt_fn) {
|
|
|
|
wrapIt();
|
|
|
|
if (opt_fn) {
|
|
|
|
WRAPPED.it(name, opt_fn);
|
|
|
|
} else {
|
|
|
|
WRAPPED.it(name);
|
|
|
|
}
|
|
|
|
};
|
2016-11-03 01:33:53 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
2016-11-16 01:59:39 +01:00
|
|
|
* @param {function()=} opt_fn The test function, or `undefined` to define
|
2016-11-03 01:33:53 +01:00
|
|
|
* a pending test case.
|
|
|
|
*/
|
2016-11-16 01:59:39 +01:00
|
|
|
exports.it.only = function(name, opt_fn) {
|
|
|
|
wrapIt();
|
|
|
|
if (opt_fn) {
|
|
|
|
WRAPPED.itOnly(name, opt_fn);
|
|
|
|
} else {
|
|
|
|
WRAPPED.itOnly(name);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-11-03 01:33:53 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds a test to the current suite while suppressing it so it is not run.
|
|
|
|
* @param {string} name The test name.
|
2016-11-16 01:59:39 +01:00
|
|
|
* @param {function()=} opt_fn The test function, or `undefined` to define
|
2016-11-03 01:33:53 +01:00
|
|
|
* a pending test case.
|
|
|
|
*/
|
2016-11-16 01:59:39 +01:00
|
|
|
exports.xit = function(name, opt_fn) {
|
|
|
|
if (!WRAPPED.xit) {
|
|
|
|
WRAPPED.xit = wrapped(getMochaGlobal('xit'));
|
|
|
|
}
|
|
|
|
if (opt_fn) {
|
|
|
|
WRAPPED.xit(name, opt_fn);
|
|
|
|
} else {
|
|
|
|
WRAPPED.xit(name);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-11-03 01:33:53 +01:00
|
|
|
|
2016-11-16 01:59:39 +01:00
|
|
|
exports.it.skip = exports.xit;
|
2016-11-03 01:33:53 +01:00
|
|
|
exports.ignore = ignore;
|