UiAutomator UiAutomator 与 Shell 结合的力量

bauul · 2015年06月22日 · 最后由 bauul 回复于 2017年02月22日 · 3818 次阅读
本帖已被设为精华帖!

自学 UiAutomator 并使用已快一年了,也独自完成了项目中的 800 条自动化测试用例的编写,其中坑坑洼洼亦不知多少,更不消说 UiAutomator 本身就有 Bug 了.现如今也来说说一点小想法.这里我主要想讲一下,最近在编写自动化测试 PC 端工具时,所想到的一些点子.

我的想法就是通过 UiAutomator 本身的 dump 命令,来抓取手机当前页面的 xml 档,这个 xml 档里面有存储手机当前页面的属性及坐标等.接着通过 shell 脚本命令来解析或过滤出我们想要点击的属性的坐标值来,最后通过 sendevent 的方式来实现:点击/长按/滑动 (Swipe)/双击/拖拽 (Drag)/多点点击等操作.

阅读前提:

  1. 读者需要掌握 uiautomator 的基本用法
  2. 读者需要掌握 shell 脚本的基本用法,以及 grep 与 sed 的常用方法

首先,我们需要借助 UiAutomator 抓取手机当前页面的配置信息,我们在 DOS 命令行中,执行 adb shell uiautomator dump 时,会自动生成一份文件,并存储在手机的 SD 卡根目录中,名为:window_dump.xml.读者可以将它复制到电脑上来查看,其实这个文件和我们在电脑上借助 Android SDK 中,uiautomatorviewer.bat 来察看手机当前页面的属性等值的方法是一样的.

REM 这里是DOS命令行,REM为DOS命令行注释,请知悉
REM 这里使用的是小米手机,Android Version: 4.4.4
C:\Users\Administrator>adb shell uiautomator dump
UI hierchary dumped to: /storage/emulated/legacy/window_dump.xml

那抓出来的档案是可以在电脑上来查看的,形如:

<?xml version="1.0" encoding="UTF-8" standalone="true"?>
<hierarchy rotation="0">
<node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="com.miui.notes" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,0][720,1280]">
<!--这里提注释,内容太多,抱歉,不便全列,省略若干字哈 -->
</hierarchy>

接着,这里我们介绍如何借助强大的 Shell 脚本,来抓取/过滤出我们想要的东东,

getCoordinateByAttribution()
{

#这里我们定义了一个instance,它的灵感是来自UiAutomator中的同名操作.意思是在当前页面下,有n个一模一样的属性,我们不好区分时,使用instance来指出我们需要点击的是第一个还是第n个属性.默认点击第一个.
instance=""
instance=${instance:=$2}
instance=${instance:=1}

uiautomator dump
#这里借助了busybox工具,至于什么是busybox工具以及如何安装,此处暂不讲,读者可以先行百度,若有困难,再说.
#这里借助grep命令,来过滤出我们需要点击的属性,个人认为此方法比UiAutomator这个工具本身要方便一些.UiAutomator本身做了很多的区别,比如text,descrption,resourceId等等.
temp=`cat /mnt/sdcard/window_dump.xml|busybox sed 's/>/\n/g'|busybox grep "$1"|busybox sed -n {$2}p`
temp=`echo ${temp%]\"*}`
temp=`echo $temp|busybox awk '{print $NF}'`

#此处我们作一个判断,如果temp的值不等空串的话,我们认为找到了我们需要查找的属性,并作进一步的处理
if busybox test ! "$temp" == ""
then
temp=`echo ${temp/bounds=/}`
temp=`echo $temp| busybox sed 's/"//g'| busybox sed 's/\[//g'| busybox sed 's/\]/\n/g'`
p1=`echo $temp|busybox awk '{print $1}'`
p2=`echo $temp|busybox awk '{print $2}'`
#定义四个变量,用例存储找到的属性的四个坐标值
p1x=`echo ${p1%,*}`
p1y=`echo ${p1#*,}`
p2x=`echo ${p2%,*}`
p2y=`echo ${p2#*,}`

let centerX=$p1x/2+$p2x/2
let centerY=$p1y/2+$p2y/2
else
#这里是查找属性失败时的动作
echo `date +%m-%d-%H-%M-%S` getCoordinateByAttribution $1 failed >> /mnt/sdcard/log.txt
#screencap是android自带的可以抓图的命令,这里加上了时间而已
screencap -p /mnt/sdcard/"`date +%m-%d-%H-%M-%S`".png
fi
}

