最近公司需要做 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 配置方法