为UiAutomatorViewer添加xpath支持

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为UiAutomatorViewer添加xpath支持相关的知识,希望对你有一定的参考价值。

UiAutomatorViewer是android SDK自带的测试工具,用来查看手机或模拟器上的界面元素,小巧,简单,开箱即用,十分方便。美中不足之处在于,它不能获取界面元素的xpath.

写自动化测试脚本时,xpath是一种非常方便的定位方式。Appium等一些成熟的工具框架可以获取到界面元素xpath,但使用起来稍有点重量级。那么是否也可以给UiAutomatorViewer添加xpath支持呢?

答案是肯定的。

首先下载UiAutomatorView源代码,我用的地址是https://android.googlesource.com/platform/frameworks/testing/+/aecdc4a/uiautomator/utils/

代码不多,可以直接组织成一个Java项目,快速上手。如下图,其中image目录在下方,没有列出。

技术分享

 

入口类是com.android.uiautomator.UiAutomatorViewer,没什么好说的,不用修改。

主要关注的类是com.android.uiautomator.tree.UiHierarchyXmlLoader,重点是以下代码片断

        DefaultHandler handler = new DefaultHandler(){
            BasicTreeNode mParentNode;
            BasicTreeNode mWorkingNode;
            @Override
            public void startElement(String uri, String localName, String qName,
                    Attributes attributes) throws SAXException {
                boolean nodeCreated = false;
                // starting an element implies that the element that has not yet been closed
                // will be the parent of the element that is being started here
                mParentNode = mWorkingNode;
                if ("hierarchy".equals(qName)) {
                    mWorkingNode = new RootWindowNode(attributes.getValue("windowName"));
                    nodeCreated = true;
                } else if ("node".equals(qName)) {
                    UiNode tmpNode = new UiNode();
                    for (int i = 0; i < attributes.getLength(); i++) {
                        tmpNode.addAtrribute(attributes.getQName(i), attributes.getValue(i));
                    }

UiAutomatorViewer调用安卓命令,生成当前界面的XML,这段代码用来解析XML,生成控件树。hierarchy是根节点,其余节点名称都是node,接下来的for循环里把node的属性存入控件节点。

生成xpath的原理:

1. 若节点resource-id属性非空且唯一,则xpath为 //CLASS[@resource-id=‘...‘],其中CLASS就是节点的class属性,比如android.widget.TextView

2. 若节点text属性非空且唯一,则xpath为 //CLASS[@text=‘...‘]

3. 若节点content-desc属性非空且唯一,则xpath为 //CLASS[@content-desc=‘...‘]

4. 若以上三属性都不唯一,则尝试它们的两两组合是否唯一,比如resource-id和text组合唯一,则xpath为 //CLASS[@resource-id=‘...‘ and @text=‘...‘]

5. 三属性两两组合不唯一,则尝试三者组合是否唯一

6. 以上条件全不满足,则先获取父节点的xpath,再把当前节点拼上去,如://android.widget.ListView[@resource-id=‘android:id/list‘]/android.widget.LinearLayout[6],其中android.widget.LinearLayout[6]代表当前节点,前面的部分则是父节点。这个过程需要以层序遍历控件树,这样当处理一个节点时,可以保证它的父节点xpath已生成完毕。

 

有一个细节需要注意,安卓设备生成的界面XML中,每个节点有一个index属性,表示它作为父节点的第几个子节点,编号从0开始。需要注意的是,这个index不区分子节点类型。比如,一个父节点有6个子节点,那么无论它们是什么类型,index属性只与它们的次序有关。

但在xpath中确不是这样,比如前面第6条中的例子,//android.widget.ListView[@resource-id=‘android:id/list‘]/android.widget.LinearLayout[6],它表示的是第6个类型为android.widget.LinearLayout的子节点,也就是说xpath中的序号是区分类型的。

为了处理这个问题,我在第一版的实现中,把xpath按这样生成://android.widget.ListView[@resource-id=‘android:id/list‘]/*[6], 也就是说,忽略子节点类型,直接获取第6个,这个序号就是节点的index属性再加1,因为xpath是从1开始编号,而控件树index属性从0开始。

一般情况下,这是可以的,但有少数情况,控件树的index属性不连续,比如第一个子节点index为0,第二个子节点index为2,1被跳过去了。那么当我还是按照前面方法生成xpath时,第二个节点就找不到了。

解决办法还是有的,当一个节点作为子节点被添加到父节点时,检查前面已经添加过几个同类型的节点,根据这个信息,确定自己的编号。为了与index区分,这个“编号”属性取名为classIndex,表示同类型子节点中的序号。比如,现在一个类型为android.widget.LinearLayout的节点被添加到父节点,发现父节点已经有了两个该类型的子节点,同时还有其他类型子节点若干,那么这个新添加的节点编号就是3,即classIndex=3

 

生成xpath的过程应该在整个XML解析完毕之后再开始,从根节点出发,按层序遍历节点生成xpath和classIndex,并调用UiNode.addAttribute(key, value)添加到节点中。需要注意的是根节点的xpath为:/hierarchy

 

大功告成,启动程序测试一下效果吧。如下图,右下角即是控件元素对应的xpath

技术分享

 

以上是关于为UiAutomatorViewer添加xpath支持的主要内容,如果未能解决你的问题,请参考以下文章

uiautomatorviewer获取控件元素增强版

app控件唯一相对Xpath自动生成(增强版uiautomatorviewer)

uiautomatorviewer 优化定位符生成,支持生成Java,Python自动化代码

Appium 元素定位 控件定位 uiautomatorviewer TouchAction Toast

Appium 元素定位 控件定位 uiautomatorviewer TouchAction Toast

uiautomatorviewer.bat中模拟器实际显示为竖屏但是录制为横屏