diff --git a/lib/devices/android/adb.js b/lib/devices/android/adb.js index 674d753a6..72ea2ee97 100644 --- a/lib/devices/android/adb.js +++ b/lib/devices/android/adb.js @@ -113,6 +113,9 @@ ADB.prototype.checkAdbPresent = function(cb) { }; ADB.prototype.exec = function(cmd, cb) { + if (!cmd) { + return cb(new Error("You need to pass in a command to exec()")); + } cmd = this.adbCmd + ' ' + cmd; logger.debug("executing: " + cmd); exec(cmd, {maxBuffer: 524288}, function(err, stdout, stderr) { @@ -432,7 +435,7 @@ ADB.prototype.getEmulatorPort = function(cb) { cb(new Error("No devices connected")); } else { // pick first device - var port = this.getPortFromEmulatorString(devices[0]); + var port = this.getPortFromEmulatorString(devices[0].udid); if (port) { cb(null, port); } else { @@ -454,6 +457,14 @@ ADB.prototype.push = function(localPath, remotePath, cb) { this.exec('push ' + localPath + ' ' + remotePath, cb); }; +ADB.prototype.pull = function(remotePath, localPath, cb) { + try { + localPath = JSON.parse(localPath); + } catch(e) { } + localPath = JSON.stringify(localPath); + this.exec('pull ' + remotePath + ' ' + localPath, cb); +}; + ADB.prototype.getPortFromEmulatorString = function(emStr) { var portPattern = /emulator-(\d+)/; if (portPattern.test(emStr)) { @@ -637,6 +648,11 @@ ADB.prototype.getLogcatLogs = function() { }; ADB.prototype.startApp = function(pkg, activity, waitActivity, cb) { + if (typeof waitActivity === "function") { + cb = waitActivity; + waitActivity = false; + } + var cmd = "am start -n " + pkg + "/" + activity; this.shell(cmd, function(err, stdout) { if(err) return cb(err); @@ -655,7 +671,11 @@ ADB.prototype.startApp = function(pkg, activity, waitActivity, cb) { } } - this.waitForActivity(pkg, waitActivity, cb); + if (waitActivity) { + this.waitForActivity(pkg, waitActivity, cb); + } else { + cb(); + } }.bind(this)); }; @@ -735,7 +755,7 @@ ADB.prototype.waitForActivity = function(pkg, act, waitMs, cb) { this.waitForActivityOrNot(pkg, act, false, waitMs, cb); }; -ADB.prototype.waitForActivity = function(pkg, act, waitMs, cb) { +ADB.prototype.waitForNotActivity = function(pkg, act, waitMs, cb) { this.waitForActivityOrNot(pkg, act, true, waitMs, cb); }; @@ -798,8 +818,8 @@ ADB.prototype.instrument = function(pkg, activity, instrumentWith, cb) { }); }; -ADB.prototype.checkAndSignApk = function(apk, cb) { - this.checkApkCert(apk, function(err, appSigned) { +ADB.prototype.checkAndSignApk = function(apk, pkg, cb) { + this.checkApkCert(apk, pkg, function(err, appSigned) { if (err) return cb(err); if (!appSigned) { this.sign([apk], cb); @@ -809,14 +829,6 @@ ADB.prototype.checkAndSignApk = function(apk, cb) { }.bind(this)); }; - -ADB.prototype.pushAndInstallApp = function(apk, remoteApk, cb) { - this.push(apk, remoteApk, function(err) { - if (err) return err; - this.installRemote(remoteApk, cb); - }); -}; - ADB.prototype.forceStop = function(pkg, cb) { this.shell('am force-stop ' + pkg, cb); }; @@ -842,8 +854,9 @@ ADB.prototype.isAppInstalled = function(pkg, cb) { var apkInstalledRgx = new RegExp('^package:' + pkg.replace(/([^a-zA-Z])/g, "\\$1") + '$', 'm'); installed = apkInstalledRgx.test(stdout); + this.debug("App is " + (!installed ? "not" : "") + " installed"); cb(null, installed); - }); + }.bind(this)); }; ADB.prototype.back = function(cb) { @@ -868,7 +881,7 @@ ADB.prototype.keyevent = function(keycode, cb) { ADB.prototype.isScreenLocked = function(cb) { var cmd = "dumpsys window"; - this.exec(cmd, function(err, stdout) { + this.shell(cmd, function(err, stdout) { if (err) return cb(err); var screenLocked = /mShowingLockscreen=\w+/gi.exec(stdout); diff --git a/lib/devices/android/android-common.js b/lib/devices/android/android-common.js index ea64d0670..42104d99a 100644 --- a/lib/devices/android/android-common.js +++ b/lib/devices/android/android-common.js @@ -47,7 +47,7 @@ androidCommon.prepareEmulator = function(cb) { return cb(); } this.adb.launchAVD(this.avdName, cb); - }); + }.bind(this)); } else { cb(); } @@ -64,12 +64,14 @@ androidCommon.prepareActiveDevice = function(cb) { } deviceId = this.udid; } else { - var emPort = this.adb.getPortFromEmulatorString(devices[0].udid); + deviceId = devices[0].udid; + var emPort = this.adb.getPortFromEmulatorString(deviceId); this.adb.setEmulatorPort(emPort); } - this.debug("Setting device id to " + deviceId); + logger.info("Setting device id to " + deviceId); this.adb.setDeviceId(deviceId); - }); + cb(); + }.bind(this)); }; androidCommon.installApp = function(cb) { @@ -80,20 +82,27 @@ androidCommon.installApp = function(cb) { } this.adb.isAppInstalled(this.appPackage, function(err, installed) { - if (installed && this.fastReset) { + if (installed && this.opts.fastReset) { this.adb.stopAndClear(this.appPackage, cb); } else if (!installed) { 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); - this.getAppMd5(function(err, md5) { - var remoteApk = this.remoteTempPath() + md5 + '.apk'; + this.getAppMd5(function(err, md5Hash) { + var remoteApk = this.remoteTempPath() + md5Hash + '.apk'; if (err) return cb(err); - this.removeTempApks([md5], function(err, appExists) { + this.removeTempApks([md5Hash], function(err, appExists) { if (err) return cb(err); - if (appExists) return cb(); - this.adb.pushAndInstallApp(this.apkPath, remoteApk, cb); + var install = function(err) { + if (err) return cb(err); + this.adb.installRemote(remoteApk, cb); + }.bind(this); + if (appExists) { + install(); + } else { + this.adb.push(this.apkPath, remoteApk, install); + } }.bind(this)); }.bind(this)); }.bind(this)); @@ -107,7 +116,9 @@ androidCommon.installApp = function(cb) { androidCommon.getAppMd5 = function(cb) { fs.readFile(this.apkPath, function(err, buffer) { if (err) return cb(err); - cb(md5(buffer)); + var md5Hash = md5(buffer); + logger.info("MD5 for app is " + md5Hash); + cb(null, md5Hash); }.bind(this)); }; @@ -116,6 +127,7 @@ androidCommon.remoteTempPath = function() { }; androidCommon.removeTempApks = function(exceptMd5s, cb) { + logger.info("Removing any old apks"); if (typeof exceptMd5s === "function") { cb = exceptMd5s; exceptMd5s = []; @@ -133,31 +145,39 @@ androidCommon.removeTempApks = function(exceptMd5s, cb) { }.bind(this); var removeApks = function(apks, cb) { + if (apks.length < 1) { + logger.info("No apks to examine"); + return cb(); + } var matchingApkFound = false; var noMd5Matched = true; var removes = []; _.each(apks, function(path) { path = path.trim(); - noMd5Matched = true; - _.each(exceptMd5s, function(md5) { - if (path !== '' && path.indexOf(md5) !== -1) { - noMd5Matched = false; + if (path !== "") { + noMd5Matched = true; + _.each(exceptMd5s, function(md5Hash) { + if (path.indexOf(md5Hash) !== -1) { + noMd5Matched = false; + } + }); + if (noMd5Matched) { + removes.push('rm "' + path + '"'); + } else { + logger.info("Found an apk we want to keep at " + path); + matchingApkFound = true; } - }); - if (noMd5Matched) { - removes.push('rm \\"' + path + '\\"'); - } else { - matchingApkFound = true; } }); // Invoking adb shell with an empty string will open a shell console // so return here if there's nothing to remove. if (removes.length < 1) { + logger.info("Couldn't find any apks to remove"); return cb(null, matchingApkFound); } - var cmd = removes.join[" && "]; + var cmd = removes.join(" && "); this.adb.shell(cmd, function() { cb(null, matchingApkFound); }); diff --git a/lib/devices/android/android-controller.js b/lib/devices/android/android-controller.js index dd7f4693d..7d4318a4d 100644 --- a/lib/devices/android/android-controller.js +++ b/lib/devices/android/android-controller.js @@ -348,23 +348,16 @@ androidController.setOrientation = function(orientation, cb) { }; androidController.localScreenshot = function(file, cb) { - this.adb.requireDeviceId(); async.series([ function(cb) { this.proxy(["takeScreenshot"], cb); }.bind(this), function(cb) { - var cmd = this.adb.adbCmd + ' pull /data/local/tmp/screenshot.png "' + file + '"'; - exec(cmd, { maxBuffer: 524288 }, function(err, stdout, stderr) { - if (err) { - logger.warn(stderr); - return cb(err); - } - cb(null); - }); + this.adb.pull('/data/local/tmp/screenshot.png', file, cb); }.bind(this), ], - function(){ + function(err) { + if (err) return cb(err); cb(null, { status: status.codes.Success.code }); @@ -372,57 +365,37 @@ androidController.localScreenshot = function(file, cb) { }; androidController.getScreenshot = function(cb) { - this.adb.requireDeviceId(); var localfile = temp.path({prefix: 'appium', suffix: '.png'}); var b64data = ""; async.series([ function(cb) { var png = "/data/local/tmp/screenshot.png"; - var cmd = [this.adb.adbCmd, 'shell', '"/system/bin/rm', png + ';', '/system/bin/screencap -p', png, '"'].join(' '); - logger.debug("getScreenshot: " + cmd); - exec(cmd, { maxBuffer: 524288 }, function(err, stdout, stderr) { - if (err) { - logger.warn(stderr); - return cb(err); - } - cb(null); - }); + var cmd = ['"/system/bin/rm', png + ';', '/system/bin/screencap -p', + png, '"'].join(' '); + this.adb.shell(cmd, cb); }.bind(this), function(cb) { if (fs.existsSync(localfile)) fs.unlinkSync(localfile); - var cmd = this.adb.adbCmd + ' pull /data/local/tmp/screenshot.png "' + localfile + '"'; - logger.debug("getScreenshot: " + cmd); - exec(cmd, { maxBuffer: 524288 }, function(err, stdout, stderr) { - if (err) { - logger.warn(stderr); - return cb(err); - } - cb(null); - }); + this.adb.pull('/data/local/tmp/screenshot.png', localfile, cb); }.bind(this), function(cb) { - fs.readFile(localfile, function read(err, data) { - if (err) { - cb(err); - } else { - b64data = new Buffer(data).toString('base64'); - cb(null); - } + fs.readFile(localfile, function(err, data) { + if (err) return cb(err); + b64data = new Buffer(data).toString('base64'); + cb(); }); }, function(cb) { fs.unlink(localfile, function(err) { - if (err) { - cb(err); - } else { - cb(null); - } + if (err) return cb(err); + cb(); }); } ], // Top level cb - function(err, res) { + function(err) { + if (err) return cb(err); cb(null, { status: status.codes.Success.code , value: b64data @@ -639,8 +612,8 @@ androidController.getCurrentActivity = function(cb) { androidController.fastReset = function(cb) { async.series([ function(cb) { this.adb.stopAndClear(this.appPackage, cb); }.bind(this), - this.adb.waitForNotActivity.bind(this), - this.adb.startApp.bind(this) + this.waitForActivityToStop.bind(this), + this.startApp.bind(this) ], cb); }; diff --git a/lib/devices/android/android.js b/lib/devices/android/android.js index 966446cb0..26bf9e841 100644 --- a/lib/devices/android/android.js +++ b/lib/devices/android/android.js @@ -34,8 +34,8 @@ Android.prototype.initialize = function(opts) { this.udid = opts.udid; this.appPackage = opts.appPackage; this.appActivity = opts.appActivity; - this.appWaitActivity = opts.appWaitActivity; - this.avdName = opts.avdName; + this.appWaitActivity = opts.appWaitActivity || opts.appActivity; + this.avdName = opts.avdName || null; this.appDeviceReadyTimeout = opts.appDeviceReadyTimeout; this.verbose = opts.verbose; this.queue = []; @@ -73,6 +73,7 @@ Android.prototype.start = function(cb, onDie) { if (typeof launchCb === "undefined" || launchCb === null) { launchCb = cb; } + // These messages are from adb.js. Must update when adb.js changes. var relaunchOn = [ 'Could not find a connected Android device' , 'Device did not become ready' @@ -86,7 +87,6 @@ Android.prototype.start = function(cb, onDie) { }; if (err) { - // This message is from adb.js. Must update when adb.js changes. if (err.message === null || typeof err.message === 'undefined' || checkShouldRelaunch(err.message.toString())) { @@ -115,25 +115,27 @@ Android.prototype.start = function(cb, onDie) { var onExit = function(code) { if (!didLaunch) { - logger.error("ADB quit before it successfully launched"); - cb("ADB quit unexpectedly before successfully launching"); + var msg = "UiAutomator quit before it successfully launched"; + logger.error(msg); + cb(new Error(msg)); code = code || 1; } else if (typeof this.cbForCurrentCmd === "function") { - var error = new UnknownError("ADB died while responding to command, " + - "please check appium logs!"); + var error = new UnknownError("UiAutomator died while responding to " + + "command, please check appium logs!"); this.cbForCurrentCmd(error, null); code = code || 1; } if (this.adb) { - this.adb.uninstallApp(function() { + this.uninstallApp(function() { this.adb = null; + this.uiautomator = null; this.shuttingDown = false; this.onStop(code); this.onStop = null; }.bind(this)); } else { - logger.info("We're in android's exit callback but adb is gone already"); + logger.info("We're in uiautomator's exit callback but adb is gone already"); } }.bind(this); @@ -151,7 +153,7 @@ Android.prototype.startAppium = function(onLaunch, onExit) { logger.info("Starting android appium"); this.uiautomator.onExit = onExit; - logger.debug("Using fast reset? " + this.fastReset); + logger.debug("Using fast reset? " + this.opts.fastReset); async.series([ this.prepareDevice.bind(this), @@ -211,7 +213,7 @@ Android.prototype.pushAppium = function(cb) { cb(new Error("Could not find AppiumBootstrap.jar; please run " + "'grunt buildAndroidBootstrap'")); } else { - this.adb.push(binPath, this.remoteTempDir(), cb); + this.adb.push(binPath, this.remoteTempPath(), cb); } }.bind(this)); }; @@ -360,6 +362,10 @@ Android.prototype.wakeUp = function(cb) { this.proxy(["wake", {}], cb); }; +Android.prototype.waitForActivityToStop = function(cb) { + this.adb.waitForNotActivity(this.appPackage, this.appWaitActivity, cb); +}; + Android.prototype.waitForCondition = deviceCommon.waitForCondition; Android.prototype.setCommandTimeout = function(secs, cb) { diff --git a/lib/devices/android/selendroid.js b/lib/devices/android/selendroid.js index 1ce51ec80..b2883170a 100644 --- a/lib/devices/android/selendroid.js +++ b/lib/devices/android/selendroid.js @@ -19,9 +19,12 @@ var ADB = require('./adb.js') var Selendroid = function(opts) { this.opts = opts; this.opts.devicePort = 8080; + this.avdName = opts.avdName || null; this.appWaitActivity = opts.appWaitActivity; this.serverApk = null; this.appPackage = opts.appPackage; + this.apkPath = opts.apkPath; + this.appActivity = opts.appActivity; this.desiredCaps = opts.desiredCaps; this.onStop = function() {}; this.selendroidSessionId = null; @@ -66,7 +69,7 @@ Selendroid.prototype.start = function(cb) { if (!modServerExists) { logger.info("Rebuilt selendroid server does not exist, inserting " + "modified manifest"); - this.insertSelendroidManifest(this.selendroidServerPath, cb); + this.insertSelendroidManifest(this.apkPath, cb); } else { logger.info("Rebuilt selendroid server already exists, no need to " + "rebuild it with a new manifest"); @@ -86,7 +89,7 @@ Selendroid.prototype.start = function(cb) { }.bind(this)); }.bind(this); - async.waterfall([ + async.series([ this.ensureServerExists.bind(this), this.prepareDevice.bind(this), checkModServerExists, @@ -96,12 +99,14 @@ Selendroid.prototype.start = function(cb) { conditionalInstallSelendroid, this.installApp.bind(this), this.forwardPort.bind(this), - this.pushUnlock(cb).bind(this), - this.unlockScreen(cb).bind(this), - this.pushSelendroid(cb).bind(this), + this.pushUnlock.bind(this), + this.unlockScreen.bind(this), + this.pushSelendroid.bind(this), this.waitForServer.bind(this), - this.createSession.bind(this) - ], cb); + ], function(err) { + if (err) return cb(err); + this.createSession(cb); + }); }; Selendroid.prototype.pushSelendroid = function(cb) { @@ -260,8 +265,8 @@ Selendroid.prototype.insertSelendroidManifest = function(serverPath, cb) { function(cb) { this.adb.checkSdkBinaryPresent("aapt", cb); }.bind(this), function(cb) { this.adb.compileManifest(dstManifest, newPackage, this.appPackage, cb); }.bind(this), - function(cb) { this.insertManifest(dstManifest, serverPath, newServerPath, - cb); }.bind(this) + function(cb) { this.adb.insertManifest(dstManifest, serverPath, + newServerPath, cb); }.bind(this) ], cb); }; diff --git a/lib/devices/android/uiautomator.js b/lib/devices/android/uiautomator.js index c54988b23..3aa207e03 100644 --- a/lib/devices/android/uiautomator.js +++ b/lib/devices/android/uiautomator.js @@ -27,7 +27,6 @@ UiAutomator.prototype.start = function(readyCb, exitCb) { } logger.info("Running bootstrap"); - this.requireDeviceId(); var args = ["shell", "uiautomator", "runtest", "AppiumBootstrap.jar", "-c", "io.appium.android.bootstrap.Bootstrap"]; try {