# 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 |
18 | var base = require('./_base'),
19 | executors = require('./executors');
20 |
21 | // Use base.require to avoid circular references between index and this module.
22 | var 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 |
30 | var 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 | */
38 | function 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 | */
90 | var 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 | */
130 | Builder.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 | */
147 | Builder.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 | */
157 | Builder.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 | */
170 | Builder.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 | */
180 | Builder.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 | */
192 | Builder.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 | */
203 | Builder.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 | */
225 | Builder.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 | */
240 | Builder.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 | */
253 | Builder.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 | */
264 | Builder.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 | */
276 | Builder.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 | */
289 | Builder.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 | */
304 | Builder.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 | */
319 | Builder.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 | */
334 | Builder.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 | */
349 | Builder.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 | */
364 | Builder.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 | */
378 | Builder.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 | */
391 | Builder.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 |
496 | exports.Builder = Builder;