一、Monkey 介绍
Monkey 就是SDK中附带的一个工具。Monkey是Android中的一个命令行工具,可以运行在模拟器里或实际设备中。它向系统发送伪随机的用户事件流(如按键输入、触摸屏输入、手势输入等),实现对正在开发的应用程序进行压力测试。Monkey测试是一种为了测试软件的稳定性、健壮性的快速有效的方法。
想玩Monkey,首先得电脑上装好JDK和安卓SDK的环境哈!
安卓开发工具帮助文档 : ,安卓开发调试小工具,帮助大大的!
命令格式: adb shell monkey [options] <event-count>
由于Monkey命令行很不友好,所以一个封装好的Monkey命令的UI工具,供大家使用~
点此链接进行下载:
注意:E:MonkeyTooltoolsplatform-tools 下的adb版本要跟本地安卓sdk一致,可以直接把安卓sdk下的adb 覆盖掉此UI工具下的adb即可!
二、monkeyrunner介绍
monkeyrunner工具提供了一个用于编写从Android代码之外控制Android设备或模拟器的程序的API。通过monkeyrunner,您可以编写一个Python程序,安装一个Android应用程序或测试包,运行它,向其发送击键,截取其用户界面,并在工作站上存储屏幕截图。monkeyrunner工具主要用于测试功能/框架级别的应用程序和设备,以及运行单元测试套件,但是您可以将其用于其他目的。
monkeyrunner工具为Android测试提供了这些独特的功能:
- 多设备控制:monkeyrunner API可以跨多个设备或模拟器应用一个或多个测试套件。您可以物理连接所有设备或一次启动所有仿真器(或两者),以编程方式依次连接每个仿真器,然后运行一个或多个测试。您也可以以编程方式启动仿真器配置,运行一个或多个测试,然后关闭仿真器。
- 功能测试:monkeyrunner可以运行一个Android应用程序的自动化的开始到结束测试。您可以使用按键或触摸事件来提供输入值,并以截屏形式查看结果。
- 回归测试 - monkeyrunner可以通过运行应用程序并将其输出截图与一组已知正确的截图进行比较来测试应用程序的稳定性。
- 可扩展的自动化 - 由于monkeyrunner是一个API工具包,您可以开发一整套基于Python的模块和程序来控制Android设备。除了使用monkeyrunner API本身外,还可以使用标准的Python os和 subprocess 模块来调用Android工具,如Android Debug Bridge。
D:android-sdk-windowstools 下 一个MonkeyRunner.bat的命令,如果木有你的安卓sdk可能是被精简化,被改过的!
MonkeyRunner录制回放工具 :
monkey_recorder monkey_playback 两个py文件放到安卓sdk的tools目录下即可!方便启动~
MonkeyRunner需要Python支持,去官网下载个稳定版本的Python即可!
>cd D:android-sdk-windowstools
>monkeyrunner monkey_recorder.py
1、可以自动显示手机当前的界面
2、自动刷新手机的最新状态
3、点击手机界面即可对手机进行操作,同时会反应到真机,而且会在右侧插入操作脚本。
4、参数设值
wait: 用来插入下一次操作的时间间隔,点击后即可设置时间,单位是秒
Press a Button:用来确定需要点击的按钮,包括menu、home、search,以及对按钮的press、down、up属性
Type Something:用来输入内容到输入框
Fling:用来进行拖动操作,可以向上、下、左、右,以及操作的范围
Export Actions:用来导出脚本,不需要后缀名,也可以添加后缀名.mr,生成的脚本建议放在sdk的tool目录下
Refresh Display:用来刷新手机界面,估计只有在断开手机后,重新连接时才会用到
5、进行回放:
monkeyrunner monkey_playback.py demo.mr
三、MonkeyRunner 进阶
EasyMonkeyDevice其实就是在前几章描述的MonkeyDevice和HierarchyViewer的基础上加了一层Wrapper,把原来的通过接受坐标点或者ViewNode来操作控件的思想统一成通过控件ID来操作,其实最终它们都会转换成坐标点或ViewNode进行操作
from com.android.monkeyrunner import MonkeyRunner,MonkeyDevice,MonkeyImage
from com.android.monkeyrunner.easy import EasyMonkeyDevice,By
device = MonkeyRunner.waitForConnection()
device.startActivity("com.android.browser/com.android.browser.BrowserActivity")
ed=EasyMonkeyDevice(device)
会出现告警信息
解决方法:
adb shell service call window 1 i32 4939
adb shell service call window 3 //启动
然后在执行,警报解除:
ed=EasyMonkeyDevice(device)
EasyMonkeyDevice是通过控件ID来操作,俺们就打开安卓sdk下tools下的hierarchyviewer.bat
通过它快速获取浏览器输入超链接地方的id=url
四、MonkeyRunner实战
用系统自带浏览器自动访问百度:
访问浏览器一些列操作完整代码,首先打开浏览器点击url输入框,然后进行清除主页或者历史的url,然后在输入百度url和回车访问,并获得标题,如果访问正确,则输出pass
#!/usr/bin/env
#-*- coding:utf-8 –*-
from com.android.monkeyrunner import MonkeyRunner,MonkeyDevice,MonkeyImage
from com.android.monkeyrunner.easy import EasyMonkeyDevice,By
device = MonkeyRunner.waitForConnection()
device.startActivity("com.android.browser/com.android.browser.BrowserActivity")
ed=EasyMonkeyDevice(device)
MonkeyRunner.sleep(6)
ed.touch(By.id('id/url'), MonkeyDevice.DOWN_AND_UP)
ed.touch(By.id('id/clear'), MonkeyDevice.DOWN_AND_UP)
device.press('KEYCODE_DEL',MonkeyDevice.DOWN)
device.press('KEYCODE_ENTER',MonkeyDevice.DOWN_AND_UP)
MonkeyRunner.sleep(6)
hv =device.getHierarchyViewer()
hv2 = hv.findViewById('id/title')
hv3 =hv2.namedProperties.get('text:mText')
str = hv3.toString().encode('utf-8')
if "百度一下" in str:
print("pass")
else:
print("fail")
四个参数:
DOWN_AND_UP: 指定应向设备发送一个DOWN事件类型,后跟UP事件类型,对应于输入密钥或单击屏幕。
UP:指定应将UP事件类型发送到设备,对应于释放键或从屏幕上抬起
DOWN:指定应向设备发送DOWN事件类型,相当于按下某个键或触摸屏幕
键盘操作:http://www.android-doc.com/reference/android/view/KeyEvent.html
五、EasyMonkeyDevice的源码,解析
EasyMonkeyDevice 的父亲是PyObject 、老师 是ClassDictInit,
EasyMonkeyDevice(MonkeyDevice device) 构造方法
public void touch(PyObject[] args, String[] kws) // 向所选对象发送触摸事件
public void touch(By selector, TouchPressType type) //通过控件ID,发送按压操作
public void type(PyObject[] args, String[] kws) //在指定的对象中键入一个字符串
public void type(By selector, String text) // 通过控件ID,发送一个字符串
public PyTuple locate(PyObject[] args, String[] kws) //定位所选对象的坐标
public boolean exists(PyObject[] args, String[] kws ) //检查指定的对象是否存在
public boolean exists(By selector)//检查指定的对象id是否存在
public boolean visible(PyObject[] args, String[] kws) //检查指定的对象是否可见
public boolean visible(By selector)//通过控件ID指定的对象是否可见
public String getText(PyObject[] args, String[] kws) //获取所选输入框中的文本。
public String getText(By selector)//通过控件ID,获取所选输入框中的文本。
public String getFocusedWindowId(PyObject[] args, String[] kws) //获取焦点窗口的ID
public String getFocusedWindowId() //获取焦点窗口的ID
public PyObject __findattr_ex__(String name) //将未知的方法转发到原始的MonkeyDevice对象
private By getSelector(ArgParser ap, int i) //向所选对象发送触摸事件
private Point getElementCenter(By selector)//指定控件ID,向所选对象发送触摸事件
以下博客整理的也算完整:
基本常用MonkeyRunner API 详见
六、Appium介绍
Appium介绍
Appium是一个移动端的自动化框架,可用于测试原生应用,移动网页应用和混合型应用,且是跨平台的。可用于IOS和Android以及firefox的操作系统。原生的应用是指用android或ios的sdk编写的应用,移动网页应用是指网页应用,类似于ios中safari应用或者Chrome应用或者类浏览器的应用。混合应用是指一种包裹webview的应用,原生应用于网页内容交互性的应用。
重要的是Appium是跨平台的,何为跨平台,意思就是可以针对不同的平台用一套api来编写测试用例。
Appium的哲学
Appium遵循下面几个原则(其实也是appium的特点):
1.使用自动化来测试一个app,但是不需要重新编译它
2.写自动化case,不需要学习特定的语言
3.一个自动化框架不需要重复造轮子
4.一个自动化框架需要开源,在精神和实践上实现开源
Appium的设计
为了遵循上面的原则,appium的解决方法分别如下:
第一条:采用底层驱动商提供的自动化框架。
IOS:苹果的UIAutomationAndroid 4.2+:谷歌的 UiAutomatorAndroid 2.3+:谷歌的Instrumentation(已被selendroid取
第二条:采用底层驱动商提供统一API,就是WebDriver API。
WebDriver(也称Selenium WebDriver)其实是一个C/S架构的协议,叫做JSON Wire Protocol。通过这个协议,用任何语言写成的客户端都可以发送HTTP请求给服务器。这就意味着你可以自由选择你想要使用的测试框架和执行器,也可以将任何包含HTTP客户端的库文件加入到你的代码中。换句话说,Appium的WebDriver不是一个技术上的测试框架,而是一个自动化库。
第三条:因为WebDriver是一个非常成熟的网页协议且已经正在起草W3C的标准。我们为什么还要创造其他东西呢?相反,我们在WebDriver的基础上,扩展了一些适合移动端自动化协议的API。
第四条:你之所以能读到这篇文章,就是因为我们开源啦。
七、Appium元素定位
我们都知道id是唯一值,就如同我们的身份证号码一样,不能有重复的。在web自动化中也是主要有ID来定位的。但手机app的定位却是用name,原因很简单。我们的app页面一般很简洁。相同内容出现的机率非常少。所以用name定位非常方便。app中name可以查找两个字段一个是text的值,一个是content-desc的值。这两个的值不需要定位工具,只要打开app就直接能够看到。
uiautomationview.bat位置在%ANDROID_HOEM%lib目录
重要元素属性解析:
index表示第N-1个子元素,所有子元素都计算在内。
text表示文本内容 查找By.name()
resource-id表示 ID 查找By.id()
class 查找用By.className()由于app一个页面多数情况下会出现多个相同的className所以app测试一般不用。
package当前应用包名 启动app键值对用。
content-desc注释,在查找元素时与text一样都用By.name()或用By.AccessibilityId()
bounds坐标
元素定位方法用户代码中书写格式:
手机中定位一般用name就可以了。手机app页面简单,一般情况下不会出
现相同的名称,比如登录页面只有一个注册,只有一个登录等。
name:上文已经说了,name可以用text或content-desc只要这两个属性,任意一个有内容都可以用他定位
不要用name定位输入框,比如输入框中有默认值,每次输入内容后值都会改变,这时再次定位就会定位不到了。
text定位:
driver.findElementByName("知乎");
class定位:这个定位在web时可以,手机中同名的className太多了~
driver.findElementByClassName("android.widget.TextView");
如果同名的太多了,可以可以装到list进行遍历,获得你想要的控件位置后在进行action操作
List<AndroidElement> iv=driver.findElementsByClassName("android.widget.ImageView");
AndroidElement iv2=iv.get(6);
iv2.click();
resource-id 定位:
driver.findElementById("com.miui.home:id/icon_title");
content-desc定位:
driver.findElementsByAccessibilityId("上图中content-desc没有内容");
网页中的链接定位:
例如:<a href="https://www.baidu.com">百度一下<a>
driver.findElementByLinkText("百度一下");
网页中的链接模糊定位:
driver.findElementsByPartialLinkText("百度一下");
通过UiAtuomator的方式定位
driver.findElementsByAndroidUIAutomator("new UiSelector().text('知乎')");
一个button按钮,通过name、id、xpath 均获取不了元素,再用此接口!
List<AndroidElement> element = driver.findElementsByAndroidUIAutomator(" new UiSelector()."
+ "className("android.widget.RadioButton")"
+ ".textContains("首页")"
+ ".resourceId("cn.sogukj.stockalert:id/rb_one")");
通过xPath定位元素实例
如果id class都定位不了,我们可以考虑用xpath来解决
通过text定位:
driver.findElementByXPath("//android.widget.TextView[contains(@text,'note2')]");
通过index定位
driver.findElementByXPath("//android.widget.TextView[contains(@index,0)]");
使用index为0来查找控件,可能会失败,如果我们有3个TextView控件,其中最上层的和中层 的控件他们的Index都是0
我们就要想办法加多点路径,让xpath能分辨出需要的是下层的index为0的TextView。
driver.findElementByXPath("//android.widget.LinearLayout[1]/android.widget.FrameLayout/android.widget.ListView/android.widget.TextView[contains(@index,1)]");
在安卓开发和调试过程中,或需要通过uiautomatorviewer.bat进行界面的调试,因为xpath总觉得自己写有点麻烦,如果跟火狐谷歌开发者模式,自动获取就太好了,
已经有人利用sdk 的一些api实现了这一功能,已经把编译好的jar文件下载到网盘里,有使用的同学直接下载当中uiautomatorviewer.jar 替换掉D:android-sdk-windowstoolslib 下的uiautomatorviewer.jar 可以变成增强版的获取控件的xpath。
总结: 很多人都说xpath运行很慢,不建议使用,这个观点我基本同意,因为如果id class都定位不了,我们可以考虑用xpath来解决。
八、Appium元素操作
1、判断元素是否可见
//webview或者原生app都通用
WebElement element =driver.findElementByName("首页");
element.isDisplayed();
2、元素是否被选择
有这样的情况,复选框或者是单选框,我们需要判断这个框是不是被勾选了,此时这个方法就派上用场了。
WebElement checkbox= driver.findElementId(“checkbox_id”);
//webview和原生app通用
checkbox.isSeelected();
checkbox.isSeelected();会有返回值,如果勾选了,返回true,如果没有勾选返回false。
3、元素是否被启用
有些按钮,可能在页面显示上被灰掉,就是不让点击,这个时候这个按钮是不可用的。那么我们如何判断这个按钮是否能被点击呢?请用isEnabled()方法
WebElement login= driver.findElementById(“loginBtn”);
//webview和原生app通用
login. isEnabled ();
如果可用返回true,如果不可用返回false。
4、submit()
在有表单的的界面上,可以不通过点击按钮进行提交操作,这就需要用到submit()方法
WebElement login= driver.findElementById(“loginBtn”);
//webview和原生的app 提交按钮通用
login. submit();
5、WebView中iFrame处理
在混合应用APP中的webview页面中也会遇到frame的情况,处理情况和web页面中的frame处理一样。
一般会出现一只定位不了的异常,解决:
WebDriver org.openqa.selenium.WebDriver.TargetLocator.frame(String nameOrId)
也提供了一个返回default content的方法:
WebDriver org.openqa.selenium.WebDriver.TargetLocator.defaultContent()
例如:driver.switchTo().frame(“city_set_ifr”); //传入的是iframe的ID
int类型 – iframe在页面中的位置,第几个
String类型 – iframe的名字 比如id
WebElement类型 - 这个iframe的元素定位。
如果要返回到以前的默认content,可以使用:
dr.switchTo().defaultContent();
如果不使用此代码,就不能退出当前iframe,也就是说iframe之外的页面内容你是不能定位和访问的。
Webview页面的相关方法 iOS和Android通用。
6、滑动操作
在appium5.x版本以上,就废除了原来的swipe方法,由TouchAction类来担当!
由于每个设备的分辨率不通过,如果用于不同设备进行测试,最好将滑动anction的位置和距离,用设备的宽高的百分比来进行
static Duration duration=Duration.ofSeconds(1);
//向上滑
public static void swipeUp(AndroidDriver driver){
int width = driver.manage().window().getSize().width; //获取屏幕分辨率的witdh
int height = driver.manage().window().getSize().height;
TouchAction action1 = new TouchAction(driver).press(width / 2,height * 4/ 5).
waitAction(duration).moveTo(width /2, height /4).release();
action1.perform();
}
元素滑动
以知乎app为例,滑动演示~
adb connect 127.0.0.1:21503 //在模拟器上打开知乎、
adb shell dumpsys window | findstr mCurrentFocus //获的包名
开始写代码
public class HelloWorld {
AndroidDriver<AndroidElement> driver = null;
SwipeScreen screen;
static Duration duration = Duration.ofSeconds(1);
@Test
public void helloWorld() throws InterruptedException {
//step1:点击登陆按钮
driver.findElementByXPath("//android.widget.Button[@resource-id='com.zhihu.android:id/login_btn']").click();
//step2:输入用户名和密码
driver.findElementByXPath(" //android.widget.EditText[@resource-id='com.zhihu.android:id/username']")
.sendKeys("15811006613");
driver.findElementByXPath(" //android.widget.EditText[@resource-id='com.zhihu.android:id/password']")
.sendKeys("zhangtongle");
//step3:再次点击登录按钮
driver.findElementByXPath(" //android.widget.Button[@resource-id='com.zhihu.android:id/btn_progress']").click();
//step4:取得主页面的第一个消息列表的位置
AndroidElement element = driver.findElementByXPath(
" //android.support.v7.widget.RecyclerView[@resource-id='com.zhihu.android:id/recycler_view']/android.support.v7.widget.LinearLayoutCompat[1]");
//step5:获得第一个消息列表元素的宽高
int width = element.getSize().getWidth();// 获得元素的宽度 1080
int height = element.getSize().getHeight();// 获得消息元素的高度 511
//step7:获得开始的滑动的x坐标和y坐标
int startX = width / 2;
int starty = height /2 ;
int endX = width / 3; // 相当于让手指滑动到endx 的坐标处
// Step6向左滑动一下,y轴不变,x轴缩小
TouchAction action1 = new TouchAction(driver).press(startX, starty ).waitAction(duration)
.moveTo(endX, starty).release();
action1.perform();
}
@BeforeTest //设置好环境
public void beforeTest() throws MalformedURLException {
/*
* DesiredCapabilities功能详解
*
*/
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("platformName", "Android");
caps.setCapability("platformVersion", "4.4.4");
File apkPath = new File(System.getProperty("user.dir") + "\apk\mobileqq_android.apk");
caps.setCapability("deviceName", "123");
caps.setCapability("appPackage", "com.zhihu.android");// 被测app的包名
caps.setCapability("appActivity", "com.zhihu.android.app.ui.activity.MainActivity");// 被测app的入口Activity名称
// 设置输入法
caps.setCapability("unicodeKeyboard", "True");
caps.setCapability("resetKeyboard", "True");
caps.setCapability("app", apkPath.getAbsolutePath());
caps.setCapability("noRese", true);
driver = new AndroidDriver<AndroidElement>(u, caps);
// 它是 webdirver 提供的一个超时等待。隐式等待一个元素被发现,或一个命令完成。如果超出了设置时间仍未定位到元素则抛出异常。
driver.manage().timeouts().implicitlyWait(15, TimeUnit.SECONDS);
}
@AfterTest //执行完毕,想的要退出
public void afterTest() {
driver.quit();
}
}
滑动坐标说明
如果调试工具用不了,也可以直接真机上屏幕上直观获取:
如果在真实同一分辨率的机器上可以快捷定位appium滑动坐标,设置》开发者选项》指针位置
把手指放那里,手机屏幕就会出现坐标,如果我们要从左滑动右。
1. 记录起始滑动位置的x轴,y轴
2. 记录结束滑动位置的x轴,y轴
注意
从左到右,或从右到左,一直都是X轴在变,而Y轴是不变的
从上到下,或从下到上,一直都是Y轴在变,而X轴是不变的
因为我们手指的原因,在滑动的时候总是不在同一水平线或垂直线,所以都会有一点的波动,但在写脚本的时候可以不用考虑这些
还有一点那就是手机大小不一致,分辨率也不一样,当然坐标X轴与Y轴是不一样的
同样在A手机执行通过的滑动,在B手机可能达不到预期的效果
这个时候,我们可能就得用百分比了,X轴的百分比,Y轴的百分比。
重点-scrollTo滑动查找方法
这个方法的规则是,先下拉刷新下页面,然后查找元素。如果没有再向上滑动继续查找。
scrollTo分两个方法,都是传入字符串,一个是模糊查询一个是精确查找。
scrollTo(String text)模糊查询
scrollToExact(String text)精确查找
由于新版本中 ,Appium删掉了两个方法,为了能跟高级新版本中用老版本的方法,我们下载 老版本的源码copy出来,改改封装成自己的工具类,
static String UiScrollable(String uiSelector) {
return "new UiScrollable(new UiSelector().scrollable(true).instance(0)).scrollIntoView(" + uiSelector
+ ".instance(0));";
}
public AndroidElement scrollTo(String text) {
String uiScrollables = UiScrollable("new UiSelector().descriptionContains("" + text + "")")
+ UiScrollable("new UiSelector().textContains("" + text + "")");
return (AndroidElement) driver.findElement(MobileBy.AndroidUIAutomator(uiScrollables));
}
public AndroidElement scrollToExact(String text) {
String uiScrollables = UiScrollable("new UiSelector().description("" + text + "")")
+ UiScrollable("new UiSelector().text("" + text + "")");
return (AndroidElement) driver.findElement(MobileBy.AndroidUIAutomator(uiScrollables));
}
进度条滑动
7、获取页面元素
获取页面元素用到的方法是getPageSource,他会把当前页面保存成一个xml文件。我们通常是为了验证某些元素是否存在
driver.getPageSource().contains("黑客"); //返回一个布尔值用来验证~
8、点击、长按官方API详解
Element.click();元素点击
TouchAction.tap(x,y);根据坐标点击屏幕上的绝对位置。
TouchAction.tap(PointOption tapOptions) ;点击一个位置。
//可以通过public static PointOption point(int xOffset,int yOffset)
它创建一个带有x和y坐标的PointOption的内置实例。 这是从屏幕的左上角偏移。
TouchAction.tap(TapOptions tapOptions);点击一个元素。
TapOptions 可以通过withTapsCount(int tapsCount)方法来获取点击的次数。该值应该大于零。
TouchAction.longPress(int x, int y, java.time.Duration duration)
被下方方法代替:use longPress(LongPressOptions)
TouchAction.longPress(PointOption longPressOptions)
按住元素的中心,直到上下文菜单事件已经触发。
TimeOutDuration 类表示等待元素呈现的持续时间。
注意:TouchAction这个类想要执行,一定要搭配
TouchAction.press().waitAction().moveTo().release().perform();
解释:按下、等待、移动、发布、执行,so这回大家知道官方要删掉swipe这个方法的原因了吧!把简单的方法,细分化,可以实现更加复杂的方法!
细化是为了实现更复杂的目标!
9、输入框操作
输入框操作顺序为:定位、清空、输入、删除的操作。
定位上面已经介绍过了,略
清空、输入:appium是用的selenium 的方法!
class org.openqa.selenium.remote.RemoteWebElement
清空操作有selenium给出的方法是clear,但这个方法有时会无法完全清空输入框字符。所以我们需要自己封装一个方法。
例如:
public void clear(AndroidElement ae) {
int length = ae.getText().length();
driver.pressKeyCode(AndroidKeyCode.KEYCODE_MOVE_END);// 让光标定到字符的末尾
for (int i = 0; i < length; i++) {
driver.pressKeyCode(AndroidKeyCode.DEL);
}
}
输入:用sendKey方法。
删除:与清空一样,不同处是自己决定删除几个字符。
例如:driver.pressKeyCode(67)
这个常量值可以在你appium api 下的 AndroidKeyCode下有说明:
10、弹出层处理
//处理安卓web版本的弹出层,原生的可以直接定位获取
Alert alert = driver.switchTo().alert();
alert.sendKeys("123"); //输入数据
String text = alert.getText(); //获取弹出层信息或数据。
alert.dismiss();// 取消
alert.accept();// 确定
11、下拉列表
原生下拉列表的要查找元素直接用scrollTo去查找。
如果是web版本的
对于webview 可以直接使用selenium 的Select类。
思路是:先定位出你要处理的下拉框(元素),然后将此元素传入Select对象中,接着用Select中的相关方法来选取下拉值。
12、元素放大、缩小的手势方法
//放大元素 安卓版本
@SuppressWarnings({ "rawtypes", "deprecation" })
public void zoom(AndroidElement el) {
MultiTouchAction multiTouch = new MultiTouchAction(driver);
Dimension dimensions = el.getSize();
Point upperLeft = el.getLocation();
Point center = new Point(upperLeft.getX() + dimensions.getWidth() / 2,
upperLeft.getY() + dimensions.getHeight() / 2);
int yOffset = center.getY() - upperLeft.getY();
TouchAction action0 = new TouchAction(driver).press(center.getX(), center.getY())
.moveTo(el, center.getX(), center.getY() - yOffset).release();
TouchAction action1 = new TouchAction(driver).press(center.getX(), center.getY())
.moveTo(el, center.getX(), center.getY() + yOffset).release();
multiTouch.add(action0).add(action1);
multiTouch.perform();
}
/*
* 缩小元素
* @param el The element to pinch
*/
public void pinch(AndroidElement el) {
MultiTouchAction multiTouch = new MultiTouchAction(driver);
Dimension dimensions = el.getSize();
Point upperLeft = el.getLocation();
Point center = new Point(upperLeft.getX() + dimensions.getWidth() / 2,
upperLeft.getY() + dimensions.getHeight() / 2);
int yOffset = center.getY() - upperLeft.getY();
TouchAction action0 =
new TouchAction(driver).press(el, center.getX(), center.getY() - yOffset).moveTo(el)
.release();
TouchAction action1 =
new TouchAction(driver).press(el, center.getX(), center.getY() + yOffset).moveTo(el)
.release();
multiTouch.add(action0).add(action1);
multiTouch.perform();
}
13、判断app应用是否安装
//判断是否安装知乎app ,没安装进行安装
if ( driver.isAppInstalled("com.zhihu.android")==false) {
driver.installApp(System.getProperty("user.dir") + "\apk\mobileqq_android.apk");
}
安装app
driver.installApp(System.getProperty("user.dir") + "\apk\mobileqq_android.apk");
|
删除app
driver.removeApp("com.zhihu.android");
重点类:InteractsWithApps
九、发生错误时自动截图
1、基于TestNG的TestListenerAdapter功能,
a、可以通过xml配置的形式,面向全局的
<suite name="" verbose="">
<listeners>
<listener class-name=" com.zxp_01.Error" />
</listeners>
<test> .......... </test>
</suite>
b、可以通过注解的方式,面向某一个类的
@Listeners({Error.class})
public class HelloWorld {}
import java.io.File;
import java.io.IOException;
import org.apache.commons.io.FileUtils;
import org.openqa.selenium.OutputType;
import org.testng.ITestResult;
import org.testng.TestListenerAdapter;
public class Error extends TestListenerAdapter {
@Override
public void onTestFailure(ITestResult tr) {
// 输出为类型文件
File screenshot = HelloWorld.driver.getScreenshotAs(OutputType.FILE);
// 定义输出路径
File location = new File("./screen/");
// 判断是否路径是否存在,不存在就创建
if (location.exists()) {
location.mkdir();
}
// 定义保存图片名称
String screenShotName = location.getAbsolutePath() + "\pic.png";
// 保存图片
try {
FileUtils.copyFile(screenshot, new File(screenShotName));
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
十、HybirdApp测试
webWiew 因为不是控件不能用隐式的等待,使用显示的等待!
因为要调试webview的元素,UIautomatorView 工具就不好用了!所以接下来,移动端webview调试工具的!
移动端webview调试工具:spy-debugger
一站式页面调试、抓包工具。远程调试任何手机浏览器页面,任何手机移动端webview(如:微信,HybirdApp等)。支持HTTP/HTTPS,无需USB连接设备
安装步骤(先决条件,机器上有node.js)
第一步:npm install spy-debugger -g
第二步:手机和PC保持在同一网络下(比如同时连到一个Wi-Fi下)
第三步:命令行输入spy-debugger,按命令行提示用浏览器打开相应地址。
第四步:设置手机的HTTP代理,代理IP地址设置为PC的IP地址,端口为spy-debugger的启动端口(默认端口:9888)。
Android设置代理步骤:设置 - WLAN - 长按选中网络 - 修改网络 - 高级 - 代理设置 - 手动
第五步:手机安装证书。注:手机必须先设置完代理后再通过(非微信)手机浏览器访问http://s.xxx 安装证书。
第六步:用手机浏览器访问你要调试的页面即可。
实战:
@Test
public void hun() throws InterruptedException {
AndroidElement url = driver.findElementById("com.android.browser:id/url");
url.click();
url.clear();
driver.pressKeyCode(66);
Thread.sleep(5000);
Set<String> contexts = driver.getContextHandles();
for (String context : contexts) {
System.out.println("context:" + context);
if (context.contains("WEBVIEW_com.android.browser")) {
Thread.sleep(3000);// 等它一会
WebDriver driver1 = driver.context(context);
Thread.sleep(5000);
WebElement keys = driver1.findElement(By.xpath("//*[@id='index-kw']"));
keys.click();
keys.sendKeys("同乐学堂");
WebElement btn = driver1.findElement(By.xpath("//*[@id='index-bn']"));
btn.click();
Thread.sleep(5000);
}
}
}
十一、等待
等待有三种方式:1、隐式等待、显示等待、强制等待.
1、隐式等待
// 它是 webdirver 提供的一个超时等待。隐式等待一个元素被发现,或一个命令完成。
如果超出了设置时间仍未定位到元素则抛出异常。
driver.manage().timeouts().implicitlyWait(15, TimeUnit.SECONDS);
//等待页面加载时间,我们设置一个超时时间,如果在设置的时间内页面加载完成就立即继续执行 。
如果一直不能完全加载,规定时间到后会报出异常。
driver.manage().timeouts().pageLoadTimeout(16, TimeUnit.SECONDS);
//设置异步脚本超时时间,也就是有async属性的JS脚本
driver.manage().timeouts().setScriptTimeout(17, TimeUnit.SECONDS);
2、显示等待
//设置等待10秒,每2秒检查一次元素是否加载成功!
WebDriverWait wait = new WebDriverWait(driver, 10,2000);
wait.until(new ExpectedCondition<WebElement>(){
@Override
public WebElement apply(WebDriver d) {
return d.findElement(By.xpath("//android.widget.RelativeLayout[@index='2']"));
}
}).click();
3、强制等待
Therad.sleep(毫秒);
十二、关键字驱动测试
主要关键字包括三类:被操作对象(Item)、操作(Operation)、值(Value),用面向对象的形式可将其表现为Item.Operation(Value).
关键字驱动测试(Keyword-driven testing),也叫做表格驱动测试或者基于行为词的测试
优点
·只需要自动化测试人员封装好相关关键字操作,就可以提供给黑盒测试人员使用,黑盒测试人员只需要掌握元素定位的方式以及对关键字框架的使用即可
·条理清晰,很容易上手使用
缺点
·需要自动化测试人员花费大量时间提前把关键字框架中的操作提前封装好
·对于复杂的测试需求,关键字框架对于操作的封装难度较大
1、利用Testng框架把预制环境的参数写到xml文件中,进行预制环境初始化。
2、加入测试的用例类
3、用例类详解
search类
执行并解析Excle文件中的文字版的测试用例,把中文版的action、和page定位元素与之后台定义封装好的action,进行一一对应,从而达到关键字和数据驱动自动化测试。
参数详解:
search:excel文件的名字
001_SearchDemo:excel中sheet的名字
appiumUtil:引用appium api的自定义封装
platformName:测试平台安卓或者是ios
描述:读取excel中每个sheet的操作步骤,进而生成测试用例
SuperAction.parseExcel("Search","001_SearchDemo",appiumUtil,platformName);
// 循环每行的操作,根据switch来判断每行的操作是什么,然后转换成具体的代码,从第二行开始循环,因为第一行是列的说明数据。
// 然后通过字符串数组存储找到定位方式、定位值,找到以后传给appiumutil封装类里面操作APP元素的方法。
locateSplit = getPageElementLocator(sheet, i, locateColumnIndex,locator.split("\.")[0]);
appiumUtil.click(getLocateWay(locateSplit[0], locateSplit[1]));
for (int i = 1; i < rows; i++) {
logger.info("正在解析excel:["+founction+".xlsx]中的sheet(用例):["+caseName+"]的第"+i+"行步骤...");
String action = sheet.getRow(i).getCell(actionColumnIndex).getStringCellValue();
Row row = sheet.getRow(i);
if (row != null) {
switch (action) {
case "输入":
//先设置Cell的类型,然后就可以把纯数字作为String类型读进来了
sheet.getRow(i).getCell(testDataColumnIndex).setCellType(Cell.CELL_TYPE_STRING);
testData = sheet.getRow(i).getCell(testDataColumnIndex).getStringCellValue(); //测试数据
locator = sheet.getRow(i).getCell(locateColumnIndex).getStringCellValue();//获取步骤中的元素定位
//locator.split("\.")[0]分离出页面名称,比如HomePage.我的单据,提取出HomePage
locateSplit = getPageElementLocator(sheet, i, locateColumnIndex,locator.split("\.")[0]); //找到定位方式、定位值
appiumUtil.typeContent(getLocateWay(locateSplit[0], locateSplit[1]), testData);
break;
case "点击":
locator = sheet.getRow(i).getCell(locateColumnIndex).getStringCellValue();//获取步骤中的定位
//locator.split("\.")[0]分离出页面名称,比如HomePage.我的单据,提取出HomePage
locateSplit = getPageElementLocator(sheet, i, locateColumnIndex,locator.split("\.")[0]);
appiumUtil.click(getLocateWay(locateSplit[0], locateSplit[1]));
break;
case "暂停":
//先设置Cell的类型,然后就可以把纯数字作为String类型读进来了
sheet.getRow(i).getCell(testDataColumnIndex).setCellType(Cell.CELL_TYPE_STRING);
testData = sheet.getRow(i).getCell(testDataColumnIndex).getStringCellValue();
appiumUtil.pause(Integer.parseInt(testData));
break;
case "等待元素":
//先设置Cell的类型,然后就可以把纯数字作为String类型读进来了
sheet.getRow(i).getCell(testDataColumnIndex).setCellType(Cell.CELL_TYPE_STRING);
testData = sheet.getRow(i).getCell(testDataColumnIndex).getStringCellValue();
locator = sheet.getRow(i).getCell(locateColumnIndex).getStringCellValue();//获取步骤中的定位
//locator.split("\.")[0]分离出页面名称,比如HomePage.我的单据,提取出HomePage
locateSplit = getPageElementLocator(sheet, i, locateColumnIndex,locator.split("\.")[0]);
appiumUtil.waitForElementToLoad(Integer.parseInt(testData), getLocateWay(locateSplit[0], locateSplit[1]));
break;
case "清除":
locator = sheet.getRow(i).getCell(locateColumnIndex).getStringCellValue();//获取步骤中的定位
//locator.split("\.")[0]分离出页面名称,比如HomePage.我的单据,提取出HomePage
locateSplit = getPageElementLocator(sheet, i, locateColumnIndex,locator.split("\.")[0]);
appiumUtil.clear(getLocateWay(locateSplit[0], locateSplit[1]));
break;
case "检查文本":
testData = sheet.getRow(i).getCell(testDataColumnIndex).getStringCellValue();
locator = sheet.getRow(i).getCell(locateColumnIndex).getStringCellValue();//获取步骤中的定位
//locator.split("\.")[0]分离出页面名称,比如HomePage.我的单据,提取出HomePage
locateSplit = getPageElementLocator(sheet, i, locateColumnIndex,locator.split("\.")[0]);
appiumUtil.isTextCorrect(appiumUtil.getText(getLocateWay(locateSplit[0], locateSplit[1])), testData);
break;
case "执行JS点击":
locator = sheet.getRow(i).getCell(locateColumnIndex).getStringCellValue();//获取步骤中的定位
//locator.split("\.")[0]分离出页面名称,比如HomePage.我的单据,提取出HomePage
locateSplit = getPageElementLocator(sheet, i, locateColumnIndex,locator.split("\.")[0]);
appiumUtil.executeJS("arguments[0].click();", appiumUtil.findElement(getLocateWay(locateSplit[0], locateSplit[1])));
break;
case "切换到context":
testData = sheet.getRow(i).getCell(testDataColumnIndex).getStringCellValue();
appiumUtil.switchWebview(testData);
break;
case "是否包含指定文本":
locator = sheet.getRow(i).getCell(locateColumnIndex).getStringCellValue();//获取步骤中的定位
testData = sheet.getRow(i).getCell(testDataColumnIndex).getStringCellValue();
locateSplit = getPageElementLocator(sheet, i, locateColumnIndex,locator.split("\.")[0]);
appiumUtil.isContains(appiumUtil.getText(getLocateWay(locateSplit[0], locateSplit[1])), testData);
break;
case "处理欢迎和定位界面":
locator = sheet.getRow(i).getCell(locateColumnIndex).getStringCellValue();//获取步骤中的定位
locateSplit = getPageElementLocator(sheet, i, locateColumnIndex,locator.split("\.")[0]);
boolean flag = appiumUtil.doesElementsExist(getLocateWay(locateSplit[0], locateSplit[1]));
if(flag){
appiumUtil.click(getLocateWay(locateSplit[0], locateSplit[1]));
}else{
return;
}
break;
default:
logger.error("你输入的操作:["+action+"]不被支持,请自行添加");
Assert.fail("你输入的操作:["+action+"]不被支持,请自行添加");
}
}
} getPageElementLocator(sheet, i, locateColumnIndex,locator.split("\.")[0]);
appiumUtil.isContains(appiumUtil.getText(getLocateWay(locateSplit[0], locateSplit[1])), testData);
break;
case "处理欢迎和定位界面":
locator = sheet.getRow(i).getCell(locateColumnIndex).getStringCellValue();//获取步骤中的定位
locateSplit = getPageElementLocator(sheet, i, locateColumnIndex,locator.split("\.")[0]);
boolean flag = appiumUtil.doesElementsExist(getLocateWay(locateSplit[0], locateSplit[1]));
if(flag){
appiumUtil.click(getLocateWay(locateSplit[0], locateSplit[1]));
}else{
return;
}
break;
default:
logger.error("你输入的操作:["+action+"]不被支持,请自行添加");
Assert.fail("你输入的操作:["+action+"]不被支持,请自行添加");
}
}
}
十三、appium开源项目
目前的初级板块,基本实现了如下功能:
发现一个开源项目不错,利用java swing开发的跨平台测试的初级版本。
1、下载
2、安装谷歌浏览器调试工具(使用绿色代理)
3、双击Extensions 下的Ext_Certificate.cer,安装证书
4、双击“Run.bat”启动UI
如果对此项目感兴趣的小伙伴,可以联系QQ:1071235258,进行二次扩展开发!
十四、总结
appium 安卓核心类继承关系
AndroidDriver--->AppiumDriver--->DefaultGenericMobileDriver---->RemoteWebDriver(selenium)
AndroidElement--》MobileElement--》DefaultGenericMobileElement---》RemoteWebElement(selenium)
selenium核心类
通篇确实没有介绍UiAutoMator的内容,因为发现有人总结的比较好了,
如果可以翻墙,可以直接访问谷歌上UIAUTOMATER的API,如果翻不了墙,这里可以提供一个国内可以访问的镜像:
参考:
appium自动化测试教程: http://edu.51cto.com/course/12630.html