builder.js

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