run commands from uiautomator.js, and migrate activity waiting methods

This commit is contained in:
Jonathan Lipps
2013-10-21 12:04:30 -07:00
parent 44c64db8e2
commit 1140908666
4 changed files with 116 additions and 173 deletions

View File

@@ -1,5 +1,6 @@
"use strict";
var spawn = require('win-spawn')
, exec = require('child_process').exec
, path = require('path')
@@ -8,7 +9,6 @@ var spawn = require('win-spawn')
, logger = require('../../server/logger.js').get('appium')
, async = require('async')
, ncp = require('ncp')
, mkdirp = require('mkdirp')
, _ = require('underscore')
, helpers = require('../../helpers.js')
, unzipFile = helpers.unzipFile
@@ -32,14 +32,6 @@ var ADB = function(opts, android) {
this.compressXml = opts.compressXml;
this.sdkRoot = opts.sdkRoot;
this.udid = opts.udid;
// 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.fastReset = opts.fastReset;
this.cleanApp = opts.cleanApp || this.fastReset;
this.systemPort = opts.systemPort || 4724;
this.internalDevicePort = opts.devicePort || 4724;
this.avdName = opts.avdName;
this.appPackage = opts.appPackage;
this.appActivity = opts.appActivity;
this.appWaitActivity = opts.appWaitActivity;
@@ -155,32 +147,6 @@ ADB.prototype.spawn = function(args) {
return spawn(adbCmd, args);
};
ADB.prototype.insertSelendroidManifest = function(serverPath, cb) {
logger.info("Inserting selendroid manifest");
var newServerPath = this.selendroidServerPath
, newPackage = this.appPackage + '.selendroid'
, srcManifest = path.resolve(__dirname, '..', '..', '..', 'build',
'selendroid', 'AndroidManifest.xml')
, dstDir = path.resolve(getTempPath(), this.appPackage)
, dstManifest = path.resolve(dstDir, 'AndroidManifest.xml');
try {
fs.mkdirSync(dstDir);
} catch (e) {
if (e.message.indexOf("EEXIST") === -1) {
throw e;
}
}
fs.writeFileSync(dstManifest, fs.readFileSync(srcManifest, "utf8"), "utf8");
async.series([
function(cb) { mkdirp(dstDir, cb); }.bind(this),
function(cb) { this.checkSdkBinaryPresent("aapt", cb); }.bind(this),
function(cb) { this.compileManifest(dstManifest, newPackage, this.appPackage, cb); }.bind(this),
function(cb) { this.insertManifest(dstManifest, serverPath, newServerPath,
cb); }.bind(this)
], cb);
};
ADB.prototype.compileManifest = function(manifest, manifestPackage, targetPackage, cb) {
logger.info("Compiling manifest " + manifest);
@@ -342,7 +308,7 @@ ADB.prototype.sign = function(apks, cb) {
};
// returns true when already signed, false otherwise.
ADB.prototype.checkApkCert = function(apk, cb) {
ADB.prototype.checkApkCert = function(apk, pkg, cb) {
if (!fs.existsSync(apk)) {
logger.debug("APK doesn't exist. " + apk);
return cb(false);
@@ -383,7 +349,7 @@ ADB.prototype.checkApkCert = function(apk, cb) {
entry = entry.entryName;
if (!rsa.test(entry)) return next();
logger.debug("Entry: " + entry);
var entryPath = path.join(getTempPath(), this.appPackage, 'cert');
var entryPath = path.join(getTempPath(), pkg, 'cert');
logger.debug("entryPath: " + entryPath);
var entryFile = path.join(entryPath, entry);
logger.debug("entryFile: " + entryFile);
@@ -567,20 +533,6 @@ ADB.prototype.forwardPort = function(systemPort, devicePort, cb) {
this.exec("forward tcp:" + systemPort + " tcp:" + devicePort, cb);
};
ADB.prototype.sendShutdownCommand = function(cb) {
setTimeout(function() {
if (!this.alreadyExited) {
logger.warn("Android did not shut down fast enough, calling it gone");
this.alreadyExited = true;
this.onExit(1);
}
}.bind(this), 7000);
this.sendCommand('shutdown', null, cb);
if (this.logcat !== null) {
this.logcat.stopCapture();
}
};
ADB.prototype.isDeviceConnected = function(cb) {
this.getConnectedDevices(function(err, devices) {
if (err) {
@@ -685,152 +637,107 @@ ADB.prototype.getLogcatLogs = function() {
return this.logcat.getLogs();
};
ADB.prototype.startApp = function(cb) {
logger.info("Starting app");
this.requireDeviceId();
this.requireApp();
var activityString = this.appActivity;
var cmd = this.adbCmd + " shell am start -n " + this.appPackage + "/" +
activityString;
this.debug("Starting app\n" + cmd);
exec(cmd, { maxBuffer: 524288 }, function(err, stdout) {
if(err) {
logger.error(err);
cb(err);
} else {
if (stdout.indexOf("Error: Activity class") !== -1 &&
stdout.indexOf("does not exist") !== -1) {
if (this.appActivity[0] !== ".") {
logger.info("We tried to start an activity that doesn't exist, " +
"retrying with . prepended to activity");
this.appActivity = "." + this.appActivity;
return this.startApp(cb);
} else {
var msg = "Activity used to start app doesn't exist! Make sure " +
"it exists";
logger.error(msg);
return cb(new Error(msg));
}
ADB.prototype.startApp = function(pkg, activity, waitActivity, cb) {
var cmd = "am start -n " + pkg + "/" + activity;
this.shell(cmd, function(err, stdout) {
if(err) return cb(err);
if (stdout.indexOf("Error: Activity class") !== -1 &&
stdout.indexOf("does not exist") !== -1) {
if (activity[0] !== ".") {
logger.info("We tried to start an activity that doesn't exist, " +
"retrying with . prepended to activity");
activity = "." + activity;
return this.startApp(pkg, activity, cb);
} else {
var msg = "Activity used to start app doesn't exist! Make sure " +
"it exists";
logger.error(msg);
return cb(new Error(msg));
}
this.waitForActivity(cb);
}
this.waitForActivity(pkg, waitActivity, cb);
}.bind(this));
};
ADB.prototype.stopApp = function(cb) {
logger.info("Killing app");
this.requireDeviceId();
this.requireApp();
var cmd = this.adbCmd + " shell am force-stop " + this.appPackage;
exec(cmd, { maxBuffer: 524288 }, function(err) {
if (err) {
logger.error(err);
return cb(err);
}
cb();
});
};
ADB.prototype.getFocusedPackageAndActivity = function(cb) {
logger.info("Getting focused package and activity");
this.requireDeviceId();
var cmd = this.adbCmd + " shell dumpsys window windows"
var cmd = "dumpsys window windows"
, searchRe = new RegExp(/mFocusedApp.+ ([a-zA-Z0-9\._]+)\/(\.?[^\}]+)\}/);
exec(cmd, { maxBuffer: 524288 }, function(err, stdout) {
if (err) {
logger.error(err);
cb(err);
} else {
var foundMatch = false;
_.each(stdout.split("\n"), function(line) {
var match = searchRe.exec(line);
if (match) {
foundMatch = match;
}
});
if (foundMatch) {
cb(null, foundMatch[1].trim(), foundMatch[2].trim());
} else {
cb(new Error("Could not parse activity from dumpsys"));
this.shell(cmd, function(err, stdout) {
if (err) return cb(err);
var foundMatch = false;
_.each(stdout.split("\n"), function(line) {
var match = searchRe.exec(line);
if (match) {
foundMatch = match;
}
});
if (foundMatch) {
cb(null, foundMatch[1].trim(), foundMatch[2].trim());
} else {
cb(new Error("Could not parse activity from dumpsys"));
}
}.bind(this));
};
ADB.prototype.waitForNotActivity = function(cb) {
this.requireApp();
ADB.prototype.waitForActivityOrNot = function(pkg, activity, not,
waitMs, cb) {
if (typeof waitMs === "function") {
cb = waitMs;
waitMs = 20000;
}
logger.info("Waiting for app's activity to not be focused");
var waitMs = 20000
, intMs = 750
, endAt = Date.now() + waitMs
, targetActivity = this.appWaitActivity || this.appActivity;
if (targetActivity.indexOf(this.appPackage) === 0) {
targetActivity = targetActivity.substring(this.appPackage.length);
}
var getFocusedApp = function() {
this.getFocusedPackageAndActivity(function(err, foundPackage,
foundActivity) {
var notFoundAct = true;
_.each(targetActivity.split(','), function(act) {
act = act.trim();
if (act === foundActivity || "." + act === foundActivity) {
notFoundAct = false;
}
});
if (foundPackage !== this.appPackage && notFoundAct) {
cb(null);
} else if (Date.now() < endAt) {
if (err) logger.info(err);
setTimeout(getFocusedApp, intMs);
} else {
var msg = "App never closed. appActivity: " +
foundActivity + " != " + targetActivity;
logger.error(msg);
cb(new Error(msg));
}
var intMs = 750
, endAt = Date.now() + waitMs;
}.bind(this));
}.bind(this);
getFocusedApp();
};
ADB.prototype.waitForActivity = function(cb, waitMsOverride) {
this.requireApp();
logger.info("Waiting for app's activity to become focused");
var waitMs = waitMsOverride || 20000
, intMs = 750
, endAt = Date.now() + waitMs
, targetActivity = this.appWaitActivity || this.appActivity;
if (targetActivity.indexOf(this.appPackage) === 0) {
targetActivity = targetActivity.substring(this.appPackage.length);
if (activity.indexOf(pkg) === 0) {
activity = activity.substring(pkg.length);
}
var getFocusedApp = function() {
this.getFocusedPackageAndActivity(function(err, foundPackage,
foundActivity) {
var foundAct = false;
_.each(targetActivity.split(','), function(act) {
var checkForActivity = function(foundPackage, foundActivity) {
var foundAct = false;
if (foundPackage === pkg) {
_.each(activity.split(','), function(act) {
act = act.trim();
if (act === foundActivity || "." + act === foundActivity) {
foundAct = true;
}
});
if (foundPackage === this.appPackage && foundAct) {
cb(null);
}
return foundAct;
};
var wait = function() {
this.getFocusedPackageAndActivity(function(err, foundPackage,
foundActivity) {
var foundAct = checkForActivity(foundPackage, foundActivity);
if ((!not && foundAct) || (not && !foundAct)) {
cb();
} else if (Date.now() < endAt) {
if (err) logger.info(err);
setTimeout(getFocusedApp, intMs);
setTimeout(wait, intMs);
} else {
var msg = "App never showed up as active. appActivity: " +
foundActivity + " != " + targetActivity;
var verb = not ? "stopped" : "started";
var msg = "Activity " + activity + " never " + verb + ". Current: " +
foundPackage + "/" + foundActivity;
logger.error(msg);
cb(new Error(msg));
}
}.bind(this));
}.bind(this);
getFocusedApp();
wait();
};
ADB.prototype.waitForActivity = function(pkg, act, waitMs, cb) {
this.waitForActivityOrNot(pkg, act, false, waitMs, cb);
};
ADB.prototype.waitForActivity = function(pkg, act, waitMs, cb) {
this.waitForActivityOrNot(pkg, act, true, waitMs, cb);
};
ADB.prototype.uninstallApk = function(pkg, cb) {

View File

@@ -83,7 +83,7 @@ androidCommon.installApp = function(cb) {
if (installed && this.fastReset) {
this.adb.stopAndClear(this.appPackage, cb);
} else if (!installed) {
this.adb.checkAndSignApk(this.apkPath, function(err) {
this.adb.checkAndSignApk(this.apkPath, this.appPackage, function(err) {
if (err) return cb(err);
this.adb.mkdir(this.remoteTempPath(), function(err) {
if (err) return cb(err);

View File

@@ -171,8 +171,8 @@ Android.prototype.startAppium = function(onLaunch, onExit) {
this.pushAppium.bind(this),
this.pushUnlock.bind(this),
this.uiautomator.start.bind(this.uiautomator),
this.wakeUp.bind(this),
this.unlockScreen.bind(this),
this.adb.wakeUp.bind(this.adb),
this.adb.unlockScreen.bind(this.adb),
this.startApp.bind(this)
], function(err) {
onLaunch(err);
@@ -224,6 +224,11 @@ Android.prototype.pushAppium = function(cb) {
}.bind(this));
};
Android.prototype.startApp = function(cb) {
this.adb.startApp(this.appPackage, this.appActivity, this.appWaitActivity,
cb);
};
Android.prototype.timeoutWaitingForCommand = function() {
logger.info("Didn't get a new command in " + (this.commandTimeoutMs / 1000) +
" secs, shutting down...");
@@ -278,8 +283,11 @@ Android.prototype.stop = function(cb) {
};
Android.prototype.shutdown = function() {
this.adb.sendShutdownCommand(function() {
this.uiautomator.sendShutdownCommand(function() {
logger.info("Sent shutdown command, waiting for ADB to stop...");
if (this.logcat !== null) {
this.logcat.stopCapture();
}
}.bind(this));
};
@@ -324,7 +332,7 @@ Android.prototype.push = function(elem) {
this.progress++;
if (this.adb && !this.shuttingDown) {
this.adb.sendAutomatorCommand(action, params, function(response) {
this.uiautomator.sendAction(action, params, function(response) {
this.cbForCurrentCmd = null;
if (typeof cb === 'function') {
this.respond(response, cb);

View File

@@ -231,7 +231,8 @@ Selendroid.prototype.createSession = function(cb) {
if (res.statusCode === 301 && body.sessionId) {
logger.info("Successfully started selendroid session");
this.selendroidSessionId = body.sessionId;
this.adb.waitForActivity(function(err) {
this.adb.waitForActivity(this.appPackage, this.appActivity, 1800,
function(err) {
if (err) {
logger.info("Selendroid hasn't started app yet, let's do it " +
"manually with adb.startApp");
@@ -263,6 +264,33 @@ Selendroid.prototype.proxyTo = proxyTo;
Selendroid.prototype.getLog = getLog;
Selendroid.prototype.getLogTypes = getLogTypes;
ADB.prototype.insertSelendroidManifest = function(serverPath, cb) {
logger.info("Inserting selendroid manifest");
var newServerPath = this.selendroidServerPath
, newPackage = this.appPackage + '.selendroid'
, srcManifest = path.resolve(__dirname, '..', '..', '..', 'build',
'selendroid', 'AndroidManifest.xml')
, dstDir = path.resolve(getTempPath(), this.appPackage)
, dstManifest = path.resolve(dstDir, 'AndroidManifest.xml');
try {
fs.mkdirSync(dstDir);
} catch (e) {
if (e.message.indexOf("EEXIST") === -1) {
throw e;
}
}
fs.writeFileSync(dstManifest, fs.readFileSync(srcManifest, "utf8"), "utf8");
async.series([
function(cb) { mkdirp(dstDir, cb); }.bind(this),
function(cb) { this.checkSdkBinaryPresent("aapt", cb); }.bind(this),
function(cb) { this.compileManifest(dstManifest, newPackage, this.appPackage, cb); }.bind(this),
function(cb) { this.insertManifest(dstManifest, serverPath, newServerPath,
cb); }.bind(this)
], cb);
};
module.exports = function(opts) {
return new Selendroid(opts);
};