# 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 webdriver.promise.ControlFlow to31 * simplify writing asynchronous tests:32 * <pre><code>33 * var webdriver = require('selenium-webdriver'),34 *     remote = require('selenium-webdriver/remote'),35 *     test = require('selenium-webdriver/testing');36 *37 * test.describe('Google Search', function() {38 *   var driver, server;39 *40 *   test.before(function() {41 *     server = new remote.SeleniumServer({42 *       jar: 'path/to/selenium-server-standalone.jar'43 *     });44 *     server.start();45 *46 *     driver = new webdriver.Builder().47 *         withCapabilities({'browserName': 'firefox'}).48 *         usingServer(server.address()).49 *         build();50 *   });51 *52 *   test.after(function() {53 *     driver.quit();54 *     server.stop();55 *   });56 *57 *   test.it('should append query to title', function() {58 *     driver.get('http://www.google.com');59 *     driver.findElement(webdriver.By.name('q')).sendKeys('webdriver');60 *     driver.findElement(webdriver.By.name('btnG')).click();61 *     driver.wait(function() {62 *       return driver.getTitle().then(function(title) {63 *         return 'webdriver - Google Search' === title;64 *       });65 *     }, 1000, 'Waiting for title to update');66 *   });67 * });68 * </code></pre>69 *70 * <p>You may conditionally suppress a test function using the exported71 * "ignore" function. If the provided predicate returns true, the attached72 * test case will be skipped:73 * <pre><code>74 *   test.ignore(maybe()).it('is flaky', function() {75 *     if (Math.random() < 0.5) throw Error();76 *   });77 *78 *   function maybe() { return Math.random() < 0.5; }79 * </code></pre>80 */8182var flow = require('..').promise.controlFlow();838485/**86 * Wraps a function so that all passed arguments are ignored.87 * @param {!Function} fn The function to wrap.88 * @return {!Function} The wrapped function.89 */90function seal(fn) {91  return function() {92    fn();93  };94}959697/**98 * Wraps a function on Mocha's BDD interface so it runs inside a99 * webdriver.promise.ControlFlow and waits for the flow to complete before100 * continuing.101 * @param {!Function} globalFn The function to wrap.102 * @return {!Function} The new function.103 */104function wrapped(globalFn) {105  return function() {106    switch (arguments.length) {107      case 1:108        globalFn(asyncTestFn(arguments[0]));109        break;110111      case 2:112        globalFn(arguments[0], asyncTestFn(arguments[1]));113        break;114115      default:116        throw Error('Invalid # arguments: ' + arguments.length);117    }118  };119120  function asyncTestFn(fn) {121    return function(done) {122      this.timeout(0);123      var timeout = this.timeout;124      this.timeout = undefined;  // Do not let tests change the timeout.125      try {126        flow.execute(fn.bind(this)).then(seal(done), done);127      } finally {128        this.timeout = timeout;129      }130    };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;