Skip to content

Commit

Permalink
[js] Simplify the API for configuring firefox by adding addExtensions…
Browse files Browse the repository at this point in the history
…() and

setPreference() functions to firefox.Options

Remove the now redundant firefox/profile module
  • Loading branch information
jleyba committed Nov 3, 2017
1 parent d4d1108 commit 4e14ea1
Show file tree
Hide file tree
Showing 7 changed files with 243 additions and 502 deletions.
15 changes: 7 additions & 8 deletions javascript/node/selenium-webdriver/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ mode.
* Removed the `firefox.Binary` class. Custom binaries can still be selected
using `firefox.Options#setBinary()`. Likewise, custom binary arguments can be
specified with `firefox.Options#addArguments()`.
* Removed the `firefox/binary` module
* Removed the `lib/actions` module
* Removed the `lib/events` module
* Removed the `phantomjs` module
Expand Down Expand Up @@ -74,13 +73,13 @@ mode.
- Added uninstallAddon(id)
* Changes to `firefox.Options`
- Removed setLoggingPreferences (was a no-op)
* Changes to `firefox.Profile`
- Removed no-op methods
- acceptUntrustedCerts
- assumeUntrustedCertIssuer
- setAcceptUntrustedCerts
- setAssumeUntrustedCertIssuer
- setHost
- setProfile now only accepts a path to an existing profile
- Added addExtensions
- Added setPreference
* Removed the `firefox.Profile` class. All of its functionality is now
provided directly by `firefox.Options`
* Removed the `firefox/binary` module
* Removed the `firefox/profile` module
* Changes to `safari.Options`
- Removed setCleanSession (was a no-op)
- Removed setLoggingPreferences (was a no-op)
Expand Down
6 changes: 3 additions & 3 deletions javascript/node/selenium-webdriver/firefox/extension.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@

'use strict';

const fs = require('fs'),
path = require('path'),
xml = require('xml2js');
const fs = require('fs');
const path = require('path');
const xml = require('xml2js');

