selenium-webdriver 3.6.0 → 4.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/CHANGES.md +181 -0
  2. package/README.md +25 -27
  3. package/chrome.js +113 -127
  4. package/edge.js +42 -117
  5. package/example/chrome_android.js +8 -8
  6. package/example/chrome_mobile_emulation.js +8 -8
  7. package/example/firefox_channels.js +22 -18
  8. package/example/google_search_test.js +39 -26
  9. package/example/logging.js +39 -6
  10. package/firefox.js +769 -0
  11. package/http/index.js +41 -4
  12. package/http/util.js +11 -6
  13. package/ie.js +56 -97
  14. package/index.js +178 -114
  15. package/jasmine.json +11 -0
  16. package/lib/actions.js +68 -63
  17. package/lib/by.js +1 -1
  18. package/lib/capabilities.js +255 -192
  19. package/lib/command.js +26 -50
  20. package/lib/error.js +85 -44
  21. package/lib/http.js +133 -110
  22. package/lib/input.js +1044 -8
  23. package/lib/promise.js +222 -3323
  24. package/lib/proxy.js +134 -39
  25. package/lib/test/bootstrap_jasmine.js +28 -0
  26. package/lib/test/data/actions/click.html +24 -0
  27. package/lib/test/data/actions/drag.html +77 -0
  28. package/lib/test/data/actions/record_click.html +21 -0
  29. package/lib/test/data/chrome/download.html +2 -0
  30. package/lib/test/data/inputs.html +42 -0
  31. package/lib/test/data/selectPage.html +12 -0
  32. package/lib/test/data/simpleTest.html +5 -0
  33. package/lib/test/data/upload_invisible.html +45 -0
  34. package/lib/test/fileserver.js +23 -19
  35. package/lib/test/index.js +30 -212
  36. package/lib/test/resources.js +0 -1
  37. package/lib/webdriver.js +675 -936
  38. package/net/index.js +21 -25
  39. package/net/portprober.js +51 -68
  40. package/package.json +5 -3
  41. package/remote/index.js +5 -19
  42. package/safari.js +41 -128
  43. package/test/actions_test.js +173 -18
  44. package/test/{session_test.js → builder_test.js} +44 -37
  45. package/test/chrome/devtools_test.js +93 -0
  46. package/test/chrome/options_test.js +48 -154
  47. package/test/chrome/service_test.js +7 -7
  48. package/test/cookie_test.js +77 -63
  49. package/test/element_finding_test.js +206 -201
  50. package/test/execute_script_test.js +115 -114
  51. package/test/fingerprint_test.js +16 -15
  52. package/test/firefox_test.js +251 -0
  53. package/test/http/http_test.js +0 -1
  54. package/test/http/util_test.js +2 -2
  55. package/test/io/zip_test.js +6 -7
  56. package/test/lib/error_test.js +34 -7
  57. package/test/lib/http_test.js +8 -128
  58. package/test/lib/input_test.js +1379 -0
  59. package/test/lib/promise_test.js +473 -908
  60. package/test/lib/webdriver_test.js +296 -868
  61. package/test/logging_test.js +37 -43
  62. package/test/page_loading_test.js +71 -81
  63. package/test/proxy_test.js +64 -72
  64. package/test/rect_test.js +14 -25
  65. package/test/remote_test.js +10 -29
  66. package/test/safari_test.js +7 -71
  67. package/test/stale_element_test.js +20 -23
  68. package/test/tag_name_test.js +10 -9
  69. package/test/upload_test.js +27 -35
  70. package/test/window_test.js +72 -85
  71. package/testing/index.js +386 -314
  72. package/.npmignore +0 -2
  73. package/example/async_await_test.js +0 -69
  74. package/example/google_search_generator.js +0 -47
  75. package/example/parallel_flows.js +0 -51
  76. package/firefox/binary.js +0 -347
  77. package/firefox/extension.js +0 -224
  78. package/firefox/index.js +0 -576
  79. package/firefox/profile.js +0 -311
  80. package/lib/events.js +0 -210
  81. package/lib/test/data/firefox/jetpack-sample.xpi +0 -0
  82. package/lib/test/data/firefox/sample.xpi +0 -0
  83. package/lib/test/promise.js +0 -79
  84. package/opera.js +0 -405
  85. package/phantomjs.js +0 -282
  86. package/test/firefox/extension_test.js +0 -120
  87. package/test/firefox/firefox_test.js +0 -244
  88. package/test/firefox/profile_test.js +0 -140
  89. package/test/lib/events_test.js +0 -177
  90. package/test/lib/promise_aplus_test.js +0 -78
  91. package/test/lib/promise_error_test.js +0 -884
  92. package/test/lib/promise_flow_test.js +0 -2288
  93. package/test/lib/promise_generator_test.js +0 -310
  94. package/test/phantomjs/execute_phantomjs_test.js +0 -59
  95. package/test/testing/assert_test.js +0 -373
  96. package/test/testing/index_test.js +0 -224
  97. package/testing/assert.js +0 -378
