目录
前言:
Appium 是一个开源的自动化测试框架,用于移动应用程序的自动化测试。它支持 iOS 和 Android 平台上的原生应用程序和混合应用程序的自动化测试。Appium 的目标是为测试人员提供一个简单而强大的工具,使他们能够轻松地创建自动化测试,同时又能够适应不断变化的应用程序环境。
准备
- 配置 Android 开发环境;
- 安装 Xcode 并打开同意协议;
安装&启动
-
安装 homebrew
-
安装 nodejs
-
安装 appium
-
启动 appium
实现内容
- UI 自动化;
- shell 启动;
- 并发之行;
- 报告生成;
具体实现
项目基本目录结构
- doc :api 文档
- output :logs、testngReport
- src :测试代码
- testSuits :testng.xml
- tools :report 生成工具、selenium driver
scr 详细目录结构
-- src
-- base
-- operateFactory
-- AndroidOperate extends AppOperate
-- IosOperate extends AppOperate
-- AppOperate(abstract) :常用操作封装
-- WebOperate :常用操作封装
-- AutoTestBase :case 基类定义
-- ExecuteListener:web 日志自动打印
-- PageObjectBase :页面对象基类(通过 PageFactory 实现页面对象实例化)
-- business
-- commonMethod :针对应用的可复用方法
-- pageObject :页面对象(通过注解形式获取?--为以后自动生成该对象做铺垫)
-- testsuits :具体的 case 实现
-- util :appium 的启动、testng 的失败重跑、应用的数据的自动生成等
case 基类定义
public class AutoTestBase {
protected static WebDriver driver;
protected static AppOperate operateBase;
protected static WebOperate webOperate;
protected String port;
protected static String date;
protected static String time;
protected static int timeout;
protected static String appPackage;
public static String platformName;
public static String udid;
public static String browser_name;
public WebDriver getDriver() {
return driver;
}
/**
* Description: Prepare before Suite.
* 测试套件运行前准备:启动driver server、driver client,配置相关参数。
*
* @param filePath app的安装包路径
* @param appName app的安装包名
* @param platformName app平台 android/ios
* @param platformVersion app运行平台的版本
* @param deviceName app设备名称,ios参数,例如:iPhone 6
* @param appPackage app包名,android参数,例如:com.xxxx.xxxx
* @param appActivity android参数,例如:com.xxxx.xxxx
* @param port app的服务端口
* @param udid app的设备识别符
* @param timeout web/app等待超时的时间,单位:秒
* @param browser_name web的浏览器类别,例如:chrome
* @param remote_url web的远程运行的url,例如:http://172.1.2.3:8888/wd/hub
*/
@BeforeSuite(alwaysRun = true)
@Parameters({"filePath", "appName", "platformName", "platformVersion", "deviceName", "appPackage", "appActivity", "port", "udid", "timeout", "browser_name", "remote_url"})
public void beforeSuite(String filePath, String appName, String platformName, String platformVersion, String deviceName, String appPackage, String appActivity, String port, String udid, int timeout, String browser_name, String remote_url) {
this.port = port;
this.timeout = timeout;
AutoTestBase.platformName = platformName;
AutoTestBase.udid = udid;
AutoTestBase.browser_name = browser_name;
AutoTestBase.appPackage = appPackage;
try {
File dateFile = new File("date.txt");
if (dateFile.exists()) {
date = Tools.readAll("date.txt").split(":")[0].trim();
time = Tools.readAll("date.txt").split(":")[1].trim();
Log.logInfo("当前日期:" + date);
Log.logInfo("当前时间:" + time);
} else {
Log.logInfo("date.txt 文件不存在!");
System.exit(0);
}
} catch (IOException e) {
e.printStackTrace();
}
if (platformName.toLowerCase().contains("android") || platformName.toLowerCase().contains("ios")) {
File appDir = new File(filePath);
File app = new File(appDir, appName);
StartAppiumServer startAppiumServer = new StartAppiumServer(port, udid);
System.setProperty("date", date);
System.setProperty("time", time);
System.setProperty("deviceID", udid);
Log.logInfo("MobileCapabilityType.filePath = " + filePath);
Log.logInfo("MobileCapabilityType.appName = " + appName);
Log.logInfo("MobileCapabilityType.platformName = " + platformName);
Log.logInfo("MobileCapabilityType.platformVersion = " + platformVersion);
Log.logInfo("MobileCapabilityType.deviceName = " + deviceName);
Log.logInfo("MobileCapabilityType.appPackage = " + appPackage);
Log.logInfo("MobileCapabilityType.appActivity = " + appActivity);
Log.logInfo("MobileCapabilityType.port = " + port);
Log.logInfo("MobileCapabilityType.udid = " + udid);
startAppiumServer.start();
try {
Thread.sleep(30000);
} catch (InterruptedException e) {
e.printStackTrace();
}
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setCapability(MobileCapabilityType.APPIUM_VERSION, "1.0");
capabilities.setCapability(MobileCapabilityType.PLATFORM_NAME, platformName);
capabilities.setCapability(MobileCapabilityType.PLATFORM_VERSION, platformVersion);
capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath());
capabilities.setCapability(MobileCapabilityType.UDID, udid);
capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, deviceName);
capabilities.setCapability(MobileCapabilityType.APP_PACKAGE, appPackage);
capabilities.setCapability("unicodeKeyboard", "True");
capabilities.setCapability("resetKeyboard", "True");
capabilities.setCapability(MobileCapabilityType.APP_ACTIVITY, appActivity);
if (platformName.toLowerCase().contains("android")) {
String[] miUdid = StaticConfig.getMiUdid();
capabilities.setCapability("noSign", true);
try {
if (Tools.isMatch(udid, miUdid)) {
int tapx;
int tapy;
Map<String, String> miResolution = StaticConfig.getMiResolution();
tapx = Integer.parseInt(miResolution.get(udid).split("x")[0]);
tapy = Integer.parseInt(miResolution.get(udid).split("x")[1]);
HandleSafetyTips handleSafetyTipsOne = new HandleSafetyTips(udid, tapx * 7 / 10, tapy * 95 / 100);
HandleSafetyTips handleSafetyTipsTwo = new HandleSafetyTips(udid, tapx * 7 / 10, tapy * 7 / 10);
handleSafetyTipsOne.start();
driver = new AndroidDriver(new URL("http://127.0.0.1:" + port + "/wd/hub"), capabilities);
handleSafetyTipsTwo.start();
} else {
driver = new AndroidDriver(new URL("http://127.0.0.1:" + port + "/wd/hub"), capabilities);
}
} catch (MalformedURLException e) {
e.printStackTrace();
}
operateBase = new AndroidOperate((AndroidDriver) driver);
} else {
capabilities.setCapability("autoAcceptAlerts", true);
capabilities.setCapability("language", "zh-Hans");
try {
// driver = new EventFiringWebDriver(new IOSDriver(new URL("http://127.0.0.1:"+port+"/wd/hub"), capabilities)).register(executeListener);
driver = new IOSDriver(new URL("http://127.0.0.1:" + port + "/wd/hub"), capabilities);
} catch (MalformedURLException e) {
e.printStackTrace();
}
operateBase = new IosOperate((IOSDriver) driver);
}
} else {
System.setProperty("log.info.file", "web_" + browser_name + ".log");
// System.setProperty("log.appium.file", "appium_" + browser_name + ".log");
Log.logInfo("CapabilityType.BROWSER_NAME = " + browser_name);
Log.logInfo("Remote_url = " + remote_url);
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setCapability(CapabilityType.BROWSER_NAME, browser_name);
String driverServer;
String driverServerPath = "tools/driverServer/";
String processName = null;
ExecuteListener executeListener = new ExecuteListener();
if (browser_name.toLowerCase().contains("chrome")) {
driverServer = Tools.isWindows() ? "chromedriver.exe" : "chromedriver";
processName = Tools.isWindows() ? "chrome.exe" : "\"Google Chrome\"";
System.setProperty("webdriver.chrome.driver", driverServerPath + driverServer);
} else if (browser_name.toLowerCase().contains("ie")) {
driverServer = "IEDriverServer.exe";
processName = "iexplore.exe";
System.setProperty("webdriver.ie.driver", driverServerPath + driverServer);
} else if (browser_name.toLowerCase().contains("safari")) {
driverServer = Tools.isWindows() ? "safari.exe" : "safari";
processName = Tools.isWindows() ? "safari.exe" : "Safari";
System.setProperty("webdriver.safari.driver", driverServerPath + driverServer);
} else if (browser_name.toLowerCase().contains("opera")) {
driverServer = Tools.isWindows() ? "operadriver.exe" : "operadriver";
processName = Tools.isWindows() ? "opera.exe" : "Opera";
System.setProperty("webdriver.opera.driver", driverServerPath + driverServer);
} else if (browser_name.toLowerCase().contains("firefox")) {
processName = Tools.isWindows() ? "firefox.exe" : "Firefox";
}
Tools.killProcess(processName);
try {
if (null != remote_url && !"".equals(remote_url)) {
if (browser_name.toLowerCase().contains("chrome")) {
driver = new EventFiringWebDriver(new RemoteWebDriver(new URL(remote_url), DesiredCapabilities.chrome())).register(executeListener);
} else if (browser_name.toLowerCase().contains("firefox")) {
driver = new EventFiringWebDriver(new RemoteWebDriver(new URL(remote_url), DesiredCapabilities.firefox())).register(executeListener);
} else if (browser_name.toLowerCase().contains("ie")) {
driver = new EventFiringWebDriver(new RemoteWebDriver(new URL(remote_url), DesiredCapabilities.internetExplorer())).register(executeListener);
} else if (browser_name.toLowerCase().contains("safari")) {
driver = new EventFiringWebDriver(new RemoteWebDriver(new URL(remote_url), DesiredCapabilities.safari())).register(executeListener);
} else if (browser_name.toLowerCase().contains("opera")) {
driver = new EventFiringWebDriver(new RemoteWebDriver(new URL(remote_url), DesiredCapabilities.opera())).register(executeListener);
}
} else {
if (browser_name.toLowerCase().contains("chrome")) {
driver = new EventFiringWebDriver(new ChromeDriver()).register(executeListener);
} else if (browser_name.toLowerCase().contains("firefox")) {
driver = new EventFiringWebDriver(new FirefoxDriver()).register(executeListener);
} else if (browser_name.toLowerCase().contains("ie")) {
driver = new EventFiringWebDriver(new InternetExplorerDriver()).register(executeListener);
} else if (browser_name.toLowerCase().contains("safari")) {
driver = new EventFiringWebDriver(new SafariDriver()).register(executeListener);
} else if (browser_name.toLowerCase().contains("opera")) {
driver = new EventFiringWebDriver(new OperaDriver()).register(executeListener);
}
}
} catch (MalformedURLException e) {
e.printStackTrace();
}
webOperate = new WebOperate(driver);
driver.manage().timeouts().pageLoadTimeout(timeout, TimeUnit.SECONDS);
driver.manage().timeouts().setScriptTimeout(timeout, TimeUnit.SECONDS);
}
driver.manage().timeouts().implicitlyWait(timeout, TimeUnit.SECONDS);
}
/**
* Description: Back to home page after method.
* app测试方法执行完成后,回到首页。
*/
@AfterMethod(alwaysRun = true)
public void afterMethod() {
if (platformName.toLowerCase().contains("android") || platformName.toLowerCase().contains("ios")) {
operateBase.backToHomePage();
}
}
/**
* Description: Driver quit after suit.
* 测试套件执行后关闭driver。
*/
@AfterSuite(alwaysRun = true)
public void afterSuit() {
if (platformName.toLowerCase().contains("android") || platformName.toLowerCase().contains("ios")) {
((AppiumDriver) driver).removeApp(this.appPackage);
}
driver.quit();
}
/**
* Description: Screen Shot.
* 实现屏幕截屏功能。
*
* @param fileName 截屏文件名
* @throws IOException IO异常
*/
public static String ScreenShot(String fileName) throws IOException {
String filePath_screenShots;
String filePath_testngReports = "output" + File.separator + date + File.separator + time + File.separator + udid + File.separator + "testngReports" + File.separator;
if (platformName.toLowerCase().contains("android") || platformName.toLowerCase().contains("ios")) {
filePath_screenShots = "output" + File.separator + date + File.separator + time + File.separator + udid + File.separator + "screenShots" + File.separator;
} else {
filePath_screenShots = "output" + File.separator + date + File.separator + time + File.separator + browser_name + File.separator + "screenShots" + File.separator;
}
FileUtils.copyFile(((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE), new File(filePath_screenShots + fileName + ".png"));
return filePath_testngReports;
}
}
testNg.xml
- 每台设备配置一个脚本(脚本可设置为模版文件自动生成)
<?xml version="1.0" encoding="UTF-8"?>
<suite name="precodition">
<parameter name="timeout" value="30"/>
<!--platformName in {web ,android ,ios }-->
<parameter name="platformName" value="web"/>
<!--APP PARAM-->
<parameter name="appName" value=""/>
<parameter name="filePath" value=""/>
<parameter name="platformVersion" value=""/>
<parameter name="deviceName" value=""/>
<parameter name="appPackage" value=""/>
<parameter name="appActivity" value=""/>
<parameter name="port" value=""/>
<parameter name="udid" value=""/>
<!--WEB PARAM-->
<parameter name = "browser_name" value = "chrome"/>
<parameter name = "remote_url" value = ""/>
<test name="Web_Tests">
<groups>
<define name="p0">
<include name="p0" />
</define>
<define name="p1">
<include name="p1" />
</define>
<define name="p2">
<include name="p2" />
</define>
<run>
<include name="p0" />
<!--<exclude name="xxxx" />-->
</run>
</groups>
<classes>
<class name="com.xxxx.xxxx.testsuits.testcases.web.testExamlpe"/>
</classes>
</test>
<listeners>
<listener class-name="com.xxxx.xxxx.util.testng.TestResultListener" />
<listener class-name="com.xxxx.xxxx.util.testng.RetryListener" />
</listeners>
</suite>
邮件报告模版
<html>
<head>
<meta charset="utf-8" />
<style>
body {
font-family: 华文细黑,Calibri,sans-serif;
font-size: 16px;
}
table.stats-table {
color:black;
border-width: 1px;
border-spacing: 2px;
border-style: outset;
border-color: gray;
border-collapse: collapse;
background-color: white;
}
table.stats-table th {
color:black;
border-width: 1px;
padding: 5px;
border-style: inset;
border-color: gray;
background-color: #66CCEE;
-moz-border-radius: ;
}
table.stats-table td {
color:black;
text-align: center;
border-width: 1px;
padding: 5px;
border-style: inset;
border-color: gray;
background-color: white;
-moz-border-radius: ;
}
</style>
</head>
<body>
<table class="stats-table">
<tbody>
<tr>
<th id="stats-header-scenarios" colspan="6">Test Report</th>
</tr>
<tr>
<th>tests</th>
<th style="background-color:#C5D88A;">passes</th>
<th style="background-color:#D88A8A;">failures</th>
<th style="background-color:#D88A8A;">errors</th>
<th style="background-color:#2DEAEC;">skipped</th>
<th style="background-color:#2DEAEC;">time</th>
</tr>
<tr>
<th style="background-color:yellow;"> #testNgResults.Total </th>
<th style="background-color:yellow;"> #testNgResults.Passed </th>
<th style="background-color:yellow;"> #testNgResults.Failed </th>
<th style="background-color:yellow;"> #testNgResults.Errors </th>
<th style="background-color:yellow;"> #testNgResults.Skipped </th>
<th style="background-color:yellow;"> #testNgResults.Time </th>
</tr>
<tr>
<th colspan="6">Failed tests </th>
</tr>
<tr>
<th colspan="2" style="background-color:#2DEAEC;">testName</th>
<th colspan="2" style="background-color:#2DEAEC;">className</th>
<th colspan="2" style="background-color:#2DEAEC;">methodName</th>
</tr>
</tbody>
</table>
</body>
</html>
报告总览模版
case 启动脚本
- 脚本由 java 多线程去启动.(踩的坑:通过设置 suitethreadpoolsize 无法并行运行)
#!/bin/bash
date=`cat date.txt |cut -d : -f1`
time=`cat date.txt |cut -d : -f2`
currentPath=`pwd`
java -classpath "target/test-classes/" -Djava.ext.dirs=lib org.testng.TestNG -suitethreadpoolsize 1 TestSuits/APP_*_{devices}_*.xml -d output/${date}/${time}/{udid}/testngReports
cd tools/testngReport
# testng 美化插件
ant transform -Din=${currentPath}/output/${date}/${time}/{udid}/testngReports/testng-results.xml -Dout=${currentPath}/output/${date}/${time}/{udid}/testngReports/index_xslt.html -Dexpression=${currentPath}/output/${date}/${time}/{udid}/testngReports/
作为一位过来人也是希望大家少走一些弯路
在这里我给大家分享一些自动化测试前进之路的必须品,希望能对你带来帮助。
(软件测试相关资料,自动化测试相关资料,技术问题答疑等等)
相信能使你更好的进步!
点击下方小卡片