airtest UI自动化测试
前言
目前游戏自动化测试这块,据我了解的信息,挺多同行都采用了网易游戏的airtest这个测试框架,容易入门,只需要了解下Python的基础语法,加上还有airtest的IDE,很快就能上手,我这里呢就不重复写官网上的例子了,主要说说脱离了airtest IDE用Python来写UI自动化测试用例,可能写的也比较粗浅,仅供参考。
环境
python3安装
安装airtest库
pip install -U airtest
注意:在Mac/Linux系统下,需要手动赋予adb可执行权限,否则可能在执行脚本时遇到 Permission denied 的报错:
# mac系统
> cd {your_python_path}/site-packages/airtest/core/android/static/adb/mac
# linux系统
> cd {your_python_path}/site-packages/airtest/core/android/static/adb/linux
> chmod +x adb
更多安装问题解决参考官网
正文
消费线的测试用例
我这里举例个ARPG游戏里的一个坐骑功能
1.需要导入我自定义的模块utils,我对airtest的断言方法进行了封装还有adb命令的封装,下文再一一介绍,先说用例本身
'''
@File : testZuoQi.py
@Contact : 512759438@qq.com
'''
from autoTest.utils import *
from airtest.cli.parser import cli_setup
if not cli_setup():
auto_setup(__file__, logdir=LOGPATH, devices=["Android:///",])
- auto_setup的参数,我这里默认连接安卓手机
def auto_setup(basedir=None, devices=None, logdir=None, project_root=None, compress=0):
- 官方示范:auto_setup(file,logdir=True,devices=[“Android://127.0.0.1:5037/emulator-5554”])
2.初始化
- 示例的触控是基于图像识别,并没有接入pocoSDK,接入SDK的伙伴要替换相应的触控方法
- 写的测试用例意思意思看看就好
class TestZuoQi(WrapAirtest):
def __init__(self):
super(TestZuoQi, self).__init__()
self.png_path = '/xxx/autoTest/airTestPng/zuoqi/'
self.switch_up = 'up_button.png'
self.switch_down = 'down_button.png'
self.model_lock = 'model_lock.png'
self.pick_on = 'pick_on.png'
self.pick_off = 'pick_off.png'
- WrapAirtest:在utils导入的模块
- 定义图片路径和一些公用控件
3.功能入口
def entrance(self,en_1=None,en_2=None,en_3=None):
if self.exists_png(en_2) is False:
pos = self.wait_png(en_1,10)
if pos is not False:
self.touch_pos(*pos)
else:raise TargetNotFoundError
cz = self.wait_png(en_2,10)
if cz is not False:
self.touch_pos(*cz)
self.assert_exists(en_3,'显示坐骑按钮')
self.touch_png(en_3)
- 有3个层级的入口所以有en_1.2.3
4.页面标题
def title(self,title_1,title_2):
self.assert_exists(title_1,'标题名字显示正确')
self.assert_exists(title_2,'页签名字显示正确')
5.tips说明
def tips(self,tips_button,ensure_button,describe):
"""
tips说明
:param tips_button: 问号按钮
:param ensure_button: 确定按钮
:param describe: 说明tips
"""
self.assert_exists(tips_button,'显示tips按钮')
self.touch_png(tips_button)
self.assert_exists(describe,'描述显示正确')
self.touch_png(ensure_button)
self.assert_not_exists(ensure_button,'点击确定后tips关闭')
6.激活按钮
def activate(self,activate_button):
"""
激活
:param activate_button: 激活按钮
"""
self.assert_exists(activate_button,'显示激活图标')
self.touch_png(activate_button)
self.assert_not_exists(activate_button,'激活后激活图标消失')
7.属性详情页对比
def attr_equal(self,init_attr,attr_1,attr_2):
self.assert_exists(init_attr,'初始化属性是否为零')
self.assert_equal(attr_1,attr_2,'升级后属性是否相等')
8.资源模型
def model(self,*model_png:list) -> bool:
"""
模型
:param model_png: 10个已解锁的模型
"""
if len(model_png) == 10:
self.assert_exists(model_png[0],'1阶坐骑')
self.touch_png(self.switch_up)
for i in range(1,len(model_png)):
if self.assert_exists(model_png[i],f'{i+1}阶坐骑'):
self.touch_png(self.switch_up)
else:self.assert_exists(self.model_lock,f'{i+1}阶坐骑没有激活-锁住图标')
return True
else:return False
9.自动购买选项
# 选中状态,默认要去选中
def pick_status(self,switch:bool=True) -> bool:
if switch is True:
if self.exists_png(self.pick_on):
return True
else:
self.touch_png(self.pick_off)
self.assert_exists(self.pick_on,'勾选自动购买成功')
return True
elif switch is False:
if self.exists_png(self.pick_off):
return False
else:
self.touch_png(self.pick_on)
self.assert_not_exists(self.pick_off, '取消勾选自动购买')
return False
10.升级
def upgrade(self,upgrade_button,upgrade_loop_button,fail_tips,get_item,star_2,star_max,lvl:list,fight:list):
"""
:param upgrade_button: 升星按钮
:param upgrade_loop_button: 一键提升
:param fail_tips: 失败tips-产出路径
:param get_item: 获取链接
:param star_2: 升星一次,两颗星
:param star_max: 满星
:param lvl: 各个阶数
:param fight: 各阶的战力
:return:
"""
# 检测自动购买是否勾选
if self.pick_status(switch=False) is False:
# 点击升星
self.touch_png(upgrade_button)
self.assert_exists(fail_tips,'升星材料不足弹出提示')
# 点击其他区域关闭tips
self.touch_png(upgrade_button)
# 点击一键提升
self.touch_png(upgrade_loop_button)
self.assert_exists(fail_tips,'一键提升时材料不足弹出提示')
self.touch_png(upgrade_button)
# 点击获取连接
self.touch_png(get_item)
self.assert_exists(fail_tips,'弹出获取路径')
self.assert_exists(lvl[0],'一阶图标显示')
self.assert_exists(fight[0],'初始战力')
if self.pick_status(switch=True) is True:
# 升星一次
self.touch_png(upgrade_button)
self.assert_exists(star_2,'升星成功,出现两颗星星')
# 升到满星
self.touch_png(upgrade_button,8)
self.assert_exists(star_max,'升到满星')
# 升阶
self.touch_png(upgrade_button)
self.assert_exists(lvl[1],'1阶变成2阶')
self.assert_exists(fight[1], '2阶战力')
for i in range(2,9):
self.touch_png(upgrade_button,9)
self.assert_exists(star_max,f'{i}升到满星')
# 升阶
self.touch_png(upgrade_button)
self.assert_exists(lvl[i], f'{i}阶变成{i+1}阶')
self.assert_exists(fight[i], f'{i+1}阶战力')
11.执行用例
def test_case(self):
self.entrance('hp.png','chengzhang.png','zuoqi_button.png')
self.title('title_1.png','title_2.png')
self.tips('tips_button.png','ensure_button.png','describe.png')
self.activate('activate_button.png')
lvl_list = [f'lvl_{i}' for i in range(1,11)]
fight_list = [f'fight_{i}' for i in range(1,11)]
self.upgrade('upgrade_button.png','upgrade_loop_button.png','fail_tips.png','get_item.png',
'star_2.png','star_max.png',lvl_list,fight_list)
model_list = [f'model_{i}' for i in range(1,11)]
self.model(model_list)
return 'zuoqi end'
运行用例
simple_report:输出测试报告
具体参数:simple_report(filepath, logpath=True, logfile=LOGFILE, output=HTML_FILE,export_report=None):
if __name__ == "__main__":
go = TestZuoQi()
go.test_case()
simple_report(__file__, output='zuoqi.html')
调用WrapAirtest
设置路径
设置Windows/Linux的报告输出路径
from pathlib import Path
import os
REPORTPATH = ''
LOGPATH = ''
if os.name == 'nt':
REPORTPATH = Path('F:/FTP') / 'report'
elif os.name == 'posix':
REPORTPATH = Path(__file__).parent / 'report'
LOGPATH = REPORTPATH / 'log'
REPORTPATH.mkdir(parents=True, exist_ok=True)
封装airTest的类方法
1.初始化,调用adb封装类,检查设备是否连接
class WrapAirtest:
def __init__(self):
self.png_path = None
self.adb = AdbShell()
if self.check_devices():
self.screen_size = self.adb.getSize()
self.app_name = '测试包名或关键字'
self.app = self.adb.getPack_3(filter=self.app_name)
else:raise ConnectionError
# 获取操作系统
self.system = os.name
- adbShell:封装的ADB命令,下文有说明
2.检查安卓设备是否连接
def check_devices(self):
if self.adb.getDevices() is not False:
return True
else:
self.adb.adb('adb kill-server')
self.adb.adb('adb start-server')
- 没有连接的话重新启动adb-server
3.断言方法封装
def assert_exists(self,v, msg):
"""
为了抛出异常时继续执行
:param v: 要识别的图片
:param msg:测试用例描述
"""
try:
assert_exists(Template(self.png_path+v, resolution=self.screen_size,threshold=0.6), msg)
return True
except:
return False
def assert_not_exists(self,v, msg):
try:
assert_not_exists(Template(self.png_path+v,resolution=self.screen_size), msg)
return True
except:
return False
def touch_png(self,png,times=1):
"""
封装touch
:param png: 要点击的图片
:param times: 点击次数
Template(r"tpl1532588127987.png", record_pos=(0.779, 0.382), resolution=(407, 264), threshold=0.6, target_pos=5, rgb=False)
threshold:识别精准度
target_pos:点击坐标偏移
rgb:色彩识别
"""
for _ in range(5):
try:
pos = touch(Template(self.png_path+png, resolution=self.screen_size,threshold=0.6),times)
if pos:
return True
except:
print('This picture was not found =>', png)
return False
调用AdbShell
为了方便在Python代码里使用adb命令进行封装
调用时直接调用 AdbShell() ,用一次初始化一次,用于及时检测设备的状态更新,热拔热插
subprocess
adb命令是外部命令,Python无法直接识别,我是采用subprocess来执行
1.判断系统和隐藏子进程窗口
import os
import subprocess
if os.name == 'nt':
si = subprocess.STARTUPINFO()
si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
2.封装指令并返回结果
def adb(self,command):
return [i.decode() for i in subprocess.Popen(command,shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE,).stdout.readlines()]
封装adb命令
1.设备连接状态
def getDevices(self,host='192.168'):
_result = self.adb('adb devices')
usbDev = []
wifiDev = []
# print(_result)
for item in _result: # 筛选有线无线设备
body_filter = item.split()
if 'device' in body_filter:
if host in body_filter[0]:
self.connectStatus = True
wifiDev.append(body_filter[0])
else:
usbDev.append(body_filter[0])
if len(usbDev) > 0 or len(wifiDev) > 0:
if len(wifiDev) > 0: # 优先返回无线设备
return wifiDev[0]
else:
return usbDev[0]
else:
return False
2.获取分辨率
def getSize(self):
body = self.adb('adb -s %s shell wm size'%self.dev)
body_1 = body[0].split()[2]
body_2 = re.search('(\d+)x(\d+)',body_1)
return int(body_2.group(1)),int(body_2.group(2))
3.获取第三方包名
def getPack_3(self, filter=''):
name_list = []
pack3 = self.adb('adb shell pm list packages -3')
if filter:
for packName in pack3:
if filter in packName:
name = str(packName).split(':')
name_list.append(name[1].split()[0])
return name_list
else:return pack3
adb命令的搬运工,更多封装请参考adb命令大全自行定制。
结语
整篇写完下来,还有些地方要去优化,airTest的应用我这里经验不足,等后续有项目要用airTest时再重新优化出新的一版,欢迎评论留言,要是觉得不错,点个赞,谢谢!
最后的最后,各位的关注、点赞、收藏、碎银子打赏是对我最大的支持,谢谢大家!
需要源码的小伙伴关注微信公众号ID:gameTesterGz
或扫描二维码关注回复airtest脚本即可