make chrome device proxy to chromedriver instead of using atoms

This commit is contained in:
Jonathan Lipps
2013-06-28 14:44:02 -07:00
parent ea650a45f6
commit 370b7e5282
5 changed files with 137 additions and 59 deletions

View File

@@ -2,67 +2,135 @@
var Android = require('./android').Android
, _ = require('underscore')
, proxyTo = require('./device').proxyTo
, logger = require('../logger').get('appium')
, deviceCommon = require('./device')
, rdp = require('./hybrid/rdp.js')
, exec = require('child_process').exec
, status = require("./uiauto/lib/status");
, spawn = require('child_process').spawn
, async = require('async')
, adb = require('../android/adb');
var ChromeAndroid = function(opts) {
this.initialize(opts);
this.systemDebuggerPort = 2773;
this.opts = opts;
this.isProxy = true;
this.proxyHost = '127.0.0.1';
this.proxyPort = opts.port || 9515;
this.chromedriver = null;
this.proc = null;
this.chromedriverStarted = false;
this.chromedriver = "chromedriver";
this.adb = null;
this.onDie = function() {};
};
_.extend(ChromeAndroid.prototype, Android.prototype);
ChromeAndroid.prototype._start = ChromeAndroid.prototype.start;
ChromeAndroid.prototype.loadFirstWebview = function(cb) {
var me = this;
this.remote = rdp.init(function() {
// TODO: fill out with what to do if webview disconnects on us
});
this.remote.port = this.systemDebuggerPort;
this.remote.pageArrayFromJson(function(pageArray) {
me.curWindowHandle = pageArray[0].id;
me.remote.connect(me.curWindowHandle, cb);
});
};
ChromeAndroid.prototype.start = function(cb, onDie) {
var me = this;
this._start(function(err) {
if (err) return cb(err);
me.forwardDebuggerPort(function(err) {
if (err) return cb(err);
me.loadFirstWebview(function() {
cb();
});
});
}, onDie);
this.adb = new adb(this.opts);
this.onDie = onDie;
// do stuff to launch chromedriver
async.waterfall([
this.ensureChromedriverExists.bind(this),
this.startChromedriver.bind(this),
this.createSession.bind(this)
], cb);
};
ChromeAndroid.prototype.startAppium = function(onLaunch, onExit) {
this.adb.startChrome(onLaunch, onExit);
ChromeAndroid.prototype.ensureChromedriverExists = function(cb) {
logger.info("Ensuring Chromedriver exists");
exec('which chromedriver', function(err, stdout) {
if (err) return cb(new Error("Could not find chromedriver, is it on PATH?"));
this.chromedriver = stdout.trim();
cb();
}.bind(this));
};
ChromeAndroid.prototype.shutdown = function() {
this.remote.disconnect();
this.onStop();
};
ChromeAndroid.prototype.forwardDebuggerPort = function(cb) {
logger.info("Forwarding debugger port");
var arg = "tcp:" + this.systemDebuggerPort +
" localabstract:chrome_devtools_remote";
exec(this.adb.adbCmd + " forward " + arg, _.bind(function(err) {
if (err) {
logger.error(err); return cb(err);
ChromeAndroid.prototype.startChromedriver = function(cb) {
logger.info("Spawning chromedriver with: " + this.chromedriver);
var alreadyReturned = false;
var args = ["--url-base=wd/hub"];
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.onDie();
}.bind(this));
this.proc.stdout.on('data', function(data) {
logger.info('[CHROMEDRIVER] ' + data.trim());
if (data.indexOf("Started ChromeDriver") !== -1) {
this.chromedriverStarted = true;
alreadyReturned = true;
cb();
}
cb(null);
}, this));
}.bind(this));
this.proc.stderr.on('data', function(data) {
logger.info('[CHROMEDRIVER STDERR] ' + data.trim());
});
this.proc.on('exit', function(code) {
logger.info("Chromedriver exited with code " + code);
alreadyReturned = true;
if (!this.chromedriverStarted) {
return cb(new Error("Chromedriver quit before it was available"));
}
this.onDie();
}.bind(this));
setTimeout(function() {
if (!alreadyReturned) {
logger.info("We didn't hear from chromedriver in a while; let's " +
"assume it started OK");
cb();
}
}.bind(this), 1000);
};
ChromeAndroid.prototype.createSession = function(cb) {
logger.info("Creating Chrome session");
var data = {
sessionId: null,
desiredCapabilities: {
chromeOptions: {
androidPackage: 'com.android.chrome'
}
}
};
this.proxyTo('/wd/hub/session', 'POST', data, function(err, res, body) {
if (err) return cb(err);
if (res.statusCode === 303 && res.headers.location) {
logger.info("Successfully started chrome session");
var loc = res.headers.location;
this.chromeSessionId = /\/([^\/]+)$/.exec(loc)[1];
cb(null, this.chromeSessionId);
} else {
logger.error("Chromedriver create session did not work. Status was " +
res.statusCode + " and body was " + body);
cb(new Error("Did not get session redirect from Chromedriver"));
}
}.bind(this));
};
ChromeAndroid.prototype.deleteSession = function(cb) {
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"));
}.bind(this));
};
ChromeAndroid.prototype.stop = function(cb) {
async.series([
this.adb.getConnectedDevices.bind(this.adb),
this.adb.stopApp.bind(this.adb)
], function(err) {
if (err) logger.error(err.message);
cb(0);
});
};
ChromeAndroid.prototype.proxyTo = proxyTo;
module.exports = function(opts) {
return new ChromeAndroid(opts);
};