这个函数如何调用呢?容我举例说明,我们可在 shell 脚本中执行以下命令来调用它.

#这是一个查找点击text属性的例子,这里加上双引号是因为,即使字串中有空格时,也不会被当成两个参数来处理
getCoordinateByAttribution "text" 1
#这是一个查找点击description属性的例子
getCoordinateByAttribution "description" 1
#这是一个查找点击resourceId属性的例子
getCoordinateByAttribution "resourceId" 1
#如果有三个一模一样的resourceId属性,而我们需要点击第三个属性的话
getCoordinateByAttribution "resourceId" 3

OK,既然已查找到属性,如何点击它呢?这里我们借用 android 自带的 sendevent 方法,当然你也可以使用 input 命令来点击,因为坐标值我们已经获取到了.
这里还是用小米 2 移动定制手机为例,

#这里定义了点击的shell函数
Tap()
{
sendevent /dev/input/event0 3 57 1
sendevent /dev/input/event0 3 53 $1
sendevent /dev/input/event0 3 54 $2
sendevent /dev/input/event0 1 330 1
sendevent /dev/input/event0 0 0 0
sendevent /dev/input/event0 3 53 $1
sendevent /dev/input/event0 3 54 $2
sendevent /dev/input/event0 0 0 0
}
#结合前面所述,我们只需点击对应坐标便可
Tap $centerX $centerY

以上便是小可今天花了一个小时,写出来的一点东东了,可能有些地方不恰当,敬请批评指正,感谢!
本来还有一些感言什么的,感觉写出来,也是无甚趣味,呵呵,不写也罢.第一次发贴,诚惶诚恐.

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 54 条回复 时间 点赞

我自己也已经使用 shell 分析 uiautomator dump 写脚本 1 年多了,几点经验:
1、uiautomator dump 在不同权限下有读写限制,需要保证可以正常写入文件,写文件的位置可以自行调整。
如:uiautomator dump /sdcard/check.xml
2、安卓 5.0 有 dump 出的文本中文乱码问题,需要替换 uiautomator.jar 或更新 uiautomator 相关代码
3、解析 xml,建议使用<node 前加回车换行的方式,一目了然,grep 特征字符锚定行之后 awk 按” 间隔选取很容易判断
注意 echo 变量需要加引号否则会没有换行符导致 grep 特征字符锚定行无效
uiautomator dump /sdcard/check.xml && busybox sed -i 's/<node/\n<node/g' /sdcard/check.xml
4、uiautomator dump 有失败的问题如动态界面抓取,需要容错此问题,每次执行前删除/sdcard/check.xml,判定文件正常生成后继续
5、由于 uiautomator dump 效率问题,在非精确判定布局和文本的情况下,灵活使用 dumpsys SurfaceFlinger,dumpsys window 判定显示状态。
以下命令配合 awk 判断显示界面情况
dumpsys SurfaceFlinger|grep "|....|"
以下命令配合 grep 判断是否存在
dumpsys window p| grep -c "特征字符"
代码实现细节就不写那么多了,思路就是如此。

#40 楼 @lookatcn
首先需要明确这条用例的测试目的,
如果是验证功能,那么可以加一个循环确认,检测新界面是否出现,未出现则等待,出现则继,
如果是验证性能,可以多执行几次,抓出时间数据,去平均,
如果是验证稳定性,可以在每条用例结束或开始时,判断是否存在 force close 或 anr 的对话框,有则处理掉。

