# chrome.js

    1// Copyright 2013 Selenium committers2// Copyright 2013 Software Freedom Conservancy3//4// Licensed under the Apache License, Version 2.0 (the "License");5// you may not use this file except in compliance with the License.6//     You may obtain a copy of the License at7//8// http://www.apache.org/licenses/LICENSE-2.09//10// Unless required by applicable law or agreed to in writing, software11// distributed under the License is distributed on an "AS IS" BASIS,12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.13// See the License for the specific language governing permissions and14// limitations under the License.1516'use strict';1718var fs = require('fs'),19    util = require('util');2021var webdriver = require('./index'),22    executors = require('./executors'),23    io = require('./io'),24    portprober = require('./net/portprober'),25    remote = require('./remote');262728/**29 * Name of the ChromeDriver executable.30 * @type {string}31 * @const32 */33var CHROMEDRIVER_EXE =34    process.platform === 'win32' ? 'chromedriver.exe' : 'chromedriver';353637/**38 * Creates {@link remote.DriverService} instances that manage a ChromeDriver39 * server.40 * @param {string=} opt_exe Path to the server executable to use. If omitted,41 *     the builder will attempt to locate the chromedriver on the current42 *     PATH.43 * @throws {Error} If provided executable does not exist, or the chromedriver44 *     cannot be found on the PATH.45 * @constructor46 */47var ServiceBuilder = function(opt_exe) {48  /** @private {string} */49  this.exe_ = opt_exe || io.findInPath(CHROMEDRIVER_EXE, true);50  if (!this.exe_) {51    throw Error(52        'The ChromeDriver could not be found on the current PATH. Please ' +53        'download the latest version of the ChromeDriver from ' +54        'http://chromedriver.storage.googleapis.com/index.html and ensure ' +55        'it can be found on your PATH.');56  }5758  if (!fs.existsSync(this.exe_)) {59    throw Error('File does not exist: ' + this.exe_);60  }6162  /** @private {!Array.<string>} */63  this.args_ = [];64  this.stdio_ = 'ignore';65};666768/** @private {number} */69ServiceBuilder.prototype.port_ = 0;707172/** @private {(string|!Array.<string|number|!Stream|null|undefined>)} */73ServiceBuilder.prototype.stdio_ = 'ignore';747576/** @private {Object.<string, string>} */77ServiceBuilder.prototype.env_ = null;787980/**81 * Sets the port to start the ChromeDriver on.82 * @param {number} port The port to use, or 0 for any free port.83 * @return {!ServiceBuilder} A self reference.84 * @throws {Error} If the port is invalid.85 */86ServiceBuilder.prototype.usingPort = function(port) {87  if (port < 0) {88    throw Error('port must be >= 0: ' + port);89  }90  this.port_ = port;91  return this;92};939495/**96 * Sets the path of the log file the driver should log to. If a log file is97 * not specified, the driver will log to stderr.98 * @param {string} path Path of the log file to use.99 * @return {!ServiceBuilder} A self reference.100 */101ServiceBuilder.prototype.loggingTo = function(path) {102  this.args_.push('--log-path=' + path);103  return this;104};105106107/**108 * Enables verbose logging.109 * @return {!ServiceBuilder} A self reference.110 */111ServiceBuilder.prototype.enableVerboseLogging = function() {112  this.args_.push('--verbose');113  return this;114};115116117/**118 * Sets the number of threads the driver should use to manage HTTP requests.119 * By default, the driver will use 4 threads.120 * @param {number} n The number of threads to use.121 * @return {!ServiceBuilder} A self reference.122 */123ServiceBuilder.prototype.setNumHttpThreads = function(n) {124  this.args_.push('--http-threads=' + n);125  return this;126};127128129/**130 * Sets the base path for WebDriver REST commands (e.g. "/wd/hub").131 * By default, the driver will accept commands relative to "/".132 * @param {string} path The base path to use.133 * @return {!ServiceBuilder} A self reference.134 */135ServiceBuilder.prototype.setUrlBasePath = function(path) {136  this.args_.push('--url-base=' + path);137  return this;138};139140141/**142 * Defines the stdio configuration for the driver service. See143 * {@code child_process.spawn} for more information.144 * @param {(string|!Array.<string|number|!Stream|null|undefined>)} config The145 *     configuration to use.146 * @return {!ServiceBuilder} A self reference.147 */148ServiceBuilder.prototype.setStdio = function(config) {149  this.stdio_ = config;150  return this;151};152153154/**155 * Defines the environment to start the server under. This settings will be156 * inherited by every browser session started by the server.157 * @param {!Object.<string, string>} env The environment to use.158 * @return {!ServiceBuilder} A self reference.159 */160ServiceBuilder.prototype.withEnvironment = function(env) {161  this.env_ = env;162  return this;163};164165166/**167 * Creates a new DriverService using this instance's current configuration.168 * @return {remote.DriverService} A new driver service using this instance's169 *     current configuration.170 * @throws {Error} If the driver exectuable was not specified and a default171 *     could not be found on the current PATH.172 */173ServiceBuilder.prototype.build = function() {174  var port = this.port_ || portprober.findFreePort();175  var args = this.args_.concat();  // Defensive copy.176177  return new remote.DriverService(this.exe_, {178    port: port,179    args: webdriver.promise.when(port, function(port) {180      return args.concat('--port=' + port);181    }),182    env: this.env_,183    stdio: this.stdio_184  });185};186187188/** @type {remote.DriverService} */189var defaultService = null;190191192/**193 * Sets the default service to use for new ChromeDriver instances.194 * @param {!remote.DriverService} service The service to use.195 * @throws {Error} If the default service is currently running.196 */197function setDefaultService(service) {198  if (defaultService && defaultService.isRunning()) {199    throw Error(200        'The previously configured ChromeDriver service is still running. ' +201        'You must shut it down before you may adjust its configuration.');202  }203  defaultService = service;204}205206207/**208 * Returns the default ChromeDriver service. If such a service has not been209 * configured, one will be constructed using the default configuration for210 * a ChromeDriver executable found on the system PATH.211 * @return {!remote.DriverService} The default ChromeDriver service.212 */213function getDefaultService() {214  if (!defaultService) {215    defaultService = new ServiceBuilder().build();216  }217  return defaultService;218}219220221/**222 * @type {string}223 * @const224 */225var OPTIONS_CAPABILITY_KEY = 'chromeOptions';226227228/**229 * Class for managing ChromeDriver specific options.230 * @constructor231 */232var Options = function() {233  /** @private {!Array.<string>} */234  this.args_ = [];235236  /** @private {!Array.<(string|!Buffer)>} */237  this.extensions_ = [];238};239240241/**242 * Extracts the ChromeDriver specific options from the given capabilities243 * object.244 * @param {!webdriver.Capabilities} capabilities The capabilities object.245 * @return {!Options} The ChromeDriver options.246 */247Options.fromCapabilities = function(capabilities) {248  var options = new Options();249250  var o = capabilities.get(OPTIONS_CAPABILITY_KEY);251  if (o instanceof Options) {252    options = o;253  } else if (o) {254    options.255        addArguments(o.args || []).256        addExtensions(o.extensions || []).257        detachDriver(!!o.detach).258        setChromeBinaryPath(o.binary).259        setChromeLogFile(o.logFile).260        setLocalState(o.localState).261        setUserPreferences(o.prefs);262  }263264  if (capabilities.has(webdriver.Capability.PROXY)) {265    options.setProxy(capabilities.get(webdriver.Capability.PROXY));266  }267268  if (capabilities.has(webdriver.Capability.LOGGING_PREFS)) {269    options.setLoggingPreferences(270        capabilities.get(webdriver.Capability.LOGGING_PREFS));271  }272273  return options;274};275276277/**278 * Add additional command line arguments to use when launching the Chrome279 * browser.  Each argument may be specified with or without the "--" prefix280 * (e.g. "--foo" and "foo"). Arguments with an associated value should be281 * delimited by an "=": "foo=bar".282 * @param {...(string|!Array.<string>)} var_args The arguments to add.283 * @return {!Options} A self reference.284 */285Options.prototype.addArguments = function(var_args) {286  this.args_ = this.args_.concat.apply(this.args_, arguments);287  return this;288};289290291/**292 * Add additional extensions to install when launching Chrome. Each extension293 * should be specified as the path to the packed CRX file, or a Buffer for an294 * extension.295 * @param {...(string|!Buffer|!Array.<(string|!Buffer)>)} var_args The296 *     extensions to add.297 * @return {!Options} A self reference.298 */299Options.prototype.addExtensions = function(var_args) {300  this.extensions_ = this.extensions_.concat.apply(301      this.extensions_, arguments);302  return this;303};304305306/**307 * Sets the path to the Chrome binary to use. On Mac OS X, this path should308 * reference the actual Chrome executable, not just the application binary309 * (e.g. "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome").310 *311 * The binary path be absolute or relative to the chromedriver server312 * executable, but it must exist on the machine that will launch Chrome.313 *314 * @param {string} path The path to the Chrome binary to use.315 * @return {!Options} A self reference.316 */317Options.prototype.setChromeBinaryPath = function(path) {318  this.binary_ = path;319  return this;320};321322323/**324 * Sets whether to leave the started Chrome browser running if the controlling325 * ChromeDriver service is killed before {@link webdriver.WebDriver#quit()} is326 * called.327 * @param {boolean} detach Whether to leave the browser running if the328 *     chromedriver service is killed before the session.329 * @return {!Options} A self reference.330 */331Options.prototype.detachDriver = function(detach) {332  this.detach_ = detach;333  return this;334};335336337/**338 * Sets the user preferences for Chrome's user profile. See the "Preferences"339 * file in Chrome's user data directory for examples.340 * @param {!Object} prefs Dictionary of user preferences to use.341 * @return {!Options} A self reference.342 */343Options.prototype.setUserPreferences = function(prefs) {344  this.prefs_ = prefs;345  return this;346};347348349/**350 * Sets the logging preferences for the new session.351 * @param {!webdriver.logging.Preferences} prefs The logging preferences.352 * @return {!Options} A self reference.353 */354Options.prototype.setLoggingPreferences = function(prefs) {355  this.logPrefs_ = prefs;356  return this;357};358359360/**361 * Sets preferences for the "Local State" file in Chrome's user data362 * directory.363 * @param {!Object} state Dictionary of local state preferences.364 * @return {!Options} A self reference.365 */366Options.prototype.setLocalState = function(state) {367  this.localState_ = state;368  return this;369};370371372/**373 * Sets the path to Chrome's log file. This path should exist on the machine374 * that will launch Chrome.375 * @param {string} path Path to the log file to use.376 * @return {!Options} A self reference.377 */378Options.prototype.setChromeLogFile = function(path) {379  this.logFile_ = path;380  return this;381};382383384/**385 * Sets the proxy settings for the new session.386 * @param {ProxyConfig} proxy The proxy configuration to use.387 * @return {!Options} A self reference.388 */389Options.prototype.setProxy = function(proxy) {390  this.proxy_ = proxy;391  return this;392};393394395/**396 * Converts this options instance to a {@link webdriver.Capabilities} object.397 * @param {webdriver.Capabilities=} opt_capabilities The capabilities to merge398 *     these options into, if any.399 * @return {!webdriver.Capabilities} The capabilities.400 */401Options.prototype.toCapabilities = function(opt_capabilities) {402  var capabilities = opt_capabilities || webdriver.Capabilities.chrome();403  capabilities.404      set(webdriver.Capability.PROXY, this.proxy_).405      set(webdriver.Capability.LOGGING_PREFS, this.logPrefs_).406      set(OPTIONS_CAPABILITY_KEY, this);407  return capabilities;408};409410411/**412 * Converts this instance to its JSON wire protocol representation. Note this413 * function is an implementation not intended for general use.414 * @return {{args: !Array.<string>,415 *           binary: (string|undefined),416 *           detach: boolean,417 *           extensions: !Array.<string>,418 *           localState: (Object|undefined),419 *           logFile: (string|undefined),420 *           prefs: (Object|undefined)}} The JSON wire protocol representation421 *     of this instance.422 */423Options.prototype.toJSON = function() {424  return {425    args: this.args_,426    binary: this.binary_,427    detach: !!this.detach_,428    extensions: this.extensions_.map(function(extension) {429      if (Buffer.isBuffer(extension)) {430        return extension.toString('base64');431      }432      return fs.readFileSync(extension, 'base64');433    }),434    localState: this.localState_,435    logFile: this.logFile_,436    prefs: this.prefs_437  };438};439440441/**442 * Creates a new ChromeDriver session.443 * @param {(webdriver.Capabilities|Options)=} opt_options The session options.444 * @param {remote.DriverService=} opt_service The session to use; will use445 *     the {@link getDefaultService default service} by default.446 * @return {!webdriver.WebDriver} A new WebDriver instance.447 */448function createDriver(opt_options, opt_service) {449  var service = opt_service || getDefaultService();450  var executor = executors.createExecutor(service.start());451452  var options = opt_options || new Options();453  if (opt_options instanceof webdriver.Capabilities) {454    // Extract the Chrome-specific options so we do not send unnecessary455    // data across the wire.456    options = Options.fromCapabilities(options);457  }458459  return webdriver.WebDriver.createSession(460      executor, options.toCapabilities());461}462463464465// PUBLIC API466467468exports.ServiceBuilder = ServiceBuilder;469exports.Options = Options;470exports.createDriver = createDriver;471exports.getDefaultService = getDefaultService;472exports.setDefaultService = setDefaultService;