最近公司需要做 APP 自动化测试。大佬提出要求,开发打包好后,自动跑自动化 case。👍
所以想了怎么实现。在网上找了些资料,框架已经搭建好了,顺便说说在搭建时遇到的坑和解决方法😁
大致运行逻辑(ps 直接上图吧!):

前面开发自动打包和上传 APK 包、调用 Jenkins 远程构建是开发写的,具体怎么实现的没有看到。
后面调用 Jenkins 远程构建后,就开始运行测试代码了。
框架使用的是 maven+testng+appium
由于是自学 java,代码写的不好的地方请多包涵。

1.先写一个下载 APK 包的方法

public static File downloadFile(String urlPath, String downloadDir) {
    File file = null;
    try {
        // 统一资源
        URL url = new URL(urlPath);
        // 连接类的父类,抽象类
        URLConnection urlConnection = url.openConnection();
        // http的连接类
        HttpURLConnection httpURLConnection = (HttpURLConnection) urlConnection;   
        // 设定请求的方法,默认是GET
        httpURLConnection.setRequestMethod("GET");
        // 设置字符编码
        httpURLConnection.setRequestProperty("Charset", "UTF-8");
        // 打开到此 URL 引用的资源的通信链接(如果尚未建立这样的连接)。
        httpURLConnection.connect();  
        // 文件大小
        int fileLength = httpURLConnection.getContentLength(); 
        // 文件名
        String filePathUrl = httpURLConnection.getURL().getFile();
        String fileFullName = filePathUrl.substring(filePathUrl.lastIndexOf(File.separatorChar) + 1);
        URLConnection con = url.openConnection();
        BufferedInputStream bin = new BufferedInputStream(httpURLConnection.getInputStream());
        String path = downloadDir+"test.apk";
        file = new File(path);
        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }
        OutputStream out = new FileOutputStream(file);
        int size = 0;
        int len = 0;
        byte[] buf = new byte[1024];
        double downloadNum;
        while ((size = bin.read(buf)) != -1) {
            len += size;
            out.write(buf, 0, size);
            // 打印下载百分比
            // downloadNum = (double)len * 100 / fileLength;
            // System.out.println("下载了-------> " + String.format("%.2f", downloadNum) +
            // "%\n");
        }
        bin.close();
        out.close();
    } catch (MalformedURLException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } finally {
        return file;
    }

}

2.安装下载好的 apk 包,因为小米手机每次安装时,会弹出 USB 安装提示需要手动点击继续安装。
这样一来就无法实现打包好后自动运行了。
于是在网上各种搜资料,终于看到有位大神的解决方法,可惜没源码,那就自己写吧。

处理此弹框的逻辑是,先初始化 appium settings 这个 APP,只要 driver 初始化完成了,我们就可以利用 appium 去查找元素并且点击 “确认安装” 按钮了。
需要用到多线程。直接上代码吧。

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.TimeUnit;

import org.apache.log4j.Logger;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.testng.ITestContext;

import io.appium.java_client.AppiumDriver;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.remote.MobileCapabilityType;
/**
 * 
 * @author heming
 * 解决小米手机使用usb安装APP时,需要手动点击继续安装按钮
 *
 */
public class InstallAppForAppium extends Thread {
    public DesiredCapabilities capabilities;
    public static  AppiumDriver<WebElement> driver; 
    public static Logger logger = Logger.getLogger(InstallAppForAppium.class);
    //声明ITestContext,用于获取testng配置文件内容
    public ITestContext testContext;
    //appium server地址
    public static String serverURL;
    //测试引擎名字
    public static String automationName;
    //测试平台名字
    public static String platformName;
    //测试平台版本号
    public static String platformVersion;
    //设备名字
    public static String deviceName;
    //ios app的路径
    public static String iosAppPath;
    //android app路径
    public static String androidAppPath;
    //android app的 package
    public static String appPackage;
    //android app的activity
    public static String appActivity;
    //ios bundleid
    public static String bundleid;
    //安卓独有 - 是否使用unicode键盘,使用此键盘可以输入中文字符
    public static boolean unicodeKeyboard;
    //android独有 - 是否重置键盘,如果设置了unicodeKeyboard键盘,可以将此参数设置为true,然后键盘会重置为系统默认的
    public static boolean resetKeyboard;
    //是否覆盖已有的seesssion,这个用于多用例执行,如果不设置的话,会提示前一个session还没有结束,用例就不能继续执行了
    public static boolean sessionOverride;
    //暂停的等待时间
    public static int sleepTime;
    //元素等待超时时间
    public static int elementTimeOut;
    //设备udid
    public static String udid;





