背景
-
问题描述:在使用Uiautomator2进行自动化测试时,尝试捕获应用的toast消息却失败了。
-
原因分析:经开发确认,我们的应用中的toast是经过
二次封装的,这可能导致Uiautomator2无法直接通过常规方法捕获toast。通常,Uiautomator2依赖于Android的Accessibility服务来查询UI组件,包括toast。如果toast被自定义或修改,可能不会被视为标准的Accessibility事件,因此Uiautomator2无法检测到它们。
原方案:uiautomator2
- Uiautomator2获取toast:代码如下:
def get_toast(self, text, wait_timeout=5):
"""获取toast"""
self.driver.toast.reset()
message = self.driver.toast.get_message(wait_timeout=wait_timeout)
return text in message if message else False
在这里可以看到,我所使用的是uiautomator2的官方获取toast的用法,但是在我所测试的应用上,也确实无法获取toast,则继续往下剖析原因。
- 剖析源码
我们可以点进uiautomator2的获取toast核心方法
self.driver.toast.get_message看到源码内容:
@property
def toast(self):
obj = self
class Toast(object):
def get_message(self,
wait_timeout=10,
cache_timeout=10,
default=None):
"""
Args:
wait_timeout: seconds of max wait time if toast now show right now
cache_timeout: return immediately if toast showed in recent $cache_timeout
default: default messsage to return when no toast show up
Returns:
None or toast message
"""
deadline = time.time() + wait_timeout
while 1:
message = obj.jsonrpc.getLastToast(cache_timeout * 1000)
if message:
return message
if time.time() > deadline:
return default
time.sleep(.5)
def reset(self):
return obj.jsonrpc.clearLastToast()
def show(self, text, duration=1.0):
return obj.jsonrpc.makeToast(text, duration * 1000)
return Toast()
源码解析:
• 方法使用一个循环来检查是否有新的 toast 消息。
• 使用 jsonrpc.getLastToast 方法从设备获取最新的 toast 消息。此方法利用 Android 的 Accessibility 服务获取屏幕上显示的 toast 文本。
• 如果在 deadline(当前时间加上等待超时)之前检测到 toast 消息,则返回该消息。
• 如果超时未检测到任何消息,则返回 default 参数指定的值。
问题分析:
- 环境依赖:
• Uiautomator2 依赖于设备的 Accessibility 服务来捕获 toast 消息。如果设备的 Accessibility 服务未能正确捕获或转发 toast 消息,那么 getLastToast 方法可能无法返回任何消息。
- 自定义Toast:
• 如果应用对toast进行了自定义处理(如修改了显示逻辑或使用了非标准组件来显示消息),标准的 Accessibility 服务可能无法识别这些自定义的toast,导致无法捕获。
狗头护体,这上面的部分只是个人推断与个人为了深挖为什么捕捉不到toast过程中看到的一些佐证,若出错欢迎一起讨论。
针对原方案所做过的尝试
-
尝试去github中查找是否有人遇到跟我一样的问题,结果有,哭了啊,他的方案我后续会写在解决方案一中,文章白写啦。github.com/openatx/uia…
-
尝试在toast弹出的时候捕捉xml,捕捉失败
解决方案
方案一:也就是上述链接中的解决方案
感谢这位叫shaping520的大哥
import uiautomator2 as u2
d = u2.connect()
d.jsonrpc.setToastListener(True)
// toast显示后
toast_msg = d.toast.get_message()
print(toast_msg)
方案二:截图+OCR识别文字
def get_toast_capture_screen(self, message_part, retry_times=2):
"""
概率匹配失败!
获取toast消息「截屏+文字分析的方式,用来弥补部分toast抓不到的场景」
:param retry_times: 重试次数
:param message_part: 包含的部分消息内容,用于增强匹配的准确性
:return: 返回布尔值结果
"""
for i in range(retry_times):
screenshot = self.driver.screenshot(format='pillow')
text = pytesseract.image_to_string(screenshot)
# 检查是否包含关键字
if message_part in text:
logger.info(f"Toast Detected: {message_part}")
return True
else:
self.sleep(1)
logger.info("No Toast Detected after final attempt.")
return False
心情糟糟的,这个写出来的OCR方案又舍不得删,就这样吧,留着万一后期用到了呢!在此也算是帮各位大哥们踩坑了,有需求先试用方案一。如果想试试方案二,可以用我写的截屏+OCR方案,感谢大家!