# 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 |
15 | var base = require('./_base'),
16 | executors = require('./executors');
17 |
18 | // Use base.require to avoid circular references between index and this module.
19 | var 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 |
27 | var 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 | */
35 | function 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 | */
87 | var 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 | */
124 | Builder.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 | */
141 | Builder.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 | */
151 | Builder.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 | */
163 | Builder.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 | */
174 | Builder.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 | */
196 | Builder.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 | */
211 | Builder.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 | */
224 | Builder.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 | */
235 | Builder.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 | */
247 | Builder.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 | */
260 | Builder.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 | */
275 | Builder.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 | */
290 | Builder.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 | */
305 | Builder.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 | */
320 | Builder.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 | */
335 | Builder.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 | */
349 | Builder.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 | */
362 | Builder.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 |
467 | exports.Builder = Builder;