最近在学习 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))
如果正常执行断言方法,失败的话后面的用例会被中断,所以得加上异常兼容;
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)
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')
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
或扫描二维码关注,公众号也同步文章。