简介
AirtestIDE 是一个跨平台的UI自动化测试编辑器,适用于游戏和App。
自动化脚本录制、一键回放、报告查看,轻而易举实现自动化测试流程
支持基于图像识别的Airtest框架,适用于所有Android/iOS/Windows应用
支持基于UI控件搜索的Poco框架,适用于Unity3d,Cocos2d与Android/iOS App等多种平台
能够运行在Windows、MacOS、Linux平台上
Airtest框架
Airtest 是一个跨平台的、 基于图像识别 的UI自动化测试框架,适用于游戏和App,支持平台有Windows、Android和iOS:
Poco框架
Poco 是一款 基于UI控件识别 的自动化测试框架,目前支持Android原生、iOS原生、Unity3D、cocos2dx、UE4和Egret等平台,也可以在其他引擎中自行接入poco-sdk来使用。
AirtestIDE
AirtestIDE 是一款跨平台的 UI自动化测试编辑器 ,内置了Airtest和Poco的相关插件功能,能够使用它快速简单地编写 Airtest 和 Poco 代码。
手机集群解决方案-DeviceFarm
DeviceFarm 是网易推出的自动化测试集群解决方案,它是软硬件一体化方案,包含设备集群建设、集群设备批量维护管理、监控报警和云端真机等功能,可以为您在企业内部搭建稳定高效的设备管理平台, 提升设备利用率,赋能自动化测试流程搭建
Airlab云测试平台
Airlab云测试平台支持用例管理、脚本管理、任务预约、任务调度、云端报告等功能,可以快速构建企业自动化测试全流程,支持ios和安卓的回归测试及兼容测试。
下载AirtestIDE
访问官网下载AirtestIDE
解压即用
使用AirtestIDE操作
脚本相关
.air 脚本
新建脚本
打开你的IDE,点击左上角的 文件–新建脚本–.air Airtest项目 ,即可新建一个.air脚本。
当你的.air脚本新建成功时,就能看到默认的初始化代码:
.air 脚本的初始化代码帮助我们从api中引入了airtest的各个接口以及自动初始化设备。实际上,.air 脚本是一个文件夹,里面存放了与 .air 同名的 .py 文件,以及相关的图片文件。在运行脚本时,实际上依然使用了python调用了里面的 .py文件,因为Airtest本质上是一个Python的第三方库。
demo01.air
# -*- encoding=utf8 -*-
__author__ = "shisuiyi"
from airtest.core.api import *
auto_setup(__file__)
需求案例:
编写一个点击启动“网易云音乐APP”的脚本,并运行查看结果,以及查看测试报告
poco脚本
Poco是一款跨平台的自动化测试框架,基于UI控件搜索原理 ,适用于Android、iOS原生和各种主流的游戏引擎应用。与基于图像识别的Airtest不同的是,Poco可以使用类似 poco(“OK”).click() 的方式来获取并操作节点。
在poco辅助框中选择相应的模式,这里选择Android,弹出是否插入poco的导入语句选择yes即可。
功能用法
poco 定位选择器
Poco控件最基本的3种定位选择器分别是:
- 基本选择器
在poco实例后加一对括号,我们就可以进行元素选择了。选择器会遍历所有元素,将满足给定条件的元素都选出来并返回。
括号里的参数就是所给定的条件,用属性名值对表示,其中第一个参数表示 节点名 ,就像 poco(“star_single”) 。后面还可以跟着一些可选参数,均表示 节点的属性及预期的属性值 :
from poco.drivers.unity3d import UnityPoco
poco = UnityPoco()
poco("star_single",type="Image")
- 相对选择器
如果直接用节点属性(或者说仅仅使用基本选择器)没法选出你所想要的元素时,你还可以通过元素之间的渲染层级关系进行选择,例如父子关系、兄弟关系、祖先后代关系等等:
offspring(name=None, **attrs)
选择后代 包括查询表达式给出的 UI 元素中的直接子元素
poco("plays").child("playBasic").offspring("star_single")
- 空间顺序选择器
按照序号(顺序)进行选择总是按照空间排布顺序,先从左往右,再像之前那样一行一行从上到下。如下图所示,我们利用选择器选中了很多个 type=“Text” 的元素,然后再利用索引顺序逐个选中单个元素:
name0 = poco("Content").child(type="Text")[0].get_name()
name1 = poco("Content").child(type="Text")[1].get_name()
name2 = poco("Content").child(type="Text")[2].get_name()
print(name0+" "+name1+" "+name2)
利用正则表达式匹配控件
- textMatches
poco(text="手机淘宝")
等价于
poco(textMatches=".*淘宝").click()
- nameMatches
poco(name="com.netease.cloudmusic:id/portalTitle",text="每日推荐")
等价于
poco(nameMatches=".*portalTitle",textMatches=".*推荐")
demo案例
# -*- encoding=utf8 -*-
__author__ = "shisuiyi"
from airtest.core.api import *
auto_setup(__file__)
from poco.drivers.android.uiautomation import AndroidUiautomationPoco
poco = AndroidUiautomationPoco(use_airtest_input=True, screenshot_each_action=False)
start_app("com.netease.cloudmusic")
sleep(3)
poco(text ="手机号登录").click()
poco("com.netease.cloudmusic:id/qc").wait_for_appearance()
poco(text="请输入手机号").click()
poco(text="请输入手机号").set_text("18812347463")
poco(text="请输入密码").click()
poco(text="请输入密码").set_text("18812347463")
poco("com.netease.cloudmusic:id/qc").click()
Airtest的一些全局设置
- log内容的设置:LOGFILE、LOGDIR
LOGFILE 用于自定义记录log内容的txt文档的名称;LOGDIR 用于自定义log内容的保存路径,示例如下:
from airtest.core.settings import Settings as ST
from airtest.core.helper import set_logdir
ST.LOG_FILE = "log123.txt"
set_logdir(r'D:\test\1234.air\logs')
auto_setup(__file__)
# 此处省略N条用例脚本
- 图像识别算法的设置:CVSTRATEGY
CVSTRATEGY 用于 设置Airtest的图像识别算法,默认情况下 CVSTRATEGY = [“surf”, “tpl”, “brisk”] ,每次查找图片的时候,airtest就会按照这个设置好的算法顺序去执行,直到找出一个符合设定阈值的识别结果,或者是一直按照这个算法顺序循环查找,直到超时。
我们可以自定义Airtest的图像识别算法,示例如下:
from airtest.core.settings import Settings as ST
ST.CVSTRATEGY = ["tpl", "sift","brisk"]
- 图像阙值:THRESHOLD、THRESHOLD_STRICT
THRESHOLD 和 THRESHOLD_STRICT 都是图像识别的阙值,Airtest1.1.6版本之后,所有用到了图像识别的接口,都是用 THRESHOLD 作为阙值。它的默认值为0.7,取值范围[0,1]。
在进行图像匹配时,只有 当识别结果的可信度大于阙值时,才认为找到了匹配的结果
除了可以修改全局的图像识别阙值,我们还支持修改单张图片的阙值,需要注意的是,如我们没有单独设置图像的阙值,将默认使用全局阙值 THRESHOLD ,示例如下:
from airtest.core.settings import Settings as ST
# 设置全局阙值为0.8
ST.THRESHOLD = 0.8
# 设置单张图片的阙值为0.9
touch(Template(r"tpl1607424190850.png", threshold=0.9, record_pos=(-0.394, -0.176), resolution=(1080, 1920)))
- 查询的超时时长:FIND_TIMEOUT、FIND_TIMEOUT_TMP
上面我们提到,在进行图像匹配时,会循环用几个算法去识别,但是循环识别并不是无限的,这里有一个查询的超时时长设置,一旦查询时间大于超时时长,还是未找到可信度大于阙值的结果,那就认定此次匹配失败,默认的超时时长如下:
FIND_TIMEOUT = 20
FIND_TIMEOUT_TMP = 3
使用 FIND_TIMEOUT 作为超时时长的接口有很多,比如:assert_exists()、touch()、wait()、swipe() 等。
而使用 FIND_TIMEOUT_TMP 作为超时时长的接口则比较少,比如:assert_not_exists()、exists() 等。
与阙值类似,我们既可以修改全局的超时时长,也可以设置单条语句的超时时长,示例如下:
from airtest.core.settings import Settings as ST
# 设置全局的超时时长为60s
ST.FIND_TIMEOUT = 60
ST.FIND_TIMEOUT_TMP = 60
# 设置单条wait语句的超时时长
wait(Template(r"tpl1607425650104.png", record_pos=(-0.044, -0.177), resolution=(1080, 1920)),timeout=120)
- 项目根目录:PROJECT_ROOT
项目根目录常用于调用其它 .air 脚本时,示例如下:
from airtest.core.settings import Settings as ST
# PROJECT_ROOT需要填写绝对路径
ST.PROJECT_ROOT = "D:/test/user/project"
using("test1.air")
using("test2.air")
# 如不设置项目根目录,我们可能要这么调用test1.air、test2.air
using("D:/test/user/project/test1.air")
using("D:/test/user/project/test2.air")
另外,上节课我们介绍的 auto_setup() 接口也可以传入项目根目录:
auto_setup(file, project_root=“D:/test/user/project”)
6. 截图压缩精度:SNAPSHOT_QUALITY
SNAPSHOT_QUALITY 用于设置全局的截图压缩精度,需要注意的是,设置的是Airtest报告里面显示的截图精度,而不是我们的截图脚本截取的那张图片的精度。默认值为10,取值范围[1,100],数值越高,截图的精度越高,越清晰。示例如下:
from airtest.core.settings import Settings as ST
# 设置全局的截图精度为90
ST.SNAPSHOT_QUALITY = 90
另外我们还可以定义单张截图的压缩精度,示例:
# 设置单张截图的压缩精度为90,其余未设置的将按照全局压缩精度来
snapshot(quality=90)
- 截图尺寸大小:IMAGE_MAXSIZE
在Airtest1.1.6中,新增了一个用于指定截图最大尺寸的设置:ST.IMAGE_MAXSIZE 。假如设置为1200,则最后保存的截图长宽都不会超过1200,有利于进一步缩小截图的图片尺寸。示例:
from airtest.core.settings import Settings as ST
# 设置全局截图尺寸不超过600*600,如果不设置,默认为原图尺寸
ST.IMAGE_MAXSIZE = 600
# 不单独设置的情况下,默认采用ST中的全局变量的数值,即600*600
snapshot(msg="test12")
# 设置单张截图的最大尺寸不超过1200*1200
snapshot(filename="test2.png", msg="test02", quality=90, max_size=1200)
Airtest的录屏操作
在命令行使用–recording录屏
airtest run "D:\test\Airtest_example.air" --device android://127.0.0.1:5037/emulator-5554?cap_method=MINICAP_STREAM^&^&ori_method=MINICAPORI^&^&touch_method=MINITOUCH --log "D:/test\41f68fdf265d8c13998d0a1a7b992889" --recording
airtest1.1.6及更高版本,支持 在 --recording 参数后面加上一个文件名来命名录屏文件 ,例如 --recording test.mp4
运行结束后,录屏文件会默认保存在log文件夹里面,使用 recording_手机序列号 来命名录屏文件
在脚本中调用录屏方法
脚本示例如下:
# -*- encoding=utf8 -*-
__author__ = "AirtestProject"
from airtest.core.api import *
from airtest.core.android.recorder import *
from airtest.core.android.adb import *
auto_setup(__file__,devices=["android://127.0.0.1:5037/emulator-5554"])
adb = ADB(serialno="emulator-5554")
recorder = Recorder(adb)
# 开启录屏
recorder.start_recording(max_time=10)
touch(Template(r"tpl1603091574169.png", record_pos=(0.113, -0.302), resolution=(900, 1600)))
sleep(3.0)
# 结束录屏
recorder.stop_recording(output="test.mp4")
脱离AirtestIDE跑自动化脚本
以pychram为例
环境部署
在pycharm新建项目之后,就需要为该项目设置Python解释器;pycharm支持我们直接设置本地安装好的Python作为解释器,也支持使用虚拟Python环境作为解释器。
那在pycharm安装第三方库的方式,就非常简单了,在项目使用的Python解释器里,点击右侧的+ 号,输入想要添加的包名,然后点击 安装包 按钮即可:
-
PYTHON版本选择
支持Python2.7或<=3.9,我们更推荐使用 Python3 ,如果你愿意的话我们也同样建议使用 virtualenv 等虚拟环境新建一个干净的python环境。注意:python3.9如果安装完毕还不能使用airtest,报错 ImportError: numpy.core.multiarray failed to import ,可以手工将 numpy 版本号降级至1.19.3就能使用了。
pip install -U numpy==1.19.3
-
② AIRTEST安装
使用 pip 安装Airtest框架 pip install airtest
注意:在Mac/Linux系统下,需要手动赋予adb可执行权限,否则可能在执行脚本时遇到 Permission denied 的报错:
若运行代码时,在cv2模块报 ImportError: DLL load failed: 找不到指定模块 的错,有几种解决方案:
本问题的根本原因应该是DLL文件的缺失,我们将它们放入了IDE的目录中,可以直接下载一个最新版本的AirtestIDE,在解压后的目录中找到api-ms-win-downlevel-shlwapi-l1-1-0.dll和IEShims.dll两个DLL文件,将他们复制到C:\Windows\System32目录,重新运行代码即可解决 -
③ POCO安装
使用 pip 安装poco框架 pip install pocoui,请注意库的名称为 pocoui,不要填错。如同时安装了 poco 和 pocoui ,则运行脚本时会出现冲突,请确保python环境里面只安装了正确的 pocoui 。 -
④ PIP指令运行失败
国内用户请在pip install 指令后面加上 -ihttps://pypi.tuna.tsinghua.edu.cn/simple
(清华源)后重试
环境部署好之后,我们就可以着手在pycharm编写/运行脚本了。
以 直接从AirtestIDE复制脚本到pycharm运行 为例
log保存
首先是脚本运行的log内容保存,我们在AirtestIDE编写和运行脚本,没有考虑过这个问题,是因为AirtestIDE会自动帮我们保存脚本运行的log内容,默认路径是 选项–设置–Airtest–默认Log存放路径 里设置的路径:
把代码复制到pycharm运行时,如需保存log内容(为后续生成测试报告做准备),我们就需要手动添加上这块的内容:
# 在auto_setup接口里设置logdir,用于保存log内容
# logdir可以传入具体的log保存路径,或者是True,传入True表示在当前项目目录下生成log内容
auto_setup(__file__,logdir=True)
log内容往往包含一些报告所需的步骤截图和一个 log.txt 文件。
设备连接
在AirtestIDE里,只要设备连接窗口已经连接上指定设备,我们在.air脚本中无需额外处理,运行.air脚本时,就会自动连接设备窗口的当前设备来跑脚本。
而pycharm显然不会帮我们处理设备连接的工作,所以我们需要在脚本中连接上待测设备:
# 在auto_setup接口传入devices参数
auto_setup(__file__,logdir=True,devices=["android://127.0.0.1:5037/127.0.0.1:7555"])
当然,我们还有非常多的接口可以用于连接我们各种待测设备
用于初始化设备的URI字符串
1.连接Android手机
# 什么都不填写,默认取当前连接中的第一台手机
Android:///
# 连接本机默认端口连的一台设备号为79d03fa的手机
Android://127.0.0.1:5037/79d03fa
# 用本机的adb连接一台adb connect过的远程设备,注意10.254.60.1:5555其实是serialno
Android://127.0.0.1:5037/10.254.60.1:5555
2.连接iOS手机
iOS:///127.0.0.1:8100
3.连接Windows窗口
# 连接一个窗口句柄为123456的Windows窗口
Windows:///123456
# 连接一个Windows窗口,窗口名称匹配某个正则表达式
Windows:///?title_re=Unity.*
# 连接windows桌面,不指定任何窗口
Windows:///
4.使用了备选连接参数的设备
# 夜神模拟器(127.0.0.1:62001为夜神模拟器的端口号)
Android://127.0.0.1:5037/127.0.0.1:62001?cap_method=JAVACAP&&ori_method=ADBORI
# 设备号为79d03fa的 MIUI11 设备
Android://127.0.0.1:5037/79d03fa?cap_method=JAVACAP&&ori_method=ADBORI
在脚本中添加连接设备的参数
- 使用 auto_setup 接口
auto_setup 是一个用来 初始化环境 的接口,它接受5个参数。我们可以设置当前脚本所在的路径、 指定运行脚本的设备 、设置默认的log路径、设置脚本父路径和指定截图精度:
其中第二个参数就是指定运行脚本的设备,我们可以在这里 传入待连接设备的URI字符串 ,例如:
# 连接本机默认端口连的一台设备号为SJE5T17B17的手机
auto_setup(__file__,devices=["Android://127.0.0.1:5037/SJE5T17B17"])
注意:devices 传入的是一个字符串列表,所以如果需要 连接多台设备 ,直接用 , 隔开多个URI字符串即可:
# 连接本机默认端口连的设备号为123和456的两台手机
auto_setup(__file__,devices=["Android://127.0.0.1:5037/123","Android://127.0.0.1:5037/456"])
- 使用 connect_device 接口
在 connect_device 接口中传入设备的URI字符串即可连接1台设备:
dev = connect_device("Android://127.0.0.1:5037/SJE5T17B17")
如果需要连接多台设备,可以编写多条的 connect_device 脚本,并且用 set_current 来切换到当前使用设备:
# 连上第一台手机
dev1 = connect_device("Android://127.0.0.1:5037/serialno1")
# 连上第二台手机
dev2 = connect_device("Android://127.0.0.1:5037/serialno2")
# 切换当前操作的手机到序列号为serialno1的手机
set_current("serialno1")
- 使用 init_device 接口
init_device 接口只需要传入 设备平台和设备的uuid 即可,参数详情可以查看下图:
init_device(platform="Android",uuid="SJE5T17B17")
注意,在多机协作中(例如让两台手机登录同一个APP并相互“添加好友”)
可以直接在脚本里使用多个connect_device语句,分别传入手机连接串信息即可:
from airtest.core.api import connect_device
dev1 = connect_device("Android://127.0.0.1:5037/serialno1") # 连上第一台手机
dev2 = connect_device("Android://127.0.0.1:5037/serialno2") # 第二台手机
在连接多台手机后,我们能够在Airtest的全局变量G.DEVICE_LIST中看到所有当前连接中的设备,可以使用set_current接口在多台设备之间切换。
print(G.DEVICE_LIST) # 此时设备列表为[dev1, dev2]
# 传入数字0切换当前操作的手机到第1台
set_current(0)
# 切换当前操作的手机到序列号为serialno2的手机
set_current("serialno2")
# 使用device()接口获取当前连接中的设备Android对象
current_dev = device()
图片路径
在AirtestIDE截取的图片,默认的路径都是相对路径,保存在.air脚本下,与.py文件同路径:
touch(Template(r"tpl1638179990578.png", record_pos=(0.179, -0.57), resolution=(810, 1440)))
但复制到pycharm执行时,相对路径大概率会发生变化,导致后面同学们运行时,经常出现 airtest.aircv.error.FileNotExistError: File not exist: tpl1638179990578.png 的报错。
此时我们要么修改成正确的相对路径,要么修改成绝对路径,只要保证pycharm能按你设定的路径找得到你的脚本截图即可:
touch(Template(r"D:\test_plu\song.air\tpl1638179990578.png", record_pos=(0.179, -0.57), resolution=(810, 1440)))
文件引用路径
调用 .air 脚本,我们可以使用Airtest提供的专用接口using
;调用 .py 脚本,就与标准Python无异,直接 from ... import ...
即可。
若在pycharm中打开并且运行之后,会发现在AirtestIDE可以运行的脚本,到pycharm就找不到模块了可使用手工添加 sys.path 的方式,让pycharm能找到相应的脚本:如
报告生成
最后一个需要注意的内容就是报告生成啦,在AirtestIDE运行完.air脚本之后,我们可以直接点击 查看报告 按钮,迅速生成并且打开HTML格式的测试报告。
但pycharm并没有这个功能,所以我们需要生成Airtest报告的话,只能在脚本中编写生成测试报告的语句:
# -*- encoding=utf8 -*-
__author__ = "AirtestProject"
from airtest.core.api import *
from airtest.report.report import simple_report
auto_setup(__file__,logdir=True,devices=["android://127.0.0.1:5037/127.0.0.1:7555"])
touch(Template(r"tpl1638243250870.png", record_pos=(-0.362, 0.13), resolution=(810, 1440)))
simple_report(__file__,logpath=True)
使用脚本生成Airtest报告,有很多注意事项:
报告生成语句要放在具体脚本的后面,防止未运行具体步骤就生成了空的测试报告
当脚本中包含poco或者airtest-selenium语句时,需添加对应的报告插件 plugins
不论中间的步骤运行成功与否,都生成测试报告,我们可以使用 try-finally 语句
需要生成测试报告,就必须保存log内容,因为报告的生成依赖于log内容
如需将报告发送给别人查看,需要在生成报告的语句中添加导出参数,只有导出报告才能发送给别人查看
在脚本中生成、导出报告
如果不借助IDE和命令行,我们能不能直接在脚本中调用一些接口,让脚本执行完毕时,自动生成或者导出1份报告给我们呢?
我们可以借助 simple_report() 接口或者 LogToHtml() 类来实现。
- simple_report :生成报告
先来讲讲这个 simple_report 接口,它其实是1个简化版的生成报告的接口,可以减少同学们的理解成本和使用成本:
如果同学们不指定任何参数,该接口会使用默认的参数生成1份HTML格式的报告,output=‘log.html’ 表示在当前脚本路径下生成名为 log.html 的airtest报告:
from airtest.report.report import simple_report
simple_report(__file__)
如果指定了 output 参数,则会按指定路径生成报告:
from airtest.report.report import simple_report
simple_report(__file__,logpath=True,output=r"D:\test\report02\log.html")
- LogToHtml:导出报告
如果不需要通过脚本的形式导出报告,仅需要生成报告在本地查看的话,只要使用 simple_report 接口即可。
但如果需要通过脚本的形式来导出1份报告,则需要用到 LogToHtml 类:
这个类的参数相对于 simple_report() 就复杂的多了,包含:
- script_root,脚本路径
- log_root,log文件的路径
- static_root,部署静态资源的服务器路径
- export_dir,导出报告的存放路径
- script_name,脚本名称
- logfile,log文件log.txt的路径
- lang,报告的语言(中文:zh;英文:en)
- plugins,插件,使用了poco或者airtest-selenium会用到
示例如下,我们在指定路径 D:\test\report02 中导出了 D:\test\report01.air 脚本的运行报告,报告语言为英文:
from airtest.report.report import LogToHtml
h1 = LogToHtml(script_root=r'D:\test\report01.air', log_root=r"D:\test\report01.air\log", export_dir=r"D:\test\report02" ,logfile=r'D:\test\report01.air\log\log.txt', lang='en', plugins=None)
h1.report()
- try-finally:保证最后都能生成报告
一般情况下,生成报告的语句应该是放在所有用例脚本的后面,保证用例执行完毕之后,才执行生成脚本的语句。
但这里容易出现一种情况,一旦前面有用例脚本执行失败,终止了整个脚本的运行,即还没有执行到生成报告的语句时,脚本运行就已经停止了,这样也不能够正常生成报告。
所以我们可以用 try-finally 语句,不论脚本是否运行失败,最终都会生成1份运行报告:
try:
poco("com.netease.newsreader.activity:id/bjd").wait_for_appearance()
poco("com.netease.newsreader.activity:id/awo").click()
...
finally:
simple_report(__file__,logpath=True,output="../netease_music/登录.html")
print("-----执行完毕-----")
常用API介绍
常用Airtest API
常用跨平台的API
init_device
init_device(platform='Android', uuid=None, **kwargs)
初始化设备,并设置为当前设备。
参数:
- platform – Android, IOS or Windows
- uuid – 目标设备的uuid,例如Android的序列号,Windows的窗口句柄,或iOS的uuid
- kwargs – 可选的平台相关的参数,例如Android下的
cap_method=JAVACAP
参数
返回:
- device对象
示例:
>>> init_device(platform="Android",uuid="SJE5T17B17", cap_method="JAVACAP")
>>> init_device(platform="Windows",uuid="123456")
connect_device
connect_device(uri)[源代码]
用URI字符串来初始化设备,并且设置为当前设备。
参数:
- uri – 一个用于初始化设备的URI字符串,例如 android://adbhost:adbport/serialno?param=value¶m2=value2
返回:
- device对象
示例:
connect_device("Android:///") # local adb device using default params
# local device with serial number SJE5T17B17 and custom params
connect_device("Android:///SJE5T17B17?cap_method=javacap&touch_method=adb")
# remote device using custom params Android://adbhost:adbport/serialno
connect_device("Windows:///123456") # Connect to the window with handle 123456
connect_device("iOS:///127.0.0.1:8100") # iOS device
connect_device("iOS:///http://localhost:8100/?mjpeg_port=9100") # iOS with mjpeg port
device
device()
- 返回当前正在使用中的设备。
返回:
- 当前设备实例
示例:
>>> dev = device()
>>> dev.touch((100, 100))
set_current
set_current(idx)
设置当前设备。
参数:
- idx – uuid或已初始化的设备列表中的编号,从0开始
引发:
- IndexError – 当查找不到设备时
返回:
- None
支持平台:
- Android, iOS, Windows
示例:
>>> # switch to the first phone currently connected
>>> set_current(0)
>>> # switch to the phone with serial number serialno1
>>> set_current("serialno1")
auto_setup
auto_setup(basedir=None, devices=None, logdir=None, project_root=None, compress=None)
自动配置运行环境,如果当前没有连接设备的话,就默认尝试连接Android设备。
参数:
- basedir – 设置当前脚本的所在路径,也可以直接传 file 变量进来
- devices – 一个内容为 connect_device uri 字符串的列表
- logdir – 可设置脚本运行时的log保存路径,默认值为None则不保存log,如果设置为True则自动保存在
/log目录中。 - project_root – 用于设置PROJECT_ROOT变量,方便 using 接口的调用
- compress – 屏幕截图的压缩比率,在[1, 99]范围内的整数,默认是10
示例:
>>> auto_setup(__file__)
>>> auto_setup(__file__, devices=["Android://127.0.0.1:5037/SJE5T17B17"],
... logdir=True, project_root=r"D:\test\logs", compress=90)
shell
shell(cmd)
在目标设备上运行远程shell指令
参数:
- cmd – 需要在设备上运行的指令,例如 ls /data/local/tmp
返回:
- shell指令的输出内容
支持平台:
- Android
示例:
>>> # Execute commands on the current device adb shell ls
>>> print(shell("ls"))
>>> # Execute adb instructions for specific devices
>>> dev = connect_device("Android:///device1")
>>> dev.shell("ls")
>>> # Switch to a device and execute the adb command
>>> set_current(0)
>>> shell("ls")
start_app
start_app(package, activity=None)
在设备上启动目标应用
参数:
- package – 想要启动的应用包名package name,例如 com.netease.my
- activity – 需要启动的activity,默认为None,意为main activity
返回:
- None
支持平台:
- Android, iOS
示例:
>>> start_app("com.netease.cloudmusic")
>>> start_app("com.apple.mobilesafari") # on iOS
stop_app
stop_app(package)
终止目标应用在设备上的运行
参数:
- package – 需要终止运行的应用包名 package name,另见 start_app
返回:
- None
支持平台:
- Android, iOS
示例:
>>> stop_app("com.netease.cloudmusic")
clear_app
clear_app(package)
清理设备上的目标应用数据
参数:
- package – 包名 package name,另见 start_app
返回:
- None
支持平台:
- Android
示例:
>>> clear_app("com.netease.cloudmusic")
install
install(filepath, **kwargs)
安装应用到设备上
参数:
- filepath – 需要被安装的应用路径
- kwargs – 平台相关的参数 kwargs,请参考对应的平台接口文档
返回:
- None
支持平台:
- Android
示例:
>>> install(r"D:\demo\test.apk")
>>> # adb install -r -t D:\demo\test.apk
>>> install(r"D:\demo\test.apk", install_options=["-r", "-t"])
uninstall
uninstall(package)
卸载设备上的应用
参数:
- package – 需要被卸载的包名 package name,另见 start_app
返回:
- None
支持平台:
- Android
示例:
>>> uninstall("com.netease.cloudmusic")
snapshot
snapshot(filename=None, msg='', quality=None, max_size=None)
对目标设备进行一次截图,并且保存到文件中。
参数:
- filename – 保存截图的文件名,默认保存路径为
ST.LOG_DIR
中 - msg – 截图文件的简短描述,将会被显示在报告页面中
- quality – 图片的质量,[1,99]的整数,默认是10
- max_size – 图片的最大尺寸,例如 1200
返回:
- {“screen”: filename, “resolution”: resolution of the screen} or None
支持平台:
- Android, iOS, Windows
示例:
>>> snapshot(msg="index")
>>> # save the screenshot to test.jpg
>>> snapshot(filename="test.png", msg="test")
可以设置截图的画质和大小
>>> # Set the screenshot quality to 30
>>> ST.SNAPSHOT_QUALITY = 30
>>> # Set the screenshot size not to exceed 600*600
>>> # if not set, the default size is the original image size
>>> ST.IMAGE_MAXSIZE = 600
>>> # The quality of the screenshot is 30, and the size does not exceed 600*600
>>> touch((100, 100))
>>> # The quality of the screenshot of this sentence is 90
>>> snapshot(filename="test.png", msg="test", quality=90)
>>> # The quality of the screenshot is 90, and the size does not exceed 1200*1200
>>> snapshot(filename="test2.png", msg="test", quality=90, max_size=1200)
wake()
wake()[源代码]
唤醒并解锁目标设备
返回:
- None
支持平台:
- Android
示例:
>>> wake()
注解
在部分品牌手机上可能无法生效
home()
home()
返回HOME界面。
返回:
- None
支持平台:
- Android, iOS
示例:
>>> home()
touch
touch(v, times=1, **kwargs)
在当前设备画面上进行一次点击
参数:
- v – 点击位置,可以是一个 Template 图片实例,或是一个绝对坐标 (x, y)
- times – 点击次数
- kwargs – 平台相关的参数 kwargs,请参考对应的平台接口文档
返回:
- finial position to be clicked, e.g. (100, 100)
支持平台:
- Android, Windows, iOS
示例:
点击绝对坐标:
>>> touch((100, 100))
点击图片的中心位置:
>>> touch(Template(r"tpl1606730579419.png", target_pos=5))
点击两次:
>>> touch((100, 100), times=2)
在Android和Windows下,可以设置点击持续时间:
>>> touch((100, 100), duration=2)
右键点击(Windows):
>>> touch((100, 100), right_click=True)
click
click(v, times=1, **kwargs)
在当前设备画面上进行一次点击
参数:
- v – 点击位置,可以是一个 Template 图片实例,或是一个绝对坐标 (x, y)
- times – 点击次数
- kwargs – 平台相关的参数 kwargs,请参考对应的平台接口文档
返回: - finial position to be clicked, e.g. (100, 100)
支持平台:
- Android, Windows, iOS
示例:
点击绝对坐标:
>>> touch((100, 100))
点击图片的中心位置:
>>> touch(Template(r"tpl1606730579419.png", target_pos=5))
点击两次:
>>> touch((100, 100), times=2)
```python
在Android和Windows下,可以设置点击持续时间:
```python
>>> touch((100, 100), duration=2)
```python
右键点击(Windows):
```python
>>> touch((100, 100), right_click=True)
double_click
double_click(v)
双击
参数:
- v – 点击位置,可以是一个 Template 图片实例,或是一个绝对坐标 (x, y)
返回:
- 实际点击位置坐标 (x, y)
示例:
>>> double_click((100, 100))
>>> double_click(Template(r"tpl1606730579419.png"))
swipe
swipe(v1, v2=None, vector=None, **kwargs)
在当前设备画面上进行一次滑动操作。
有两种传入参数的方式
- swipe(v1, v2=Template(…)) # 从 v1 滑动到 v2
- swipe(v1, vector=(x, y)) # 从 v1 开始滑动,沿着vector方向。
参数: - v1 – 滑动的起点,可以是一个Template图片实例,或是绝对坐标 (x, y)
- v2 – 滑动的终点,可以是一个Template图片实例,或是绝对坐标 (x, y)
- vector – 滑动动作的矢量坐标,可以是绝对坐标 (x,y) 或是屏幕百分比,例如 (0.5, 0.5)
- **kwargs – 平台相关的参数 kwargs,请参考对应的平台接口文档
引发:
- Exception – 当没有足够的参数来执行滑动时引发异常
返回:
- 原点位置和目标位置
支持平台:
- Android, Windows, iOS
示例:
>>> swipe(Template(r"tpl1606814865574.png"), vector=[-0.0316, -0.3311])
>>> swipe((100, 100), (200, 200))
自定义滑动持续时间和经过几步到达终点:
>>> # swiping lasts for 1 second, divided into 6 steps
>>> swipe((100, 100), (200, 200), duration=1, steps=6)
pinch
pinch(in_or_out='in', center=None, percent=0.5)
在设备屏幕上执行一个双指pinch捏合操作
参数:
- in_or_out – 向内捏合或向外扩大,在[“in”, “out”] 中枚举一个值
- center – pinch动作的中心位置,默认值为None则为屏幕中心点
- percent – pinch动作的屏幕百分比,默认值为0.5
返回:
- None
支持平台:
- Android
示例:
- 两指向屏幕中心点捏合:
>>> pinch()
将(100, 100)作为中心点,向外扩张两指:
>>> pinch('out', center=(100, 100))
keyevent
keyevent(keyname, **kwargs)
在设备上执行keyevent按键事件
- 参数:
keyname – 平台相关的按键名称
**kwargs – 平台相关的参数 kwargs,请参考对应的平台接口文档
返回:
- None
支持平台:
- Android, Windows, iOS
示例:
Android: 相当于执行了 adb shell input keyevent KEYNAME
>>> keyevent("HOME")
>>> # The constant corresponding to the home key is 3
>>> keyevent("3") # same as keyevent("HOME")
>>> keyevent("BACK")
>>> keyevent("KEYCODE_DEL")
Windows: 使用 pywinauto.keyboard 进行按键点击:
>>> keyevent("{DEL}")
>>> keyevent("%{F4}") # close an active window with Alt+F4
iOS: Only supports home/volumeUp/volumeDown:
>>> keyevent("HOME")
>>> keyevent("volumeUp")
text
text(text, enter=True, **kwargs)
在目标设备上输入文本,文本框需要处于激活状态。
参数:
- text – 要输入的文本
- enter – 是否在输入完毕后,执行一次 Enter ,默认是True
返回:
- None
支持平台:
- Android, Windows, iOS
示例:
>>> text("test")
>>> text("test", enter=False)
在Android上,有时你需要在输入完毕后点击搜索按钮:
>>> text("test", search=True)
如果希望输入其他按键,可以用这个接口:
>>> text("test")
>>> device().yosemite_ime.code("3") # 3 = IME_ACTION_SEARCH
Ref: Editor Action Code
sleep
sleep(secs=1.0)
设置一个等待sleep时间,它将会被显示在报告中
参数:
- secs – sleep的时长
返回:
- None
支持平台:
- Android, Windows, iOS
示例:
sleep(1)
wait
wait(v, timeout=None, interval=0.5, intervalfunc=None)
等待当前画面上出现某个匹配的Template图片
参数:
- v – 要等待出现的目标Template实例
- timeout – 等待匹配的最大超时时长,默认为None即默认取 ST.FIND_TIMEOUT 的值
- interval – 尝试查找匹配项的时间间隔(以秒为单位)
- intervalfunc – 在首次尝试查找匹配失败后的回调函数
引发:
- TargetNotFoundError – 在超时后仍未找到目标则触发
返回:
- 匹配目标的坐标
支持平台:
- Android, Windows, iOS
示例:
>>> wait(Template(r"tpl1606821804906.png")) # timeout after ST.FIND_TIMEOUT
>>> # find Template every 3 seconds, timeout after 120 seconds
>>> wait(Template(r"tpl1606821804906.png"), timeout=120, interval=3)
你可以在每次查找目标失败时,指定一个回调函数:
>>> def notfound():
>>> print("No target found")
>>> wait(Template(r"tpl1607510661400.png"), intervalfunc=notfound)
exists
exists(v)
检查设备上是否存在给定目标
参数:
- v – 要检查的目标
返回:
- 如果未找到目标,则返回False,否则返回目标的坐标
支持平台:
- Android, Windows, iOS
示例:
>>> if exists(Template(r"tpl1606822430589.png")):
>>> touch(Template(r"tpl1606822430589.png"))
因为 exists() 会返回坐标,我们可以直接点击坐标来减少一次图像查找
>>> pos = exists(Template(r"tpl1606822430589.png"))
>>> if pos:
>>> touch(pos)
##### find_all
`find_all(v)`
在设备屏幕上查找所有出现的目标并返回其坐标列表
参数:
- v – 寻找目标
返回:
- 结果列表, [{‘result’: (x, y), ‘rectangle’: ( (left_top, left_bottom, right_bottom, right_top) ), ‘confidence’: 0.9}, …]
支持平台:
- Android, Windows, iOS
示例:
>>> find_all(Template(r"tpl1607511235111.png"))
[{'result': (218, 468), 'rectangle': ((149, 440), (149, 496), (288, 496), (288, 440)),
'confidence': 0.9999996423721313}]
assert_exists
assert_exists(v, msg='')
设备屏幕上存在断言目标
参数:
- v – 要检查的目标
- msg – 断言的简短描述,它将被记录在报告中
引发:
- AssertionError – 如果断言失败
返回:
- 目标坐标
支持平台:
- Android, Windows, iOS
示例:
>>> assert_exists(Template(r"tpl1607324047907.png"), "assert exists")
assert_not_exists
assert_not_exists(v, msg='')
设备屏幕上不存在断言目标
参数:
- v – 要检查的目标
- msg – 断言的简短描述,它将被记录在报告中
引发:
- AssertionError – 如果断言失败
返回:
- None.
支持平台:
- Android, Windows, iOS
示例:
>>> assert_not_exists(Template(r"tpl1607324047907.png"), "assert not exists")
assert_equal
assert_equal(first, second, msg='')
断言两个值相等
参数:
- first – 第一个值
- second – 第二个值
- msg – 断言的简短描述,它将被记录在报告中
引发:
- AssertionError – 如果断言失败
返回:
- None
支持平台:
- Android, Windows, iOS
示例:
>>> assert_equal(1, 1, msg="assert 1==1")
assert_not_equal
assert_not_equal(first, second, msg='')
断言两个值不相等
参数:
- first – 第一个值
- second – 第二个值
- msg – 断言的简短描述,它将被记录在报告中
引发:
- AssertionError – 如果断言异常
返回:
- None
支持平台:
- Android, Windows, iOS
示例:
>>> assert_not_equal(1, 2, msg="assert 1!=2")
常用安卓平台的API
首先 基本上airtest.core.api中的接口(文档地址),在Android平台上都可以直接使用,例如:
# 清理某个应用数据
clear_app("org.cocos2d.blackjack")
# 启动某个应用
start_app("org.cocos2d.blackjack")
# 传入某个按键响应
keyevent("BACK")
除了在airtest.core.api
中提供的跨平台接口之外,Android设备对象还有很多内置的接口可以调用,我们可以在airtest.core.android.android module这个文档中查阅到Android设备对象拥有的方法,然后像这样调用:
dev = device() # 获取到当前设备的Android对象
print(dev.get_display_info()) # 查看当前设备的显示信息
print(dev.list_app()) # 打印出当前安装的app列表
print(dev.uuid()) # 序列号
此外还有
check_app(包)
检查package在设备中是否存在
参数: package – package name
返回: 如果存在,返回True
Raises: AirtestError – raised if package is not found
logcat( *args , **kwargs )
执行 logcat
参数:
*args – optional arguments
**kwargs – optional arguments
返回:
logcat 输出
get_ip_address()
执行以下几种命令行来获取IP地址
adb shell netcfg | grep wlan0
adb shell ifconfig
adb getprop dhcp.wlan0.ipaddress
返回: 如果获取IP失败,返回None,否则返回IP地址
get_top_activity()
Get the top activity
返回: (package, activity, pid)
其他Airtest封装好的ADB接口
- 返回应用的完整路径:path_app()
android = Android()
android.path_app("com.netease.cloudmusic")
- 检查应用是否存在于当前设备上:check_app()
android = Android()
android.check_app("com.netease.cloudmusic")
- 停止应用运行:stop_app()
stop_app("com.netease.cloudmusic")
# 启动应用:start_app()
start_app("com.netease.cloudmusic")
# 清除应用数据:clear_app()
clear_app("com.netease.cloudmusic")
- 安装应用:install_app()
install(r"D:\demo\tutorial-blackjack-release-signed.apk")
# 卸载应用:uninstall_app()
uninstall("org.cocos2dx.javascript")
- 检查屏幕是否打开:is_screenon()
android = Android()
android.is_screenon()
- 检查设备是否锁定:is_locked()
android = Android()
android.is_locked()
- 获取当前设备的分辨率:get_current_resolution()
android = Android()
android.get_current_resolution()
Poco控件的核心API
控件点击操作
目前poco控件的支持的点击操作包含单击和长按,双击和右键单击均未实现。
# 控件单击
poco("star_single").click()
# 控件长按
poco('star_single').long_click()
控件的滑动操作
Poco支持对控件进行滑动操作,我们需要先定位到这个控件,然后指定它按照某个方向滑动即可:
# -*- encoding=utf8 -*-
__author__ = "AirtestProject"
from airtest.core.api import *
auto_setup(__file__)
from poco.drivers.unity3d import UnityPoco
poco = UnityPoco()
# 向下滑动0.2个单位距离
poco("Handle").swipe([0,0.2])
sleep(1.0)
# 向上滑动0.2个单位距离
poco("Handle").swipe([0,-0.2])
sleep(1.0)
# 向下滑动0.1个单位距离
poco("Handle").swipe("down")
sleep(1.0)
# 向上滑动0.1个单位距离
poco("Handle").swipe("up")
sleep(1.0)
控件属性的读取和设置
1. 控件属性的读取
我们在IDE的poco辅助窗检索出来的控件属性,基本上都可以通过 attr 接口读取出来:
# -*- encoding=utf8 -*-
__author__ = "AirtestProject"
from airtest.core.api import *
auto_setup(__file__)
from poco.drivers.unity3d import UnityPoco
poco = UnityPoco()
print("----------------")
print("name:"+poco("star_single").attr("name"))
print("type:"+poco("star_single").attr("type"))
print("texture:"+poco("star_single").attr("texture"))
另外,Poco还支持使用特定的API获取控件的某一属性值:
获取控件的name属性:get_name
获取控件的text属性:get_text
获取控件的position属性:get_position
获取控件的size属性:get_size
…
# -*- encoding=utf8 -*-
__author__ = "AirtestProject"
from airtest.core.api import *
auto_setup(__file__)
from poco.drivers.unity3d import UnityPoco
poco = UnityPoco()
print("----------------")
print("name:"+poco("star_single").get_name())
print("position:"+str(poco("star_single").get_position()))
print("size:"+str(poco("star_single").get_size()))
2. 设置控件的属性值
通常我们需要设置元素属性的情况,就是设置文本框的文本属性(输入文本),可以使用 set_text() 方法或者 setattr() 方法:
# -*- encoding=utf8 -*-
__author__ = "AirtestProject"
from airtest.core.api import *
auto_setup(__file__)
from poco.drivers.unity3d import UnityPoco
poco = UnityPoco()
# 先激活输入光标
poco("pos_input").click()
# 再执行输入动作
poco("pos_input").set_text("123")
sleep(1.0)
poco("pos_input").setattr('text',"456")
3. 判断控件是否存在
判断控件是否存在,我们可以使用exists()方法,它给我们返回的是布尔值,利用这一点,我们可以做很多应用。
- 控件存在则xx,不存在则yy
if poco("star_single").exists():
poco("star_single").click()
else:
print("未找到星星控件")
区分Poco和Airtest的exists
Poco和Airtest框架都有一个exists方法,但我们需要区分它们俩者的用法,Airtest的exists是用于判断图片存在,exists(图片);而Poco的exists是用于判断控件存在,poco(xxx).exists()。
- 断言控件存在
利用控件存在返回的布尔值,我们可以巧妙地结合Airtest的断言相等,来断言控件存在:
assert_equal(poco("star_single").exists(),True,"断言星星控件存在")
4. 控件的拖动
控件的drag_to() 方法,终点可以是一个元素控件,也可以是一个固定的相对坐标:
# 拖动到另一个控件上
poco("playDragAndDrop").child("star")[0].drag_to(poco("shell"))
# 拖动到固定目标上
poco("playDragAndDrop").child("star")[1].drag_to([0.503, 0.705])
5. 控件的内外部偏移
对这个控件进行点击操作,实际上点击的坐标是控件上(0.5,0.5)的位置:
内部偏移
如果选中控件之后,你并不想点击控件的中心位置,而是想点击控件内部的其它位置,我们可以使用 focus() 方法来指定内部偏移量:
# 内部偏移
pearl = poco(texture="icon")
pearl.focus('center').long_click()
sleep(1.0)
pearl.focus([0.1,0.1]).long_click()
sleep(1.0)
pearl.focus([0.9,0.9]).long_click()
外部偏移
选中1个控件以后,如果我们想点击控件之外的位置,也可以使用 focus() 方法来指定外部偏移量;并且会出现 点击坐标的值小于0或者大于1 的情况:
# 外部偏移
pearl_text = poco(text="pearl")
pearl_text.focus([0.5,-3]).long_click()
6. 控件的等待事件
- 仅等待不报错
我们可以使用wait方法,指定时间等待控件出现,再进行点击操作(该方法的返回值是控件本身,所以后面可以紧跟控件操作,比如点击、长按):
# 在10s内等待控件出现,如出现,则进行长按操作
poco(texture="icon").wait(timeout=10).long_click()
- 等待,不满足则报错
Poco控件还支持另外2个等待事件,wait_for_appearance()和wait_for_disappearance();这两个API可以帮助我们等待页面上 某1个UI 出现或者消失,等待的超时时间 timeout 默认为120秒,如果在超时时长之内元素没有出现或者消失的话,会报 PocoTargetTimeout 的错误。
# 等待黄色小鱼出现
poco("yellow").wait_for_appearance(timeout=20)
# 等待计分文本控件消失
poco(text="Count:").wait_for_disappearance(timeout=3)
- 拓展:Poco类的等待事件
这里我们拓展一个Poco类的等待事件,wait_for_any()和wait_for_all()。与上述等待事件不同的是,wait_for_any() 和 wait_for_all() 可以给定多个UI对象让其等待。(需要注意这两个方法是Poco类的方法)
wait_for_all() 是在超时时长结束之前,需要 等待所有给定的UI对象都显示出来 ,即一次轮询所有UI,例如等待三个图标都显示之后,再点击返回按钮:
poco("wait_ui2").click()
yellow = poco("yellow")
blue = poco("blue")
black = poco("black")
poco.wait_for_all([yellow,blue,black])
poco("btn_back").click()
wait_for_any() 则是在超时时长结束之前,等待任意一个UI显示出来,即一次轮询任何一个给定的UI,例如:
bomb = poco("bomb")
yellow = poco("yellow")
blue = poco("blue")
while True:
fish = poco.wait_for_any([bomb,yellow,blue])
print(fish.get_name())
7. 遍历元素
过python的for循环,我们可以 遍历任何序列的项目 ,如一个列表或者字符串。
举个例子,poco(“playDragAndDrop”).child(“star”) 得到的就是1个控件序列(包含了5个星星元素), star 代表控件序列中的1个元素。因此通过这个循环,我们就遍历了5个星星元素的序列,并把每个星星元素依次拖动到贝壳上:
for star in poco("playDragAndDrop").child("star"):
star.drag_to(poco("shell"))
注: 以上内容大多整理于Airtest 官方教程
评论