UiAutomator UiAutomator2 原理介绍 + 源码走读

slink · 2023年09月05日 · 最后由 哲豪 回复于 2024年07月22日 · 60675 次阅读
本帖已被设为精华帖!

前言

如果 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运作链路
  • 基本上整篇文章都是围绕这个调用链路进行调试 + 讲解,现在没看懂也不要紧,跟着代码走一遍就清楚了,接下来看看常用的一些接口都做了什么。

uiautomator2 init 做了什么

  • 要测试这个功能,只需要给__main__.py加上init的启动参数即可,如下图所示:
  • ide启动参数
  • 在进入cmd_init前,我们可以看下传进来的默认参数,如下图所示:
  • cmd_init默认传参
    • 需要注意的其实只有默认传的--addr 127.0.0.1:7912,这个并非在 python 层使用的,而是后续传给 atx-agent 作为启动参数使用
  • 接下来正式进入cmd_init函数了,可以看到一开始如果没有指定设备序列号的话,会自动遍历所有设备并初始化:
  • init不带参数是初始化所有设备
  • 我们继续跟进install函数,核心逻辑如下图所示:
  • install核心逻辑
  • install的逻辑其实有很多是重合的,下面只挑一些有差异的点来看
    • 下载逻辑
      • 首先无论你在国内外,下载链接都会被暴力替换成镜像地址(我一开始还以为有啥别的判断,结果没有),代码如下图所示:
      • 下载地址替换成国内镜像
      • cache_download 就是先判断缓存有没有已经下载的,有的话就判断文件信息是否正确,正确就直接拿来用,这里不再展开有兴趣的可以自己看看
      • 后续的 minicap、atx-agent、2 个 apk 都是通过这样的方式下载
      • 下载完后都会被 push 到/data/local/tmp/目录下,代码如下图所示:
      • push目录
    • apk 版本校验
      • 主要就是校验了版本号、签名,还有类似安装时间的警告,代码如下:
      • apk校验逻辑
      • apk 的信息则是通过pm pathdumpsys package获取的,下面展示部分代码:
      • apk信息获取
    • apk 安装
      • 因为包含了 debug 的包,所以会加-t的参数,如下图所示:
      • debug包需要加-t参数
      • 需要注意的是这里会卸载掉原来的 APP 的,而 minicap、minitouch、atx-agent 不会删除,个人猜测是因为 app 有的情况下无法覆盖安装,需要先卸载,而可执行文件没有这个问题。
    • atx-agent 启动
      • 这里只简单看下启动参数的含义(golang 代码中都有其含义),代码如下图所示:
      • atx-agent启动逻辑
      • 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 框架,如下图所示:
      • 引入Junit
      • 实际上当我们去看 atx-agent 启动逻辑就明白了,假设我们启动的时候去掉--nouia参数,就表示启动 atx-agent 时也启动 uiautomator 服务,此时 golang 代码中fNoUiautomator的值为 false,如下图所示:
      • fNoUiautomator为false
      • 之后的逻辑中会添加一个启动 uiautomator 的任务代码,就是通过am instrument启动单元测试的命令行,如下图所示:
      • am instrument命令行
      • 上面的代码只是添加了一个任务,实际上还没执行,到后面判断!*fNoUiautomator为 True 时才会执行,如下图所示:
      • 真正执行的位置
      • 此时如果你尝试用 kill 命令杀掉 uiautomator 进程,会杀不掉:
      • 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端口
    • 小米的 wifi 连接只支持已配对的设备,没配对的设备是连不上去的,内网穿透也一样
  • 不管如何,对这个端口做映射即可(服务器的端口也记得开):
  • 内网穿透端口配置
  • 手机启动内网穿透:
  • 手机启动
  • 然后试着在另一台 win 电脑上使用 adb connect 连接,可以看到使用外网访问完全没问题:
  • 外网adb connect

  • 牛逼的就要来了,我在 win 电脑上执行adb forward,具体命令如下图所示:
  • adb forward
  • 之后我可以通过 win 电脑浏览器输入localhost:8888/info就访问到手机上运行的 atx-agent 服务了,如下图所示:
  • win电脑访问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 其实也只是一层参数包装:
  • Selector构造
  • UIObject 就是对 session、selector、jsonrpc 包装,咋看之下好像没啥问题,但当你查看session.address属性时,你会发现已经存在 ip 端口了:
  • 已经有agent的ip+port了
  • 而此时你在手机里试图寻找 atx-agent 的进程,会发现并不存在(如果存在可能是你访问了其它属性):
  • atx-agent不存在

