AirTest 基本使用及框架浅剖析——五分钟上手制作游戏辅助

简介

Airtest Project 是为编写自动化脚本,达到提升测试效率的一整套解决方案。它可以轻松的扩展到多平台、多引擎上;如基础的 Android和IOS手机应用、App;Windows上的应用等。

学习使用 Airtest Project 很容易,由于 Airtest Project 是基于Python的,只需要会一点基础的 Python 基础知识即可。Airtest Project 需要一个开发环境,推荐使用配套的 AirtestIDE;AirtestIDE针对于 Airtest Project 有一些特殊的功能,使用别的环境可能会让你开发时工作繁琐,效率降低等。

Airtest Project 包含了两个框架,一个是 Airtest 一个是 Poco,这两个框架都是Python 的第三方库。在开发过程中,可以在开发时引入其它库加强你的脚本。

Airtest
是一个跨平台的、基于图像识别的UI自动化测试框架,适用于游戏和App,支持平台有Windows、Android和iOS——引于官方文档

Airtest 可实现“即看见可操作”,但是对文本内容的获取缺无能为力;这一点在官方文档中也有说明。这一点缺点也有解决办法:可通过引入文字识别库进行补缺,如:pytesseract。

使用 Airtest 进行自动化测试时,操作流程一般为:图片截取 → 图片对比 → 相似度与设定值对比 → 找出坐标位置 → 点击。默认情况下 Airtest 对于不同颜色的对比并不敏感,需要开启颜色对比。

在测试对象非原生App或无法取得项目源码时使用 Airtest 进行测试是个很好的选择。

Poco
是一款基于UI控件识别的自动化测试框架,目前支持Unity3D/cocos2dx-*/Android原生app/iOS原生app/微信小程序,也可以在其他引擎中自行接入poco-sdk来使用——引于官方文档

在已有项目源码或测试对象为原生App时使用Poco进行自动化测试,不仅满足可对文本的获取,而且相比 Airtest 更为简洁!本篇只讲解 Airtest 的操作。

AirtestIDE
是一个我们配套推出的跨平台的UI自动化测试编辑器,内置了Airtest和Poco的相关插件功能,能够使用它快速简单地编写脚本——引于官方文档

使用 AirtestIDE 将极大的简便我们的开发过程,对开发者非常友好。提供了截图及截图预览、可连接设备自动读取、高亮的编辑界面、脚本录制、支持设备远程连接并且在嵌入设备对象窗口实时刷新。

界面

安装 AirtestIDE 后,打开 AirtestIDE ,打开模拟器中需要测试的App。
AirtestIDE 的设备窗口默认在可是界面的最右边。
在这里插入图片描述
在 AirtestIDE 中,界面元素可以拖拽,布置成你所喜爱的界面风格。假设一些窗口无意中关闭,可在窗口下拉选项中打开窗口。
在这里插入图片描述

连接

连接设备只需要在移动设备的窗口下列表点击出现的设备信息中的connect,即可连接。
在这里插入图片描述
假设设备列表并未出现设备,点击刷新;
在这里插入图片描述
如果是使用真机设备请使用USB线连接手机,并且允许USB调试,之后刷新ADB。
远程连接需要只是IP及端口号,填入字段点击连接即可。
更多链接本文不再赘述。可查看官方文档

我当前使用的设备为模拟器设备,模拟器连接过程直接在出现的设备列中点击connect即可:
在这里插入图片描述

尝试

在 Airtest 开发中是以“.air”作为文件后缀。
连接设备后,查看代码:

# -*- encoding=utf8 -*-
__author__ = "Administrator"from airtest.core.api import *

其中 from airtest.core.api import * 将Airtest的基本API引入,为之后编写做好准备。
查看可视窗口最左侧,有Airtest辅助窗与Poco辅助窗,本篇主要讲解Airtest。
在这里插入图片描述
首先尝试第一个操作touch,touch中文译为“触摸”,从命名上得知,这是个可实现“触碰”功能的操作。首先鼠标悬浮在 touch 选项处:
在这里插入图片描述
将会提示 touch 功能的相关信息,现在简单的尝试一下 touch 功能。
点击 touch ,把鼠标移动到设备窗,找到你想要实现点击的按钮,点下左键不放,进行拖拽选中,随后放手。
在这里插入图片描述
这时,代码编辑区将会出现 touch('你所选中的图片'),点击运行脚本,尝试使用:
在这里插入图片描述
运行效果如下:
在这里插入图片描述
从效果中可以看到 touch 将会找到与我们所选中的图形相似的图案,进行计算匹配,达到匹配的要求后,进行点击操作。

浅剖析

