想必做安卓自动化测试的同学都会遇到安装 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();
        }
    }
}


↙↙↙阅读原文可查看相关链接,并与作者交流