上面的 session 不要在断点时展开所有属性,否则你会发现展开得很慢,因为有些属性是通过请求 atx-agent 获取的,而发现 atx-agent 进程不在时,就会自动拉起,正常的启动逻辑不是这样的。而只获取 address 属性不会有这个问题。

  • 在这里我直接先说结论,之所以 atx-agent 不存在就有 ip+port,是因为 uiautomator 的逻辑里面会直接复用之前转发到手机端 7912 的端口,后续 atx-agent 是固定死 7912 端口的所以不会有问题
  • 而先前说的uiautomator2 purge只是卸载 APP+ 可执行文件,并没有删除端口转发,我们可以使用adb forward --list查看已存在的端口映射,会发现正好等于上面获取到的 port:
  • adb forward --list
  • 我们可以持续跟进 address 属性的获取逻辑,看看是不是这样:
  • address
  • _get_atx_agent_url
  • forward_port
  • 在前面uiautomator2 init的流程中是先启动 atx-agent server 再进行端口映射的,但实际上先进行端口映射也没关系,因为 atx-agent server 的端口固定 7912,只要保证 jsonrpc 请求前映射到就行。

click(timeout=3) 做了什么

  • 在正式 debug 代码前,我先说明一些环境问题,比如你刚进入 click 的断点时,会发现控制台的对象一直在加载(前提是你前面的步骤没有误启动过 atx-agent,且之后执行uiautomator purge清理),像下面这样这样:
  • 加载属性
  • 当加载完成后,会发现手机上 atx-agent 也启动了:
  • 属性加载完成
  • 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,这个函数默认就是在规定时间内看指定元素是否存在,代码如下:
  • must_wait
  • 我们继续跟进上面的wait函数,会发现里面其实是 jsonrpc 的调用:
  • wait函数
  • 但不要忘了,我们正常流程下 agx-agent 还没启动呢,所以继续要继续深入 jsonrpc 的逻辑,在调试的过程中有一段代码可能会让你产生误解,如下图所示:
  • 可能产生误解的代码
  • 继续看_AgentRequestSession#request的实现,终于发现初始化 atx-agent 的代码了:
  • image-1693812095696
  • 到这里为止就可以停止先前执行的循环删除 atx-agent 的脚本了,至于_prepare_atx_agent的执行逻辑我想应该不用多说太多,最终还是会执行到前面uiautomator2 init提到的setup_atx_agent函数,所以启动参数啥的都是一样的,调用栈如下图所示:
  • _prepare_atx_agent调用栈
  • 之后就是真正的去请求了,只不过还是会请求失败,失败的原因我们可以看下 golang 的代码(是 debug 手机的 atx-agent,非本地的),如下图所示:
  • 确认是wait的请求
  • 实际上这段 golang 代码就是将所有/jsonrpc/0的请求都转发到127.0.0.1:9008,上面代码遮住了可能看不清,下面看下完整的:
  • 转发逻辑
  • 转发失败后控制台也有打印:
  • 控制台打印
  • 到这里你可能就要问了,为啥固定转发 9008 端口呢,实际上这段逻辑在 APP 层,这里可以先贴出代码看看:
  • 9008端口来源

  • 上面的接口因为尝试转发到 APP 上,但是因为 APP 进程还不存在,所以返回失败,进入如下逻辑,不难想到肯定有设置 uiautomator 的兜底逻辑:
  • 请求失败后逻辑
  • reset_uiautomator的核心逻辑如下
    • 再次确认 atx-agent 请求返回:
    • 在此确认atx-agent请求返回
    • 因为 uiautomator 还没启动,所以铁定是不通的,之后确认 atx-agent 版本号,不对则重新调用_prepare_atx_agent(前面说过这个函数):
    • 检查atx-agent
    • 我们 atx-agent 没问题,所以直接过到下一步,进入_force_reset_uiautomator_v2开始重置 ui2 环境,这段逻辑比较长,下面单独拆分字标题说。