现在查看一下 touch 函数的实现,从中得到更多的信息,帮助我们进行脚本的开发;点击文件名,然后选择“打开当前文件项目目录”:
在这里插入图片描述
找到当前文件目录后,找到与文件名相同的 .air 文件,使用编辑器进行打开。
以下为编辑器打开该该文件后的代码:

# -*- encoding=utf8 -*-
__author__ = "Administrator"from airtest.core.api import *touch(Template(r"tpl1587733818550.png", record_pos=(-0.217, 0.565), resolution=(540, 960)))

在不经过 AirtestIDE 处理的代码中,图片的表现形式为路径,以及使用了 Template 作为处理,此处,Template 函数接收3个函数,分别为:图片路径\record_pos以及resolution。

在 Airtest api 文档中查询 Template 方法。

在这里插入图片描述
查看文档的值,刚刚使用的Template将会直接使用参数初始化一个类。

其中参数查看文档得知:

  • filename:文件路径
  • threshold:图像识别阈值,是用来判定一张图片识别是否成功的阈值,例如一张图片识别到的匹配度是0.65,而我们设置的threshold为0.7的话,Airtest会认为匹配失败,从而进行下一次匹配。
  • target_pos:图像点击位置,当识别出一张图像后,Airtest将会默认去点击图像的正中心位置,有时我们希望它识别出图片后点击其他位置,可以通过修改target_pos属性来实现。
  • rgb:切换彩色与灰度识别,在识别图像时,Airtest会先将图像转为灰度图再进行识别。因此假如有两个按钮,形状内容相同,只有颜色不同的情况下,Airtest将认为它们都是相同内容。
    通过勾选rgb选项,或在代码中加入rgb=True,可以强制指定使用彩色图像进行识别。
    其中参数,还差 record_pos 与 resolution;以下为Template类,查看文档得知:
  • resolution:录制时的屏幕分辨率
  • record_pos:录制时屏幕上的坐标
class Template(object):"""picture as touch/swipe/wait/exists target and extra info for cv matchfilename: pic filenametarget_pos: ret which pos in the picrecord_pos: pos in screen when recordingresolution: screen resolution when recordingrgb: 识别结果是否使用rgb三通道进行校验."""def __init__(self, filename, threshold=None, target_pos=TargetPos.MID, record_pos=None, resolution=(), rgb=False):self.filename = filenameself._filepath = Noneself.threshold = threshold or ST.THRESHOLDself.target_pos = target_posself.record_pos = record_posself.resolution = resolutionself.rgb = rgb@propertydef filepath(self):if self._filepath:return self._filepathfor dirname in G.BASEDIR:filepath = os.path.join(dirname, self.filename)if os.path.isfile(filepath):self._filepath = filepathreturn self._filepathreturn self.filenamedef __repr__(self):filepath = self.filepath if PY3 else self.filepath.encode(sys.getfilesystemencoding())return "Template(%s)" % filepathdef match_in(self, screen):match_result = self._cv_match(screen)G.LOGGING.debug("match result: %s", match_result)if not match_result:return Nonefocus_pos = TargetPos().getXY(match_result, self.target_pos)return focus_posdef match_all_in(self, screen):image = self._imread()image = self._resize_image(image, screen, ST.RESIZE_METHOD)return self._find_all_template(image, screen)@logwrapdef _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 = Nonefor method in ST.CVSTRATEGY:# get function definition and execute:func = MATCHING_METHODS.get(method, None)if func is None:raise InvalidMatchingMethodError("Undefined method in CVSTRATEGY: '%s', try 'kaze'/'brisk'/'akaze'/'orb'/'surf'/'sift'/'brief' instead." % method)else:ret = self._try_match(func, image, screen, threshold=self.threshold, rgb=self.rgb)if ret:breakreturn ret@staticmethoddef _try_match(func, *args, **kwargs):G.LOGGING.debug("try match with %s" % func.__name__)try:ret = func(*args, **kwargs).find_best_result()except aircv.NoModuleError as err:G.LOGGING.debug("'surf'/'sift'/'brief' is in opencv-contrib module. You can use 'tpl'/'kaze'/'brisk'/'akaze'/'orb' in CVSTRATEGY, or reinstall opencv with the contrib module.")return Noneexcept aircv.BaseError as err:G.LOGGING.debug(repr(err))return Noneelse:return retdef _imread(self):return aircv.imread(self.filepath)def _find_all_template(self, image, screen):return TemplateMatching(image, screen, threshold=self.threshold, rgb=self.rgb).find_all_results()def _find_keypoint_result_in_predict_area(self, func, image, screen):if not self.record_pos:return None# calc predict area in screenimage_wh, screen_resolution = aircv.get_resolution(image), aircv.get_resolution(screen)xmin, ymin, xmax, ymax = Predictor.get_predict_area(self.record_pos, image_wh, self.resolution, screen_resolution)# crop predict image from screenpredict_area = aircv.crop_image(screen, (xmin, ymin, xmax, ymax))if not predict_area.any():return None# keypoint matching in predicted area:ret_in_area = func(image, predict_area, threshold=self.threshold, rgb=self.rgb)# calc cv ret if foundif not ret_in_area:return Noneret = deepcopy(ret_in_area)if "rectangle" in ret:for idx, item in enumerate(ret["rectangle"]):ret["rectangle"][idx] = (item[0] + xmin, item[1] + ymin)ret["result"] = (ret_in_area["result"][0] + xmin, ret_in_area["result"][1] + ymin)return retdef _resize_image(self, image, screen, resize_method):"""模板匹配中,将输入的截图适配成 等待模板匹配的截图."""# 未记录录制分辨率,跳过if not self.resolution:return imagescreen_resolution = aircv.get_resolution(screen)# 如果分辨率一致,则不需要进行im_search的适配:if tuple(self.resolution) == tuple(screen_resolution) or resize_method is None:return imageif isinstance(resize_method, types.MethodType):resize_method = resize_method.__func__# 分辨率不一致则进行适配,默认使用cocos_min_strategy:h, w = image.shape[:2]w_re, h_re = resize_method(w, h, self.resolution, screen_resolution)# 确保w_re和h_re > 0, 至少有1个像素:w_re, h_re = max(1, w_re), max(1, h_re)# 调试代码: 输出调试信息.G.LOGGING.debug("resize: (%s, %s)->(%s, %s), resolution: %s=>%s" % (w, h, w_re, h_re, self.resolution, screen_resolution))# 进行图片缩放:image = cv2.resize(image, (w_re, h_re))return image

