简介

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
解压即用
image-1656842236803

使用AirtestIDE操作

image-1656939971520

脚本相关

.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”的脚本,并运行查看结果,以及查看测试报告
image-1656942016331
image-1656942040242

poco脚本

Poco是一款跨平台的自动化测试框架,基于UI控件搜索原理 ,适用于Android、iOS原生和各种主流的游戏引擎应用。与基于图像识别的Airtest不同的是,Poco可以使用类似 poco(“OK”).click() 的方式来获取并操作节点。
在poco辅助框中选择相应的模式,这里选择Android,弹出是否插入poco的导入语句选择yes即可。
image-1657435951769

功能用法

image-1657436311709

image-1657436419942

image-1657436679091

poco 定位选择器

Poco控件最基本的3种定位选择器分别是:

  1. 基本选择器
    在poco实例后加一对括号,我们就可以进行元素选择了。选择器会遍历所有元素,将满足给定条件的元素都选出来并返回。
    括号里的参数就是所给定的条件,用属性名值对表示,其中第一个参数表示 节点名 ,就像 poco(“star_single”) 。后面还可以跟着一些可选参数,均表示 节点的属性及预期的属性值 :
from poco.drivers.unity3d import UnityPoco
poco = UnityPoco()

poco("star_single",type="Image")
  1. 相对选择器

如果直接用节点属性(或者说仅仅使用基本选择器)没法选出你所想要的元素时,你还可以通过元素之间的渲染层级关系进行选择,例如父子关系、兄弟关系、祖先后代关系等等:
offspring(name=None, **attrs)
选择后代 包括查询表达式给出的 UI 元素中的直接子元素

poco("plays").child("playBasic").offspring("star_single")
  1. 空间顺序选择器

按照序号(顺序)进行选择总是按照空间排布顺序,先从左往右,再像之前那样一行一行从上到下。如下图所示,我们利用选择器选中了很多个 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)
利用正则表达式匹配控件
  1. textMatches

poco(text="手机淘宝") 等价于
poco(textMatches=".*淘宝").click()

  1. 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的一些全局设置

  1. 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条用例脚本
  1. 图像识别算法的设置:CVSTRATEGY
    CVSTRATEGY 用于 设置Airtest的图像识别算法,默认情况下 CVSTRATEGY = [“surf”, “tpl”, “brisk”] ,每次查找图片的时候,airtest就会按照这个设置好的算法顺序去执行,直到找出一个符合设定阈值的识别结果,或者是一直按照这个算法顺序循环查找,直到超时。
    我们可以自定义Airtest的图像识别算法,示例如下:
from airtest.core.settings import Settings as ST

ST.CVSTRATEGY = ["tpl", "sift","brisk"]

  1. 图像阙值: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)))

  1. 查询的超时时长: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)
  1. 项目根目录: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)
  1. 截图尺寸大小: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解释器里,点击右侧的+ 号,输入想要添加的包名,然后点击 安装包 按钮即可:
image-1656903482422

  • 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 指令后面加上 -i https://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

在脚本中添加连接设备的参数

  1. 使用 auto_setup 接口
    auto_setup 是一个用来 初始化环境 的接口,它接受5个参数。我们可以设置当前脚本所在的路径、 指定运行脚本的设备 、设置默认的log路径、设置脚本父路径和指定截图精度:
    image-1656904403049
    其中第二个参数就是指定运行脚本的设备,我们可以在这里 传入待连接设备的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"])
  1. 使用 connect_device 接口
    在 connect_device 接口中传入设备的URI字符串即可连接1台设备:
dev = connect_device("Android://127.0.0.1:5037/SJE5T17B17")

image-1656904539517
如果需要连接多台设备,可以编写多条的 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")
  1. 使用 init_device 接口
    init_device 接口只需要传入 设备平台和设备的uuid 即可,参数详情可以查看下图:
init_device(platform="Android",uuid="SJE5T17B17")

image-1656904606576

注意,在多机协作中(例如让两台手机登录同一个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能找到相应的脚本:如
image-1656905050439

报告生成

最后一个需要注意的内容就是报告生成啦,在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() 类来实现。

  1. simple_report :生成报告
    先来讲讲这个 simple_report 接口,它其实是1个简化版的生成报告的接口,可以减少同学们的理解成本和使用成本:
    image-1656905450790
    如果同学们不指定任何参数,该接口会使用默认的参数生成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")
  1. LogToHtml:导出报告

如果不需要通过脚本的形式导出报告,仅需要生成报告在本地查看的话,只要使用 simple_report 接口即可。
但如果需要通过脚本的形式来导出1份报告,则需要用到 LogToHtml 类:
image-1656905471654
这个类的参数相对于 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()
  1. 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&param2=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,请参考对应的平台接口文档

返回:

  1. 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接口

  1. 返回应用的完整路径:path_app()
android = Android()
android.path_app("com.netease.cloudmusic")
  1. 检查应用是否存在于当前设备上:check_app()
android = Android()
android.check_app("com.netease.cloudmusic")
  1. 停止应用运行:stop_app()
stop_app("com.netease.cloudmusic")

# 启动应用:start_app()
start_app("com.netease.cloudmusic")

# 清除应用数据:clear_app()
clear_app("com.netease.cloudmusic")
  1. 安装应用:install_app()
install(r"D:\demo\tutorial-blackjack-release-signed.apk")

# 卸载应用:uninstall_app()
uninstall("org.cocos2dx.javascript")
  1. 检查屏幕是否打开:is_screenon()
android = Android()
android.is_screenon()
  1. 检查设备是否锁定:is_locked()
android = Android()
android.is_locked()
  1. 获取当前设备的分辨率: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 官方教程

上一篇 下一篇

评论 | 0条评论