Airtest是由网易游戏推出的UI自动化测试解决方案,是一个跨平台的、基于图像识别的UI自动化测试框架,适用于游戏和App,支持平台有Windows、Android和iOS。
AirtestIDE对Python进行了封装,通过Airtest、Poco两个框架对应用元素进行定位,可以使用python的语法编写脚本。
- 基于图像识别的Airtest框架,适用于所有Android/iOS/Windows应用
- 基于UI控件搜索的Poco框架,适用于Unity3d,Cocos2d与Android/iOS App等多种平台
除了UIAutomator2外,还有Appium、AirTest等App自动测试框架。
自动化框架 | 支持平台 | 语言 | 特点 |
---|---|---|---|
Appium | Android、iOS、Windows | Java、Python、Ruby等 | 跨平台,支持多种编程语言,是APP UI自动化领域的标杆产品 |
AirTest | Android、iOS、Windows | Python | 基于图像识别和UI控件检索技术,适用于游戏和应用测试,上手简单,通过图像识别技术定位UI元素,无需嵌入代码即可进行自动化测试 |
UIAutomator2 | Android | Python | API设计简洁易用,适用于较简单的原生Android应用,支持跨app操作,但无法使用录制功能,适用于对性能要求不高、操作简单的测试场景 |
本文是App测试自动化系列工具之UIAutomator2的使用。
注:由于Poco需要安装App对应的框架SDK(如Android、iOS、Unity、UE4、Qt等),所以虽然Poco写的脚本可以适应不同分辨率的终端,但本文只涉及Airtest图像识别框架的使用。
1. 安装AirtestIDE
AirtestIDE是绿色软件,无需安装,从官网下载页面下载AirtestIDE,解压后在AirtestIDE下找到AirtestIDE.exe,双击打开即可。
2. 安装python
AirtestIDE内置了Python环境,并安装了Airtest、Poco、airtest-selenium等库。如果在AirtestIDE内编辑、运行脚本,不需要额外安装环境。如果要部署本地的Python环境来跑脚本,首先要准备一个合适的Python环境,大于Python3,小于等于Python3.9均可。然后在Python环境里面,像安装其它Python第三方库一样,安装我们的自动化测试框架:
# 安装Airtest框架
pip install airtest
# 安装Poco框架;编写了Poco语句就需要安装
pip install pocoui
# 安装airtest-selenium框架;编写了airtest-selenium语句就需要安装
pip install airtest-selenium
更多运行环境配置请参考官方文档。
3. Airtest项目文件结构
Airtest项目是一个以.air
结尾的文件夹,其中的文件包括一个与文件夹同名(不含.air
的部分)的.py
脚本,以及用于图像匹配的图片若干。
一个典型的Airtest项目的目录结构如下:
├─AttackDarkArmyTeam.air # 项目文件夹(必须以`.air`结尾)
│ AttackDarkArmyTeam.py # 与项目文件夹同名的.py脚本(该脚本必须存在)
│ tpl1721443079684.png # 用于图像匹配的图片1(可选)
│ tpl1721443091889.png # 用于图像匹配的图片2(可选)
│ tpl1721443110665.png # 用于图像匹配的图片3(可选)
│ ... # 用于图像匹配的图片n(可选)
4. Airtest常用API
AirtestIDE左边的Airtest辅助窗有常用的API,点击对应的API,做出相应动作,即可生成代码。常用API及功能说明如下:
API | 类型 | 功能说明 |
---|---|---|
touch | 操作 | 点击某个位置,可以设定被点击的位置、次数、按住时长等参数 |
wait | 操作 | 等待某个指定的图片元素出现 |
swipe | 操作 | 从一个位置滑动到另外一个位置 |
exists | 操作 | 判断是否存在图片元素 |
snapshot | 操作 | 对当前画面截图 |
text | 辅助 | 输入文本 |
keyevent | 辅助 | 输入某个按键响应,例如回车键、删除键 |
sleep | 辅助 | 等待,就是python中的time.sleep() |
assert_exists | 断言 | 断言存在 |
assert_not_exists | 断言 | 断言不存在 |
assert_equal | 断言 | 断言相等 |
assert_not_equal | 断言 | 断言不相等 |
5. 使用AirtestIDE编写代码
- 左上角,文件 -> 新建脚本 -> .air Airtest项目,选择项目文件保存地址;
- 右侧“设备窗”可以连接Android或iOS设备,也可以选择Windows窗口;
- 点击左侧Airtest辅助窗,按提示操作即可;
- 第3步会在“脚本编辑窗”生成代码,并且可以预览用于图像匹配的图片,图片被保存在脚本同级目录;
- 根据需要在“脚本编辑窗”编辑代码,封装函数等,完成脚本编写。
- 脚本编写完成后,在“脚本编辑窗”左上角的脚本文件名上右键,或点击“脚本编辑窗”右上角“+”号旁边的展开按钮,可以清理未使用的多余图片,压缩当前脚本。
注:使用Airtest图像匹配时,如果App窗口有缩放,可能导致图像匹配不上。
6. 代码样例
以某个小游戏的某种玩法为例,以下是.py脚本的代码,Template读取用于图像匹配的图片:
# -*- encoding=utf8 -*-
from airtest.core.api import *
auto_setup(__file__)
# 增加体力
def addPhysicalStrength():
# 如果存在大体力,就用一次大体力
if exists(Template(r"tpl1721444184608.png", record_pos=(0.003, 0.232), resolution=(449, 842))):
# 点击选中大体力
touch(Template(r"tpl1721444200238.png", record_pos=(-0.006, 0.078), resolution=(449, 842)))
# 点击使用
touch(Template(r"tpl1721443818854.png", record_pos=(0.001, 0.37), resolution=(449, 842)))
# 如果没有大体力,那就看有没有小体力
elif exists(Template(r"tpl1721444225899.png", record_pos=(-0.253, 0.232), resolution=(449, 842))):
# 点击选中小体力
touch(Template(r"tpl1721444239134.png", record_pos=(-0.255, 0.076), resolution=(449, 842)))
# 点击使用
touch(Template(r"tpl1721443818854.png", record_pos=(0.001, 0.37), resolution=(449, 842)))
# 使用体力后,关闭窗口
touch(Template(r"tpl1721467021987.png", record_pos=(0.415, -0.29), resolution=(449, 842)))
# 判断是否还有队列
def isNoMoreQueue():
# 是否弹出了队列不足提示框
if exists(Template(r"tpl1721444758059.png", record_pos=(0.008, -0.428), resolution=(449, 842))):
# 关闭队列不足提示框
touch(Template(r"tpl1721467021987.png", record_pos=(0.415, -0.29), resolution=(449, 842)))
# 队列不足
return True
# 没有队列不足
return False
# 寻找黑暗军团
def findDarkArmy(isPlus):
# 当前界面是否存在黑暗军团
if exists(Template(r"tpl1721446075178.png", record_pos=(-0.01, 0.029), resolution=(449, 842))):
# 如果存在,不需要再找了
return
# 点击搜索按钮
touch(Template(r"tpl1721443079684.png", record_pos=(-0.316, 1.362), resolution=(293, 842)))
# 选择搜索类型
touch(Template(r"tpl1721472679705.png", record_pos=(-0.36, -0.038), resolution=(449, 842)))
# isPlus用来控制搜索敌军的等级
if isPlus:
# 搜索的敌军等级+1
touch(Template(r"tpl1721450183747.png", record_pos=(0.273, 0.673), resolution=(449, 842)))
else:
# 搜索的敌军等级-1
touch(Template(r"tpl1721450172927.png", record_pos=(-0.268, 0.67), resolution=(449, 842)))
# 点击搜索
touch(Template(r"tpl1721443091889.png", record_pos=(0.278, 1.222), resolution=(293, 842)))
# 攻击
def attack(isPlus):
# 寻找黑暗军团
findDarkArmy(isPlus)
# 点击选中黑暗军团
touch(Template(r"tpl1721446075178.png", record_pos=(-0.01, 0.029), resolution=(449, 842)))
# 点击攻击五次
touch(Template(r"tpl1721443110665.png", record_pos=(0.067, -0.29), resolution=(293, 842)))
# 如果顺利进入攻击页面
if exists(Template(r"tpl1721443138054.png", record_pos=(0.023, -0.131), resolution=(449, 842))):
# 点击一键上阵
touch(Template(r"tpl1721449556405.png", record_pos=(0.406, 0.557), resolution=(449, 842)))
# 点击出征
touch(Template(r"tpl1721443138054.png", record_pos=(0.023, -0.131), resolution=(449, 842)))
# 返回攻击结果
return True
# 返回攻击结果
return False
# 黑暗五连
def attackDarkArmyTeam(isPlus):
# 如果出征成功
if attack(isPlus):
# 本次出击完成,返回success
return 'success'
# 如果缺少体力
if exists(Template(r"tpl1721443799439.png", record_pos=(0.003, -0.29), resolution=(449, 842))):
# 恢复体力
addPhysicalStrength()
# 攻击
attack()
# 如果队列不够
if isNoMoreQueue():
# 返回wait,等待队列归队
return 'wait'
if __name__ == '__main__':
isPlus = True
# 出兵成功后等待时间
attackWaitInSecs = 50
# 队列不足时的等待时间
noQueueWaitInSecs = 10
while True:
try:
ret = attackDarkArmyTeam(isPlus)
if ret == 'wait':
print("==========>has no more queue. wait " + str(noQueueWaitInSecs) + "s")
sleep(noQueueWaitInSecs)
else:
print("==========>success. wait " + str(attackWaitInSecs) + "s")
sleep(attackWaitInSecs)
isPlus = not isPlus
# 捕获异常,增强程序健壮性
except Exception:
# 如果有弹窗,关闭弹窗恢复到初始状态,尝试3次
repeatCnt = 3
while repeatCnt > 0:
repeatCnt -= 1
# 如果存在弹窗关闭按钮
if exists(Template(r"tpl1721467021987.png", record_pos=(0.415, -0.29), resolution=(449, 842))):
# 点击弹窗关闭按钮
touch(Template(r"tpl1721467021987.png", record_pos=(0.415, -0.29), resolution=(449, 842)))