通过以上分析的值,在代码:

touch(Template(r"tpl1587733818550.png", record_pos=(-0.217, 0.565), resolution=(540, 960)))

其中 resolution 为当前设备的分辨率为:540, 960;可是这和我设置的分辨率不一样,查看文档得知:“在使用不同分辨率的设备进行图像识别时,可能会导致识别成功率不佳,因此Airtest提供了默认的分辨率适配规则”。从中也得到了些许信息,如“使用缩放后是否不精确?”,当然,文档也给出了解决方案:“想要提高2d游戏的识别精度,最好的办法就是明确指定你的游戏的分辨率适配规则;下面的代码指定了一个自定义的缩放规则:直接return原来的值,不管屏幕分辨率,所有UI都不进行缩放。”,
代码如下:

from airtest.core.api import *def custom_resize_method(w, h, sch_resolution, src_resolution):return int(w), int(h)
# 替换默认的RESIZE_METHOD
ST.RESIZE_METHOD = custom_resize_method

这里的RESIZE_METHOD,即我们定义的custom_resize_method使用的输入参数为:

  • w, h # 录制下来的UI图片的宽高
  • sch_resolution # 录制时的屏幕分辨率
  • src_resolution # 回放时的屏幕分辨率

以上分析得知,通过Template示例后一个对象,作为参数传给touch方法,那么touch方法应该进行剩下的图片查找及触摸操作;继续分析touch方法。

以下在文档中找到touch方法:
在这里插入图片描述
文档中说明,touch方法为在设备屏幕上执行触摸操作。参数有:

  • 一个目标,这个目标可以是 Template 的实例或者是一个坐标;
  • 执行多少次点击
  • 按照平台的不同所需的不同参数
  • 最终返回位点击的坐标
  • 适用平台为 Android, 、Windows 、iOS

点击源代码查看实现:

@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 = vfor _ in range(times):G.DEVICE.touch(pos, **kwargs)time.sleep(0.05)delay_after_operation()return pos

经过之前的分析,得知 touch 将会执行查找图片和点击的操作;从实现中得知:
传入参数后,首先判断传入的对象 v 是否属于 Template对象,是这个对象,执行 loop_find方法,传入对象,设置超时为 ST.FIND_TIMEOUT,然后把查找得到的坐标给予 pos 变量。

之后使用循环实现点击,循环1次点击1次,循环2次点击2次,以此类推,调用G.DEVICE.touch 方法,传入 pos 及 kwargs 进行点击。
查看 G 类具体实现,G在helper中,查看helper:

