Airtest-poco脚本在checklist中的实践

写在前面

自从敏捷迭代开始以后,公司的发版速度也是越来越敏捷了,每个迭代的时间都是很短的。我经常被沙沙同学催,就差你一个了快去过包,被元元问新架构的包好了没。痛定思痛决定用UI自动化的方式把checklist这个事简化掉。

目前市面上各种自动化工具框架有很多。找到适合的不太容易。其中Appium 和 AirTest 都是针对 APP 的自动化测试工具,都可以进行自动话测试脚本的录制和回放。但是之所以选择了 AirTest 最主要的原因是他能更方便的生成测试脚本,即使测试人员不会编程,不懂脚本,也可以通过正常用户的点击拖拽等操作,自动完成脚本的录制,从而大幅度降低自动化维护成本。(ps:主要是减少了大量的编写脚本的时间)

类别
安装
语言
平台
难度
图像识别
开源
Airtest
简单
python
Android、iOS
windows、微信小程序
支持
开源
Appium
复杂
pyhton、java
Ruby、js等
Android、iOS
windows、微信小程序
不支持
开源

下面大概介绍一下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设备连接为例:

  1. 首先打开设备USB调试功能(设置-开发者选项-USB调试开关)
  2. 点击刷新设备
  3. 点击连接连接(如果第一次使用可能要信任电脑)
  4. 连接成功后,屏幕截图在 AirTestIDE 中手机屏幕的镜像
  1. 如果连接虚拟机的话,可以使用远程设备连接的方式(点击后刷新或重启adb)
  2. 如果要连接iOS手机,你需要准备好Xcode的MAC电脑文档,请查看
  3. 虚拟机的不同端口:
序号
模拟器端口号备选连接参数
常见问题
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生成的测试报告来判断脚本执行结果:

遇到的坑

  1. 一些需要连续点击的地方会因为调用pocoservice过慢而导致超时失败
  2. text 输入在OPPO机型上面无法输入
  3. 真机输入失败的处理:
  • 如果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

2. https://zhuanlan.zhihu.com/p/350567689

3. http://testerhome.com/articles/31674

展示评论