You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
333 lines
10 KiB
333 lines
10 KiB
// Copyright 2013 Software Freedom Conservancy
|
|
//
|
|
// Licensed 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.
|
|
|
|
'use strict';
|
|
|
|
var spawn = require('child_process').spawn,
|
|
os = require('os'),
|
|
path = require('path'),
|
|
url = require('url'),
|
|
util = require('util');
|
|
|
|
var promise = require('../').promise,
|
|
httpUtil = require('../http/util'),
|
|
net = require('../net'),
|
|
portprober = require('../net/portprober');
|
|
|
|
|
|
|
|
/**
|
|
* Configuration options for a DriverService instance.
|
|
* <ul>
|
|
* <li>
|
|
* <li>{@code loopback} - Whether the service should only be accessed on this
|
|
* host's loopback address.
|
|
* <li>{@code port} - The port to start the server on (must be > 0). If the
|
|
* port is provided as a promise, the service will wait for the promise to
|
|
* resolve before starting.
|
|
* <li>{@code args} - The arguments to pass to the service. If a promise is
|
|
* provided, the service will wait for it to resolve before starting.
|
|
* <li>{@code path} - The base path on the server for the WebDriver wire
|
|
* protocol (e.g. '/wd/hub'). Defaults to '/'.
|
|
* <li>{@code env} - The environment variables that should be visible to the
|
|
* server process. Defaults to inheriting the current process's
|
|
* environment.
|
|
* <li>{@code stdio} - IO configuration for the spawned server process. For
|
|
* more information, refer to the documentation of
|
|
* {@code child_process.spawn}.
|
|
* </ul>
|
|
*
|
|
* @typedef {{
|
|
* port: (number|!webdriver.promise.Promise.<number>),
|
|
* args: !(Array.<string>|webdriver.promise.Promise.<!Array.<string>>),
|
|
* path: (string|undefined),
|
|
* env: (!Object.<string, string>|undefined),
|
|
* stdio: (string|!Array.<string|number|!Stream|null|undefined>|undefined)
|
|
* }}
|
|
*/
|
|
var ServiceOptions;
|
|
|
|
|
|
/**
|
|
* Manages the life and death of a native executable WebDriver server.
|
|
*
|
|
* <p>It is expected that the driver server implements the
|
|
* <a href="http://code.google.com/p/selenium/wiki/JsonWireProtocol">WebDriver
|
|
* Wire Protocol</a>. Furthermore, the managed server should support multiple
|
|
* concurrent sessions, so that this class may be reused for multiple clients.
|
|
*
|
|
* @param {string} executable Path to the executable to run.
|
|
* @param {!ServiceOptions} options Configuration options for the service.
|
|
* @constructor
|
|
*/
|
|
function DriverService(executable, options) {
|
|
|
|
/** @private {string} */
|
|
this.executable_ = executable;
|
|
|
|
/** @private {boolean} */
|
|
this.loopbackOnly_ = !!options.loopback;
|
|
|
|
/** @private {(number|!webdriver.promise.Promise.<number>)} */
|
|
this.port_ = options.port;
|
|
|
|
/**
|
|
* @private {!(Array.<string>|webdriver.promise.Promise.<!Array.<string>>)}
|
|
*/
|
|
this.args_ = options.args;
|
|
|
|
/** @private {string} */
|
|
this.path_ = options.path || '/';
|
|
|
|
/** @private {!Object.<string, string>} */
|
|
this.env_ = options.env || process.env;
|
|
|
|
/** @private {(string|!Array.<string|number|!Stream|null|undefined>)} */
|
|
this.stdio_ = options.stdio || 'ignore';
|
|
}
|
|
|
|
|
|
/**
|
|
* The default amount of time, in milliseconds, to wait for the server to
|
|
* start.
|
|
* @type {number}
|
|
*/
|
|
DriverService.DEFAULT_START_TIMEOUT_MS = 30 * 1000;
|
|
|
|
|
|
/** @private {child_process.ChildProcess} */
|
|
DriverService.prototype.process_ = null;
|
|
|
|
|
|
/**
|
|
* Promise that resolves to the server's address or null if the server has not
|
|
* been started.
|
|
* @private {webdriver.promise.Promise.<string>}
|
|
*/
|
|
DriverService.prototype.address_ = null;
|
|
|
|
|
|
/**
|
|
* Promise that tracks the status of shutting down the server, or null if the
|
|
* server is not currently shutting down.
|
|
* @private {webdriver.promise.Promise}
|
|
*/
|
|
DriverService.prototype.shutdownHook_ = null;
|
|
|
|
|
|
/**
|
|
* @return {!webdriver.promise.Promise.<string>} A promise that resolves to
|
|
* the server's address.
|
|
* @throws {Error} If the server has not been started.
|
|
*/
|
|
DriverService.prototype.address = function() {
|
|
if (this.address_) {
|
|
return this.address_;
|
|
}
|
|
throw Error('Server has not been started.');
|
|
};
|
|
|
|
|
|
/**
|
|
* @return {boolean} Whether the underlying service process is running.
|
|
*/
|
|
DriverService.prototype.isRunning = function() {
|
|
return !!this.address_;
|
|
};
|
|
|
|
|
|
/**
|
|
* Starts the server if it is not already running.
|
|
* @param {number=} opt_timeoutMs How long to wait, in milliseconds, for the
|
|
* server to start accepting requests. Defaults to 30 seconds.
|
|
* @return {!webdriver.promise.Promise.<string>} A promise that will resolve
|
|
* to the server's base URL when it has started accepting requests. If the
|
|
* timeout expires before the server has started, the promise will be
|
|
* rejected.
|
|
*/
|
|
DriverService.prototype.start = function(opt_timeoutMs) {
|
|
if (this.address_) {
|
|
return this.address_;
|
|
}
|
|
|
|
var timeout = opt_timeoutMs || DriverService.DEFAULT_START_TIMEOUT_MS;
|
|
|
|
var self = this;
|
|
this.address_ = promise.defer();
|
|
this.address_.fulfill(promise.when(this.port_, function(port) {
|
|
if (port <= 0) {
|
|
throw Error('Port must be > 0: ' + port);
|
|
}
|
|
return promise.when(self.args_, function(args) {
|
|
self.process_ = spawn(self.executable_, args, {
|
|
env: self.env_,
|
|
stdio: self.stdio_
|
|
}).once('exit', onServerExit);
|
|
|
|
// This process should not wait on the spawned child, however, we do
|
|
// want to ensure the child is killed when this process exits.
|
|
self.process_.unref();
|
|
process.once('exit', killServer);
|
|
|
|
var serverUrl = url.format({
|
|
protocol: 'http',
|
|
hostname: !self.loopbackOnly_ && net.getAddress() ||
|
|
net.getLoopbackAddress(),
|
|
port: port,
|
|
pathname: self.path_
|
|
});
|
|
|
|
return httpUtil.waitForServer(serverUrl, timeout).then(function() {
|
|
return serverUrl;
|
|
});
|
|
});
|
|
}));
|
|
|
|
return this.address_;
|
|
|
|
function onServerExit(code, signal) {
|
|
self.address_.reject(code == null ?
|
|
Error('Server was killed with ' + signal) :
|
|
Error('Server exited with ' + code));
|
|
|
|
if (self.shutdownHook_) {
|
|
self.shutdownHook_.fulfill();
|
|
}
|
|
|
|
self.shutdownHook_ = null;
|
|
self.address_ = null;
|
|
self.process_ = null;
|
|
process.removeListener('exit', killServer);
|
|
}
|
|
|
|
function killServer() {
|
|
process.removeListener('exit', killServer);
|
|
self.process_ && self.process_.kill('SIGTERM');
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Stops the service if it is not currently running. This function will kill
|
|
* the server immediately. To synchronize with the active control flow, use
|
|
* {@link #stop()}.
|
|
* @return {!webdriver.promise.Promise} A promise that will be resolved when
|
|
* the server has been stopped.
|
|
*/
|
|
DriverService.prototype.kill = function() {
|
|
if (!this.address_) {
|
|
return promise.fulfilled(); // Not currently running.
|
|
}
|
|
|
|
if (!this.shutdownHook_) {
|
|
// No process: still starting; wait on address.
|
|
// Otherwise, kill the process now. Exit handler will resolve the
|
|
// shutdown hook.
|
|
if (this.process_) {
|
|
this.shutdownHook_ = promise.defer();
|
|
this.process_.kill('SIGTERM');
|
|
} else {
|
|
var self = this;
|
|
this.shutdownHook_ = this.address_.thenFinally(function() {
|
|
self.process_ && self.process_.kill('SIGTERM');
|
|
});
|
|
}
|
|
}
|
|
|
|
return this.shutdownHook_;
|
|
};
|
|
|
|
|
|
/**
|
|
* Schedules a task in the current control flow to stop the server if it is
|
|
* currently running.
|
|
* @return {!webdriver.promise.Promise} A promise that will be resolved when
|
|
* the server has been stopped.
|
|
*/
|
|
DriverService.prototype.stop = function() {
|
|
return promise.controlFlow().execute(this.kill.bind(this));
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
* Manages the life and death of the Selenium standalone server. The server
|
|
* may be obtained from http://selenium-release.storage.googleapis.com/index.html.
|
|
* @param {string} jar Path to the Selenium server jar.
|
|
* @param {!SeleniumServer.Options} options Configuration options for the
|
|
* server.
|
|
* @throws {Error} If an invalid port is specified.
|
|
* @constructor
|
|
* @extends {DriverService}
|
|
*/
|
|
function SeleniumServer(jar, options) {
|
|
if (options.port < 0)
|
|
throw Error('Port must be >= 0: ' + options.port);
|
|
|
|
var port = options.port || portprober.findFreePort();
|
|
var args = promise.when(options.jvmArgs || [], function(jvmArgs) {
|
|
return promise.when(options.args || [], function(args) {
|
|
return promise.when(port, function(port) {
|
|
return jvmArgs.concat(['-jar', jar, '-port', port]).concat(args);
|
|
});
|
|
});
|
|
});
|
|
|
|
DriverService.call(this, 'java', {
|
|
port: port,
|
|
args: args,
|
|
path: '/wd/hub',
|
|
env: options.env,
|
|
stdio: options.stdio
|
|
});
|
|
}
|
|
util.inherits(SeleniumServer, DriverService);
|
|
|
|
|
|
/**
|
|
* Options for the Selenium server:
|
|
* <ul>
|
|
* <li>{@code port} - The port to start the server on (must be > 0). If the
|
|
* port is provided as a promise, the service will wait for the promise to
|
|
* resolve before starting.
|
|
* <li>{@code args} - The arguments to pass to the service. If a promise is
|
|
* provided, the service will wait for it to resolve before starting.
|
|
* <li>{@code jvmArgs} - The arguments to pass to the JVM. If a promise is
|
|
* provided, the service will wait for it to resolve before starting.
|
|
* <li>{@code env} - The environment variables that should be visible to the
|
|
* server process. Defaults to inheriting the current process's
|
|
* environment.
|
|
* <li>{@code stdio} - IO configuration for the spawned server process. For
|
|
* more information, refer to the documentation of
|
|
* {@code child_process.spawn}.
|
|
* </ul>
|
|
*
|
|
* @typedef {{
|
|
* port: (number|!webdriver.promise.Promise.<number>),
|
|
* args: !(Array.<string>|webdriver.promise.Promise.<!Array.<string>>),
|
|
* jvmArgs: (!Array.<string>|
|
|
* !webdriver.promise.Promise.<!Array.<string>>|
|
|
* undefined),
|
|
* env: (!Object.<string, string>|undefined),
|
|
* stdio: (string|!Array.<string|number|!Stream|null|undefined>|undefined)
|
|
* }}
|
|
*/
|
|
SeleniumServer.Options;
|
|
|
|
|
|
// PUBLIC API
|
|
|
|
exports.DriverService = DriverService;
|
|
exports.SeleniumServer = SeleniumServer;
|