当前位置: 首页 > news >正文

为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

 

转载于:https://www.cnblogs.com/venux021/p/7355184.html

相关文章:

  • Java8 日期时间API
  • hp打印机怎么连接电脑_打印机连接电脑怎么做
  • pycharm怎么打开python shell-如何在PyCharm运行配置中运行shell脚本而不是python?
  • pdf exe如何提取pdf文件_PDF文件如何另存为
  • 苹果隐藏app_初探iOS 14主屏幕体验:小部件、App Library、隐藏应用等
  • 医保业务综合服务终端技术规范_25项社保医保业务随时办!三亚启用“政务便民服务站”自助机...
  • 事件模型
  • simulink降维观测器设计_现代控制理论线性系统入门(九)设计状态观测器
  • java nio中,HeapByteBuffer与DirectByteBuffer的区别
  • excel找到对应数据的列指标_python数据分析——医院销售数据实战案例
  • 二维数组元素的地址
  • 大话2烧法助手_大话西游2说出你自己的服务器名称,看看有没有一起玩耍的小伙伴...
  • 运行项目时报Server Tomcat v8.0 Server at localhost failed to start.
  • web安全
  • python 元组_python学习04-2:元组
  • 【Leetcode】101. 对称二叉树
  • 【划重点】MySQL技术内幕:InnoDB存储引擎
  • 〔开发系列〕一次关于小程序开发的深度总结
  • 2019年如何成为全栈工程师?
  • leetcode386. Lexicographical Numbers
  • MySQL主从复制读写分离及奇怪的问题
  • node.js
  • vue2.0一起在懵逼的海洋里越陷越深(四)
  • vuex 学习笔记 01
  • webpack入门学习手记(二)
  • 初识 beanstalkd
  • 从重复到重用
  • 多线程 start 和 run 方法到底有什么区别?
  • 原生JS动态加载JS、CSS文件及代码脚本
  • (10)STL算法之搜索(二) 二分查找
  • (3)选择元素——(14)接触DOM元素(Accessing DOM elements)
  • (C#)一个最简单的链表类
  • (Redis使用系列) Springboot 使用redis实现接口幂等性拦截 十一
  • (附源码)springboot家庭财务分析系统 毕业设计641323
  • (三)Honghu Cloud云架构一定时调度平台
  • (三)mysql_MYSQL(三)
  • (三十五)大数据实战——Superset可视化平台搭建
  • (学习日记)2024.01.19
  • (转)shell中括号的特殊用法 linux if多条件判断
  • (转)创业家杂志:UCWEB天使第一步
  • .net core 依赖注入的基本用发
  • .NET DataGridView数据绑定说明
  • .NET 设计模式—适配器模式(Adapter Pattern)
  • .NET 中什么样的类是可使用 await 异步等待的?
  • .NET6实现破解Modbus poll点表配置文件
  • .Net8 Blazor 尝鲜
  • .NET命名规范和开发约定
  • @Transactional类内部访问失效原因详解
  • [20160807][系统设计的三次迭代]
  • [BZOJ4016][FJOI2014]最短路径树问题
  • [dfs搜索寻找矩阵中最长递减序列]魔法森林的秘密路径
  • [ERROR] Plugin 'InnoDB' init function returned error
  • [GN] 设计模式——面向对象设计原则概述
  • [HJ56 完全数计算]
  • [I2C]I2C通信协议详解(二) --- I2C时序及规格指引