python airtest UI自动化测试

前言

目前游戏自动化测试这块,据我了解的信息,挺多同行都采用了网易游戏的airtest这个测试框架,容易入门,只需要了解下Python的基础语法,加上还有airtest的IDE,很快就能上手,我这里呢就不重复写官网上的例子了,主要说说脱离了airtest IDE用Python来写UI自动化测试用例,可能写的也比较粗浅,仅供参考。

环境

python3安装

  • Python版本:3.7,官网原话:支持Python(2.7或<=3.7,3.8暂不支持),我们更推荐使用 Python3
  • Mac:下载链接
  • Windows:下载链接

安装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脚本即可
微信二维码

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

游戏测试-AJian

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值