UiSelector的作用用来选择定位控件,利用控件属性描述目标控件,供控件匹配使用。
在自动化测试过程中,UiObject拥有其作为成员变量,使用非常广泛,只要做到利用其属性来描述约束控件的唯一性。
所有的方法调用后返回的都是UiSelector,按匹配的策略大体可以分为以下几种类型:
1、完全匹配。
返回值 | 方法及说明 |
UiSelector |
checked(boolean val) / selected(boolean val) 目标控件是否可以被勾选(一般为checkBox) / 是否被选中 |
UiSelector |
enabled(boolean val) / clickable(boolean val) / longClickable(boolean val) 目标控件是否可用 / 响应点击 / 响应长按 |
UiSelector |
className(Class<T> type) / className(String className) 指定目标控件的类型为type |
UiSelector |
description(String desc) 指定目标控件的描述为desc |
UiSelector |
focusable(boolean val) / focused(boolean val) 目标控件是否可被聚焦 / 是否正被聚焦 |
UiSelector |
index(int index) 指定目标控件的下标为index |
UiSelector |
instance(int instance) 指定目标控件为符合条件的第N个实例,通常在集合遍历时使用 |
UiSelector |
packageName(String name) 指定目标控件的包名为name |
UiSelector |
text(String text) 指定目标控件的文案为text |
UiSelector |
scrollable(boolean val) 目标控件是否可以滚动,当listView为一页时实际上为false |
UiSelector |
resourceId(String id) 指定目标控件的ID为id |
2、部分包含。
返回值 | 方法及说明 |
UiSelector |
descriptionStartsWith(String desc) 指定目标控件描述以desc开头 |
UiSelector |
descriptionContains(String desc) 指定目标控件描述包含desc |
UiSelector |
textStartsWith(String text) 指定目标控件文案以text开头 |
UiSelector |
textContains(String text) 指定目标控件文案包含text |
3、正则匹配。
返回值 | 方法及说明 |
UiSelector |
textMatches(String regex) / descriptionMatches(String regex) 指定目标控件文案 / 描述匹配regex |
UiSelector |
packageNameMatches(String regex) 指定目标控件包名匹配regex |
UiSelector |
classNameMatches(String regex) 指定目标控件类名匹配regex |
UiSelector |
resourceIdMatches(String regex) 指定目标控件ID匹配regex |
4、父子关系。
返回值 |
方法及说明 |
UiSelector |
childSelector(UiSelector selector) 指定目标控件拥有子节点匹配selector |
UiSelector |
fromParent(UiSelector selector) 指定目标控件拥有父节点selector |
定位元素:
下面来讲一下怎样来定位测试过程中的控件:
首先使用uiautomatorviewer来查看界面的快照
1、通过文本信息来定位:
通过控件的text属性定位控件应该是最常用的一种方法了,毕竟移动应用的屏幕大小有限,存在text重复的可能性并不大,就算真的有重复,可以添加其他定位方法来缩写误差。
1.1 UISelector.text方法
UiObject obj = new UiObject(new UiSelector().text(“重置"));
该方法通过直接查找当前界面上所有的控件来比较每个控件的text属性是否如预期值来定位控件,挺好理解的,所以就没有必要细说了。
1.2. UISelector.textContains方法
UiObject obj = new UiObject(new UiSelector().textContains("重"));
此方法跟以上方法类似,但是不需要输入控件的全部text信息,如例子,只要写“重”或“置”即可。
1.3 UISelector.textStartsWith方法
UiObject obj = new UiObject(new UiSelector().textStartsWith("重置"));
顾名思义,通过判断一个控件的text的开始是否和预期的字串相吻合来获得控件,其实个人觉得这个方法存在的必要性不强,因为它的功能完全可以用上面的方法或者下面的正则表达式的方法取代。况且既然你提供了textStartsWith方法,为什么你不提供个textEndWith的方法呢!
1.4 UISelector.textMatches方法
UiObject obj = new UiObject(new UiSelector().textMatches("^重.*"));
这个方法是通过正则表达式的方式来比较控件的text来定位控件,这里有意思的是用户使用的正则表达式是有限制的,请看该方法的官方描述:“ Set the search criteria to match the visible text displayed for a widget (for example, the text label to launch an app). The text for the widget must match exactly with the string in your input argument ”。第一句我们不用管它,关键是第二句,翻译过来就是“目标控件的text(的所有内容)必须和我们输入的正则表达式完全匹配 ”。什么意思呢? 意思就是你不能像往常的正则表达式那样通过比较text的部分吻合来获得控件 。以下面代码为例子:
UiObject obj1 = new UiObject(new UiSelector().textMatches("^重"));
正常来说这个正则表达式是没有问题的,它的意思就是想要“获取以“重”开头的text的控件,至于“重”是什么值,没有必要去管它”。但是按照我们上面的官方描述,这样子是不行的,你必须要把正则表达式补充完整以使得正而表达式和控件的text完全进行匹配,至于你用什么通配符或者字串就完全按照正则表达式的语法了。
如上图,正则没有补充完整,就无法找到该控件。注意这个限制在UISlector使用所有的正则表达式相关的方法中都有效哦。
2、通过控件ID定位。
在Android API Level18及其以上的版本增加了一个Android控件的属性ResourceId, 所以要注意在使用这种方法之前先确保你的目标测试设备和你的UIAutomoator库jar包使用的都是API Level 18以上的版本 。例如创建工程时使用的就是本地sdk中版本19的库;
2.1 UiSelector.resourceId方法
UiObject obj = new UiObject(new UiSelector().resourceId("com.android.settings:id/title"));
2.2 UiSelector.resourceIdMatches方法
UiObject obj1 = new UiObject(new UiSelector().resourceIdMatches(".+id/title"));
注意正则表达式的限制和章节1.4描述的一致
3、通过控件className定位。
通过这种方法定位控件存在的一个问题是很容易发生重复,所以一般都是先用这种方法去缩小目标控件范围,然后再去添加其他如text判断等条件进行控件定位。
3.1 UISelector.className方法
UiObject obj = new UiObject(new UiSelector().className("android.widget.TextView").text("重置"));
实例中首先通过ClassName找到所有的TextView控件,然后再在这些TextView控件查找text是”Add note“的控件。
3.2 UISelector.classNameMatches方法
UiObject obj1 = new UiObject(new UiSelector().classNameMatches(".*TextView$"));
通过正则表达式判断className是否和预期的一致, 注意正则表达式的限制和章节1.4描述的一致
4. 通过伪xpath方法定位
UiSelector类提供了一些方法根据控件在界面的XML布局中的层级关系来进行定位,但是UiAutomator又没有真正的提供类似Appium的findElementWithXpath相关的方法,所以这里我就称之为伪xpath方法。
4.1 通过UiSelector.fromParent或UiObject.getFromParent方法
这种方法我觉得最有用的情况是测试代码当前在操作的是同一层级的一组控件中的某一个控件,转而又需要操作同一层级的另外一个控件的时候。下面的实例就是通过“应用中心”控件的父控件找到其同一层级的兄弟控件“王牌联盟”。这里分别列出了通过UiObject.getFromParent方法和UiSelector.fromParent方法的实例,事实上他们的功能是一样的。
UiObject.getFromPatrent方法:
UiObject zhushou = new UiObject(new UiSelector().text("应用中心"));
assertEquals(zhushou.exists(), true);
UiObject lm = zhushou.getFromParent(new UiSelector().text("王牌联盟"));
assertEquals(lm.getText(),"王牌联盟");
UiSelector.fromParent方法(这个例子有点迂回笨拙,但为了演示功能就将就着看吧):‘
UiObject lmapp = new UiObject(new UiSelector().text("应用中心").fromParent(new UiSelector().text("王牌联盟")));
4.2 通过UiSelector.childSelector或UiObject.getChild方法
这种方法是在已知父控件的时候如何快速的查找该父控件下面的子控件。
UiObject.getChild方法:
UiObject parentView = new UiObject(new UiSelector().packageName("com.sec.android.app.launcher"));
UiObject zhushou = parentView.getChild(new UiSelector().text("应用中心"));
assertEquals(zhushou.exists(), true);
assertEquals(zhushou.getText(),"应用中心");
UiSelector.childSelector方法:
UiObject lmapp = new UiObject(new UiSelector().packageName("com.sec.android.app.launcher").childSelector(new UiSelector().text("王牌联盟")));
5. 通过contentDescription定位
在UiAutomator框架和使用了Uiautomator框架的Appium中,控件的属性contentDescription一直是强调开发人员需要添加进去的,因为两个原因:
(1)有些控件使用其他办法很难或者根本没有办法定位
(2)最重要的是给每个控件的contentDescription设计个唯一值让我们可以非常快速的定位控件。
以下的实例并没有真正跑过的,因为没有找到控件是有contentDescription这个属性的,但是如果我们假设联盟app这个控件的contentDescription是“lmappDesc”的话,代码的相应写法应该就如下了。
5.1 UiSelector.description方法
UiObject lmapp = new UiObject(new UiSelector().description("lmappDesc));
5.2 UiSelector.descriptionContains方法
UiObject lmapp = new UiObject(new UiSelector().descriptionContains("lmapp"));
5.3 UiSelector.descriptionStartWith方法
UiObject lmapp = new UiObject(new UiSelector().descriptionStartsWith("lmapp"));
5.4 UiSelector.descriptionMatches方法
UiObject lmapp = new UiObject(new UiSelector().descriptionMatches("^lmapp.*$"));
测试脚本:
package com.uiautomator.demo;
import com.android.uiautomator.core.UiObject;
import com.android.uiautomator.core.UiObjectNotFoundException;
import com.android.uiautomator.core.UiSelector;
import com.android.uiautomator.testrunner.UiAutomatorTestCase;
public class MUiSelector extends UiAutomatorTestCase {
public static void main(String[] args) {
String testClass, testName;
testClass = "com.uiautomator.demo.MUiSelector";
testName = "testXpath_child";
new UiAutomatorHelper(testClass, testName);
}
public void testText() {
UiObject obj = new UiObject(new UiSelector().textMatches("^重.*"));
System.out.println(obj.exists());
UiObject obj1 = new UiObject(new UiSelector().textMatches("^重"));
System.out.println(obj1.exists());
}
public void testId(){
UiObject obj = new UiObject(new UiSelector().resourceId("com.android.settings:id/title"));
UiObject obj1 = new UiObject(new UiSelector().resourceIdMatches(".+id/title"));
}
public void testClassName(){
UiObject obj = new UiObject(new UiSelector().className("android.widget.TextView").text("重置"));
UiObject obj1 = new UiObject(new UiSelector().classNameMatches(".*TextView$"));
}
public void testXpath_parent() throws UiObjectNotFoundException{
//UiObject.getFromPatrent方法
UiObject zhushou = new UiObject(new UiSelector().text("应用中心"));
assertEquals(zhushou.exists(), true);
UiObject lm = zhushou.getFromParent(new UiSelector().text("王牌联盟"));
assertEquals(lm.getText(),"王牌联盟");
//UiSelector.fromParent方法
UiObject lmapp = new UiObject(new UiSelector().text("应用中心").fromParent(new UiSelector().text("王牌联盟")));
assertEquals(lmapp.getText(),"王牌联盟");
}
public void testXpath_child() throws UiObjectNotFoundException{
//UiObject.getChild方法
UiObject parentView = new UiObject(new UiSelector().packageName("com.sec.android.app.launcher"));
UiObject zhushou = parentView.getChild(new UiSelector().text("应用中心"));
assertEquals(zhushou.exists(), true);
assertEquals(zhushou.getText(),"应用中心");
//UiSelector.childSelector方法
UiObject lmapp = new UiObject(new UiSelector().packageName("com.sec.android.app.launcher").childSelector(new UiSelector().text("王牌联盟")));
assertEquals(lmapp.getText(),"王牌联盟");
}
}