selenium
This commit is contained in:
852
node_modules/selenium-webdriver/firefox.js
generated
vendored
Normal file
852
node_modules/selenium-webdriver/firefox.js
generated
vendored
Normal file
@@ -0,0 +1,852 @@
|
||||
// Licensed to the Software Freedom Conservancy (SFC) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The SFC licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
/**
|
||||
* @fileoverview Defines the {@linkplain Driver WebDriver} client for Firefox.
|
||||
* Before using this module, you must download the latest
|
||||
* [geckodriver release] and ensure it can be found on your system [PATH].
|
||||
*
|
||||
* Each FirefoxDriver instance will be created with an anonymous profile,
|
||||
* ensuring browser historys do not share session data (cookies, history, cache,
|
||||
* offline storage, etc.)
|
||||
*
|
||||
* __Customizing the Firefox Profile__
|
||||
*
|
||||
* 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 options = new firefox.Options()
|
||||
* .addExtensions('/path/to/firebug.xpi')
|
||||
* .setPreference('extensions.firebug.showChromeErrors', true);
|
||||
*
|
||||
* let driver = new Builder()
|
||||
* .forBrowser('firefox')
|
||||
* .setFirefoxOptions(options)
|
||||
* .build();
|
||||
*
|
||||
* The {@linkplain Options} class may also be used to configure WebDriver based
|
||||
* on a pre-existing browser profile:
|
||||
*
|
||||
* 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
|
||||
* create a copy for it to modify. By extension, there are certain browser
|
||||
* preferences that are required for WebDriver to function properly and they
|
||||
* will always be overwritten.
|
||||
*
|
||||
* __Using a Custom Firefox Binary__
|
||||
*
|
||||
* On Windows and MacOS, the FirefoxDriver will search for Firefox in its
|
||||
* default installation location:
|
||||
*
|
||||
* - Windows: C:\Program Files and C:\Program Files (x86).
|
||||
* - MacOS: /Applications/Firefox.app
|
||||
*
|
||||
* For Linux, Firefox will always be located on the PATH: `$(where firefox)`.
|
||||
*
|
||||
* Several methods are provided for starting Firefox with a custom executable.
|
||||
* First, on Windows and MacOS, you may configure WebDriver to check the default
|
||||
* install location for a non-release channel. If the requested channel cannot
|
||||
* be found in its default location, WebDriver will fallback to searching your
|
||||
* PATH. _Note:_ on Linux, Firefox is _always_ located on your path, regardless
|
||||
* of the requested channel.
|
||||
*
|
||||
* const {Builder} = require('selenium-webdriver');
|
||||
* const firefox = require('selenium-webdriver/firefox');
|
||||
*
|
||||
* let options = new firefox.Options().setBinary(firefox.Channel.NIGHTLY);
|
||||
* let driver = new Builder()
|
||||
* .forBrowser('firefox')
|
||||
* .setFirefoxOptions(options)
|
||||
* .build();
|
||||
*
|
||||
* On all platforms, you may configure WebDriver to use a Firefox specific
|
||||
* executable:
|
||||
*
|
||||
* let options = new firefox.Options()
|
||||
* .setBinary('/my/firefox/install/dir/firefox-bin');
|
||||
*
|
||||
* __Remote Testing__
|
||||
*
|
||||
* You may customize the Firefox binary and profile when running against a
|
||||
* remote Selenium server. Your custom profile will be packaged as a zip and
|
||||
* transferred to the remote host for use. The profile will be transferred
|
||||
* _once for each new session_. The performance impact should be minimal if
|
||||
* you've only configured a few extra browser preferences. If you have a large
|
||||
* profile with several extensions, you should consider installing it on the
|
||||
* remote host and defining its path via the {@link Options} class. Custom
|
||||
* binaries are never copied to remote machines and must be referenced by
|
||||
* installation path.
|
||||
*
|
||||
* const {Builder} = require('selenium-webdriver');
|
||||
* const firefox = require('selenium-webdriver/firefox');
|
||||
*
|
||||
* let options = new firefox.Options()
|
||||
* .setProfile('/profile/path/on/remote/host')
|
||||
* .setBinary('/install/dir/on/remote/host/firefox-bin');
|
||||
*
|
||||
* let driver = new Builder()
|
||||
* .forBrowser('firefox')
|
||||
* .usingServer('http://127.0.0.1:4444/wd/hub')
|
||||
* .setFirefoxOptions(options)
|
||||
* .build();
|
||||
*
|
||||
* [geckodriver release]: https://github.com/mozilla/geckodriver/releases/
|
||||
* [PATH]: http://en.wikipedia.org/wiki/PATH_%28variable%29
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const Symbols = require('./lib/symbols')
|
||||
const command = require('./lib/command')
|
||||
const http = require('./http')
|
||||
const io = require('./io')
|
||||
const remote = require('./remote')
|
||||
const webdriver = require('./lib/webdriver')
|
||||
const zip = require('./io/zip')
|
||||
const { Browser, Capabilities } = require('./lib/capabilities')
|
||||
const { Zip } = require('./io/zip')
|
||||
const { driverLocation } = require('./common/seleniumManager')
|
||||
|
||||
/**
|
||||
* Thrown when there an add-on is malformed.
|
||||
* @final
|
||||
*/
|
||||
class AddonFormatError extends Error {
|
||||
/** @param {string} msg The error message. */
|
||||
constructor(msg) {
|
||||
super(msg)
|
||||
/** @override */
|
||||
this.name = this.constructor.name
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs an extension to the given directory.
|
||||
* @param {string} extension Path to the xpi extension file to install.
|
||||
* @param {string} dir Path to the directory to install the extension in.
|
||||
* @return {!Promise<string>} A promise for the add-on ID once
|
||||
* installed.
|
||||
*/
|
||||
async function installExtension(extension, dir) {
|
||||
const ext = extension.slice(-4)
|
||||
if (ext !== '.xpi' && ext !== '.zip') {
|
||||
throw Error('File name does not end in ".zip" or ".xpi": ' + ext)
|
||||
}
|
||||
|
||||
let archive = await zip.load(extension)
|
||||
if (!archive.has('manifest.json')) {
|
||||
throw new AddonFormatError(`Couldn't find manifest.json in ${extension}`)
|
||||
}
|
||||
|
||||
let buf = await archive.getFile('manifest.json')
|
||||
let parsedJSON = JSON.parse(buf.toString('utf8'))
|
||||
|
||||
let { browser_specific_settings } =
|
||||
/** @type {{browser_specific_settings:{gecko:{id:string}}}} */
|
||||
parsedJSON
|
||||
|
||||
if (browser_specific_settings && browser_specific_settings.gecko) {
|
||||
/* browser_specific_settings is an alternative to applications
|
||||
* It is meant to facilitate cross-browser plugins since Firefox48
|
||||
* see https://bugzilla.mozilla.org/show_bug.cgi?id=1262005
|
||||
*/
|
||||
parsedJSON.applications = browser_specific_settings
|
||||
}
|
||||
|
||||
let { applications } =
|
||||
/** @type {{applications:{gecko:{id:string}}}} */
|
||||
parsedJSON
|
||||
if (!(applications && applications.gecko && applications.gecko.id)) {
|
||||
throw new AddonFormatError(`Could not find add-on ID for ${extension}`)
|
||||
}
|
||||
|
||||
await io.copy(extension, `${path.join(dir, applications.gecko.id)}.xpi`)
|
||||
return applications.gecko.id
|
||||
}
|
||||
|
||||
class Profile {
|
||||
constructor() {
|
||||
/** @private {?string} */
|
||||
this.template_ = null
|
||||
|
||||
/** @private {!Array<string>} */
|
||||
this.extensions_ = []
|
||||
}
|
||||
|
||||
addExtensions(/** !Array<string> */ paths) {
|
||||
this.extensions_ = this.extensions_.concat(...paths)
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {(!Promise<string>|undefined)} a promise for a base64 encoded
|
||||
* profile, or undefined if there's no data to include.
|
||||
*/
|
||||
[Symbols.serialize]() {
|
||||
if (this.template_ || this.extensions_.length) {
|
||||
return buildProfile(this.template_, this.extensions_)
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 installExtension(extensions[i], extensionsDir)
|
||||
}
|
||||
}
|
||||
|
||||
let zip = new Zip()
|
||||
return zip
|
||||
.addDir(dir)
|
||||
.then(() => zip.toBuffer())
|
||||
.then((buf) => buf.toString('base64'))
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration options for the FirefoxDriver.
|
||||
*/
|
||||
class Options extends Capabilities {
|
||||
/**
|
||||
* @param {(Capabilities|Map<string, ?>|Object)=} other Another set of
|
||||
* capabilities to initialize this instance from.
|
||||
*/
|
||||
constructor(other) {
|
||||
super(other)
|
||||
this.setBrowserName(Browser.FIREFOX)
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!Object}
|
||||
* @private
|
||||
*/
|
||||
firefoxOptions_() {
|
||||
let options = this.get('moz:firefoxOptions')
|
||||
if (!options) {
|
||||
options = {}
|
||||
this.set('moz:firefoxOptions', options)
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {!Profile}
|
||||
* @private
|
||||
*/
|
||||
profile_() {
|
||||
let options = this.firefoxOptions_()
|
||||
if (!options.profile) {
|
||||
options.profile = new Profile()
|
||||
}
|
||||
return options.profile
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify additional command line arguments that should be used when starting
|
||||
* the Firefox browser.
|
||||
*
|
||||
* @param {...(string|!Array<string>)} args The arguments to include.
|
||||
* @return {!Options} A self reference.
|
||||
*/
|
||||
addArguments(...args) {
|
||||
if (args.length) {
|
||||
let options = this.firefoxOptions_()
|
||||
options.args = options.args ? options.args.concat(...args) : args
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link Options#addArguments} instead.
|
||||
* @example
|
||||
* options.addArguments('-headless');
|
||||
* @example
|
||||
* Configures the geckodriver to start Firefox in headless mode.
|
||||
*
|
||||
* @return {!Options} A self reference.
|
||||
*/
|
||||
headless() {
|
||||
return this.addArguments('-headless')
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the initial window size when running in
|
||||
* {@linkplain #headless headless} mode.
|
||||
*
|
||||
* @param {{width: number, height: number}} size The desired window size.
|
||||
* @return {!Options} A self reference.
|
||||
* @throws {TypeError} if width or height is unspecified, not a number, or
|
||||
* less than or equal to 0.
|
||||
*/
|
||||
windowSize({ width, height }) {
|
||||
function checkArg(arg) {
|
||||
if (typeof arg !== 'number' || arg <= 0) {
|
||||
throw TypeError('Arguments must be {width, height} with numbers > 0')
|
||||
}
|
||||
}
|
||||
checkArg(width)
|
||||
checkArg(height)
|
||||
return this.addArguments(`--width=${width}`, `--height=${height}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add extensions that should be installed when starting Firefox.
|
||||
*
|
||||
* @param {...string} paths The paths to the extension XPI files to install.
|
||||
* @return {!Options} A self reference.
|
||||
*/
|
||||
addExtensions(...paths) {
|
||||
this.profile_().addExtensions(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}`
|
||||
)
|
||||
}
|
||||
let options = this.firefoxOptions_()
|
||||
options.prefs = options.prefs || {}
|
||||
options.prefs[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') {
|
||||
throw TypeError(`profile must be a string, but got ${typeof profile}`)
|
||||
}
|
||||
this.profile_().template_ = profile
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the binary to use. The binary may be specified as the path to a
|
||||
* Firefox executable or a desired release {@link Channel}.
|
||||
*
|
||||
* @param {(string|!Channel)} binary The binary to use.
|
||||
* @return {!Options} A self reference.
|
||||
* @throws {TypeError} If `binary` is an invalid type.
|
||||
*/
|
||||
setBinary(binary) {
|
||||
if (binary instanceof Channel || typeof binary === 'string') {
|
||||
this.firefoxOptions_().binary = binary
|
||||
return this
|
||||
}
|
||||
throw TypeError('binary must be a string path or Channel object')
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables Mobile start up features
|
||||
*
|
||||
* @param {string} androidPackage The package to use
|
||||
* @return {!Options} A self reference
|
||||
*/
|
||||
enableMobile(
|
||||
androidPackage = 'org.mozilla.firefox',
|
||||
androidActivity = null,
|
||||
deviceSerial = null
|
||||
) {
|
||||
this.firefoxOptions_().androidPackage = androidPackage
|
||||
|
||||
if (androidActivity) {
|
||||
this.firefoxOptions_().androidActivity = androidActivity
|
||||
}
|
||||
if (deviceSerial) {
|
||||
this.firefoxOptions_().deviceSerial = deviceSerial
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables moz:debuggerAddress for firefox cdp
|
||||
*/
|
||||
enableDebugger() {
|
||||
return this.set('moz:debuggerAddress', true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable bidi connection
|
||||
* @returns {!Capabilities}
|
||||
*/
|
||||
enableBidi() {
|
||||
return this.set('webSocketUrl', true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum of available command contexts.
|
||||
*
|
||||
* Command contexts are specific to Marionette, and may be used with the
|
||||
* {@link #context=} method. Contexts allow you to direct all subsequent
|
||||
* commands to either "content" (default) or "chrome". The latter gives
|
||||
* you elevated security permissions.
|
||||
*
|
||||
* @enum {string}
|
||||
*/
|
||||
const Context = {
|
||||
CONTENT: 'content',
|
||||
CHROME: 'chrome',
|
||||
}
|
||||
|
||||
const GECKO_DRIVER_EXE =
|
||||
process.platform === 'win32' ? 'geckodriver.exe' : 'geckodriver'
|
||||
|
||||
/**
|
||||
* _Synchronously_ attempts to locate the geckodriver executable on the current
|
||||
* system.
|
||||
*
|
||||
* @return {?string} the located executable, or `null`.
|
||||
*/
|
||||
function locateSynchronously() {
|
||||
return io.findInPath(GECKO_DRIVER_EXE, true)
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string} .
|
||||
* @throws {Error}
|
||||
*/
|
||||
function findGeckoDriver() {
|
||||
let exe = locateSynchronously()
|
||||
|
||||
if (!exe) {
|
||||
console.log(
|
||||
`The ${GECKO_DRIVER_EXE} executable could not be found on the current PATH, trying Selenium Manager`
|
||||
)
|
||||
|
||||
try {
|
||||
exe = driverLocation(Browser.FIREFOX)
|
||||
} catch (err) {
|
||||
console.log(`Unable to obtain driver using Selenium Manager: ${err}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (!exe) {
|
||||
throw Error(
|
||||
`The ${GECKO_DRIVER_EXE} executable could not be found on the current PATH.
|
||||
Please download the latest version from https://github.com/mozilla/geckodriver/releases/
|
||||
and ensure it can be found on your PATH.`
|
||||
)
|
||||
}
|
||||
|
||||
return exe
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} file Path to the file to find, relative to the program files
|
||||
* root.
|
||||
* @return {!Promise<?string>} A promise for the located executable.
|
||||
* The promise will resolve to {@code null} if Firefox was not found.
|
||||
*/
|
||||
function findInProgramFiles(file) {
|
||||
let files = [
|
||||
process.env['PROGRAMFILES'] || 'C:\\Program Files',
|
||||
process.env['PROGRAMFILES(X86)'] || 'C:\\Program Files (x86)',
|
||||
].map((prefix) => path.join(prefix, file))
|
||||
return io.exists(files[0]).then(function (exists) {
|
||||
return exists
|
||||
? files[0]
|
||||
: io.exists(files[1]).then(function (exists) {
|
||||
return exists ? files[1] : null
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/** @enum {string} */
|
||||
const ExtensionCommand = {
|
||||
GET_CONTEXT: 'getContext',
|
||||
SET_CONTEXT: 'setContext',
|
||||
INSTALL_ADDON: 'install addon',
|
||||
UNINSTALL_ADDON: 'uninstall addon',
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a command executor with support for Marionette's custom commands.
|
||||
* @param {!Promise<string>} serverUrl The server's URL.
|
||||
* @return {!command.Executor} The new command executor.
|
||||
*/
|
||||
function createExecutor(serverUrl) {
|
||||
let client = serverUrl.then((url) => new http.HttpClient(url))
|
||||
let executor = new http.Executor(client)
|
||||
configureExecutor(executor)
|
||||
return executor
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the given executor with Firefox-specific commands.
|
||||
* @param {!http.Executor} executor the executor to configure.
|
||||
*/
|
||||
function configureExecutor(executor) {
|
||||
executor.defineCommand(
|
||||
ExtensionCommand.GET_CONTEXT,
|
||||
'GET',
|
||||
'/session/:sessionId/moz/context'
|
||||
)
|
||||
|
||||
executor.defineCommand(
|
||||
ExtensionCommand.SET_CONTEXT,
|
||||
'POST',
|
||||
'/session/:sessionId/moz/context'
|
||||
)
|
||||
|
||||
executor.defineCommand(
|
||||
ExtensionCommand.INSTALL_ADDON,
|
||||
'POST',
|
||||
'/session/:sessionId/moz/addon/install'
|
||||
)
|
||||
|
||||
executor.defineCommand(
|
||||
ExtensionCommand.UNINSTALL_ADDON,
|
||||
'POST',
|
||||
'/session/:sessionId/moz/addon/uninstall'
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates {@link selenium-webdriver/remote.DriverService} instances that manage
|
||||
* a [geckodriver](https://github.com/mozilla/geckodriver) server in a child
|
||||
* process.
|
||||
*/
|
||||
class ServiceBuilder extends remote.DriverService.Builder {
|
||||
/**
|
||||
* @param {string=} opt_exe Path to the server executable to use. If omitted,
|
||||
* the builder will attempt to locate the geckodriver on the system PATH.
|
||||
*/
|
||||
constructor(opt_exe) {
|
||||
super(opt_exe || findGeckoDriver())
|
||||
this.setLoopback(true) // Required.
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables verbose logging.
|
||||
*
|
||||
* @param {boolean=} opt_trace Whether to enable trace-level logging. By
|
||||
* default, only debug logging is enabled.
|
||||
* @return {!ServiceBuilder} A self reference.
|
||||
*/
|
||||
enableVerboseLogging(opt_trace) {
|
||||
return this.addArguments(opt_trace ? '-vv' : '-v')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A WebDriver client for Firefox.
|
||||
*/
|
||||
class Driver extends webdriver.WebDriver {
|
||||
/**
|
||||
* Creates a new Firefox session.
|
||||
*
|
||||
* @param {(Options|Capabilities|Object)=} opt_config The
|
||||
* configuration options for this driver, specified as either an
|
||||
* {@link Options} or {@link Capabilities}, or as a raw hash object.
|
||||
* @param {(http.Executor|remote.DriverService)=} opt_executor Either a
|
||||
* pre-configured command executor to use for communicating with an
|
||||
* externally managed remote end (which is assumed to already be running),
|
||||
* or the `DriverService` to use to start the geckodriver in a child
|
||||
* process.
|
||||
*
|
||||
* If an executor is provided, care should e taken not to use reuse it with
|
||||
* other clients as its internal command mappings will be updated to support
|
||||
* Firefox-specific commands.
|
||||
*
|
||||
* _This parameter may only be used with Mozilla's GeckoDriver._
|
||||
*
|
||||
* @throws {Error} If a custom command executor is provided and the driver is
|
||||
* configured to use the legacy FirefoxDriver from the Selenium project.
|
||||
* @return {!Driver} A new driver instance.
|
||||
*/
|
||||
static createSession(opt_config, opt_executor) {
|
||||
let caps =
|
||||
opt_config instanceof Capabilities ? opt_config : new Options(opt_config)
|
||||
|
||||
let executor
|
||||
let onQuit
|
||||
|
||||
if (opt_executor instanceof http.Executor) {
|
||||
executor = opt_executor
|
||||
configureExecutor(executor)
|
||||
} else if (opt_executor instanceof remote.DriverService) {
|
||||
executor = createExecutor(opt_executor.start())
|
||||
onQuit = () => opt_executor.kill()
|
||||
} else {
|
||||
let service = new ServiceBuilder().build()
|
||||
executor = createExecutor(service.start())
|
||||
onQuit = () => service.kill()
|
||||
}
|
||||
|
||||
return /** @type {!Driver} */ (super.createSession(executor, caps, onQuit))
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is a no-op as file detectors are not supported by this
|
||||
* implementation.
|
||||
* @override
|
||||
*/
|
||||
setFileDetector() {}
|
||||
|
||||
/**
|
||||
* Get the context that is currently in effect.
|
||||
*
|
||||
* @return {!Promise<Context>} Current context.
|
||||
*/
|
||||
getContext() {
|
||||
return this.execute(new command.Command(ExtensionCommand.GET_CONTEXT))
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes target context for commands between chrome- and content.
|
||||
*
|
||||
* Changing the current context has a stateful impact on all subsequent
|
||||
* commands. The {@link Context.CONTENT} context has normal web
|
||||
* platform document permissions, as if you would evaluate arbitrary
|
||||
* JavaScript. The {@link Context.CHROME} context gets elevated
|
||||
* permissions that lets you manipulate the browser chrome itself,
|
||||
* with full access to the XUL toolkit.
|
||||
*
|
||||
* Use your powers wisely.
|
||||
*
|
||||
* @param {!Promise<void>} ctx The context to switch to.
|
||||
*/
|
||||
setContext(ctx) {
|
||||
return this.execute(
|
||||
new command.Command(ExtensionCommand.SET_CONTEXT).setParameter(
|
||||
'context',
|
||||
ctx
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs a new addon with the current session. This function will return an
|
||||
* ID that may later be used to {@linkplain #uninstallAddon uninstall} the
|
||||
* addon.
|
||||
*
|
||||
*
|
||||
* @param {string} path Path on the local filesystem to the web extension to
|
||||
* install.
|
||||
* @param {boolean} temporary Flag indicating whether the extension should be
|
||||
* installed temporarily - gets removed on restart
|
||||
* @return {!Promise<string>} A promise that will resolve to an ID for the
|
||||
* newly installed addon.
|
||||
* @see #uninstallAddon
|
||||
*/
|
||||
async installAddon(path, temporary = false) {
|
||||
let stats = fs.statSync(path)
|
||||
let buf
|
||||
if (stats.isDirectory()) {
|
||||
let zip = new Zip()
|
||||
await zip.addDir(path)
|
||||
buf = await zip.toBuffer('DEFLATE')
|
||||
} else {
|
||||
buf = await io.read(path)
|
||||
}
|
||||
return this.execute(
|
||||
new command.Command(ExtensionCommand.INSTALL_ADDON)
|
||||
.setParameter('addon', buf.toString('base64'))
|
||||
.setParameter('temporary', temporary)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninstalls an addon from the current browser session's profile.
|
||||
*
|
||||
* @param {(string|!Promise<string>)} id ID of the addon to uninstall.
|
||||
* @return {!Promise} A promise that will resolve when the operation has
|
||||
* completed.
|
||||
* @see #installAddon
|
||||
*/
|
||||
async uninstallAddon(id) {
|
||||
id = await Promise.resolve(id)
|
||||
return this.execute(
|
||||
new command.Command(ExtensionCommand.UNINSTALL_ADDON).setParameter(
|
||||
'id',
|
||||
id
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides methods for locating the executable for a Firefox release channel
|
||||
* on Windows and MacOS. For other systems (i.e. Linux), Firefox will always
|
||||
* be located on the system PATH.
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class Channel {
|
||||
/**
|
||||
* @param {string} darwin The path to check when running on MacOS.
|
||||
* @param {string} win32 The path to check when running on Windows.
|
||||
*/
|
||||
constructor(darwin, win32) {
|
||||
/** @private @const */ this.darwin_ = darwin
|
||||
/** @private @const */ this.win32_ = win32
|
||||
/** @private {Promise<string>} */
|
||||
this.found_ = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to locate the Firefox executable for this release channel. This
|
||||
* will first check the default installation location for the channel before
|
||||
* checking the user's PATH. The returned promise will be rejected if Firefox
|
||||
* can not be found.
|
||||
*
|
||||
* @return {!Promise<string>} A promise for the location of the located
|
||||
* Firefox executable.
|
||||
*/
|
||||
locate() {
|
||||
if (this.found_) {
|
||||
return this.found_
|
||||
}
|
||||
|
||||
let found
|
||||
switch (process.platform) {
|
||||
case 'darwin':
|
||||
found = io
|
||||
.exists(this.darwin_)
|
||||
.then((exists) => (exists ? this.darwin_ : io.findInPath('firefox')))
|
||||
break
|
||||
|
||||
case 'win32':
|
||||
found = findInProgramFiles(this.win32_).then(
|
||||
(found) => found || io.findInPath('firefox.exe')
|
||||
)
|
||||
break
|
||||
|
||||
default:
|
||||
found = Promise.resolve(io.findInPath('firefox'))
|
||||
break
|
||||
}
|
||||
|
||||
this.found_ = found.then((found) => {
|
||||
if (found) {
|
||||
// TODO: verify version info.
|
||||
return found
|
||||
}
|
||||
throw Error('Could not locate Firefox on the current system')
|
||||
})
|
||||
return this.found_
|
||||
}
|
||||
|
||||
/** @return {!Promise<string>} */
|
||||
[Symbols.serialize]() {
|
||||
return this.locate()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Firefox's developer channel.
|
||||
* @const
|
||||
* @see <https://www.mozilla.org/en-US/firefox/channel/desktop/#aurora>
|
||||
*/
|
||||
Channel.AURORA = new Channel(
|
||||
'/Applications/FirefoxDeveloperEdition.app/Contents/MacOS/firefox-bin',
|
||||
'Firefox Developer Edition\\firefox.exe'
|
||||
)
|
||||
|
||||
/**
|
||||
* Firefox's beta channel. Note this is provided mainly for convenience as
|
||||
* the beta channel has the same installation location as the main release
|
||||
* channel.
|
||||
* @const
|
||||
* @see <https://www.mozilla.org/en-US/firefox/channel/desktop/#beta>
|
||||
*/
|
||||
Channel.BETA = new Channel(
|
||||
'/Applications/Firefox.app/Contents/MacOS/firefox-bin',
|
||||
'Mozilla Firefox\\firefox.exe'
|
||||
)
|
||||
|
||||
/**
|
||||
* Firefox's release channel.
|
||||
* @const
|
||||
* @see <https://www.mozilla.org/en-US/firefox/desktop/>
|
||||
*/
|
||||
Channel.RELEASE = new Channel(
|
||||
'/Applications/Firefox.app/Contents/MacOS/firefox-bin',
|
||||
'Mozilla Firefox\\firefox.exe'
|
||||
)
|
||||
|
||||
/**
|
||||
* Firefox's nightly release channel.
|
||||
* @const
|
||||
* @see <https://www.mozilla.org/en-US/firefox/channel/desktop/#nightly>
|
||||
*/
|
||||
Channel.NIGHTLY = new Channel(
|
||||
'/Applications/Firefox Nightly.app/Contents/MacOS/firefox-bin',
|
||||
'Nightly\\firefox.exe'
|
||||
)
|
||||
|
||||
// PUBLIC API
|
||||
|
||||
module.exports = {
|
||||
Channel,
|
||||
Context,
|
||||
Driver,
|
||||
Options,
|
||||
ServiceBuilder,
|
||||
locateSynchronously,
|
||||
}
|
||||
Reference in New Issue
Block a user