前言
如果 TesterHome 观看不方便(目录不方便),可以去:https://wenjie.store/archives/uiautomator2 看;TesterHome 有些格式问题可能还没来得及改
- 看完本篇博客你可能会了解以下事情:
- uiautomator2 原理基本介绍(常见的 uiautomator2 init 命令、默认的点击方案&坑)
- UI 自动化稳定性问题解决(伪解决),适用场景,不适用怎么办(对比百度、字节、面试公司捞到的一些信息)
- 或许可以帮你定位框架偶现的一些问题
PS:文中的对象 hash 如果出现上下文不一致的情况不要见怪,因为 usb 线有些不稳定,重新调试时对象 hash 就会发生变化
uiautomator2 运作链路
- 首先你需要知道构成 uiautomator2 整体运作的仓库其实总共有三个:
- 其中 android-uiautomator-server 虽然是一个仓库,但实际打出来的包是两个,从 github ci 的脚本中也可以看出来这一点,如下图所示:
-
- 两个 APK 作用各有不同,不过自动化控件 dump、默认点击操作这些都是在 test 后缀的 apk 中
- 整体运作链路如下图所示:
- 基本上整篇文章都是围绕这个调用链路进行调试 + 讲解,现在没看懂也不要紧,跟着代码走一遍就清楚了,接下来看看常用的一些接口都做了什么。
uiautomator2 init 做了什么
- 要测试这个功能,只需要给
__main__.py
加上init
的启动参数即可,如下图所示:
- 在进入
cmd_init
前,我们可以看下传进来的默认参数,如下图所示:
-
- 需要注意的其实只有默认传的
--addr 127.0.0.1:7912
,这个并非在 python 层使用的,而是后续传给 atx-agent 作为启动参数使用
- 接下来正式进入
cmd_init
函数了,可以看到一开始如果没有指定设备序列号的话,会自动遍历所有设备并初始化:
- 我们继续跟进
install
函数,核心逻辑如下图所示:
-
install
的逻辑其实有很多是重合的,下面只挑一些有差异的点来看
-
下载逻辑:
- 首先无论你在国内外,下载链接都会被暴力替换成镜像地址(我一开始还以为有啥别的判断,结果没有),代码如下图所示:
- cache_download 就是先判断缓存有没有已经下载的,有的话就判断文件信息是否正确,正确就直接拿来用,这里不再展开有兴趣的可以自己看看
- 后续的 minicap、atx-agent、2 个 apk 都是通过这样的方式下载
- 下载完后都会被 push 到
/data/local/tmp/
目录下,代码如下图所示:
-
apk 版本校验:
- 主要就是校验了版本号、签名,还有类似安装时间的警告,代码如下:
- apk 的信息则是通过
pm path
、dumpsys package
获取的,下面展示部分代码:
-
apk 安装:
- 因为包含了 debug 的包,所以会加
-t
的参数,如下图所示:
- 需要注意的是这里会卸载掉原来的 APP 的,而 minicap、minitouch、atx-agent 不会删除,个人猜测是因为 app 有的情况下无法覆盖安装,需要先卸载,而可执行文件没有这个问题。
-
atx-agent 启动:
- 这里只简单看下启动参数的含义(golang 代码中都有其含义),代码如下图所示:
-
server
:表示启动 atx-agent 内置的 server
-
--nouia
:带上此参数表示启动 atx-agent 时,不要把 uiautomator 也拉起来
-
-d
:表示后台运行
-
--addr
:指定监听的ip:port
-
端口映射:
- 上面的 atx-agent 虽然启动完成了,atx-agent 也能获取到手机的 ip,那么后续 python client 直接使用
ip:7912
请求就完了?实际上并没有,中间还进行了一次端口映射,python client 使用的其实是映射后的端口
- 说白了就是执行了一次
adb forward tcp:本地电脑随机端口 tcp:7912
,这个命令很好理解,比如{本地电脑随机端口}是 8080,那么你请求127.0.0.1:8080
就等于在请求手机ip:7912:
,代码实现如下:
-
uiautomator 启动方式:
- 如果你看过 APP 层的代码,你一定会很疑惑为什么会有一堆代码放在
androidTest
下,并且还引入了 JUnit 框架,如下图所示:
- 实际上当我们去看 atx-agent 启动逻辑就明白了,假设我们启动的时候去掉
--nouia
参数,就表示启动 atx-agent 时也启动 uiautomator 服务,此时 golang 代码中fNoUiautomator
的值为 false,如下图所示:
- 之后的逻辑中会添加一个启动 uiautomator 的任务代码,就是通过
am instrument
启动单元测试的命令行,如下图所示:
- 上面的代码只是添加了一个任务,实际上还没执行,到后面判断
!*fNoUiautomator
为 True 时才会执行,如下图所示:
- 此时如果你尝试用 kill 命令杀掉 uiautomator 进程,会杀不掉:
- 难道这是
am instrument
的神奇力量吗?并不是,实际上是因为 atx-agent 使用 goroutine 写了个死循环占有进程,要退出循环释放进程的话只能自己传入中断参数,最后还是使用 kill 命令杀掉进程的,这会在后面的【重置 uiautomator_v2 如何进行】处讲到。
- 如何 debug 在 Android 中的 golang 代码我之后补充
adb forward 的好处
这部分比较偏向猜想,觉得不对欢迎补充
- 先说一个东西,叫
内网穿透
,你可以试着访问这个页面(随缘在线):https://wenjie.store/chat/,如果成功的话说明你可以间接使用我 4090 的算力了
- 说白了我就是使用
内网穿透
使得你可以通过一个公网的服务器访问到我本地的物理主机,比如上面的链接,你实际上能访问到的是我在自己电脑部署的 ChatGLM2(这东西总不能是一台 1c1g 的电脑能跑起来的)
- OK,这时候问题来了,既然我可以通过
内网穿透
访问到电脑主机,那么手机是不是也可以?答案是肯定的
以下操作看不懂就 SKIP 吧,你只需要知道能通过外网 adb 连接手机即可
- 我们可以做一做实验,先来对 Android 手机进行内网穿透,大概就是下面这个样子
- 我的小米手机先插上一台 Ubuntu,使用 adb shell 启动 atx-agent,便于之后有东西可以访问
- 老牌手机 adb connect 的端口默认是 5555,但目前我的小米比较奇葩要手动打开无线模式才可用 adb connect 连接,且端口不为 5555+ 每次开关都会变:
-
- 小米的 wifi 连接只支持已配对的设备,没配对的设备是连不上去的,内网穿透也一样
- 不管如何,对这个端口做映射即可(服务器的端口也记得开):
- 手机启动内网穿透:
- 然后试着在另一台 win 电脑上使用 adb connect 连接,可以看到使用外网访问完全没问题:
- 牛逼的就要来了,我在 win 电脑上执行
adb forward
,具体命令如下图所示:
- 之后我可以通过 win 电脑浏览器输入
localhost:8888/info
就访问到手机上运行的 atx-agent 服务了,如下图所示:
- 到这里相信
adb forward
的好处已经体现出来了,假如你的设备是通过某种代理的手段(如内网穿透)开放出来的,那么 uiautomator 默认获取的网卡 IP 就只是内网 IP,如果你不在这内网之中而是通过代理手段访问的,那返回给你的内网的 IP 你是肯定无法访问的
- 而
adb forward
的强大之处就在于它不会出现获取错 IP 这种情况,并且我上面的操作中,云端无论是 8888 端口还是 7912 端口的防火墙都是开着的(生效着的),这还意味着adb forward
本身能通过长连接绕过一些规则
PS:你可能会说我都知道adb connect
的 ip 和 port 了,那我直接访问不就完了?如果你问出这个问题,那你可能还没完全理解上面的意思。在知道远程手机 ip:port 的情况下,如果直接使用ip:7912/info
访问,是必须要打开防火墙 7912 端口的,而我上面使用adb forward
根本就没打开。
- OK,到此为止
uiautomator2 init
指令的流程就基本解释清楚了,uiautomator2 stop
就不多说了,有个意料之外的地方在于它没有停下 atx-agent。
- 下面的篇幅基本就是看一些常见函数的调用链路了,上面一不小心费了点口水导致开头的流程图还没用上,下面应该就开始对上了。
click 流程
- 注意讲的这里是执行
uiautomator2 purge
后,再执行如下代码走的click
逻辑:
import uiautomator2 as u2
if __name__ == '__main__':
d = u2.connect_usb(serial="af80d1e4") # connect to device
d(text="首页").click(timeout=3)
关于 u2.connect_usb 就不过多讲解了,返回的 Devices 对象里面由多个父类接口组合而成,click 函数也是众多父类的实现之一
d(text="首页") 做了什么
-
d(text="首页")
其实只做了一些包装对象的工作,但如果你在这之前运行过 UI 自动化,你会发现此时有些参数怪怪的,即便你之后执行了uiautomator2 purge
把东西都卸载干净了,接下来就一步步去看
- 首先是
d
的初始化,实际上就是包装了一个 UIObject,而传进去的 Selector 其实也只是一层参数包装:
- UIObject 就是对 session、selector、jsonrpc 包装,咋看之下好像没啥问题,但当你查看
session.address
属性时,你会发现已经存在 ip 端口了:
- 而此时你在手机里试图寻找 atx-agent 的进程,会发现并不存在(如果存在可能是你访问了其它属性):
上面的 session 不要在断点时展开所有属性,否则你会发现展开得很慢,因为有些属性是通过请求 atx-agent 获取的,而发现 atx-agent 进程不在时,就会自动拉起,正常的启动逻辑不是这样的。而只获取 address 属性不会有这个问题。
- 在这里我直接先说结论,之所以 atx-agent 不存在就有 ip+port,是因为 uiautomator 的逻辑里面会直接复用之前转发到手机端 7912 的端口,后续 atx-agent 是固定死 7912 端口的所以不会有问题
- 而先前说的
uiautomator2 purge
只是卸载 APP+ 可执行文件,并没有删除端口转发,我们可以使用adb forward --list
查看已存在的端口映射,会发现正好等于上面获取到的 port:
- 我们可以持续跟进 address 属性的获取逻辑,看看是不是这样:
- 在前面
uiautomator2 init
的流程中是先启动 atx-agent server 再进行端口映射的,但实际上先进行端口映射也没关系,因为 atx-agent server 的端口固定 7912,只要保证 jsonrpc 请求前映射到就行。
click(timeout=3) 做了什么
- 在正式 debug 代码前,我先说明一些环境问题,比如你刚进入 click 的断点时,会发现控制台的对象一直在加载(前提是你前面的步骤没有误启动过 atx-agent,且之后执行
uiautomator purge
清理),像下面这样这样:
- 当加载完成后,会发现手机上 atx-agent 也启动了:
- 那有没有办法不让它成功启动 atx-agent 呢?有,我目前只想出一个愚钝的方法,那就是不停地删除,在 PC 端运行如下脚本:
while true
do
adb shell rm -rf /data/local/tmp/atx-agent
sleep 0.01
done
- 直接贴进 PC 命令行窗口,然后回车就行(如果还是成功启动就把 sleep 那行删掉),如下图所示:
- 这样即便 debug 模式中因为特有的属性访问而导致尝试拉起 atx-agent,也可以在下载后、启动前删除,不过记得在真正启动 atx-agent 之前终止停止命令(ctrl+c 即可)
- 我们先进入 click 中的第一个函数
must_wait
,这个函数默认就是在规定时间内看指定元素是否存在,代码如下:
- 我们继续跟进上面的
wait
函数,会发现里面其实是 jsonrpc 的调用:
- 但不要忘了,我们正常流程下 agx-agent 还没启动呢,所以继续要继续深入 jsonrpc 的逻辑,在调试的过程中有一段代码可能会让你产生误解,如下图所示:
- 继续看
_AgentRequestSession#request
的实现,终于发现初始化 atx-agent 的代码了:
- 到这里为止就可以停止先前执行的循环删除 atx-agent 的脚本了,至于
_prepare_atx_agent
的执行逻辑我想应该不用多说太多,最终还是会执行到前面uiautomator2 init
提到的setup_atx_agent
函数,所以启动参数啥的都是一样的,调用栈如下图所示:
- 之后就是真正的去请求了,只不过还是会请求失败,失败的原因我们可以看下 golang 的代码(是 debug 手机的 atx-agent,非本地的),如下图所示:
- 实际上这段 golang 代码就是将所有
/jsonrpc/0
的请求都转发到127.0.0.1:9008
,上面代码遮住了可能看不清,下面看下完整的:
- 转发失败后控制台也有打印:
- 到这里你可能就要问了,为啥固定转发 9008 端口呢,实际上这段逻辑在 APP 层,这里可以先贴出代码看看:
- 上面的接口因为尝试转发到 APP 上,但是因为 APP 进程还不存在,所以返回失败,进入如下逻辑,不难想到肯定有设置 uiautomator 的兜底逻辑:
-
reset_uiautomator
的核心逻辑如下
- 再次确认 atx-agent 请求返回:
- 因为 uiautomator 还没启动,所以铁定是不通的,之后确认 atx-agent 版本号,不对则重新调用
_prepare_atx_agent
(前面说过这个函数):
- 我们 atx-agent 没问题,所以直接过到下一步,进入
_force_reset_uiautomator_v2
开始重置 ui2 环境,这段逻辑比较长,下面单独拆分字标题说。
重置 uiautomator_v2 如何进行
- 进入到
_force_reset_uiautomator_v2
,头部逻辑如下:
- 到这里我先说明一个可能的新问题:你觉得上面的
self.shell(...)
是怎么调用的?你是不是觉得是 python 直接在 pc 端运行的命令?如果你这么想恭喜你答错了,实际上self.shell(...)
是把命令给到 atx-agent 去执行的
- 看看这里 shell 的转发代码。依旧是使用 jsonrpc,只不过这个 path 是 atx-agent 自己处理的:
- golang 侧 shell 的实现等启动 uiautomator 的时候再看,普通命令没太大区别,后续进入
self.uiautomator.stop()
,我们看看这个stop
干了啥:
- 我们再到 golang 中看一下,发现是在 golang 中是通过之前存储的字典取出保活进程:
- 我们再跟进
pkeeper.stop()
看看,发现核心就是传了个True
到p.stopC
:
- 前面没讲
pkeeper.start()
是怎么运作的,实际上它就是运行了一个死循环,当p.stopC
传入 True 时就会结束,然后释放进程;截取了部分关键代码如下图所示:
- 保活进程释放后,python 层会使用 kill -9 杀掉 uiautomator 进程:
- 接下来就是安装 uiautomator 的两个 apk 了,安装的逻辑前面也看过了,这里不再赘述,安装完成会打印两条日志:
- 剩下的
self.uiautomator.start()
跟之前的stop
十分有九分相似,python 层依旧是 jsonrpc 请求,只是变成了 post 方法:
- 至于 golang 端的实现,之前已经看过一次了,就是使用
am instrument
启动单元测试的方式,然后再加个保活锁:
- 到此为止,uiautomator 的进程就都起来了,我们可以用 ps 命令看看(有点乱):
-
reset_uiautomator
函数也到此结束了,后面虽然还有一些兜底逻辑,但大部分都是已经见过的函数实现,所以不再赘述。
判定控件是否存在
- 回到之前的
_jsonrpc_retry_call
处,reset_uiautomator
成功后会重新发起一次请求:
- 这一次就能正确打到 APP 的代码上了,而 APP 是使用
com.googlecode.jsonrpc4j.JsonRpcServer
实现了 jsonrpc 服务,并在AutomatorServiceImpl
中实现了具体实现,其中waitForExists
如下:
- 之后还会继续调用
androidx.test.uiautomator
包提供的能力,而uiautomator
提供的能力其实大部分来自AccessibilityService
:
- 到此为止,从 python client -> atx-agent server -> app 层都经历过了,其它实现基本都是这么流程,我就不再一一展开赘述了。
默认点击实现与坑
- 这里我就不再从 python 层一个个过了,直接看 APP 层的点击实现,无论你是 xpath、text 还是别的点击方式,最终大概率都会来到
com.github.uiautomator.stub.AutomatorServiceImpl#click(int, int, long)
,按下和松开中间有个间隔的就是长按函数了:
- 跟进
touchUp
,因为最终的返回值是它决定的,原理大同小异:
-
injectEventSync
继续深入的话需要下载源码,这里就不再深入了,你只需要知道这里使用的是一个同步的注入方法,如果注入失败就会返回 fasle:
- 那么,有哪些坑呢?
- 第一坑:
- 事件注入可能会和其它应用有冲突,比如我曾尝试和 github 上的 Fastbot_Android 放在一起运行(原因是经常会误触一些车控开关,想用自动化识别错误时返回)
- 但结果是,每次运行一段时间后,两者之一就会报错并且停止
- 第二坑:
- 就是上面
injectEventSync
的返回值,在某款不知道什么游戏引擎构建的应用上使用自动化点击时,我脚本明明只点了一下,但 APP 上总是点两下。
- 后来发现是在这个 APP 上
injectEventSync
都返回 false,而内部框架额外处理了这个injectEventSync
的返回值,如果返回 false 就额外点一下,气死个人。
- 解决方法?如果是点击的话自己写 adb,如果想效率更快些就考虑 minitouch 这种(明日方舟的 MAA 挂机使用 minitouch 给我感觉就快了很多)
- 到此为止,点击的处理流程也讲完了,本来想再讲讲 dump 控件树的接口,但想想好像都大同小异就算了,接下来基本不用再看代码了,来聊些稍微有趣的话题。
UI 自动化稳定性/收益问题
稳定性问题
- 先说一个可能、应该、大概普遍的结论:如果 UI 自动化落地一段时间,且尝试过各种手段优化,但稳定性提升还是不明显,那大概率是没救的。
- UI 自动化不稳定/维护难的原因通常如下:
-
uiautomator2 自身稳定性问题,但通过外部测试框架调度封装,增加一些兜底逻辑还是比较容易的避免的(内部自研的也一样有类似的问题)
-
网络问题,比如 21 年字节的机房自动化还是会出现白屏,广州百度早起极烂的网络经常导致入库失败等
-
业务变更频繁,字节的业务尤为明显,以至于某些团队会放弃 UI 自动化的维护;最近面试某些大公司的时候也是因为这个原因放弃
-
业务链路太长,比如滴滴用户端和司机端,美团用户端&骑手端&商家端,涉及多端联动 + 链路过长大大提高失败率
-
线上 ab 实验/UI 适配/降级等变更策略过多,估计是大厂才会出现的通病,分 uid/did/设备型号输出页面/特效,UI 自动化难以持续维护
-
非原生控件只能用 CV,比如百度地图,底层是用 OpenGL ES 绘制的,开源的方案目前来看都没啥办法,引擎层的代码保密级别又高,基本就只能用 CV 了
- 虽然后续搞出了很多 “智能” UI 自动化方案,但在职期间看落地效果似乎都不咋地。
收益问题
- UI 自动化打从我开始接触的那一刻起,就一直被 diss 收益的问题
- 特别是在字节期间,字节的自动化一般都是用机房的集群回归的,上面说到的稳定性问题除了 CV 这一项外,基本都是天天出现
- 于是测试报告就各种误报,误报还要排查,排查之后还要兼容,代码变更频率特别频繁
- 因为投入的人力与产出不太能成正比,原本一些还在疯狂投入人力的业务也开始慢慢不投了,或者缩减维护范围
如何规避问题
- 如上面的结论所说,UI 自动化的问题通常是无法解决的(至少短期内)
- 那么思路就应该转变成如何利用 UI 自动化做出收益,并且规避它的短板,比如:长期维护乏力,线上变更多,收益不明确
- 实际上我之前所在的团队早就意识到该问题,只是解决的思路可能只适用在类似字节这样的大厂,下面我就来说一下。
- 结论:团队转型日常性能评测专项(偏基础体验)+ 活动业务 BP 专项
- 你看着描述可能还有点懵,我来解释下具体逻辑:
- 性能评测:通常是比对公司业务 APP 与竞品的差异,单场景性能的 case 通常不多,且通用性较强,维护成本比起业务 UI 自动化低非常多;之后根据人力接入各个业务,定时输出对比报告就是稳妥的产出。
- 活动业务 BP:字节内部各大 APP 都有自己的活动,再加上类似中秋节、国庆节、春节这种节日活动,不愁没活;同时活动内容一般都偏向使用新技术 + 写新代码,这意味着出现功能、性能、体验的 BUG 概率会更高;并且,活动自动化的代码写完大概率就可以扔了,基本不需要考虑后续维护,后续也是持续输出报告就可以规避 UI 自动化原本的缺点。
- 简而言之,UI 自动化不再像之前一样是投入产出不明的累赘,而是成为了专项环节中的一个小小的脚本工具,不是过程指标也不是结果指标,单纯就是一个辅助工具。
其它补充
如何远程调试 Android 上的 Golang 代码
- 核心参考:https://github.com/golang/vscode-go/blob/master/docs/debugging.md
- 不过光有参考资料还不太够,因为大部分是 PC 环境下的,Android 环境还要小小处理下
- 先说一些踩过的坑:
- golang arm64 架构的包是无法再 Android 上运行的,使用
ldd
查看可执行文件会发现少了一些 linux 的 so,目测属于硬伤救不了
- 上面 debug 文档中,大部分都是使用 dlv(delve)开启 debug 的,但 dlv 有些命令是依赖 go 相关的指令的,基于上一条 Android 中无法使用 go,有些 dlv 方法是不可用的,比如
dlv debug
就是
- dlv 的 github 仓库:https://github.com/go-delve/delve 没有提供 Android 可运行的 dlv 可执行文件
- 最终我自己的解决方式还是使用 dlv,对应上面参考文档中的如下部分:
- 首先是要自己打一个 Android 上可以运行的 dlv,这样才能开启 debug server,主流手机一般都支持 armv7、armv8,armv8 一般就对应 arm64,所以 build 的时候设置
GOARCH=arm64 GOOS=linux
即可
- 然后就是 dlv 仓库的版本选择问题,我本地的 golang 是 1.18.10,下载最新的 dlv 时,项目是 1.19.x 的,可以打包成功并运行,但本地 VSCode 开始远程调试时就会报出版本对不上的问题,后更换低版本 dlv(golang 1.18.3)远程调试成功,目测是不向上兼容,向下大版本能兼容
- 之后参考 dlv 的 github ci 脚本,得出完整构建命令如下:
GOOS=linux GOARCH=arm64 go build -ldflags "-extldflags -static" -ldflags= github.com/go-delve/delve/cmd/dlv
- 构建完后直接推手机上就可以,我这里推的跟 uiautomator2 是同一个目录:
- 之后我们在手机对应目录上就可以执行文档中的命令了(
dlv
和./dlv
是有区别的):
- 之后按照先前文档,配置 vscode 的
launch.json
文件如下:
- 现在还不能按 F5 启动,上面配置的 program 指的是 debug 包的路径,我们 atx-agent 的 debug 包还没打,打包命令如下(顺手推上去):
GOOS=linux GOARCH=arm64 go build -gcflags="all = -N -l"
- 之后我们就可以在 atx-agent 的工程按下 F5 启动调试了(有个警告不用管),确认 vscode 进入 debug 状态:
- 确认手机端的 atx-agent server 也被启动了:
- 确认断点是红色的,不是红色说明没生效:
- 最后就是确认能命中断点和看到参数了,可以访问
http://手机ip:7912/info
试试看,debug 生效的话上面就会停在上面的断点:
CV 真的不靠谱吗
- 老实说,这要看你所在厂的 CV 积累如何,比如我面快手的时候,对于一些自研的渲染引擎,快手的技术中台基本就不考虑 CV 方案,更倾向于一些深度学习的方案,比如点掉一些突然出现的浮动窗口
- 但在字节就不是这样,字节因为有比较强大的 CV Lab,所以 CV 是可以解决绝大部分问题的,比如 Android 耗时自动化如何判断起始帧,就是打开开发者设置的指针位置,然后用 CV 去识别屏幕左上角那个用肉眼都不一定看得清的
X/X
- 在百度车机业务,开源方案的表现也还行,因为一个车厂下不同车型通常分辨率都是一致的,如果你是负责一个车厂下的不同车型,那么复用起来基本没有太大问题
遇到过适合 UI 自动化落地的项目吗
- 目前就是百度车机业务比较合适纯 UI 自动化做收益,原因如下:
- 多个项目虽然会出现 text 文案不一致的情况,但是 RD 基本保持 resource-id 是一致的
- 业务层面的改动不多,短的项目可能 1 年就交付了,长的 2 年 +,但以大部分车厂的佛性文化来说,需求确认后变更点就不多了
- 车机系统便利,没有市场上各种自己都不记得密码的密码锁、权限拦截等,进一步保证运行的稳定性
- 车机可自由 root,这点确实就比较牛逼,通常情况下自动化运行 crash 了,要想抓到要么靠 logcat,要么靠 adb bugreport;而前者不一定出现对应日志,后者又导出齐慢;而有了系统权限后就可以直接去系统目录取 anr、crash、coredump 了,且速度极快,这直接给 UI 自动化附加了一层深度更深的稳定性测试,实际落地中也确实抓到不少跑 Monkey 没出现的问题。
有对 uiautomator2 扩展来满足需求吗
- 有,就是百度的车机地图,车机地图有一个特殊的业务场景,即:多屏地图,比如主控副屏、HUD 投影等等
- uiautomator2、weditor 等工具在遇到这些场景时,默认只会显示主屏的控件,调试起来非常不方便,于是就稍微改造了一下
- 在说具体改造之前,我先说一下多屏地图的主要方案,如下图所示:
- Android 原生的 Presentation 基本不会使用,其余的可抽象成两种方案:
- 魔改 Presentation:所有屏幕属于一台 Android,看到的内容都是真实控件,且屏幕是可控的
- 推流:适用于 C/S 架构,即屏幕和主控 Android 不是同一台机器,屏幕是不可控的
- 对于推流的方式,只能从流中截取图片做 CV、OCR 断言
- 对于真实存在控件且可控的魔改 Presentation 方案改造,我这里就不从头到尾扯一遍了,就只提一些线索:
- 魔改 Presentation 是基于 Andorid Context -> window service 管理窗口的
- 每个窗口都有对应的 display id
- adb 的 screencap 命令官方文档没有说全,当我们
screencap --help
后,会发现有如下内容:
-
- 没错,
-d
参数就是可以指定 display id 截图
- scrcpy 可以根据 display id 来选择远控的屏幕,实测多屏地图确实可以,官方文档中描述如下:
- uiautomator2 的一些实现也是也有用到 window manager:
- 剩下的就是如何拼装了
最后
- 上面提到在百度、字节工作过,看起来好 diao,但实际上我目前只工作过两年,字节一年,百度一年;并且最近因为业务人员裁撤,处于失业的状态
- 不过闲着也是闲着,并且发现身为一个测试开发都没怎么写过相关的内容,所以就趁热写一篇吧
↙↙↙阅读原文可查看相关链接,并与作者交流