# 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 webdriver = require('selenium-webdriver'),34 *     portprober = require('selenium-webdriver/net/portprober'),35 *     remote = require('selenium-webdriver/remote'),36 *     test = require('selenium-webdriver/testing');37 *38 * test.describe('Google Search', function() {39 *   var driver, server;40 *41 *   test.before(function() {42 *     server = new remote.SeleniumServer(43 *         'path/to/selenium-server-standalone.jar',44 *         {port: portprober.findFreePort()});45 *     server.start();46 *47 *     driver = new webdriver.Builder().48 *         withCapabilities({'browserName': 'firefox'}).49 *         usingServer(server.address()).50 *         build();51 *   });52 *53 *   test.after(function() {54 *     driver.quit();55 *     server.stop();56 *   });57 *58 *   test.it('should append query to title', function() {59 *     driver.get('http://www.google.com');60 *     driver.findElement(webdriver.By.name('q')).sendKeys('webdriver');61 *     driver.findElement(webdriver.By.name('btnG')).click();62 *     driver.wait(function() {63 *       return driver.getTitle().then(function(title) {64 *         return 'webdriver - Google Search' === title;65 *       });66 *     }, 1000, 'Waiting for title to update');67 *   });68 * });69 * </code></pre>70 *71 * <p>You may conditionally suppress a test function using the exported72 * "ignore" function. If the provided predicate returns true, the attached73 * test case will be skipped:74 * <pre><code>75 *   test.ignore(maybe()).it('is flaky', function() {76 *     if (Math.random() < 0.5) throw Error();77 *   });78 *79 *   function maybe() { return Math.random() < 0.5; }80 * </code></pre>81 */8283var promise = require('..').promise;84var flow = promise.controlFlow();858687/**88 * Wraps a function so that all passed arguments are ignored.89 * @param {!Function} fn The function to wrap.90 * @return {!Function} The wrapped function.91 */92function seal(fn) {93  return function() {94    fn();95  };96}979899/**100 * Wraps a function on Mocha's BDD interface so it runs inside a101 * webdriver.promise.ControlFlow and waits for the flow to complete before102 * continuing.103 * @param {!Function} globalFn The function to wrap.104 * @return {!Function} The new function.105 */106function wrapped(globalFn) {107  return function() {108    if (arguments.length === 1) {109      return globalFn(asyncTestFn(arguments[0]));110    }111    else if (arguments.length === 2) {112      return globalFn(arguments[0], asyncTestFn(arguments[1]));113    }114    else {115      throw Error('Invalid # arguments: ' + arguments.length);116    }117  };118119  function asyncTestFn(fn) {120    var ret = function(done) {121      function cleanupBeforeCallback() {122        flow.reset();123        return cleanupBeforeCallback.mochaCallback.apply(this, arguments);124      }125      // We set this as an attribute of the callback function to allow us to126      // test this properly.127      cleanupBeforeCallback.mochaCallback = this.runnable().callback;128129      this.runnable().callback = cleanupBeforeCallback;130131      var testFn = fn.bind(this);132      flow.execute(function() {133        var done = promise.defer();134        promise.asap(testFn(done.reject), done.fulfill, done.reject);135        return done.promise;136      }).then(seal(done), done);137    };138139    ret.toString = function() {140      return fn.toString();141    };142143    return ret;144  }145}146147148/**149 * Ignores the test chained to this function if the provided predicate returns150 * true.151 * @param {function(): boolean} predicateFn A predicate to call to determine152 *     if the test should be suppressed. This function MUST be synchronous.153 * @return {!Object} An object with wrapped versions of {@link #it()} and154 *     {@link #describe()} that ignore tests as indicated by the predicate.155 */156function ignore(predicateFn) {157  var describe = wrap(exports.xdescribe, exports.describe);158  describe.only = wrap(exports.xdescribe, exports.describe.only);159160  var it = wrap(exports.xit, exports.it);161  it.only = wrap(exports.xit, exports.it.only);162163  return {164    describe: describe,165    it: it166  };167168  function wrap(onSkip, onRun) {169    return function(title, fn) {170      if (predicateFn()) {171        onSkip(title, fn);172      } else {173        onRun(title, fn);174      }175    };176  }177}178179180// PUBLIC API181182/**183 * Registers a new test suite.184 * @param {string} name The suite name.185 * @param {function()=} fn The suite function, or {@code undefined} to define186 *     a pending test suite.187 */188exports.describe = global.describe;189190/**191 * Defines a suppressed test suite.192 * @param {string} name The suite name.193 * @param {function()=} fn The suite function, or {@code undefined} to define194 *     a pending test suite.195 */196exports.xdescribe = global.xdescribe;197exports.describe.skip = global.describe.skip;198199/**200 * Register a function to call after the current suite finishes.201 * @param {function()} fn .202 */203exports.after = wrapped(global.after);204205/**206 * Register a function to call after each test in a suite.207 * @param {function()} fn .208 */209exports.afterEach = wrapped(global.afterEach);210211/**212 * Register a function to call before the current suite starts.213 * @param {function()} fn .214 */215exports.before = wrapped(global.before);216217/**218 * Register a function to call before each test in a suite.219 * @param {function()} fn .220 */221exports.beforeEach = wrapped(global.beforeEach);222223/**224 * Add a test to the current suite.225 * @param {string} name The test name.226 * @param {function()=} fn The test function, or {@code undefined} to define227 *     a pending test case.228 */229exports.it = wrapped(global.it);230231/**232 * An alias for {@link #it()} that flags the test as the only one that should233 * be run within the current suite.234 * @param {string} name The test name.235 * @param {function()=} fn The test function, or {@code undefined} to define236 *     a pending test case.237 */238exports.iit = exports.it.only = wrapped(global.it.only);239240/**241 * Adds a test to the current suite while suppressing it so it is not run.242 * @param {string} name The test name.243 * @param {function()=} fn The test function, or {@code undefined} to define244 *     a pending test case.245 */246exports.xit = exports.it.skip = wrapped(global.xit);247248exports.ignore = ignore;