前言

最近在学习 UI 自动化,关于框架的挑选对我来说没啥好挑的,就 airtest 了,图像识别和接入 poco-SDK,公司项目还没有接好 SDK,拿网易的提供的 demo 先来练练手。


安装

我习惯使用 python 来跑自动化,有些库得安装下
1、airtest

pip install airtest

2、UI 自动化框架:poco

pip install pocoui

3、测试框架:pocounit

pip install pocounit

4、Androiddemo下载链接:

先上官网的例子

一、父类

from poco.drivers.unity3d import UnityPoco
from pocounit.case import PocoTestCase
from pocounit.addons.poco.action_tracking import ActionTracker

class MyBaseTestCase(PocoTestCase):
    @classmethod
    def setUpClass(cls):
        super(MyBaseTestCase, cls).setUpClass()
        cls.poco = UnityPoco()

        # 启用动作捕捉(action tracker)
        action_tracker = ActionTracker(cls.poco)
        cls.register_addon(action_tracker)

二、测试用例 (继承父类)

# 一个文件里建议就只有一个TestCase
# 一个Case做的事情尽量简单,不要把一大串操作都放到一起
class MyTestCase(MyBaseTestCase):
    def setUp(self):
        # 可以调用一些前置条件指令和预处理指令
        pass

    # 函数名就是这个,用其他名字无效
    def runTest(self):
        '''self.assertEqual相关的断言方法不会收集在测试报告中(网易那边不支持)
           要用self.call_airtest.airtest_api.assert_equal相关方法才会收集在测试报告中
            '''
        # 普通语句跟原来一样
        # self.poco(text='角色').click()

        # 断言语句跟python unittest写法一模一样
        # self.assertTrue(self.poco(text='最大生命').wait(3).exists(), "看到了最大生命")

        # self.poco('btn_close').click()
        # self.poco('movetouch_panel').offspring('point_img').swipe('up')

        start_btn = self.poco('btn_start')
        start_btn.click()
        time.sleep(0.5)
        self.assertEqual(start_btn.get_name(),'btn_start',msg='测试一下断言报告')
        try:
            self.assertEqual(start_btn.get_name(),'btn_start1',msg='测试一下断言报告 1')
        except :pass
        self.try_assert_mothod(self.assertEqual,start_btn.get_name(), 'btn_start1','测试一下断言报告 2')
        basic_btn = self.poco(text='basic')
        basic_btn.click([0.5,0.5])

        self.try_assert_mothod(self.assertEqual,basic_btn.get_name(),'abc','测试一下断言报告 3')

        back_button= self.poco('btn_back',type='Button').focus([1,0.1])
        back_button.click()
        if self.poco(text='drag drop').exists():
            self.poco(text='drag drop').click()
        else:
            back_button.click()

    def tearDown(self):
        # 如果没有清场操作,这个函数就不用写出来
        pass

    # 不要写以test开头的函数,除非你知道会发生什么
    # def test_xxx():
    #     pass

if __name__ in '__main__':
    pocounit.main()
    # generate html report
    time_str = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
    simple_report(__file__,output=REPORTPATH/f'test_{time_str}.html',logpath=str(LOGPATH))

实践 poco+ 图像识别

一、封装断言方法

如果正常执行断言方法,失败的话后面的用例会被中断,所以得加上异常兼容;
1、poco 的封装

def poco_assert_mothod(self,func,*args,case_msg=None):
    '''
    :param func: 断言方法
    :param args: 断言方法里需要的参数
    :return: 返回断言结果
    '''
    try:
        if case_msg:
            return func(*args,msg=case_msg)
        else:
            return func(*args)
    except Exception as e:
        print(f'{func} ERROR:', e)
        return False

2、airtest 图像识别的封装

def img_assert_mothod(self,func=None,img_1=None,img_2=None,case_msg='空白说明',threshold=0.8,target_pos=False,rgb=False):
    '''
    :param func: 断言方法,用加括号
    :param args: 断言方法里需要的参数
    :return: 返回断言结果
    '''
    '''
    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:色彩识别
            示例:assert_not_exists(self.img_template(img), msg)
            '''
    def img_template(img):
        if not target_pos:
            return Template(self.img_path + img, threshold=threshold, resolution=self.screen_size, rgb=rgb)
        else:
            return Template(self.img_path + img, threshold=threshold, target_pos=target_pos, resolution=self.screen_size, rgb=rgb)
    try:
        if case_msg and img_1 and img_2:
            return func(img_template(img_1),img_template(img_2),case_msg)
        elif case_msg and (img_1 or img_2):
            if img_1:
                return func(img_template(img_1),case_msg)
            elif img_2:
                return func(img_template(img_2),case_msg)
        elif case_msg is False:
            return func(img_template(img_1))
    except Exception as e:
        print(f'{func} ERROR:', e)

