本篇更偏向于源码解析,适用于对airtest有一些了解,看过入门教程,写过demo的童鞋,当然初学者也可以在本章的上手环节跳转到网易官方最快5分钟教程中学习,因为我觉得那篇教程已经够好了,就不多写入门教程了。
目录
简介
上手
进阶
总结
Airtest Project是最近非常火的一个ui自动化测试工具,由网易游戏内部工具团队开发并开源,获得谷歌力挺。
AirtestIDE 是一个跨平台、多端(Windows、web、android、ios、游戏)的UI自动化测试编辑器。
网易官方的最快五分钟上手教程
官方教程,有演示视频,有动图,一目了然。环境搭建也相当简单,基本上安装好IDE就可以了。
AirTest IDE提供了一站式功能:脚本开发(录制、编辑)、设备管理、运行、回放、结果查看
相信通过网易的这个上手教程,很多人都能很快就可以把airtest玩起来了。
当我们跟随着教程写好一条脚本,运行起来以后,一起来看看AirTest的大致框架。
首先在AirTest的定义中脚本文件名的后缀是.air,当我们在IDE中新建一个脚本文件
再来到文件管理中我们可以看到这是一个文件夹。
这里面有一个跟air脚本同名的py文件,其他的png图片就是在IDE里截图,录制,生成的图像文件。
打开这个py文件来看看:
可以看出在IDE里显示的touch(图片),就是在api里的一个touch接口,里面传入一个Template,这个对象包含了图片文件的名称、录制时的相对坐标(record_pos),分辨率(resolution)等,当然还有其他参数:目标位置(target_pos)、rgb匹配(rgb),如果你在IDE里双击图片就会弹出窗口设置这些详细参数。
我想图像识别大概就是这样了:写脚本时截下目标图片(你想要点击的地方),这图片就跟python脚本保存在一起,touch接口传入这些目标图片,进行匹配,成功后点击目标图片的位置,有兴趣的话继续来看看这个touch接口的源码。
@logwrap
def touch(v, times=1, **kwargs):
"""
Perform the touch action on the device screen
:param v: target to touch, either a Template instance or absolute coordinates (x, y)
:param times: how many touches to be performed
:param kwargs: platform specific `kwargs`, please refer to corresponding docs
:return: finial position to be clicked
:platforms: Android, Windows, iOS
"""
if isinstance(v, Template):
pos = loop_find(v, timeout=ST.FIND_TIMEOUT)
else:
try_log_screen()
pos = v
for _ in range(times):
G.DEVICE.touch(pos, **kwargs)
time.sleep(0.05)
delay_after_operation()
return pos
入参:
loop_find(v, timeout=ST.FIND_TIMEOUT)#通过名字大概知道,循环查找这个v,有个超时退出,返回坐标点
G.DEVICE.touch(pos, **kwargs)#点击设备的指定坐标点
G.DEVICE应该就是一个当前的设备,兼容android、ios、windows
delay_after_operation#最后点击完以后还等待一下,所以这里可以配置每步点击的等待时间
所以touch接口的逻辑是:
再往下,看一下loop_find这个接口,我想这就是“图像识别”的“核心”部分了,哈哈
@logwrap
def loop_find(query, timeout=ST.FIND_TIMEOUT, threshold=None, interval=0.5, intervalfunc=None):
"""
Search for image template in the screen until timeout
Args:
query: image template to be found in screenshot
timeout: time interval how long to look for the image template
threshold: default is None
interval: sleep interval before next attempt to find the image template
intervalfunc: function that is executed after unsuccessful attempt to find the image template
Raises:
TargetNotFoundError: when image template is not found in screenshot
Returns:
TargetNotFoundError if image template not found, otherwise returns the position where the image template has
been found in screenshot
"""
G.LOGGING.info("Try finding:\n%s", query)
start_time = time.time()
while True:
screen = G.DEVICE.snapshot(filename=None)
if screen is None:
G.LOGGING.warning("Screen is None, may be locked")
else:
if threshold:
query.threshold = threshold
match_pos = query.match_in(screen)
if match_pos:
try_log_screen(screen)
return match_pos
if intervalfunc is not None:
intervalfunc()
# 超时则raise,未超时则进行下次循环:
if (time.time() - start_time) > timeout:
try_log_screen(screen)
raise TargetNotFoundError('Picture %s not found in screen' % query)
else:
time.sleep(interval)
入参:
返参:pos:目标图片在设备屏幕中的位置
screen = G.DEVICE.snapshot(filename=None)#设备截图,所以运行完脚本以后工程路径会有很多个截图文件,就是这里产生的。
match_pos = query.match_in(screen)#在设备截图中匹配查找我们传入的目标图片
所以这loop_find的逻辑就是:一个循环,从设备中截取屏幕的图片,在屏幕图片上查找匹配我们的目标图片,匹配成功则记录日志然后返回位置坐标,失败则判断是否是否有intervalfunc方法需要执行,默认是没有的,跳过,然后接着继续循环截图、匹配,直到超时报一个TargetNotFoundError异常出去。
那么图像的匹配算法大概就是在这个match_in接口里了,接着再看一点吧,哈哈
def match_in(self, screen):
match_result = self._cv_match(screen)
G.LOGGING.debug("match result: %s", match_result)
if not match_result:
return None
focus_pos = TargetPos().getXY(match_result, self.target_pos)
return focus_pos
@logwrap
def _cv_match(self, screen):
# in case image file not exist in current directory:
image = self._imread()
image = self._resize_image(image, screen, ST.RESIZE_METHOD)
ret = None
for method in ST.CVSTRATEGY:
if method == "tpl":
ret = self._try_match(self._find_template, image, screen)
elif method == "sift":
ret = self._try_match(self._find_sift_in_predict_area, image, screen)
if not ret:
ret = self._try_match(self._find_sift, image, screen)
else:
G.LOGGING.warning("Undefined method in CV_STRATEGY: %s", method)
if ret:
break
return ret
match_in调用cv_match进行匹配,然后TargetPos().getXY(match_result, self.target_pos)就是对匹配出来的结果进行处理,在前面讲touch的时候有一个参数是target_pos,还有印象吗?根据教程和文档说明,target_pos是以123456789的数字按九宫格键盘排列,分别代表左上角,正上角,右上角,...,右下角。这个getXY就是对这个进行处理的,根据传入的target_pos对匹配到的坐标信息再做处理返回目标图片中的不同位置上的坐标,默认是返回中心点。
再看cv_match接口,
其中sift策略中优先对预测的区域进行匹配,也就是用到了再touch接口中传入的record_pos,终于知道为啥要传入写脚本是截图的位置了吧。
这个try_match是转换接口,method,再调用method,也就是说匹配的算法有三个不同的,有兴趣可以继续去看看:
_find_template、_find_sift_in_predict_area、_find_sift这三个接口。
@staticmethod
def _try_match(method, *args, **kwargs):
G.LOGGING.debug("try match with %s" % method.__name__)
try:
ret = method(*args, **kwargs)
except aircv.BaseError as err:
G.LOGGING.debug(repr(err))
return None
else:
return ret
Airtest的优点
本篇通过touch接口对airtest的图像识别的源码进行了初步的分析,更多图像匹配算法实现部分,下回分解。