package/testing/index.js CHANGED
@@ -16,51 +16,8 @@
16
16
  // under the License.
17
17
 
18
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
- * Each of the wrapped functions support generator functions. If the generator
32
- * {@linkplain ../lib/promise.consume yields a promise}, the test will wait
33
- * for that promise to resolve before invoking the next iteration of the
34
- * generator:
35
- *
36
- * test.it('generators', function*() {
37
- * let x = yield Promise.resolve(1);
38
- * assert.equal(x, 1);
39
- * });
40
- *
41
- * The provided wrappers leverage the {@link webdriver.promise.ControlFlow}
42
- * to simplify writing asynchronous tests:
43
- *
44
- * var {Builder, By, Key, until} = require('selenium-webdriver');
45
- * var test = require('selenium-webdriver/testing');
46
- *
47
- * test.describe('Google Search', function() {
48
- * var driver;
49
- *
50
- * test.before(function() {
51
- * driver = new Builder().forBrowser('firefox').build();
52
- * });
53
- *
54
- * test.after(function() {
55
- * driver.quit();
56
- * });
57
- *
58
- * test.it('should append query to title', function() {
59
- * driver.get('http://www.google.com/ncr');
60
- * driver.findElement(By.name('q')).sendKeys('webdriver', Key.RETURN);
61
- * driver.wait(until.titleIs('webdriver - Google Search'), 1000);
62
- * });
63
- * });
19
+ * @fileoverview Provides extensions for
20
+ * [Jasmine](https://jasmine.github.io) and [Mocha](https://mochajs.org).
64
21
  *
65
22
  * You may conditionally suppress a test function using the exported
66
23
  * "ignore" function. If the provided predicate returns true, the attached
@@ -75,352 +32,467 @@
75
32
 
76
33
  'use strict';
77
34
 
78
- const promise = require('..').promise;
79
- const flow = (function() {
80
- const initial = process.env['SELENIUM_PROMISE_MANAGER'];
81
- try {
82
- process.env['SELENIUM_PROMISE_MANAGER'] = '1';
83
- return promise.controlFlow();
84
- } finally {
85
- if (initial === undefined) {
86
- delete process.env['SELENIUM_PROMISE_MANAGER'];
87
- } else {
88
- process.env['SELENIUM_PROMISE_MANAGER'] = initial;
89
- }
90
- }
91
- })();
35
+ const {isatty} = require('tty');
36
+
37
+ const chrome = require('../chrome');
38
+ const edge = require('../edge');
39
+ const firefox = require('../firefox');
40
+ const ie = require('../ie');
41
+ const remote = require('../remote');
42
+ const safari = require('../safari');
43
+ const {Browser} = require('../lib/capabilities');
44
+ const {Builder} = require('../index');
92
45
 
93
46
 
94
47
  /**
95
- * Wraps a function so that all passed arguments are ignored.
96
- * @param {!Function} fn The function to wrap.
97
- * @return {!Function} The wrapped function.
48
+ * Describes a browser targetted by a {@linkplain suite test suite}.
49
+ * @record
98
50
  */
99
- function seal(fn) {
100
- return function() {
101
- fn();
102
- };
103
- }
104
-
51
+ function TargetBrowser() {}
105
52
 
106
53
  /**
107
- * Wraps a function on Mocha's BDD interface so it runs inside a
108
- * webdriver.promise.ControlFlow and waits for the flow to complete before
109
- * continuing.
110
- * @param {!Function} globalFn The function to wrap.
111
- * @return {!Function} The new function.
54
+ * The {@linkplain Browser name} of the targetted browser.
55
+ * @type {string}
112
56
  */
113
- function wrapped(globalFn) {
114
- return function() {
115
- if (arguments.length === 1) {
116
- return globalFn(wrapArgument(arguments[0]));
57
+ TargetBrowser.prototype.name;
117
58
 
118
- } else if (arguments.length === 2) {
119
- return globalFn(arguments[0], wrapArgument(arguments[1]));
59
+ /**
60
+ * The specific version of the targetted browser, if any.
61
+ * @type {(string|undefined)}
62
+ */
63
+ TargetBrowser.prototype.version;
120
64
 
121
- } else {
122
- throw Error('Invalid # arguments: ' + arguments.length);
123
- }
124
- };
125
- }
65
+ /**
66
+ * The specific {@linkplain ../lib/capabilities.Platform platform} for the
67
+ * targetted browser, if any.
68
+ * @type {(string|undefined)}.
69
+ */
70
+ TargetBrowser.prototype.platform;
126
71
 
127
72
 
128
- function wrapArgument(value) {
129
- if (typeof value === 'function') {
130
- return makeAsyncTestFn(value);
131
- }
132
- return value;
73
+ /** @suppress {checkTypes} */
74
+ function color(c, s) {
75
+ return isatty(process.stdout) ? `\u001b[${c}m${s}\u001b[0m` : s;
133
76
  }
77
+ function green(s) { return color(32, s); }
78
+ function cyan(s) { return color(36, s); }
79
+ function info(msg) { console.info(`${green('[INFO]')} ${msg}`); }
80
+ function warn(msg) { console.warn(`${cyan('[WARNING]')} ${msg}`); }
134
81
 
135
82
 
136
83
  /**
137
- * Make a wrapper to invoke caller's test function, fn. Run the test function
138
- * within a ControlFlow.
139
- *
140
- * Should preserve the semantics of Mocha's Runnable.prototype.run (See
141
- * https://github.com/mochajs/mocha/blob/master/lib/runnable.js#L192)
84
+ * Extracts the browsers for a test suite to target from the `SELENIUM_BROWSER`
85
+ * environment variable.
142
86
  *
143
- * @param {!Function} fn
144
- * @return {!Function}
87
+ * @return {!Array<!TargetBrowser>} the browsers to target.
145
88
  */
146
- function makeAsyncTestFn(fn) {
147
- const isAsync = fn.length > 0;
148
- const isGenerator = promise.isGenerator(fn);
149
- if (isAsync && isGenerator) {
150
- throw TypeError(
151
- 'generator-based tests must not take a callback; for async testing,'
152
- + ' return a promise (or yield on a promise)');
89
+ function getBrowsersToTestFromEnv() {
90
+ let browsers = process.env['SELENIUM_BROWSER'];
91
+ if (!browsers) {
92
+ return [];
153
93
  }
154
-
155
- var ret = /** @type {function(this: mocha.Context)}*/ (function(done) {
156
- const runTest = (resolve, reject) => {
157
- try {
158
- if (isAsync) {
159
- fn.call(this, err => err ? reject(err) : resolve());
160
- } else if (isGenerator) {
161
- resolve(promise.consume(fn, this));
162
- } else {
163
- resolve(fn.call(this));
164
- }
165
- } catch (ex) {
166
- reject(ex);
167
- }
168
- };
169
-
170
- if (!promise.USE_PROMISE_MANAGER) {
171
- new Promise(runTest).then(seal(done), done);
172
- return;
94
+ return browsers.split(',').map(spec => {
95
+ const parts = spec.split(/:/, 3);
96
+ let name = parts[0];
97
+ if (name === 'ie') {
98
+ name = Browser.IE;
99
+ } else if (name === 'edge') {
100
+ name = Browser.EDGE;
173
101
  }
174
-
175
- var runnable = this.runnable();
176
- var mochaCallback = runnable.callback;
177
- runnable.callback = function() {
178
- flow.reset();
179
- return mochaCallback.apply(this, arguments);
180
- };
181
-
182
- flow.execute(function controlFlowExecute() {
183
- return new promise.Promise(function(fulfill, reject) {
184
- return runTest(fulfill, reject);
185
- }, flow);
186
- }, runnable.fullTitle()).then(seal(done), done);
102
+ let version = parts[1];
103
+ let platform = parts[2];
104
+ return {name, version, platform};
187
105
  });
188
-
189
- ret.toString = function() {
190
- return fn.toString();
191
- };
192
-
193
- return ret;
194
106
  }
195
107
 
196
108
 
197
109
  /**
198
- * Ignores the test chained to this function if the provided predicate returns
199
- * true.
200
- * @param {function(): boolean} predicateFn A predicate to call to determine
201
- * if the test should be suppressed. This function MUST be synchronous.
202
- * @return {!Object} An object with wrapped versions of {@link #it()} and
203
- * {@link #describe()} that ignore tests as indicated by the predicate.
110
+ * @return {!Array<!TargetBrowser>} the browsers available for testing on this
111
+ * system.
204
112
  */
205
- function ignore(predicateFn) {
206
- var describe = wrap(exports.xdescribe, exports.describe);
207
- describe.only = wrap(exports.xdescribe, exports.describe.only);
208
-
209
- var it = wrap(exports.xit, exports.it);
210
- it.only = wrap(exports.xit, exports.it.only);
211
-
212
- return {
213
- describe: describe,
214
- it: it
215
- };
113
+ function getAvailableBrowsers() {
114
+ info(`Searching for WebDriver executables installed on the current system...`);
115
+
116
+ let targets = [
117
+ [chrome.locateSynchronously, Browser.CHROME],
118
+ [edge.locateSynchronously, Browser.EDGE],
119
+ [firefox.locateSynchronously, Browser.FIREFOX],
120
+ [ie.locateSynchronously, Browser.IE],
121
+ [safari.locateSynchronously, Browser.SAFARI],
122
+ ];
123
+
124
+ let availableBrowsers = [];
125
+ for (let pair of targets) {
126
+ const fn = pair[0];
127
+ const name = pair[1];
128
+ if (fn()) {
129
+ info(`... located ${name}`);
130
+ availableBrowsers.push({name});
131
+ }
132
+ }
216
133
 
217
- function wrap(onSkip, onRun) {
218
- return function(title, fn) {
219
- if (predicateFn()) {
220
- onSkip(title, fn);
221
- } else {
222
- onRun(title, fn);
223
- }
224
- };
134
+ if (availableBrowsers.length === 0) {
135
+ warn(`Unable to locate any WebDriver executables for testing`);
225
136
  }
137
+
138
+ return availableBrowsers;
226
139
  }
227
140
 
141
+ let wasInit;
142
+ let targetBrowsers;
143
+ let seleniumJar;
144
+ let seleniumUrl;
145
+ let seleniumServer;
228
146
 
229
147
  /**
230
- * @param {string} name
231
- * @return {!Function}
232
- * @throws {TypeError}
148
+ * Initializes this module by determining which browsers a
149
+ * {@linkplain ./index.suite test suite} should run against. The default
150
+ * behavior is to run tests against every browser with a WebDriver executables
151
+ * (chromedriver, firefoxdriver, etc.) are installed on the system by `PATH`.
152
+ *
153
+ * Specific browsers can be selected at runtime by setting the
154
+ * `SELENIUM_BROWSER` environment variable. This environment variable has the
155
+ * same semantics as with the WebDriver {@link ../index.Builder Builder},
156
+ * except you may use a comma-delimited list to run against multiple browsers:
157
+ *
158
+ * SELENIUM_BROWSER=chrome,firefox mocha --recursive tests/
159
+ *
160
+ * The `SELENIUM_REMOTE_URL` environment variable may be set to configure tests
161
+ * to run againt an externally managed (usually remote) Selenium server. When
162
+ * set, the WebDriver builder provided by each
163
+ * {@linkplain TestEnvironment#builder TestEnvironment} will automatically be
164
+ * configured to use this server instead of starting a browser drively locally.
165
+ *
166
+ * The `SELENIUM_SERVER_JAR` environment variable may be set to the path of a
167
+ * standalone Selenium server on the local machine that should be used for
168
+ * WebDriver sessions. When set, the WebDriver builder provided by each
169
+ * {@linkplain TestEnvironment} will automatically be configured to use the
170
+ * started server instead of using a browser driver directly. It should only be
171
+ * necessary to set the `SELENIUM_SERVER_JAR` when testing locally against
172
+ * browsers not natively supported by the WebDriver
173
+ * {@link ../index.Builder Builder}.
174
+ *
175
+ * When either of the `SELENIUM_REMOTE_URL` or `SELENIUM_SERVER_JAR` environment
176
+ * variables are set, the `SELENIUM_BROWSER` variable must also be set.
177
+ *
178
+ * @param {boolean=} force whether to force this module to re-initialize and
179
+ * scan `process.env` again to determine which browsers to run tests
180
+ * against.
233
181
  */
234
- function getMochaGlobal(name) {
235
- let fn = global[name];
236
- let type = typeof fn;
237
- if (type !== 'function') {
238
- throw TypeError(
239
- `Expected global.${name} to be a function, but is ${type}. `
240
- + 'This can happen if you try using this module when running '
241
- + 'with node directly instead of using the mocha executable');
182
+ function init(force = false) {
183
+ if (wasInit && !force) {
184
+ return;
242
185
  }
243
- return fn;
244
- }
186
+ wasInit = true;
245
187
 
188
+ // If force re-init, kill the current server if there is one.
189
+ if (seleniumServer) {
190
+ seleniumServer.kill();
191
+ seleniumServer = null;
192
+ }
246
193
 
247
- const WRAPPED = {
248
- after: null,
249
- afterEach: null,
250
- before: null,
251
- beforeEach: null,
252
- it: null,
253
- itOnly: null,
254
- xit: null
255
- };
194
+ seleniumJar = process.env['SELENIUM_SERVER_JAR'];
195
+ seleniumUrl = process.env['SELENIUM_REMOTE_URL'];
196
+ if (seleniumJar) {
197
+ info(`Using Selenium server jar: ${seleniumJar}`);
198
+ }
256
199
 
200
+ if (seleniumUrl) {
201
+ info(`Using Selenium remote end: ${seleniumUrl}`);
202
+ }
203
+
204
+ if (seleniumJar && seleniumUrl) {
205
+ throw Error(
206
+ 'Ambiguous test configuration: both SELENIUM_REMOTE_URL'
207
+ + ' && SELENIUM_SERVER_JAR environment variables are set');
208
+ }
257
209
 
258
- function wrapIt() {
259
- if (!WRAPPED.it) {
260
- let it = getMochaGlobal('it');
261
- WRAPPED.it = wrapped(it);
262
- WRAPPED.itOnly = wrapped(it.only);
210
+ const envBrowsers = getBrowsersToTestFromEnv();
211
+ if ((seleniumJar || seleniumUrl) && envBrowsers.length === 0) {
212
+ throw Error(
213
+ 'Ambiguous test configuration: when either the SELENIUM_REMOTE_URL or'
214
+ + ' SELENIUM_SERVER_JAR environment variable is set, the'
215
+ + ' SELENIUM_BROWSER variable must also be set.');
263
216
  }
217
+
218
+ targetBrowsers =
219
+ envBrowsers.length > 0 ? envBrowsers : getAvailableBrowsers();
220
+ info(`Running tests against [${targetBrowsers.map(b => b.name).join(', ')}]`);
221
+
222
+ after(function() {
223
+ if (seleniumServer) {
224
+ return seleniumServer.kill();
225
+ }
226
+ });
264
227
  }
265
228
 
266
229
 
267
230
 
268
- // PUBLIC API
231
+ const TARGET_MAP = /** !WeakMap<!Environment, !TargetBrowser> */new WeakMap;
232
+ const URL_MAP =
233
+ /** !WeakMap<!Environment, ?(string|remote.SeleniumServer)> */new WeakMap;
269
234
 
270
235
 
271
236
  /**
272
- * @return {!promise.ControlFlow} the control flow instance used by this module
273
- * to coordinate test actions.
237
+ * Defines the environment a {@linkplain suite test suite} is running against.
238
+ * @final
274
239
  */
275
- exports.controlFlow = function(){
276
- return flow;
277
- };
240
+ class Environment {
241
+
242
+ /**
243
+ * @param {!TargetBrowser} browser the browser targetted in this environment.
244
+ * @param {?(string|remote.SeleniumServer)=} url remote URL of an existing
245
+ * Selenium server to test against.
246
+ */
247
+ constructor(browser, url = undefined) {
248
+ browser =
249
+ /** @type {!TargetBrowser} */(Object.seal(Object.assign({}, browser)));
250
+
251
+ TARGET_MAP.set(this, browser);
252
+ URL_MAP.set(this, url || null);
253
+ }
278
254
 
255
+ /** @return {!TargetBrowser} the target browser for this test environment. */
256
+ get browser() {
257
+ return TARGET_MAP.get(this);
258
+ }
279
259
 
280
- /**
281
- * Registers a new test suite.
282
- * @param {string} name The suite name.
283
- * @param {function()=} opt_fn The suite function, or `undefined` to define
284
- * a pending test suite.
285
- */
286
- exports.describe = function(name, opt_fn) {
287
- let fn = getMochaGlobal('describe');
288
- return opt_fn ? fn(name, opt_fn) : fn(name);
289
- };
260
+ /**
261
+ * Returns a predicate function that will suppress tests in this environment
262
+ * if the {@linkplain #browser current browser} is in the list of
263
+ * `browsersToIgnore`.
264
+ *
265
+ * @param {...(string|!Browser)} browsersToIgnore the browsers that should
266
+ * be ignored.
267
+ * @return {function(): boolean} a new predicate function.
268
+ */
269
+ browsers(...browsersToIgnore) {
270
+ return () => browsersToIgnore.indexOf(this.browser.name) != -1;
271
+ }
290
272
 
273
+ /**
274
+ * @return {!Builder} a new WebDriver builder configured to target this
275
+ * environment's {@linkplain #browser browser}.
276
+ */
277
+ builder() {
278
+ const browser = this.browser;
279
+ const urlOrServer = URL_MAP.get(this);
280
+
281
+ const builder = new Builder();
282
+ builder.disableEnvironmentOverrides();
283
+
284
+ const realBuild = builder.build;
285
+ builder.build = function() {
286
+ builder.forBrowser(browser.name, browser.version, browser.platform);
287
+ if (typeof urlOrServer === 'string') {
288
+ builder.usingServer(urlOrServer);
289
+ } else if (urlOrServer) {
290
+ builder.usingServer(urlOrServer.address());
291
+ }
292
+ return realBuild.call(builder);
293
+ };
291
294
 
292
- /**
293
- * An alias for {@link #describe()} that marks the suite as exclusive,
294
- * suppressing all other test suites.
295
- * @param {string} name The suite name.
296
- * @param {function()=} opt_fn The suite function, or `undefined` to define
297
- * a pending test suite.
298
- */
299
- exports.describe.only = function(name, opt_fn) {
300
- let desc = getMochaGlobal('describe');
301
- return opt_fn ? desc.only(name, opt_fn) : desc.only(name);
302
- };
295
+ return builder;
296
+ }
297
+ }
303
298
 
304
299
 
305
300
  /**
306
- * Defines a suppressed test suite.
307
- * @param {string} name The suite name.
308
- * @param {function()=} opt_fn The suite function, or `undefined` to define
309
- * a pending test suite.
301
+ * Configuration options for a {@linkplain ./index.suite test suite}.
302
+ * @record
310
303
  */
311
- exports.describe.skip = function(name, opt_fn) {
312
- let fn = getMochaGlobal('describe');
313
- return opt_fn ? fn.skip(name, opt_fn) : fn.skip(name);
314
- };
315
-
304
+ function SuiteOptions() {}
316
305
 
317
306
  /**
318
- * Defines a suppressed test suite.
319
- * @param {string} name The suite name.
320
- * @param {function()=} opt_fn The suite function, or `undefined` to define
321
- * a pending test suite.
307
+ * The browsers to run the test suite against.
308
+ * @type {!Array<!(Browser|TargetBrowser)>}
322
309
  */
323
- exports.xdescribe = function(name, opt_fn) {
324
- let fn = getMochaGlobal('xdescribe');
325
- return opt_fn ? fn(name, opt_fn) : fn(name);
326
- };
310
+ SuiteOptions.prototype.browsers;
327
311
 
328
312
 
329
- /**
330
- * Register a function to call after the current suite finishes.
331
- * @param {function()} fn .
332
- */
333
- exports.after = function(fn) {
334
- if (!WRAPPED.after) {
335
- WRAPPED.after = wrapped(getMochaGlobal('after'));
336
- }
337
- WRAPPED.after(fn);
338
- };
313
+ let inSuite = false;
339
314
 
340
315
 
341
316
  /**
342
- * Register a function to call after each test in a suite.
343
- * @param {function()} fn .
317
+ * Defines a test suite by calling the provided function once for each of the
318
+ * target browsers. If a suite is not limited to a specific set of browsers in
319
+ * the provided {@linkplain ./index.SuiteOptions suite options}, the suite will
320
+ * be configured to run against each of the {@linkplain ./index.init runtime
321
+ * target browsers}.
322
+ *
323
+ * Sample usage:
324
+ *
325
+ * const {By, Key, until} = require('selenium-webdriver');
326
+ * const {suite} = require('selenium-webdriver/testing');
327
+ *
328
+ * suite(function(env) {
329
+ * describe('Google Search', function() {
330
+ * let driver;
331
+ *
332
+ * before(async function() {
333
+ * driver = await env.builder().build();
334
+ * });
335
+ *
336
+ * after(() => driver.quit());
337
+ *
338
+ * it('demo', async function() {
339
+ * await driver.get('http://www.google.com/ncr');
340
+ *
341
+ * let q = await driver.findElement(By.name('q'));
342
+ * await q.sendKeys('webdriver', Key.RETURN);
343
+ * await driver.wait(
344
+ * until.titleIs('webdriver - Google Search'), 1000);
345
+ * });
346
+ * });
347
+ * });
348
+ *
349
+ * By default, this example suite will run against every WebDriver-enabled
350
+ * browser on the current system. Alternatively, the `SELENIUM_BROWSER`
351
+ * environment variable may be used to run against a specific browser:
352
+ *
353
+ * SELENIUM_BROWSER=firefox mocha -t 120000 example_test.js
354
+ *
355
+ * @param {function(!Environment)} fn the function to call to build the test
356
+ * suite.
357
+ * @param {SuiteOptions=} options configuration options.
344
358
  */
345
- exports.afterEach = function(fn) {
346
- if (!WRAPPED.afterEach) {
347
- WRAPPED.afterEach = wrapped(getMochaGlobal('afterEach'));
359
+ function suite(fn, options = undefined) {
360
+ if (inSuite) {
361
+ throw Error('Calls to suite() may not be nested');
348
362
  }
349
- WRAPPED.afterEach(fn);
350
- };
363
+ try {
364
+ init();
365
+ inSuite = true;
366
+
367
+ const suiteBrowsers = new Map;
368
+ if (options && options.browsers) {
369
+ for (let browser of options.browsers) {
370
+ if (typeof browser === 'string') {
371
+ suiteBrowsers.set(browser, {name: browser});
372
+ } else {
373
+ suiteBrowsers.set(browser.name, browser);
374
+ }
375
+ }
376
+ }
377
+
378
+ for (let browser of targetBrowsers) {
379
+ if (suiteBrowsers.size > 0 && !suiteBrowsers.has(browser.name)) {
380
+ continue;
381
+ }
351
382
 
383
+ describe(`[${browser.name}]`, function() {
384
+ if (!seleniumUrl && seleniumJar && !seleniumServer) {
385
+ seleniumServer = new remote.SeleniumServer(seleniumJar);
352
386
 
353
- /**
354
- * Register a function to call before the current suite starts.
355
- * @param {function()} fn .
356
- */
357
- exports.before = function(fn) {
358
- if (!WRAPPED.before) {
359
- WRAPPED.before = wrapped(getMochaGlobal('before'));
360
- }
361
- WRAPPED.before(fn);
362
- };
387
+ const startTimeout = 65 * 1000;
388
+ function startSelenium() {
389
+ if (typeof this.timeout === 'function') {
390
+ this.timeout(startTimeout); // For mocha.
391
+ }
363
392
 
364
- /**
365
- * Register a function to call before each test in a suite.
366
- * @param {function()} fn .
367
- */
368
- exports.beforeEach = function(fn) {
369
- if (!WRAPPED.beforeEach) {
370
- WRAPPED.beforeEach = wrapped(getMochaGlobal('beforeEach'));
371
- }
372
- WRAPPED.beforeEach(fn);
373
- };
393
+ info(`Starting selenium server ${seleniumJar}`);
394
+ return seleniumServer.start(60 * 1000);
395
+ }
374
396
 
375
- /**
376
- * Add a test to the current suite.
377
- * @param {string} name The test name.
378
- * @param {function()=} opt_fn The test function, or `undefined` to define
379
- * a pending test case.
380
- */
381
- exports.it = function(name, opt_fn) {
382
- wrapIt();
383
- if (opt_fn) {
384
- WRAPPED.it(name, opt_fn);
385
- } else {
386
- WRAPPED.it(name);
397
+ const /** !Function */beforeHook = global.beforeAll || global.before;
398
+ beforeHook(startSelenium, startTimeout);
399
+ }
400
+
401
+ fn(new Environment(browser, seleniumUrl || seleniumServer));
402
+ });
403
+ }
404
+ } finally {
405
+ inSuite = false;
387
406
  }
388
- };
407
+ }
408
+
409
+
389
410
 
390
411
  /**
391
- * An alias for {@link #it()} that flags the test as the only one that should
392
- * be run within the current suite.
393
- * @param {string} name The test name.
394
- * @param {function()=} opt_fn The test function, or `undefined` to define
395
- * a pending test case.
412
+ * Returns an object with wrappers for the standard mocha/jasmine test
413
+ * functions: `describe` and `it`, which will redirect to `xdescribe` and `xit`,
414
+ * respectively, if provided predicate function returns false.
415
+ *
416
+ * Sample usage:
417
+ *
418
+ * const {Browser} = require('selenium-webdriver');
419
+ * const {suite, ignore} = require('selenium-webdriver/testing');
420
+ *
421
+ * suite(function(env) {
422
+ *
423
+ * // Skip tests the current environment targets Chrome.
424
+ * ignore(env.browsers(Browser.CHROME)).
425
+ * describe('something', async function() {
426
+ * let driver = await env.builder().build();
427
+ * // etc.
428
+ * });
429
+ * });
430
+ *
431
+ * @param {function(): boolean} predicateFn A predicate to call to determine
432
+ * if the test should be suppressed. This function MUST be synchronous.
433
+ * @return {{describe: !Function, it: !Function}} an object with wrapped
434
+ * versions of the `describe` and `it` wtest functions.
396
435
  */
397
- exports.it.only = function(name, opt_fn) {
398
- wrapIt();
399
- if (opt_fn) {
400
- WRAPPED.itOnly(name, opt_fn);
401
- } else {
402
- WRAPPED.itOnly(name);
436
+ function ignore(predicateFn) {
437
+ const isJasmine = global.jasmine && typeof global.jasmine === 'object';
438
+
439
+ const hooks = {
440
+ describe: getTestHook('describe'),
441
+ xdescribe: getTestHook('xdescribe'),
442
+ it: getTestHook('it'),
443
+ xit: getTestHook('xit')
444
+ };
445
+ hooks.fdescribe = isJasmine ? getTestHook('fdescribe') : hooks.describe.only;
446
+ hooks.fit = isJasmine ? getTestHook('fit') : hooks.it.only;
447
+
448
+ let describe = wrap(hooks.xdescribe, hooks.describe);
449
+ let fdescribe = wrap(hooks.xdescribe, hooks.fdescribe);
450
+ describe.only = fdescribe;
451
+
452
+ let it = wrap(hooks.xit, hooks.it);
453
+ let fit = wrap(hooks.xit, hooks.fit);
454
+ it.only = fit;
455
+
456
+ return {describe, it};
457
+
458
+ function wrap(onSkip, onRun) {
459
+ return function(...args) {
460
+ if (predicateFn()) {
461
+ onSkip(...args);
462
+ } else {
463
+ onRun(...args);
464
+ }
465
+ };
403
466
  }
404
- };
467
+ }
405
468
 
406
469
 
407
470
  /**
408
- * Adds a test to the current suite while suppressing it so it is not run.
409
- * @param {string} name The test name.
410
- * @param {function()=} opt_fn The test function, or `undefined` to define
411
- * a pending test case.
471
+ * @param {string} name
472
+ * @return {!Function}
473
+ * @throws {TypeError}
412
474
  */
413
- exports.xit = function(name, opt_fn) {
414
- if (!WRAPPED.xit) {
415
- WRAPPED.xit = wrapped(getMochaGlobal('xit'));
416
- }
417
- if (opt_fn) {
418
- WRAPPED.xit(name, opt_fn);
419
- } else {
420
- WRAPPED.xit(name);
475
+ function getTestHook(name) {
476
+ let fn = global[name];
477
+ let type = typeof fn;
478
+ if (type !== 'function') {
479
+ throw TypeError(
480
+ `Expected global.${name} to be a function, but is ${type}.`
481
+ + ' This can happen if you try using this module when running with'
482
+ + ' node directly instead of using jasmine or mocha');
421
483
  }
422
- };
484
+ return fn;
485
+ }
486
+
423
487
 
488
+ // PUBLIC API
424
489
 
425
- exports.it.skip = exports.xit;
426
- exports.ignore = ignore;
490
+
491
+ module.exports = {
492
+ Environment,
493
+ TargetBrowser,
494
+ SuiteOptions,
495
+ init,
496
+ ignore,
497
+ suite
498
+ };