import time
import sys
import os
import six
import traceback
from airtest.core.settings import Settings as ST
from airtest.utils.logwraper import Logwrap, AirtestLogger
from airtest.utils.logger import get_loggerclass G(object):"""Represent the globals variables"""BASEDIR = []LOGGER = AirtestLogger(None)LOGGING = get_logger("airtest.core.api")SCREEN = NoneDEVICE = NoneDEVICE_LIST = []RECENT_CAPTURE = NoneRECENT_CAPTURE_PATH = NoneCUSTOM_DEVICES = {}@classmethoddef add_device(cls, dev):"""Add device instance in G and set as current device.Examples:G.add_device(Android())Args:dev: device to initReturns:None"""cls.DEVICE = devcls.DEVICE_LIST.append(dev)@classmethoddef register_custom_device(cls, device_cls):cls.CUSTOM_DEVICES[device_cls.__name__.lower()] = device_cls"""
helper functions
"""def set_logdir(dirpath):"""set log dir for logfile and screenshots.Args:dirpath: directory to save logfile and screenshotsReturns:"""if not os.path.exists(dirpath):os.mkdir(dirpath)ST.LOG_DIR = dirpathG.LOGGER.set_logfile(os.path.join(ST.LOG_DIR, ST.LOG_FILE))def log(arg, trace=""):"""Insert user log, will be displayed in Html report.:param data: log message or Exception:param trace: log traceback if exists, use traceback.format_exc to get best format:return: None"""if G.LOGGER:if isinstance(arg, Exception):G.LOGGER.log("info", {"name": arg.__class__.__name__,"traceback": ''.join(traceback.format_exception(type(arg), arg, arg.__traceback__))})elif isinstance(arg, six.string_types):G.LOGGER.log("info", {"name": arg, "traceback": trace}, 0)else:raise TypeError("arg must be Exception or string")def logwrap(f):return Logwrap(f, G.LOGGER)def device_platform(device=None):if not device:device = G.DEVICEreturn device.__class__.__name__def using(path):if not os.path.isabs(path):abspath = os.path.join(ST.PROJECT_ROOT, path)if os.path.exists(abspath):path = abspathG.LOGGING.debug("using path: %s", path)if path not in sys.path:sys.path.append(path)G.BASEDIR.append(path)def import_device_cls(platform):"""lazy import device class"""platform = platform.lower()if platform in G.CUSTOM_DEVICES:cls = G.CUSTOM_DEVICES[platform]elif platform == "android":from airtest.core.android.android import Android as clselif platform == "windows":from airtest.core.win.win import Windows as clselif platform == "ios":from airtest.core.ios.ios import IOS as clselif platform == "linux":from airtest.core.linux.linux import Linux as clselse:raise RuntimeError("Unknown platform: %s" % platform)return clsdef delay_after_operation():time.sleep(ST.OPDELAY)

其实在这里,已经注册过了设备,默认的编辑窗口已经隐藏了这个过程,我们点击新建文件可以看到 auto_steup(),该方法实现在 airtest.core.api 中,其中auto_steup()方法定义如下:

def auto_setup(basedir=None, devices=None, logdir=None, project_root=None):"""Auto setup running env and try connect android device if not device connected."""if devices:for dev in devices:connect_device(dev)elif not G.DEVICE_LIST:try:connect_device("Android:///")except IndexError:passif basedir:if os.path.isfile(basedir):basedir = os.path.dirname(basedir)if basedir not in G.BASEDIR:G.BASEDIR.append(basedir)if logdir:set_logdir(logdir)if project_root:ST.PROJECT_ROOT = project_rootdef connect_device(uri):"""Initialize device with uri, and set as current device.:param uri: an URI where to connect to device, e.g. `android://adbhost:adbport/serialno?param=value&param2=value2`:return: device instance:Example:* ``android:///`` # local adb device using default params* ``android://adbhost:adbport/1234566?cap_method=javacap&touch_method=adb``  # remote device using custom params* ``windows:///`` # local Windows application* ``ios:///`` # iOS device"""d = urlparse(uri)platform = d.schemehost = d.netlocuuid = d.path.lstrip("/")params = dict(parse_qsl(d.query))if host:params["host"] = host.split(":")dev = init_device(platform, uuid, **params)return devdef init_device(platform="Android", uuid=None, **kwargs):"""Initialize device if not yet, and set as current device.:param platform: Android, IOS or Windows:param uuid: uuid for target device, e.g. serialno for Android, handle for Windows, uuid for iOS:param kwargs: Optional platform specific keyword args, e.g. `cap_method=JAVACAP` for Android:return: device instance"""cls = import_device_cls(platform)dev = cls(uuid, **kwargs)for index, instance in enumerate(G.DEVICE_LIST):if dev.uuid == instance.uuid:G.LOGGING.warn("Device:%s updated %s -> %s" % (dev.uuid, instance, dev))G.DEVICE_LIST[index] = devbreakelse:G.add_device(dev)return dev

