Appium自动化测试框架
- 环境搭建
- adb
-
- 构成和原理
- 连接
- 包名,界面名
- 文件传输
- 获取APP启动时间
- 获取手机日志
- 安装和卸载APP
- 进入到安卓手机内部的Linux系统命令行
- 启动和停止adb服务端
- 查看命令帮助
- 其他命令
- Appium
-
- inspector工具
-
- 元素探测
- 包名和类名
- aapt命令
- APP类型
- APP布局和控件
- JAVA SDK
- 元素定位和元素操作
- 配合Testng框架使用
- Appium自动化原理
-
- 初始化流程
- 元素定位
-
- ID定位
- text定位
- className定位
- xpath定位
- accessibility id定位
- 测试前不清除应用程序数据
- 元素等待
-
- 强制等待
- 隐式等待
- 显示等待
- 手势操作—滑动
-
- 九宫格滑动解锁
- 多点触摸
- 常用API
-
- 页面跳转
- 获取当前页面的dom结构
- 获取当前页面的类名
- 重置应用的数据
- 判断app是否安装
- 键值事件
- 截图
- 获取设备时间,DPI,引擎,横竖状态
- Hybrid应用自动化测试
-
- 线上app开启webview调试(root)
-
- Hybrid自动化测试脚本编写
环境搭建
- JDK环境安装(不介绍,自己百度)
- 安卓SDK环境安装
百度网盘下载链接,密码: kgwb
- 安卓SDK可以直接从下面的镜像连接进行下载
http://110.40.155.17/download/
把SDK下的这两个目录位置加入PATH环境变量即可
D:\SDK\androidsdk\platform-tools
D:\SDK\androidsdk\tools
为了防止可能存在的SDK冲突,建议将夜神模拟器安装目录下的nx-adb.exe替换为我们安装的SDK目录的adb.exe
adb
ADB全名Android Debug Bridge,是一个调试工具
构成和原理
- Client端 发送adb命令的电脑
- Daemon守护进程,安卓手机中接受adb命令的
- Server端 手机中管理通信的
- client发送命令给server
- server将命令交给Daemon
- daemon执行命令
- 执行结果返回给server端
- 执行结果发送给client端
abd工具可以在电脑通过终端命令来操作安卓手机/模拟器
连接
- 连接某台手机或者模拟器(夜神模拟器程序暴露连接端口为620001)
adb connect 127.0.0,1:62001
包名,界面名
自动化测试需要通过代码的形式告诉手机测试那个应用程序的哪一个界面,所以需要通过某种方式定位到某个应用程序的某个页面。
- 包名(package):决定程序的唯一性—可以用来定位到某个应用程序
- 界面名(activity): 一个界面名通常对应一个界面,界面名又被叫做启动名
获取包名和界面名的命令:
- Mac/Linux
adb shell dumpsys window windows | grep mFocusedApp
- Windows
adb shell dumpsys window windows | findstr mFocusedApp
获取当前手机上打开正在显示的那个应用程序的包名和界面名
mFocusedApp=AppWindowToken{43fda26 token=Token{51a168 ActivityRecord{ffecc8b u0 com.android.settings/.Settings t3}}}
- 包名: com.android.settings
- 界面名(启动名):.Settings
文件传输
- 发送数据到手机
adb push 电脑文件路径 手机文件夹路径
C:\Users\zdh\Desktop\stu.sql
- 将某个.txt文件发送到手机的SDK卡,SDK卡的根目录名固定为/sdcard
- 从手机获得数据
adb pull 手机的文件路径 电脑的文件夹路径
例如:
adb pull /sdcard/stu.sql C:\Users\hhh
获取APP启动时间
adb shell am start -W 包名/启动名(界面名)
- 命令作用: 启动对应的程序,并进入指定的界面
例如: 我们点击设置程序,看看它的启动时间(我们只需要执行下面的命令,该命令会发送给对应的手机和模拟器,然后对应会去打开设置应用程序,记录相关时间参数后,返回给我们)
adb shell am start -W com.android.settings/.Settings
- ThisTime: 该界面(activity)启动耗时(毫秒)
- TotalTime:应用自身启动耗时=ThisTime+应用application等资源启动时间(毫秒)
- WaitTime:系统启动应用耗时=TotalTime+系统资源启动时间(毫秒)
获取手机日志
adb logcat
- 使用上面命令后,会实时监控手机运行时输出的日志信息
- 在调试程序的时候,通过实时监控日志信息,可以找到错误日志,然后记录下来
安装和卸载APP
安装app到手机
adb install 路径/xx,apk
卸载手机上的app,需要指定包名--获取应用程序包名的方法上面给出了
adb uninstall 包名
进入到安卓手机内部的Linux系统命令行
adb shell
启动和停止adb服务端
- 启动adb服务端,出bug时使用可以重启服务器,先关闭再启动
adb start-server
- 停止adb服务端
adb kill-server
查看命令帮助
adb --help
其他命令
Appium
inspector工具
填写完毕后,先保存配置,然后点击start session开启会话即可
元素探测
包名和类名
adb shell dumpsys window windows | findstr mFocusedApp
aapt命令
需要先进入sdk安装目录的build-tools目录下
- 查询包名
aapt dump badging 应用apk的路径 | findstr package
- 查询类名
aapt dump badging 应用apk的路径 | findstr launchable-activity
APP类型
APP布局和控件
JAVA SDK
- 引入依赖
<dependency><groupId>io.appium</groupId><artifactId>java-client</artifactId><version>7.6.0</version></dependency>
- 入门案例
@Testpublic void test() throws MalformedURLException {//1.创建配置对象DesiredCapabilities desiredCapabilities=new DesiredCapabilities();//2.添加配置//deviceName:找到测试的设备desiredCapabilities.setCapability("deviceName","127.0.0.1:62001");//platformName:测试平台desiredCapabilities.setCapability("platformName","Android");//appPackage:测试app包名desiredCapabilities.setCapability("appPackage","com.android.browser");//appActivity:测试App启动入口desiredCapabilities.setCapability("appActivity",".BrowserActivity");//3.创建驱动//appium的通讯地址和配置对象AndroidDriver<WebElement> androidDriver=new AndroidDriver<WebElement>(new URL("http://127.0.0.1:4723/wd/hub"),desiredCapabilities);}
adb shell dumpsys window windows | findStr mFocusedApp: 命令可以获取到我们要测试app的包名和对应的界面名
元素定位和元素操作
这里通过测试登录qq为案例
- 建议通过原生SDK拍摄快照的方式来定位元素的id,这样会快很多
public class TestOne {@Testpublic void test() throws MalformedURLException, InterruptedException {//1.准备,并打开qq程序AndroidDriver<WebElement> androidDriver = prepare("com.tencent.mobileqq", ".activity.LoginActivity");//2.定位然后操作元素//点击同意按钮androidDriver.findElementById("com.tencent.mobileqq:id/dialogRightBtn").click();//等待程序更新--休眠10sThread.sleep(15000);System.out.println("点击登录按钮");//点击登录按钮TouchAction touchAction=new TouchAction(androidDriver);touchAction.tap(PointOption.point(500,1470)).release().perform();//等待页面刷新Thread.sleep(3000);System.out.println("输入账号和密码");//输入qq账号androidDriver.findElementByAccessibilityId("请输入QQ号码或手机或邮箱").sendKeys("xxxx");//输入qq密码androidDriver.findElementById("com.tencent.mobileqq:id/password").sendKeys("xxx");//点击登录androidDriver.findElementByAccessibilityId("登 录").click();Thread.sleep(10000);//退出androidDriver.quit();}public AndroidDriver<WebElement> prepare(String appPackage,String appActivity) throws MalformedURLException {//1.创建配置对象DesiredCapabilities desiredCapabilities=new DesiredCapabilities();//2.添加配置//deviceName:找到测试的设备desiredCapabilities.setCapability("deviceName","127.0.0.1:62001");//platformName:测试平台desiredCapabilities.setCapability("platformName","Android");//appPackage:测试app包名desiredCapabilities.setCapability("appPackage",appPackage);//appActivity:测试App启动入口desiredCapabilities.setCapability("appActivity",appActivity);//更换新引擎--uiautomator2解决输入框输入不了数据desiredCapabilities.setCapability("automationName","uiautomator2");//3.创建驱动//appium的通讯地址和配置对象return new AndroidDriver<WebElement>(new URL("http://127.0.0.1:4723/wd/hub"),desiredCapabilities);}}
配合Testng框架使用
自动化测试提速之利剑——TestNG
TestNG Hello World入门示例
Appium自动化原理
对于安卓应用来说,Appium会往对应安卓手机上推送一个Bootstrap.jar并运行它,当我们自动化测试程序向appium发送请求时,appium向Boostrap.jar发送请求,由Bootstrap.jar转发请求到安卓手机底层的自动化测试框架UIAutomator。
再由底至上,将测试结果最终返回给我们的测试程序。
初始化流程
通过appium初始化日志分析得到
- 获取配置相关参数
- 相关环境检查—jdk版本,连接的设备,adb配置,设备的sdk版本
- 检查对应的应用程序是否安装,并且判断设备是否可以正常连接
- 将自动辅助程序appium.setting推送到手机端,如果已经安装就不管了
- 获取手机相关信息,屏幕尺寸,品牌等
- 检查对应包名是否已经安装好了,如果安装好了,如果该应用程序在执行,会停止掉
- 清空应用程序数据—shell pm clear 包名
- 设置端口转发,推送Bootstrap.jat包到手机端,然后启动
- 启动应用程序
元素定位
ID定位
//如果resource-id唯一,那么使用下面这个方法就行
//如果存在多个元素resource-id相同,那么下面api默认选择第一个
androidDriver.findElementById();
//如果存在多个元素resource-id相同,使用下面api---返回一个list
androidDriver.findElementsById();
text定位
- Appium 1.5之前版本支持By.name方式
androidDriver.findElement(By.name(""));
- 最新版本Appium不再支持此API,需要使用UIAutomator原生自动化引擎
androidDriver.findElementByAndroidUIAutomator("new UiSelector().text('登录')");
className定位
根据class属性去找元素,一般在页面中很多元素的class属性都是一致的,所以这种方式基本不用。
xpath定位
- xpath有相对定位和绝对定位,注意区分
//使用相对定位---//开头androidDriver.findElementByXPath("//android.widget.Button[@text='登录']").click();
accessibility id定位
在UIAutomatorViewer并没有此属性,对应是content-desc属性
androidDriver.findElementByAccessibilityId("登 录").click();
测试前不清除应用程序数据
//不清除应用数据启动测试desiredCapabilities.setCapability("noReset","true");
元素等待
强制等待
设置固定等待时间,即便不需要等待即可定位到元素,依然要求进行等待
Thread.sleep();
隐式等待
针对全局元素设置等待时间,服务端(Appium)会在特定的超时时间内重试多次寻找控件
androidDriver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
隐式等待就是在设置的时间范围内整个页面元素加载出来,然后再轮询页面元素直到寻找成功,如果超出时间后仍然未找到元素则继续往下面执行。
同时要注意的是,driver设置的隐式等待时间会对当前driver的整个生命周期都生效,直到调用driver.close()方法。因此,通过driver定位每一个元素都会有隐式等待的时间,这会影响测试脚本执行的效率
显示等待
针对某个元素设置等待时间,服务端(Appium)会在特定的超时时间内重试多次寻找控件
- 在设定的时间范围内,每间隔设定的轮询时间定位指定元素,每次间隔的轮询时间内没有定位成功会忽略异常,如果超出设定时间仍未定位成功则抛出异常
- 可以使用ExpectedConditions中的多种方法来满足不同的定位需求
WebDriverWait wait = (WebDriverWait) new WebDriverWait(androidDriver,Duration.ofSeconds(10).getSeconds(),Duration.ofSeconds(1).getSeconds()).ignoring(NoSuchElementException.class).ignoring(WebDriverException.class).ignoring(UnreachableBrowserException.class).ignoring(ProtocolException.class);WebElement element = wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("continueid")));element.click();
- driver: appiumdriver的初始化实例
- timeout:查询条件的最大等待时间,Duration.ofSeconds(10)代表最大的等待时间10秒
- sleep:设置查询条件的时间频率,Duration.ofSeconds(1)代表每间隔1秒去定位元素
- ignoring:如果每间隔1秒定位元素失败则忽略对应的异常
- wait.until:WebDriverWait需要和until方法结合使用,通过调用ExpectedConditions里面的方法来返回你想要的值
手势操作—滑动
- java-client 6.0版本之后的使用方式
/*** @param startPointX 滑动起始坐标x* @param startPointY 滑动起始坐标y* @param endPointX 滑动结束坐标x* @param endPointY 滑动结束坐标y* @param duration 滑动耗时--默认毫秒*/public void swipe(AndroidDriver<WebElement> androidDriver, int startPointX, int startPointY, int endPointX, int endPointY, int duration){TouchAction touchAction = new TouchAction(androidDriver);touchAction.press(PointOption.point(startPointX,startPointY)).waitAction(WaitOptions.waitOptions(Duration.ofMillis(duration))).moveTo(PointOption.point(endPointX,endPointY)).release();//让滑动生效touchAction.perform();}
九宫格滑动解锁
- 首先需要定位到每个点的坐标
public void test(AndroidDriver<WebElement> androidDriver){TouchAction touchAction = new TouchAction(androidDriver);PointOption point1 = PointOption.point(150, 427);PointOption point2 = PointOption.point(362, 427);PointOption point3 = PointOption.point(569, 427);PointOption point4 = PointOption.point(359, 625);PointOption point5 = PointOption.point(150, 850);PointOption point6 = PointOption.point(362, 850);PointOption point7 = PointOption.point(569, 850);touchAction.press(point1).moveTo(point2).moveTo(point3).moveTo(point4).moveTo(point5).moveTo(point6).moveTo(point7).release();touchAction.perform();}
多点触摸
- 参考百度地图,双指滑动放大和缩小地图
public void test(AndroidDriver<WebElement> androidDriver){MultiTouchAction multiTouchAction=new MultiTouchAction(androidDriver);//得到当前屏幕的高度和宽度int x = androidDriver.manage().window().getSize().getWidth();int y = androidDriver.manage().window().getSize().getHeight();//第一根手指从B点滑动到A点TouchAction touchAction1 = new TouchAction<>(androidDriver);touchAction1.press(PointOption.point(x*4/10,y*4/10)).waitAction(WaitOptions.waitOptions(Duration.ofMillis(1000))).moveTo(PointOption.point(x*2/10,y*2/10)).release();//第二根手指从C点滑动到D点TouchAction touchAction2 = new TouchAction<>(androidDriver);touchAction2.press(PointOption.point(x*6/10,y*6/10)).waitAction(WaitOptions.waitOptions(Duration.ofMillis(1000))).moveTo(PointOption.point(x*8/10,y*8/10)).release();//将两根手指的动作添加到MultiTouchAction里面multiTouchAction.add(touchAction1).add(touchAction2);multiTouchAction.perform();}
常用API
页面跳转
实现页面跳转,包括APP内部页面和APP相互跳转
- 查询当前页面的包名和类名
adb shell dumpsys window windows | findstr mFocusedApp
- 注意区分页面内部跳转和app之间页面跳转的区别
/*** 当前APP内部页面跳转*/public void moveToPage(String appActivity){androidDriver.startActivity(new Activity("com.tencent.mobileqq", appActivity));}/*** 实现APP相互跳转---只能跳转到另一个app的启动页面*/public void moveToPage(String AppPackage,String appActivity){androidDriver.startActivity(new Activity(AppPackage, appActivity));}
获取当前页面的dom结构
String pageSource = androidDriver.getPageSource();System.out.println(pageSource);
可以用于断言当前页面是否已经有了某个元素,或者判断当前页面有没有产生变化,如上下滚动判断是否已经已经到了底端或者顶端。
<?xml version="1.0" encoding="UTF-8"?>
<hierarchy rotation="0">
<android.widget.FrameLayout index="0" text="" ....
获取当前页面的类名
String currentActivity = androidDriver.currentActivity();
重置应用的数据
androidDriver.resetApp();
有些场景我们需要清除应用的数据,相当于第一次安装时候的状态,比如: 第一次启动app的引导页,登录等等。
判断app是否安装
- 如果是安卓端的话,需要传入的是对应app的包名
boolean appInstalled = androidDriver.isAppInstalled("");
键值事件
Android平台独有,向系统发送键值事件,不同的键值对应不同的功能,如: keyevent(4)表示手机的HOME按键
public void pressKey(AndroidKey androidKey){//1.创建KeyEvent对象KeyEvent keyEvent = new KeyEvent();//2.使用withKey传入键值keyEvent.withKey(androidKey);//3.使用pressKey发送键值androidDriver.pressKey(keyEvent);}
截图
当测试用例执行失败之后进行屏幕截图,保存到本地为了更好的查找问题。
- 图片默认存储于temp目录中
File imgFile = androidDriver.getScreenshotAs(OutputType.FILE);
获取设备时间,DPI,引擎,横竖状态
//获取设备时间System.out.println("当前时间为:"+androidDriver.getDeviceTime());//获取设备DPISystem.out.println("当前屏幕DPI:"+androidDriver.getDisplayDensity());//获取当前自动化引擎System.out.println("当前引擎为:"+androidDriver.getAutomationName());//获取设备横竖屏状态System.out.println("当前横竖屏状态:"+androidDriver.getOrientation());
Hybrid应用自动化测试
上面都是对原生Native app的测试,下面开始讲解,如何完成对Hybrid APP(混合型—>Native+H5)进行自动化测试
手机端页面分为两类,一类使用原生安卓开发,另一类使用原生安卓加h5页面混合而成,对于原生页面的自动化测试,就如上面所讲,而要把上面对原生安卓页面的自动化测试放到webView–>web页面上来的时候,则无法生效,需要进行特殊处理。
Appium提供对Hybrid app进行自动化测试的方法= 基于UIAutomator+ChromeDriver
准备工作:
- 准备android 4.4+ 版本以上的手机/模拟器
- 在app源码中将webview调试模式打开 —- webview.setWebContentsDebuggingEnabled(true)
- 安装UC开发者工具
如何区分原生界面和web界面呢?
- 打开手机端的开发者选项
- 对于原生界面来说,都会被线条包裹起来
线上app开启webview调试(root)
如果是第三方线上app,一般webview debug开关都是关闭的,这就需要借助第三方工具,才能将debug开关打开。
解决方法
- Xposed+WebviewDebugHook
Xposed是一个框架,能够集成很多功能模块,这些模块能够在不修改APK的情况下,修改APP的运行方式,这里我们需要WebViewDebugHook模块来开启APP的WebView debug模式。
手机或者模拟器需要开启ROOT模式
- 访问下面的网址,即可下载两个相关的apk文件
http://110.40.155.17/download/
UC开发者工具下载地址
- 改为本地,选择第二个选项
Hybrid自动化测试脚本编写
我们需要一个Hybrid的线上app进行测试,这里选择58同城
- 先进入到某个web界面后,再进行下面这些操作
每一种页面都存在一种上下文,要定位到web页面里面的元素,需要切换到对应的context中,然后进行元素定位。
- 打开58同城,点击某个页面,如果在UC开发者工具,看到了该页面对应的链接,说明是h5页面,否则说明是原生页面
- 具体模板代码如下
//1.进入web页面中---text文本值定位到新车元素androidDriver.findElementByAndroidUIAutomator("new UiSelector().text('新车')").click();//2.获取到所有的contextsSet<String> contextHandles = androidDriver.getContextHandles();System.out.println(contextHandles);//3.切换到webview对应的上下文androidDriver.context("WEBVIEW_com.wuba");Thread.sleep(2000);//4.元素定位,然后进行相关操作androidDriver.findElementByXPath("").click();