通过"adb shell am start"启动app的一个坑
通过”adb shell am start”启动app的一个坑
[TOC]
起因
这里用的自动化框架是openatx/uiautomator2,测试框架是:pytest
+ allure
。先上一下关键代码:
1 | import pytest |
这个时候遇到一个问题:
为了便于理解,这里拿 QQ音乐 举例,实际是一个类似的 app
打开QQ音乐,选择一首歌曲-播放,在播放器界面按 home 键回到桌面。
点击桌面快捷方式返回QQ音乐
现象:手动操作返回的是播放器界面(即回到桌面 launcher 之前的界面),但是通过代码执行,返回的却是QQ音乐的首页。
初步分析
出了这个问题以后,查看了下uiautomator2中app_start()
的代码,发现就是通过android的am
命令实现的。
这就很奇怪了,因为在写成脚本之前,我已经手动通过命令行验证了使用am
命令是可行的,完整的命令是:adb shell am start -n com.tencent.qqmusic/.activity.AppStarterActivity
,执行结果:
1 | am start -n com.tencent.qqmusic/com.tencent.qqmusic.activity.AppStarterActivity |
难道 uiautomator2 的封装有问题,或者用了什么 amazing 的方法我没看懂?
干脆在代码里直接把app_start()
替换成了shell("am start -n com.tencent.qqmusic/.activity.AppStarterActivity")
。
但是执行一下,问题依旧。不过这个时候又有了新的发现:
手动执行的时候多了一个warning:`Warning: Activity not started, its current task has been brought to the front`,在脚本中执行shell命令却没有这个warning。
实在想不通了,给作者提了个issues。
继续思考
在等待回复这段时间,整理了下思路,换了个角度去思考:
既然在 android 系统里桌面 launcher 也是一个 app ,播放器是一个 app ,那么 app 的冷启动和热启动过程本质上就是从一个 app 拉起另外一个 app 。所以撸起袖子继续请教谷歌大神,发现拉起另外一个 app 的 demo ,核心代码都是在构造一个Intent
,在这个过程中会传入 ACTION、CATEGORY 等各种参数。
所以,有没有办法记录所有 Intent ?
查了一圈没有找到答案,但是根据以往的经验,dumpsys
这个命令可以查到系统里很多信息,但是不是没人详细介绍这个命令,就是开发写的源码分析,对于我来讲从 android 源码角度去分析还是有点难了。没办法,一条一条试吧!
……(此处省略五千字)
功夫不负有心人,当尝试dumpsys activity recents
这条命令时,终于发现端倪了:
1 | dumpsys activity recents |
按字面意思理解,这条命令查看的是最近的 activity ,这里通过手动切换多任务、尝试各个 app 之间互相拉起后查看结果,也验证了这一点。而且,第一个task(#0)是最新的一个 activity ,这里面有 Intent 等各种信息。
所以,就用这种办法分析试试。这里尝试手动桌面返回 app 和脚本自动化,再把两次的操作结果保存下来作对比,有了新的发现:
- 出错的时候(脚本),Intent是:
intent={flg=0x10000000 cmp=com.tencent.qqmusic/.activity.AppStarterActivity}
- 正常的时候(手动),Intent是:
intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.tencent.qqmusic/.activity.AppStarterActivity}
act
和cat
这两个参数十分可疑!查看一下帮助信息am -h
,发现这两个参数是ACTION
和CATEGORY
,赶紧加上试试!
所以完整shell命令修改为:adb shell am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -n com.tencent.qqmusic/.activity.AppStarterActivity
。
手动执行一下,没问题!那再回到代码,把桌面回到QQ音乐的代码改成:shell("am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -n com.tencent.qqmusic/.activity.AppStarterActivity")
。这次该解决了吧!执行一遍,又报错了!
喵???
上大招吧!这次干脆一点,直接在第一行代码就加上断点。不过,灵机一动,在断点这里停下时,手动执行了一下dumpsys activity recents
,终于找到问题了!原来启动的时候没有ACTION
和CATEGORY
!
这时候不禁恍然大悟,原来我把启动app封装在了 fixture 里,然而 fixture 里依然使用的是方法app_start()
,而这个方法是不带参数的!
所以其实问题出在app首次启动不带参数,实在是太马虎了。
既然找到根本原因了,修改一下代码,反手给作者一记PR( pull request ),折腾了小半天终于真正解决!
这里故意忽略了
FLAG
这个参数,因为我也不是十分肯定我的理解是否正确:像FLAG
、EXTRA_KEY
、DATA_URI
这类参数,传递的都是附加信息/数据,通常来讲,开发同学在 coding 时不会通过这些个字段作为判断”跳转”来源的依据。
举个不恰当的例子:这就像用
selenium
做 web 自动化测试,driver
指定用什么浏览器测试,driver.get(URL)
执行打开URL。我们一般不会去通过 URL(数据)判断用什么浏览器去测试。因为“数据”是外部传递过来的,它的内容是不可预期的;“行为”是系统规定的,只有几个选项可用。
另外,
最后还是再强调一下,因为场景比较复杂,单靠文字描述不太容易解释清楚,所以文中是用QQ音乐举例的,实际用QQ音乐是无法复现这个问题的。
总结
老话说的好:面试造火箭,入职拧螺丝。
我印象最深的就是一般的面试题都有四大组件啊、android 系统架构这些东西,基本大家就是背一下而已,工作中觉得用不上。真正出了问题,能查到的资料大部分又大多是开发视角,对于测试来讲非常不友好。
就拿这个dumpsys
命令来说吧,大部分开发不会深入去研究具体逻辑和实现,因为这与实际业务逻辑无关。即使某些情况下需要,直接阅读源码就能快速理解。但是测试面临这个问题就很难办了:
- 代码能力不一定够
- 工具文档没有,或不够详细
- 因为android开源,厂商可以对系统进行定制,所以在不同的厂商、不同的系统版本、甚至不同的ROM上,表现并不一致。亲眼见过,某个人定制 ROM ,作者把所有命令行工具都加上了自己的名字。
作为一名小测试,可能很难改变这种现状,但是千万不要把自己的工作局限于每天点点点。打好基础,努力保持每天都在学习,才不会被淘汰。