其中所需的 import_device_cls 方法在 airtest.core.helper中:

def import_device_cls(platform):"""lazy import device class"""platform = platform.lower()if platform in G.CUSTOM_DEVICES:cls = G.CUSTOM_DEVICES[platform]elif platform == "android":from airtest.core.android.android import Android as clselif platform == "windows":from airtest.core.win.win import Windows as clselif platform == "ios":from airtest.core.ios.ios import IOS as clselif platform == "linux":from airtest.core.linux.linux import Linux as clselse:raise RuntimeError("Unknown platform: %s" % platform)return cls

很清楚的看到,在 auto_setup 中有层级的调用了connect_device进行设备连接初始化,在connect_device中调用import_device_cls添加设备,随后使新设备在G类中赋值给G.DEVICE,最后传给G.DEVICE_LIST。

在这里出现了 DEVICE_LIST 给对多设备操作的方式有了可能性,当然 Airtest Project 本就是这么一个解决方案。在文档中就有多机协作的介绍。以下文字引于文档。

在我们的脚本中,支持通过 set_current
接口来切换当前连接的手机,因此我们一个脚本中,是能够调用多台手机,编写出一些复杂的多机交互脚本的。

在命令行运行脚本时,只需要将手机依次使用 --device Android:/// 添加到命令行中即可,例如:

>airtest run untitled.air --device Android:///serialno1 --device Android:///serialno2 --device Android:///serialno1

当然多设备并行的方案现在也有很多之后补充。

最终,调用 airtest.core.android.android 中 touch 完成点击:
在这里插入图片描述
实现如下:

def touch(self, pos, duration=0.01):"""Perform touch event on the deviceArgs:pos: coordinates (x, y)duration: how long to touch the screenReturns:None"""if self.touch_method == TOUCH_METHOD.MINITOUCH:pos = self._touch_point_by_orientation(pos)self.minitouch.touch(pos, duration=duration)elif self.touch_method == TOUCH_METHOD.MAXTOUCH:pos = self._touch_point_by_orientation(pos)self.maxtouch.touch(pos, duration=duration)else:self.adb.touch(pos)

以上就是简单的一个 touch 完成的所实现的过程。

脚本再尝试

我们现在就来尝试开启颜色识别以及阀值设置:
增加 if 判断,判断是否存在图片,存在则点击,并且提高阀值以及开启颜色识别:

双击图片进行更改值:
在这里插入图片描述

去代码查看是否改动

# -*- encoding=utf8 -*-
__author__ = "Administrator"from airtest.core.api import *if exists(Template(r"tpl1587750838857.png", threshold=0.8, rgb=True, record_pos=(-0.213, 0.57), resolution=(540, 960))):touch(Template(r"tpl1587750838857.png", threshold=0.8, rgb=True, record_pos=(-0.213, 0.57), resolution=(540, 960)))

最后优化一下,根据流程编写了如下脚本:
在这里插入图片描述
其中程序代码为:

# -*- encoding=utf8 -*-
__author__ = "Administrator"from airtest.core.api import *if exists(Template(r"tpl1587750838857.png", threshold=0.8, rgb=True, record_pos=(-0.213, 0.57), resolution=(540, 960))):if touch(Template(r"tpl1587750838857.png", threshold=0.8, rgb=True, record_pos=(-0.213, 0.57), resolution=(540, 960))):if touch(Template(r"tpl1587751404697.png", record_pos=(0.004, 0.18), resolution=(540, 960))):if touch(Template(r"tpl1587751451726.png", rgb=True, record_pos=(0.174, 0.204), resolution=(540, 960))):if touch(Template(r"tpl1587751472685.png", threshold=0.8, rgb=True, record_pos=(0.222, 0.794), resolution=(540, 960))):sleep(1)

运行结果如下:
在这里插入图片描述
以上脚本使用了 exists 断言,判断图片是否存在,存在返回 pos 坐标点,不存在返回False:
在这里插入图片描述
使用 exist 判断可以当做为脚本逻辑的一个分支,存在,则执行之后的操作,不存在。在使用 exist 时使用if,在同级下,多个if可以有效的让所有情况出现不交叉的分支,使脚本代码结构清晰是个不错的选择!

以上脚本还存在一个小尾巴,那就是在结尾处点击训练后,自动返回主目录。修改如下:
在这里插入图片描述

为了时脚本保持健壮性,我在点击训练意外情况找不到时,用了else语句,使其返回。
为了更好的深入理解脚本,我们查看一下 exists 的实现;exists 的实现在 airtest.core.api 中:

