# testing/index.js

    1// Copyright 2013 Selenium committers2// Copyright 2013 Software Freedom Conservancy3//4// Licensed under the Apache License, Version 2.0 (the "License");5// you may not use this file except in compliance with the License.6//     You may obtain a copy of the License at7//8// http://www.apache.org/licenses/LICENSE-2.09//10// Unless required by applicable law or agreed to in writing, software11// distributed under the License is distributed on an "AS IS" BASIS,12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.13// See the License for the specific language governing permissions and14// limitations under the License.1516/**17 * @fileoverview Provides wrappers around the following global functions from18 * <a href="http://visionmedia.github.io/mocha/">Mocha's BDD interface</a>:19 * <ul>20 *   <li>after21 *   <li>afterEach22 *   <li>before23 *   <li>beforeEach24 *   <li>it25 *   <li>it.only26 *   <li>it.skip27 *   <li>xit28 * </ul>29 *30 * <p>The provided wrappers leverage the {@link webdriver.promise.ControlFlow}31 * to simplify writing asynchronous tests:32 * <pre><code>33 * var By = require('selenium-webdriver').By,34 *     until = require('selenium-webdriver').until,35 *     firefox = require('selenium-webdriver/firefox'),36 *     test = require('selenium-webdriver/testing');37 *38 * test.describe('Google Search', function() {39 *   var driver;40 *41 *   test.before(function() {42 *     driver = new firefox.Driver();43 *   });44 *45 *   test.after(function() {46 *     driver.quit();47 *   });48 *49 *   test.it('should append query to title', function() {50 *     driver.get('http://www.google.com/ncr');51 *     driver.findElement(By.name('q')).sendKeys('webdriver');52 *     driver.findElement(By.name('btnG')).click();53 *     driver.wait(until.titleIs('webdriver - Google Search'), 1000);54 *   });55 * });56 * </code></pre>57 *58 * <p>You may conditionally suppress a test function using the exported59 * "ignore" function. If the provided predicate returns true, the attached60 * test case will be skipped:61 * <pre><code>62 *   test.ignore(maybe()).it('is flaky', function() {63 *     if (Math.random() < 0.5) throw Error();64 *   });65 *66 *   function maybe() { return Math.random() < 0.5; }67 * </code></pre>68 */6970var promise = require('..').promise;71var flow = promise.controlFlow();727374/**75 * Wraps a function so that all passed arguments are ignored.76 * @param {!Function} fn The function to wrap.77 * @return {!Function} The wrapped function.78 */79function seal(fn) {80  return function() {81    fn();82  };83}848586/**87 * Wraps a function on Mocha's BDD interface so it runs inside a88 * webdriver.promise.ControlFlow and waits for the flow to complete before89 * continuing.90 * @param {!Function} globalFn The function to wrap.91 * @return {!Function} The new function.92 */93function wrapped(globalFn) {94  return function() {95    if (arguments.length === 1) {96      return globalFn(asyncTestFn(arguments[0]));97    }98    else if (arguments.length === 2) {99      return globalFn(arguments[0], asyncTestFn(arguments[1]));100    }101    else {102      throw Error('Invalid # arguments: ' + arguments.length);103    }104  };105106  function asyncTestFn(fn) {107    var ret = function(done) {108      function cleanupBeforeCallback() {109        flow.reset();110        return cleanupBeforeCallback.mochaCallback.apply(this, arguments);111      }112      // We set this as an attribute of the callback function to allow us to113      // test this properly.114      cleanupBeforeCallback.mochaCallback = this.runnable().callback;115116      this.runnable().callback = cleanupBeforeCallback;117118      var testFn = fn.bind(this);119      flow.execute(function() {120        var done = promise.defer();121        promise.asap(testFn(done.reject), done.fulfill, done.reject);122        return done.promise;123      }).then(seal(done), done);124    };125126    ret.toString = function() {127      return fn.toString();128    };129130    return ret;131  }132}133134135/**136 * Ignores the test chained to this function if the provided predicate returns137 * true.138 * @param {function(): boolean} predicateFn A predicate to call to determine139 *     if the test should be suppressed. This function MUST be synchronous.140 * @return {!Object} An object with wrapped versions of {@link #it()} and141 *     {@link #describe()} that ignore tests as indicated by the predicate.142 */143function ignore(predicateFn) {144  var describe = wrap(exports.xdescribe, exports.describe);145  describe.only = wrap(exports.xdescribe, exports.describe.only);146147  var it = wrap(exports.xit, exports.it);148  it.only = wrap(exports.xit, exports.it.only);149150  return {151    describe: describe,152    it: it153  };154155  function wrap(onSkip, onRun) {156    return function(title, fn) {157      if (predicateFn()) {158        onSkip(title, fn);159      } else {160        onRun(title, fn);161      }162    };163  }164}165166167// PUBLIC API168169/**170 * Registers a new test suite.171 * @param {string} name The suite name.172 * @param {function()=} fn The suite function, or {@code undefined} to define173 *     a pending test suite.174 */175exports.describe = global.describe;176177/**178 * Defines a suppressed test suite.179 * @param {string} name The suite name.180 * @param {function()=} fn The suite function, or {@code undefined} to define181 *     a pending test suite.182 */183exports.xdescribe = global.xdescribe;184exports.describe.skip = global.describe.skip;185186/**187 * Register a function to call after the current suite finishes.188 * @param {function()} fn .189 */190exports.after = wrapped(global.after);191192/**193 * Register a function to call after each test in a suite.194 * @param {function()} fn .195 */196exports.afterEach = wrapped(global.afterEach);197198/**199 * Register a function to call before the current suite starts.200 * @param {function()} fn .201 */202exports.before = wrapped(global.before);203204/**205 * Register a function to call before each test in a suite.206 * @param {function()} fn .207 */208exports.beforeEach = wrapped(global.beforeEach);209210/**211 * Add a test to the current suite.212 * @param {string} name The test name.213 * @param {function()=} fn The test function, or {@code undefined} to define214 *     a pending test case.215 */216exports.it = wrapped(global.it);217218/**219 * An alias for {@link #it()} that flags the test as the only one that should220 * be run within the current suite.221 * @param {string} name The test name.222 * @param {function()=} fn The test function, or {@code undefined} to define223 *     a pending test case.224 */225exports.iit = exports.it.only = wrapped(global.it.only);226227/**228 * Adds a test to the current suite while suppressing it so it is not run.229 * @param {string} name The test name.230 * @param {function()=} fn The test function, or {@code undefined} to define231 *     a pending test case.232 */233exports.xit = exports.it.skip = wrapped(global.xit);234235exports.ignore = ignore;