testing/index.js

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 * <a href="http://visionmedia.github.io/mocha/">Mocha's BDD interface</a>:
19 * <ul>
20 * <li>after
21 * <li>afterEach
22 * <li>before
23 * <li>beforeEach
24 * <li>it
25 * <li>it.only
26 * <li>it.skip
27 * <li>xit
28 * </ul>
29 *
30 * <p>The provided wrappers leverage the webdriver.promise.ControlFlow to
31 * 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 exported
71 * "ignore" function. If the provided predicate returns true, the attached
72 * 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 */
81
82var flow = require('..').promise.controlFlow();
83
84
85/**
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}
95
96
97/**
98 * Wraps a function on Mocha's BDD interface so it runs inside a
99 * webdriver.promise.ControlFlow and waits for the flow to complete before
100 * 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;
110
111 case 2:
112 globalFn(arguments[0], asyncTestFn(arguments[1]));
113 break;
114
115 default:
116 throw Error('Invalid # arguments: ' + arguments.length);
117 }
118 };
119
120 function asyncTestFn(fn) {
121 return function(done) {
122 this.timeout(0);
123 flow.execute(fn).then(seal(done), done);
124 };
125 }
126}
127
128
129/**
130 * Ignores the test chained to this function if the provided predicate returns
131 * true.
132 * @param {function(): boolean} predicateFn A predicate to call to determine
133 * if the test should be suppressed. This function MUST be synchronous.
134 * @return {!Object} An object with wrapped versions of {@link #it()} and
135 * {@link #describe()} that ignore tests as indicated by the predicate.
136 */
137function ignore(predicateFn) {
138 var describe = wrap(exports.xdescribe, exports.describe);
139 describe.only = wrap(exports.xdescribe, exports.describe.only);
140
141 var it = wrap(exports.xit, exports.it);
142 it.only = wrap(exports.xit, exports.it.only);
143
144 return {
145 describe: describe,
146 it: it
147 };
148
149 function wrap(onSkip, onRun) {
150 return function(title, fn) {
151 if (predicateFn()) {
152 onSkip(title, fn);
153 } else {
154 onRun(title, fn);
155 }
156 };
157 }
158}
159
160
161// PUBLIC API
162
163/**
164 * Registers a new test suite.
165 * @param {string} name The suite name.
166 * @param {function()=} fn The suite function, or {@code undefined} to define
167 * a pending test suite.
168 */
169exports.describe = global.describe;
170
171/**
172 * Defines a suppressed test suite.
173 * @param {string} name The suite name.
174 * @param {function()=} fn The suite function, or {@code undefined} to define
175 * a pending test suite.
176 */
177exports.xdescribe = global.xdescribe;
178exports.describe.skip = global.describe.skip;
179
180/**
181 * Register a function to call after the current suite finishes.
182 * @param {function()} fn .
183 */
184exports.after = wrapped(global.after);
185
186/**
187 * Register a function to call after each test in a suite.
188 * @param {function()} fn .
189 */
190exports.afterEach = wrapped(global.afterEach);
191
192/**
193 * Register a function to call before the current suite starts.
194 * @param {function()} fn .
195 */
196exports.before = wrapped(global.before);
197
198/**
199 * Register a function to call before each test in a suite.
200 * @param {function()} fn .
201 */
202exports.beforeEach = wrapped(global.beforeEach);
203
204/**
205 * Add a test to the current suite.
206 * @param {string} name The test name.
207 * @param {function()=} fn The test function, or {@code undefined} to define
208 * a pending test case.
209 */
210exports.it = wrapped(global.it);
211
212/**
213 * An alias for {@link #it()} that flags the test as the only one that should
214 * be run within the current suite.
215 * @param {string} name The test name.
216 * @param {function()=} fn The test function, or {@code undefined} to define
217 * a pending test case.
218 */
219exports.iit = exports.it.only = wrapped(global.it.only);
220
221/**
222 * Adds a test to the current suite while suppressing it so it is not run.
223 * @param {string} name The test name.
224 * @param {function()=} fn The test function, or {@code undefined} to define
225 * a pending test case.
226 */
227exports.xit = exports.it.skip = wrapped(global.xit);
228
229exports.ignore = ignore;