在UI自动化测试中弹窗是影响自动化用例稳定性的一大因素,如何方便快捷的处理各种情况下的弹窗,是搞UI自动化测试必须要面临的问题.
弹窗的种类:
安装APP时的系统弹窗 此类弹窗一般有两种,一种是自动化测试框初始化本身也需要安装一些APP,比如uiautomator2会安装atx-agent、com.github.uiautomator,这些弹窗在初始化环境的时候可以手动点掉,case里不需要关注。另一种就是安装我们的被测app,像下面这种
都是我们不得不去处理的,不然,自动化也就是不自动了。 APP启动时的权限弹窗
这类弹窗是APP在启动时会申请一些基础的权限
APP内的业务弹窗
弹窗处理
本文使用的是uiautomator2这个自动化框架,它提供了一种watcher对象,可以用来配置要监控的元素,这里我们配置要监控的就是页面上的弹窗,下面来看看具体怎么做。
watcher的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | # 常用写法,注册匿名监控 d.watcher.when( "安装" ).click() # 注册名为ANR的监控,当出现ANR和Force Close时,点击Force Close d.watcher( "ANR" ).when(xpath= "ANR" ).when( "Force Close" ).click() # 其他回调例子 d.watcher.when( "抢红包" ).press( "back" ) d.watcher.when( "//*[@text = 'Out of memory']" ).call(lambda d: d.shell( 'am force-stop com.im.qq' )) # 回调说明 def click_callback(d: u2.Device): d.xpath( "确定" ).click() # 在回调中调用不会再次触发watcher d.xpath( "继续" ).click() # 使用d.xpath检查元素的时候,会触发watcher(目前最多触发 5 次 # 移除ANR的监控 d.watcher.remove( "ANR" ) # 移除所有的监控 d.watcher.remove() # 开始后台监控 d.watcher.start() d.watcher.start( 2.0 ) # 默认监控间隔 2 .0s # 强制运行所有监控 d.watcher.run() # 停止监控 d.watcher.stop() # 停止并移除所有的监控,常用于初始化 d.watcher.reset() |
上面是watcher的一些常用api以及解释,来源于github。嘻嘻,自己懒的写了。
实战案例
下面我们用B站apk为例,处理从安装到登录后的一系列弹窗。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | import uiautomator2 as u2 import os import time base_dir = os.path.dirname(__file__) apk_path = os.path.join(base_dir, 'apks/bilibili.apk' ) d = u2.connect_usb(serial= 'MDX0220924018819' ) # 从安装到登录成功后,可能会出现的弹窗,在这里进行注册,这个是华为手机出现的弹窗类型 d.watcher.when( '继续安装' ).click() d.watcher.when( '完成' ).click() d.watcher.when( '同意并继续' ).click() d.watcher.when( "我知道了" ).click() d.watcher.start() d.app_install(apk_path) d.app_start( 'tv.danmaku.bili' ) d(text= '我的' ).click() time.sleep( 3 ) if d(resourceId= "tv.danmaku.bili:id/btn_change_account" ).exists: d(resourceId= "tv.danmaku.bili:id/btn_change_account" ).click() else : d(resourceId= "tv.danmaku.bili:id/tv_login" ).click() time.sleep( 3 ) d(resourceId= "tv.danmaku.bili:id/username" ).set_text( 'xxxxxxxxx' ) d(resourceId= "tv.danmaku.bili:id/userpwd" ).set_text( 'xxxxxxxx' ) d(resourceId= "tv.danmaku.bili:id/log_reg_checkbox" ).click() time.sleep( 2 ) d(resourceId= "tv.danmaku.bili:id/btn_login" ).click() d(text= '首页' ).click() |
弹窗处理的核心思想是,起一个线程,不停的监听页面上有没有弹窗出现,出现了就点击,或点击取消或点击确认等等。
uiautomator2处理弹窗的核心思想
采用了后台运行了一个线程的方法(依赖threading库),然后每隔一段时间dump一次hierarchy,匹配到元素之后执行相应的操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class Watcher(): def __init__(self, d: "uiautomator2.Device" ): self._d = d self._watchers = [] self._watch_stop_event = threading.Event() self._watch_stopped = threading.Event() self._watching = False # func start is calling self._triggering = False self.logger = setup_logger() self.logger.setLevel(logging.INFO) def when(self, xpath=None): return XPathWatcher(self, xpath) |
Watcher对象个self._watchers 属性来维护所有要监控的元素,d.watcher.when('继续安装')当我们调用when方法后会返回一个XPathWatcher对象,然后再调用这个对象的click方法实现对监控元素的操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | class XPathWatcher(): def __init__(self, parent: Watcher, xpath: str, name: str = '' ): self._name = name self._parent = parent self._xpath_list = [xpath] if xpath else [] def when(self, xpath=None): self._xpath_list.append(xpath) return self def call(self, func): "" " func accept argument, key(d, el) d=self._d, el=element "" " self._parent._watchers.append({ "name" : self._name, "xpaths" : self._xpath_list, "callback" : func, }) def click(self): def _inner_click(selector): selector.get_last_match().click() self.call(_inner_click) |
click方法就是将点击的操作放到回调函数,然后调用XPathWatcher对象的call方法,这个方法会生成一个监控规则,并将监控规则放到我们前面提到的Watcher对象的self._watchers 属性。
1 2 3 4 5 6 7 8 9 10 11 12 | def start(self, interval: float = 2.0 ): "" " stop watcher " "" if self._watching: self.logger.warning( "already started" ) return self._watching = True th = threading.Thread(name= "watcher" , target=self._watch_forever, args=(interval, )) th.daemon = True th.start() return th |
再然后调用Watcher对象的的start方法,开启一个线程,按照指定间隔时间从页面dump信息,查看是否有要监控的元素,找到后调用回调函数。
现在我也找了很多测试的朋友,做了一个分享技术的交流群,共享了很多我们收集的技术文档和视频教程。
如果你不想再体验自学时找不到资源,没人解答问题,坚持几天便放弃的感受
qq群号:485187702【暗号:csdn11】
可以加入我们一起交流。而且还有很多在自动化,性能,安全,测试开发等等方面有一定建树的技术大牛
分享他们的经验,还会分享很多直播讲座和技术沙龙
可以免费学习!划重点!开源的!!!
视频+文档+PDF+面试题可以关注公众号:【软件测试小dao】
最后感谢每一个认真阅读我文章的人,看着粉丝一路的上涨和关注,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走! 希望能帮助到你!【100%无套路免费领取】