写在前面
自从敏捷迭代开始以后,公司的发版速度也是越来越敏捷了,每个迭代的时间都是很短的。我经常被沙沙同学催,就差你一个了快去过包,被元元问新架构的包好了没。痛定思痛决定用UI自动化的方式把checklist这个事简化掉。
目前市面上各种自动化工具框架有很多。找到适合的不太容易。其中Appium 和 AirTest 都是针对 APP 的自动化测试工具,都可以进行自动话测试脚本的录制和回放。但是之所以选择了 AirTest 最主要的原因是他能更方便的生成测试脚本,即使测试人员不会编程,不懂脚本,也可以通过正常用户的点击拖拽等操作,自动完成脚本的录制,从而大幅度降低自动化维护成本。(ps:主要是减少了大量的编写脚本的时间)
下面大概介绍一下Airtest的架构和一些基础功能:
AirtestIDE是一个跨平台的UI自动化测试编辑器,适用于游戏和App。它的特点如下:
- 脚本、一键输入、报告查看,轻而易举实现自动化测试流程支持
- 基于图像识别的Airtest框架,适用于所有Android和Windows游戏支持
- 基于UI控件搜索的Poco框架,适用于Unity3d,Cocos2d与Android App
- 能够运行在 Windows 和 MacOS 上。
架构图
通过Airtest 架构图我们可以看到,底层的主要测试框架是AirTest 和 Poco,二者区别在于:
- AirTest:基于 Python 、跨平台的 UI 自动化测试框架,基于图像识别原理,适用于游戏和 APP。
- Poco:基于 UI 控件搜索的自动化测试框架,核心优势是除了对 Android 和 IOS 之外,对游戏也支持,同时支持微信小程序、微信小游戏和 H5 应用
- PS:poco 脚本的可移植性更高,更方便的转为纯py脚本
Airtest的安装和使用
AirtestIDE中内置了python3.6.5、Airtest和poco环境可以很方便的开始工作,AirTestIDE了Windows和Mac两个版本的客户端,通过官网提供安装下载。安装方式在此不再赘述,属于解压即用非常方便。附官网网址:https://airtest.netease.com/changelog.html
前面已经讲到Airtest支持Android和iOS的设备连接,我们以Android设备连接为例:
- 首先打开设备USB调试功能(设置-开发者选项-USB调试开关)
- 点击刷新设备
- 点击连接连接(如果第一次使用可能要信任电脑)
- 连接成功后,屏幕截图在 AirTestIDE 中手机屏幕的镜像
- 如果连接虚拟机的话,可以使用远程设备连接的方式(点击后刷新或重启adb)
- 如果要连接iOS手机,你需要准备好Xcode的MAC电脑文档,请查看
- 虚拟机的不同端口:
序号 | 模拟器 | 端口号 | 备选连接参数 | 常见问题 |
1 | 雷电 | 5554 | 无 | 刷出多台端口号为5554的设备 |
2 | mumu | 7555 | 无 | 暂未发现 |
3 | 夜神 | 62001 | use javacap | 不勾选javacap会黑屏、卡死 |
4 | 逍遥 | 21503 | use javacap、use ADB orientation | 不勾选会报AdbShellError |
5 | 天天 | 6555 | use javacap | 不勾选javacap会黑屏 |
接口介绍
Airtest是通过模拟人为点击的方式实现UI自动化的,下面是常用的一些接口:
- Touch 点击某个位置,可以设定被点击的位置、次数、按住时长参数
- Swipe 从一个位置滑动到另一个位置
- Text 调用输入法输入指定内容
- KeyEvent 输入某个按键响应,回车键、删除键
- Wait 等待某个指定的图片元素出现
#点击坐标点两次
touch((100, 100), times=2)
#根据坐标滑动
swipe((100, 100), (200, 200))
#对处于激活状态的文本框输入
text("qimao")
#返回上一级
keyevent("HOME")
#等待目标图片
wait(Template("qimao.png"))
- 如果您要查看更详细的信息,请点击这里
了解一些关于Airtest的基本知识后我们看下最方便的脚本录制是怎么用的:
点击AirTestIDE左侧的AirTest辅助窗口上的“录制”按钮,然后通过鼠标操作可视化可以进行脚本录制。
emmmm...是不是很简单,我们看下自动生成的代码:
touch(Template(r"tpl1638152918708.png", record_pos=(0.406, 1.044), resolution=(1080, 2400)))
因为Airtest 生成的脚本都是基于图片识别实现的,兼容性不是太好,可移植性也不高,所以建议大家可以使用poco 语句进行脚本写。当然也是可以录制的:Poco辅助框选择Android,点击上的录制图标,然后通过鼠标操作进行脚本录制生成Poco操作代码。
开始前会提示插入初始化poco代码通知
Poco mode has changed. Do you want to insert poco init code at the current cursor position?
点击yes会在当前脚本内插入如下代码:
from poco.drivers.android.uiautomation import AndroidUiautomationPoco
poco = AndroidUiautomationPoco(use_airtest_input=True, screenshot_each_action=False)
PS : poco 初始化语句必须在poco语句之前进行。
另外我们也可以通过双击左侧poco辅助工具的UI树的对应元素生成代码
#点击我的-头像
poco("com.kmxs.reader:id/user_avatar").click()
#点击neme=七猫免费小说
poco(text = "七猫免费小说").click()
前面有说到poco的脚本可移植性更高,所以下面的脚本主要以poco为主进行介绍:
poco同样支持各种点击操作:选中控件点击、坐标点击、长按等等。
PS:poco使用的坐标系是相对坐标系,所以使用poco的坐标点击时,X、Y的坐标值的范围都在0~1之内,否则会报Click position out of screen
的错误。
# 控件点击
poco("btn_start").click()
# 坐标点击
poco.click([0.14,0.2])
# 普通点击、长按
poco("star_single").click()
poco('star_single').long_click()
要对一个文本框进行输入的话 我们可以用poco的set_text()
方法或者setattr()
方法:
# 设置属性
poco("pos_input").set_text("123")
poco("pos_input").setattr('text',"456")
# 错误示范
poco("pos_input").setattr('type',"456")
poco中等待UI元素的方式有三种:等待一个元素,等待多个元素,等待任一元素
#定位三个元素
a = poco(text= "立即投票")
b = poco(text= "七猫免费小说")
c = poco(text= "我知道了")
等待一个元素出现:a.wait_for_appearance(timeout=10)
等待多个元素都出现:poco.wait_for_all([a,b,c])
等待任一元素出现:poco.wait_for_any([a,b,c])
a = poco(text = "立即免费阅读")
a.wait_for_appearance(timeout=10)
判断元素是否存在,我们可以使用poco(XXX).exists()
:
poco("com.kmxs.reader:id/gift_icon").exists()
其他一些API不在这里进行赘述了,想了解更多可以点击这里
用于实践
在开始前我们可以先做好初始化的准备,可以通过调用adb命令的方式来对我们的APP进行安装卸载,清除数据等初始化操作:
# 卸载应用:uninstall_app()
uninstall("org.cocos2dx.javascript")
#停止运行
stop_app("com.kmxs.reader")
# 启动应用:start_app()
start_app("com.kmxs.reader")
# 清除应用数据:clear_app()
clear_app("com.kmxs.reader")
目前我主要负责的是用户相关的工作,所以就用poco脚本写了启动APP,登录账号,查看个人主页信息及写书评,打赏,投票功能的主流程验证,代码如下:
# -*- encoding=utf8 -*-
__author__ = "zhangbaohua"
__title__ = "七猫免费小说"
__desc__ = """
1.启动七猫并登录
2.查看个人主页及写书评
3.打赏及投票
4.完整运行上述脚本并统计运行时长
"""
from airtest.core.api import *
from airtest.cli.parser import cli_setup
import time,datetime
if not cli_setup():
auto_setup(__file__, logdir=True, devices=["android://127.0.0.1:5037/emulator-5554?cap_method=MINICAP&&ori_method=MINICAPORI&&touch_method=MINITOUCH",])
#poco 初始化语句
from poco.drivers.android.uiautomation import AndroidUiautomationPoco
poco = AndroidUiautomationPoco(use_airtest_input=True, screenshot_each_action=False)
# script content
#打开七猫免费小说并登录
def start_qimao_app():
print("start")
keyevent("home")
clear_app("com.kmxs.reader")
#点击启动七猫APP
poco(text = "七猫免费小说").click()
sleep(2)
#点击同意隐私协议
poco("com.kmxs.reader:id/submit").click()
#等待权限申请框展示
sleep(5)
#点击授权
poco(text = "允许").click()
sleep(2)
#选择阅读喜好
poco(text = "随便看看").click()
#等待青少年弹窗
while not exists(Template(r"tpl1637839441705.png", record_pos=(-0.009, -0.269), resolution=(1080, 2400))):
sleep(1)
poco(text = "知道了").click()
i = exists(Template(r"tpl1638697950793.png", record_pos=(-0.004, 0.81), resolution=(1440, 2560)))
if i :
print("pass")
else:
print("fail")
#点击进入我的页面
poco(text = "我的").click()
sleep(5)
poco(text="点击登录").click()
#点击输入框
poco(text = "请输入手机号").click()
#调用adb 输入手机号
shell("input text '18738898028'")
poco(text = "获取验证码").click()
sleep(1)
poco(text = "同意").click()
sleep(2)
#调用adb 输入验证码
shell("input text '4321'") #输入验证码
#判读是否登录成功
a = poco(text = "污浊常态")
if a :
print("pass")
else:
print("fail")
#查看个人主页及写书评
def user_page():
#点击头像进入个人主页
poco("com.kmxs.reader:id/user_avatar").click()
#判读是否展示我的书评
a = poco(text = "我的书评")
if a :
poco(text="剑来")
else :
print("个人主页打开失败")
#点击评论进入评论详情
poco("com.kmxs.reader:id/ttv_reward_message_content").click()
sleep(2)
poco("com.kmxs.reader:id/ttv_reply_view").click
#判读评论规范弹窗
i = poco(text="请遵守七猫社区规范哦")
if i :
poco("com.kmxs.reader:id/got_it").click
else:
print("fail")
#调用adb 输入评论内容
shell("input text 'good'")
poco("com.kmxs.reader:id/ll_reply_like").click
#返回查看评论
keyevent("back")
sleep(2)
#如果找不到全部书评入口就继续滑动
while not exists(Template(r"tpl1638706704530.png", record_pos=(-0.002, 0.609), resolution=(1440, 2560))):
swipe([680, 1800],[680, 1400])
poco("com.kmxs.reader:id/total_layout")
# 断言进入全部书评页
value = poco(text = "污浊常态的书评")
assert_equal(value, "污浊常态的书评", "打开全部书评页")
keyevent("back")
#打赏及投票
def reward_tickt():
poco(text ="剑来").click()
a = poco(text = "立即免费阅读")
a.wait_for_appearance(timeout=10)
poco(text = "立即免费阅读").click()
#点击进入阅读器
poco("com.kmxs.reader:id/edit_reply_content").click()
poco("com.kmxs.reader:id/read_title_left_arrow").click()
#判断打赏入口是否存在
b = poco (text = "打赏")
if b :
poco (text = "打赏").click()
sleep(2)
#选择打赏礼物
poco(text = "金币助力").click()
poco(text = "立即打赏").click()
#等待确认打赏弹窗
while not exists(Template(r"tpl1638710218224.png", record_pos=(0.012, 0.801), resolution=(1440, 2560))):
sleep(2)
poco("com.kmxs.reader:id/do_pay").click()
b = poco("com.kmxs.reader:id/gift_icon")
if b :
print("打赏成功")
else :
print("打赏失败")
#点击关闭打赏弹窗
poco(text = "知道了").click()
poco("com.kmxs.reader:id/read_title_left_arrow").click()
poco("com.kmxs.reader:id/btn_progress_right").click
#循环点击下一章
for i in range(2):
poco(text = "下一章").click
poco("com.kmxs.reader:id/edit_reply_content").click()
#滑动翻页找到投票入口
poco("com.kmxs.reader:id/edit_reply_content").swipe([0.4565, -0.013])
#点击投票入口
poco(text = "投票 (99+)").click()
#点击立即投票
poco(text= "立即投票").click()
#等待投票弹窗出现
ticket = poco(text="知道了")
ticket.wait_for_appearance(timeout=10)
if ticket :
poco(text = "知道了").click() #关闭投票弹窗
else:
print("fail")
#停止运行
stop_app("com.kmxs.reader")
#返回主页
keyevent("home")
for i in range(1):
startTime = datetime.datetime.now()
try:
start_qimao_app()
user_page()
reward_tickt()
except:
print("任务执行失败")
endTime = datetime.datetime.now()
#统计运行总用时,或者在报告中查看
print("----------------此次用时:"+str((endTime-startTime).seconds)+"s-----------------------")
脚本运行结束后我们可以通过查看Air test生成的测试报告来判断脚本执行结果:
遇到的坑
- 一些需要连续点击的地方会因为调用pocoservice过慢而导致超时失败
- text 输入在OPPO机型上面无法输入
- 真机输入失败的处理:
- 如果text()接口输入失败,请查看手机是否阻止了yosemite.apk(Airtest自带输入法)的安装及运行,该apk安装成功以后,手机上会出现对应的图标。
- PS:当Airtest断开设备的连接之后,设备当前的输入法还是yosemite.apk,我们找到手机设置-更多设置-与输入法(不同型号手机修改输入法的位置可能有一些空间),然后将当前输入法改成你平时使用的输入法。
- 部分手机兼容性问题也会导致text()输入失败,具体可以查看官网教程中“Android连接FAQ”小结的内容。另外我们也可以尝试尝试yosemite.apk输入法设置为手机默认输入法,然后再进行text()接口的调用,可以部分避免输入失败的问题。
- 有部分特殊型号的手机,可能在使用Yosemite输入法时容易失败,无法输入文字(OPPO与Vivo品牌更容易出现),没有输入中文的需求,可以尝试使用adb shell输入指令来进行文字输入:
shell("input text 'hello world'")
4.pocoservice 频繁重启
- 未设置允许自动启动、允许后台运行
这是“最最最”常见的1个原因,(PS:我就遇到了这个看似很简单的问题,所以着重说下)。在许多品牌手机上,我们都需要检查系统设置中,是否有 电池优化相关 或者 后台活跃相关 的选项,并将 PocoService.apk 设为允许自动启动、允许后台运行,否则就非常容易出现poco无限重启的问题。
这里列举几个常见手机品牌的设置方式:
- OPPO:设置-电池-应用耗电管理-PocoService.apk-允许应用自启动,允许完全后台行为
- VIVO:电池-后台高耗电-> PocoService 开启
- 华为:手机管家-应用启动管理-PocoService.apk-手动管理,允许自启动开启,允许后台活动开启
- 一加:设置-电池优化-PocoService-不优化
当然,不同手机品牌,甚至同品牌不同型号手机的配置方式,都有可能不大一样,同学们要自己查找手机里面与 电池优化 和 后台活跃 相关的设置即可,保证给 pocoservice.apk 足够的活跃权限且不被电池优化行为干掉。
- 电脑或者手机上设置了网络代理
网络代理会影响 pocoservice.apk 的启动,所以我们务必关闭PC或手机上连接的网络代理Proxy
- 与uiautomator同时启动
pocoservice.apk 不能和 uiautomator 同时启动,否则会相互冲突
- Android版本过低
Poco支持 Android SDK API ≥ 19,即Android 4.4及以上,如果Android版本过低,可能影响 pocoservice.apk 的正常运行。
参考文章:
1. https://my.oschina.net/u/4594743/blog/4459706