UiAutomator UiAutomator 与 Shell 结合的力量

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

自学 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 条回复 时间 点赞

adb shell input

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

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

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

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

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

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

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

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

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

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

bauul #10 · 2015年06月22日 Author

#9 楼 @monkey
这个可以有

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

这个很棒=,=!

这个如果想做的更强大需要考虑设备兼容,不同设备的 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 #20 · 2015年06月24日 Author

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

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

bauul #22 · 2015年06月24日 Author

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

我自己也已经使用 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 "特征字符"
代码实现细节就不写那么多了,思路就是如此。

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

bauul #25 · 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 个小时,没问题,再跑下去我觉得也没什么大问题。

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

29楼 已删除

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

bauul #31 · 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 #39 · 2015年11月17日 Author

#38 楼 @lookatcn

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

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

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

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

匿名 #44 · 2016年03月21日

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

bauul #45 · 2016年03月21日 Author

#43 楼 @actionwind
不需要

bauul #46 · 2016年03月21日 Author

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

47楼 已删除

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

bauul #49 · 2016年10月17日 Author

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

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

bauul #51 · 2016年12月27日 Author

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

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

bauul #53 · 2016年12月30日 Author

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

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

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

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

bauul 基于 uiautomator 与 shell 的自动化测试工具 中提及了此贴 05月02日 10:12
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册