View File

@@ -62,29 +62,36 @@ exports.waitForCondition = function(waitMs, condFn, cb, intervalMs) {
spin();
};
exports.request = function(url, method, body, contentType, cb) {
exports.doRequest = function(url, method, body, contentType, cb) {
if (typeof cb === "undefined" && typeof contentType === "function") {
cb = contentType;
contentType = null;
}
if (typeof contentType === "undefined" || contentType === null) {
contentType = "application/json";
contentType = "application/json;charset=UTF-8";
}
if (!(/^https?:\/\//.exec(url))) {
url = 'http://' + url;
}
var host = /^https?:\/\/([^\/]+)/.exec(url)[1];
var opts = {
url: url
, method: method
, headers: {'Content-Type': contentType}
, jar: false
};
opts.headers = {};
if (_.contains(['put', 'post', 'patch'], method.toLowerCase())) {
if (typeof body !== "string") {
opts.json = body;
if (typeof body === "object") {
opts.body = JSON.stringify(body);
} else {
opts.body = body;
}
opts.headers['Content-Length'] = opts.body.length;
}
// explicitly set these headers with correct capitalization to work around
// an issue in node/requests
opts.headers['Content-Type'] = contentType;
opts.headers.Host = host;
logger.info("Making http request with opts: " + JSON.stringify(opts));
request(opts, cb);
};
@@ -132,6 +139,14 @@ exports.unpackApp = function(req, packageExtension, cb) {
}
};
exports.proxyTo = function(endpoint, method, data, cb) {
if (endpoint[0] !== '/') {
endpoint = '/' + endpoint;
}
var url = 'http://' + this.proxyHost + ':' + this.proxyPort + endpoint;
exports.doRequest(url, method, data ? data : '', cb);
};
exports.parseExecuteResponse = function(response, cb) {
if ((response.value !== null) && (typeof response.value !== "undefined")) {
var wdElement = null;

View File

@@ -1,7 +1,7 @@
"use strict";
var controller = require('./controller')
, respondError = require('./controller').respondError
, request = require('./device').request
, doRequest = require('./device').doRequest
, _ = require('underscore')
, _s = require("underscore.string")
, logger = require('../logger').get('appium');
@@ -48,7 +48,7 @@ module.exports = function(appium) {
req.device.proxyPort);
var url = 'http://' + req.device.proxyHost + ':' + req.device.proxyPort +
req.originalUrl;
request(url, req.route.method.toUpperCase(), req.body,
doRequest(url, req.route.method.toUpperCase(), req.body,
req.headers['content-type'], function(err, response, body) {
if (err) return next(err);
if (body && body.value) {

View File

@@ -4,6 +4,7 @@ var errors = require('./errors')
, adb = require('../android/adb')
, _ = require('underscore')
, request = require('./device').request
, proxyTo = require('./device').proxyTo
, logger = require('../logger').get('appium')
, status = require("./uiauto/lib/status")
, fs = require('fs')
@@ -134,13 +135,7 @@ Selendroid.prototype.deleteSession = function(cb) {
}, this));
};
Selendroid.prototype.proxyTo = function(endpoint, method, data, cb) {
if (endpoint[0] !== '/') {
endpoint = '/' + endpoint;
}
var url = 'http://' + this.proxyHost + ':' + this.proxyPort + endpoint;
request(url, method, data ? data : null, cb);
};
Selendroid.prototype.proxyTo = proxyTo;
module.exports = function(opts) {
return new Selendroid(opts);

View File

@@ -61,7 +61,7 @@
"adm-zip" : "~0.4.3",
"ws": "0.4.25",
"socket.io" : "~0.9.14"
},
},
"scripts": {
"test": "grunt travis"
},