重置 uiautomator_v2 如何进行

  • 进入到_force_reset_uiautomator_v2,头部逻辑如下:
  • _force_reset_uiautomator_v2头部
  • 到这里我先说明一个可能的新问题:你觉得上面的self.shell(...)是怎么调用的?你是不是觉得是 python 直接在 pc 端运行的命令?如果你这么想恭喜你答错了,实际上self.shell(...)是把命令给到 atx-agent 去执行的
  • 看看这里 shell 的转发代码。依旧是使用 jsonrpc,只不过这个 path 是 atx-agent 自己处理的:
  • shell实现
  • golang 侧 shell 的实现等启动 uiautomator 的时候再看,普通命令没太大区别,后续进入self.uiautomator.stop(),我们看看这个stop干了啥:
  • stop逻辑
  • 我们再到 golang 中看一下,发现是在 golang 中是通过之前存储的字典取出保活进程
  • delete请求命中
  • 字典取出进程对象然后再调用stop
  • 我们再跟进pkeeper.stop()看看,发现核心就是传了个Truep.stopC
  • pkeeper.stop()
  • 前面没讲pkeeper.start()是怎么运作的,实际上它就是运行了一个死循环,当p.stopC传入 True 时就会结束,然后释放进程;截取了部分关键代码如下图所示:
  • pkeeper.start()跳出逻辑

  • 保活进程释放后,python 层会使用 kill -9 杀掉 uiautomator 进程:
  • kill -9杀掉uiautomator
  • 接下来就是安装 uiautomator 的两个 apk 了,安装的逻辑前面也看过了,这里不再赘述,安装完成会打印两条日志:
  • 安装日志
  • 剩下的self.uiautomator.start()跟之前的stop十分有九分相似,python 层依旧是 jsonrpc 请求,只是变成了 post 方法:
  • start请求
  • 至于 golang 端的实现,之前已经看过一次了,就是使用am instrument启动单元测试的方式,然后再加个保活锁:
  • golang层start
  • 到此为止,uiautomator 的进程就都起来了,我们可以用 ps 命令看看(有点乱):
  • 确认uiautomator进程
  • reset_uiautomator函数也到此结束了,后面虽然还有一些兜底逻辑,但大部分都是已经见过的函数实现,所以不再赘述。

判定控件是否存在

  • 回到之前的_jsonrpc_retry_call处,reset_uiautomator成功后会重新发起一次请求:
  • 重新发起请求
  • 这一次就能正确打到 APP 的代码上了,而 APP 是使用com.googlecode.jsonrpc4j.JsonRpcServer实现了 jsonrpc 服务,并在AutomatorServiceImpl中实现了具体实现,其中waitForExists如下:
  • waitForExists
  • 之后还会继续调用androidx.test.uiautomator包提供的能力,uiautomator提供的能力其实大部分来自AccessibilityService
  • androidx.test.uiautomator
  • findAccessibilityNodeInfo
  • 到此为止,从 python client -> atx-agent server -> app 层都经历过了,其它实现基本都是这么流程,我就不再一一展开赘述了。