二、给父类添加 ADB 的方法

class MyBaseTestCase(PocoTestCase):
    @classmethod
    def setUpClass(cls):
        super(MyBaseTestCase, cls).setUpClass()
        cls.poco = UnityPoco()

        # 启用动作捕捉(action tracker)
        action_tracker = ActionTracker(cls.poco)
        cls.register_addon(action_tracker)
            self.img_ath = None
        # AdbShell在之前的的文章里可以找到,封装常用的adb命令
        self.adb = AdbShell()
        self.task_id = None
        if self.check_devices():
            self.screen_size = self.adb.get_screen_size()
            # demo游戏包名
            self.app_name = 'com.NetEase'
            self.app = self.adb.get_thirdparty_app(filter=self.app_name)
        else:raise ConnectionError
        # 获取操作系统
        self.system = os.name

    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')

三、poco 常用方法

def __doc__(self):
    '''选择UI对象'''
    self.poco('')
    # select by node name
    self.poco('bg_mission')
    # select by name and other properties
    self.poco('bg_mission', type='Button')
    self.poco(textMatches='^据点.*$', type='Button', enable=True) #加上对应属性来定位
    # select by direct child/offspring
    self.poco('main_node').child('list_item').offspring('item')
    # 顺序选择
    items = self.poco('main_node').child('list_item').offspring('item')
    print(items[0].child('material_name').get_text())
    # 迭代遍历一组UI
    items = self.poco('main_node').child('list_item').offspring('item')
    for item in items:
        item.child('icn_item')

    '''读取属性'''
    mission_btn = self.poco('bg_mission')
    print(mission_btn.attr('type'))  # 'Button'
    print(mission_btn.get_text())  # '获取text'
    print(mission_btn.attr('text'))  # equivalent to .get_text()
    print(mission_btn.exists())  # True/False, exists in the screen or not

    '''操作UI对象'''
    # 点击
    self.poco('bg_mission').click()
    self.poco('bg_mission').click('center')
    self.poco('bg_mission').click([0.5, 0.5])  # equivalent to center
    self.poco('bg_mission').focus([0.5, 0.5]).click()  # equivalent to above expression
    self.poco()
    # 滑动
    joystick = self.poco('movetouch_panel').child('point_img')
    joystick.swipe('up')
    joystick.swipe([0.2, -0.2])  # swipe sqrt(0.08) unit distance at 45 degree angle up-and-right
    joystick.swipe([0.2, -0.2], duration=0.5)
    # 拖拽
    self.poco(text='突破芯片').drag_to(self.poco(text='岩石司康饼'))
    # 等待
    self.poco('bg_mission').wait(5).click()  # wait 5 seconds at most,click once the object appears
    self.poco('bg_mission').wait(5).exists()  # wait 5 seconds at most,return Exists or Not Exists
    self.poco().wait_for_appearance() # 等待目标出现,超时raises PocoTargetTimeout
    self.poco().wait_for_disappearance() # 等待目标消失,超时raises PocoTargetTimeout
    # self.poco.wait_for_any()
    # self.poco.wait_for_all()

    '''全局操作'''
    # 点击
    self.poco.click([0.5, 0.5])  # click the center of screen
    self.poco.long_click([0.5, 0.5], duration=3)
    # swipe from A to B
    point_a = [0.1, 0.1]
    center = [0.5, 0.5]
    self.poco.swipe(point_a, center)
    # 滑动
    direction = [0.1, 0]
    self.poco.swipe(point_a, direction=direction)
    # 截屏
    from base64 import b64decode
    b64img, fmt = self.poco.snapshot(width=720)
    open('screen.{}'.format(fmt), 'wb').write(b64decode(b64img))

四、商店测试用例

SDK 还没有接好,目前是用图像识别的方式写个 demo