    @Override
    public void run() {
        // TODO Auto-generated method stub
          capabilities = new DesiredCapabilities();
          //设置capability,以便和appium创建session
          capabilities.setCapability("platformName",platformName);
          capabilities.setCapability("platformVersion",platformVersion);
          capabilities.setCapability("deviceName",deviceName);
          capabilities.setCapability("sessionOverride", sessionOverride);
          capabilities.setCapability("udid", udid);
          capabilities.setCapability(MobileCapabilityType.NO_RESET,true);
          //未收到下一条命令时的超时设置
          capabilities.setCapability("newCommandTimeout", "180");
          capabilities.setCapability("unicodeKeyboard", unicodeKeyboard);
          capabilities.setCapability("resetKeyboard", resetKeyboard);
          capabilities.setCapability("automationName",automationName);                    
          capabilities.setCapability("appPackage", "io.appium.settings");
          capabilities.setCapability("appActivity", "io.appium.settings.Settings"); 
          capabilities.setCapability("automationName", "uiautomator2");

          try {
              driver = new AndroidDriver<WebElement>(new URL(serverURL), capabilities);
          } catch (MalformedURLException e) {
              e.printStackTrace();
          }      
          driver.manage().timeouts().implicitlyWait(100, TimeUnit.SECONDS);                                                       
          driver.findElement(By.id("android:id/button2")).click();

          driver.quit();    


    }

