firefox/index.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/**
19 * @fileoverview Defines the {@linkplain Driver WebDriver} client for Firefox.
20 * Each FirefoxDriver instance will be created with an anonymous profile,
21 * ensuring browser historys do not share session data (cookies, history, cache,
22 * offline storage, etc.)
23 *
24 * __Customizing the Firefox Profile__
25 *
26 * The {@link Profile} class may be used to configure the browser profile used
27 * with WebDriver, with functions to install additional
28 * {@linkplain Profile#addExtension extensions}, configure browser
29 * {@linkplain Profile#setPreference preferences}, and more. For example, you
30 * may wish to include Firebug:
31 *
32 * var firefox = require('selenium-webdriver/firefox');
33 *
34 * var profile = new firefox.Profile();
35 * profile.addExtension('/path/to/firebug.xpi');
36 * profile.setPreference('extensions.firebug.showChromeErrors', true);
37 *
38 * var options = new firefox.Options().setProfile(profile);
39 * var driver = new firefox.Driver(options);
40 *
41 * The {@link Profile} class may also be used to configure WebDriver based on a
42 * pre-existing browser profile:
43 *
44 * var profile = new firefox.Profile(
45 * '/usr/local/home/bob/.mozilla/firefox/3fgog75h.testing');
46 * var options = new firefox.Options().setProfile(profile);
47 * var driver = new firefox.Driver(options);
48 *
49 * The FirefoxDriver will _never_ modify a pre-existing profile; instead it will
50 * create a copy for it to modify. By extension, there are certain browser
51 * preferences that are required for WebDriver to function properly and they
52 * will always be overwritten.
53 *
54 * __Using a Custom Firefox Binary__
55 *
56 * On Windows and OSX, the FirefoxDriver will search for Firefox in its
57 * default installation location:
58 *
59 * * Windows: C:\Program Files and C:\Program Files (x86).
60 * * Mac OS X: /Applications/Firefox.app
61 *
62 * For Linux, Firefox will be located on the PATH: `$(where firefox)`.
63 *
64 * You can configure WebDriver to start use a custom Firefox installation with
65 * the {@link Binary} class:
66 *
67 * var firefox = require('selenium-webdriver/firefox');
68 * var binary = new firefox.Binary('/my/firefox/install/dir/firefox-bin');
69 * var options = new firefox.Options().setBinary(binary);
70 * var driver = new firefox.Driver(options);
71 *
72 * __Remote Testing__
73 *
74 * You may customize the Firefox binary and profile when running against a
75 * remote Selenium server. Your custom profile will be packaged as a zip and
76 * transfered to the remote host for use. The profile will be transferred
77 * _once for each new session_. The performance impact should be minimal if
78 * you've only configured a few extra browser preferences. If you have a large
79 * profile with several extensions, you should consider installing it on the
80 * remote host and defining its path via the {@link Options} class. Custom
81 * binaries are never copied to remote machines and must be referenced by
82 * installation path.
83 *
84 * var options = new firefox.Options()
85 * .setProfile('/profile/path/on/remote/host')
86 * .setBinary('/install/dir/on/remote/host/firefox-bin');
87 *
88 * var driver = new (require('selenium-webdriver')).Builder()
89 * .forBrowser('firefox')
90 * .usingServer('http://127.0.0.1:4444/wd/hub')
91 * .setFirefoxOptions(options)
92 * .build();
93 */
94
95'use strict';
96
97var url = require('url'),
98 util = require('util');
99
100var Binary = require('./binary').Binary,
101 Profile = require('./profile').Profile,
102 decodeProfile = require('./profile').decode,
103 webdriver = require('..'),
104 executors = require('../executors'),
105 httpUtil = require('../http/util'),
106 io = require('../io'),
107 net = require('../net'),
108 portprober = require('../net/portprober');
109
110
111/**
112 * Configuration options for the FirefoxDriver.
113 * @constructor
114 */
115var Options = function() {
116 /** @private {Profile} */
117 this.profile_ = null;
118
119 /** @private {Binary} */
120 this.binary_ = null;
121
122 /** @private {webdriver.logging.Preferences} */
123 this.logPrefs_ = null;
124
125 /** @private {webdriver.ProxyConfig} */
126 this.proxy_ = null;
127};
128
129
130/**
131 * Sets the profile to use. The profile may be specified as a
132 * {@link Profile} object or as the path to an existing Firefox profile to use
133 * as a template.
134 *
135 * @param {(string|!Profile)} profile The profile to use.
136 * @return {!Options} A self reference.
137 */
138Options.prototype.setProfile = function(profile) {
139 if (typeof profile === 'string') {
140 profile = new Profile(profile);
141 }
142 this.profile_ = profile;
143 return this;
144};
145
146
147/**
148 * Sets the binary to use. The binary may be specified as the path to a Firefox
149 * executable, or as a {@link Binary} object.
150 *
151 * @param {(string|!Binary)} binary The binary to use.
152 * @return {!Options} A self reference.
153 */
154Options.prototype.setBinary = function(binary) {
155 if (typeof binary === 'string') {
156 binary = new Binary(binary);
157 }
158 this.binary_ = binary;
159 return this;
160};
161
162
163/**
164 * Sets the logging preferences for the new session.
165 * @param {webdriver.logging.Preferences} prefs The logging preferences.
166 * @return {!Options} A self reference.
167 */
168Options.prototype.setLoggingPreferences = function(prefs) {
169 this.logPrefs_ = prefs;
170 return this;
171};
172
173
174/**
175 * Sets the proxy to use.
176 *
177 * @param {webdriver.ProxyConfig} proxy The proxy configuration to use.
178 * @return {!Options} A self reference.
179 */
180Options.prototype.setProxy = function(proxy) {
181 this.proxy_ = proxy;
182 return this;
183};
184
185
186/**
187 * Converts these options to a {@link webdriver.Capabilities} instance.
188 *
189 * @return {!webdriver.Capabilities} A new capabilities object.
190 */
191Options.prototype.toCapabilities = function(opt_remote) {
192 var caps = webdriver.Capabilities.firefox();
193 if (this.logPrefs_) {
194 caps.set(webdriver.Capability.LOGGING_PREFS, this.logPrefs_);
195 }
196 if (this.proxy_) {
197 caps.set(webdriver.Capability.PROXY, this.proxy_);
198 }
199 if (this.binary_) {
200 caps.set('firefox_binary', this.binary_);
201 }
202 if (this.profile_) {
203 caps.set('firefox_profile', this.profile_);
204 }
205 return caps;
206};
207
208
209/**
210 * A WebDriver client for Firefox.
211 *
212 * @param {(Options|webdriver.Capabilities|Object)=} opt_config The
213 * configuration options for this driver, specified as either an
214 * {@link Options} or {@link webdriver.Capabilities}, or as a raw hash
215 * object.
216 * @param {webdriver.promise.ControlFlow=} opt_flow The flow to
217 * schedule commands through. Defaults to the active flow object.
218 * @constructor
219 * @extends {webdriver.WebDriver}
220 */
221var Driver = function(opt_config, opt_flow) {
222 var caps;
223 if (opt_config instanceof Options) {
224 caps = opt_config.toCapabilities();
225 } else {
226 caps = new webdriver.Capabilities(opt_config);
227 }
228
229 var binary = caps.get('firefox_binary') || new Binary();
230 if (typeof binary === 'string') {
231 binary = new Binary(binary);
232 }
233
234 var profile = caps.get('firefox_profile') || new Profile();
235
236 caps.set('firefox_binary', null);
237 caps.set('firefox_profile', null);
238
239 /** @private {?string} */
240 this.profilePath_ = null;
241
242 /** @private {!Binary} */
243 this.binary_ = binary;
244
245 var self = this;
246 var serverUrl = portprober.findFreePort().then(function(port) {
247 var prepareProfile;
248 if (typeof profile === 'string') {
249 prepareProfile = decodeProfile(profile).then(function(dir) {
250 var profile = new Profile(dir);
251 profile.setPreference('webdriver_firefox_port', port);
252 return profile.writeToDisk();
253 });
254 } else {
255 profile.setPreference('webdriver_firefox_port', port);
256 prepareProfile = profile.writeToDisk();
257 }
258
259 return prepareProfile.then(function(dir) {
260 self.profilePath_ = dir;
261 return binary.launch(dir);
262 }).then(function() {
263 var serverUrl = url.format({
264 protocol: 'http',
265 hostname: net.getLoopbackAddress(),
266 port: port,
267 pathname: '/hub'
268 });
269
270 return httpUtil.waitForServer(serverUrl, 45 * 1000).then(function() {
271 return serverUrl;
272 });
273 });
274 });
275
276 var executor = executors.createExecutor(serverUrl);
277 var driver = webdriver.WebDriver.createSession(executor, caps, opt_flow);
278
279 webdriver.WebDriver.call(this, driver.getSession(), executor, opt_flow);
280};
281util.inherits(Driver, webdriver.WebDriver);
282
283
284/**
285 * This function is a no-op as file detectors are not supported by this
286 * implementation.
287 * @override
288 */
289Driver.prototype.setFileDetector = function() {
290};
291
292
293/** @override */
294Driver.prototype.quit = function() {
295 return this.call(function() {
296 var self = this;
297 return Driver.super_.prototype.quit.call(this)
298 .thenFinally(function() {
299 return self.binary_.kill();
300 })
301 .thenFinally(function() {
302 if (self.profilePath_) {
303 return io.rmDir(self.profilePath_);
304 }
305 });
306 }, this);
307};
308
309
310// PUBLIC API
311
312
313exports.Binary = Binary;
314exports.Driver = Driver;
315exports.Options = Options;
316exports.Profile = Profile;