class TestShop(MyBaseTestCase):
    '''
    用例设计参考思维导图的测试用例
    '''
    def setUp(self):
        self.call_airtest.img_path = Path(__file__).parent / 'imgFolder'

    def close_tips(self):
        tips_1 = self.call_airtest.img_assert_mothod(exists,'goods_tips_close_btn.png',case_msg=False)
        tips_2 = self.call_airtest.img_assert_mothod(exists,'rank_close_btn.png',target_pos=3,case_msg=False)
        if tips_1:
            touch(tips_1)
        if tips_2:
            touch(tips_2)
    def runTest(self):
        self.close_tips()
        '''用例执行方法在这里'''
        self.entrance('shop_icon_1.png', 'shop_icon_2.png', 'shop_homepage.png')
        self.shop_type('yuanbao_shop.png', 'duihuan_shop.png','jipin_shop.png','jifen_shop.png')
        self.title('shop_tag_1.png','shop_tag_2.png','shop_tag_3.png')
        # 购买多个商品,图片坐标偏移8
        shop_goods_list = [
            ('银两','yinliang.png','yinliang_max.png',5),
            ('福袋碎片','fudai.png','fudai_max.png',1),
            ('5级宝石', 'baoshi5.png', 'baoshi5_max.png', 5),
            ('兽丹礼包', 'shoudan.png', 'shoudan_max.png', 5),
        ]
        for test_data in shop_goods_list:
            self.gold_daily_shop(*test_data)

    def tearDown(self):
        # 如果没有清场操作,这个函数就不用写出来
        pass

    # 判断入口
    def entrance(self,enter_img_1=None,enter_img_2=None,shop_homepage=None):
        shop_icon = self.call_airtest.img_assert_mothod(assert_exists,enter_img_1,case_msg='显示商店图标')
        if shop_icon:
            touch(shop_icon)
            sleep(1.5)
            self.call_airtest.img_assert_mothod(assert_exists,shop_homepage,case_msg='进入后出现商品页签')
        else:
            wait_shop_icon = wait(Template(self.call_airtest.img_path+enter_img_2),5)
            if wait_shop_icon:
                touch(wait_shop_icon)
                self.call_airtest.img_assert_mothod(assert_exists,shop_homepage,case_msg='进入后出现商品主页')
            else:
                raise TargetNotFoundError

    def shop_type(self,type_1,type_2,type_3,type_4):
        '''各个商城'''
        self.shop_type_1 = self.call_airtest.img_assert_mothod(assert_exists,type_1,case_msg='显示元宝商城')
        self.shop_type_2 = self.call_airtest.img_assert_mothod(assert_exists,type_2,case_msg='显示兑换商城')
        self.shop_type_3 = self.call_airtest.img_assert_mothod(assert_exists,type_3,case_msg='显示极品商城')
        self.shop_type_4 = self.call_airtest.img_assert_mothod(assert_exists,type_4,case_msg='显示积分商城')

    def title(self,shop_tag_1,shop_tag_2,shop_tag_3):
        '''页签'''
        self.shop_tag_1 = self.call_airtest.img_assert_mothod(assert_exists,shop_tag_1,case_msg='显示每日特惠页签')
        self.shop_tag_2 = self.call_airtest.img_assert_mothod(assert_exists,shop_tag_2,case_msg='显示常用道具页签')
        self.shop_tag_3 = self.call_airtest.img_assert_mothod(assert_exists,shop_tag_3,case_msg='显示特权商城页签')

    def gold_daily_shop(self,goods,goods_img,goods_max_img,buy_max):
        '''商店购买'''
        # 点击元宝商城
        if self.shop_type_1:
            touch(self.shop_type_1)
            # 点击每日特惠
            if self.shop_tag_1:
                touch(self.shop_tag_1)
        else:
            print('入口目标没有找到,调整截图或降低识别率')
            raise TargetNotFoundError
        # self.call_airtest.img_assert_mothod(assert_exists,cost_img,case_msg=f'{goods}道具价格')
        # 商品
        self.goods_pos = self.call_airtest.img_assert_mothod(assert_exists,goods_img,target_pos=8,case_msg=f'显示{goods}道具')
        # swipe(self.goods_pos,vector=(0.5,0.8))
        if self.goods_pos:
            # 限购
            for i in range(buy_max):
                touch(self.goods_pos)
            # 滑动到底部
            # 滑动一次接近翻一页
            self.swipe_up_pos = (520,1910),(520,607)
            swipe(*self.swipe_up_pos)
            swipe(*self.swipe_up_pos)
            sleep(1)
            self.goods_max_pos = self.call_airtest.img_assert_mothod(assert_exists,goods_max_img,case_msg=f'{goods}购买{buy_max}次后,达到上限灰化')
            # 划回去
            self.swipe_down_pos = (520,607),(520,1910)
            swipe(*self.swipe_down_pos)
            swipe(*self.swipe_down_pos)
            sleep(1)


结语

UI 自动化这块我接触的少,经验也不足,属于摸索起步阶段,据了解目前多数同行都应用于回归测试,向他们请教怎么应用,后续有好的应用方法再更新上来,有好的建议欢迎留言,感谢。

最后的最后,各位的关注、点赞、收藏是对我最大的支持,谢谢大家!
小伙伴可以关注微信公众号 ID:gameTesterGz
或扫描二维码关注,公众号也同步文章。
微信二维码


↙↙↙阅读原文可查看相关链接,并与作者交流