#6 楼 @monkey
你说的对,我对纯理论的东西接受起来有点排斥,还是行动起来,比较容易知道个中滋味,哈哈.

4楼 已删除

#4 楼 @carl 好,谢谢。
我和 Monkey 一样,没有机会大量实践 UIAutomator ,所以对你这一年的实践经历这块比较感兴趣。毕竟弄个小工具什么的不难,难的是让一个项目真正把这个工具用起来,并且让它发挥价值。

#3 楼 @monkey
我们是做安卓整机测试的,那所以的话,在不同的阶段,需要对产品执行本阶段必须执行的自动化测试用例.在新项目来时,我们除了需要修改这部分已有的用例,仍然需要针对新项目中,新的功能或模块,编写新的自动化测试用例.那这里的自动化测试用例,是我们从系统测试中,捞出可以自动化的部分,一条线是压力,一条线是系统稳定性.Case 从最简单的打开退出应用,到让重复手机执行恢复出厂等等.坦白说,你讲的这些专业名词,我不是太了解.不知道是不是有回答你的问题.

#5 楼 @carl 哈哈,基本上都回答了。我主要是想了解用的一些场景以及针对能够解决的问题。毕竟了解一个东西还是需要实践来考证的。所以就比较感兴趣你做的这个实践~~

#7 楼 @carl 毕竟每个人的环境不同。我对于自己没有机会去实践的东西很有兴趣去了解了解别人的经验~~~感谢分享

#7 楼 @carl 对了。。。添加个头像吧。。。

bauul #10 · 2015年06月22日 Author

#9 楼 @monkey
这个可以有

这个很棒=,=!

这个如果想做的更强大需要考虑设备兼容,不同设备的 event number 不一样,这个是我做录制工具时遇到的坑。。。。其实你可以摒弃 sendevent,结合 ddmlib、chimpchat 去做事件处理,这样比较稳定且兼容

#14 楼 @vigossjjj event number 你说的是 adb shell input keyevent 的 number 吗?这个 google 上面有都是一样的啊

#15 楼 @james88233 这个 number 可以定制的

#14 楼 @vigossjjj 你说的应该是 dev/input 下的 event number 吧,确实不同的设备 eventXX 分管的事件不一样 我之前也用这种方式做过录制回放工具
其实就是对这些设备写入和读取数据的过程
回放的时候有种比较暴力的方法....往 dev/input 这下面所有的设备的都写入一遍

bauul #18 · 2015年06月23日 Author

#14 楼 @vigossjjj
不同的设备当然是不一样的 event 类型,这对我来说小菜一碟,不是事儿.至于 ddmlib 我知道,但要放到 shell 脚本中去执行的话,可能还需要配合其他工具使用.我这写好之后,就是一纯 shell 脚本,执行速度是很快的,比 uiautomator 要快许多.

#17 楼 @fresh @carl send event 对于 android 来说他做的是多次 IO,即读一行写一行,这样回放起来会卡顿 (特别是滑动 action),原因就在于非一次性读取解析,回放体验和准确性不是很好。对于兼容来说没那么简单,其实如果你只是针对自家 ROM 做测试那也没必要考虑咯。ddmlib 其实是和 device 建立 adb 连接桥,通过 device 对象操控设备,和你说的 shell 那种关系不大,执行速度快慢和 dump window 的解析耗时关系较大。

bauul #19 · 2015年06月24日 Author

#19 楼 @vigossjjj
读一行写一行?你搞错了,我说的 shell 执行是用 shell 脚本,而不是用 dos 命令行往下发,shell 是手机的 linux 内核,执行速度比 dos 往下发快得多,它已经脱离了 adb 了。
不同的设备我们写出不同的配置文件出来即可

#20 楼 @carl 哦,sorry,我可能没说清楚,我是指 sendevent 的底层实现机制,你可以去先了解一下。

bauul #21 · 2015年06月24日 Author

