赞
踩
介绍:uiautomator2是一个Android自动化测试开源工具,是Google提供的一个自动化测试的Java库,后来发展了python-uiautomator2,封装了谷歌自带的uiautomator测试框架。
看完本篇文章你可能会了解以下事情:
uiautomator2原理基本介绍(常见的uiautomator2 init 命令、默认的点击方案&坑)
UI自动化稳定性问题解决(伪解决),适用场景,不适用怎么办(对比百度、字节、面试公司捞到的一些信息)
或许可以帮你定位框架偶现的一些问题
PS:文中的对象hash如果出现上下文不一致的情况不要见怪,因为usb线有些不稳定,重新调试时对象hash就会发生变化
首先你需要知道构成uiautomator2整体运作的仓库其实总共有三个:
python client 层:https://github.com/openatx/uiautomator2
Android server 层:https://github.com/openatx/atx-agent
Android App+Server 驱动:https://github.com/openatx/android-uiautomator-server
当然了,还有一些第三方库,比如:minicap、minitouch
其中 android-uiautomator-server 虽然是一个仓库,但实际打出来的包是两个,从 github ci 的脚本中也可以看出来这一点,如下图所示:
两个APK作用各有不同,不过自动化控件dump、默认点击操作这些都是在test后缀的apk中。
整体运作链路如下图所示:
基本上整篇文章都是围绕这个调用链路进行调试 + 讲解,现在没看懂也不要紧,跟着代码走一遍就清楚了,接下来看看常用的一些接口都做了什么。
要测试这个功能,只需要给__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 代码我之后补充
这部分比较偏向猜想,觉得不对欢迎补充
先说一个东西,叫内网穿透
,你可以试着访问这个页面(随缘在线):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。
下面的篇幅基本就是看一些常见函数的调用链路了,上面一不小心费了点口水导致开头的流程图还没用上,下面应该就开始对上了。
注意讲的这里是执行uiautomator2 purge
后,再执行如下代码走的click
逻辑:
import uiautomator2 as u2if __name__ == '__main__':
d = u2.connect_usb(serial="af80d1e4") # connect to device d(text="首页").click(timeout=3)
关于u2.connect_usb就不过多讲解了,返回的Devices对象里面由多个父类接口组合而成,click函数也是众多父类的实现之一
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 请求前映射到就行。
在正式 debug 代码前,我先说明一些环境问题,比如你刚进入 click 的断点时,会发现控制台的对象一直在加载(前提是你前面的步骤没有误启动过 atx-agent,且之后执行uiautomator purge
清理),像下面这样这样:
当加载完成后,会发现手机上 atx-agent 也启动了:
那有没有办法不让它成功启动 atx-agent 呢?有,我目前只想出一个愚钝的方法,那就是不停地删除,在 PC 端运行如下脚本:
while truedo adb shell rm -rf /data/local/tmp/atx-agent sleep 0.01done
直接贴进 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 环境,这段逻辑比较长,下面单独拆分字标题说。
进入到_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 自动化不稳定/维护难的原因通常如下:
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 自动化不再像之前一样是投入产出不明的累赘,而是成为了专项环节中的一个小小的脚本工具,不是过程指标也不是结果指标,单纯就是一个辅助工具。
核心参考: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 Lab,所以 CV 是可以解决绝大部分问题的,比如 Android 耗时自动化如何判断起始帧,就是打开开发者设置的指针位置,然后用 CV 去识别屏幕左上角那个用肉眼都不一定看得清的X/X
在百度车机业务,开源方案的表现也还行,因为一个车厂下不同车型通常分辨率都是一致的,如果你是负责一个车厂下的不同车型,那么复用起来基本没有太大问题。
目前就是百度车机业务比较合适纯UI自动化做收益,原因如下:
多个项目虽然会出现 text 文案不一致的情况,但是 RD 基本保持 resource-id 是一致的
业务层面的改动不多,短的项目可能 1 年就交付了,长的 2 年 +,但以大部分车厂的佛性文化来说,需求确认后变更点就不多了
车机系统便利,没有市场上各种自己都不记得密码的密码锁、权限拦截等,进一步保证运行的稳定性
车机可自由 root,这点确实就比较牛逼,通常情况下自动化运行 crash 了,要想抓到要么靠 logcat,要么靠 adb bugreport;而前者不一定出现对应日志,后者又导出齐慢;而有了系统权限后就可以直接去系统目录取 anr、crash、coredump 了,且速度极快,这直接给 UI 自动化附加了一层深度更深的稳定性测试,实际落地中也确实抓到不少跑 Monkey 没出现的问题。
有,就是百度的车机地图,车机地图有一个特殊的业务场景,即:多屏地图,比如主控副屏、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:
剩下的就是如何拼装了
行动吧,在路上总比一直观望的要好,未来的你肯定会感 谢现在拼搏的自己!如果想学习提升找不到资料,没人答疑解惑时,请及时加入扣群: 320231853,里面有各种软件测试+开发资料和技术可以一起交流学习哦。
最后感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:
这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。