想必做安卓自动化测试的同学都会遇到安装 app 时手机提示 “是否继续” 安装的问题,每次弹出这个窗口需要 “人肉” 点击很烦,看下图:
有一种解决这个问题的方法,就是 root 你的安卓手机,刷一个不会在安装 app 之后弹窗的系统,然后再做自动化测试。
本文分享一种不需要 root , 在安装 app 之后,如果出现了确认弹窗,直接在 UI 上单击 “确认” 按钮的方法,而且可以跨机型支持,相对简单粗暴一些; 不是最好的,只是提供一种解决思路。
思路:
如何确认安装之后是否有弹窗呢?
思路 a. 程序判断,常规的思路是自己写方法 isConfirmInstallDialogExist(), 在方法里面可以用 uiautomator 或者 appium 程序去抓窗口的 xpath 关键词,判断是否已经弹窗确认安装;不过这里有一个问题,这种确认安装的弹窗一般都是系统窗口,不同机型的窗口 xpath 参数信息可能是不同的,所以如果用这个思路,还需要一坨程序去处理、记录不同机型的确认安装窗口的 xpath 信息。
思路 b. 简单粗暴的在发起安装 app 请求之后,等三秒,然后再尝试单击确认安装按钮;
思路 a 有点麻烦,我偷个懒,用思路 b 来实现一下;
实验代码在一台手机上的尝试安装日志结果
代码贴在下面。
几点注意事项:
I. 实验的时候执行 AdbUtil 类中的 main() 函数;
II. 如需实验自己需要找一个 apk 例子程序, package name 改为你自己实验用的 app 包名;
III. 按钮位置这里做了 mock, 需要你自己填写你的实验手机的实际参数;
说明: 个人体会,这个方法不算很简便,因为如果需要多个手机都兼容,需要自己记录每个手机对应安装按钮的坐标、UID 等;手头手机少还能扛得住,太多还真受不鸟 但是这个方法的确可行,实在没招儿的情况不妨试试。
/**
* 执行安装动作的线程类
*/
public class InstallAndroidApp extends Thread{
boolean finish;
String apkFileName ;
String uid;
public InstallAndroidApp(String apkFileName,String uid){
finish = false;
this.apkFileName = apkFileName;
this.uid = uid;
}
@Override
public void run() {
AdbUtil.installApp(apkFileName,uid);
finish = true;
}
}
import org.apache.log4j.Logger;
/**
* 执行单击按钮动作的线程类
*/
public class ClickAcceptAndroidDevice extends Thread{
private static Logger logger = Logger.getLogger(ClickAcceptAndroidDevice.class);
boolean finished;
public ClickAcceptAndroidDevice(){
finished = false;
}
@Override
public void run() {
String uid = AdbUtil.getAndroidDeviceId();
int x = 0 ,y = 0;
//从btnPosition中获取X,Y坐标
for(int i = 0; i < BtnPosition.values().length; i++){
String tmp_id = BtnPosition.values()[i].getUid();
if(tmp_id.equalsIgnoreCase(uid)){
x = BtnPosition.values()[i].getX();
y = BtnPosition.values()[i].getY();
break;
}
}
if(x == -1 && y == -1){ //处理安装没有弹窗点击同意的情形
logger.info("该机型不弹确认安装窗口,跳过点击接受按钮!");
finished = true;
return;
}
AdbUtil.clickAccept(uid, x, y);
finished = true;
}
}
/**
* 不同机型对应的按钮坐标位置
*/
public enum BtnPosition {
mi2("9999xxxx",232,1187),
mi5("88888xxxxx",-1,-1),
samsangS7("77777xxxxx",-1,-1);
private String uid;
private int x;
private int y;
BtnPosition(String uid, int x, int y){
this.uid = uid;
this.x = x;
this.y = y;
}
public String getUid() {
return uid;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
}
import org.apache.log4j.Logger;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* adb 控制类
*/
public class AdbUtil{
private static Logger logger = Logger.getLogger(AdbUtil.class);
public static void main(String[] args) {
String uid = AdbUtil.getAndroidDeviceId();
String apkFileName = "ContactManager.apk";
String appPackageName = "com.example.android.contactmanager";
AdbUtil.uninstallApp(appPackageName, uid);
installAppByAutoAccept(apkFileName, uid);
AdbUtil.isAppExist(appPackageName,uid);
}
/**
* 先在系统环境变量中找androidId参数,如果为空,则随机抽取一个安卓id
* @return
*/
public static String getAndroidDeviceId(){
String androidId = System.getenv("androidId");
logger.debug("System property androidId = " + androidId);
if(null == androidId || "".equalsIgnoreCase(androidId)){
androidId = getRandomAndroidDeviceId();
}
logger.info("安卓设备uid:" + androidId);
return androidId;
}
public static String getRandomAndroidDeviceId(){
String id = "";
List<String> al = getAndroidDeviceIDs();
if(null != al && al.size() > 0){
int i = new Random().nextInt(al.size());
id = al.get(i);
}
return id;
}
public static List<String> getAndroidDeviceIDs(){
List<String> deviceIds = new ArrayList<>();
List<String> al = runExec("adb devices");
if(null != al) {
for (int i = 1; i < al.size(); i++){
String tmpStr = al.get(i);
if(null != tmpStr && tmpStr.contains("device")){
tmpStr = tmpStr.replace("device","").trim();
deviceIds.add(tmpStr);
}
}
}
return deviceIds;
}
/**
* 执行command命令
* @param cmd 运行命令
* @return
*/
public static List<String> runExec(String cmd) {
List<String> output = new ArrayList<>();
try {
logger.debug("cmd = '" + cmd + "'");
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec(cmd);
InputStream stdin = proc.getInputStream();
InputStreamReader isr = new InputStreamReader(stdin);
BufferedReader br = new BufferedReader(isr);
String line = null;
logger.debug("This is java runExec output:");
logger.debug("<OUTPUT>");
while ((line = br.readLine()) != null) {
output.add(line);
logger.debug(line);
}
logger.debug("</OUTPUT>");
int exitVal = proc.waitFor();
logger.debug("Process exitValue: " + exitVal);
} catch (Throwable t) {
t.printStackTrace();
}
return output;
}
/**
* 列出所有的包名 adb shell pm list packages
* @param appName
* @return
*/
public static boolean isAppExist(String appName, String uid){
boolean result = false;
List<String> pList = runExec("adb -s " + uid + " shell pm list packages");
for(int i = 0; pList != null && i < pList.size(); i++){
String pName = pList.get(i);
if(pName.contains(appName)){
result = true;
logger.info("The application: " + pName + " is exist!");
break;
}
}
if(!result){
logger.info("The application: " + appName + " is not exist!");
}
return result;
}
/**
* 安装安卓app
* @param apkFileName
* @param uid
* @return
*/
public static boolean installApp(String apkFileName, String uid){
boolean result = false;
String absolutPath = System.getProperty("user.dir");
String apkFilePath = absolutPath + File.separator + "androidAppium"
+ File.separator + "apps"
+ File.separator + apkFileName; //需要将apk文件放置在 $user.dir/androidAppium/apps 下
logger.info("apkFilePath = " + apkFilePath);
String cmd = "adb -s " + uid + " install " + apkFilePath;
List<String> al = runExec(cmd);
for(int i = 0; al != null && i < al.size(); i++){
String tmpStr = al.get(i);
if(tmpStr != null && tmpStr.contains("Success")){
result = true;
logger.info(apkFileName + " install success!");
break;
}
}
if(!result){
logger.info(apkFileName + " install fail!");
}
return result;
}
/**
* 安装安卓app, 自动单击同意安装按钮
* @param apkFileName apk文件名
* @param uid 安装device uid
* @return
*/
public static boolean installAppByAutoAccept(String apkFileName, String uid){
boolean result = false;
InstallAndroidApp installAndroidApp = new InstallAndroidApp(apkFileName, uid);
ClickAcceptAndroidDevice clickAcceptAndroidDevice = new ClickAcceptAndroidDevice();
installAndroidApp.start(); //启动安装线程
clickAcceptAndroidDevice.start(); //启动点击接受线程
boolean loop = true;
long start = System.currentTimeMillis();
while(loop){
long now = System.currentTimeMillis();
long running = (now - start) / 1000; //一共运行的时长(秒数)
if(installAndroidApp.finish && clickAcceptAndroidDevice.finished || running >= 15){ //安装与点击任务都完成 或者 运行时间大于15秒 就退出while循环
loop = false;
result = true;
}
waiting(1);
}
return result;
}
/**
* 卸载
* @param appPackageName app包名
* @param uid
* @return
*/
public static boolean uninstallApp(String appPackageName, String uid){
boolean result = false;
String cmd = "adb -s " + uid + " uninstall " + appPackageName;
List<String> al = runExec(cmd);
if(al != null && al.size() > 0 && al.get(0) != null && al.get(0).contains("Success")){
result = true;
logger.info(appPackageName + " uninstall success!");
}else{
logger.info(appPackageName + " uninstall fail!");
}
return result;
}
/**
* 单击动作
* @param uid
* @param x 坐标x轴
* @param y 坐标y轴
* @return
*/
public static boolean clickAccept(String uid, int x, int y){
boolean result = false;
String cmd = "adb -s " + uid + " shell input tap " + x + " " + y;
logger.info(cmd);
waiting(3); //等三秒
runExec(cmd);
logger.info("Click accept button finished!");
return result;
}
/**
* 等待
* @param second
*/
public static void waiting(int second){
try {
Thread.sleep(1000 * second);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}