效果
为了实现测试自动化这一“伪命题”,经过不懈的努力(折腾),终于基于Airtest(其实关联倒也没那么大)做出来有那么一点意思的自动化脚本。
import logging
logging.getLogger("airtest").setLevel(logging.ERROR)
from airtest.core.api import *
import sys
sys.path.append(".")
from support.app.manager import AppManager
auto_setup(__file__)
am = AppManager('com.android.settings', os.path.join(os.path.dirname(os.path.realpath(__file__)), 'snap'), os.path.join(os.path.dirname(os.path.realpath(__file__)), 'exempt'))
am.stop()
am.start()
am.uiCrawl()
am.stop()
am.clear()
这是调用脚本,只需要传入PackageName就行了。剩下的就交给AppManager.uiCrawl()
完成。
遍历思路
在以前研究(折腾)成果的基础上,叠加了更多的逻辑判断(允许我叉会儿腰),大致流程如下图所示。
以区域查找(基于图像识别与位置关系的Android控件遍历方式)为主要逻辑,同时以检测Activity堆栈信息为辅助,实现自动搜寻未访问过的控件以及自动从别的App跳转回待测App。
难点
1. 如何在当前界面上匹配图形
利用Opencv做图形匹配在之前的博文中已有分享,这里就不过多赘叙。但是很可能出现希望搜索的控件图形并不在当前屏幕显示范围内,即需要上下左右滑动屏幕才会出现的控件,如:提交按钮一般都在填完大量信息之后才会出现,因此需要将屏幕滑动到底。如何判断屏幕滑到尽头了呢?
从代码上判断吗?就算能捕获到ScrollView控件,并获取其当前值和最大值就能判断是否到达底部了吗?实际情况可能未必。有大量信息浏览类App采用滑动的方式代替了过去的翻页操作,体现在用户体验上就是用户只需要不停的将屏幕滑动到底部,就会自动加载下一页的内容。
从用户的角度去思考呢?我们潜意识中判断是否还可以再滑动,其实是以当前屏幕在滑动以后有没有新东西出现做依据的,有,则可继续滑动。没有,则已经到头了。有了解决思路就可以转换成代码了。
def screen_match(last, current, confidence = 0.8):
if(last is None or current is None):
return False
img1 = cv2.resize(last, (256,256))
img2 = cv2.resize(current, (256,256))
sub_image1 = cv2.split(img1)
sub_image2 = cv2.split(img2)
sub_data = 0
for im1, im2 in zip(sub_image1, sub_image2):
sub_data += UIConfidence.calculate(im1, im2)
sub_data = sub_data / 3
print('screen_match:{}'.format(sub_data))
if(type(sub_data) is float):
return sub_data > confidence
else:
return sub_data[0] > confidence
通过对比两幅屏幕截图的三通道(BGR)各自的差值来判断两幅屏幕截图的相似度,如果大于阈值(默认80%),则认为屏幕相同。这里的80%是一个经验值,需要根据实际情况做些调整。
有了屏幕对比,接下来就是在滑动前后各做一次屏幕截图,并对比截图相似度即可。
def _swipedTo(self, dir):
while(True):
screen = self._saveSnapScreen()
if(screen is None):
break
self.poco.swipe([0, 0.5], direction=dir)
sleep(5)
if(uic.screen_match(screen, self._saveSnapScreen())):
break
为了让屏幕截图尽可能稳定,因此滑动后sleep(5)。
2.跳转判断
现在App之间存在各种的跳转,当然能理解这种导流的方式(其实和早期PC时代,网站间交换链接的情况是类似的),但是我们的脚本可就理解不了这种情况了。当点击了某个按钮之后跳转到另一个App,脚本呢?还傻乎乎的继续遍历(人工智障)。这样有什么不好吗?当然不好!从测试的角度讲,我们不希望测试的范围无限扩大,从项目管理的角度讲这叫范围蔓延。要及时纠正这样的错误,就需要在必要的时候检测有没有跳转发生。
幸运的是Android对于已显示的Activity有着相当成熟的管理机制,我们可以简单理解为FILO(First-In/Last-Out),如果我们能获取到当前Activity堆栈最顶层的Activity就可以判断是否发生了App跳转。而dump可以提供相关的信息。
def getCurrentPageName(self):
ret = shell("dumpsys activity | grep \"Run #\"")
lines = ret.split('\n')
emt = lines[0].strip().split(' ')
return emt[4].split('/')
有了这样的判断,我们可以进一步获取当前是否还有需要遍历的Activity,并以此作为程序退出的条件(在流程图中已标注)。
def hasAnyActivity(self):
ret = shell("dumpsys activity | grep \"Run #\" | grep \"{}\"".format(self.pkg))
print(ret)
return len(ret.split('\n'))
可气的是,这个逻辑判断目前还有问题,它工作起来像是一个固执且愚蠢的小可爱
遗憾
1. 本人对Python理解不深刻,代码中充满了智障的写法,也没有将Python优美的语法体现在代码当中,所以整个代码显得臃肿不堪。
2. 还有很多需要优化的地方,目前运行效率并不高。
3. 虽然获取了控件可操作属性(可点击、可长按、可编辑),但是却并没有充分使用。这就像一个测试人员将App所有界面看了一遍,却不知道哪里有错,哪里需要改进,简单来讲就是没啥用。
后续
进一步优化逻辑判断,简化操作步骤。并将检测方式融入脚本当中,这样才可以让脚本在业务上发挥真正的作用。
仓库在Gitee并持续更新。