UiAutomator UiAutomator 与 Shell 结合的力量

bauul · June 22, 2015 · Last by bauul replied at February 22, 2017 · 6516 hits
本帖已被设为精华帖!

自学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命令行,REMDOS命令行注释,请知悉
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本身做了很多的区别,比如textdescrptionresourceId等等.
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
#screencapandroid自带的可以抓图的命令,这里加上了时间而已
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呢?颗粒度的细节程度大概是什么样子呢?这些是否都可以分享分享?

bauul #4 · June 22, 2015 作者

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

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

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

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

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

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

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

bauul #10 · June 22, 2015 作者

#9楼 @monkey
这个可以有

#10楼 @carl 已加精

#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 · June 23, 2015 作者

#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 · June 24, 2015 作者

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

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

bauul #22 · June 24, 2015 作者

#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 · June 25, 2015 作者

#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 #31 · July 10, 2015 作者

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

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

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

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

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

bauul #36 · November 10, 2015 作者

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

bauul #37 · November 11, 2015 作者

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

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

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

#38楼 @lookatcn

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

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

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

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

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

bauul #45 · March 21, 2016 作者

#43楼 @actionwind
不需要

bauul #46 · March 21, 2016 作者

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

47Floor has been deleted

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

bauul #49 · October 17, 2016 作者

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

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

bauul #51 · December 27, 2016 作者

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

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

bauul #53 · December 30, 2016 作者

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

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

bauul #55 · February 22, 2017 作者
PhoenixBaymax 回复

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

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