| 1 | // Copyright 2013 Selenium committers | 
| 2 | // Copyright 2013 Software Freedom Conservancy | 
| 3 | // | 
| 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 at | 
| 7 | // | 
| 8 | // http://www.apache.org/licenses/LICENSE-2.0 | 
| 9 | // | 
| 10 | // Unless required by applicable law or agreed to in writing, software | 
| 11 | // 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 and | 
| 14 | // limitations under the License. | 
| 15 |  | 
| 16 | /** | 
| 17 |  * @fileoverview Provides wrappers around the following global functions from | 
| 18 |  * [Mocha's BDD interface](https://github.com/mochajs/mocha): | 
| 19 |  * | 
| 20 |  * - after | 
| 21 |  * - afterEach | 
| 22 |  * - before | 
| 23 |  * - beforeEach | 
| 24 |  * - it | 
| 25 |  * - it.only | 
| 26 |  * - it.skip | 
| 27 |  * - xit | 
| 28 |  * | 
| 29 |  * The provided wrappers leverage the {@link webdriver.promise.ControlFlow} | 
| 30 |  * to simplify writing asynchronous tests: | 
| 31 |  * | 
| 32 |  *     var By = require('selenium-webdriver').By, | 
| 33 |  *         until = require('selenium-webdriver').until, | 
| 34 |  *         firefox = require('selenium-webdriver/firefox'), | 
| 35 |  *         test = require('selenium-webdriver/testing'); | 
| 36 |  * | 
| 37 |  *     test.describe('Google Search', function() { | 
| 38 |  *       var driver; | 
| 39 |  * | 
| 40 |  *       test.before(function() { | 
| 41 |  *         driver = new firefox.Driver(); | 
| 42 |  *       }); | 
| 43 |  * | 
| 44 |  *       test.after(function() { | 
| 45 |  *         driver.quit(); | 
| 46 |  *       }); | 
| 47 |  * | 
| 48 |  *       test.it('should append query to title', function() { | 
| 49 |  *         driver.get('http://www.google.com/ncr'); | 
| 50 |  *         driver.findElement(By.name('q')).sendKeys('webdriver'); | 
| 51 |  *         driver.findElement(By.name('btnG')).click(); | 
| 52 |  *         driver.wait(until.titleIs('webdriver - Google Search'), 1000); | 
| 53 |  *       }); | 
| 54 |  *     }); | 
| 55 |  * | 
| 56 |  * You may conditionally suppress a test function using the exported | 
| 57 |  * "ignore" function. If the provided predicate returns true, the attached | 
| 58 |  * test case will be skipped: | 
| 59 |  * | 
| 60 |  *     test.ignore(maybe()).it('is flaky', function() { | 
| 61 |  *       if (Math.random() < 0.5) throw Error(); | 
| 62 |  *     }); | 
| 63 |  * | 
| 64 |  *     function maybe() { return Math.random() < 0.5; } | 
| 65 |  */ | 
| 66 |  | 
| 67 | var promise = require('..').promise; | 
| 68 | var flow = promise.controlFlow(); | 
| 69 |  | 
| 70 |  | 
| 71 | /** | 
| 72 |  * Wraps a function so that all passed arguments are ignored. | 
| 73 |  * @param {!Function} fn The function to wrap. | 
| 74 |  * @return {!Function} The wrapped function. | 
| 75 |  */ | 
| 76 | function seal(fn) { | 
| 77 |   return function() { | 
| 78 |     fn(); | 
| 79 |   }; | 
| 80 | } | 
| 81 |  | 
| 82 |  | 
| 83 | /** | 
| 84 |  * Wraps a function on Mocha's BDD interface so it runs inside a | 
| 85 |  * webdriver.promise.ControlFlow and waits for the flow to complete before | 
| 86 |  * continuing. | 
| 87 |  * @param {!Function} globalFn The function to wrap. | 
| 88 |  * @return {!Function} The new function. | 
| 89 |  */ | 
| 90 | function wrapped(globalFn) { | 
| 91 |   return function() { | 
| 92 |     if (arguments.length === 1) { | 
| 93 |       return globalFn(makeAsyncTestFn(arguments[0])); | 
| 94 |     } | 
| 95 |     else if (arguments.length === 2) { | 
| 96 |       return globalFn(arguments[0], makeAsyncTestFn(arguments[1])); | 
| 97 |     } | 
| 98 |     else { | 
| 99 |       throw Error('Invalid # arguments: ' + arguments.length); | 
| 100 |     } | 
| 101 |   }; | 
| 102 | } | 
| 103 |  | 
| 104 | /** | 
| 105 |  * Make a wrapper to invoke caller's test function, fn.  Run the test function | 
| 106 |  * within a ControlFlow. | 
| 107 |  * | 
| 108 |  * Should preserve the semantics of Mocha's Runnable.prototype.run (See | 
| 109 |  * https://github.com/mochajs/mocha/blob/master/lib/runnable.js#L192) | 
| 110 |  * | 
| 111 |  * @param {Function} fn | 
| 112 |  * @return {Function} | 
| 113 |  */ | 
| 114 | function makeAsyncTestFn(fn) { | 
| 115 |   var async = fn.length > 0; // if test function expects a callback, its "async" | 
| 116 |  | 
| 117 |   var ret = function(done) { | 
| 118 |     var runnable = this.runnable(); | 
| 119 |     var mochaCallback = runnable.callback; | 
| 120 |     runnable.callback = function() { | 
| 121 |       flow.reset(); | 
| 122 |       return mochaCallback.apply(this, arguments); | 
| 123 |     }; | 
| 124 |  | 
| 125 |     var testFn = fn.bind(this); | 
| 126 |     flow.execute(function controlFlowExecute() { | 
| 127 |       return new promise.Promise(function(fulfill, reject) { | 
| 128 |         if (async) { | 
| 129 |           // If testFn is async (it expects a done callback), resolve the promise of this | 
| 130 |           // test whenever that callback says to.  Any promises returned from testFn are | 
| 131 |           // ignored. | 
| 132 |           testFn(function testFnDoneCallback(err) { | 
| 133 |             if (err) { | 
| 134 |               reject(err); | 
| 135 |             } else { | 
| 136 |               fulfill(); | 
| 137 |             } | 
| 138 |           }); | 
| 139 |         } else { | 
| 140 |           // Without a callback, testFn can return a promise, or it will | 
| 141 |           // be assumed to have completed synchronously | 
| 142 |           fulfill(testFn()); | 
| 143 |         } | 
| 144 |       }, flow); | 
| 145 |     }, runnable.fullTitle()).then(seal(done), done); | 
| 146 |   }; | 
| 147 |  | 
| 148 |   ret.toString = function() { | 
| 149 |     return fn.toString(); | 
| 150 |   }; | 
| 151 |  | 
| 152 |   return ret; | 
| 153 | } | 
| 154 |  | 
| 155 |  | 
| 156 | /** | 
| 157 |  * Ignores the test chained to this function if the provided predicate returns | 
| 158 |  * true. | 
| 159 |  * @param {function(): boolean} predicateFn A predicate to call to determine | 
| 160 |  *     if the test should be suppressed. This function MUST be synchronous. | 
| 161 |  * @return {!Object} An object with wrapped versions of {@link #it()} and | 
| 162 |  *     {@link #describe()} that ignore tests as indicated by the predicate. | 
| 163 |  */ | 
| 164 | function ignore(predicateFn) { | 
| 165 |   var describe = wrap(exports.xdescribe, exports.describe); | 
| 166 |   describe.only = wrap(exports.xdescribe, exports.describe.only); | 
| 167 |  | 
| 168 |   var it = wrap(exports.xit, exports.it); | 
| 169 |   it.only = wrap(exports.xit, exports.it.only); | 
| 170 |  | 
| 171 |   return { | 
| 172 |     describe: describe, | 
| 173 |     it: it | 
| 174 |   }; | 
| 175 |  | 
| 176 |   function wrap(onSkip, onRun) { | 
| 177 |     return function(title, fn) { | 
| 178 |       if (predicateFn()) { | 
| 179 |         onSkip(title, fn); | 
| 180 |       } else { | 
| 181 |         onRun(title, fn); | 
| 182 |       } | 
| 183 |     }; | 
| 184 |   } | 
| 185 | } | 
| 186 |  | 
| 187 |  | 
| 188 | // PUBLIC API | 
| 189 |  | 
| 190 | /** | 
| 191 |  * Registers a new test suite. | 
| 192 |  * @param {string} name The suite name. | 
| 193 |  * @param {function()=} fn The suite function, or {@code undefined} to define | 
| 194 |  *     a pending test suite. | 
| 195 |  */ | 
| 196 | exports.describe = global.describe; | 
| 197 |  | 
| 198 | /** | 
| 199 |  * Defines a suppressed test suite. | 
| 200 |  * @param {string} name The suite name. | 
| 201 |  * @param {function()=} fn The suite function, or {@code undefined} to define | 
| 202 |  *     a pending test suite. | 
| 203 |  */ | 
| 204 | exports.xdescribe = global.xdescribe; | 
| 205 | exports.describe.skip = global.describe.skip; | 
| 206 |  | 
| 207 | /** | 
| 208 |  * Register a function to call after the current suite finishes. | 
| 209 |  * @param {function()} fn . | 
| 210 |  */ | 
| 211 | exports.after = wrapped(global.after); | 
| 212 |  | 
| 213 | /** | 
| 214 |  * Register a function to call after each test in a suite. | 
| 215 |  * @param {function()} fn . | 
| 216 |  */ | 
| 217 | exports.afterEach = wrapped(global.afterEach); | 
| 218 |  | 
| 219 | /** | 
| 220 |  * Register a function to call before the current suite starts. | 
| 221 |  * @param {function()} fn . | 
| 222 |  */ | 
| 223 | exports.before = wrapped(global.before); | 
| 224 |  | 
| 225 | /** | 
| 226 |  * Register a function to call before each test in a suite. | 
| 227 |  * @param {function()} fn . | 
| 228 |  */ | 
| 229 | exports.beforeEach = wrapped(global.beforeEach); | 
| 230 |  | 
| 231 | /** | 
| 232 |  * Add a test to the current suite. | 
| 233 |  * @param {string} name The test name. | 
| 234 |  * @param {function()=} fn The test function, or {@code undefined} to define | 
| 235 |  *     a pending test case. | 
| 236 |  */ | 
| 237 | exports.it = wrapped(global.it); | 
| 238 |  | 
| 239 | /** | 
| 240 |  * An alias for {@link #it()} that flags the test as the only one that should | 
| 241 |  * be run within the current suite. | 
| 242 |  * @param {string} name The test name. | 
| 243 |  * @param {function()=} fn The test function, or {@code undefined} to define | 
| 244 |  *     a pending test case. | 
| 245 |  */ | 
| 246 | exports.iit = exports.it.only = wrapped(global.it.only); | 
| 247 |  | 
| 248 | /** | 
| 249 |  * Adds a test to the current suite while suppressing it so it is not run. | 
| 250 |  * @param {string} name The test name. | 
| 251 |  * @param {function()=} fn The test function, or {@code undefined} to define | 
| 252 |  *     a pending test case. | 
| 253 |  */ | 
| 254 | exports.xit = exports.it.skip = wrapped(global.xit); | 
| 255 |  | 
| 256 | exports.ignore = ignore; |