# 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 |
97 | var url = require('url'),
98 | util = require('util');
99 |
100 | var 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 | */
115 | var 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 | */
138 | Options.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 | */
154 | Options.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 | */
168 | Options.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 | */
180 | Options.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 | */
191 | Options.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 | */
221 | var 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 | var self = this;
243 | var freePort = portprober.findFreePort();
244 |
245 | /** @private */
246 | this.command_ = freePort.then(function(port) {
247 | if (typeof profile === 'string') {
248 | return decodeProfile(profile).then(function(dir) {
249 | var profile = new Profile(dir);
250 | profile.setPreference('webdriver_firefox_port', port);
251 | return profile.writeToDisk();
252 | });
253 | } else {
254 | profile.setPreference('webdriver_firefox_port', port);
255 | return profile.writeToDisk();
256 | }
257 | }).then(function(profileDir) {
258 | self.profilePath_ = profileDir;
259 | return binary.launch(profileDir);
260 | });
261 |
262 | var serverUrl = this.command_
263 | .then(function() { return freePort; })
264 | .then(function(port) {
265 | var serverUrl = url.format({
266 | protocol: 'http',
267 | hostname: net.getLoopbackAddress(),
268 | port: port,
269 | pathname: '/hub'
270 | });
271 |
272 | return httpUtil.waitForServer(serverUrl, 45 * 1000).then(function() {
273 | return serverUrl;
274 | });
275 | });
276 |
277 | var executor = executors.createExecutor(serverUrl);
278 | var driver = webdriver.WebDriver.createSession(executor, caps, opt_flow);
279 |
280 | webdriver.WebDriver.call(this, driver.getSession(), executor, opt_flow);
281 | };
282 | util.inherits(Driver, webdriver.WebDriver);
283 |
284 |
285 | /**
286 | * This function is a no-op as file detectors are not supported by this
287 | * implementation.
288 | * @override
289 | */
290 | Driver.prototype.setFileDetector = function() {
291 | };
292 |
293 |
294 | /** @override */
295 | Driver.prototype.quit = function() {
296 | return this.call(function() {
297 | var self = this;
298 | return Driver.super_.prototype.quit.call(this)
299 | .thenFinally(function() {
300 | return self.command_.then(function(command) {
301 | command.kill();
302 | return command.result();
303 | });
304 | })
305 | .thenFinally(function() {
306 | if (self.profilePath_) {
307 | return io.rmDir(self.profilePath_);
308 | }
309 | });
310 | }, this);
311 | };
312 |
313 |
314 | // PUBLIC API
315 |
316 |
317 | exports.Binary = Binary;
318 | exports.Driver = Driver;
319 | exports.Options = Options;
320 | exports.Profile = Profile;