默认点击实现与坑

  • 这里我就不再从 python 层一个个过了,直接看 APP 层的点击实现,无论你是 xpath、text 还是别的点击方式,最终大概率都会来到com.github.uiautomator.stub.AutomatorServiceImpl#click(int, int, long),按下和松开中间有个间隔的就是长按函数了:
  • click函数
  • 跟进touchUp,因为最终的返回值是它决定的,原理大同小异:
  • touchUp
  • injectEventSync继续深入的话需要下载源码,这里就不再深入了,你只需要知道这里使用的是一个同步的注入方法,如果注入失败就会返回 fasle:
  • injectEventSync

  • 那么,有哪些坑呢?
  • 第一坑:
    • 事件注入可能会和其它应用有冲突,比如我曾尝试和 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./dlv是有区别的):
  • Android启动dlv
  • 之后按照先前文档,配置 vscode 的launch.json文件如下:
  • launch.json配置
  • 现在还不能按 F5 启动,上面配置的 program 指的是 debug 包的路径,我们 atx-agent 的 debug 包还没打,打包命令如下(顺手推上去):
GOOS=linux GOARCH=arm64 go build -gcflags="all = -N -l"
  • build+push
  • 之后我们就可以在 atx-agent 的工程按下 F5 启动调试了(有个警告不用管),确认 vscode 进入 debug 状态:
  • 确认vscode debug状态
  • 确认手机端的 atx-agent server 也被启动了:
  • 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后,会发现有如下内容:
    • screencap --help
      • 没错,-d参数就是可以指定 display id 截图
    • scrcpy 可以根据 display id 来选择远控的屏幕,实测多屏地图确实可以,官方文档中描述如下:
    • scrcpy
    • uiautomator2 的一些实现也是也有用到 window manager:
    • window manager
  • 剩下的就是如何拼装了

最后

  • 上面提到在百度、字节工作过,看起来好 diao,但实际上我目前只工作过两年,字节一年,百度一年;并且最近因为业务人员裁撤,处于失业的状态
  • 不过闲着也是闲着,并且发现身为一个测试开发都没怎么写过相关的内容,所以就趁热写一篇吧
共收到 47 条回复 时间 点赞
slink #48 · 2023年09月05日 Author

不知道会不会写的太啰嗦了

帮顶

slink #46 · 2023年09月05日 Author

@chenhengjie123 有无加精机会

是 wenjie 本人吗?😀

slink #44 · 2023年09月05日 Author
王稀饭 回复

yep,xinhuan 你好

slink #43 · 2023年09月05日 Author
Eason 回复

感谢,还以为你都没怎么看 TesterHome 了🤣

slink 回复

图片都挂了。

slink #41 · 2023年09月05日 Author
恒温 回复

嗯?我看着都是正常的呀

slink 回复

现在有了。。感觉你的域名有抖动。。

slink #39 · 2023年09月05日 Author
恒温 回复

七牛云的 CDN,这个锅七牛背🤣

陈恒捷 将本帖设为了精华贴 09月05日 20:26

很详细,给你加个精。欢迎后面多来分享

slink 回复

有个好奇,字节和百度,有落地使用 Appium 的吗?

这位大佬,做个自我介绍,应该还有很多人对你不了解

slink 回复

偶尔会来看看😆

slink #33 · 2023年09月06日 Author
干饭狂人 回复

百度只能说目测没有(后面转高精了,所以了解可能不多),即便用了也肯定改过,因为原生的 appium 很慢;字节有一套完整的自研,我在的时候很多内部资源都没整合,现在估计都整合好了,跟 appium 相似都是一个框架能编写 Android、iOS、PC、各种自研渲染引擎等

slink #32 · 2023年09月06日 Author

TesterHome 这边文章末尾说了

现在手机厂商是不是都是用的自己的 rpc 去实现的

仅楼主可见

这位是我的前同事,我可以作证他本人是很硬核的技术爱好者,可惜时运不济没找到合适的岗位

干饭狂人 回复