@logwrap
def exists(v):"""Check whether given target exists on device screen:param v: target to be checked:return: False if target is not found, otherwise returns the coordinates of the target:platforms: Android, Windows, iOS"""try:pos = loop_find(v, timeout=ST.FIND_TIMEOUT_TMP)except TargetNotFoundError:return Falseelse:return pos

exists 将会在屏幕中查找目标,如果找到将会返回坐标值。
在这里我们已经是第二次看见 loop_find 方法,此方法是 Airtest 的核心方法。loop_find 方法实现于 airtest.core.cv :

@logwrap
def loop_find(query, timeout=ST.FIND_TIMEOUT, threshold=None, interval=0.5, intervalfunc=None):"""Search for image template in the screen until timeoutArgs:query: image template to be found in screenshottimeout: time interval how long to look for the image templatethreshold: default is Noneinterval: sleep interval before next attempt to find the image templateintervalfunc: function that is executed after unsuccessful attempt to find the image templateRaises:TargetNotFoundError: when image template is not found in screenshotReturns:TargetNotFoundError if image template not found, otherwise returns the position where the image template hasbeen found in screenshot"""G.LOGGING.info("Try finding:\n%s", query)start_time = time.time()while True:screen = G.DEVICE.snapshot(filename=None, quality=ST.SNAPSHOT_QUALITY)if screen is None:G.LOGGING.warning("Screen is None, may be locked")else:if threshold:query.threshold = thresholdmatch_pos = query.match_in(screen)if match_pos:try_log_screen(screen)return match_posif 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)

文档 对于 loop_find 有些接口介绍:
在这里插入图片描述
query:截图对象
timeout:超时
threshold:阀值,也就是对比后的相似度的值,越大越难匹配,要求精度越高
interval:匹配相距时间
intervalfunc:失败后的响应

在执行 loop_find 时首先给个计时器计时,获取屏幕后验证屏幕是否为None,为None可能没连接上;屏幕获取无异常则,使用截图对象调用 match_in 方法,成功进行匹配返回坐标值,否则返回False。

其中主要方法为 match_in ,match_in 也在 airtest.core.cv : 中,定义如下:

def match_in(self, screen):match_result = self._cv_match(screen)G.LOGGING.debug("match result: %s", match_result)if not match_result:return Nonefocus_pos = TargetPos().getXY(match_result, self.target_pos)return focus_pos

_cv_match 如下:

 @logwrapdef _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 = Nonefor method in ST.CVSTRATEGY:# get function definition and execute:func = MATCHING_METHODS.get(method, None)if func is None:raise InvalidMatchingMethodError("Undefined method in CVSTRATEGY: '%s', try 'kaze'/'brisk'/'akaze'/'orb'/'surf'/'sift'/'brief' instead." % method)else:ret = self._try_match(func, image, screen, threshold=self.threshold, rgb=self.rgb)if ret:breakreturn ret

其中在 match_in 中调用了 _cv_match,_cv_match 首先对图片 _imread() 进行CV2 处理,然后后面对图片进行压缩,应该是通过文档中所说的通过COSCOS的规则。然后根据一个策略遍历里面的算法进行计算,最后得到 ret 返回计算结果。(在深度就不会了,毕竟我不是搞测试的,点到为止)

Airtest 的核心浅显流程搞清楚了,我们得知,在进行 touch 及 exists 时都会进行 loop_find 合理的调整查询阀值和RGB开启可以有效的节省匹配时间。优化脚本,合理的把部分图片的RGB关闭以及部分图片阀值减小以提升脚本运行效率。
修改后脚本如下:(修改了2个按钮,降低了阀值及关闭了RGB)

# -*- encoding=utf8 -*-
__author__ = "Administrator"
from airtest.core.api import *
if exists(Template(r"tpl1587750838857.png", threshold=0.8, rgb=True, record_pos=(-0.213, 0.57), resolution=(540, 960))):if touch(Template(r"tpl1587750838857.png", threshold=0.8, rgb=True, record_pos=(-0.213, 0.57), resolution=(540, 960))):if touch(Template(r"tpl1587751404697.png", record_pos=(0.004, 0.18), resolution=(540, 960))):if touch(Template(r"tpl1587751451726.png", threshold=0.75, rgb=True, record_pos=(0.174, 0.204), resolution=(540, 960))):if touch(Template(r"tpl1587751472685.png", threshold=0.7, rgb=False, record_pos=(0.222, 0.794), resolution=(540, 960))):touch(Template(r"tpl1587752119032.png", threshold=0.7, rgb=False, record_pos=(-0.441, -0.776), resolution=(540, 960)))else:touch(Template(r"tpl1587752119032.png", threshold=0.7, rgb=False, record_pos=(-0.441, -0.776), resolution=(540, 960)))