#21 楼 @vigossjjj
sendevent 的实现机制我确实不了解,望能指点一二。感谢!
另外,解析 uiautomator dump 出来的 xml 档,用 shell 脚本解析会很快,本文即是使用 shell 来解析的。uiautomator 本身是通过 java 去解析的,做了很多种方式,我感觉有点复杂了。这是我喜欢 shell 的原因。直来直去,哈哈哈哈

使用 shell 写脚本就是要很清楚信息来源 proc 伪文件系统下的可用信息,dumpsys 可获取到的状态,busybox 可已被利用的命令,uiautomator dump 获取 ui 布局,uiautomator events 获取 ui 事件 log 等;文本操作处理灵活使用 awk,grep,sed;明晰权限对执行命令的影响;控制操作的方式:input,sendevent,am,svc 控制网络开关等。剩下的就是如何组织逻辑,容错异常,设定输入格式将结果信息格式化输出等整体设计的事项。

bauul #23 · 2015年06月25日 Author

#23 楼 @sandman

  1. 这个我知道,我只是用了默认的路径
  2. 我在设计时,主要选择的是英文,因为我们做的是整机测试,所以事先将语言调成了英文
  3. 我的方法也能凑效,至于双引号,我在执行时是有加的,且更新了本文中调用的说明
  4. 这点我只是知道,却没在脚本中加判断
  5. dumpsys SurfaceFlinger 这条命令不知道你使用的目的是什么呢?dumpsys window 我也用这个,主要来判断当前界面是否有 crash 或 anr 发生,做一些错误处理。 以上, 感谢你的回答,谢谢!

5、dumpsys SurfaceFlinger 可以直接判断显示内容的情况,你使用 dumpsys SurfaceFlinger|grep "|....|"取几次数据就能清楚,这部分数据是有用来做判定的价值的

ddmlib、chimpchat 还是比较稳定的,我曾经测过,不断的发送事件,隔 3 秒发一次,跑了 20 个小时,没问题,再跑下去我觉得也没什么大问题。

刚开始学习自动化,第一个学习的自动化框架,目前可以编写一些简单的操作,学习中,感谢楼主的分享。

作为一个刚使用 UiAutomator 的新手,很不能理解为什么还要用这种方式来实现,UiAutomator 自带的对象和方法不是能很方便的来满足需求吗?而且用属性的关键字来识别不是比用坐标更好吗?这么做的意义是什么?

bauul #28 · 2015年07月10日 Author

#30 楼 @wtucel
可以提升执行的效率,属性到最后还是点的坐标,你明白不?用 shell 来分析属性,比 java 本身快。

你好问下怎样能将 automator 执行的 log 生成报告

uiautomator 要先 dump 到手机,再 pull 到本地再解析,印象中是很慢的。我现在都用 AndroidViewClient 来获取 xml 元素,解析坐标等属性,要比 uiautomator 快很多。

#2 楼 @chenhengjie123 我最开始也是用的 dump 然后解析的方法,这样确实是比较慢的。比较快的方法是直接写成 jar 包,这样速度和稳定性都会好很多的

新手,请问这个脚本是运行在机子上嘛?先 push 进手机,然后在 adb 中运行。然后就是要寻找中文怎么办?

bauul #36 · 2015年11月10日 Author

#35 楼 @rdmclin2 如果你用 uiautomator 的话,它本身可以处理中文。shell 脚本的话我目前没搞定,编码上有些问题。

bauul #37 · 2015年11月11日 Author

#35 楼 @rdmclin2 对了,在 window 上将文件保存成 utf-8 就可以点击中文了

#5 楼 @carl 很强!谢谢分享!

  1. 找到元素后点击,结果的验证是怎么考虑的呢? 2.每次点击元素,跳转 到新的页面后,要重新使用 uiautomator dump 生成新的 xml,然后再解析,寻找要找的元素,对吗?
bauul #36 · 2015年11月17日 Author