const io = require('../io');
const zip = require('../io/zip');
Expand Down
131 changes: 96 additions & 35 deletions javascript/node/selenium-webdriver/firefox/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,30 +26,26 @@
*
* __Customizing the Firefox Profile__
*
* The {@linkplain Profile} class may be used to configure the browser profile
* used with WebDriver, with functions to install additional
* {@linkplain Profile#addExtension extensions}, configure browser
* {@linkplain Profile#setPreference preferences}, and more. For example, you
* may wish to include Firebug:
* The profile used for each WebDriver session may be configured using the
* {@linkplain Options} class. For example, you may install an extension, like
* Firebug:
*
* const {Builder} = require('selenium-webdriver');
* const firefox = require('selenium-webdriver/firefox');
*
* let profile = new firefox.Profile();
* profile.addExtension('/path/to/firebug.xpi');
* profile.setPreference('extensions.firebug.showChromeErrors', true);
* let options = new firefox.Options()
* .addExtensions('/path/to/firebug.xpi')
* .setPreference('extensions.firebug.showChromeErrors', true);
*
* let options = new firefox.Options().setProfile(profile);
* let driver = new Builder()
* .forBrowser('firefox')
* .setFirefoxOptions(options)
* .build();
*
* The {@linkplain Profile} class may also be used to configure WebDriver based
* The {@linkplain Options} class may also be used to configure WebDriver based
* on a pre-existing browser profile:
*
* let profile = new firefox.Profile(
* '/usr/local/home/bob/.mozilla/firefox/3fgog75h.testing');
* let profile = '/usr/local/home/bob/.mozilla/firefox/3fgog75h.testing';
* let options = new firefox.Options().setProfile(profile);
*
* The FirefoxDriver will _never_ modify a pre-existing profile; instead it will
Expand Down Expand Up @@ -126,24 +122,28 @@ const url = require('url');
const capabilities = require('../lib/capabilities');
const command = require('../lib/command');
const exec = require('../io/exec');
const extension = require('./extension');
const http = require('../http');
const httpUtil = require('../http/util');
const io = require('../io');
const net = require('../net');
const portprober = require('../net/portprober');
const remote = require('../remote');
const webdriver = require('../lib/webdriver');
const {Profile} = require('./profile');
const {Zip} = require('../io/zip');


/**
* Configuration options for the FirefoxDriver.
*/
class Options {
constructor() {
/** @private {./profile.Profile} */
/** @private {?string} */
this.profile_ = null;

/** @private @const {!Map<string, (string|number|boolean)>} */
this.prefs_ = new Map;

/** @private {(Channel|string|null)} */
this.binary_ = null;

Expand All @@ -152,6 +152,9 @@ class Options {

/** @private {?../lib/proxy.Config} */
this.proxy_ = null;

/** @private {!Array<string>} */
this.extensions_ = [];
}

/**
Expand Down Expand Up @@ -196,16 +199,48 @@ class Options {
}

/**
* Sets the profile to use. The profile may be specified as a
* {@link Profile} object or as the path to an existing Firefox profile to use
* as a template.
* Add extensions that should be installed when starting Firefox.
*
* @param {(string|!./profile.Profile)} profile The profile to use.
* @param {...string} paths The paths to the extension XPI files to install.
* @return {!Options} A self reference.
*/
addExtensions(...paths) {
this.extensions_ = this.extensions_.concat(...paths);
return this;
}

/**
* @param {string} key the preference key.
* @param {(string|number|boolean)} value the preference value.
* @return {!Options} A self reference.
* @throws {TypeError} if either the key or value has an invalid type.
*/
setPreference(key, value) {
if (typeof key !== 'string') {
throw TypeError(`key must be a string, but got ${typeof key}`);
}
if (typeof value !== 'string'
&& typeof value !== 'number'
&& typeof value !== 'boolean') {
throw TypeError(
`value must be a string, number, or boolean, but got ${typeof value}`);
}
this.prefs_.set(key, value);
return this;
}

/**
* Sets the path to an existing profile to use as a template for new browser
* sessions. This profile will be copied for each new session - changes will
* not be applied to the profile itself.
*
* @param {string} profile The profile to use.
* @return {!Options} A self reference.
* @throws {TypeError} if profile is not a string.
*/
setProfile(profile) {
if (typeof profile === 'string') {
profile = new Profile(profile);
if (typeof profile !== 'string') {
throw TypeError(`profile must be a string, but got ${typeof profile}`);
}
this.profile_ = profile;
return this;
Expand Down Expand Up @@ -265,20 +300,15 @@ class Options {
}
}

if (this.profile_) {
// If the user specified a template directory or any extensions to
// install, we need to encode the profile as a base64 string (which
// requires writing it to disk first). Otherwise, if the user just
// specified some custom preferences, we can send those directly.
let profile = this.profile_;
if (profile.getTemplateDir() || profile.getExtensions().length) {
firefoxOptions['profile'] = profile.encode();

} else {
let prefs = profile.getPreferences();
if (Object.keys(prefs).length) {
firefoxOptions['prefs'] = prefs;
}
if (this.profile_ || this.extensions_.length) {
firefoxOptions['profile'] = buildProfile(this.profile_, this.extensions_);
}

if (this.prefs_.size) {
let prefs = {};
firefoxOptions['prefs'] = prefs;
for (let entry of this.prefs_.entries()) {
prefs[entry[0]] = entry[1];
}
}

Expand All @@ -287,6 +317,38 @@ class Options {
}


/**
* @param {?string} template path to an existing profile to use as a template.
* @param {!Array<string>} extensions paths to extensions to install in the new
* profile.
* @return {!Promise<string>} a promise for the base64 encoded profile.
*/
async function buildProfile(template, extensions) {
let dir = template;

if (extensions.length) {
dir = await io.tmpDir();
if (template) {
await io.copyDir(
/** @type {string} */(template),
dir, /(parent\.lock|lock|\.parentlock)/);
}

const extensionsDir = path.join(dir, 'extensions');
await io.mkdir(extensionsDir);

for (let i = 0; i < extensions.length; i++) {
await extension.install(extensions[i], extensionsDir);
}
}

let zip = new Zip;
return zip.addDir(dir)
.then(() => zip.toBuffer())
.then(buf => buf.toString('base64'));
}


/**
* Enum of available command contexts.
*
Expand Down Expand Up @@ -704,6 +766,5 @@ exports.Channel = Channel;
exports.Context = Context;
exports.Driver = Driver;
exports.Options = Options;
exports.Profile = Profile;
exports.ServiceBuilder = ServiceBuilder;
exports.locateSynchronously = locateSynchronously;
Loading

0 comments on commit 4e14ea1

Please sign in to comment.