运行结果(没有加倍数,确实快了很多):
在这里插入图片描述
使用循环让程序一直挂机吧!
修改程序如下:

在这里插入图片描述
结果发现在程序正常运行后,逻辑出现错误,运行结果如下:
在这里插入图片描述
这时需要修改程序,把头盔的判断增加else分支,改为:
在这里插入图片描述
结果再次运行发现训练过后,第二次训练时间变成等待时间:
在这里插入图片描述
改为先判断后点击,头盔也是:
在这里插入图片描述

这次就很完美了:
在这里插入图片描述
那我再按照游戏左下角提示操作去完成另外的逻辑,一共2个分支在运行判断:
在这里插入图片描述
运行如下:在这里插入图片描述
可能某些情况需要拖拽屏幕,这个使用需要使用:
在这里插入图片描述
使用 swipe 推荐对于坐标系不熟的使用录制脚本功能编写,通过这个功能,可以快速的写好脚本:
在这里插入图片描述
点击后,进入录制脚本状态,这个时候直接在屏幕上进行拖拽即可,记得幅度不要过大,不然在运行时导致滑动过多。

断开连接点击设备窗右上角。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/288171.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机组成原理xchg,8088数据传送指令-计算机组成原理与汇编语言-电子发烧友网站...

3.2.1 数据传送指令1. MOVOPRD1,OPRD2MOV是操作码,OPRD1和OPRD2分别是目的操作数和源操作数。该指令可把一个字节或一个字操作数从源地址传送到目的地址。源操作数可以是累加器、寄存器、存贮器以及立即操作数,而目的操作数可以是累加器、寄存器和存贮器…

Facebook用户遭遇千年虫:瞬间有了46年老友

一些Facebook用户今天碰到了一个有趣的bug,系统提示他们与好友有了46年长的好友关系。要知道,Facebook还不到15岁,很多用户也没有46岁的高龄。Facebook一直没有透露产生这个bug的原因,但它说已经意识到该问题。“我们已经确定了这…

(上)python3 selenium3 从框架实现代码学习selenium让你事半功倍

本文感谢以下文档或说明提供的参考。 Selenium-Python中文文档 Selenium Documentation Webdriver 参考 如有错误欢迎在评论区指出,作者将即时更改。 环境说明 操作系统:Windows7 SP1 64python 版本:3.7.7浏览器:谷歌浏览器浏览…

.NET 7 中的 HostApplicationBuilder

.NET 7 Preview 3 引入的 HostApplicationBuilderIntro在 .NET 6 中,ASP.NET Core 引入了 Minimal API,对于简单的应用使用 Minimal API 我们可以使用非常精简的代码来实现我们的 API,在 .NET 7 Preview 3 中,引入了一个 HostAppl…

SQL Server 2008 R2 Developer Edition图文安装教程

本文主要以截图的方式,详细说明SQL Server 2008 R2 Developer Edition的安装过程。 1、双击安装包中的setup.exe,如下图,点击“安装”→“全新安装或现有安装添加功能”。 2、点击“确定”。 3、点击“下一步”。

新款ATM恶意软件Alice 可对抗动态分析 但目前需要物理接触主机

趋势科技(Trend Micro)安全公司的研究人员警告称,新发现的恶意软件家族主要针对ATM机(自动取款机),唯一目的就是要掏空ATM机保险箱里的现金。 alice软件是什么 这款恶意软件被称为“Alice”,是迄…

(下)python3 selenium3 从框架实现代码学习selenium让你事半功倍

上一篇博文简要 在上一篇博文中已得知:使用 execute 向远程服务器发送请求会通过 webdriver 与浏览器交互,且发送已定义的命令常量可获得一些相关信息。 其中 execute 方法实现已经在上一篇博文中有实现说明。并且在我们已经知道 webdriver基类&#x…

【空间数据库技术】ArcSDE 10.1安装配置与企业级地理空间数据库的建立及连接

1、工具: (1)ArcGIS Desktop 10.1 (2)SQL Server 2008 R2 (3)ArcSDE 10.1 2、安装过程 (1)ArcGIS Desktop 10.1的安装 请参照:ArcGIS 10.2 Desktop完全破解图文安装教程 (2)SQL Server 2008 R2的安装 请参照:SQL Server 2008 R2 Developer Edition图文安装教…

点对点 客户端-服务器 聊天程序

服务器&#xff0c;客户端都是父进程、子进程分别负责发送、接收字符串。 另外使用了信号函数来发送和接收处理信号&#xff0c;比如当子进程结束时&#xff0c;传递一个信号给父进程&#xff0c;然后父进程会执行信号处理函数。 服务器端&#xff1a; 1 #include<stdio.h&g…

