搭建开发环境,导入testng/log4j/maven
1.配置jdk环境
2.安装appium,下载eclipse-adt,配置appium环境
github.com/get*/forum/issues/4775
3.导入testNG用做用例管理 [testng离线安装方法:http://www.cnblogs.com/xusweeter/p/6559196.html]
4.下载android sdk
3.导入log4j
??????不会用这个,不过已经用上了,,,,日志超级多?不会用啊,摔。是个知识点,慢慢补齐
4.导入maven
git上clone了两个项目,但是是maven项目,之前写web的时候用过,IDE用的是ideaJ,现在换成eclipse,有点懵逼,然后导入项目之后,一直报错。头发都掉光了。
解决办法是:
http://www.yiibai.com/maven/maven_creating_project.html#article-start
跟着上面来一遍,在cmd里面创建一个项目,cmd里面会详细的提示settings.xml哪里不对,比直接构建的时候一堆提示要靠谱得多。
我就是settings.xml没写好,导致项目一直有问题
EditText不能用clear清除
5.appium中途遇到的一些问题和解决办法
1.切换webView,这个一直切不过去,后来才找同事打包了一个可以切换webview的版本,不过这个还没试过
2.输入法问题,导致sendkey的字符一直有问题,这里我下载了一个必应输入法,设置了一下keyboard参数:
capabilities.setCapability("unicodeKeyboard",true);
3.由于我把测试的类,建了多个,在用testng.xml执行的时候,每个类都要重新安装一遍app,在appium里面勾选了no reset也没用,于是代码设置了一下:
capabilities.setCapability("noReset", true);
4.EditText不能直接clear,sendkeys,会一直提示nosuchElement,解决办法如下:
public static void clearText(AndroidDriver<AndroidElement> driver,String text) throws InterruptedException, IOException { driver.pressKeyCode(123);
for(int i=0;i<text.length();i++){
driver.pressKeyCode(67);
}
}
适配7.0
5.appium不支持7.0,然后要瞎几把改改改各种配置,日日日日日日日日日日日日日日。
https://www.cnblogs.com/littleyang/p/7700780.html
https://www.cnblogs.com/noodles1/p/7237933.html
https://testerhome.com/topics/9208
1.修改源码文件-注释安装appiumSettings和unlock的两行代码
位置:\Program Files (x86)\Appium\node_modules\appium\lib\devices\android\android.js
//this.pushAppium.bind(this),
this.initUnicode.bind(this),
// DO NOT push settings app and unlock app
//this.pushSettingsApp.bind(this),
//this.pushUnlock.bind(this),
2.取消重新安装IME.APK
Appium\node_modules\appium\lib\devices\android\android-common.js
androidCommon.pushUnicodeIME = function (cb) {
cb()
/*
logger.debug("Pushing unicode ime to device...");
var imePath = path.resolve(__dirname, "..", "..", "..", "build",
"unicode_ime_apk", "UnicodeIME-debug.apk");
fs.stat(imePath, function (err) {
if (err) {
cb(new Error("Could not find Unicode IME apk; please run " +
"'reset.sh --android' to build it."));
} else {
this.adb.install(imePath, false, cb);
}
}.bind(this));
*/
};
3.找到appium的安装目录下的adb.js文件,目录为:Appium\node_modules\appium\node_modules\appium-adb\lib
打开adb.js,更换为以下代码:
"use strict"; var spawn = require('child_process').spawn
, path = require('path')
, fs = require('fs')
, net = require('net')
, mkdirp = require('mkdirp')
, logger = require('./logger')
, async = require('async')
, ncp = require('ncp')
, _ = require('underscore')
, helpers = require('./helpers')
, unzipFile = helpers.unzipFile
, testZipArchive = helpers.testZipArchive
, AdmZip = require('adm-zip')
, rimraf = require('rimraf')
, Logcat = require('./logcat')
, isWindows = helpers.isWindows()
, tempDir = require('appium-support').tempDir
, mv = require('mv')
, helperJarPath = path.resolve(__dirname, '../jars')
, logger = require('./logger')
, getDirectories = helpers.getDirectories
, prettyExec = helpers.prettyExec; var ADB = function (opts) {
if (!opts) {
opts = {};
}
if (typeof opts.sdkRoot === "undefined") {
opts.sdkRoot = process.env.ANDROID_HOME || '';
}
this.sdkRoot = opts.sdkRoot;
this.udid = opts.udid;
this.appDeviceReadyTimeout = opts.appDeviceReadyTimeout;
this.useKeystore = opts.useKeystore;
this.keystorePath = opts.keystorePath;
this.keystorePassword = opts.keystorePassword;
this.keyAlias = opts.keyAlias;
this.keyPassword = opts.keyPassword;
this.adb = {path: "adb", defaultArgs:[]};
this.tmpDir = opts.tmpDir;
if (opts.remoteAdbHost) {
this.adb.defaultArgs.push("-H", opts.remoteAdbHost);
}
if (opts.remoteAdbPort) {
this.adb.defaultArgs.push("-P", opts.remoteAdbPort);
}
this.curDeviceId = null;
this.emulatorPort = null;
this.logcat = null;
this.binaries = {};
this.instrumentProc = null;
this.javaVersion = opts.javaVersion;
this.suppressKillServer = opts.suppressAdbKillServer; this.jars = {};
_(['move_manifest.jar', 'sign.jar', 'appium_apk_tools.jar', 'unsign.jar',
'verify.jar']).each(function (jarName) {
this.jars[jarName] = path.resolve(__dirname, '../jars', jarName);
}.bind(this)); if (!this.javaVersion || parseFloat(this.javaVersion) < 1.7) {
this.jars["appium_apk_tools.jar"] = path.resolve(__dirname, '../jars', "appium_apk_tools_1.6.jar");
}
}; // exposing logger
ADB.logger = logger; ADB.createADB = function (opts, cb) {
var adb = new ADB(opts);
adb.checkAdbPresent(function (err) {
cb(err, adb);
});
}; ADB.prototype.checkSdkBinaryPresent = function (binary, cb) {
logger.debug("Checking whether " + binary + " is present");
var binaryLoc = null;
var binaryName = binary;
var cmd = "which";
if (isWindows) {
if (binaryName === "android") {
binaryName += ".bat";
} else {
if (binaryName.indexOf(".exe", binaryName.length - 4) === -1) {
binaryName += ".exe";
}
}
cmd = "where";
}
if (this.sdkRoot) {
var binaryLocs = [path.resolve(this.sdkRoot, "platform-tools", binaryName)
, path.resolve(this.sdkRoot, "tools", binaryName)];
// get subpaths for currently installed build tool directories
var buildToolDirs = getDirectories(path.resolve(this.sdkRoot, "build-tools")); _.each(buildToolDirs, function (versionDir) {
binaryLocs.push(path.resolve(this.sdkRoot, "build-tools", versionDir, binaryName));
}.bind(this)); _.each(binaryLocs, function (loc) {
if (fs.existsSync(loc)) binaryLoc = loc;
}); if (binaryLoc === null) {
cb(new Error("Could not find " + binary + " in tools, platform-tools, " +
"or supported build-tools under \"" + this.sdkRoot + "\"; " +
"do you have the Android SDK installed at this location?"));
return;
}
binaryLoc = binaryLoc.trim();
logger.debug("Using " + binary + " from " + binaryLoc);
this.binaries[binary] = binaryLoc;
cb(null, binaryLoc);
} else {
logger.warn("The ANDROID_HOME environment variable is not set to the Android SDK root directory path. " +
"ANDROID_HOME is required for compatibility with SDK 23+. Checking along PATH for " + binary + ".");
prettyExec(cmd, [binary], { maxBuffer: 524288 }, function (err, stdout) {
if (stdout) {
logger.debug("Using " + binary + " from " + stdout);
this.binaries[binary] = '"' + stdout.trim() + '"';
cb(null, this.binaries[binary]);
} else {
cb(new Error("Could not find " + binary + ". Please set the ANDROID_HOME " +
"environment variable with the Android SDK root directory path."));
}
}.bind(this));
}
}; ADB.prototype.checkAdbPresent = function (cb) {
this.checkSdkBinaryPresent("adb", function (err, binaryLoc) {
if (err) return cb(err);
this.adb.path = binaryLoc;
cb(null, this.adb);
}.bind(this));
}; ADB.prototype.checkAaptPresent = function (cb) {
this.checkSdkBinaryPresent("aapt", cb);
}; ADB.prototype.checkZipAlignPresent = function (cb) {
this.checkSdkBinaryPresent("zipalign", cb);
}; ADB.prototype.exec = function (cmd, opts, cb) {
if (!cb && typeof opts === 'function') {
cb = opts;
opts = {};
}
if (!cmd) {
return cb(new Error("You need to pass in a command to exec()"));
}
opts = _.defaults(opts, {maxBuffer: 524288, wrapArgs: false});
var retryNum = 2;
async.retry(retryNum, function (_cb) {
prettyExec(this.adb.path, this.adb.defaultArgs.concat([cmd]),
opts, function (err, stdout, stderr) {
var linkerWarningRe = /^WARNING: linker.+$/m;
// sometimes ADB prints out stupid stdout warnings that we don't want
// to include in any of the response data, so let's strip it out
stdout = stdout.replace(linkerWarningRe, '').trim();
if (err) {
var protocolFaultError = new RegExp("protocol fault \\(no status\\)", "i").test(stderr);
var deviceNotFoundError = new RegExp("error: device not found", "i").test(stderr);
if (protocolFaultError || deviceNotFoundError) {
logger.info("error sending command, reconnecting device and retrying: " + cmd);
return setTimeout(function () {
this.getDevicesWithRetry(function (err, devices) {
if (err) return _cb(new Error("Reconnect failed, devices are: " + devices));
_cb(new Error(stderr)); // we've reconnected, so get back into the retry loop
});
}.bind(this), 1000);
}
return cb(err); // shortcut retry and fall out since we have a non-recoverable error
} else {
cb(null, stdout, stderr); // shortcut retry and respond with success
}
}.bind(this));
}.bind(this), function (err) {
if (err) return cb(err); // if we retry too many times, we'll get the error here, the success case is handled in the retry loop
});
}; ADB.prototype.shell = function (cmd, cb) {
if (cmd.indexOf('"') === -1) {
cmd = '"' + cmd + '"';
}
var execCmd = 'shell ' + cmd;
this.exec(execCmd, cb);
};
ADB.prototype.shell_grep = function (cmd, grep, cb) {
if (cmd.indexOf('"') === -1) {
cmd = '"' + cmd + '"';
}
var execCmd = 'shell ' + cmd + '| grep ' + grep;
this.exec(execCmd, cb);
};
ADB.prototype.spawn = function (args) {
logger.debug("spawning: " + [this.adb.path].concat(this.adb.defaultArgs, args).join(" "));
return spawn(this.adb.path, this.adb.defaultArgs.concat(args));
}; // android:process= may be defined in AndroidManifest.xml
// http://developer.android.com/reference/android/R.attr.html#process
// note that the process name when used with ps must be truncated to the last 15 chars
// ps -c com.example.android.apis becomes ps -c le.android.apis
ADB.prototype.processFromManifest = function (localApk, cb) {
this.checkAaptPresent(function (err) {
if (err) return cb(err);
logger.debug("Retrieving process from manifest.");
prettyExec(this.binaries.aapt, ['dump', 'xmltree', localApk, 'AndroidManifest.xml'],
{ maxBuffer: 524288 }, function (err, stdout, stderr) {
if (err || stderr) {
logger.warn(stderr);
return cb(new Error("processFromManifest failed. " + err));
} var result = null;
var lines = stdout.split("\n");
var applicationRegex = new RegExp(/\s+E: application \(line=\d+\).*/);
var applicationFound = false;
var attributeRegex = new RegExp(/\s+A: .+/);
var processRegex = new RegExp(/\s+A: android:process\(0x01010011\)="([^"]+).*"/);
for (var i = 0; i < lines.length; i++) {
var line = lines[i]; if (!applicationFound) {
if (applicationRegex.test(line)) {
applicationFound = true;
}
} else {
var notAttribute = !attributeRegex.test(line);
// process must be an attribute after application.
if (notAttribute) {
break;
} var process = processRegex.exec(line);
// this is an application attribute process.
if (process && process.length > 1) {
result = process[1];
// must trim to last 15 for android's ps binary
if (result.length > 15) result = result.substr(result.length - 15);
break;
}
}
} cb(null, result);
});
}.bind(this));
}; ADB.prototype.packageAndLaunchActivityFromManifest = function (localApk, cb) {
this.checkAaptPresent(function (err) {
if (err) return cb(err);
logger.debug("Extracting package and launch activity from manifest.");
prettyExec(this.binaries.aapt, ['dump', 'badging', localApk], { maxBuffer: 524288 }, function (err, stdout, stderr) {
if (err || stderr) {
logger.warn(stderr);
return cb(new Error("packageAndLaunchActivityFromManifest failed. " + err));
} var apkPackage = new RegExp(/package: name='([^']+)'/g).exec(stdout);
if (apkPackage && apkPackage.length >= 2) {
apkPackage = apkPackage[1];
} else {
apkPackage = null;
}
var apkActivity = new RegExp(/launchable-activity: name='([^']+)'/g).exec(stdout);
if (apkActivity && apkActivity.length >= 2) {
apkActivity = apkActivity[1];
} else {
apkActivity = null;
}
logger.debug("badging package: " + apkPackage);
logger.debug("badging act: " + apkActivity); cb(null, apkPackage, apkActivity);
});
}.bind(this));
}; ADB.prototype.processExists = function (processName, cb) {
if (!this.isValidClass(processName)) return cb(new Error("Invalid process name: " + processName)); this.shell("ps", function (err, out) {
if (err) return cb(err);
var exists = _.find(out.split(/\r?\n/), function (line) {
line = line.trim().split(/\s+/);
var pkgColumn = line[line.length - 1];
if (pkgColumn && pkgColumn.indexOf(processName) !== -1) {
return pkgColumn;
}
});
exists = exists ? true : false;
logger.debug("process: " + processName + " exists:" + exists);
cb(null, exists);
});
}; ADB.prototype.compileManifest = function (manifest, manifestPackage,
targetPackage, cb) {
logger.debug("Compiling manifest " + manifest); var platform = helpers.getAndroidPlatform();
if (!platform || !platform[1]) {
return cb(new Error("Required platform doesn't exist (API level >= 17)"));
}
logger.debug('Compiling manifest.');
prettyExec(this.binaries.aapt,
['package', '-M', manifest, '--rename-manifest-package', manifestPackage,
'--rename-instrumentation-target-package', targetPackage, '-I',
path.resolve(platform[1], 'android.jar'), '-F', manifest + '.apk', '-f'],
{ maxBuffer: 524288 }, function (err, stdout, stderr) {
if (err) {
logger.debug(stderr);
return cb("error compiling manifest");
}
logger.debug("Compiled manifest");
cb();
}); }; ADB.prototype.insertManifest = function (manifest, srcApk, dstApk, cb) {
logger.debug("Inserting manifest, src: " + srcApk + ", dst: " + dstApk);
var extractManifest = function (cb) {
logger.debug("Extracting manifest");
// Extract compiled manifest from manifest.xml.apk
unzipFile(manifest + '.apk', function (err, stderr) {
if (err) {
logger.debug("Error unzipping manifest apk, here's stderr:");
logger.debug(stderr);
return cb(err);
}
cb();
});
}; 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) {
if (isWindows) {
var java = path.resolve(process.env.JAVA_HOME, 'bin', 'java');
if (isWindows) java = java + '.exe';
logger.debug("Moving manifest.");
prettyExec(java,
['-jar', path.resolve(helperJarPath, 'move_manifest.jar'),
dstApk, manifest],
{ maxBuffer: 524288 }, function (err) {
if (err) {
logger.debug("Got error moving manifest: " + err);
return cb(err);
}
logger.debug("Inserted manifest.");
cb(null);
});
} else {
// Insert compiled manifest into /tmp/appPackage.clean.apk
// -j = keep only the file, not the dirs
// -m = move manifest into target apk.
logger.debug("Moving manifest.");
prettyExec('zip', ['-j', '-m', dstApk, manifest], { maxBuffer: 524288 }, function (err) {
if (err) {
logger.debug("Got error moving manifest: " + err);
return cb(err);
}
logger.debug("Inserted manifest.");
cb();
});
}
}; async.series([
function (cb) { extractManifest(cb); },
function (cb) { createTmpApk(cb); },
function (cb) { testDstApk(cb); },
function (cb) { moveManifest(cb); }
], cb);
}; ADB.prototype.signWithDefaultCert = function (apk, cb) {
var signPath = path.resolve(helperJarPath, 'sign.jar');
logger.debug("Resigning apk.");
prettyExec('java', ['-jar', signPath, apk, '--override'],
{ maxBuffer: 524288 }, function (err, stdout, stderr) {
if (stderr.indexOf("Input is not an existing file") !== -1) {
logger.warn("Could not resign apk, got non-existing file error");
return cb(new Error("Could not sign apk. Are you sure " +
"the file path is correct: " +
JSON.stringify(apk)));
}
cb(err);
});
}; ADB.prototype.signWithCustomCert = function (apk, cb) {
var jarsigner = path.resolve(process.env.JAVA_HOME, 'bin', 'jarsigner');
if (isWindows) jarsigner = jarsigner + '.exe';
var java = path.resolve(process.env.JAVA_HOME, 'bin', 'java');
if (isWindows) java = java + '.exe'; if (!fs.existsSync(this.keystorePath)) {
return cb(new Error("Keystore doesn't exist. " + this.keystorePath));
} logger.debug("Unsigning apk.");
prettyExec(java,
['-jar', path.resolve(helperJarPath, 'unsign.jar'), apk],
{ maxBuffer: 524288 }, function (err, stdout, stderr) {
if (err || stderr) {
logger.warn(stderr);
return cb(new Error("Could not unsign apk. Are you sure " +
"the file path is correct: " +
JSON.stringify(apk)));
}
logger.debug("Signing apk.");
prettyExec(
jarsigner,
['-sigalg', 'MD5withRSA', '-digestalg', 'SHA1', '-keystore', this.keystorePath,
'-storepass', this.keystorePassword, '-keypass', this.keyPassword, apk,
this.keyAlias],
{ maxBuffer: 524288 }, function (err, stdout, stderr) {
if (err || stderr) {
logger.warn(stderr);
return cb(new Error("Could not sign apk. Are you sure " +
"the file path is correct: " +
JSON.stringify(apk)));
}
cb(err);
}.bind(this));
}.bind(this));
}; ADB.prototype.sign = function (apk, cb) {
async.series([
function (cb) {
if (this.useKeystore) {
this.signWithCustomCert(apk, cb);
} else {
this.signWithDefaultCert(apk, cb);
}
}.bind(this),
function (cb) { this.zipAlignApk(apk, cb); }.bind(this),
], cb);
}; ADB.prototype.zipAlignApk = function (apk, cb) {
logger.debug("Zip-aligning " + apk);
this.checkZipAlignPresent(function (err) {
if (err) return cb(err); var alignedApk = tempDir.path({prefix: 'appium', suffix: '.tmp'});
mkdirp.sync(path.dirname(alignedApk)); logger.debug("Zip-aligning apk.");
prettyExec(this.binaries.zipalign,
['-f', '4', apk, alignedApk], { maxBuffer: 524288 }, function (err, stdout, stderr) {
if (err || stderr) {
logger.warn(stderr);
return cb(new Error("zipAlignApk failed. " + err));
} mv(alignedApk, apk, { mkdirp: true }, cb);
});
}.bind(this));
}; // returns true when already signed, false otherwise.
ADB.prototype.checkApkCert = function (apk, pkg, cb) {
if (!fs.existsSync(apk)) {
logger.debug("APK doesn't exist. " + apk);
return cb(null, false);
} if (this.useKeystore) {
return this.checkCustomApkCert(apk, pkg, cb);
} logger.debug("Checking app cert for " + apk + ".");
prettyExec('java',
['-jar', path.resolve(helperJarPath, 'verify.jar'), apk],
{ maxBuffer: 524288 }, function (err) {
if (err) {
logger.debug("App not signed with debug cert.");
return cb(null, false);
}
logger.debug("App already signed.");
this.zipAlignApk(apk, function (err) {
if (err) return cb(err);
cb(null, true);
}); }.bind(this));
}; ADB.prototype.checkCustomApkCert = function (apk, pkg, cb) {
var h = "a-fA-F0-9";
var md5Str = ['.*MD5.*((?:[', h, ']{2}:){15}[', h, ']{2})'].join('');
var md5 = new RegExp(md5Str, 'mi');
if (!process.env.JAVA_HOME) return cb(new Error("JAVA_HOME is not set"));
fs.exists(process.env.JAVA_HOME, function (exists) {
if (!exists) return cb(new Error("JAVA_HOME is not set or the directory does not exist: " + process.env.JAVA_HOME));
var keytool = path.resolve(process.env.JAVA_HOME, 'bin', 'keytool');
keytool = isWindows ? '"' + keytool + '.exe"' : '"' + keytool + '"';
this.getKeystoreMd5(keytool, md5, function (err, keystoreHash) {
if (err) return cb(err);
this.checkApkKeystoreMatch(keytool, md5, keystoreHash, pkg, apk, cb);
}.bind(this));
}.bind(this));
}; ADB.prototype.getKeystoreMd5 = function (keytool, md5re, cb) {
var keystoreHash;
logger.debug("Printing keystore md5.");
prettyExec(keytool,
['-v', '-list', '-alias', this.keyAlias,
'-keystore', this.keystorePath, '-storepass',
this.keystorePassword],
{ maxBuffer: 524288 }, function (err, stdout) {
if (err) return cb(err);
keystoreHash = md5re.exec(stdout);
keystoreHash = keystoreHash ? keystoreHash[1] : null;
logger.debug('Keystore MD5: ' + keystoreHash);
cb(null, keystoreHash);
});
}; ADB.prototype.checkApkKeystoreMatch = function (keytool, md5re, keystoreHash,
pkg, apk, cb) {
var entryHash = null;
var zip = new AdmZip(apk);
var rsa = /^META-INF\/.*\.[rR][sS][aA]$/;
var entries = zip.getEntries();
var numEntries = entries.length;
var responded = false;
var examined = 0; var onExamine = function (err, matched) {
examined++;
if (!responded) {
if (err) {
responded = true;
return cb(err);
} else if (matched) {
responded = true;
return cb(null, true);
} else if (examined === numEntries) {
responded = true;
return cb(null, false);
}
}
}; var checkMd5 = function (err, stdout) {
if (responded) return;
entryHash = md5re.exec(stdout);
entryHash = entryHash ? entryHash[1] : null;
logger.debug('entryHash MD5: ' + entryHash);
logger.debug(' keystore MD5: ' + keystoreHash);
var matchesKeystore = entryHash && entryHash === keystoreHash;
logger.debug('Matches keystore? ' + matchesKeystore);
onExamine(null, matchesKeystore);
}; while (entries.length > 0) {
if (responded) break;
var entry = entries.pop(); // meta-inf tends to be at the end
entry = entry.entryName;
if (!rsa.test(entry)) {
onExamine(null, false);
continue;
}
logger.debug("Entry: " + entry);
var entryPath = path.join(this.tmpDir, pkg, 'cert');
logger.debug("entryPath: " + entryPath);
var entryFile = path.join(entryPath, entry);
logger.debug("entryFile: " + entryFile);
// ensure /tmp/pkg/cert/ doesn't exist or extract will fail.
rimraf.sync(entryPath);
// META-INF/CERT.RSA
zip.extractEntryTo(entry, entryPath, true); // overwrite = true
logger.debug("extracted!");
// check for match
logger.debug("Printing apk md5.");
prettyExec(keytool,
['-v', '-printcert', '-file', entryFile],
{ maxBuffer: 524288 }, checkMd5);
}
}; ADB.prototype.getDevicesWithRetry = function (timeoutMs, cb) {
if (typeof timeoutMs === "function") {
cb = timeoutMs;
timeoutMs = 20000;
}
var start = Date.now();
logger.debug("Trying to find a connected android device");
var error = new Error("Could not find a connected Android device.");
var getDevices = function () {
this.getConnectedDevices(function (err, devices) {
if (err || devices.length < 1) {
if ((Date.now() - start) > timeoutMs) {
cb(error);
} else {
logger.debug("Could not find devices, restarting adb server...");
setTimeout(function () {
this.restartAdb(function () {
getDevices();
}.bind(this));
}.bind(this), 1000);
}
} else {
cb(null, devices);
}
}.bind(this));
}.bind(this);
getDevices();
}; ADB.prototype.getApiLevel = function (cb) {
logger.debug("Getting device API level");
this.shell("getprop ro.build.version.sdk", function (err, stdout) {
if (err) {
logger.warn(err);
cb(err);
} else {
logger.debug("Device is at API Level " + stdout.trim());
cb(null, stdout);
}
});
}; ADB.prototype.getEmulatorPort = function (cb) {
logger.debug("Getting running emulator port");
if (this.emulatorPort !== null) {
return cb(null, this.emulatorPort);
}
this.getConnectedDevices(function (err, devices) {
if (err || devices.length < 1) {
cb(new Error("No devices connected"));
} else {
// pick first device
var port = this.getPortFromEmulatorString(devices[0].udid);
if (port) {
cb(null, port);
} else {
cb(new Error("Emulator port not found"));
}
}
}.bind(this));
}; ADB.prototype.rimraf = function (path, cb) {
this.shell('rm -rf ' + path, cb);
}; ADB.prototype.push = function (localPath, remotePath, cb) {
try {
localPath = JSON.parse(localPath);
} catch (e) { }
localPath = JSON.stringify(localPath);
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)) {
return parseInt(portPattern.exec(emStr)[1], 10);
}
return false;
}; ADB.prototype.getRunningAVD = function (avdName, cb) {
logger.debug("Trying to find " + avdName + " emulator");
this.getConnectedEmulators(function (err, emulators) {
if (err || emulators.length < 1) {
return cb(new Error("No emulators connected"), null);
} else {
async.forEach(emulators, function (emulator, asyncCb) {
this.setEmulatorPort(emulator.port);
this.sendTelnetCommand("avd name", function (err, runningAVDName) {
if (avdName === runningAVDName) {
logger.debug("Found emulator " + avdName + " in port " + emulator.port);
this.setDeviceId(emulator.udid);
return cb(null, emulator);
}
asyncCb();
}.bind(this));
}.bind(this), function (err) {
logger.debug("Emulator " + avdName + " not running");
cb(err, null);
});
}
}.bind(this));
}; ADB.prototype.getRunningAVDWithRetry = function (avdName, timeoutMs, cb) {
var start = Date.now();
var error = new Error("Could not find " + avdName + " emulator.");
var getAVD = function () {
this.getRunningAVD(avdName.replace('@', ''), function (err, runningAVD) {
if (err || runningAVD === null) {
if ((Date.now() - start) > timeoutMs) {
cb(error);
} else {
setTimeout(function () {
getAVD();
}.bind(this), 2000);
}
} else {
cb();
}
}.bind(this));
}.bind(this);
getAVD();
}; ADB.prototype.killAllEmulators = function (cb) {
var cmd, args;
if (isWindows) {
cmd = 'TASKKILL';
args = ['TASKKILL' ,'/IM', 'emulator.exe'];
} else {
cmd = '/usr/bin/killall';
args = ['-m', 'emulator*'];
}
prettyExec(cmd, args, { maxBuffer: 524288 }, function (err) {
if (err) {
logger.debug("Could not kill emulator. It was probably not running.: " +
err.message);
}
cb();
});
}; ADB.prototype.launchAVD = function (avdName, avdArgs, language, locale, avdLaunchTimeout,
avdReadyTimeout, cb, retry) {
if (typeof retry === "undefined") {
retry = 0;
}
logger.debug("Launching Emulator with AVD " + avdName + ", launchTimeout " +
avdLaunchTimeout + "ms and readyTimeout " + avdReadyTimeout +
"ms");
this.checkSdkBinaryPresent("emulator", function (err, emulatorBinaryPath) {
if (err) return cb(err); if (avdName[0] === "@") {
avdName = avdName.substr(1);
} var launchArgs = ["-avd", avdName];
if (typeof language === "string") {
logger.debug("Setting Android Device Language to " + language);
launchArgs.push("-prop", "persist.sys.language=" + language.toLowerCase());
}
if (typeof locale === "string") {
logger.debug("Setting Android Device Country to " + locale);
launchArgs.push("-prop", "persist.sys.country=" + locale.toUpperCase());
}
if (typeof avdArgs === "string") {
avdArgs = avdArgs.split(" ");
launchArgs = launchArgs.concat(avdArgs);
}
var proc = spawn(emulatorBinaryPath,
launchArgs);
proc.on("error", function (err) {
logger.error("Unable to start Emulator: " + err.message);
// actual error will get caught by getRunningAVDWithRetry
});
proc.stderr.on('data', function (data) {
logger.error("Unable to start Emulator: " + data);
});
proc.stdout.on('data', function (data) {
if (data.toString().indexOf('ERROR') > -1) {
logger.error("Unable to start Emulator: " + data);
}
});
this.getRunningAVDWithRetry(avdName.replace('@', ''), avdLaunchTimeout,
function (err) {
if (err) {
if (retry < 1) {
logger.warn("Emulator never became active. Going to retry once");
proc.kill();
return this.launchAVD(avdName, avdArgs, language, locale, avdLaunchTimeout,
avdReadyTimeout, cb, retry + 1);
} else {
return cb(err);
}
}
this.waitForEmulatorReady(avdReadyTimeout, cb);
}.bind(this));
}.bind(this));
}; ADB.prototype.waitForEmulatorReady = function (timeoutMs, cb) {
var start = Date.now();
var error = new Error("Emulator is not ready.");
logger.debug("Waiting until emulator is ready");
var getBootAnimStatus = function () {
this.shell("getprop init.svc.bootanim", function (err, stdout) {
if (err || stdout === null || stdout.indexOf('stopped') !== 0) {
if ((Date.now() - start) > timeoutMs) {
cb(error);
} else {
setTimeout(function () {
getBootAnimStatus();
}.bind(this), 3000);
}
} else {
cb();
}
}.bind(this));
}.bind(this);
getBootAnimStatus();
}; ADB.prototype.getConnectedDevices = function (cb) {
logger.debug("Getting connected devices...");
this.exec("devices", function (err, stdout) {
if (err) return cb(err);
if (stdout.toLowerCase().indexOf("error") !== -1) {
logger.error(stdout);
cb(new Error(stdout));
} else {
var devices = [];
_.each(stdout.split("\n"), function (line) {
if (line.trim() !== "" &&
line.indexOf("List of devices") === -1 &&
line.indexOf("* daemon") === -1 &&
line.indexOf("offline") === -1) {
var lineInfo = line.split("\t");
// state is either "device" or "offline", afaict
devices.push({udid: lineInfo[0], state: lineInfo[1]});
}
});
logger.debug(devices.length + " device(s) connected");
cb(null, devices);
}
}.bind(this));
}; ADB.prototype.getConnectedEmulators = function (cb) {
logger.debug("Getting connected emulators");
this.getConnectedDevices(function (err, devices) {
if (err) return cb(err);
var emulators = [];
_.each(devices, function (device) {
var port = this.getPortFromEmulatorString(device.udid);
if (port) {
device.port = port;
emulators.push(device);
}
}.bind(this));
logger.debug(emulators.length + " emulator(s) connected");
cb(null, emulators);
}.bind(this));
}; ADB.prototype.forwardPort = function (systemPort, devicePort, cb) {
logger.debug("Forwarding system:" + systemPort + " to device:" + devicePort);
this.exec("forward tcp:" + systemPort + " tcp:" + devicePort, cb);
}; ADB.prototype.forwardAbstractPort = function (systemPort, devicePort, cb) {
logger.debug("Forwarding system:" + systemPort + " to abstract device:" + devicePort);
this.exec("forward tcp:" + systemPort + " localabstract:" + devicePort, cb);
}; ADB.prototype.isDeviceConnected = function (cb) {
this.getConnectedDevices(function (err, devices) {
if (err) {
cb(err);
} else {
cb(null, devices.length > 0);
}
});
}; /*
* Check whether the ADB connection is up
*/
ADB.prototype.ping = function (cb) {
this.shell("echo 'ping'", function (err, stdout) {
if (!err && stdout.indexOf("ping") === 0) {
cb(null, true);
} else if (err) {
cb(err);
} else {
cb(new Error("ADB ping failed, returned: " + stdout));
}
});
}; ADB.prototype.setDeviceId = function (deviceId) {
logger.debug("Setting device id to " + deviceId);
this.curDeviceId = deviceId;
this.adb.defaultArgs.push("-s", deviceId);
}; ADB.prototype.setEmulatorPort = function (emPort) {
this.emulatorPort = emPort;
}; ADB.prototype.waitForDevice = function (cb) {
var doWait = function (innerCb) {
logger.debug("Waiting for device to be ready and to respond to shell " +
"commands (timeout = " + this.appDeviceReadyTimeout + ")");
var movedOn = false
, timeoutSecs = parseInt(this.appDeviceReadyTimeout, 10); setTimeout(function () {
if (!movedOn) {
movedOn = true;
innerCb("Device did not become ready in " + timeoutSecs + " secs; " +
"are you sure it's powered on?");
}
}.bind(this), timeoutSecs * 1000); this.exec("wait-for-device", function (err) {
if (!movedOn) {
if (err) {
logger.error("Error running wait-for-device");
movedOn = true;
innerCb(err);
} else {
this.shell("echo 'ready'", function (err) {
if (!movedOn) {
movedOn = true;
if (err) {
logger.error("Error running shell echo: " + err);
innerCb(err);
} else {
innerCb();
}
}
}.bind(this));
}
}
}.bind(this));
}.bind(this); var tries = 0;
var waitCb = function (err) {
if (err) {
var lastCb = cb;
if (tries < 3) {
tries++;
logger.debug("Retrying restartAdb");
lastCb = waitCb.bind(this);
}
this.restartAdb(function () {
this.getConnectedDevices(function () {
doWait(lastCb);
});
}.bind(this));
} else {
cb(null);
}
};
doWait(waitCb.bind(this));
}; ADB.prototype.restartAdb = function (cb) {
if (!this.suppressKillServer) {
this.exec("kill-server", function (err) {
if (err) {
logger.error("Error killing ADB server, going to see if it's online " +
"anyway");
}
cb();
});
} else {
logger.debug("'adb kill-server' suppressed. Ignoring command.");
cb();
}
}; ADB.prototype.restart = function (cb) {
async.series([
this.stopLogcat.bind(this)
, this.restartAdb.bind(this)
, this.waitForDevice.bind(this)
, this.startLogcat.bind(this)
], cb);
}; ADB.prototype.startLogcat = function (cb) {
if (this.logcat !== null) {
cb(new Error("Trying to start logcat capture but it's already started!"));
return;
}
this.logcat = new Logcat({
adb: this.adb
, debug: false
, debugTrace: false
});
this.logcat.startCapture(cb);
}; ADB.prototype.stopLogcat = function (cb) {
if (this.logcat !== null) {
this.logcat.stopCapture(cb);
this.logcat = null;
} else {
cb();
}
}; ADB.prototype.getLogcatLogs = function () {
if (this.logcat === null) {
throw new Error("Can't get logcat logs since logcat hasn't started");
}
return this.logcat.getLogs();
};
/*
ADB.prototype.getPIDsByName = function (name, cb) {
logger.debug("Getting all processes with '" + name + "'");
this.shell("ps '" + name + "'", function (err, stdout) {
if (err) return cb(err);
stdout = stdout.trim();
var procs = [];
var outlines = stdout.split("\n");
_.each(outlines, function (outline) {
if (outline.indexOf(name) !== -1) {
procs.push(outline);
}
});
if (procs.length < 1) {
logger.debug("No matching processes found");
return cb(null, []);
}
var pids = [];
_.each(procs, function (proc) {
var match = /[^\t ]+[\t ]+([0-9]+)/.exec(proc);
if (match) {
pids.push(parseInt(match[1], 10));
}
});
if (pids.length !== procs.length) {
var msg = "Could not extract PIDs from ps output. PIDS: " +
JSON.stringify(pids) + ", Procs: " + JSON.stringify(procs);
return cb(new Error(msg));
}
cb(null, pids);
});
};
*/
ADB.prototype.getPIDsByName = function (name, cb) {
logger.debug("Getting all processes with '" + name + "'");
this.shell_grep("ps", name, function (err, stdout) {
if (err) {
logger.debug("No matching processes found");
return cb(null, []);
}
var pids = [];
_.each(procs, function (proc) {
var match = /[^\t ]+[\t ]+([0-9]+)/.exec(proc);
if (match) {
pids.push(parseInt(match[1], 10));
}
});
if (pids.length !== procs.length) {
var msg = "Could not extract PIDs from ps output. PIDS: " +
JSON.stringify(pids) + ", Procs: " + JSON.stringify(procs);
return cb(new Error(msg));
}
cb(null, pids);
});
};
ADB.prototype.killProcessesByName = function (name, cb) {
logger.debug("Attempting to kill all '" + name + "' processes");
this.getPIDsByName(name, function (err, pids) {
if (err) return cb(err);
var killNext = function (err) {
if (err) return cb(err);
var pid = pids.pop();
if (typeof pid !== "undefined") {
this.killProcessByPID(pid, killNext);
} else {
cb();
}
}.bind(this);
killNext();
}.bind(this));
}; ADB.prototype.killProcessByPID = function (pid, cb) {
logger.debug("Attempting to kill process " + pid);
this.shell("kill " + pid, cb);
}; var _buildStartCmd = function (startAppOptions, apiLevel) {
var cmd = "am start "; cmd += startAppOptions.stopApp && apiLevel >= 15 ? "-S" : ""; if (startAppOptions.action) {
cmd += " -a " + startAppOptions.action;
} if (startAppOptions.category) {
cmd += " -c " + startAppOptions.category;
} if (startAppOptions.flags) {
cmd += " -f " + startAppOptions.flags;
} if (startAppOptions.pkg) {
cmd += " -n " + startAppOptions.pkg + "/" + startAppOptions.activity + startAppOptions.optionalIntentArguments;
} return cmd;
}; ADB.prototype.startApp = function (startAppOptions, cb) {
startAppOptions = _.clone(startAppOptions);
// initializing defaults
_.defaults(startAppOptions, {
waitPkg: startAppOptions.pkg,
waitActivity: false,
optionalIntentArguments: false,
retry: true,
stopApp: true
});
// preventing null waitpkg
startAppOptions.waitPkg = startAppOptions.waitPkg || startAppOptions.pkg;
startAppOptions.optionalIntentArguments = startAppOptions.optionalIntentArguments ? " " + startAppOptions.optionalIntentArguments : "";
this.getApiLevel(function (err, apiLevel) {
if (err) return cb(err); var cmd = _buildStartCmd(startAppOptions, apiLevel); 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 (!startAppOptions.activity) {
return cb(new Error("Parameter 'appActivity' is required for launching application"));
}
if (startAppOptions.retry && startAppOptions.activity[0] !== ".") {
logger.debug("We tried to start an activity that doesn't exist, " +
"retrying with . prepended to activity");
startAppOptions.activity = "." + startAppOptions.activity;
startAppOptions.retry = false;
return this.startApp(startAppOptions, cb);
} else {
var msg = "Activity used to start app doesn't exist or cannot be " +
"launched! Make sure it exists and is a launchable activity";
logger.error(msg);
return cb(new Error(msg));
}
} else if (stdout.indexOf("java.lang.SecurityException") !== -1) {
// if the app is disabled on a real device it will throw a security exception
logger.error("Permission to start activity denied.");
return cb(new Error("Permission to start activity denied."));
} if (startAppOptions.waitActivity) {
if (startAppOptions.hasOwnProperty("waitDuration")) {
this.waitForActivity(startAppOptions.waitPkg, startAppOptions.waitActivity, startAppOptions.waitDuration, cb);
} else {
this.waitForActivity(startAppOptions.waitPkg, startAppOptions.waitActivity, cb);
}
} else {
cb();
}
}.bind(this));
}.bind(this));
}; ADB.prototype.isValidClass = function (classString) {
// some.package/some.package.Activity
return new RegExp(/^[a-zA-Z0-9\./_]+$/).exec(classString);
}; ADB.prototype.broadcastProcessEnd = function (intent, process, cb) {
// start the broadcast without waiting for it to finish.
this.broadcast(intent, function () {}); // wait for the process to end
var start = Date.now();
var timeoutMs = 40000;
var intMs = 400; var waitForDeath = function () {
this.processExists(process, function (err, exists) {
if (!exists) {
cb();
} else if ((Date.now() - start) < timeoutMs) {
setTimeout(waitForDeath, intMs);
} else {
cb(new Error("Process never died within " + timeoutMs + " ms."));
}
});
}.bind(this); waitForDeath();
}; ADB.prototype.broadcast = function (intent, cb) {
if (!this.isValidClass(intent)) return cb(new Error("Invalid intent " + intent)); var cmd = "am broadcast -a " + intent;
logger.debug("Broadcasting: " + cmd);
this.shell(cmd, cb);
}; ADB.prototype.endAndroidCoverage = function () {
if (this.instrumentProc) this.instrumentProc.kill();
}; ADB.prototype.androidCoverage = function (instrumentClass, waitPkg, waitActivity, cb) {
if (!this.isValidClass(instrumentClass)) return cb(new Error("Invalid class " + instrumentClass));
var args = this.adb.defaultArgs
.concat('shell am instrument -e coverage true -w'.split(' '))
.concat([instrumentClass]);
logger.debug("Collecting coverage data with: " + [this.adb.path].concat(args).join(' ')); var alreadyReturned = false;
this.instrumentProc = spawn(this.adb.path, args); // am instrument runs for the life of the app process.
this.instrumentProc.on('error', function (err) {
logger.error(err);
if (!alreadyReturned) {
alreadyReturned = true;
return cb(err);
}
});
this.instrumentProc.stderr.on('data', function (data) {
if (!alreadyReturned) {
alreadyReturned = true;
return cb(new Error("Failed to run instrumentation: " + new Buffer(data).toString('utf8')));
}
});
this.waitForActivity(waitPkg, waitActivity, function (err) {
if (!alreadyReturned) {
alreadyReturned = true;
return cb(err);
}
});
}; ADB.prototype.getFocusedPackageAndActivity = function (cb) {
logger.debug("Getting focused package and activity");
var cmd = "dumpsys window windows"
, nullRe = new RegExp(/mFocusedApp=null/)
, searchRe = new RegExp(
/mFocusedApp.+Record\{.*\s([^\s\/\}]+)\/([^\s\/\}]+)(\s[^\s\/\}]+)*\}/); this.shell(cmd, function (err, stdout) {
if (err) return cb(err);
var foundMatch = false;
var foundNullMatch = false;
_.each(stdout.split("\n"), function (line) {
var match = searchRe.exec(line);
if (match) {
foundMatch = match;
} else if (nullRe.test(line)) {
foundNullMatch = true;
}
});
if (foundMatch) {
cb(null, foundMatch[1].trim(), foundMatch[2].trim());
} else if (foundNullMatch) {
cb(null, null, null);
} else {
var msg = "Could not parse activity from dumpsys";
logger.error(msg);
logger.debug(stdout);
cb(new Error(msg));
}
}.bind(this));
}; ADB.prototype.waitForActivityOrNot = function (pkg, activity, not,
waitMs, cb) { if (typeof waitMs === "function") {
cb = waitMs;
waitMs = 20000;
} if (!pkg) return cb(new Error("Package must not be null.")); logger.debug("Waiting for pkg \"" + pkg + "\" and activity \"" + activity +
"\" to " + (not ? "not " : "") + "be focused");
var intMs = 750
, endAt = Date.now() + waitMs; var activityRelativeName = helpers.getActivityRelativeName(pkg, activity); var checkForActivity = function (foundPackage, foundActivity) {
var foundAct = false;
if (foundPackage === pkg) {
_.each(activityRelativeName.split(','), function (act) {
act = act.trim();
if (act === foundActivity || "." + act === foundActivity) {
foundAct = true;
}
});
}
return foundAct;
}; var wait = function () {
this.getFocusedPackageAndActivity(function (err, foundPackage,
foundActivity) {
if (err) return cb(err);
var foundAct = checkForActivity(foundPackage, foundActivity);
if ((!not && foundAct) || (not && !foundAct)) {
cb();
} else if (Date.now() < endAt) {
setTimeout(wait, intMs);
} else {
var verb = not ? "stopped" : "started";
var msg = pkg + "/" + activityRelativeName + " never " + verb + ". Current: " +
foundPackage + "/" + foundActivity;
logger.error(msg);
cb(new Error(msg));
}
}.bind(this));
}.bind(this); wait();
}; ADB.prototype.waitForActivity = function (pkg, act, waitMs, cb) {
this.waitForActivityOrNot(pkg, act, false, waitMs, cb);
}; ADB.prototype.waitForNotActivity = function (pkg, act, waitMs, cb) {
this.waitForActivityOrNot(pkg, act, true, waitMs, cb);
}; ADB.prototype.uninstallApk = function (pkg, cb) {
logger.debug("Uninstalling " + pkg);
this.forceStop(pkg, function (err) {
if (err) logger.debug("Force-stopping before uninstall didn't work; " +
"maybe app wasn't running");
this.exec("uninstall " + pkg, {timeout: 20000}, function (err, stdout) {
if (err) {
logger.error(err);
cb(err);
} else {
stdout = stdout.trim();
// stdout may contain warnings meaning success is not on the first line.
if (stdout.indexOf("Success") !== -1) {
logger.debug("App was uninstalled");
} else {
logger.debug("App was not uninstalled, maybe it wasn't on device?");
}
cb();
}
});
}.bind(this));
}; ADB.prototype.installRemote = function (remoteApk, cb) {
var cmd = 'pm install -r ' + remoteApk;
this.shell(cmd, function (err, stdout) {
if (err) return cb(err);
if (stdout.indexOf("Failure") !== -1) {
return cb(new Error("Remote install failed: " + stdout));
}
cb();
});
}; ADB.prototype.install = function (apk, replace, cb) {
if (typeof replace === "function") {
cb = replace;
replace = true;
}
var cmd = 'install ';
if (replace) {
cmd += '-r ';
}
cmd += '"' + apk + '"';
this.exec(cmd, cb);
}; ADB.prototype.mkdir = function (remotePath, cb) {
this.shell('mkdir -p ' + remotePath, cb);
}; ADB.prototype.instrument = function (pkg, activity, instrumentWith, cb) {
if (activity[0] !== ".") {
pkg = "";
}
var cmd = "am instrument -e main_activity '" + pkg + activity + "' " +
instrumentWith;
cmd = cmd.replace(/\.+/g, '.'); // Fix pkg..activity error
this.shell(cmd, function (err, stdout) {
if (err) return cb(err);
if (stdout.indexOf("Exception") !== -1) {
logger.error(stdout);
var msg = stdout.split("\n")[0] || "Unknown exception during " +
"instrumentation";
return cb(new Error(msg));
}
cb();
});
}; 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);
} else {
cb();
}
}.bind(this));
}; ADB.prototype.forceStop = function (pkg, cb) {
this.shell('am force-stop ' + pkg, cb);
}; ADB.prototype.clear = function (pkg, cb) {
this.shell("pm clear " + pkg, cb);
}; ADB.prototype.stopAndClear = function (pkg, cb) {
this.forceStop(pkg, function (err) {
if (err) return cb(err);
this.clear(pkg, cb);
}.bind(this));
}; ADB.prototype.isAppInstalled = function (pkg, cb) {
var installed = false; logger.debug("Getting install status for " + pkg);
this.getApiLevel(function (err, apiLevel) {
if (err) return cb(err);
var thirdparty = apiLevel >= 15 ? "-3 " : "";
var listPkgCmd = "pm list packages " + thirdparty + pkg;
this.shell(listPkgCmd, function (err, stdout) {
if (err) return cb(err);
var apkInstalledRgx = new RegExp('^package:' +
pkg.replace(/(\.)/g, "\\$1") + '$', 'm');
installed = apkInstalledRgx.test(stdout);
logger.debug("App is" + (!installed ? " not" : "") + " installed");
cb(null, installed);
}.bind(this));
}.bind(this));
}; ADB.prototype.lock = function (cb) {
logger.debug("Pressing the KEYCODE_POWER button to lock screen");
this.keyevent(26, cb);
}; ADB.prototype.back = function (cb) {
logger.debug("Pressing the BACK button");
var cmd = "input keyevent 4";
this.shell(cmd, cb);
}; ADB.prototype.goToHome = function (cb) {
logger.debug("Pressing the HOME button");
this.keyevent(3, cb);
}; ADB.prototype.keyevent = function (keycode, cb) {
var code = parseInt(keycode, 10);
// keycode must be an int.
var cmd = 'input keyevent ' + code;
this.shell(cmd, cb);
}; ADB.prototype.isScreenLocked = function (cb) {
var cmd = "dumpsys window";
this.shell(cmd, function (err, stdout) {
if (err) return cb(err);
if (process.env.APPIUM_LOG_DUMPSYS) {
// optional debugging
// if the method is not working, turn it on and send us the output
var dumpsysFile = path.resolve(process.cwd(), "dumpsys.log");
logger.debug("Writing dumpsys output to " + dumpsysFile);
fs.writeFileSync(dumpsysFile, stdout);
}
cb(null, helpers.isShowingLockscreen(stdout) || helpers.isCurrentFocusOnKeyguard(stdout) ||
!helpers.isScreenOnFully(stdout));
});
}; ADB.prototype.isSoftKeyboardPresent = function (cb) {
var cmd = "dumpsys input_method";
this.shell(cmd, function (err, stdout) {
if (err) return cb(err);
var isKeyboardShown = false;
var canCloseKeyboard = false;
var inputShownMatch = /mInputShown=\w+/gi.exec(stdout);
if (inputShownMatch && inputShownMatch[0]) {
isKeyboardShown = inputShownMatch[0].split('=')[1] === 'true';
var isInputViewShownMatch = /mIsInputViewShown=\w+/gi.exec(stdout);
if (isInputViewShownMatch && isInputViewShownMatch[0]) {
canCloseKeyboard = isInputViewShownMatch[0].split('=')[1] === 'true';
}
}
cb(null, isKeyboardShown, canCloseKeyboard);
});
}; ADB.prototype.sendTelnetCommand = function (command, cb) {
logger.debug("Sending telnet command to device: " + command);
this.getEmulatorPort(function (err, port) {
if (err) return cb(err);
var conn = net.createConnection(port, 'localhost');
var connected = false;
var readyRegex = /^OK$/m;
var dataStream = "";
var res = null;
var onReady = function () {
logger.debug("Socket connection to device ready");
conn.write(command + "\n");
};
conn.on('connect', function () {
logger.debug("Socket connection to device created");
});
conn.on('data', function (data) {
data = data.toString('utf8');
if (!connected) {
if (readyRegex.test(data)) {
connected = true;
onReady();
}
} else {
dataStream += data;
if (readyRegex.test(data)) {
res = dataStream.replace(readyRegex, "").trim();
logger.debug("Telnet command got response: " + res);
conn.write("quit\n");
}
}
});
conn.on('close', function () {
if (res === null) {
cb(new Error("Never got a response from command"));
} else {
cb(null, res);
}
});
});
}; ADB.prototype.isAirplaneModeOn = function (cb) {
var cmd = 'settings get global airplane_mode_on';
this.shell(cmd, function (err, stdout) {
if (err) return cb(err);
cb(null, parseInt(stdout) !== 0);
});
}; /*
* on: 1 (to turn on) or 0 (to turn off)
*/
ADB.prototype.setAirplaneMode = function (on, cb) {
var cmd = 'settings put global airplane_mode_on ' + on;
this.shell(cmd, cb);
}; /*
* on: 1 (to turn on) or 0 (to turn off)
*/
ADB.prototype.broadcastAirplaneMode = function (on, cb) {
var cmd = 'am broadcast -a android.intent.action.AIRPLANE_MODE --ez state ' +
(on === 1 ? 'true' : 'false');
this.shell(cmd, cb);
}; ADB.prototype.isWifiOn = function (cb) {
var cmd = 'settings get global wifi_on';
this.shell(cmd, function (err, stdout) {
if (err) return cb(err);
cb(null, parseInt(stdout) !== 0);
});
}; /*
* on: 1 (to turn on) or 0 (to turn off)
*/
ADB.prototype.setWifi = function (on, cb) {
var cmd = 'am start -n io.appium.settings/.Settings -e wifi ' + (on === 1 ? 'on' : 'off');
this.shell(cmd, cb);
}; ADB.prototype.isDataOn = function (cb) {
var cmd = 'settings get global mobile_data';
this.shell(cmd, function (err, stdout) {
if (err) return cb(err);
cb(null, parseInt(stdout) !== 0);
});
}; /*
* on: 1 (to turn on) or 0 (to turn off)
*/
ADB.prototype.setData = function (on, cb) {
var cmd = 'am start -n io.appium.settings/.Settings -e data ' + (on === 1 ? 'on' : 'off');
this.shell(cmd, cb);
}; /*
* opts: { wifi: 1/0, data 1/0 } (1 to turn on, 0 to turn off)
*/
ADB.prototype.setWifiAndData = function (opts, cb) {
var cmdOpts = '';
if (typeof opts.wifi !== 'undefined') {
cmdOpts = '-e wifi ' + (opts.wifi === 1 ? 'on' : 'off');
}
if (typeof opts.data !== 'undefined') {
cmdOpts = cmdOpts + ' -e data ' + (opts.data === 1 ? 'on' : 'off');
}
var cmd = 'am start -n io.appium.settings/.Settings ' + cmdOpts;
this.shell(cmd, cb);
}; ADB.prototype.availableIMEs = function (cb) {
this.shell('ime list -a', function (err, stdout) {
if (err) return cb(err);
var engines = [];
_.each(stdout.split('\n'), function (line) {
// get a listing that has IME IDs flush left,
// and lots of extraneous info indented
if (line.length > 0 && line[0] !== ' ') {
// remove newline and trailing colon, and add to the list
engines.push(line.trim().replace(/:$/, ''));
}
});
cb(null, engines);
});
}; ADB.prototype.defaultIME = function (cb) {
var cmd = 'settings get secure default_input_method';
this.shell(cmd, function (err, engine) {
if (err) return cb(err);
cb(null, engine.trim());
});
}; ADB.prototype.enableIME = function (imeId, cb) {
var cmd = 'ime enable ' + imeId;
this.shell(cmd, cb);
}; ADB.prototype.disableIME = function (imeId, cb) {
var cmd = 'ime disable ' + imeId;
this.shell(cmd, cb);
}; ADB.prototype.setIME = function (imeId, cb) {
var cmd = 'ime set ' + imeId;
this.shell(cmd, cb);
}; ADB.prototype.hasInternetPermissionFromManifest = function (localApk, cb) {
this.checkAaptPresent(function (err) {
if (err) return cb(err);
logger.debug("Checking if has internet permission from manifest.");
prettyExec(this.binaries.aapt,
['dump', 'badging', localApk],
{ maxBuffer: 524288 }, function (err, stdout, stderr) {
if (err || stderr) {
logger.warn(stderr);
return cb(new Error("hasInternetPermissionFromManifest failed. " + err));
}
var hasInternetPermission = new RegExp("uses-permission:.*'android.permission.INTERNET'").test(stdout);
cb(null, hasInternetPermission);
});
}.bind(this));
}; ADB.prototype.reboot = function (cb) {
var adbCmd = "stop; sleep 2; setprop sys.boot_completed 0; start";
this.shell(adbCmd, function (err) {
if (err) return cb(err);
var bootCompleted = false;
var i = 90;
logger.debug('waiting for reboot, this takes time.');
async.until(
function test() { return bootCompleted; },
function fn(cb) {
i--;
if (i < 0) return cb(new Error('device didn\'t reboot within 90 seconds'));
if (i % 5 === 0) logger.debug('still waiting for reboot.');
this.shell("getprop sys.boot_completed", function (err, stdout) {
if (err) return cb(err);
bootCompleted = '1' === stdout.trim();
setTimeout(cb, 1000);
});
}.bind(this),
cb
);
}.bind(this));
}; ADB.getAdbServerPort = function () {
return process.env.ANDROID_ADB_SERVER_PORT || 5037;
}; module.exports = ADB;
4.如果是mi5 点击事件不生效
还需要在开发者模式里面打开调试权限
问题,优化,提速:
1.执行用例,没有执行操作,提示60s没有command。
解决:appium客户端没有设置apkPackage,代码没有设置apkPackage
2.切换webview,提示chromeDriver process报错
解决:capabilities设置"recreateChromeDriverSessions"为true
3.appium 5.0.4版本没有swipe方法:
touchAction.press().moveTo().release();
4.findby的web element的click提示java.lang.NullPointerException
-pageFactory没有初始化driver
-driver的capabilities配置错误
-appium的bug,更新appium和selenium的版本
-以上问题都不是,删除所有buildpath 的依赖包,更改capabilities的参数:PLAT_FORM修改为PLATFORM
5.appium desktop 1.81版本不识别name字段
https://blog.csdn.net/wuyepiaoxue789/article/details/78411170
6.H5-getSize()
这个狗比方法不适用于H5。淘汰
7.H5-界面元素没有刷新出来,thread之后也找不到
解决办法:
while(true){ String str= driver.getPageSource().toString();
if(str.contains("user-id-input")){
break;
}else{
System.out.println("???");
}
}
等啊等,总能等到刷出来元素的时候。。。。
_(:з)∠)_ 居然还有这种找元素的方法,我真的太JB机智了。
8.appium-提速-xpath改成uiautomator
参考:
Appium python自动化测试系列之Android UIAutomator终极定位(七) https://www.cnblogs.com/Mushishi_xu/p/7691820.html
Appium对于xpath 查找元素慢的原因,以及使用uiautomator改造:https://blog.csdn.net/wanglha/article/details/49272853
8 isDisplayed()的包装方法,isExist()的写法
参考:https://www.cnblogs.com/testlurunxiu/p/6013605.html
public boolean isElementExist(String xpath ){
try{
driver.findElement(By.xpath(xpath));
return true;
}catch(org.openqa.selenium.NoSuchElementException ex){
return false;
}
}
原始代码:
try{
WebElement des=driver.findElementByAccessibilityId(aID);
if(des.isDisplayed()){
System.out.println("已经找到了元素"+aID+"break");
we=des;
break;
}
}catch(NoSuchElementException e){
e.printStackTrace();
logger.debug("没找到元素"+aID+",继续查找底部元素");
}
try{
WebElement undes=driver.findElementByAccessibilityId("没有发现心仪的内容?点击告诉我们您想看什么");
if(undes.isDisplayed()){
logger.debug("当前已经到底了--break");
break;
}
}catch(NoSuchElementException e){
e.printStackTrace();
logger.debug("查看当前是否已经到底部--没有--翻页");
Operate.swipeToUp(1000, 1);
i++;
}
if(i>10){
logger.debug("已经翻了10页了,退出当前页");
break;
}
}
return we;
}
优化为
while(true){
if(isExistByaId(aID)){
logger.debug("已经找到了元素"+aID+"break");
we=driver.findElementByAccessibilityId(aID);
break;
}else{
logger.debug("没找到元素"+aID+",继续查找底部元素");
}
if(isExistByaId("没有发现心仪的内容?点击告诉我们您想看什么")){
logger.debug("当前已经到底了--break");
break;
}else{
logger.debug("没有到底部--翻页");
Operate.swipeToUp(1000, 1);
i++;
}
if(i>10){
logger.debug("已经翻了10页了,退出当前页");
break;
}
}
public static boolean isExistByaId(String accessibilityId){
try{
driver.findElementByAccessibilityId(accessibilityId);
return true;
}catch(NoSuchElementException e){
e.printStackTrace();
logger.debug("没有找到元素accessibilityId:"+accessibilityId);
return false;
}
}
9.从pageSource里面查看元素是否存在
public static boolean findElementByContext(String context) {
long time = System.currentTimeMillis();
logger.debug("当前时间是:"+time);
boolean flag= false;
while(true){
long nowTime = System.currentTimeMillis();
String str= driver.getPageSource().toString();
if(str.contains(context)){
logger.debug("找到了"+context);
flag=true;
break;
}else if ((nowTime-time)>10000){
logger.debug("超过10s仍然没有找到");
break;
}
}
return flag;
}
10 H5页面点击后元素变更,查找元素时,找不到元素
不喜欢点击之后睡几秒,感觉性能太差。
用了driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
但是还是有时候click,页面元素变化了,但是点击的时候找不到。
解决办法:
1.Thread.sleep()
发现并没有用,,,,,
2.页面变化后,driver.getPageSource(),重新拉取页面元素。
生效了。。。。
总结:
狗比H5
driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
driver.getPageSource();
logger.debug("开始查找元素:" + card);
Operate.clickResource(card);
logger.debug("点击了" + card);
Assert.assertTrue(Operate.isExistByaId(verifyName), "没看到"+verifyName);
appium-desktop 不重复安装几个apk的设置
参考:https://blog.csdn.net/darkmanno5/article/details/72781791
第一个文件:android-helpers.js位置: ~/appium/node_modules/appium-android-driver/lib改动点:
第二个文件:android-helpers.js位置:~/appium/node_modules/appium-android-driver/build/lib
全部修改完毕之后,就不会再appium执行过程中再次安装以上三个app。