# testing/index.js

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