C# 使用压缩流和 Brotli

如前所述&#xff0c;流的一个特性是可以将它们链接起来。为了压缩流&#xff0c;只需要创建 DeflateStream&#xff0c;并给构造函数传递另一个流(在这个例子中&#xff0c;是写入文件的outputStream)&#xff0c;使用 CompressionMode. Compress 表示压缩。使用 Write方法或其…

高考610考哪个计算机学校,2019年高考成绩610分_615分左右能报考上什么学校 文科理科大学名单推荐...

一眨眼&#xff0c;高考已经结束一段时间了&#xff1b;早晨猛地一睁眼&#xff0c;还在抱怨妈妈怎么不叫你起床&#xff0c;才突然发现今天你已经不用去学校了,这场全国性的考试高考已经结束了&#xff1b;高考这个城门攻破之后&#xff0c;还有大把壁垒再等你去攻克&#xff…

Power的力量

ZD至顶网服务器频道 08月26日 新闻消息&#xff08;文/董培欣&#xff09;&#xff1a;谈到企业级服务器市场&#xff0c;人们首先想到的会是x86 E5、E7系列的CPU产品&#xff0c;IBM在企业级市场推出的Power Systems服务器产品很少会被用户了解。可是在今年春天举行的OpenPOWE…

C#语法糖系列 —— 第三篇:聊聊闭包的底层玩法

有朋友好奇为什么将 闭包 归于语法糖&#xff0c;这里简单声明下&#xff0c;C# 中的所有闭包最终都会归结于 类 和 方法&#xff0c;为什么这么说&#xff0c;因为 C# 的基因就已经决定了&#xff0c;如果大家了解 CLR 的话应该知道&#xff0c; C#中的类最终都会用 MethodTab…

空间数据库Spatial Tools的使用

工具下载:http://www.sharpgis.net/page/SQL-Server-2008-Spatial-Tools 该工具为绿色版,点击即可使用。 1、导入Shapefile数据 双击“Shape2Sql.exe”,打开界面如下: 2、查询空间数据 双击打开“SqlSpatial.exe”

自定义View 进度条

1.在values下面新建一个attrs.xml&#xff0c;现在里面定义我们的自定义属性&#xff0c; <?xml version"1.0" encoding"utf-8"?> <resources><declare-styleable name"RoundProgressBar"><attr name"roundColor&qu…

python图形绘制库turtle中文开发文档及示例大全【最详细、连结果图都有,gif格式的!】

前言 本文参考&#xff1a;Python库官方文档 本文例子摘抄自Python库官方文档&#xff0c;为了方便讲解&#xff0c;个人进行了修改&#xff0c;并且相关函数说明不完全参照文档&#xff0c;在结果处贴出了执行结果&#xff0c;不方便用jpg等图片作为展示的用了gif格式图片进行…

oracle 事务_从Oracle到PG-PostgreSQL的MVCC机制简介

作者&#xff1a;甘植恳-AkenPostgreSQL和Oracle、MySQL等RDBMS一样&#xff0c;都有自己的并发控制机制。而并发控制的目的是为了在多个事务同时运行时保持事务ACID属性。MVCC即Multi-version concurrence control首字母缩写&#xff0c;MVCC会为每个数据更改操作创建数据块或…

【Microstation】不能从对话框中装载/创建类型为 ‘HTML‘,id =41510001 的对话框条目,该对话框为: “文本编辑器 - 字处理器“,GCSDIALOG 已装载。

在Win7上安装MicroStation V8i简体中文版,在添加文字图层的时候,点击出现提示“不能从对话框中装载/创建类型为 HTML,id =41510001 的对话框条目,该对话框为: "文本编辑器 - 字处理器",GCSDIALOG 已装载。”,问题出在Win7对该软件的兼容性上。 MS软件提供了三种…

fastdfs 一个group内实现按照不同的项目,指定路径存储.

为什么80%的码农都做不了架构师&#xff1f;>>> 环境介绍: 1: 公司目前有5个项目 A B C D E 日后可能会有所增加. 2: 使用fastdfs存储这5各项目的文件,要求各各项目的文件分开存储,也就是每个项目的文件存储到一个固定的位置. 3: 三台机器ip地址分配如下 tracker…

一个WPF开发的打印对话框-PrintDialogX

今天五月一号&#xff0c;大家玩的开心哦。1. 介绍今天介绍一个WPF开发的打印对话框开源项目-PrintDialogX[1]&#xff0c;该开源项目由《WPF开源项目&#xff1a;AIStudio.Wpf.AClient》[2]作者推荐。欢迎使用 PrintDialogX, 这是一个开源项目。免费用于商业用途。用于 C# 的自…