# 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    loopback: true,179    port: port,180    args: webdriver.promise.when(port, function(port) {181      return args.concat('--port=' + port);182    }),183    env: this.env_,184    stdio: this.stdio_185  });186};187188189/** @type {remote.DriverService} */190var defaultService = null;191192193/**194 * Sets the default service to use for new ChromeDriver instances.195 * @param {!remote.DriverService} service The service to use.196 * @throws {Error} If the default service is currently running.197 */198function setDefaultService(service) {199  if (defaultService && defaultService.isRunning()) {200    throw Error(201        'The previously configured ChromeDriver service is still running. ' +202        'You must shut it down before you may adjust its configuration.');203  }204  defaultService = service;205}206207208/**209 * Returns the default ChromeDriver service. If such a service has not been210 * configured, one will be constructed using the default configuration for211 * a ChromeDriver executable found on the system PATH.212 * @return {!remote.DriverService} The default ChromeDriver service.213 */214function getDefaultService() {215  if (!defaultService) {216    defaultService = new ServiceBuilder().build();217  }218  return defaultService;219}220221222/**223 * @type {string}224 * @const225 */226var OPTIONS_CAPABILITY_KEY = 'chromeOptions';227228229/**230 * Class for managing ChromeDriver specific options.231 * @constructor232 */233var Options = function() {234  /** @private {!Array.<string>} */235  this.args_ = [];236237  /** @private {!Array.<(string|!Buffer)>} */238  this.extensions_ = [];239};240241242/**243 * Extracts the ChromeDriver specific options from the given capabilities244 * object.245 * @param {!webdriver.Capabilities} capabilities The capabilities object.246 * @return {!Options} The ChromeDriver options.247 */248Options.fromCapabilities = function(capabilities) {249  var options = new Options();250251  var o = capabilities.get(OPTIONS_CAPABILITY_KEY);252  if (o instanceof Options) {253    options = o;254  } else if (o) {255    options.256        addArguments(o.args || []).257        addExtensions(o.extensions || []).258        detachDriver(!!o.detach).259        setChromeBinaryPath(o.binary).260        setChromeLogFile(o.logFile).261        setLocalState(o.localState).262        setUserPreferences(o.prefs);263  }264265  if (capabilities.has(webdriver.Capability.PROXY)) {266    options.setProxy(capabilities.get(webdriver.Capability.PROXY));267  }268269  if (capabilities.has(webdriver.Capability.LOGGING_PREFS)) {270    options.setLoggingPreferences(271        capabilities.get(webdriver.Capability.LOGGING_PREFS));272  }273274  return options;275};276277278/**279 * Add additional command line arguments to use when launching the Chrome280 * browser.  Each argument may be specified with or without the "--" prefix281 * (e.g. "--foo" and "foo"). Arguments with an associated value should be282 * delimited by an "=": "foo=bar".283 * @param {...(string|!Array.<string>)} var_args The arguments to add.284 * @return {!Options} A self reference.285 */286Options.prototype.addArguments = function(var_args) {287  this.args_ = this.args_.concat.apply(this.args_, arguments);288  return this;289};290291292/**293 * Add additional extensions to install when launching Chrome. Each extension294 * should be specified as the path to the packed CRX file, or a Buffer for an295 * extension.296 * @param {...(string|!Buffer|!Array.<(string|!Buffer)>)} var_args The297 *     extensions to add.298 * @return {!Options} A self reference.299 */300Options.prototype.addExtensions = function(var_args) {301  this.extensions_ = this.extensions_.concat.apply(302      this.extensions_, arguments);303  return this;304};305306307/**308 * Sets the path to the Chrome binary to use. On Mac OS X, this path should309 * reference the actual Chrome executable, not just the application binary310 * (e.g. "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome").311 *312 * The binary path be absolute or relative to the chromedriver server313 * executable, but it must exist on the machine that will launch Chrome.314 *315 * @param {string} path The path to the Chrome binary to use.316 * @return {!Options} A self reference.317 */318Options.prototype.setChromeBinaryPath = function(path) {319  this.binary_ = path;320  return this;321};322323324/**325 * Sets whether to leave the started Chrome browser running if the controlling326 * ChromeDriver service is killed before {@link webdriver.WebDriver#quit()} is327 * called.328 * @param {boolean} detach Whether to leave the browser running if the329 *     chromedriver service is killed before the session.330 * @return {!Options} A self reference.331 */332Options.prototype.detachDriver = function(detach) {333  this.detach_ = detach;334  return this;335};336337338/**339 * Sets the user preferences for Chrome's user profile. See the "Preferences"340 * file in Chrome's user data directory for examples.341 * @param {!Object} prefs Dictionary of user preferences to use.342 * @return {!Options} A self reference.343 */344Options.prototype.setUserPreferences = function(prefs) {345  this.prefs_ = prefs;346  return this;347};348349350/**351 * Sets the logging preferences for the new session.352 * @param {!webdriver.logging.Preferences} prefs The logging preferences.353 * @return {!Options} A self reference.354 */355Options.prototype.setLoggingPreferences = function(prefs) {356  this.logPrefs_ = prefs;357  return this;358};359360361/**362 * Sets preferences for the "Local State" file in Chrome's user data363 * directory.364 * @param {!Object} state Dictionary of local state preferences.365 * @return {!Options} A self reference.366 */367Options.prototype.setLocalState = function(state) {368  this.localState_ = state;369  return this;370};371372373/**374 * Sets the path to Chrome's log file. This path should exist on the machine375 * that will launch Chrome.376 * @param {string} path Path to the log file to use.377 * @return {!Options} A self reference.378 */379Options.prototype.setChromeLogFile = function(path) {380  this.logFile_ = path;381  return this;382};383384385/**386 * Sets the proxy settings for the new session.387 * @param {ProxyConfig} proxy The proxy configuration to use.388 * @return {!Options} A self reference.389 */390Options.prototype.setProxy = function(proxy) {391  this.proxy_ = proxy;392  return this;393};394395396/**397 * Converts this options instance to a {@link webdriver.Capabilities} object.398 * @param {webdriver.Capabilities=} opt_capabilities The capabilities to merge399 *     these options into, if any.400 * @return {!webdriver.Capabilities} The capabilities.401 */402Options.prototype.toCapabilities = function(opt_capabilities) {403  var capabilities = opt_capabilities || webdriver.Capabilities.chrome();404  capabilities.405      set(webdriver.Capability.PROXY, this.proxy_).406      set(webdriver.Capability.LOGGING_PREFS, this.logPrefs_).407      set(OPTIONS_CAPABILITY_KEY, this);408  return capabilities;409};410411412/**413 * Converts this instance to its JSON wire protocol representation. Note this414 * function is an implementation not intended for general use.415 * @return {{args: !Array.<string>,416 *           binary: (string|undefined),417 *           detach: boolean,418 *           extensions: !Array.<string>,419 *           localState: (Object|undefined),420 *           logFile: (string|undefined),421 *           prefs: (Object|undefined)}} The JSON wire protocol representation422 *     of this instance.423 */424Options.prototype.toJSON = function() {425  return {426    args: this.args_,427    binary: this.binary_,428    detach: !!this.detach_,429    extensions: this.extensions_.map(function(extension) {430      if (Buffer.isBuffer(extension)) {431        return extension.toString('base64');432      }433      return fs.readFileSync(extension, 'base64');434    }),435    localState: this.localState_,436    logFile: this.logFile_,437    prefs: this.prefs_438  };439};440441442/**443 * Creates a new ChromeDriver session.444 * @param {(webdriver.Capabilities|Options)=} opt_options The session options.445 * @param {remote.DriverService=} opt_service The session to use; will use446 *     the {@link getDefaultService default service} by default.447 * @return {!webdriver.WebDriver} A new WebDriver instance.448 */449function createDriver(opt_options, opt_service) {450  var service = opt_service || getDefaultService();451  var executor = executors.createExecutor(service.start());452453  var options = opt_options || new Options();454  if (opt_options instanceof webdriver.Capabilities) {455    // Extract the Chrome-specific options so we do not send unnecessary456    // data across the wire.457    options = Options.fromCapabilities(options);458  }459460  return webdriver.WebDriver.createSession(461      executor, options.toCapabilities());462}463464465466// PUBLIC API467468469exports.ServiceBuilder = ServiceBuilder;470exports.Options = Options;471exports.createDriver = createDriver;472exports.getDefaultService = getDefaultService;473exports.setDefaultService = setDefaultService;