Appium 简单粗暴的方法解决 appium 自动化测试时安装安卓 app 时需要手动确认安装的问题

网球王子 · November 08, 2018 · Last by wtnhz replied at November 12, 2018 · 2170 hits

想必做安卓自动化测试的同学都会遇到安装app时手机提示“是否继续”安装的问题,每次弹出这个窗口需要“人肉”点击很烦,看下图:

有一种解决这个问题的方法,就是root你的安卓手机,刷一个不会在安装app之后弹窗的系统,然后再做自动化测试。

本文分享一种不需要root , 在安装app之后,如果出现了确认弹窗,直接在UI上单击“确认”按钮的方法,而且可以跨机型支持,相对简单粗暴一些; 不是最好的,只是提供一种解决思路。

思路:

  1. 首先,禁掉appium安装app, 包括所有默认的appium需要安装的依赖app,禁掉方法请参考:https://testerhome.com/topics/7917
  2. 需要程序安装app时,使用 adb install xxxx.apk 进行安装,什么时候需要安装的逻辑由自己的代码控制;
  3. 利用程序调用adb来执行单击确认安装弹窗中的确认按钮;
  4. 安装app的线程 与 单击确认按钮的线程 需要利用多线程进行动作配合;
  5. 不同device的确认按钮坐标位置需要单独用程序记录,比如做个枚举类记录坐标信息,实际点击操作的时候利用device uid找出坐标值,然后执行单击操作;
  6. 当然需要在手机的开发者选项中打开“指针位置”然后获取手机上确定按钮的位置,然后保存到对应的枚举类中,看下图;

如何确认安装之后是否有弹窗呢?
思路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();
}
}
}

共收到 9 条回复 时间 点赞

很黄很暴力

能不能在启动appium时候另外起一个线程去监控安装弹窗进行点击

跟这个很像 https://www.jianshu.com/p/f08caa6fadee ,不过不能解决大量机器的问题,我们以前安装的时候也是启动一个UI线程去点击,就是抓取页面上出现“安装”,“继续”,“下一步”的文案,然后点击,用的穷举法

为啥不用AccessibilityService,监听下event,写个函数就搞定了,搞那么一堆。。

用uiautomato,有个观察者功能轻松解决。或者每次adb install以后都抓ui.xml分析一下,如果有确认安装按钮还能通过xml获取到按钮坐标进行处理,不需要写死坐标点。。

mling 回复

请问下你们是用这个去做监听的么?我记得这个是要写在apk里面的,你们是写了这么一个apk,启动起来放后台一直监控 这样子么?请教下你们的具体的做法?

wtnhz 回复

百度下"AccessibilityService 安装" 或者github上搜,相关文章或者代码不要太多

感觉这样也不是很适合,要匹配各个品牌的各个机型,不太现实。。。最好还是能实现静默安装
话说第二步,什么时候需要安装的逻辑由自己的代码控制,这个你是怎么实现的?通过设备型号或者序列号写死么?

mling 回复

我不是问你AccessibilityService这个是什么东西~ 这个东西我也知道 而且早就用过了~
我问你们是不是在appium做UI自动化的时候也用这种方案?你自己有没有试过?可不可行?

需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up