搭建开发环境,导入testng/log4j/maven

1.配置jdk环境

2.安装appium,下载eclipse-adt,配置appium环境

github.com/getlantern/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。

appium-基础搭建,适配,问题,优化,提速的更多相关文章

  1. RF+Appium框架自动化测试系列一之(Mac下Appium环境搭建)万事开头难

    消失了3个月,有一段时间没来园子更新博客了,各位看官见谅哈哈,消失是因为刚换了工作环境没外网,好多笔记没能及时的记录分享,以后有时间慢慢补上吧,这段时间主要接触了移动端app的自动化测试,公司为了快速 ...

  2. Appium python自动化测试系列之appium环境搭建(二)

    ​2.1 基础环境搭建 当我们学习新的一项技术开始基本都是从环境搭建开始,本书除了第一章节也是的,如果你连最基础的环境都没有那么我们也没必要去说太多,大概介绍一下: 1.因为appium是支持andr ...

  3. 深度学习之PyTorch实战(2)——神经网络模型搭建和参数优化

    上一篇博客先搭建了基础环境,并熟悉了基础知识,本节基于此,再进行深一步的学习. 接下来看看如何基于PyTorch深度学习框架用简单快捷的方式搭建出复杂的神经网络模型,同时让模型参数的优化方法趋于高效. ...

  4. 关于web系统整体优化提速总结

    关于web系统整体优化提速总结 一.背景 随着公司业务的拓展,随之而来就是各种系统横向和纵向的增加,PV.UV也都随之增加,原有的系统架构和模式慢慢遇上了瓶颈,需要逐步的对系统从整体上进行改造升级,通 ...

  5. Appium基础教程

    目录 Appium教程 Appium简介 App自动化测试工具对比 Appium实现原理 环境搭建 Andorid介绍 基本架构 常见布局/视图 基本控件 控件常见属性 Adb介绍 Adb常用命令 A ...

  6. Mac Appium环境搭建

    安装brew ruby -e "$(curl -fsSL https://raw.github.com/mxcl/homebrew/go)" 安装java brew install ...

  7. Appium环境搭建(win/mac)

    课程使用Windows+Android虚拟机, 建议使用Windows系统学习课程, 如使用Mac系统, 请另外准备一台Andorid手机 Windows系统Appium环境搭建 安装JDK并配置环境 ...

  8. appium基础之简单的小例子

    appium环境搭建了,当然也要开始用起来了,记录一下学习的过程 遇到问题 1.The permission to start '.ui.home.view.HomeActivity' activit ...

  9. Robot framework + appium环境搭建

    Robot framework+appium环境搭建 首先梳理一下要用到的工具和安装包: 1. Android + JAVA. jdk : http://www.oracle.com/technetw ...

  10. 0基础搭建Hadoop大数据处理-编程

    Hadoop的编程可以是在Linux环境或Winows环境中,在此以Windows环境为示例,以Eclipse工具为主(也可以用IDEA).网上也有很多开发的文章,在此也参考他们的内容只作简单的介绍和 ...

随机推荐

  1. shiro使用ajax登陆实现,success但页面无法跳转的问题

    首先:简述一下登陆的后台流程 页面提交——>对应controller中的方法——>对应Realm认证——>controller返回 json 这样,无论成功与否,都有返回值,可以用 ...

  2. DataSet 取值,DataSet行数,DataSet列数 从DataSet中取出特定值

    1 DataSet.Table[0].Rows[ i ][ j ] 其中i 代表第 i 行数, j 代表第 j 列数 2 DataSet.Table[0].Rows[ i ].ItemArray[ j ...

  3. Wpf ViewModel中 ObservableCollection不支持从调度程序线程以外的线程对其 SourceCollection 进行的更改

    Wpf中ViewModel类里面经常会需要用到ObservableCollection来管理列表数据,在做异步通信的时候也会碰到“不支持从调度程序线程以外的线程对其 SourceCollection ...

  4. Spring Boot 构建电商基础秒杀项目 (三) 通用的返回对象 & 异常处理

    SpringBoot构建电商基础秒杀项目 学习笔记 定义通用的返回对象 public class CommonReturnType { // success, fail private String ...

  5. SharePoint 2013 使用 RBS 功能将二进制大型对象 BLOB 存储在内容数据库外部。

    为每个内容数据库设置 BLOB 存储   启用并配置 FILESTREAM 之后,请按照以下过程在文件系统中设置 BLOB 存储.必须为要对其使用 RBS 的每个内容数据库设置 BLOB 存储. 设置 ...

  6. Nginx 缓存深入理解

    100课陶辉 proxy_cache_methods 指令主要是根据请求方法指定是否使用缓存 Syntax: proxy_cache_methods GET | HEAD | POST ...; De ...

  7. oracle复习(二)

    十一.replace 替换格式:(原字符串,要查找的字符或字符串,替换的字符或字符串)select replace('hello world','o','a') from dual; //替换时区分大 ...

  8. Typora——安装Pandoc

    安装 打开typora,帮助-> Install and Use Pandoc  |  访问在线地址 https://support.typora.io/Install-and-Use-Pand ...

  9. Windows服务一直“正在启动”怎么杀

    转载:https://blog.csdn.net/huanglong8/article/details/71156848 PS:cmd 记得使用 管理员身份运行 这里需要通过控制台 命令行来查询PID ...

  10. 【XSY2691】中关村 卢卡斯定理 数位DP

    题目描述 在一个\(k\)维空间中,每个整点被黑白染色.对于一个坐标为\((x_1,x_2,\ldots,x_k)\)的点,他的颜色我们通过如下方式计算: 如果存在一维坐标是\(0\),则颜色是黑色. ...