PS:前言
工作中使用airtest遇到的一些问题(或者说坑的地方),记录一下,顺便把官网记录的一些问题及解决办法也拿过来梳理一下,做一下总结!后续也会持续补充,看到此篇文章的小伙伴也可以评论留言提供遇到的问题。
实际工作中的一些使用经验<py脚本>
先了解一下高阶py格式脚本
首先,新建一个纯py脚本
设置脚本路径,其它配置一般默认

新建完成后,会自动插入脚本初始化接口代码,包含 cli_setup()
、auto_setup()
和 simple_report()
方法

cli_setup:
当使用 python xx.py
来运行本文件,且不带任何命令行参数时,则 if not cli_setup()
的判断成立,自动使用 auto_setup
这个接口来对运行环境进行初始化。这样只需要在写 .py
脚本时,在 auto_setup()
里填写好指定的参数就能直接用 python xx.py
指令来运行脚本了。
同时,传统的 airtest run xx.air –-devices Android:///
命令行运行方式也不受影响,只要脚本检测到传入了命令行参数(即代码中的 if not cli_setup()
判断不成立,不走到里面的 auto_setup()
初始化去),就依然优先使用命令行参数来初始化脚本运行环境的相关配置项。
auto_setup :
主要作用是连接设备、设置日志路径。(不填入设备参数的情况下,都是尝试连接第一台安卓设备)
simple_report:
生成报告的简单接口,只需要在脚本中调用这个接口,然后传入几个必要的报告参数,就能够在脚本运行完毕之后,按照要求自动生成Airtest报告
注意:纯py脚本,需要自己设置生成报告,执行结束后才会产生报告,传参要注意和auto_setup对应
auto_setup 方法解析
def auto_setup(basedir=None, devices=None, logdir=None, project_root=None, compress=None):
"""
自动设置运行环境,如果未连接设备,请尝试连接android设备。
:param basedir: 脚本的Basedir, __file__也是可以接受的
:param devices: 连接列表中的设备uri
:param logdir: 默认为None表示没有日志,设置为' ' True ' '
:param project_root: project root dir for `using` api. 获取环境变量
:param compress: 截图图像的压缩率,范围为[1,99]的整数,默认为10
:Example:
>>> auto_setup(__file__)
>>> auto_setup(__file__, devices=["Android://127.0.0.1:5037/SJE5T17B17"],
... logdir=True, project_root=r"D:\\test\\logs", compress=90)
"""
if basedir: # __file__ 当前脚本的绝对路径--例如:E:/PythonPRT/AirTest/a1/test.py
if os.path.isfile(basedir): # os.path.isfile(path) 如果path是一个存在的文件,返回True。否则返回False
basedir = os.path.dirname(basedir) # basedir是当前脚本__file__目录的上级目录
if basedir not in G.BASEDIR: # G.BASEDIR 默认是一个空列表[]
G.BASEDIR.append(basedir) # 如果当前脚本路径不在G.BASEDIR列表内就添加进去
if devices: # devices为传入的设备参数 devices=["android://127.0.0.1:5037/8B9X16KBS"])
for dev in devices: # 找到传入的设备
connect_device(dev) # 连接设备
if logdir: # logdir 为传入的参数:log文件夹的路径,用于设置日志文件和截图的日志目录
logdir = script_log_dir(basedir, logdir) # 查看basedir上级目录有没有logdir文件夹
set_logdir(logdir) # 如果没有该目录,就新建一个目录
if project_root:
ST.PROJECT_ROOT = project_root # ST.PROJECT_ROOT 大概意思是获取环境变量,默认为None,一般不用管
if compress:
ST.SNAPSHOT_QUALITY = compress # 截图图像的压缩率,范围为[1,99]的整数,默认为10
在我使用过程中,由于结合了unittest框架,就拆分了原有的auto_setup方法,一个用来单独建立设备连接、一个用来单独设置日志文件夹
simple_report 方法解析
simple_report 接口,其实是1个简化版的生成报告的接口
filepath:脚本文件的路径,可以直接传入变量
__file__
logpath :log内容所在路径,如为 True ,则默认去当前脚本所在路径找log内容
logfile :log.txt的文件路径
output :报告的到处路径,必须以 .html 结尾
下面为源码:
def simple_report(filepath, logpath=True, logfile=None, output=HTML_FILE):
path, name = script_dir_name(filepath)
if logpath is True:
logpath = os.path.join(path, getattr(ST, "LOG_DIR", DEFAULT_LOG_DIR))
rpt = LogToHtml(path, logpath, logfile=logfile or getattr(ST, "LOG_FILE", DEFAULT_LOG_FILE), script_name=name)
rpt.report(HTML_TPL, output_file=output)
如果不指定任何参数,该接口会使用默认的参数生成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
auto_setup(__file__, logdir=True)
# 此处省略N条用例脚本
simple_report(__file__,logpath=True,output=r"D:\test\report02\log.html")
另外需要注意,要在用例脚本之后调用这个生成报告的接口,如在脚本开头调用,则意味着还没有运行后面的用例步骤,就已经生成了一份报告,最终不论脚本运行情况如何,都只能拿到一份空的测试报告。
注意点:
1.场景:很多同学找到了报告所在的本地文件夹之后,就直接把这个文件夹打包发给同事/领导查看,结果发现,同事/领导根本不能正常查看这个报告
2.分析:出现这个问题的原因其实是,使用1simple_report1生成的html报告,里面的一些图片路径和静态资源路径是 绝对路径 。当报告被发到别的电脑上时,html再想通过绝对路径找到这些图片资源和静态资源,就不可能了,所以才造成 其他人看到的报告完全没有图片和相应样式 的情况
3.解决方案:
①若是在AirTest IDE中运行时,IDE中提供了导出报告的功能,可以通过右键点击脚本名称,选择
导出报告
,之后再选择导出报告的存放路径即可,导出报告之后,图片资源和静态资源的路径都被改成了 相对路径 ,并且文件夹中还 打包了对应的静态资源文件 。这样我们再把导出好的报告发到别的电脑查看时,就不会出现上述问题了②但如果需要通过脚本的形式来导出1份报告,则需要用到logtohtml()方法,参考下面logtohtml方法解析
4.总结:如果不需要通过脚本的形式导出报告,仅需要生成报告在本地查看的话,只要使用
simple_report
接口即可
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()
ps:若不使用static_root参数时,则每次导出报告都会 打包静态资源文件 。虽然这样我们把导出好的报告发到别的电脑查看时,就不会出现确实静态资源无法查看的问题,但每次执行都要打包静态资源,每次发给别人也要把静态资源打包一起发送,不仅使报告文件变大,也会非常繁琐
static_root
每次导出报告时,目录下都会有一份静态资源文件static
,它包含了报告中的css和js等文件。一般来说,除非报告的样式做了某些更新,否则这些静态资源文件都是固定不变的。
所以我们可以将这些资源文件部署到静态资源文件服务器上,用例如 https://host:port/static/css/ 的路径来访问它。然后在生成报告时,将这个部署出来的服务器地址作为 tatic_root
的参数传过去:
h1 = LogToHtml(__file__, log_root=LOG_DIR, static_root='http://xx.xx',
export_dir=BASE_DIR, logfile=LOG_DIR + '\\log.txt', lang='zh',plugins=["poco.utils.airtest.report"])
h1.report()
这样报告中会默认去访问static_root
参数传入的服务器地址,在上面读取静态资源文件
这样可以很好地避免导出报告时重复拷贝这些资源文件造成的磁盘空间占用。
可以使用七牛云、又拍云等部署静态资源文件,我用的是又拍云
log方法解析
log()
接口方便插入用户自定义的一些log信息,将会被显示在Airtest报告中。log接口支持传入4个参数:
args
,可以是字符串、非字符串或者 traceback
对象;
timestamp
,用于自定义当前log的时间戳;
desc
,用于自定义log的标题;
snapshot
,表示是否需要截取一张当前的屏幕图像并显示到报告中
说明,在air脚本中不需要传log()方法以及生成报告方法,air脚本执行会自动配置生成
用法演示:
auto_setup(__file__, devices=["android://127.0.0.1:5037/8B9X16KBS?cap_method=JAVACAP&&ori_method=ADBORI&&touch_method=MAXTOUCH"])
#用例方法内容这里忽略
def test()
...
...
# 执行用例
test()
simple_report(__file__,logpath=True)
上面这种是正常用法,弊端是一旦前面有用例脚本执行失败,终止了整个脚本的运行,即还没有执行到生成报告的语句时,脚本运行就已经停止了,这样也不能够正常生成报告。
auto_setup(__file__, devices=["android://127.0.0.1:5037/8B9X16KBS?cap_method=JAVACAP&&ori_method=ADBORI&&touch_method=MAXTOUCH"])
#用例方法内容这里忽略
def test()
...
...
try:
# 执行用例
test()
except Exception as e:
log(e,timestamp=time.time(),desc='报错信息',snapshot=True)
finally:
# finally:保证最后都能生成报告,执行出错也会生成报告,并生成错误log
simple_report(__file__,logpath=True)
上面设置,当脚本运行报错时except抛出异常,不论脚本是否运行失败,最终都会生成1份运行报告
也可以用于插入自定义的一些log信息,并显示在Airtest报告中
修改simple_report接口(自定义airtest报告静态资源)
目的:前面说结合了unittest框架,拆分了原有的auto_setup方法,一个用来单独建立设备连接、一个用来单独设置日志文件夹。使用simple_report则可以每步用例都能生成一份单独的airtest测试报告
但由于源码中该接口的默认设置,报告静态资源读取的为python/lib/sit-pages/airtest/report目录下的静态资源,所以使用中有一些局限,于是研究了源码中simple_report方法,做了一些修改
先贴下simple_report
源码:
def simple_report(filepath, logpath=True, logfile=None, output=HTML_FILE):
path, name = script_dir_name(filepath)
if logpath is True:
logpath = os.path.join(path, getattr(ST, "LOG_DIR", DEFAULT_LOG_DIR))
rpt = LogToHtml(path, logpath, logfile=logfile or getattr(ST, "LOG_FILE", DEFAULT_LOG_FILE), script_name=name)
rpt.report(HTML_TPL, output_file=output)
通过源码发现也调用了LogToHtml
方法,我们再进入LogToHtml
方法内:
之前讲过static_root
参数的作用,现在就来分析这个参数
static_root默认为空,x or y if x is False,then y,else x
,所以这里取值为STATIC_DIR
查看STATIC_DIR
的值
首先__file__
是report.py
文件的path:
C:/Program Files/Python37/Lib/site-packages/airtest/report/report.py
语法:os.path.dirname(path)
功能:去掉文件名,返回目录
所以,STATIC_DIR
的值为:
C:/Program Files/Python37/Lib/site-packages/airtest/report
到这里就发现了,原来simple_report
方法使用的静态资源是python安装目录下的
所以我们要修改这个参数,使其指向我们项目目录内相对路径下的静态资源位置
怎么修改,其实很简单,就是修改STATIC_DIR
参数,把这个参数替换为我们自己重置好之后的静态资源目录,可以是项目报告目录内,也可以是服务器静态资源托管地址,
然后我们再在simple_report
内把调用的LogToHtml
方法修改为我们自己改过的新的lotohtml方法,这样就解决了报告静态资源读取的问题
AirTest常见的问题<从官网摘录>
设备连接参数怎么理解?
IDE提供了3个备用的连接参数: Use javacap 、Use ADB orientation 和 Use ADB touch ;
① 第一个 Use javacap ,是给部分无法正常看到手机画面、minicap初始化失败 的手机或设备用的,所以模拟器看到黑屏、部分特殊的平板等设备可以考虑勾选这个选项
② 第二个 Use ADB orientation 是 屏幕旋转 的,如果在安卓手机屏幕旋转方向检测有问题、或者是部分特殊的平板无法显示正确的屏幕方向时可以勾选
③ 第三个 Use ADB touch 是 发送adb指令来点击屏幕 ,效果很差,速度也很慢,不建议勾选,只有在部分无法点击屏幕的特殊安卓设备上才需要使用(例如智能后视镜、特殊型号的平板等设备上)
正常情况下,手机都可以点击,如果无法被点击(比如小米设备),一般都是因为手机设置有选项漏了打开,特别是 小米设备要注意开启允许模拟点击 的设置
如何取消脚本执行过程刷新的大量log信息
如果你不想这些log信息干扰你提取有效的报错信息时,你可以在脚本代码开头加上log级别的设定:
# -*- encoding=utf8 -*-
import logging
logger = logging.getLogger("airtest")
logger.setLevel(logging.ERROR)
这样运行时只会在初始化手机时会有少量log输出,初始化完毕后就能够对logger进行过滤了
在IDE中如何引入第三方库?
AirtestIDE内置了一个精简的python环境,缺少很多第三方库;如果需要在IDE中引入各种第三方库,可以先在本地的python环境中装好,再设置IDE使用本地的python环境,具体步骤如下:
① 请自行在你的电脑上安装属于你自己的Python(目前已经支持3.9),然后再安装对应的依赖库。你可以通过一些环境管理方案来管理你的python环境,例如virtualenv
② 在刚才安装好的本地Python环境中,安装airtest相关的依赖库,详细内容请参考文档:https://airtest.doc.io.netease.com/IDEdocs/run_script/1_useCommand_runScript/#python
③ 在本地安装完属于你自己的python环境后,再在IDE里面打开选项——设置——手动选择自定义python.exe路径即可,详细内容请参考文档:https://airtest.doc.io.netease.com/IDEdocs/settings/1_ide_settings/#python
RuntimeError : minitouch setup timeout
出现这个报错,最常见的是以下俩种情况:
① 手机系统是MIUI11,此时我们需要在点击“connect”按钮之前,把 “use Javacap + use orientation” 这两个选项勾选上,再点击“connect”按钮即可正常使用
② 手机的安卓版本是Android10,此时仅需要把IDE更新到最新版本即可;如果IDE使用的是本地的python环境,那还需要把本地python环境的Airtest更新到最新版本。
ADB版本冲突
在Windows环境中运行Airtest脚本时,假如运行环境本地已经存在有 adb.exe (例如装了android_sdk, 或者是别的工具带有adb.exe),并且ADB版本与Airtest中使用的不同,运行脚本时就可能会出现各种报错。
常见情况下,我们会看到log中包含这样的语句:
adb server version (40) doesn't match this client (39); killing...
* daemon started successfully *
如log所示,我们可以看到环境里面使用了40和39版本,版本冲突导致报错。解决办法是将本地所有的adb.exe统一成同样的版本就行。
另外,adb版本冲突,还会容易导致设备断开,出现报错。
解决办法依旧是将本地所有的adb.exe统一成同样的版本。
具体操作方法:
首先查看系统adb版本:进入cmd——adb version
再进入本地airtest\core\android\static\adb\windows
路径查看airtest的adb版本
如果不一致,就将airtest中的adb复制替换本地的adb即可
poco无限重启的解决办法
① 如果开了网络代理的话,需要先 关闭各种代理和VPN ,否则可能会影响到poco通讯
② 检查手机助手内是否对 pocoservice.apk 做了限制,例如在某版本的华为手机中需要开启 允许自启动 和 允许后台活动
③ 不能和uiautomator同时启动,否则会相互冲突
④ 可以尝试 重启手机 看看是否会恢复
用Airtest测试iOS一定要用macOS吗?
① 使用 xcode
部署 iOS-Tagent
需要在macOS完成;
② 部署好以后可以在macOS或Windows机器上连接到iOS手机进行测试。
如何删除iOS输入框的内容
① 对于Android平台,我们可以使用多种方法来删除输入框的内容,比如使用 keyevent 接口: keyevent("KEYCODE_DEL") ;或者使用Poco的 set_text() 方法: poco("xxx").set_text("") ;
②但对于iOS平台来说,暂不支持 set_text() 接口,也不支持 keyevent("KEYCODE_DEL") ,所以这俩种方法对于iOS的输入框来说是无效的。iOS支持 text() 方法,所以我们可以用 text("\b",False) ,来实现iOS输入框内容的删除
本地pip list可以找到airtest,但pycharm里找不到
这种情况很有可能就是pycharm使用了 虚拟环境的解释器 。
可以随意运行1个项目,然后查看运行结果窗口显示的解释器是不是你在本地安装的解释器的路径,如果不是,一般会带有 venv 的字样,例如:
D:\test\vene\Scripts\python.exe D:\test\test.py
1
这种情况只需要将pycharm从虚拟环境切换到安装了Airtest的本地环境即可
连接模拟器出现黑屏
① 先尝试在连接模拟器之前下拉勾选 “Use javacap” 选项,之后再点击“connect”按钮连接模拟器
② 如果仍然黑屏,断开模拟器,下拉勾选 “Use javacap” 和 “Use ADB orientation” 这俩个选项,再次连接即可
③ 如上述方式都不见效,可以上GitHub提一个issue,贴上使用的IDE和模拟器版本详情(GitHub地址:https://github.com/AirtestProject/AirtestIDE/issues)
模拟器勾选上连接参数之后还是连不上
连接模拟器的时候,我们需要勾选上一些备选的连接参数才能连接上模拟器,或者不让模拟器黑屏。
比如连接雷电时需要勾选上 Use javacap
,而连接夜神的时候需要勾选上 Use javacap
和 Use ADB orientation
这俩个选项。
但是在一些版本的模拟器中,即使勾选上了连接参数,仍然可能连接不上模拟器
这是因为在连接这些版本的模拟器时,不能自动安装上 Yosemite.apk
,我们可以通过手动安装这个 apk
,再重新连接模拟器即可。
报告可以导出发给别人看吗?
Airtest的报告是可以打包发给别人看的。
① 想要导出报告发给别人观看,我们需要生成报告的命令中传入 --export 参数,这样就可以将 包含静态资源文件和图片文件的报告 导出到一个指定的文件夹内,之后直接将整个文件夹发送给别人观看即可。
② 如果生成报告时不传入 --export 参数,那么报告中的静态资源文件和图片文件将使用 绝对路径 来访问,此时将整个文件夹发给别人观看,别人也是无法正常观看的。
复制IDE中log窗口生成的报告在命令行执行报错
常见的情况可能有如下几种:
① 复制的命令路径中含有空格,导致程序报错:找不到文件或者路径;只需要将命令中的路径用英文模式下的双引号括起来即可。
② 命令行中包含了&这样的符号,可能会导致命令行被中断,常见于一些勾选了 “use javacap” 这样的选项后连接的设备。但是&
这个字符需要转义才能够生效:Windows下改写成^&
,MAC下改写成/&
ImportError: DLL load failed: 找不到指定模块
常见的DLL报错会出现在以下2种情况中:
① 在 cv2
模块报 ImportError: DLL load failed: 找不到指定模块
的错
根本原因应该是DLL文件的缺失,你可以直接下载一个最新版本的AirtestIDE,在解压后的目录中找到 api-ms-win-downlevel-shlwapi-l1-1-0.dll
和 IEShims.dll
两个DLL文件(或者自行在网上搜索这俩个DLL文件也是可以的),然后将它们复制到 C:\Windows\System32
目录,重新运行代码即可解决。
② 若在 win.py
中 import win32api
时报 DLL load failed
:
<Module>
import win32api
ImportError: DLL load failed: 找不到指定的程序。
建议您运行下列指令,更新为223版本的 pywin32
:
pip uninstall pywin32
pip install pywin32==223
各种常用的pip命令
① 安装Airtest库: pip install airtest
② 安装poco库: pip install pocoui
③ 更新Airtest: pip install -U airtest
④ 更新Poco: pip install -U pocoui
⑤ 卸载Airtest库: pip uninstall airtest
特别注意:Poco依赖库是 pocoui
而不是 poco
,如果你发现你的环境里面同时存在 poco
和 pocoui
,请务必把 poco
卸载了,留下 pocoui
即可。
另外,如果你的电脑同时安装了 python3 和 python2 ,在不同python环境里面使用pip命令时可以使用如下方法:
# Python2
pip2 install XXX
python2 -m pip install XXX
# Python3
pip3 install XXX
python3 -m pip install XXX
AirTest框架源码目录结构解析
摘自:微信公众号(测试工程师小站)