#38 楼 @lookatcn

  1. 举例来说,从主界面点击 Apps(Android 手机主界面上的 6 个小块的图标),会进入应用列表界面,下一步操作是点击 Messaging,如果没有进到这个界面,点击 Messaging 的操作会失败,这就验证了前面一个操作了。

#39 楼 @carl
谢谢回复!
1.目前我也是这个思路,在跳转的新界面中寻找期望的元素,没找到就认为有错,大部分功能验证都可以这样做到。但是有时候会碰到这样的情况,点击后的操作非常费时,比如和网络相关,会有一个弹出的进度条对话框,如果不等到新界面出来,就快速点击旧界面上其它按钮,导致另一个进度条对话框出现,如此快速反复,希望能抓住 ANR 或者 force close 出现。感觉要增加一个监听 ANR 或者 force close 的处理进程,不知这方面你有什么建议呢?

请问一下,shell 脚本的执行需不需要 root 权限?

匿名 #44 · 2016年03月21日

请问一下怎么能找到 UiautomatorViewer 的源代码呢?

bauul #41 · 2016年03月21日 Author

#43 楼 @actionwind
不需要

bauul #42 · 2016年03月21日 Author

#44 楼 @black_blue
在 google 的源码链接,你 google 下可以找到

想请教下,这个可不可在在安卓代码里 dump 出界面信息,在安卓代码里执行 shell,多谢~

bauul #44 · 2016年10月17日 Author

#48 楼 @huihui
除非你有系统权限,否则不可以

已经明白了楼主的基本思路,有一个问题就是这种思路针对的 app 是否会受限制,即完成的测试目的是不是受限,只能完成一些简单的操作?

bauul #51 · 2016年12月27日 Author

#50 楼 @chinawifi
受限于工具 (uiautomator 或 uiautomator 2.0) 本身吧,都是 UI 层面的测试,不过也要看你的测试目的是什么了?

bauul #53 · 2016年12月30日 Author

#52 楼 @chinawifi
没问题啊,你找的方式不对吧

#51 楼 @carl uiautomator 还有 uiautomator2.0 对自定义控件的支持不是很好吧,By.text 找到的元素不对呀

还有一个问题,uiautomatorviewer 分析 UI 控件的基本思路是 dump,然后解析.xml 文件,这种方式是否存在效率的一个问题?

bauul #50 · 2017年02月22日 Author
PhoenixBaymax 回复

效率多少会损失一点的,不过 UI2.0 提供流的方式,也提供文件的方式,如果使用流的话,可以提高一点效率吧

bauul 基于 uiautomator 与 shell 的自动化测试工具 中提及了此贴 05月02日 10:12
52楼 已删除

我也有问题,800 个 case 的实际使用频率是多少?有效率是多少?另外 lz 是测试 OS 的么?都是什么样子的 case 呢?颗粒度的细节程度大概是什么样子呢?这些是否都可以分享分享?

adb shell input

赞!思路很好,排版也不错!
感言可以写出来啊,做了一年,写了 800 多个用例,遇到各种坑,肯定感言不少吧。

有个问题想请教下,你们 800 多个用例执行速度怎样?UIAutomator 速度比较慢(主要是 dump 元素和 输入文字 比较慢),实践中有没有什么好办法提速?

bauul #56 · 2015年06月22日 Author

#2 楼 @chenhengjie123
哈哈哈,确实有蛮多坑的,感言这东西,写得好叫情怀了,写的不好的就叫吐槽了.

  1. 比较慢是真的,我这边也没有更好的办法,除非从源代码着手,能改进一点儿速度.另外,我们虽然有 800 条用例,但不是全部一起拉起来跑的,按不同的测试目的或模块,写成了 10 多个 shell 脚本,来执行的.一个 shell 脚本里面最多有 100 条用例左右.我们是做整机测试的.
  2. 输入文字,我在测试时,主要是借助 input text 方法来输入英文,中文几乎不在自动化测试中使用.
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册