Appium+Python-环境搭建
出来工作好几年了,这两年终于踏上了自动化测试的道路,慢慢记录下学习的艰辛路程,哈哈。
特别说明:这里罗列出了搭建的必要步骤,每个环节的详细安装教程,可以自行百度哈
一、安装JDK
因为Android是由Java语言开发的,所以需要先安装java环境。
打开官网下载链接:https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html,下载想要的版本,安装完成后,配置环境变量,需要配置3个变量,具体如下:
1)在系统变量里新建"JAVA_HOME"变量,变量值为:C:\Program Files\Java\jdk1.8.0_60(JDK的安装路径)
2)在系统变量里新建"classpath"变量,变量值为:.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar
3)找到path变量(已存在不用新建)添加变量值:%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin
下面验证下是否配置成功:打开 cmd,输入java -version回车显示java版本号,说明环境变更已配置成功
注:详细的jdk安装教程也可参考:https://blog.csdn.net/qq_42003566/article/details/82629570
二、安装android SDK
安卓sdk下载地址:http://tools.android-studio.org/index.php/sdk/ (直接下载zip版本,解压至任意目录即可),需要配置环境变量,具体如下:
1)在系统变量里新建“ANDROID_HOME”变量,变量值为:C:\Program Files\Java\android-sdk_r24.4.1-windows\android-sdk-windows(AndroidSDK解压存放路径)
2)找到path变量(已存在不用新建)添加变量值:;%ANDROID_HOME%\platform-tools;%ANDROID_HOME%\tools;(win10是每个变量都单独一行的,所以如果是win10的话就不需要分号隔离了)
因为我是直接用真机,所以解压后就不下载镜像了,只需要下载部分必须sdk就可以了,如下图
下面验证下android sdk是否安装成功,在命令行输入:adb --version回车后显示版本号,说明已安装成功。
安装这个的目的是需要用adb命令去看手机设备的相关信息,还有就是tools文件夹下面有个元素定位工具:uiautomatorviewer.bat ,非常实用,还有需要下载相关sdk用于appium。
注:元素定位工具,也可以用AppiumDesktop,详细安装和用法可以参考(写的非常详细了):https://www.jianshu.com/p/bf1ca3d4ac76
三、安装python
打开官网下载链接:https://www.python.org/,下载想要的版本,安装时要勾选 Add python to PATH,安装完成后,下面验证是否安装成功:
1)打开cmd,输入python回车显示python版本号,说明已安装成功
四、安装node.js
打开官网下载链接:https://nodejs.org/en/download/ ,下载想要的版本,安装完成后,配置环境变量,即把安装路径加到path,下面验证下是否安装成功:
1)打开cmd,输入node -v回车显示node.js的版本号,说明已安装成功
2)打开cmd,输入npm -v回车显示npm版本号,说明自带的npm也已安装成功
五、安装appium
前面已经安装完node.js,
1·)通过npm安装appium:npm install -g appium
2)通过npm安装appium-doctor:npm install -g appium-doctor(打开cmd,输入appium-doctor --version出现版本号即完成设置)
特别提醒1:可能在android7.0真机运行时会出现报错,此时需要修改adb.js文件(找到appium的安装目录下的adb.js文件,目录为:Appium\node_modules\appium\node_modules\appium-adb\lib),具体修改方法可看:https://blog.csdn.net/pjl6523853/article/details/72886048
下面附上已经修改好的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); }; //2020-0322tianjia 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); //}); //}; //2020-0322 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;
特别提醒2:Android 真机运行时可能每次都会询问是否替换 Appium Setting,此时需要修改android.js文件(目录为:Appium\Appium\node_modules\appium\lib\devices\android),把以下代码注释掉
this.initUnicode.bind(this), this.pushSettingsApp.bind(this), this.pushUnlock.bind(this),
六、安装appium-python-client
前面已经安装完python,所以可以通过pip安装:pip install Appium-Python-Client
安装这个的目的是将Python与appium关联起来
七、安装pycharm
打开官网下载链接:https://www.jetbrains.com/pycharm/download/#section=windows,下载Community
版本就够了,安装时要勾选将 pycharm 的启动目录添加到环境变量的选项,安装完成后直接打开新建项目即可,安装这个工具的目的是为了写代码脚本哈。
以上,appium+python环境搭建成功。