зеркало из https://github.com/microsoft/appium.git
use appium-chromedriver package for chrome/chromedriver
This commit is contained in:
Родитель
6c78716d96
Коммит
a7b85ecd84
|
@ -3,14 +3,20 @@
|
|||
var logger = require('../../server/logger.js').get('appium')
|
||||
, _ = require('underscore')
|
||||
, errors = require('../../server/errors.js')
|
||||
, path = require('path')
|
||||
, system = require('appium-support').system
|
||||
, isWindows = system.isWindows()
|
||||
, isLinux = system.isLinux()
|
||||
, exec = require('child_process').exec
|
||||
, UnknownError = errors.UnknownError
|
||||
, async = require('async')
|
||||
, Chromedriver = require('./chromedriver.js')
|
||||
, Chromedriver = require('appium-chromedriver').default
|
||||
, status = require("../../server/status.js");
|
||||
|
||||
var androidHybrid = {};
|
||||
|
||||
androidHybrid.chromedriver = null;
|
||||
androidHybrid.chromedriverStopCbs = {};
|
||||
androidHybrid.sessionChromedrivers = {};
|
||||
|
||||
androidHybrid.listWebviews = function (cb) {
|
||||
|
@ -65,17 +71,12 @@ androidHybrid.listWebviews = function (cb) {
|
|||
|
||||
var previousState = {};
|
||||
|
||||
// remember whether we were previously proxying to a chromedriver or not
|
||||
androidHybrid.rememberProxyState = function () {
|
||||
previousState.proxyHost = this.proxyHost;
|
||||
previousState.proxyPort = this.proxyPort;
|
||||
previousState.proxySessionId = this.proxySessionId;
|
||||
previousState.isProxy = this.isProxy;
|
||||
};
|
||||
|
||||
androidHybrid.restoreProxyState = function () {
|
||||
this.proxyHost = previousState.proxyHost;
|
||||
this.proxyPort = previousState.proxyPort;
|
||||
this.proxySessionId = previousState.proxySessionId;
|
||||
this.isProxy = previousState.isProxy;
|
||||
};
|
||||
|
||||
|
@ -118,101 +119,136 @@ androidHybrid.getProcessNameFromWebview = function (webview, cb) {
|
|||
};
|
||||
|
||||
androidHybrid.startChromedriverProxy = function (context, cb) {
|
||||
cb = _.once(cb);
|
||||
logger.debug("Connecting to chrome-backed webview");
|
||||
if (this.chromedriver !== null) {
|
||||
return cb(new Error("We already have a chromedriver instance running"));
|
||||
}
|
||||
|
||||
var setupNewChromeDriver = function (ocb) {
|
||||
var chromeArgs = {
|
||||
port: this.args.chromeDriverPort
|
||||
, executable: this.args.chromedriverExecutable
|
||||
, deviceId: this.adb.curDeviceId
|
||||
, enablePerformanceLogging: this.args.enablePerformanceLogging
|
||||
};
|
||||
this.chromedriver = new Chromedriver(chromeArgs, this.onChromedriverExit.bind(this));
|
||||
this.rememberProxyState();
|
||||
this.proxyHost = this.chromedriver.proxyHost;
|
||||
this.proxyPort = this.chromedriver.proxyPort;
|
||||
this.isProxy = true;
|
||||
var caps = {
|
||||
chromeOptions: {
|
||||
androidPackage: this.args.appPackage,
|
||||
androidUseRunningApp: true
|
||||
}
|
||||
};
|
||||
// For now the only known arg passed this way is androidDeviceSocked used by Operadriver (deriving from Chromedriver)
|
||||
// We don't know how other Chromium embedders will call this argument so for now it's name needs to be configurable
|
||||
// When Google adds the androidDeviceSocket argument to the original Chromedriver then we will be sure about it's name
|
||||
// for all Chromium embedders (as their Webdrivers will derive from Chromedriver)
|
||||
if (this.args.specialChromedriverSessionArgs) {
|
||||
_.each(this.args.specialChromedriverSessionArgs, function (val, option) {
|
||||
logger.debug("This method is being deprecated. Apply chromeOptions normally to pass along options," +
|
||||
"see sites.google.com/a/chromium.org/chromedriver/capabilities for more info");
|
||||
caps.chromeOptions[option] = val;
|
||||
});
|
||||
}
|
||||
caps = this.decorateChromeOptions(caps);
|
||||
this.chromedriver.createSession(caps, function (err, sessId) {
|
||||
if (err) return cb(err);
|
||||
logger.debug("Setting proxy session id to " + sessId);
|
||||
this.proxySessionId = sessId;
|
||||
this.sessionChromedrivers[context] = this.chromedriver;
|
||||
ocb();
|
||||
}.bind(this));
|
||||
}.bind(this);
|
||||
|
||||
if (this.sessionChromedrivers[context]) {
|
||||
logger.debug("Found existing Chromedriver for context '" + context + "'." +
|
||||
" Using it.");
|
||||
this.rememberProxyState();
|
||||
this.chromedriver = this.sessionChromedrivers[context];
|
||||
this.proxyHost = this.chromedriver.proxyHost;
|
||||
this.proxyPort = this.chromedriver.proxyPort;
|
||||
this.proxySessionId = this.chromedriver.chromeSessionId;
|
||||
this.isProxy = true;
|
||||
// in the case where we've already set up a chromedriver for a context,
|
||||
// we want to reconnect to it, not create a whole new one
|
||||
this.setupExistingChromedriver(context, cb);
|
||||
} else {
|
||||
this.setupNewChromedriver(context, cb);
|
||||
}
|
||||
};
|
||||
|
||||
// check the status by sending a simple window-based command to ChromeDriver
|
||||
// if there is an error, we want to recreate the ChromeDriver session
|
||||
var url = '/wd/hub/session/' + this.proxySessionId + '/url';
|
||||
this.chromedriver.proxyTo(url, 'GET', {}, function (err, res, body) {
|
||||
body = JSON.parse(body);
|
||||
if ((body.status === status.codes.NoSuchWindow.code && body.value.message.indexOf('no such window: window was already closed') !== -1) ||
|
||||
(body.status === 100 && body.value.message.indexOf('chrome not reachable') !== -1)) {
|
||||
// the window is closed and we need to reinitialize
|
||||
logger.debug("ChromeDriver is not associated with a window. Re-initializing the session.");
|
||||
this.cleanupChromedriver(this.chromedriver, function () {
|
||||
setupNewChromeDriver(cb);
|
||||
});
|
||||
} else {
|
||||
cb();
|
||||
androidHybrid.setupNewChromedriver = function (context, ocb) {
|
||||
var chromeArgs = {
|
||||
port: this.args.chromeDriverPort
|
||||
, executable: this.args.chromedriverExecutable
|
||||
, deviceId: this.adb.curDeviceId
|
||||
};
|
||||
this.chromedriver = new Chromedriver(chromeArgs);
|
||||
this.proxyReqRes = this.chromedriver.proxyReq.bind(this.chromedriver);
|
||||
this.rememberProxyState();
|
||||
this.isProxy = true;
|
||||
var caps = {
|
||||
chromeOptions: {
|
||||
androidPackage: this.args.appPackage,
|
||||
androidUseRunningApp: true
|
||||
}
|
||||
};
|
||||
if (this.args.enablePerformanceLogging) {
|
||||
caps.loggingPrefs = {performance: 'ALL'};
|
||||
}
|
||||
// For now the only known arg passed this way is androidDeviceSocket used
|
||||
// by Operadriver (deriving from Chromedriver) // We don't know how other
|
||||
// Chromium embedders will call this argument so for now it's name needs to
|
||||
// be configurable. When Google adds the androidDeviceSocket argument to
|
||||
// the original Chromedriver then we will be sure about its name for all
|
||||
// Chromium embedders (as their Webdrivers will derive from Chromedriver)
|
||||
if (this.args.specialChromedriverSessionArgs) {
|
||||
_.each(this.args.specialChromedriverSessionArgs, function (val, option) {
|
||||
logger.debug("This method is being deprecated. Apply chromeOptions " +
|
||||
"normally to pass along options,see sites.google.com/a/" +
|
||||
"chromium.org/chromedriver/capabilities for more info");
|
||||
caps.chromeOptions[option] = val;
|
||||
});
|
||||
}
|
||||
caps = this.decorateChromeOptions(caps);
|
||||
// see note in chrome.js::createSession for explanation of this pattern
|
||||
this.chromedriver.once(Chromedriver.EVENT_ERROR, ocb);
|
||||
this.chromedriver.on(Chromedriver.EVENT_CHANGED, function (msg) {
|
||||
if (msg.state === Chromedriver.STATE_ONLINE) {
|
||||
// save the chromedriver object under the context
|
||||
this.sessionChromedrivers[context] = this.chromedriver;
|
||||
// let whoever called us know that we're done setting up session
|
||||
ocb();
|
||||
} else if (msg.state === Chromedriver.STATE_STOPPED) {
|
||||
// bind our stop/exit handler, passing in context so we know which
|
||||
// one stopped
|
||||
this.onChromedriverStop(context);
|
||||
}
|
||||
}.bind(this));
|
||||
this.chromedriver.start(caps);
|
||||
};
|
||||
|
||||
|
||||
androidHybrid.setupExistingChromedriver = function (context, cb) {
|
||||
logger.debug("Found existing Chromedriver for context '" + context + "'." +
|
||||
" Using it.");
|
||||
this.rememberProxyState();
|
||||
this.chromedriver = this.sessionChromedrivers[context];
|
||||
this.proxyReqRes = this.chromedriver.proxyReq.bind(this.chromedriver);
|
||||
this.isProxy = true;
|
||||
|
||||
// make sure that no matter how many times Chromedriver emits a changed
|
||||
// state event, we only call back on the first ONLINE message
|
||||
var restartCb = _.once(function () {
|
||||
// once we're back online, let upstream know
|
||||
this.chromedriverRestartingContext = null;
|
||||
cb();
|
||||
}.bind(this));
|
||||
|
||||
// check the status by sending a simple window-based command to ChromeDriver
|
||||
// if there is an error, we want to recreate the ChromeDriver session
|
||||
this.chromedriver.hasWorkingWebview().then(function (works) {
|
||||
if (works) return cb();
|
||||
logger.debug("ChromeDriver is not associated with a window. " +
|
||||
"Re-initializing the session.");
|
||||
// catch any errors the restart process bubbles up
|
||||
this.chromedriver.once(Chromedriver.EVENT_ERROR, cb);
|
||||
// once the restart is successful, reset context flag and continue
|
||||
this.chromedriver.on(Chromedriver.EVENT_CHANGED, function (msg) {
|
||||
if (msg.state === Chromedriver.STATE_ONLINE) {
|
||||
restartCb();
|
||||
}
|
||||
}.bind(this));
|
||||
} else {
|
||||
setupNewChromeDriver(cb);
|
||||
}
|
||||
this.chromedriverRestartingContext = context;
|
||||
this.chromedriver.restart();
|
||||
}.bind(this)).catch(cb);
|
||||
};
|
||||
|
||||
androidHybrid.onChromedriverExit = function () {
|
||||
logger.debug("Chromedriver exited unexpectedly");
|
||||
if (typeof this.cbForCurrentCmd === "function") {
|
||||
androidHybrid.onChromedriverStop = function (context) {
|
||||
logger.debug("Chromedriver for context " + context + " stopped");
|
||||
// chromedriver isn't valid anymore, so remove it from context list
|
||||
if (_.has(this.chromedriverStopCbs, context)) {
|
||||
// if we intentionally stopped this context's chromedriver, we'll have a
|
||||
// callback for it in this.chromedriverStopCbs
|
||||
delete this.sessionChromedrivers[context];
|
||||
this.chromedriverStopCbs[context]();
|
||||
} else if (context === this.curContext) {
|
||||
// if we don't have a stop callback, we exited unexpectedly and so want
|
||||
// to shut down the session and respond with an error
|
||||
// TODO: this kind of thing should be emitted and handled by a higher-level
|
||||
// controlling function
|
||||
var error = new UnknownError("Chromedriver quit unexpectedly during session");
|
||||
this.shutdown(function () {
|
||||
this.cbForCurrentCmd(error, null);
|
||||
}.bind(this));
|
||||
}
|
||||
};
|
||||
|
||||
androidHybrid.cleanupChromedriver = function (chromedriver, cb) {
|
||||
if (chromedriver) {
|
||||
logger.debug("Cleaning up Chromedriver");
|
||||
chromedriver.stop(function (err) {
|
||||
if (err) logger.warn("Error stopping chromedriver: " + err.message);
|
||||
this.restoreProxyState();
|
||||
cb();
|
||||
}.bind(this));
|
||||
} else {
|
||||
cb();
|
||||
logger.error(error.message);
|
||||
if (typeof this.cbForCurrentCmd === "function") {
|
||||
this.shutdown(function () {
|
||||
this.cbForCurrentCmd(error, null);
|
||||
}.bind(this));
|
||||
}
|
||||
} else if (context !== this.chromedriverRestartingContext) {
|
||||
// if a Chromedriver in the non-active context barfs, we don't really
|
||||
// care, we'll just make a new one next time we need the context.
|
||||
// The only time we ignore this is if we know we're in the middle of a
|
||||
// Chromedriver restart
|
||||
logger.warn("Chromedriver quit unexpectedly, but it wasn't the active " +
|
||||
"context, ignoring");
|
||||
delete this.sessionChromedrivers[context];
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -224,13 +260,16 @@ androidHybrid.suspendChromedriverProxy = function (cb) {
|
|||
|
||||
androidHybrid.stopChromedriverProxies = function (ocb) {
|
||||
async.eachSeries(Object.keys(this.sessionChromedrivers), function (context, cb) {
|
||||
var chromedriver = this.sessionChromedrivers[context];
|
||||
chromedriver.deleteSession(function (err) {
|
||||
if (err) return cb(err);
|
||||
this.cleanupChromedriver(chromedriver, cb);
|
||||
delete this.sessionChromedrivers[context];
|
||||
}.bind(this));
|
||||
}.bind(this), ocb);
|
||||
logger.debug("Stopping chromedriver for context " + context);
|
||||
// add a stop cb for this context so we get called back once this context's
|
||||
// chromedriver finishes exiting
|
||||
this.chromedriverStopCbs[context] = _.once(cb);
|
||||
this.sessionChromedrivers[context].stop();
|
||||
}.bind(this), function (err) {
|
||||
// if one of these fails, go back to last proxy state and error out
|
||||
this.restoreProxyState();
|
||||
ocb(err);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
androidHybrid.defaultWebviewName = function () {
|
||||
|
@ -255,4 +294,38 @@ androidHybrid.initAutoWebview = function (cb) {
|
|||
}
|
||||
};
|
||||
|
||||
// get the correct chromedriver executable path based on our system
|
||||
// TODO: don't download/build chromedriver in reset.sh, instead let this be
|
||||
// something that the appium-chromedriver package manages
|
||||
androidHybrid.initChromedriverPath = function (cb) {
|
||||
if (this.args.chromedriverExecutable) {
|
||||
cb();
|
||||
} else {
|
||||
var setPath = function (platform, executable) {
|
||||
this.args.chromedriverExecutable = path.resolve(__dirname, "..", "..",
|
||||
"..", "build", "chromedriver", platform, executable);
|
||||
logger.debug("Set chromedriver binary as: " + this.args.chromedriverExecutable);
|
||||
}.bind(this);
|
||||
if (isLinux) {
|
||||
logger.debug("Determining linux architecture");
|
||||
exec("uname -m", function (err, stdout) {
|
||||
var executable;
|
||||
if (err) return cb(err);
|
||||
if (stdout.trim() === "i686") {
|
||||
executable = "chromedriver32";
|
||||
} else {
|
||||
executable = "chromedriver64";
|
||||
}
|
||||
setPath("linux", executable);
|
||||
cb();
|
||||
});
|
||||
} else {
|
||||
var executable = isWindows ? "chromedriver.exe" : "chromedriver";
|
||||
var platform = isWindows ? "windows" : "mac";
|
||||
setPath(platform, executable);
|
||||
cb();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = androidHybrid;
|
||||
|
|
|
@ -101,6 +101,7 @@ Android.prototype.start = function (cb, onDie) {
|
|||
this.initAdb.bind(this),
|
||||
this.packageAndLaunchActivityFromManifest.bind(this),
|
||||
this.initUiautomator.bind(this),
|
||||
this.initChromedriverPath.bind(this),
|
||||
this.prepareDevice.bind(this),
|
||||
this.checkApiLevel.bind(this),
|
||||
this.pushStrings.bind(this),
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
"use strict";
|
||||
|
||||
var Android = require('./android.js')
|
||||
, Chromedriver = require('appium-chromedriver').default
|
||||
, _ = require('underscore')
|
||||
, logger = require('../../server/logger.js').get('appium')
|
||||
, status = require('../../server/status.js')
|
||||
, deviceCommon = require('../common.js')
|
||||
, jwpSuccess = deviceCommon.jwpSuccess
|
||||
, async = require('async')
|
||||
, Chromedriver = require('./chromedriver.js');
|
||||
, ADB = require('./adb.js')
|
||||
, UiAutomator = require('./uiautomator.js');
|
||||
|
||||
var NATIVE_WIN = "NATIVE_APP";
|
||||
var WEBVIEW_WIN = "WEBVIEW";
|
||||
|
@ -22,7 +24,7 @@ ChromeAndroid.prototype._androidInit = Android.prototype.init;
|
|||
ChromeAndroid.prototype.init = function () {
|
||||
this._androidInit();
|
||||
this.adb = null;
|
||||
this.onDie = null;
|
||||
this.stopCb = null;
|
||||
this.setChromedriverMode();
|
||||
};
|
||||
|
||||
|
@ -79,12 +81,16 @@ ChromeAndroid.prototype.startAutomation = function (cb) {
|
|||
};
|
||||
|
||||
ChromeAndroid.prototype.start = function (cb, onDie) {
|
||||
this.onDie = onDie;
|
||||
this.adb = new ADB(this.args);
|
||||
this.uiautomator = new UiAutomator(this.adb, this.args);
|
||||
this.uiautomator.setExitHandler(this.onUiautomatorExit.bind(this));
|
||||
this.stopCb = onDie;
|
||||
|
||||
async.series([
|
||||
this.initAdb.bind(this),
|
||||
this.initUiautomator.bind(this),
|
||||
this.prepareDevice.bind(this),
|
||||
this.initChromedriverPath.bind(this),
|
||||
this.prepareChromedriver.bind(this),
|
||||
this.pushAndUnlock.bind(this),
|
||||
this.forwardPort.bind(this),
|
||||
|
@ -105,14 +111,9 @@ ChromeAndroid.prototype.prepareChromedriver = function (cb) {
|
|||
port: this.args.proxyPort
|
||||
, executable: this.args.chromedriverExecutable
|
||||
, deviceId: this.adb.curDeviceId
|
||||
, enablePerformanceLogging: this.args.enablePerformanceLogging
|
||||
};
|
||||
this.chromedriver = new Chromedriver(chromeArgs, this.adb,
|
||||
this.onChromedriverExit.bind(this));
|
||||
this.proxyTo = this.chromedriver.proxyTo.bind(this.chromedriver);
|
||||
this.proxyHost = this.chromedriver.proxyHost;
|
||||
this.proxyPort = this.chromedriver.proxyPort;
|
||||
this.deleteSession = this.chromedriver.deleteSession.bind(this.chromedriver);
|
||||
this.chromedriver = new Chromedriver(chromeArgs);
|
||||
this.proxyReqRes = this.chromedriver.proxyReq.bind(this.chromedriver);
|
||||
cb();
|
||||
};
|
||||
|
||||
|
@ -124,11 +125,15 @@ ChromeAndroid.prototype.pushAndUnlock = function (cb) {
|
|||
};
|
||||
|
||||
ChromeAndroid.prototype.createSession = function (cb) {
|
||||
cb = _.once(cb);
|
||||
var caps = {
|
||||
chromeOptions: {
|
||||
androidPackage: this.args.appPackage
|
||||
}
|
||||
};
|
||||
if (this.args.enablePerformanceLogging) {
|
||||
caps.loggingPrefs = {performance: 'ALL'};
|
||||
}
|
||||
|
||||
var knownPackages = ["org.chromium.chrome.shell",
|
||||
"com.android.chrome",
|
||||
|
@ -139,30 +144,34 @@ ChromeAndroid.prototype.createSession = function (cb) {
|
|||
}
|
||||
|
||||
caps = this.decorateChromeOptions(caps);
|
||||
this.chromedriver.createSession(caps, cb);
|
||||
this.chromedriver.once(Chromedriver.EVENT_ERROR, cb);
|
||||
this.chromedriver.on(Chromedriver.EVENT_CHANGED, function (msg) {
|
||||
if (msg.state === Chromedriver.STATE_ONLINE) {
|
||||
cb();
|
||||
} else if (msg.state === Chromedriver.STATE_STOPPED) {
|
||||
this.onChromedriverStop();
|
||||
}
|
||||
}.bind(this));
|
||||
this.chromedriver.start(caps);
|
||||
};
|
||||
|
||||
ChromeAndroid.prototype.stop = function (cb) {
|
||||
this.uiautomator.shutdown(function () {
|
||||
this.chromedriver.stop(function (err) {
|
||||
if (err) return cb(err);
|
||||
this.stopCb = cb; // change stopCb from original onDie to this cb
|
||||
this.chromedriver.stop();
|
||||
};
|
||||
|
||||
ChromeAndroid.prototype.onChromedriverStop = function () {
|
||||
var cb = this.stopCb;
|
||||
if (this.adb) {
|
||||
this.uiautomator.shutdown(function () {
|
||||
this.adb.forceStop(this.args.appPackage, function (err) {
|
||||
if (err) return cb(err);
|
||||
this.adb.stopLogcat(cb);
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
ChromeAndroid.prototype.onChromedriverExit = function () {
|
||||
if (!this.adb) return; // cleaning up has already occured.
|
||||
async.series([
|
||||
this.adb.getConnectedDevices.bind(this.adb),
|
||||
_.partial(this.adb.forceStop.bind(this.adb), this.args.appPackage)
|
||||
], function (err) {
|
||||
if (err) logger.error(err.message);
|
||||
this.adb.stopLogcat(this.onDie.bind(this));
|
||||
}.bind(this));
|
||||
} else {
|
||||
cb();
|
||||
}
|
||||
};
|
||||
|
||||
// since we're in chrome, our default context is not the native mode, but web
|
||||
|
|
|
@ -1,249 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
var proxyTo = require('../common.js').proxyTo
|
||||
, _ = require('underscore')
|
||||
, logger = require('../../server/logger.js').get('appium')
|
||||
, exec = require('child_process').exec
|
||||
, spawn = require('child_process').spawn
|
||||
, async = require('async')
|
||||
, through = require('through')
|
||||
, system = require('appium-support').system
|
||||
, isWindows = system.isWindows()
|
||||
, isLinux = system.isLinux()
|
||||
, path = require('path')
|
||||
, fs = require('fs')
|
||||
, ADB = require('./adb.js');
|
||||
|
||||
var Chromedriver = function (args, adb, onDie) {
|
||||
this.proxyHost = '127.0.0.1';
|
||||
this.proxyPort = args.port || 9515;
|
||||
this.deviceId = args.deviceId;
|
||||
this.enablePerformanceLogging = args.enablePerformanceLogging;
|
||||
this.proc = null;
|
||||
this.onChromedriverStart = null;
|
||||
this.onDie = onDie;
|
||||
this.exitCb = null;
|
||||
this.shuttingDown = false;
|
||||
this.executable = args.executable;
|
||||
this.adb = adb;
|
||||
};
|
||||
|
||||
Chromedriver.prototype.initChromedriverPath = function (cb) {
|
||||
if (this.executable) {
|
||||
this.chromedriver = this.executable;
|
||||
cb();
|
||||
} else {
|
||||
var setPath = function (platform, executable) {
|
||||
this.chromedriver = path.resolve(__dirname, "..", "..", "..", "build",
|
||||
"chromedriver", platform, executable);
|
||||
logger.debug("Set chromedriver binary as: " + this.chromedriver);
|
||||
}.bind(this);
|
||||
if (isLinux) {
|
||||
logger.debug("Determining linux architecture");
|
||||
exec("uname -m", function (err, stdout) {
|
||||
var executable;
|
||||
if (err) return cb(err);
|
||||
if (stdout.trim() === "i686") {
|
||||
executable = "chromedriver32";
|
||||
} else {
|
||||
executable = "chromedriver64";
|
||||
}
|
||||
setPath("linux", executable);
|
||||
cb();
|
||||
});
|
||||
} else {
|
||||
var executable = isWindows ? "chromedriver.exe" : "chromedriver";
|
||||
var platform = isWindows ? "windows" : "mac";
|
||||
setPath(platform, executable);
|
||||
cb();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Chromedriver.prototype.ensureChromedriverExists = function (cb) {
|
||||
logger.debug("Ensuring Chromedriver exists");
|
||||
fs.exists(this.chromedriver, function (exists) {
|
||||
if (!exists) return cb(new Error("Could not find chromedriver. Need to run reset script?"));
|
||||
cb();
|
||||
});
|
||||
};
|
||||
|
||||
Chromedriver.prototype.killOldChromedrivers = function (cb) {
|
||||
var cmd;
|
||||
if (isWindows) {
|
||||
cmd = "FOR /F \"usebackq tokens=5\" %a in (`netstat -nao ^| findstr /R /C:\"" + this.proxyPort + " \"`) do (" +
|
||||
"FOR /F \"usebackq\" %b in (`TASKLIST /FI \"PID eq %a\" ^| findstr /I chromedriver.exe`) do (" +
|
||||
"IF NOT %b==\"\" TASKKILL /F /PID %a" +
|
||||
")" +
|
||||
")";
|
||||
} else {
|
||||
cmd = "ps -ef | grep " + this.chromedriver + " | grep -v grep |" +
|
||||
"grep -e '--port=" + this.proxyPort + "\\(\\s.*\\)\\?$' | awk '{ print $2 }' | " +
|
||||
"xargs kill -15";
|
||||
}
|
||||
logger.debug("Killing any old chromedrivers, running: " + cmd);
|
||||
exec(cmd, function (err) {
|
||||
if (err) {
|
||||
logger.debug("No old chromedrivers seemed to exist");
|
||||
} else {
|
||||
logger.debug("Successfully cleaned up old chromedrivers");
|
||||
}
|
||||
cb();
|
||||
});
|
||||
};
|
||||
|
||||
Chromedriver.prototype.start = function (cb) {
|
||||
this.onChromedriverStart = cb;
|
||||
logger.debug("Spawning chromedriver with: " + this.chromedriver);
|
||||
var alreadyReturned = false;
|
||||
var args = ["--url-base=wd/hub", "--port=" + this.proxyPort, "--adb-port=" + ADB.getAdbServerPort()];
|
||||
this.proc = spawn(this.chromedriver, args);
|
||||
this.proc.stdout.setEncoding('utf8');
|
||||
this.proc.stderr.setEncoding('utf8');
|
||||
|
||||
this.proc.on('error', function (err) {
|
||||
logger.error('Chromedriver process failed with error: ' + err.message);
|
||||
alreadyReturned = true;
|
||||
this.shuttingDown = true;
|
||||
logger.error('Killing chromedriver');
|
||||
this.proc.kill();
|
||||
this.onDie();
|
||||
}.bind(this));
|
||||
|
||||
this.proc.stdout.pipe(through(function (data) {
|
||||
logger.debug('[CHROMEDRIVER] ' + data.trim());
|
||||
if (!alreadyReturned && data.indexOf('Starting ') === 0) {
|
||||
this.chromedriverStarted = true;
|
||||
alreadyReturned = true;
|
||||
return cb();
|
||||
}
|
||||
}.bind(this)));
|
||||
|
||||
this.proc.stderr.pipe(through(function (data) {
|
||||
logger.debug('[CHROMEDRIVER STDERR] ' + data.trim());
|
||||
}));
|
||||
|
||||
this.proc.on('exit', this.onClose.bind(this));
|
||||
this.proc.on('close', this.onClose.bind(this));
|
||||
};
|
||||
|
||||
Chromedriver.prototype.onClose = function (code, signal) {
|
||||
if (!this.shuttingDown) {
|
||||
this.shuttingDown = true;
|
||||
logger.debug("Chromedriver exited with code " + code);
|
||||
if (signal) {
|
||||
logger.debug("(killed by signal " + signal + ")");
|
||||
}
|
||||
if (!this.chromedriverStarted) {
|
||||
return this.onChromedriverStart(
|
||||
new Error("Chromedriver quit before it was available"));
|
||||
}
|
||||
if (this.exitCb !== null) {
|
||||
return this.exitCb();
|
||||
}
|
||||
this.onDie();
|
||||
}
|
||||
};
|
||||
|
||||
Chromedriver.prototype.createSession = function (caps, cb) {
|
||||
logger.debug("Creating Chrome session");
|
||||
caps.chromeOptions.androidDeviceSerial = this.deviceId;
|
||||
if (this.enablePerformanceLogging) {
|
||||
caps.loggingPrefs = {performance: 'ALL'};
|
||||
}
|
||||
var data = {
|
||||
sessionId: null,
|
||||
desiredCapabilities: caps
|
||||
};
|
||||
async.waterfall([
|
||||
this.initChromedriverPath.bind(this),
|
||||
this.ensureChromedriverExists.bind(this),
|
||||
this.killOldChromedrivers.bind(this),
|
||||
this.start.bind(this),
|
||||
_.partial(this.proxyNewSession.bind(this), data)
|
||||
], cb);
|
||||
};
|
||||
|
||||
Chromedriver.prototype.proxyNewSession = function (data, cb) {
|
||||
var maxRetries = 5;
|
||||
var curRetry = 0;
|
||||
var retryInt = 500;
|
||||
var doProxy = function (alreadyRestarted) {
|
||||
this.proxyTo('/wd/hub/session', 'POST', data, function (err, res, body) {
|
||||
if (err) {
|
||||
if (/ECONNREFUSED/.test(err.message) && curRetry < maxRetries) {
|
||||
logger.debug("Could not connect yet; retrying");
|
||||
curRetry++;
|
||||
setTimeout(doProxy, retryInt);
|
||||
return;
|
||||
}
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
// first checking if we get a well formed success response
|
||||
this.chromeSessionId = null;
|
||||
try {
|
||||
if (body.status === 0 && body.sessionId) {
|
||||
logger.debug("Successfully started chrome session " + body.sessionId);
|
||||
this.chromeSessionId = body.sessionId;
|
||||
}
|
||||
} catch (ignore) {}
|
||||
if (this.chromeSessionId) return cb(null, this.chromeSessionId);
|
||||
|
||||
// then check redirect success case
|
||||
try {
|
||||
if (res.statusCode === 303 && res.headers.location) {
|
||||
logger.debug("Successfully started chrome session");
|
||||
var loc = res.headers.location;
|
||||
this.chromeSessionId = /\/([^\/]+)$/.exec(loc)[1];
|
||||
}
|
||||
} catch (ignore) {}
|
||||
if (this.chromeSessionId) return cb(null, this.chromeSessionId);
|
||||
|
||||
// those are error cases
|
||||
if (typeof body !== "undefined" &&
|
||||
typeof body.value !== "undefined" &&
|
||||
typeof body.value.message !== "undefined" &&
|
||||
body.value.message.indexOf("Failed to run adb command") !== -1) {
|
||||
logger.error("Chromedriver had trouble running adb");
|
||||
if (!alreadyRestarted) {
|
||||
logger.error("Restarting adb for chromedriver");
|
||||
return this.adb.restartAdb(function () {
|
||||
this.adb.getConnectedDevices(function () {
|
||||
doProxy(true);
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
} else {
|
||||
cb(new Error("Chromedriver wasn't able to use adb. Is the server up?"));
|
||||
}
|
||||
} else {
|
||||
logger.error("Chromedriver create session did not work. Status was " +
|
||||
res.statusCode + " and body was " +
|
||||
JSON.stringify(body));
|
||||
cb(new Error("Did not get session redirect from Chromedriver"));
|
||||
}
|
||||
}.bind(this));
|
||||
}.bind(this);
|
||||
|
||||
doProxy();
|
||||
};
|
||||
|
||||
Chromedriver.prototype.deleteSession = function (cb) {
|
||||
logger.debug("Deleting Chrome session");
|
||||
var url = '/wd/hub/session/' + this.chromeSessionId;
|
||||
this.proxyTo(url, 'DELETE', null, function (err, res) {
|
||||
if (err) return cb(err);
|
||||
if (res.statusCode !== 200) return cb(new Error("Status was not 200"));
|
||||
cb();
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
Chromedriver.prototype.stop = function (cb) {
|
||||
logger.debug('Killing chromedriver and waiting for close');
|
||||
this.exitCb = cb;
|
||||
this.proc.kill('SIGINT');
|
||||
};
|
||||
|
||||
Chromedriver.prototype.proxyTo = proxyTo;
|
||||
|
||||
module.exports = Chromedriver;
|
|
@ -39,6 +39,16 @@ module.exports.shouldProxy = function (req) {
|
|||
};
|
||||
|
||||
module.exports.doProxy = function (req, res) {
|
||||
if (req.device.proxyReqRes) {
|
||||
// this section is triggered when we have defined the proxy device to use
|
||||
// the appium-jsonwp-proxy method proxyReqRes. Ultimately we'll be moving
|
||||
// everything to this paradigm and will delete the code after this block
|
||||
req.device.proxyReqRes(req, res).catch(function (err) {
|
||||
logger.error(err.message);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("Proxying command to " + req.device.proxyHost + ":" +
|
||||
req.device.proxyPort);
|
||||
var sessRe = new RegExp('^/wd/hub/session/([^/]+)');
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
"adm-zip": "~0.4.7",
|
||||
"appium-adb": "=1.7.5",
|
||||
"appium-atoms": "=0.0.5",
|
||||
"appium-chromedriver": "=0.2.0",
|
||||
"appium-instruments": "=1.5.4",
|
||||
"appium-support": "=0.1.1",
|
||||
"appium-uiauto": "=1.10.7",
|
||||
|
|
Загрузка…
Ссылка в новой задаче