    public static void installApp(ITestContext context){


          serverURL = context.getCurrentXmlTest().getParameter("serverURL");
          automationName = context.getCurrentXmlTest().getParameter("automationName");
          platformName = context.getCurrentXmlTest().getParameter("platformName");
          platformVersion = context.getCurrentXmlTest().getParameter("platformVersion");
          deviceName = context.getCurrentXmlTest().getParameter("deviceName");
          androidAppPath = context.getCurrentXmlTest().getParameter("androidAppPath");
          unicodeKeyboard = Boolean.parseBoolean(context.getCurrentXmlTest().getParameter("unicodeKeyboard"));
          resetKeyboard = Boolean.parseBoolean(context.getCurrentXmlTest().getParameter("resetKeyboard"));
          sessionOverride = Boolean.parseBoolean(context.getCurrentXmlTest().getParameter("sessionOverride"));
          udid = context.getCurrentXmlTest().getParameter("udid");


        InstallAppForAppium install_appium = new InstallAppForAppium();
        install_appium.start();
        InstallAppForDos install_dos = new InstallAppForDos();
        install_dos.start();
        while(install_appium.isAlive()){
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        try {
            Thread.sleep(6000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;

import org.apache.log4j.Logger;
import org.testng.Assert;
/**
 * 
 * @author heming
 * 
 * 使用adb install 命令安装APP
 *
 */

public class InstallAppForDos extends Thread{
    public static Logger logger = Logger.getLogger(InstallAppForDos.class);
    @Override
    public void run() {
        try {             
            Runtime rt = Runtime.getRuntime(); // 获取运行时系统  
            Process proc = rt.exec("cmd /c adb install d:/jenkins_app/android/test.apk"); // 执行命令                           
            InputStream stderr =  proc.getInputStream(); // 获取输入流  
            InputStreamReader isr = new InputStreamReader(stderr);                
            BufferedReader br = new BufferedReader(isr);  
            String line = null;  
            while ((line = br.readLine()) != null) { // 打印出命令执行的结果
                if(line.equalsIgnoreCase("Success")){
                    logger.info("APP安装成功");
                }else{
                    logger.error("APP安装失败");
                    Assert.fail("APP安装失败");
                }
            }  
        } catch (Throwable t) {  
            t.printStackTrace();  
        } 
    }
}

这样安装 app 时那讨厌的弹窗就可以解决了。
先写到这。。。。后面有时间接着写!!!!
包完饺子接着写。

3.前面三个方法写好后,在 testng 中的 beforeSuite 方法中调用这三个方法就可以了。

    @BeforeSuite
    public void beforeSuite(ITestContext context) throws MalformedURLException{
          LogConfiguration.initLog(this.getClass().getSimpleName());
          //下载apk包,下面的url地址换成apk包的下载地址
          HttpConnectionUtil.getDownloadUrl("https://www.baidu.com",
                  "d:/jenkins_app/android/");
          //先初始化appium setting和安装APP,自动点击安装过程中需要允许的权限
          InstallAppForAppium.installApp(context);

          //初始化被测试的APP
          serverURL = context.getCurrentXmlTest().getParameter("serverURL");
          automationName = context.getCurrentXmlTest().getParameter("automationName");
          platformName = context.getCurrentXmlTest().getParameter("platformName");
          platformVersion = context.getCurrentXmlTest().getParameter("platformVersion");
          deviceName = context.getCurrentXmlTest().getParameter("deviceName");
          androidAppPath = context.getCurrentXmlTest().getParameter("androidAppPath");
          iosAppPath = context.getCurrentXmlTest().getParameter("iosAppPath");
          appPackage = context.getCurrentXmlTest().getParameter("appPackage");
          appActivity = context.getCurrentXmlTest().getParameter("appActivity");
          bundleid = context.getCurrentXmlTest().getParameter("bundleid");
          unicodeKeyboard = Boolean.parseBoolean(context.getCurrentXmlTest().getParameter("unicodeKeyboard"));
          resetKeyboard = Boolean.parseBoolean(context.getCurrentXmlTest().getParameter("resetKeyboard"));
          sessionOverride = Boolean.parseBoolean(context.getCurrentXmlTest().getParameter("sessionOverride"));
          sleepTime = Integer.valueOf(context.getCurrentXmlTest().getParameter("sleepTime"));
          elementTimeOut = Integer.valueOf(context.getCurrentXmlTest().getParameter("elementTimeOut"));
          udid = context.getCurrentXmlTest().getParameter("udid");                
          capabilities = new DesiredCapabilities();       
          //File classpathRoot = new File(System.getProperty("user.dir"));
          //设置capability,以便和appium创建session
          capabilities.setCapability("platformName",platformName);
          capabilities.setCapability("platformVersion",platformVersion);
          capabilities.setCapability("deviceName",deviceName);
          capabilities.setCapability("sessionOverride", sessionOverride);
          capabilities.setCapability("udid", udid);
          capabilities.setCapability(MobileCapabilityType.NO_RESET,"false");
          //未收到下一条命令时的超时设置
          capabilities.setCapability("newCommandTimeout", "180");
          if(platformName.equalsIgnoreCase("android")){
                /**
                 * 设置和android  测试相关的capability并实例化driver对象
                 * */
              //File app = new File(classpathRoot, androidAppPath);
              //capabilities.setCapability("app", app.getAbsolutePath());
              capabilities.setCapability("unicodeKeyboard", unicodeKeyboard);
              capabilities.setCapability("resetKeyboard", resetKeyboard);
              capabilities.setCapability("automationName",automationName);
              capabilities.setCapability("appPackage", appPackage);
              capabilities.setCapability("appActivity", appActivity);   
              capabilities.setCapability("automationName", "uiautomator2");
              driver = new AndroidDriver<WebElement>(new URL(serverURL), capabilities);      
              logger.info("全民直播APP 已经启动");
              driver.manage().timeouts().implicitlyWait(elementTimeOut, TimeUnit.SECONDS);        
          }else if(platformName.equalsIgnoreCase("ios")){
            /**
             * 设置和ios  测试相关的capability并实例化driver对象
             * */           
//            File app = new File(classpathRoot, iosAppPath);
//            capabilities.setCapability("app", app.getAbsolutePath());
              //ios设置自动接收系统alert,注意IOS弹出的alert,APPIUM可以自动处理掉,支持ios8以上系统
              capabilities.setCapability("autoAcceptAlerts", true);       
              capabilities.setCapability("appium-version", "1.6.5"); 
              capabilities.setCapability("bundleid", bundleid);
              capabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME, "XCUITest");  
              driver = new IOSDriver<WebElement>(new URL(serverURL), capabilities); 
              logger.info("全民直播APP 已经启动");
              driver.manage().timeouts().implicitlyWait(elementTimeOut,TimeUnit.SECONDS);           
        }else{
            logger.info("初始化driver失败");   
            Assert.fail("初始化driver失败");
        }                         
    }

后面有时间再写一个 Jenkins 配置方法


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