builder.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
18var base = require('./_base'),
19 executors = require('./executors');
20
21// Use base.require to avoid circular references between index and this module.
22var Browser = base.require('webdriver.Browser'),
23 Capabilities = base.require('webdriver.Capabilities'),
24 Capability = base.require('webdriver.Capability'),
25 WebDriver = base.require('webdriver.WebDriver'),
26 promise = base.require('webdriver.promise');
27
28
29
30var seleniumServer;
31
32/**
33 * Starts an instance of the Selenium server if not yet running.
34 * @param {string} jar Path to the server jar to use.
35 * @return {!webdriver.promise.Promise<string>} A promise for the server's
36 * addrss once started.
37 */
38function startSeleniumServer(jar) {
39 if (!seleniumServer) {
40 // Requiring 'chrome' above would create a cycle:
41 // index -> builder -> chrome -> index
42 var remote = require('./remote');
43 seleniumServer = new remote.SeleniumServer(jar);
44 }
45 return seleniumServer.start();
46}
47
48
49/**
50 * Creates new {@link webdriver.WebDriver WebDriver} instances. The environment
51 * variables listed below may be used to override a builder's configuration,
52 * allowing quick runtime changes.
53 *
54 * - {@code SELENIUM_BROWSER}: defines the target browser in the form
55 * {@code browser[:version][:platform]}.
56 *
57 * - {@code SELENIUM_REMOTE_URL}: defines the remote URL for all builder
58 * instances. This environment variable should be set to a fully qualified
59 * URL for a WebDriver server (e.g. http://localhost:4444/wd/hub). This
60 * option always takes precedence over {@code SELENIUM_SERVER_JAR}.
61 *
62 * - {@code SELENIUM_SERVER_JAR}: defines the path to the
63 * <a href="http://selenium-release.storage.googleapis.com/index.html">
64 * standalone Selenium server</a> jar to use. The server will be started the
65 * first time a WebDriver instance and be killed when the process exits.
66 *
67 * Suppose you had mytest.js that created WebDriver with
68 *
69 * var driver = new webdriver.Builder()
70 * .forBrowser('chrome')
71 * .build();
72 *
73 * This test could be made to use Firefox on the local machine by running with
74 * `SELENIUM_BROWSER=firefox node mytest.js`. Rather than change the code to
75 * target Google Chrome on a remote machine, you can simply set the
76 * `SELENIUM_BROWSER` and `SELENIUM_REMOTE_URL` environment variables:
77 *
78 * SELENIUM_BROWSER=chrome:36:LINUX \
79 * SELENIUM_REMOTE_URL=http://www.example.com:4444/wd/hub \
80 * node mytest.js
81 *
82 * You could also use a local copy of the standalone Selenium server:
83 *
84 * SELENIUM_BROWSER=chrome:36:LINUX \
85 * SELENIUM_SERVER_JAR=/path/to/selenium-server-standalone.jar \
86 * node mytest.js
87 *
88 * @constructor
89 */
90var Builder = function() {
91
92 /** @private {webdriver.promise.ControlFlow} */
93 this.flow_ = null;
94
95 /** @private {string} */
96 this.url_ = '';
97
98 /** @private {?string} */
99 this.proxy_ = null;
100
101 /** @private {!webdriver.Capabilities} */
102 this.capabilities_ = new Capabilities();
103
104 /** @private {chrome.Options} */
105 this.chromeOptions_ = null;
106
107 /** @private {firefox.Options} */
108 this.firefoxOptions_ = null;
109
110 /** @private {opera.Options} */
111 this.operaOptions_ = null;
112
113 /** @private {ie.Options} */
114 this.ieOptions_ = null;
115
116 /** @private {safari.Options} */
117 this.safariOptions_ = null;
118
119 /** @private {boolean} */
120 this.ignoreEnv_ = false;
121};
122
123
124/**
125 * Configures this builder to ignore any environment variable overrides and to
126 * only use the configuration specified through this instance's API.
127 *
128 * @return {!Builder} A self reference.
129 */
130Builder.prototype.disableEnvironmentOverrides = function() {
131 this.ignoreEnv_ = true;
132 return this;
133};
134
135
136/**
137 * Sets the URL of a remote WebDriver server to use. Once a remote URL has been
138 * specified, the builder direct all new clients to that server. If this method
139 * is never called, the Builder will attempt to create all clients locally.
140 *
141 * As an alternative to this method, you may also set the `SELENIUM_REMOTE_URL`
142 * environment variable.
143 *
144 * @param {string} url The URL of a remote server to use.
145 * @return {!Builder} A self reference.
146 */
147Builder.prototype.usingServer = function(url) {
148 this.url_ = url;
149 return this;
150};
151
152
153/**
154 * @return {string} The URL of the WebDriver server this instance is configured
155 * to use.
156 */
157Builder.prototype.getServerUrl = function() {
158 return this.url_;
159};
160
161
162/**
163 * Sets the URL of the proxy to use for the WebDriver's HTTP connections.
164 * If this method is never called, the Builder will create a connection without
165 * a proxy.
166 *
167 * @param {string} proxy The URL of a proxy to use.
168 * @return {!Builder} A self reference.
169 */
170Builder.prototype.usingWebDriverProxy = function(proxy) {
171 this.proxy_ = proxy;
172 return this;
173};
174
175
176/**
177 * @return {string} The URL of the proxy server to use for the WebDriver's HTTP
178 * connections.
179 */
180Builder.prototype.getWebDriverProxy = function() {
181 return this.proxy_;
182};
183
184
185/**
186 * Sets the desired capabilities when requesting a new session. This will
187 * overwrite any previously set capabilities.
188 * @param {!(Object|webdriver.Capabilities)} capabilities The desired
189 * capabilities for a new session.
190 * @return {!Builder} A self reference.
191 */
192Builder.prototype.withCapabilities = function(capabilities) {
193 this.capabilities_ = new Capabilities(capabilities);
194 return this;
195};
196
197
198/**
199 * Returns the base set of capabilities this instance is currently configured
200 * to use.
201 * @return {!webdriver.Capabilities} The current capabilities for this builder.
202 */
203Builder.prototype.getCapabilities = function() {
204 return this.capabilities_;
205};
206
207
208/**
209 * Configures the target browser for clients created by this instance.
210 * Any calls to {@link #withCapabilities} after this function will
211 * overwrite these settings.
212 *
213 * You may also define the target browser using the {@code SELENIUM_BROWSER}
214 * environment variable. If set, this environment variable should be of the
215 * form `browser[:[version][:platform]]`.
216 *
217 * @param {(string|webdriver.Browser)} name The name of the target browser;
218 * common defaults are available on the {@link webdriver.Browser} enum.
219 * @param {string=} opt_version A desired version; may be omitted if any
220 * version should be used.
221 * @param {string=} opt_platform The desired platform; may be omitted if any
222 * version may be used.
223 * @return {!Builder} A self reference.
224 */
225Builder.prototype.forBrowser = function(name, opt_version, opt_platform) {
226 this.capabilities_.set(Capability.BROWSER_NAME, name);
227 this.capabilities_.set(Capability.VERSION, opt_version || null);
228 this.capabilities_.set(Capability.PLATFORM, opt_platform || null);
229 return this;
230};
231
232
233/**
234 * Sets the proxy configuration to use for WebDriver clients created by this
235 * builder. Any calls to {@link #withCapabilities} after this function will
236 * overwrite these settings.
237 * @param {!webdriver.ProxyConfig} config The configuration to use.
238 * @return {!Builder} A self reference.
239 */
240Builder.prototype.setProxy = function(config) {
241 this.capabilities_.setProxy(config);
242 return this;
243};
244
245
246/**
247 * Sets the logging preferences for the created session. Preferences may be
248 * changed by repeated calls, or by calling {@link #withCapabilities}.
249 * @param {!(webdriver.logging.Preferences|Object.<string, string>)} prefs The
250 * desired logging preferences.
251 * @return {!Builder} A self reference.
252 */
253Builder.prototype.setLoggingPrefs = function(prefs) {
254 this.capabilities_.setLoggingPrefs(prefs);
255 return this;
256};
257
258
259/**
260 * Sets whether native events should be used.
261 * @param {boolean} enabled Whether to enable native events.
262 * @return {!Builder} A self reference.
263 */
264Builder.prototype.setEnableNativeEvents = function(enabled) {
265 this.capabilities_.setEnableNativeEvents(enabled);
266 return this;
267};
268
269
270/**
271 * Sets how elements should be scrolled into view for interaction.
272 * @param {number} behavior The desired scroll behavior: either 0 to align with
273 * the top of the viewport or 1 to align with the bottom.
274 * @return {!Builder} A self reference.
275 */
276Builder.prototype.setScrollBehavior = function(behavior) {
277 this.capabilities_.setScrollBehavior(behavior);
278 return this;
279};
280
281
282/**
283 * Sets the default action to take with an unexpected alert before returning
284 * an error.
285 * @param {string} beahvior The desired behavior; should be "accept", "dismiss",
286 * or "ignore". Defaults to "dismiss".
287 * @return {!Builder} A self reference.
288 */
289Builder.prototype.setAlertBehavior = function(behavior) {
290 this.capabilities_.setAlertBehavior(behavior);
291 return this;
292};
293
294
295/**
296 * Sets Chrome specific {@linkplain selenium-webdriver/chrome.Options options}
297 * for drivers created by this builder. Any logging or proxy settings defined
298 * on the given options will take precedence over those set through
299 * {@link #setLoggingPrefs} and {@link #setProxy}, respectively.
300 *
301 * @param {!chrome.Options} options The ChromeDriver options to use.
302 * @return {!Builder} A self reference.
303 */
304Builder.prototype.setChromeOptions = function(options) {
305 this.chromeOptions_ = options;
306 return this;
307};
308
309
310/**
311 * Sets Firefox specific {@linkplain selenium-webdriver/firefox.Options options}
312 * for drivers created by this builder. Any logging or proxy settings defined
313 * on the given options will take precedence over those set through
314 * {@link #setLoggingPrefs} and {@link #setProxy}, respectively.
315 *
316 * @param {!firefox.Options} options The FirefoxDriver options to use.
317 * @return {!Builder} A self reference.
318 */
319Builder.prototype.setFirefoxOptions = function(options) {
320 this.firefoxOptions_ = options;
321 return this;
322};
323
324
325/**
326 * Sets Opera specific {@linkplain selenium-webdriver/opera.Options options} for
327 * drivers created by this builder. Any logging or proxy settings defined on the
328 * given options will take precedence over those set through
329 * {@link #setLoggingPrefs} and {@link #setProxy}, respectively.
330 *
331 * @param {!opera.Options} options The OperaDriver options to use.
332 * @return {!Builder} A self reference.
333 */
334Builder.prototype.setOperaOptions = function(options) {
335 this.operaOptions_ = options;
336 return this;
337};
338
339
340/**
341 * Sets Internet Explorer specific
342 * {@linkplain selenium-webdriver/ie.Options options} for drivers created by
343 * this builder. Any proxy settings defined on the given options will take
344 * precedence over those set through {@link #setProxy}.
345 *
346 * @param {!ie.Options} options The IEDriver options to use.
347 * @return {!Builder} A self reference.
348 */
349Builder.prototype.setIeOptions = function(options) {
350 this.ieOptions_ = options;
351 return this;
352};
353
354
355/**
356 * Sets Safari specific {@linkplain selenium-webdriver/safari.Options options}
357 * for drivers created by this builder. Any logging settings defined on the
358 * given options will take precedence over those set through
359 * {@link #setLoggingPrefs}.
360 *
361 * @param {!safari.Options} options The Safari options to use.
362 * @return {!Builder} A self reference.
363 */
364Builder.prototype.setSafariOptions = function(options) {
365 this.safariOptions_ = options;
366 return this;
367};
368
369
370/**
371 * Sets the control flow that created drivers should execute actions in. If
372 * the flow is never set, or is set to {@code null}, it will use the active
373 * flow at the time {@link #build()} is called.
374 * @param {webdriver.promise.ControlFlow} flow The control flow to use, or
375 * {@code null} to
376 * @return {!Builder} A self reference.
377 */
378Builder.prototype.setControlFlow = function(flow) {
379 this.flow_ = flow;
380 return this;
381};
382
383
384/**
385 * Creates a new WebDriver client based on this builder's current
386 * configuration.
387 *
388 * @return {!webdriver.WebDriver} A new WebDriver instance.
389 * @throws {Error} If the current configuration is invalid.
390 */
391Builder.prototype.build = function() {
392 // Create a copy for any changes we may need to make based on the current
393 // environment.
394 var capabilities = new Capabilities(this.capabilities_);
395
396 var browser;
397 if (!this.ignoreEnv_ && process.env.SELENIUM_BROWSER) {
398 browser = process.env.SELENIUM_BROWSER.split(/:/, 3);
399 capabilities.set(Capability.BROWSER_NAME, browser[0]);
400 capabilities.set(Capability.VERSION, browser[1] || null);
401 capabilities.set(Capability.PLATFORM, browser[2] || null);
402 }
403
404 browser = capabilities.get(Capability.BROWSER_NAME);
405
406 if (typeof browser !== 'string') {
407 throw TypeError(
408 'Target browser must be a string, but is <' + (typeof browser) + '>;' +
409 ' did you forget to call forBrowser()?');
410 }
411
412 if (browser === 'ie') {
413 browser = Browser.INTERNET_EXPLORER;
414 }
415
416 // Apply browser specific overrides.
417 if (browser === Browser.CHROME && this.chromeOptions_) {
418 capabilities.merge(this.chromeOptions_.toCapabilities());
419
420 } else if (browser === Browser.FIREFOX && this.firefoxOptions_) {
421 capabilities.merge(this.firefoxOptions_.toCapabilities());
422
423 } else if (browser === Browser.INTERNET_EXPLORER && this.ieOptions_) {
424 capabilities.merge(this.ieOptions_.toCapabilities());
425
426 } else if (browser === Browser.OPERA && this.operaOptions_) {
427 capabilities.merge(this.operaOptions_.toCapabilities());
428
429 } else if (browser === Browser.SAFARI && this.safariOptions_) {
430 capabilities.merge(this.safariOptions_.toCapabilities());
431 }
432
433 // Check for a remote browser.
434 var url = this.url_;
435 if (!this.ignoreEnv_) {
436 if (process.env.SELENIUM_REMOTE_URL) {
437 url = process.env.SELENIUM_REMOTE_URL;
438 } else if (process.env.SELENIUM_SERVER_JAR) {
439 url = startSeleniumServer(process.env.SELENIUM_SERVER_JAR);
440 }
441 }
442
443 if (url) {
444 var executor = executors.createExecutor(url, this.proxy_);
445 return WebDriver.createSession(executor, capabilities, this.flow_);
446 }
447
448 // Check for a native browser.
449 switch (browser) {
450 case Browser.CHROME:
451 // Requiring 'chrome' above would create a cycle:
452 // index -> builder -> chrome -> index
453 var chrome = require('./chrome');
454 return new chrome.Driver(capabilities, null, this.flow_);
455
456 case Browser.FIREFOX:
457 // Requiring 'firefox' above would create a cycle:
458 // index -> builder -> firefox -> index
459 var firefox = require('./firefox');
460 return new firefox.Driver(capabilities, this.flow_);
461
462 case Browser.INTERNET_EXPLORER:
463 // Requiring 'ie' above would create a cycle:
464 // index -> builder -> ie -> index
465 var ie = require('./ie');
466 return new ie.Driver(capabilities, this.flow_);
467
468 case Browser.OPERA:
469 // Requiring 'opera' would create a cycle:
470 // index -> builder -> opera -> index
471 var opera = require('./opera');
472 return new opera.Driver(capabilities, this.flow_);
473
474 case Browser.PHANTOM_JS:
475 // Requiring 'phantomjs' would create a cycle:
476 // index -> builder -> phantomjs -> index
477 var phantomjs = require('./phantomjs');
478 return new phantomjs.Driver(capabilities, this.flow_);
479
480 case Browser.SAFARI:
481 // Requiring 'safari' would create a cycle:
482 // index -> builder -> safari -> index
483 var safari = require('./safari');
484 return new safari.Driver(capabilities, this.flow_);
485
486 default:
487 throw new Error('Do not know how to build driver: ' + browser
488 + '; did you forget to call usingServer(url)?');
489 }
490};
491
492
493// PUBLIC API
494
495
496exports.Builder = Builder;