Uiautomator2获取toast失败的方案补充——OCR方式获取toast内容

531 阅读3分钟

背景

  • 问题描述:在使用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 参数指定的值。

问题分析:

  1. 环境依赖

• Uiautomator2 依赖于设备的 Accessibility 服务来捕获 toast 消息。如果设备的 Accessibility 服务未能正确捕获或转发 toast 消息,那么 getLastToast 方法可能无法返回任何消息。

  1. 自定义Toast

• 如果应用对toast进行了自定义处理(如修改了显示逻辑或使用了非标准组件来显示消息),标准的 Accessibility 服务可能无法识别这些自定义的toast,导致无法捕获。

狗头护体,这上面的部分只是个人推断与个人为了深挖为什么捕捉不到toast过程中看到的一些佐证,若出错欢迎一起讨论。

针对原方案所做过的尝试

  1. 尝试去github中查找是否有人遇到跟我一样的问题,结果有,哭了啊,他的方案我后续会写在解决方案一中,文章白写啦。github.com/openatx/uia…

  2. 尝试在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方案,感谢大家!