补充一下答案
字节移动端 ui 自动化一直有专门的中台质量团队开发(这些人理解为测开或者纯开发都行,和业务线没半毛钱关系)。早期移动端 ui 自动化就是使用 UIautomator 的封装,具体来说在 20 年及以前基本都还是在用它,大概 20 年中后期开始大范围推广自研 ui 自动化框架(本质上是腾讯 QT4A 的二次开发,核心开发者是从腾讯过来的)。
截止至 2023 年 9 月,字节内部的 UI 自动化框架已经支持很多端拓展,包括但不仅限于 Win、Mac、Android、iOS、Flutter、Cocos、Unity、Ue 以及一些内部技术栈等,依然是专门团队在做它以及周边扩展(部分核心老成员我还认识),包括集成到研发体系的云真机平台、集成到 vscode 的调试插件等。

slink #27 · 2023年09月06日 Author
王稀饭 回复

前半句夸张了,也就普通 RD 水平,广度稍宽一些;后半句,合适的岗位不要我.jpg

写得挺好的,有许多自己到思考和学习,在自动化能力上,uia2 可用性较差,有兴趣进一步学习,可以深入下 android framework 架构 和 uiautomation 的架构,可以实现一套更轻量的架构,基于 shell 的身份权限可以做很多事情, 像多屏控制,底层接口都是支持的。

王稀饭 回复

确实,很硬核

图片能否上传到 testerhome?我这边内网环境外链看不了图片

好东西 必须支持一下

slink #22 · 2023年09月26日 Author
WCAG 回复

我看 testerhome 公众号有转发,可以看那个,图片重新上传一遍容易翻车

请问 uiautomator2 支援同一台电脑连多台 andorid 设备吗?
目前我使用上时不时会遇到其中一台的设备会断线

希望多点这种文章

UI 自动化最终的解决方案可能就是使用 AI 图像识别,这样可能保证稳定性

slink #31 · 2023年11月30日 Author
轩天 回复

只能说很悬,这活现在即便是大厂,在普通测试/研发域也不会投入专门的人做,成本高且收益成迷,大概率是等其它域出现成果了就拿来试试看

slink 回复

嗯,最终还是要结合产品的性质来看的,也不能一概而论。

仅楼主可见
slink #34 · 2023年12月21日 Author
debugtalk 回复

不是不想去,有别的硬伤在暂时去不了,而且刚在新岗位上屁股坐热🤣

很硬的文章

大佬,能请教下 weditor 如何显示车机第二块屏幕的元素嘛

你好,我是 uiautomator2 的作者,最近参加了广进计划,因为时间太充沛,导致精力无处释放,所以把 uiautomator2 大刀阔斧的改革了一下(atx-agent 直接去掉了),另外 weditor 也重新实现了一个 https://uiauto.dev
你有没有兴趣再写一篇解读 _^

codeskyblue 回复

真的被裁了? 我以前用过你写的工具😁

codeskyblue 回复

这么优秀都被广进了, 哪家公司这么不长眼啊

slink #40 · 2024年06月14日 Author
codeskyblue 回复

从高精开始到现在,一直怎么动过客户端的东西了,以后业务有机会落地再拜读一下🤣

codeskyblue 回复

大佬都被广进了吗?

@codeskyblue https://uiauto.dev 大佬,这地址打不开

codeskyblue 回复

大佬,安装 app-uiautomator.apk 和 app-uiautomator-test.apk 老是提示签名不一致怎么搞

Memory 回复

python uiautomator2 3.2.0 发布了,不再依赖 apk 啦,签名不一致的问题不会有了。

codeskyblue 回复

是使用反射方式连接的吗? 好处是方便了,无需安装。但不已应用的方式 会存在一些功能实现不了,比如 wifi 连接 等这些,兼容所有手机获取 Context 也是个问题。目前我只用来处理自动安装这块

哲豪 回复

是的 java 反射调用,效果很好

codeskyblue 回复

嗯,这种可以很好的避免之前 vivo、oppo 等手机需要安装 apk 的问题,现在直接推送到手机就行😆

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册