mirror of
https://github.com/appium/appium.git
synced 2026-10-21 08:20:26 +08:00
Merge pull request #433 from jlipps/selendroid
Selendroid support and adb.js rewrite
This commit is contained in:
@@ -29,3 +29,4 @@ app/android/AndroidManifest.xml
|
||||
*~
|
||||
uiautomator/bootstrap/target/
|
||||
org.eclipse.ltk.core.refactoring.prefs
|
||||
/selendroid
|
||||
|
||||
@@ -7,3 +7,6 @@
|
||||
[submodule "submodules/appium.io"]
|
||||
path = submodules/appium.io
|
||||
url = https://github.com/appium/appium.io.git
|
||||
[submodule "submodules/selendroid"]
|
||||
path = submodules/selendroid
|
||||
url = https://github.com/DominikDary/selendroid.git
|
||||
|
||||
+2
-1
@@ -72,6 +72,7 @@ Android.prototype.start = function(cb, onDie) {
|
||||
var skipRelaunchOn = [
|
||||
'App never showed up'
|
||||
, 'Could not sign one or more apks'
|
||||
, 'Could not find a connected Android device'
|
||||
];
|
||||
var checkShouldSkipRelaunch = function(msg) {
|
||||
var skip = false;
|
||||
@@ -127,7 +128,7 @@ Android.prototype.start = function(cb, onDie) {
|
||||
if (this.adb === null) {
|
||||
// Pass Android opts and Android ref to adb.
|
||||
this.adb = adb(this.opts, this);
|
||||
this.adb.start(onLaunch, onExit);
|
||||
this.adb.startAppium(onLaunch, onExit);
|
||||
} else {
|
||||
logger.error("Tried to start ADB when we already have one running!");
|
||||
}
|
||||
|
||||
+25
-1
@@ -15,6 +15,7 @@ var routing = require('./routing')
|
||||
, _ = require('underscore')
|
||||
, ios = require('./ios')
|
||||
, android = require('./android')
|
||||
, selendroid = require('./selendroid')
|
||||
, status = require("./uiauto/lib/status");
|
||||
|
||||
var Appium = function(args) {
|
||||
@@ -108,6 +109,8 @@ Appium.prototype.getDeviceType = function(desiredCaps) {
|
||||
return "ios";
|
||||
} else if (desiredCaps.device.toLowerCase().indexOf('ipad') !== -1) {
|
||||
return "ios";
|
||||
} else if (desiredCaps.device.toLowerCase().indexOf('selendroid') !== -1) {
|
||||
return "selendroid";
|
||||
} else {
|
||||
return "android";
|
||||
}
|
||||
@@ -116,6 +119,8 @@ Appium.prototype.getDeviceType = function(desiredCaps) {
|
||||
return "ios";
|
||||
} else if (desiredCaps.browserName.toLowerCase() === "safari") {
|
||||
return "ios";
|
||||
} else if (desiredCaps.browserName.toLowerCase().indexOf('selendroid') !== -1) {
|
||||
return "selendroid";
|
||||
} else {
|
||||
return "android";
|
||||
}
|
||||
@@ -147,6 +152,10 @@ Appium.prototype.isAndroid = function() {
|
||||
return this.deviceType === "android";
|
||||
};
|
||||
|
||||
Appium.prototype.isSelendroid = function() {
|
||||
return this.deviceType === "selendroid";
|
||||
};
|
||||
|
||||
Appium.prototype.getAppExt = function() {
|
||||
return this.isIos() ? ".app" : ".apk";
|
||||
};
|
||||
@@ -352,6 +361,17 @@ Appium.prototype.invoke = function() {
|
||||
, fastReset: this.args.fastReset
|
||||
};
|
||||
this.devices[this.deviceType] = android(androidOpts);
|
||||
} else if (this.isSelendroid()) {
|
||||
var selendroidOpts = {
|
||||
apkPath: this.args.app
|
||||
, desiredCaps: this.desiredCapabilities
|
||||
, verbose: this.args.verbose
|
||||
, udid: this.args.udid
|
||||
, appPackage: this.args.androidPackage
|
||||
, appActivity: this.args.androidActivity
|
||||
, appDeviceReadyTimeout: this.args.androidDeviceReadyTimeout
|
||||
};
|
||||
this.devices[this.deviceType] = selendroid(selendroidOpts);
|
||||
} else {
|
||||
throw new Error("Tried to start a device that doesn't exist: " +
|
||||
this.deviceType);
|
||||
@@ -359,13 +379,17 @@ Appium.prototype.invoke = function() {
|
||||
}
|
||||
this.device = this.devices[this.deviceType];
|
||||
|
||||
this.device.start(function(err) {
|
||||
this.device.start(function(err, sessionIdOverride) {
|
||||
me.progress++;
|
||||
// Ensure we don't use an undefined session.
|
||||
if (typeof me.sessions[me.progress] === 'undefined') {
|
||||
me.progress--;
|
||||
return;
|
||||
}
|
||||
if (sessionIdOverride) {
|
||||
me.sessionId = sessionIdOverride;
|
||||
logger.info("Overriding session id with " + sessionIdOverride);
|
||||
}
|
||||
|
||||
me.sessions[me.progress].sessionId = me.sessionId;
|
||||
me.sessions[me.progress].callback(err, me.device);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
"use strict";
|
||||
|
||||
var errors = require('./errors')
|
||||
, request = require('request')
|
||||
, _ = require('underscore')
|
||||
, logger = require('../logger').get('appium');
|
||||
|
||||
var UnknownError = errors.UnknownError
|
||||
@@ -57,3 +59,29 @@ exports.waitForCondition = function(waitMs, condFn, cb, intervalMs) {
|
||||
};
|
||||
spin();
|
||||
};
|
||||
|
||||
exports.request = 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";
|
||||
}
|
||||
if (!(/^https?:\/\//.exec(url))) {
|
||||
url = 'http://' + url;
|
||||
}
|
||||
var opts = {
|
||||
url: url
|
||||
, method: method
|
||||
, headers: {'Content-Type': contentType}
|
||||
};
|
||||
if (_.contains(['put', 'post', 'patch'], method.toLowerCase())) {
|
||||
if (typeof body !== "string") {
|
||||
opts.json = body;
|
||||
} else {
|
||||
opts.body = body;
|
||||
}
|
||||
}
|
||||
request(opts, cb);
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ var logger = require('../logger').get('appium')
|
||||
, path = require('path')
|
||||
, rimraf = require('rimraf')
|
||||
, exec = require('child_process').exec
|
||||
, util = require('util')
|
||||
, temp = require('temp');
|
||||
|
||||
exports.downloadFile = function(fileUrl, cb) {
|
||||
|
||||
+47
-10
@@ -1,18 +1,55 @@
|
||||
"use strict";
|
||||
var controller = require('./controller')
|
||||
, request = require('./device').request
|
||||
, _ = require('underscore')
|
||||
, logger = require('../logger').get('appium');
|
||||
|
||||
var shouldProxy = function(req) {
|
||||
if (req.device === null) return false;
|
||||
|
||||
var avoid = [
|
||||
['POST', new RegExp('^/wd/hub/session$')]
|
||||
, ['DELETE', new RegExp('^/wd/hub/session/[^/]+$')]
|
||||
];
|
||||
var method = req.route.method.toUpperCase();
|
||||
var path = req.originalUrl;
|
||||
var shouldAvoid = false;
|
||||
_.each(avoid, function(pathToAvoid) {
|
||||
if (method === pathToAvoid[0] && pathToAvoid[1].exec(path)) {
|
||||
shouldAvoid = true;
|
||||
}
|
||||
});
|
||||
return !shouldAvoid;
|
||||
};
|
||||
|
||||
|
||||
module.exports = function(appium) {
|
||||
var rest = appium.rest
|
||||
, inject = function(req, res, next) {
|
||||
req.appium = appium;
|
||||
req.device = appium.device;
|
||||
logger.debug("Appium request initiated at " + req.url);
|
||||
if (typeof req.body === "object") {
|
||||
logger.debug("Request received with params: " + JSON.stringify(req.body));
|
||||
}
|
||||
next();
|
||||
};
|
||||
var rest = appium.rest;
|
||||
var inject = function(req, res, next) {
|
||||
req.appium = appium;
|
||||
req.device = appium.device;
|
||||
logger.debug("Appium request initiated at " + req.url);
|
||||
if (typeof req.body === "object") {
|
||||
logger.debug("Request received with params: " + JSON.stringify(req.body));
|
||||
}
|
||||
if (shouldProxy(req)) {
|
||||
logger.debug("Proxying command to " + req.device.proxyHost + ":" +
|
||||
req.device.proxyPort);
|
||||
var url = 'http://' + req.device.proxyHost + ':' + req.device.proxyPort +
|
||||
req.originalUrl;
|
||||
request(url, req.route.method.toUpperCase(), req.body,
|
||||
req.headers['content-type'], function(err, response, body) {
|
||||
if (err) return next(err);
|
||||
logger.debug("Proxied response received with status " +
|
||||
response.statusCode + ": " +
|
||||
JSON.stringify(body).slice(0, 1000));
|
||||
res.headers = response.headers;
|
||||
res.send(response.statusCode, body);
|
||||
});
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
};
|
||||
|
||||
// Make appium available to all REST http requests.
|
||||
rest.all('/wd/*', inject);
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
"use strict";
|
||||
|
||||
var errors = require('./errors')
|
||||
, adb = require('../uiautomator/adb')
|
||||
, _ = require('underscore')
|
||||
, request = require('./device').request
|
||||
, logger = require('../logger').get('appium')
|
||||
, status = require("./uiauto/lib/status")
|
||||
, exec = require('child_process').exec
|
||||
, fs = require('fs')
|
||||
, ncp = require('ncp')
|
||||
, async = require('async')
|
||||
, path = require('path')
|
||||
, UnknownError = errors.UnknownError;
|
||||
|
||||
var Selendroid = function(opts) {
|
||||
this.opts = opts;
|
||||
this.serverApk = null;
|
||||
this.appPackage = opts.appPackage;
|
||||
this.desiredCaps = opts.desiredCaps;
|
||||
this.onStop = function() {};
|
||||
this.selendroidSessionId = null;
|
||||
this.adb = null;
|
||||
this.isProxy = true;
|
||||
this.proxyHost = 'localhost';
|
||||
this.proxyPort = 8080;
|
||||
};
|
||||
|
||||
Selendroid.prototype.start = function(cb) {
|
||||
logger.info("Starting selendroid server");
|
||||
var opts = _.clone(this.opts)
|
||||
, me = this;
|
||||
opts.port = this.proxyPort;
|
||||
opts.devicePort = 8080;
|
||||
this.adb = new adb(opts);
|
||||
|
||||
async.waterfall([
|
||||
function(cb) { me.ensureServerExists(cb); },
|
||||
function(cb) { me.adb.startSelendroid(me.serverApk, cb); },
|
||||
function(res, cb) { me.waitForServer(cb); },
|
||||
function(cb) { me.createSession(cb); },
|
||||
], cb);
|
||||
};
|
||||
|
||||
Selendroid.prototype.stop = function(cb) {
|
||||
logger.info("Stopping selendroid server");
|
||||
this.deleteSession(function(err) {
|
||||
cb(err ? 1 : 0);
|
||||
});
|
||||
};
|
||||
|
||||
Selendroid.prototype.ensureServerExists = function(cb) {
|
||||
logger.info("Checking whether selendroid is built for package yet");
|
||||
var fileName = 'selendroid-' + this.appPackage + '.apk';
|
||||
var filePath = path.resolve(__dirname, "../selendroid/selendroid-server",
|
||||
"target", fileName);
|
||||
fs.stat(filePath, _.bind(function(err) {
|
||||
if (err) {
|
||||
logger.info("Selendroid needs to be built");
|
||||
return this.buildServer(cb);
|
||||
}
|
||||
logger.info("Selendroid server already exists, not rebuilding");
|
||||
this.serverApk = filePath;
|
||||
cb(null);
|
||||
}, this));
|
||||
};
|
||||
|
||||
Selendroid.prototype.buildServer = function(cb) {
|
||||
logger.info("Building selendroid server for package " + this.appPackage);
|
||||
var buildDir = path.resolve(__dirname, "../selendroid/selendroid-server");
|
||||
var src = buildDir + "/target/selendroid-server-0.3.apk";
|
||||
var dest = buildDir + "/target/selendroid-" + this.appPackage + '.apk';
|
||||
var cmd = "mvn install -Dandroid.renameInstrumentationTargetPackage=" +
|
||||
this.appPackage;
|
||||
exec(cmd, {cwd: buildDir}, _.bind(function(err, stdout, stderr) {
|
||||
if (err) {
|
||||
logger.error("Unable to build selendroid server. Stdout was: ");
|
||||
logger.error(stdout);
|
||||
logger.error(stderr);
|
||||
return cb(err);
|
||||
}
|
||||
logger.info("Copying selendroid server to correct destination");
|
||||
ncp(src, dest, _.bind(function(err) {
|
||||
if (err) {
|
||||
logger.error("Error copying selendroid to destination");
|
||||
return cb(err);
|
||||
}
|
||||
logger.info("Selendroid server copied successfully");
|
||||
this.serverApk = dest;
|
||||
cb(null);
|
||||
}, this));
|
||||
}, this));
|
||||
};
|
||||
|
||||
Selendroid.prototype.waitForServer = function(cb) {
|
||||
var waitMs = 20000
|
||||
, intMs = 800
|
||||
, start = Date.now();
|
||||
|
||||
var pingServer = _.bind(function() {
|
||||
this.proxyTo('/wd/hub/status', 'GET', null, function(err, res, body) {
|
||||
if (body === null || typeof body === "undefined" || !body.trim()) {
|
||||
if (Date.now() - start < waitMs) {
|
||||
setTimeout(pingServer, intMs);
|
||||
} else {
|
||||
cb(new Error("Waited " + (waitMs / 1000) + " secs for " +
|
||||
"selendroid server and it never showed up"));
|
||||
}
|
||||
} else {
|
||||
logger.info("Selendroid server is alive!");
|
||||
cb(null);
|
||||
}
|
||||
});
|
||||
}, this);
|
||||
|
||||
pingServer();
|
||||
};
|
||||
|
||||
Selendroid.prototype.createSession = function(cb) {
|
||||
var data = {desiredCapabilities: this.desiredCaps};
|
||||
this.proxyTo('/wd/hub/session', 'POST', data, _.bind(function(err, res, body) {
|
||||
if (err) return cb(err);
|
||||
|
||||
if (res.statusCode === 301 && body.sessionId) {
|
||||
logger.info("Successfully started selendroid session");
|
||||
this.selendroidSessionId = body.sessionId;
|
||||
cb(null, body.sessionId);
|
||||
} else {
|
||||
logger.error("Selendroid create session did not work. Status was " +
|
||||
res.statusCode + " and body was " + body);
|
||||
cb(new Error("Did not get session redirect from selendroid"));
|
||||
}
|
||||
}, this));
|
||||
};
|
||||
|
||||
Selendroid.prototype.deleteSession = function(cb) {
|
||||
var url = '/wd/hub/session/' + this.selendroidSessionId;
|
||||
this.proxyTo(url, 'DELETE', null, _.bind(function(err, res) {
|
||||
if (err) return cb(err);
|
||||
if (res.statusCode !== 200) return cb(new Error("Status was not 200"));
|
||||
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);
|
||||
};
|
||||
|
||||
module.exports = function(opts) {
|
||||
return new Selendroid(opts);
|
||||
};
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
"ncp": "~0.4.2",
|
||||
"swig": "~0.13.5",
|
||||
"async": "~0.2.6",
|
||||
"mkdirp": "~0.3.5",
|
||||
"binary-cookies": "~0.1.1"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -7,20 +7,24 @@
|
||||
set -e
|
||||
should_reset_android=0
|
||||
should_reset_ios=0
|
||||
should_reset_selendroid=0
|
||||
appium_home=$(pwd)
|
||||
selendroid_ver="0.3"
|
||||
|
||||
while test $# != 0
|
||||
do
|
||||
echo $1
|
||||
case "$1" in
|
||||
"--android") should_reset_android=1;;
|
||||
"--ios") should_reset_ios=1;;
|
||||
"--selendroid") should_reset_selendroid=1;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [[ $should_reset_android -eq 0 ]] && [[ $should_reset_ios -eq 0 ]]; then
|
||||
if [[ $should_reset_android -eq 0 ]] && [[ $should_reset_ios -eq 0 ]] && [[ $should_reset_selendroid -eq 0 ]]; then
|
||||
should_reset_android=1
|
||||
should_reset_ios=1
|
||||
should_reset_selendroid=1
|
||||
fi
|
||||
|
||||
reset_general() {
|
||||
@@ -63,12 +67,16 @@ reset_ios() {
|
||||
grunt buildApp:WebViewApp
|
||||
}
|
||||
|
||||
reset_android() {
|
||||
echo "---- RESETTING ANDROID ----"
|
||||
get_apidemos() {
|
||||
echo "Downloading/updating AndroidApiDemos"
|
||||
git submodule update --init submodules/ApiDemos
|
||||
rm -rf sample-code/apps/ApiDemos
|
||||
ln -s $(pwd)/submodules/ApiDemos $(pwd)/sample-code/apps/ApiDemos
|
||||
ln -s $appium_home/submodules/ApiDemos $appium_home/sample-code/apps/ApiDemos
|
||||
}
|
||||
|
||||
reset_android() {
|
||||
echo "---- RESETTING ANDROID ----"
|
||||
get_apidemos
|
||||
echo "Building Android bootstrap"
|
||||
grunt configAndroidBootstrap
|
||||
grunt buildAndroidBootstrap
|
||||
@@ -77,6 +85,16 @@ reset_android() {
|
||||
grunt buildAndroidApp:ApiDemos
|
||||
}
|
||||
|
||||
reset_selendroid() {
|
||||
echo "---- RESETTING SELENDROID ----"
|
||||
get_apidemos
|
||||
echo "Downloading/updating selendroid"
|
||||
rm -rf submodules/selendroid/selendroid-server/target
|
||||
git submodule update --init submodules/selendroid
|
||||
rm -rf selendroid
|
||||
ln -s $appium_home/submodules/selendroid $appium_home/selendroid
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
echo "---- CLEANING UP ----"
|
||||
echo "Cleaning temp files"
|
||||
@@ -92,4 +110,7 @@ fi
|
||||
if [ $should_reset_android -eq 1 ]; then
|
||||
reset_android
|
||||
fi
|
||||
if [ $should_reset_selendroid -eq 1 ]; then
|
||||
reset_selendroid
|
||||
fi
|
||||
cleanup
|
||||
|
||||
Submodule
+1
Submodule submodules/selendroid added at f3b5e7fdea
@@ -0,0 +1,34 @@
|
||||
/*global it:true */
|
||||
"use strict";
|
||||
|
||||
var path = require('path')
|
||||
, appPath = path.resolve(__dirname, "../../../sample-code/apps/ApiDemos/bin/ApiDemos-debug.apk")
|
||||
, appPkg = "com.example.android.apis"
|
||||
, appAct = "ApiDemos"
|
||||
, driverBlock = require("../../helpers/driverblock.js")
|
||||
, describeWd = driverBlock.describeForApp(appPath, "selendroid", appPkg, appAct)
|
||||
, should = require('should');
|
||||
|
||||
describeWd('basic', function(h) {
|
||||
it('should find and click an element', function(done) {
|
||||
// selendroid appears to have some issues with implicit waits
|
||||
// hence the timeouts
|
||||
setTimeout(function() {
|
||||
h.driver.elementByName('App', function(err, el) {
|
||||
should.not.exist(err);
|
||||
should.exist(el);
|
||||
el.click(function(err) {
|
||||
should.not.exist(err);
|
||||
setTimeout(function() {
|
||||
h.driver.elementByLinkText("Action Bar", function(err, el) {
|
||||
should.not.exist(err);
|
||||
should.exist(el);
|
||||
done();
|
||||
});
|
||||
}, 1000);
|
||||
});
|
||||
});
|
||||
}, 1000);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -108,22 +108,24 @@ var describeForApp = function(app, device, appPackage, appActivity) {
|
||||
if (typeof device === "undefined") {
|
||||
device = "ios";
|
||||
}
|
||||
var browserName, appPath;
|
||||
var browserName, appPath, realDevice;
|
||||
if (device === "ios") {
|
||||
realDevice = "iPhone Simulator";
|
||||
browserName = "iOS";
|
||||
} else if (device === "android") {
|
||||
browserName = "Android";
|
||||
browserName = realDevice = "Android";
|
||||
} else if (device === "selendroid") {
|
||||
browserName = realDevice = "Selendroid";
|
||||
}
|
||||
if (/\//.exec(app) || /\./.exec(app)) {
|
||||
appPath = app;
|
||||
} else {
|
||||
if (device === "ios") {
|
||||
appPath = path.resolve(__dirname, "../../sample-code/apps/" + app + "/build/Release-iphonesimulator/" + app + ".app");
|
||||
} else if (device === "android") {
|
||||
} else if (device === "android" || device === "selendroid") {
|
||||
appPath = path.resolve(__dirname, "../../sample-code/apps/" + app + "/bin/" + app + "-debug.apk");
|
||||
}
|
||||
}
|
||||
var realDevice = device == "ios" ? "iPhone Simulator" : "Android";
|
||||
|
||||
return function(desc, tests, host, port, caps, extraCaps) {
|
||||
if (typeof extraCaps === "undefined") {
|
||||
|
||||
+386
-276
@@ -11,6 +11,7 @@ var spawn = require('child_process').spawn
|
||||
, testZipArchive = require('../app/helpers').testZipArchive
|
||||
, async = require('async')
|
||||
, ncp = require('ncp')
|
||||
, mkdirp = require('mkdirp')
|
||||
, _ = require('underscore');
|
||||
|
||||
var noop = function() {};
|
||||
@@ -27,7 +28,8 @@ var ADB = function(opts, android) {
|
||||
// Don't uninstall if using fast reset.
|
||||
// Uninstall if reset is set and fast reset isn't.
|
||||
this.skipUninstall = opts.fastReset || !(opts.reset || false);
|
||||
this.port = opts.port || 4724;
|
||||
this.systemPort = opts.port || 4724;
|
||||
this.devicePort = opts.devicePort || 4724;
|
||||
this.avdName = opts.avdName;
|
||||
this.appPackage = opts.appPackage;
|
||||
this.appActivity = opts.appActivity;
|
||||
@@ -45,6 +47,7 @@ var ADB = function(opts, android) {
|
||||
this.portForwarded = false;
|
||||
this.debugMode = true;
|
||||
this.fastReset = opts.fastReset;
|
||||
this.cleanApp = opts.cleanApp || this.fastReset;
|
||||
this.cleanAPK = '/tmp/' + this.appPackage + '.clean.apk';
|
||||
// This is set to true when the bootstrap jar crashes.
|
||||
this.restartBootstrap = false;
|
||||
@@ -54,193 +57,192 @@ var ADB = function(opts, android) {
|
||||
this.cmdCb = null;
|
||||
};
|
||||
|
||||
ADB.prototype.checkAdbPresent = function(cb) {
|
||||
ADB.prototype.checkSdkBinaryPresent = function(binary, cb) {
|
||||
var binaryLoc = null;
|
||||
if (this.sdkRoot) {
|
||||
this.adb = path.resolve(this.sdkRoot, "platform-tools", "adb");
|
||||
this.debug("Using adb from " + this.adb);
|
||||
cb(null, this);
|
||||
binaryLoc = path.resolve(this.sdkRoot, "platform-tools", binary);
|
||||
this.debug("Using " + binary + " from " + binaryLoc);
|
||||
cb(null, binaryLoc);
|
||||
} else {
|
||||
exec("which adb", _.bind(function(err, stdout) {
|
||||
exec("which " + binary, _.bind(function(err, stdout) {
|
||||
if (stdout) {
|
||||
this.debug("Using adb from " + stdout);
|
||||
this.adb = stdout;
|
||||
cb(null, this);
|
||||
this.debug("Using " + binary + " from " + stdout);
|
||||
cb(null, stdout);
|
||||
} else {
|
||||
cb("Could not find adb; do you have android SDK installed?", null);
|
||||
cb(new Error("Could not find " + binary + "; do you have android " +
|
||||
"SDK installed?"),
|
||||
null);
|
||||
}
|
||||
}, this));
|
||||
}
|
||||
};
|
||||
|
||||
ADB.prototype.checkAdbPresent = function(cb) {
|
||||
this.checkSdkBinaryPresent("adb", _.bind(function(err, binaryLoc) {
|
||||
if (err) return cb(err);
|
||||
this.adb = binaryLoc;
|
||||
cb(null);
|
||||
}, this));
|
||||
};
|
||||
|
||||
ADB.prototype.retargetManifest = function(newPkg, newTarget, inFile, outFile, cb) {
|
||||
var data = fs.readFileSync(inFile, "utf8");
|
||||
var rePkg = /package="([^"]+)"/;
|
||||
var reTarget = /targetPackage="([^"]+)"/;
|
||||
var matchPkg = rePkg.exec(data);
|
||||
var matchTarget = reTarget.exec(data);
|
||||
|
||||
if (newPkg && !matchPkg) {
|
||||
logger.debug("Could not find package= in manifest");
|
||||
return cb("could not find package= in manifest");
|
||||
}
|
||||
|
||||
if (newTarget && !matchTarget) {
|
||||
logger.debug("Could not find targetPackage= in manifest");
|
||||
return cb("could not find targetPackage= in manifest");
|
||||
}
|
||||
|
||||
if (newPkg) {
|
||||
var newPkgData = matchPkg[0].replace(matchPkg[1], newPkg);
|
||||
data = data.replace(matchPkg[0], newPkgData);
|
||||
}
|
||||
|
||||
if (newTarget) {
|
||||
var newTargetData = matchTarget[0].replace(matchTarget[1], newTarget);
|
||||
data = data.replace(matchTarget[0], newTargetData);
|
||||
}
|
||||
|
||||
fs.writeFileSync(outFile, data, "utf8");
|
||||
logger.debug("Created manifest");
|
||||
cb(null);
|
||||
};
|
||||
|
||||
// Fast reset
|
||||
ADB.prototype.buildFastReset = function(skipAppSign, cb) {
|
||||
// Create manifest
|
||||
var me = this;
|
||||
var targetAPK = me.apkPath;
|
||||
var targetPackage = me.appPackage;
|
||||
// gsub AndroidManifest.xml using targetPackage
|
||||
var manifestPath = path.resolve(__dirname, '../app/android/AndroidManifest.xml.src');
|
||||
var cleanAPKSrc = path.resolve(__dirname, '../app/android/Clean.apk');
|
||||
var newPackage = me.appPackage + '.clean';
|
||||
var inFile = path.resolve(__dirname, '../app/android/AndroidManifest.xml.src');
|
||||
var outFile = inFile.substr(0, inFile.length - '.src'.length);
|
||||
|
||||
var utf8 = 'utf8';
|
||||
var data = fs.readFileSync(manifestPath, utf8);
|
||||
var manifest = manifestPath.substr(0, manifestPath.length - '.src'.length);
|
||||
var resignApks = function(cb) {
|
||||
// Resign clean apk and target apk
|
||||
var apks = [ me.cleanAPK ];
|
||||
if (!skipAppSign) {
|
||||
logger.debug("Signing app and clean apk.");
|
||||
apks.push(targetAPK);
|
||||
} else {
|
||||
logger.debug("Skip app sign. Sign clean apk.");
|
||||
}
|
||||
me.sign(apks, cb);
|
||||
};
|
||||
|
||||
var rePkg = /package="([^"]+)"/;
|
||||
var reTarget = /targetPackage="([^"]+)"/;
|
||||
var matchPkg = rePkg.exec(data);
|
||||
var matchTarget = reTarget.exec(data);
|
||||
|
||||
if (!matchPkg) {
|
||||
logger.debug("Could not find package= in manifest");
|
||||
return cb("could not find package= in manifest");
|
||||
}
|
||||
|
||||
if (!matchTarget) {
|
||||
logger.debug("Could not find targetPackage= in manifest");
|
||||
return cb("could not find targetPackage= in manifest");
|
||||
}
|
||||
|
||||
var newPkg = matchPkg[0].replace(matchPkg[1], targetPackage + '.clean');
|
||||
var newTarget = matchTarget[0].replace(matchTarget[1], targetPackage);
|
||||
|
||||
var newContent = data.replace(matchPkg[0], newPkg);
|
||||
newContent = newContent.replace(matchTarget[0], newTarget);
|
||||
|
||||
fs.writeFileSync(manifest, newContent, utf8);
|
||||
logger.debug("Created manifest");
|
||||
|
||||
async.series(
|
||||
[
|
||||
function(cb) {
|
||||
me.compileManifest(function(err) { if (err) return cb(err); cb(null); }, manifest);
|
||||
},
|
||||
function(cb) {
|
||||
me.insertManifest(manifest, skipAppSign, function(err) {
|
||||
if (err) return cb(err);
|
||||
cb(null);
|
||||
});
|
||||
},
|
||||
],
|
||||
// Invoke top level function cb
|
||||
function(err) { cb(err); }
|
||||
);
|
||||
async.series([
|
||||
function(cb) { me.retargetManifest(newPackage, me.appPackage, inFile, outFile, cb); },
|
||||
function(cb) { me.checkSdkBinaryPresent("aapt", cb); },
|
||||
function(cb) { me.compileManifest(outFile, cb); },
|
||||
function(cb) { me.insertManifest(outFile, cleanAPKSrc, me.cleanAPK, cb); },
|
||||
function(cb) { resignApks(cb); }
|
||||
], cb);
|
||||
};
|
||||
|
||||
ADB.prototype.compileManifest = function(cb, manifest) {
|
||||
async.series(
|
||||
[
|
||||
function(cb) {
|
||||
exec('which aapt', function(err, stdout) {
|
||||
if (!stdout) {
|
||||
return cb(new Error("Error finding aapt binary, is it on your path?"));
|
||||
}
|
||||
cb(null);
|
||||
});
|
||||
},
|
||||
function(cb) {
|
||||
var androidHome = process.env.ANDROID_HOME;
|
||||
ADB.prototype.insertSelendroidManifest = function(serverPath, cb) {
|
||||
var me = this
|
||||
, newServerPath = me.selendroidServerPath
|
||||
, srcManifest = path.resolve(__dirname, "../selendroid/selendroid-gem",
|
||||
"selendroid-prebuild/AndroidManifest.xml")
|
||||
, dstDir = '/tmp/' + this.appPackage
|
||||
, dstManifest = dstDir + '/AndroidManifest.xml';
|
||||
|
||||
var platforms = androidHome + '/platforms/';
|
||||
var platform = 'android-17';
|
||||
|
||||
// android-17 may be called android-4.2
|
||||
if (!fs.existsSync(platforms + platform)) {
|
||||
platform = 'android-4.2';
|
||||
|
||||
if (!fs.existsSync(platforms + platform)) {
|
||||
return cb("Platform doesn't exist " + platform);
|
||||
}
|
||||
}
|
||||
|
||||
// Compile manifest into manifest.xml.apk
|
||||
var compile_manifest = ['aapt package -M "', manifest,
|
||||
'" -I "', platforms + platform + '/android.jar" -F "',
|
||||
manifest, '.apk" -f'].join('');
|
||||
logger.debug(compile_manifest);
|
||||
exec(compile_manifest, {}, function(err, stdout, stderr) {
|
||||
if (err) {
|
||||
logger.debug(stderr);
|
||||
return cb("error compiling manifest");
|
||||
}
|
||||
logger.debug("Compiled manifest");
|
||||
cb(null);
|
||||
});
|
||||
},
|
||||
],
|
||||
// Invoke top level function cb
|
||||
function(err) { cb(err); }
|
||||
);
|
||||
async.series([
|
||||
function(cb) { mkdirp(dstDir, cb); },
|
||||
function(cb) { me.retargetManifest(null, me.appPackage, srcManifest,
|
||||
dstManifest, cb); },
|
||||
function(cb) { me.checkSdkBinaryPresent("aapt", cb); },
|
||||
function(cb) { me.compileManifest(dstManifest, cb); },
|
||||
function(cb) { me.insertManifest(dstManifest, serverPath, newServerPath,
|
||||
cb); }
|
||||
], cb);
|
||||
};
|
||||
|
||||
ADB.prototype.insertManifest = function(manifest, skipAppSign, cb) {
|
||||
var me = this;
|
||||
var targetAPK = me.apkPath;
|
||||
var cleanAPK = me.cleanAPK;
|
||||
async.series(
|
||||
[
|
||||
// Extract compiled manifest from manifest.xml.apk
|
||||
function(cb) {
|
||||
unzipFile(manifest + '.apk', function(err, stderr) {
|
||||
if (err) {
|
||||
logger.debug(stderr);
|
||||
return cb(err);
|
||||
}
|
||||
cb(null);
|
||||
});
|
||||
},
|
||||
// Insert compiled manfiest into /tmp/appPackage.clean.apk
|
||||
function(cb) {
|
||||
var cleanAPKSource = path.resolve(__dirname, '../app/android/Clean.apk');
|
||||
logger.debug("Writing tmp apk. " + cleanAPKSource + ' to ' + cleanAPK);
|
||||
ADB.prototype.compileManifest = function(manifest, cb) {
|
||||
var androidHome = process.env.ANDROID_HOME
|
||||
, platforms = androidHome + '/platforms/'
|
||||
, platform = 'android-17';
|
||||
|
||||
// Write clean apk to /tmp
|
||||
ncp(cleanAPKSource, cleanAPK, function(err) {
|
||||
if (err) return cb(err);
|
||||
cb(null);
|
||||
});
|
||||
},
|
||||
function(cb) {
|
||||
logger.debug("Testing tmp clean apk.");
|
||||
testZipArchive(cleanAPK, function(err) {
|
||||
if (err) return cb(err);
|
||||
cb(null);
|
||||
});
|
||||
},
|
||||
function(cb) {
|
||||
// -j = keep only the file, not the dirs
|
||||
// -m = move manifest into target apk.
|
||||
var replaceCmd = 'zip -j -m "' + cleanAPK + '" "' + manifest + '"';
|
||||
logger.debug(replaceCmd);
|
||||
exec(replaceCmd, {}, function(err) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
logger.debug("Inserted manifest.");
|
||||
cb(null);
|
||||
});
|
||||
},
|
||||
// Resign clean apk and target apk
|
||||
function(cb) {
|
||||
var apks = [ cleanAPK ];
|
||||
if (!skipAppSign) {
|
||||
logger.debug("Signing app and clean apk.");
|
||||
apks.push(targetAPK);
|
||||
} else {
|
||||
logger.debug("Skip app sign. Sign clean apk.");
|
||||
}
|
||||
me.sign(function(err) {
|
||||
if (err) return cb(err);
|
||||
cb(null);
|
||||
}, apks);
|
||||
// android-17 may be called android-4.2
|
||||
if (!fs.existsSync(platforms + platform)) {
|
||||
platform = 'android-4.2';
|
||||
|
||||
if (!fs.existsSync(platforms + platform)) {
|
||||
return cb(new Error("Platform doesn't exist " + platform));
|
||||
}
|
||||
}
|
||||
|
||||
// Compile manifest into manifest.xml.apk
|
||||
var compileManifest = ['aapt package -M "', manifest,
|
||||
'" -I "', platforms + platform + '/android.jar" -F "',
|
||||
manifest, '.apk" -f'].join('');
|
||||
logger.debug(compileManifest);
|
||||
exec(compileManifest, {}, function(err, stdout, stderr) {
|
||||
if (err) {
|
||||
logger.debug(stderr);
|
||||
return cb("error compiling manifest");
|
||||
}
|
||||
logger.debug("Compiled manifest");
|
||||
cb(null);
|
||||
});
|
||||
};
|
||||
|
||||
ADB.prototype.insertManifest = function(manifest, srcApk, dstApk, cb) {
|
||||
var extractManifest = function(cb) {
|
||||
// Extract compiled manifest from manifest.xml.apk
|
||||
unzipFile(manifest + '.apk', function(err, stderr) {
|
||||
if (err) {
|
||||
logger.debug(stderr);
|
||||
return cb(err);
|
||||
}
|
||||
],
|
||||
// Invoke top level function cb
|
||||
function(err) { cb(err); }
|
||||
);
|
||||
cb(null);
|
||||
});
|
||||
};
|
||||
|
||||
var createTmpApk = function(cb) {
|
||||
logger.debug("Writing tmp apk. " + srcApk + ' to ' + dstApk);
|
||||
ncp(srcApk, dstApk, cb);
|
||||
};
|
||||
|
||||
var testDstApk = function(cb) {
|
||||
logger.debug("Testing new tmp apk.");
|
||||
testZipArchive(dstApk, cb);
|
||||
};
|
||||
|
||||
var moveManifest = function(cb) {
|
||||
// Insert compiled manfiest into /tmp/appPackage.clean.apk
|
||||
// -j = keep only the file, not the dirs
|
||||
// -m = move manifest into target apk.
|
||||
var replaceCmd = 'zip -j -m "' + dstApk + '" "' + manifest + '"';
|
||||
logger.debug(replaceCmd);
|
||||
exec(replaceCmd, {}, function(err) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
logger.debug("Inserted manifest.");
|
||||
cb(null);
|
||||
});
|
||||
};
|
||||
|
||||
async.series([
|
||||
function(cb) { extractManifest(cb); },
|
||||
function(cb) { createTmpApk(cb); },
|
||||
function(cb) { testDstApk(cb); },
|
||||
function(cb) { moveManifest(cb); }
|
||||
], cb);
|
||||
};
|
||||
|
||||
// apks is an array of strings.
|
||||
ADB.prototype.sign = function(cb, apks) {
|
||||
ADB.prototype.sign = function(apks, cb) {
|
||||
var signPath = path.resolve(__dirname, '../app/android/sign.jar');
|
||||
var resign = 'java -jar "' + signPath + '" "' + apks.join('" "') + '" --override';
|
||||
logger.debug("Resigning: " + resign);
|
||||
@@ -250,10 +252,8 @@ ADB.prototype.sign = function(cb, apks) {
|
||||
return cb(new Error("Could not sign one or more apks. Are you sure " +
|
||||
"the file paths are correct: " +
|
||||
JSON.stringify(apks)));
|
||||
} else if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
cb(null);
|
||||
cb(err);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -278,6 +278,8 @@ ADB.prototype.checkFastReset = function(cb) {
|
||||
return cb(null);
|
||||
}
|
||||
|
||||
if (!this.appPackage) return cb(new Error("appPackage must be set."));
|
||||
|
||||
var me = this;
|
||||
me.checkApkCert(me.cleanAPK, function(cleanSigned){
|
||||
me.checkApkCert(me.apkPath, function(appSigned){
|
||||
@@ -287,7 +289,7 @@ ADB.prototype.checkFastReset = function(cb) {
|
||||
} else {
|
||||
if (!appSigned) {
|
||||
// Resign app apk because it's not signed.
|
||||
me.sign(function(err) { if (err) return cb(err); cb(null); }, [me.apkPath]);
|
||||
me.sign([me.apkPath], cb);
|
||||
} else {
|
||||
// App and clean are already existing and signed.
|
||||
cb(null);
|
||||
@@ -297,66 +299,163 @@ ADB.prototype.checkFastReset = function(cb) {
|
||||
});
|
||||
};
|
||||
|
||||
ADB.prototype.start = function(onReady, onExit) {
|
||||
ADB.prototype.getDeviceWithRetry = function(cb) {
|
||||
var me = this;
|
||||
var getDevices = function(innerCb) {
|
||||
me.getConnectedDevices(function(err, devices) {
|
||||
if (devices.length === 0 || err) {
|
||||
return innerCb(new Error("Could not find a connected Android device."));
|
||||
}
|
||||
innerCb(null);
|
||||
});
|
||||
};
|
||||
getDevices(function(err) {
|
||||
if (err) {
|
||||
logger.info("Could not find devices, restarting adb server...");
|
||||
me.restartAdb(function() {
|
||||
getDevices(cb);
|
||||
});
|
||||
} else {
|
||||
cb(null);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
ADB.prototype.prepareDevice = function(onReady) {
|
||||
var me = this;
|
||||
async.series([
|
||||
function(cb) { me.checkAdbPresent(cb); },
|
||||
function(cb) { me.getDeviceWithRetry(cb);},
|
||||
function(cb) { me.waitForDevice(cb); },
|
||||
function(cb) { me.checkFastReset(cb); }
|
||||
], onReady);
|
||||
};
|
||||
|
||||
ADB.prototype.startAppium = function(onReady, onExit) {
|
||||
var me = this
|
||||
, doRun = function(err) {
|
||||
if (err) return onReady(err);
|
||||
me.runBootstrap(onReady, onExit);
|
||||
};
|
||||
this.onExit = onExit;
|
||||
var doRun = _.bind(function() {
|
||||
this.runBootstrap(onReady, onExit);
|
||||
}, this);
|
||||
|
||||
logger.debug("Using fast reset? " + this.fastReset);
|
||||
|
||||
var me = this;
|
||||
async.series(
|
||||
[
|
||||
function(cb) {
|
||||
me.checkAdbPresent(function(err) { if (err) return onReady(err); cb(null); });
|
||||
},
|
||||
function(cb) {
|
||||
var getDevices = function(innerCb) {
|
||||
me.getConnectedDevices(function(err, devices) {
|
||||
if (devices.length === 0 || err) {
|
||||
innerCb("Could not find a connected Android device.");
|
||||
}
|
||||
innerCb(null);
|
||||
});
|
||||
};
|
||||
getDevices(function(err) {
|
||||
if (err) {
|
||||
logger.info("restarting...");
|
||||
me.restartAdb(function() {
|
||||
getDevices(cb);
|
||||
});
|
||||
} else {
|
||||
cb(null);
|
||||
}
|
||||
});
|
||||
},
|
||||
function(cb) {
|
||||
me.waitForDevice(function(err) { if (err) return onReady(err); cb(null); });
|
||||
},
|
||||
function(cb) {
|
||||
me.pushAppium(function(err) { if (err) return onReady(err); cb(null); });
|
||||
},
|
||||
function(cb) {
|
||||
me.forwardPort(function(err) { if (err) return onReady(err); cb(null); });
|
||||
},
|
||||
function(cb) {
|
||||
if (!me.appPackage) return onReady("appPackage must be set.");
|
||||
me.checkFastReset(function(err) {
|
||||
if (err) return onReady(err); cb(null);
|
||||
});
|
||||
},
|
||||
function(cb) {
|
||||
me.installApp(function(err) { if (err) return onReady(err); cb(null); });
|
||||
},
|
||||
function(cb) {
|
||||
me.startApp(function(err) {
|
||||
if (err) return onReady(err);
|
||||
doRun(function(){ cb(null); });
|
||||
});
|
||||
async.series([
|
||||
function(cb) { me.prepareDevice(cb); },
|
||||
function(cb) { me.installApp(cb); },
|
||||
function(cb) { me.forwardPort(cb); },
|
||||
function(cb) { me.pushAppium(cb); },
|
||||
function(cb) { me.startApp(cb); }
|
||||
], doRun);
|
||||
};
|
||||
|
||||
ADB.prototype.startSelendroid = function(serverPath, onReady) {
|
||||
var me = this
|
||||
, modServerExists = false;
|
||||
this.selendroidServerPath = serverPath.replace(/\.apk$/, ".mod.apk");
|
||||
|
||||
var checkModServerExists = function(cb) {
|
||||
fs.stat(me.selendroidServerPath, function(err) {
|
||||
modServerExists = !err;
|
||||
cb();
|
||||
});
|
||||
};
|
||||
|
||||
var conditionalUninstallSelendroid = function(cb) {
|
||||
if (!modServerExists) {
|
||||
// assume we're going to rebuild selendroid and therefore
|
||||
// need to uninstall it if it's already on device
|
||||
logger.info("Rebuilt selendroid apk does not exist, uninstalling " +
|
||||
"any instances of it on device to make way for new one");
|
||||
me.uninstallApk('org.openqa.selendroid', cb);
|
||||
} else {
|
||||
logger.info("Rebuilt selendroid apk exists, doing nothing");
|
||||
cb();
|
||||
}
|
||||
};
|
||||
|
||||
var conditionalInsertManifest = function(cb) {
|
||||
if (!modServerExists) {
|
||||
logger.info("Rebuilt selendroid server does not exist, inserting " +
|
||||
"modified manifest");
|
||||
me.insertSelendroidManifest(serverPath, cb);
|
||||
} else {
|
||||
logger.info("Rebuilt selendroid server already exists, no need to " +
|
||||
"rebuild it with a new manifest");
|
||||
cb();
|
||||
}
|
||||
};
|
||||
|
||||
var conditionalInstallSelendroid = function(cb) {
|
||||
me.checkAppInstallStatus('org.openqa.selendroid', function(e, installed) {
|
||||
if (!installed) {
|
||||
logger.info("Rebuilt selendroid is not installed, installing it");
|
||||
me.installApk(me.selendroidServerPath, cb);
|
||||
} else {
|
||||
logger.info("Rebuilt selendroid is already installed");
|
||||
cb();
|
||||
}
|
||||
]
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
async.series([
|
||||
function(cb) { me.prepareDevice(cb); },
|
||||
function(cb) { checkModServerExists(cb); },
|
||||
function(cb) { conditionalUninstallSelendroid(cb); },
|
||||
function(cb) { conditionalInsertManifest(cb); },
|
||||
function(cb) { me.checkSelendroidCerts(me.selendroidServerPath, cb); },
|
||||
function(cb) { conditionalInstallSelendroid(cb); },
|
||||
function(cb) { me.installApp(cb); },
|
||||
function(cb) { me.forwardPort(cb); },
|
||||
function(cb) { me.pushSelendroid(cb); },
|
||||
function(cb) { logger.info("Selendroid server is launching"); cb(); }
|
||||
], onReady);
|
||||
};
|
||||
|
||||
ADB.prototype.pushSelendroid = function(cb) {
|
||||
var cmd = "adb shell am instrument -e main_activity '" + this.appPackage +
|
||||
"." + this.appActivity + "' org.openqa.selendroid/" +
|
||||
"org.openqa.selendroid.ServerInstrumentation";
|
||||
logger.info("Starting instrumentation process for selendroid with cmd: " +
|
||||
cmd);
|
||||
exec(cmd, function(err, stdout) {
|
||||
if (stdout.indexOf("Exception") !== -1) {
|
||||
logger.error(stdout);
|
||||
var msg = stdout.split("\n")[0] || "Unknown exception starting selendroid";
|
||||
return cb(new Error(msg));
|
||||
}
|
||||
cb();
|
||||
});
|
||||
};
|
||||
|
||||
ADB.prototype.checkSelendroidCerts = function(serverPath, cb) {
|
||||
var me = this
|
||||
, alreadyReturned = false
|
||||
, checks = 0;
|
||||
|
||||
var onDoneSigning = function() {
|
||||
checks++;
|
||||
if (checks === 2 && !alreadyReturned) {
|
||||
cb();
|
||||
}
|
||||
};
|
||||
|
||||
// these run in parallel
|
||||
var apks = [serverPath, this.apkPath];
|
||||
_.each(apks, function(apk) {
|
||||
logger.info("Checking signed status of " + apk);
|
||||
me.checkApkCert(apk, function(isSigned) {
|
||||
if (isSigned) return onDoneSigning();
|
||||
me.sign([apk], function(err) {
|
||||
if (err && !alreadyReturned) {
|
||||
alreadyReturned = true;
|
||||
return cb(err);
|
||||
}
|
||||
onDoneSigning();
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
ADB.prototype.getConnectedDevices = function(cb) {
|
||||
@@ -383,9 +482,9 @@ ADB.prototype.getConnectedDevices = function(cb) {
|
||||
|
||||
ADB.prototype.forwardPort = function(cb) {
|
||||
this.requireDeviceId();
|
||||
var devicePort = 4724;
|
||||
this.debug("Forwarding system:" + this.port + " to device:" + devicePort);
|
||||
var arg = "tcp:" + this.port + " tcp:" + devicePort;
|
||||
this.debug("Forwarding system:" + this.systemPort + " to device:" +
|
||||
this.devicePort);
|
||||
var arg = "tcp:" + this.systemPort + " tcp:" + this.devicePort;
|
||||
exec(this.adbCmd + " forward " + arg, _.bind(function(err) {
|
||||
if (err) {
|
||||
logger.error(err);
|
||||
@@ -445,7 +544,7 @@ ADB.prototype.checkForSocketReady = function(output) {
|
||||
if (/Appium Socket Server Ready/.exec(output)) {
|
||||
this.requirePortForwarded();
|
||||
this.debug("Connecting to server on device...");
|
||||
this.socketClient = net.connect(this.port, _.bind(function() {
|
||||
this.socketClient = net.connect(this.systemPort, _.bind(function() {
|
||||
this.debug("Connected!");
|
||||
this.onSocketReady(null);
|
||||
}, this));
|
||||
@@ -657,13 +756,13 @@ ADB.prototype.waitForDevice = function(cb) {
|
||||
ADB.prototype.restartAdb = function(cb) {
|
||||
logger.info("Killing ADB server so it will come back online");
|
||||
var cmd = this.adb + " kill-server";
|
||||
exec(cmd, _.bind(function(err) {
|
||||
exec(cmd, function(err) {
|
||||
if (err) {
|
||||
logger.error("Error killing ADB server, going to see if it's online " +
|
||||
"anyway");
|
||||
}
|
||||
cb();
|
||||
}, this));
|
||||
});
|
||||
};
|
||||
|
||||
ADB.prototype.pushAppium = function(cb) {
|
||||
@@ -850,58 +949,69 @@ ADB.prototype.runFastReset = function(cb) {
|
||||
});
|
||||
};
|
||||
|
||||
ADB.prototype.checkAppInstallStatus = function(pkg, cb) {
|
||||
var installed = false
|
||||
, cleanInstalled = false;
|
||||
this.requireDeviceId();
|
||||
|
||||
logger.debug("Getting install/clean status for " + pkg);
|
||||
var listPkgCmd = this.adbCmd + " shell pm list packages -3 " + pkg;
|
||||
console.log(listPkgCmd);
|
||||
exec(listPkgCmd, function(err, stdout) {
|
||||
//console.log("Packages found:");
|
||||
//console.log(stdout);
|
||||
var apkInstalledRgx = new RegExp('^package:' +
|
||||
pkg.replace(/([^a-zA-Z])/g, "\\$1") + '$', 'm');
|
||||
installed = !!apkInstalledRgx.test(stdout);
|
||||
var cleanInstalledRgx = new RegExp('^package:' +
|
||||
(pkg + '.clean').replace(/([^a-zA-Z])/g, "\\$1") + '$', 'm');
|
||||
cleanInstalled = !!cleanInstalledRgx.test(stdout);
|
||||
cb(null, installed, cleanInstalled);
|
||||
});
|
||||
};
|
||||
|
||||
ADB.prototype.installApp = function(cb) {
|
||||
var me = this;
|
||||
var next = function() {
|
||||
me.requireDeviceId();
|
||||
me.requireApk();
|
||||
var installApp = false;
|
||||
var installClean = false;
|
||||
async.series(
|
||||
[
|
||||
function(cb) {
|
||||
var listPkgCmd = me.adbCmd + " shell pm list packages -3 " + me.appPackage;
|
||||
exec(listPkgCmd, function(err, stdout) {
|
||||
var apkInstalledRgx = new RegExp('^package:' + me.appPackage.replace(/([^a-zA-Z])/g, "\\$1") + '$', 'm');
|
||||
installApp = ! apkInstalledRgx.test(stdout);
|
||||
var cleanInstalledRgx = new RegExp('^package:' + (me.appPackage + '.clean').replace(/([^a-zA-Z])/g, "\\$1") + '$', 'm');
|
||||
installClean = ! cleanInstalledRgx.test(stdout);
|
||||
me.debug("Packages found:");
|
||||
me.debug(stdout);
|
||||
cb(null);
|
||||
});
|
||||
},
|
||||
function(cb) {
|
||||
if (installApp) {
|
||||
me.debug("Installing app apk");
|
||||
me.installApk(me.apkPath, function(err) {
|
||||
if (err) return cb(err);
|
||||
return cb(null);
|
||||
});
|
||||
} else { cb(null); }
|
||||
},
|
||||
function(cb) {
|
||||
if (installClean) {
|
||||
me.debug("Installing clean apk");
|
||||
me.installApk(me.cleanAPK, function(err) { if (err) return cb(err); return cb(null); });
|
||||
} else { cb(null); }
|
||||
},
|
||||
function(cb) {
|
||||
// App is already installed so reset it.
|
||||
if (!installApp) {
|
||||
me.runFastReset(function(err) {
|
||||
if (err) return cb(err);
|
||||
return cb(null);
|
||||
});
|
||||
} else { cb(null); }
|
||||
}
|
||||
],
|
||||
// Top level callback.
|
||||
function(err) { cb(err); }
|
||||
);
|
||||
var me = this
|
||||
, installApp = false
|
||||
, installClean = false;
|
||||
me.requireDeviceId();
|
||||
me.requireApk();
|
||||
|
||||
var determineInstallAndCleanStatus = function(cb) {
|
||||
me.checkAppInstallStatus(me.appPackage, function(err, installed, cleaned) {
|
||||
installApp = !installed;
|
||||
installClean = !cleaned;
|
||||
cb();
|
||||
});
|
||||
};
|
||||
|
||||
next();
|
||||
var doInstall = function(cb) {
|
||||
if (installApp) {
|
||||
me.debug("Installing app apk");
|
||||
me.installApk(me.apkPath, cb);
|
||||
} else { cb(null); }
|
||||
};
|
||||
|
||||
var doClean = function(cb) {
|
||||
if (installClean && me.cleanApp) {
|
||||
me.debug("Installing clean apk");
|
||||
me.installApk(me.cleanAPK, cb);
|
||||
} else { cb(null); }
|
||||
};
|
||||
|
||||
var doFastReset = function(cb) {
|
||||
// App is already installed so reset it.
|
||||
if (!installApp && me.fastReset) {
|
||||
me.runFastReset(cb);
|
||||
} else { cb(null); }
|
||||
};
|
||||
|
||||
async.series([
|
||||
function(cb) { determineInstallAndCleanStatus(cb); },
|
||||
function(cb) { doInstall(cb); },
|
||||
function(cb) { doClean(cb); },
|
||||
function(cb) { doFastReset(cb); }
|
||||
], cb);
|
||||
};
|
||||
|
||||
ADB.prototype.back = function(cb) {
|
||||
|
||||
Reference in New Issue
Block a user