1. 为什么Windows自动化会选Airtest
做Windows桌面端的自动化测试,我一开始其实没打算用Airtest。原因是这个工具在游戏测试、App测试圈子里更出名,大家的印象里它就是个"连手机、点模拟器"的脚本工具。但等我真正在Windows系统上完成安装、跑通第一批用例之后,我的判断变了:这套工具在Windows桌面自动化场景里的表现,被严重低估了。这篇教程就围绕Airtest在Windows系统上的安装、使用和实际用例展开,把我在真实项目里踩过的坑和验证过的做法都写出来。
我负责的产品是一个Windows桌面客户端,每周发一版,手工回归要花大半天。刚开始想用录制回放类工具,脚本录完过两天界面一变就全废;后来试过几种控件识别方案,识别原生控件没问题,但遇到自绘界面、嵌入Web页面和第三方组件时就抓瞎。最后Airtest进入视野,是因为它默认基于图像识别,界面怎么画它不管,只要能截图、能找到目标图,就能点击和断言。这个思路恰好绕开了Windows自绘UI的识别难题。
1.1 一次真实的自动化回归需求
项目背景是这样的:Windows客户端包含登录、工作台、任务列表、设置中心几个模块,其中任务列表里大量元素不是原生控件,而是用自绘技术渲染出来的。用传统控件自动化工具去拿控件树,拿到的要么是空白,要么是几个无关节点,根本定位不到按钮。手工测试则每天重复点同样的位置,随着版本更新,按钮坐标常常偏移几个像素,整个用例就崩了。
后来我们转变思路:不用控件坐标,改用图像特征定位。也就是说,先把要点击的按钮截图保存下来,脚本运行时在屏幕上寻找这张小图,找到之后把这个按钮的实际中心点算出来,再执行点击。这个思路下,哪怕按钮位置变了,只要外观变化不大,脚本依然能跟上。Airtest的核心能力恰好就是这个,它把截图定位、模拟点击、图像断言都封装成了现成API,比从零写OpenCV匹配要快得多。
1.2 几个主流方案的横向对比
在定下Airtest之前,我实际对比了好几个工具,包括Appium、Pywinauto、TestComplete以及一些商业录制工具。每家的强项不一样,适合的场景也不一样。
- Appium:跨平台生态完整,移动端能力强,但Windows桌面端支持需要额外的WinAppDriver,配置繁琐,对自绘UI依然无能为力。
- Pywinauto:对Windows原生控件识别很准,适合传统Win32和部分Qt应用,但纯图像定位能力基本没有,遇到自绘UI就难以下手。
- TestComplete:功能全面,质量也好,但价格高且学习曲线陡,个人和中小团队很难直接上。
- Airtest:图像识别是第一优先级,也支持Poco控件树,Windows端可以通过AirtestIDE一键连接,还能生成带截图的HTML报告,算是覆盖了自动化测试的完整闭环。
我最终的选择逻辑很简单:优先要能解决自绘UI定位问题的方案,其次要上手够快、能出报告、能对接命令行。Airtest在这几个维度的平衡是最好的。
1.3 Airtest的适用边界
任何工具都有自己的边界,Airtest也不是万能药。它在以下场景我实测下来效果很好:
- Windows原生窗口、游戏客户端、自绘界面、嵌入WebView的混合应用。
- 需要做跨端回归的情况,同一套图像定位逻辑可以从Windows切到Android,脚本改动很小。
- 界面经常变,但视觉特征稳定的产品,光是微调图片素材就能继续跑。
不太适合的场景也有:需要大量后端接口断言、需要高频读取表格数据进行复杂计算、对测试运行时间要求极苛刻的任务。图像识别本身有毫秒级的截图和匹配开销,把它当成纯单元测试工具用,方向就错了。
2. 动手前的环境判断与版本选型
很多人安装Airtest失败,不是因为软件本身问题,而是环境没有提前处理好。我自己在Windows上装过纯命令行环境和AirtestIDE两条路线,下面把环境准备的关键点整理清楚。
2.1 Windows版本、Python版本与显示缩放的影响
Airtest对Windows版本的要求不算苛刻,我在Windows 10和Windows 11上都跑通过。需要注意的倒是Python版本选择。官方文档长期声明支持Python 3.6到3.9,后来版本对3.10、3.11兼容性也在改善。我实测下来,Python 3.10环境装airtest和pocoui很顺畅,Python 3.12上则可能遇到个别依赖包没有预编译版本的情况,所以保守一点建议直接用Python 3.10。
显示缩放是我这一路遇到的最大坑。Windows系统默认把显示缩放设为125%或150%时,Airtest截图和点击的坐标会产生偏差,截图看起来是正常的,但点击位置会往右下角偏。后面踩坑章节我会详细说解决方案,环境准备阶段就要有意识:要么把跑测试机器的显示缩放固定为100%,要么所有脚本素材都在同一个缩放比例下截图。
另外,如果你的测试对象是Windows桌面应用,尽量让被测窗口保持固定的启动大小和位置,不要让它最大化或移动到副屏。窗口位置变化虽然图像识别能扛住,但部分极端情况会因遮挡导致匹配失败。
2.2 两条路线:AirtestIDE与纯pip脚本
Airtest提供两种使用方式,新手和自动化老手的选择差异很大。
| 对比项 | AirtestIDE | 纯pip命令行 |
|---|---|---|
| 安装方式 | 下载压缩包解压即用 | pip install airtest |
| 自带Python环境 | 自带,独立 | 依赖系统Python/虚拟环境 |
| 图形化录制 | 支持,适合新手 | 不支持 |
| 运行与报告 | 界面一键运行/打开报告 | 命令行运行/生成报告 |
| 适合场景 | 脚本开发、调试、演示 | CI集成、批量执行、正式回归 |
我个人的建议是:IDE和pip环境都要装。IDE负责开发脚本、截图素材、单步调试,pip环境负责跑批量回归和对接持续集成。两条路线共用同一个脚本目录,互不冲突。
AirtestIDE本质上是一个打包好的开发环境,它会带一份独立的Python,所以即使你系统Python装坏了,IDE里的脚本依然能跑。但如果要把脚本交给CI服务器执行,就得靠pip环境。
2.3 安卓模拟器与真机的前置准备
如果你除了Windows桌面应用,还想顺手验证移动端场景,那需要先准备好ADB环境。Airtest连接Android设备时,依赖ADB来做设备通信。真机需要开启开发者模式并打开USB调试,模拟器则有各自的调试端口。
常见Android模拟器端口并不统一:夜神模拟器常见的是62001,雷电模拟器常见的是5555或5554,逍遥模拟器常见的是21503,MuMu模拟器有7555和16316等。需要注意的是,这些端口会因为模拟器版本不同而变化,最准确的办法是在模拟器设置里找到"ADB调试端口",或者直接运行adb devices查看当前识别到的设备编号。
如果你暂时只做Windows桌面应用,这节可以跳过。但安装完AirtestIDE后,它自带的ADB会自动启动,连接移动设备时基本不用额外配置。
3. 完整安装过程:IDE和命令行双路线实测
安装过程看起来简单,但实际执行时有不少细节。我把两种路线从头到尾走了一遍,记录下每个关键步骤和可能出现的问题。
3.1 安装AirtestIDE
第一步是去Airtest官网下载AirtestIDE。下载完成后得到的是一个压缩包,解压到本地目录,进入目录后双击AirtestIDE.exe即可启动,不需要执行安装程序。这里有一个非常重要的实测经验:解压路径不要带中文,也不要有空格。刚开始我把IDE放到D:\自动化工具\AirtestIDE,启动时部分功能异常,ADB初始化失败,改成D:\Tools\AirtestIDE之后一切正常。
启动IDE后,界面左侧是设备连接区,右侧是脚本编辑区和截图区。首次启动会自动检出当前接入的Android设备,同时会尝试初始化ADB。如果杀毒软件弹窗拦截,需要允许程序运行,否则设备连接和截图功能会直接失效。
AirtestIDE自带Python运行环境和全部依赖,所以你打开IDE就能新建.air脚本并直接运行。这也是为什么我建议新手先装IDE,它帮你屏蔽掉了环境变量、依赖版本这些乱七八糟的事情。
3.2 用pip安装独立运行环境
如果要在命令行环境里跑脚本,或者要让脚本可以在CI上执行,建议单独建一个虚拟环境来安装Airtest。我常用的命令如下:
python -m venv .venv .venv\Scripts\activate pip install --upgrade pip setuptools wheel pip install airtest pocoui这里安装两个包:airtest是核心自动化框架,负责截图、点击、查找图像;pocoui是Poco SDK,用于控件树定位。如果你只需要纯图像识别,不打算用Poco,只装airtest也足够。
安装完成后可以用以下命令验证:
python -c "import airtest; print(airtest.__version__)"我在Windows上实测输出类似1.3.0的版本号。如果pip下载速度很慢,可以临时指定国内镜像源:
pip install airtest pocoui -i https://pypi.tuna.tsinghua.edu.cn/simple3.3 首次启动验证
环境装好后不要急着写复杂用例,先做一个最小验证:连接Windows设备并截一张图。
在AirtestIDE里,设备连接区下拉选择"Windows",点击连接,界面会刷新一张桌面截图。这说明Airtest已经能访问当前Windows会话画面。
命令行环境下,可以写一个最简单的脚本:
from airtest.core.api import * auto_setup(__file__, logdir=True, devices=["Windows:///"]) snapshot("first_snapshot.png")运行后,脚本会截取当前Windows桌面并保存图片。如果这一步能成功,说明截屏、设备连接、日志目录都正常,可以进入正式的脚本编写阶段。
3.4 安装失败的常见处理
我实际踩过的安装失败主要有三种。
第一,pip install过程中报编译错误。这类问题多半是某个依赖包需要本地编译,而系统缺少C++构建工具。解决办法是提前装好Microsoft C++ Build Tools,或者换用Python 3.10版本避开部分预编译缺失的坑。
第二,提示No module named 'airtest'。看到这个报错先别慌,大概率是当前命令行激活的Python环境和你安装包时用的Python环境不一致。在Windows上执行where python,看一下当前用的是哪个解释器,确认它和你pip install时用的是同一个。
第三,IDE启动后ADB一直转圈。可以检查IDE解压路径是否含中文,也可以手动在设置里把ADB路径指向你系统里已有的ADB工具。多数情况是杀毒软件把IDE内部的ADB进程拦了,放行之后就好了。
4. 第一个脚本:用Airtest跑通Windows窗口上的应用
安装验证通过后,接下来就是核心环节:让Airtest在Windows窗口应用上完成一次真实的点击流程。这里我用Windows自带的记事本来演示,思路也适用于任何桌面应用。
4.1 连接Windows设备的两种方式
在IDE里连接Windows设备只需要在设备类型里选择Windows即可,这个方式适合开发调试时使用。命令行脚本里,则需要通过设备URI来指定连接目标。
from airtest.core.api import * connect_device("Windows:///")这段代码表示连接当前Windows桌面。Airtest会捕获当前整个桌面画面的截图,所有图像识别都在这个画面范围内查找。如果同一时间打开了多个窗口,需要注意目标窗口最好在最前面,不要被其他窗口挡住。想要更精确地控制到某个固定窗口,可以在脚本里用系统命令启动应用后,先进行窗口前置操作,再做后续点击。
4.2 图像识别核心API:touch、wait、exists、assert_exists
Airtest最常用的是下面这几个API,都是围绕Template图片对象工作的。
| API | 作用 | 关键参数 |
|---|---|---|
touch | 点击找到的目标 | v、times、duration |
wait | 等待目标出现 | v、timeout、interval |
exists | 判断目标是否存在 | v |
assert_exists | 断言目标必须存在 | v、msg |
assert_not_exists | 断言目标必须不存在 | v、msg |
Template是图像模板的构造器,核心参数有三个:
threshold:图像匹配阈值,默认0.6,表示相似度至少达到60%才认为匹配成功。阈值越高,匹配越严格,误报越少,但也更容易漏匹配。target_pos:点击位置相对于匹配区域的偏移位置,取值范围1到9,对应九宫格。默认是5,也就是中心点。如果按钮中间有点击无效区域,可以改成1到9中的其他位置。rgb:是否开启彩色匹配。默认为False,只比对亮度结构;如果按钮颜色很重要,可以开启rgb=True。
一个典型写法是这样的:
touch(Template(r"btn_open.png", threshold=0.8, target_pos=5))这个写法表示:在屏幕上找到btn_open.png这张图,相似度达到80%后,点击它的中心点。
4.3 用IDE录制时的注意事项
AirtestIDE提供录制功能,你连接Windows设备后点击录制,然后像操作真实应用一样点击按钮,IDE会把每一步操作转化为脚本。录制功能对快速上手很有帮助,但直接生成的脚本有很明显的短板:它默认记录的是坐标点击,而不是图像识别点击。
比如录制后你会看到:
touch((860, 540))这种脚本在窗口位置一变就会失效。正确做法是把坐标点击替换成图像识别:
touch(Template(r"btn_ok.png", threshold=0.8))替换时注意,所有图片素材都需要先用截图工具裁剪保存。在AirtestIDE里可以直接框选区域保存模板图片,非常方便。我的习惯是先手动跑一遍应用流程,把每个要操作的元素都截一遍图,再组织脚本。
4.4 跑脚本与查看报告
IDE里直接点击运行按钮即可。命令行环境下,需要先进入脚本目录,然后执行:
python -m airtest run calc.air --device Windows:/// --log calc_log这里的calc.air不是单个文件,而是一个目录。Airtest约定的.air项目目录里包含一个__init__.py脚本和若干图片素材。运行结束后生成日志目录,再执行报告生成命令:
python -m airtest report calc.air --log_root calc_log --outfile calc_report.html打开生成的HTML报告,可以看到每一步操作的前后截图、识别结果和断言结果。这个报告我在实际项目里直接发给开发和测试团队,比贴一堆日志高效得多。
5. 一个完整实测用例:Windows计算器自动计算
光讲理论不够,我拿Windows系统自带的计算器设计了一个完整用例:自动点击数字1、加号、数字2、等号,最后断言结果区显示3。这个用例逻辑简单,但覆盖了启动应用、图像定位、点击、断言、报告生成五个环节。
5.1 用例需求与设计
计算器界面上的数字键和运算符键在外观上相对稳定,适合作为图像识别示例。用例流程如下:
- 启动
calc.exe,等待窗口出现。 - 点击数字键1。
- 点击加号键。
- 点击数字键2。
- 点击等号键。
- 断言结果区域显示数字3。
在开始写脚本前,我需要先把这些键的图片截下来,放到脚本目录里。素材文件建议用英文命名:btn_1.png、btn_2.png、btn_plus.png、btn_equal.png、result_3.png。这样脚本可读性更强,也避免中文路径在部分环境下引发编码问题。
5.2 编写计算器脚本
脚本内容如下:
import subprocess import time from airtest.core.api import * auto_setup(__file__, logdir=True, devices=["Windows:///"]) subprocess.Popen("calc.exe") time.sleep(2.5) touch(Template(r"btn_1.png", threshold=0.8)) touch(Template(r"btn_plus.png", threshold=0.8)) touch(Template(r"btn_2.png", threshold=0.8)) touch(Template(r"btn_equal.png", threshold=0.8)) assert_exists(Template(r"result_3.png", threshold=0.8), "1+2的结果应该显示为3")这里有几个细节需要说明。threshold=0.8是我在计算器用例里实测比较合适的值,比默认0.6严格,不容易误点相邻按键。图片素材截取时,建议只截取按键本体,不要带周围大片背景,否则背景变化会干扰匹配。time.sleep(2.5)是为了等计算器启动完成,如果你的机器较慢,可以适当调大。
运行结束后,如果断言失败,报告里会标红错误步骤,并显示实际截图,你可以直接看出是没找到图片还是找错了位置。
5.3 识别失败时怎么调
计算器脚本看起来简单,但我在实测定中仍然遇到几次识别失败的情况。最典型的是结果区域的3没有匹配上,原因是计算器在不同系统主题下,结果数字的颜色、粗细会有差异。
遇到这种问题,我通常按下面顺序排查:
- 重新截图当前实际运行的结果区域,替换旧素材。
- 调整
threshold,如果图片截得足够准确,可以降到0.7;如果图片截得不够准,反而要升到0.85以上。 - 开启
rgb=True,用颜色辅助匹配,避免相近形状的数字误匹配。 - 用
wait替代直接touch,给页面加载留出时间,避免元素还没出现就去点击。
排查完之后,我会在报告中对比前后两轮截图,确认新素材的匹配位置确实是目标按钮。
5.4 批量执行多个Windows用例
计算器只是一个示例,实际回归时不可能只跑一个用例。我的做法是把每个业务流程建立为一个独立的.air项目目录,然后写一个Python脚本循环执行它们。
python -m airtest run login.air --device Windows:/// --log logs/login_log python -m airtest run task.air --device Windows:/// --log logs/task_log python -m airtest run settings.air --device Windows:/// --log logs/settings_log每条命令执行完,用airtest report生成各自报告。如果用例之间有先后关系,就把它们按顺序串起来;如果没有依赖,也可以并行跑,但并行时要注意设备冲突,同一台Windows机器不建议多个Airtest进程同时截图。
6. 真实踩坑记录:从识别失败到设备断连
工具装上、脚本能跑只是第一步,真正让Airtest在Windows上稳定的,是我连续踩了一周坑总结出来的一堆排查经验。下面这些问题是论坛和文档里不太会详细写的。
6.1 ADB连不上安卓设备
虽然这篇文章主场景是Windows,但很多人会在同一台机器上同时做Android和Windows测试,ADB问题很常见。现象是AirtestIDE里一直显示"设备连接中",或者脚本报错找不到设备。
排查步骤我建议按顺序来:
adb devices如果列表里没有你的设备,先检查模拟器的ADB调试开关,或者真机的USB调试授权弹窗是否点了允许。如果列表显示offline,执行:
adb kill-server adb start-server这能解决大部分ADB假死问题。还有一部分情况是模拟器多开导致端口冲突,那就关闭多余模拟器,或者手动指定模拟器端口。
6.2 多显示器与DPI缩放导致点击偏移
这是我在Windows环境遇到的最隐蔽的坑。一台带副屏的测试机器,主屏缩放是125%,副屏缩放是100%。Airtest在屏幕上截图看起来一切正常,但点击时就是点到按钮右下角,坐标明显偏移。
根因是Windows的DPI缩放机制导致逻辑坐标和物理坐标不一致。Airtest截图时基于物理像素,而点击事件在某些版本上按逻辑像素换算,于是出现了偏差。解决办法有几种:
- 把测试机显示缩放统一设置为100%,这是最省事的方案。
- 如果项目必须用缩放,那就保证脚本里所有图片素材都在目标缩放比例下截图,且不要跨不同缩放的屏幕运行。
- 多显示器环境只在主屏跑测试,避免窗口落到副屏。
这个坑对我的项目影响很大,最后专门把CI测试机设置成固定分辨率、固定缩放100%才彻底解决。
6.3 图片识别率突然下降
有时候脚本昨天还全绿,今天就跑挂了,报错信息是找不到某个模板图片。我遇到的真实原因有几个:Windows更新改变了系统深色/浅色模式,计算器和设置界面整体配色变化;另一个应用弹窗恰好遮挡了目标区域;还有窗口大小被用户拖动过,导致按钮缩放变形。
应对方法是在脚本关键步骤前增加wait等待,让目标完全显示后再操作。同时统一测试机的系统主题,关闭弹窗干扰。对于窗口大小问题,可以在自动化前置步骤里通过快捷键或命令把窗口恢复成标准尺寸。
6.4 脚本运行到一半卡死
卡死和找不到元素不一样,它是脚本在某个touch或wait上长时间不返回。大多数原因是目标图片一直没有出现,而代码里没有给timeout,或者默认超时被设成了无限长。
我在写脚本时有一个强制要求:所有wait调用必须显式传timeout参数,比如:
wait(Template(r"btn_submit.png"), timeout=20)同时给可能抛异常的步骤加上try...finally结构,即使脚本失败也在finally里做清理动作,比如截图留底、关闭被测应用。这样至少能保证失败现场有据可查。
6.5 环境变量与包冲突
这个坑主要出现在同时装了AirtestIDE和pip环境的机器上。AirtestIDE自带Python,系统也装了Python,命令行执行python和pip时,用的可能是两个完全不同的环境。
解决办法是:在命令行里先激活虚拟环境,再执行pip list确认airtest在这个环境里。运行脚本时,也显式使用虚拟环境里的python -m airtest run,不要直接敲airtest run。这看起来是小问题,但能省掉非常多定位时间。
7. 让Airtest脚本更可靠:我的几个实操建议
最后这部分不是官方文档里的基础用法,而是我在真实项目里慢慢养成的习惯。按这套习惯写脚本,维护成本会低很多。
7.1 图像素材管理
素材是Airtest项目最容易失控的地方。我的管理规范是这样:
- 素材统一放在对应
.air目录下,不用绝对路径引用,统一用相对路径。 - 命名规则用
模块_元素_状态的格式,比如task_btn_create.png、login_input_user.png。 - 素材只截取元素本体,尽量小,但不要小到丢失特征。一个完整按钮是一张图,不要为了省事截整个窗口。
这样维护下来,界面上一个按钮变了,只需要替换对应图片,脚本逻辑不动。
7.2 合理设置超时与重试
直接使用touch(Template(...))虽然方便,但如果目标还没出现,会直接失败。我更推荐先写一个通用的点击函数:
def click_tpl(tpl_path, timeout=30): el = wait(Template(tpl_path), timeout=timeout) touch(el)这样每次点击前都会等待元素出现,超时时间也能统一控制。对于网络请求较慢的界面,这个函数能显著降低脚本脆性。
7.3 优先用Poco控件树,不要硬怼图像
图像识别能解决大部分问题,但遇到列表项、动态加载内容、多种字体颜色变化的场景,纯图像匹配会很吃力。这时候我建议尝试Poco控件树。
以Android设备为例,Poco初始化如下:
from poco.drivers.android import AndroidPoco poco = AndroidPoco() poco(text="确定").click()Windows端是否能用Poco,取决于你的目标应用类型。自绘UI和游戏引擎控件通常有对应Poco驱动,原生Win32控件则不一定能拿到理想控件树。我的原则是:能用控件树定位的,优先用控件树;控件树拿不到的,再用图像识别兜底。两者结合,脚本的可靠性会提升一个档次。
7.4 CLI与CI集成
如果你和我一样需要每天自动化回归,把Airtest脚本放进CI是绕不开的一步。我建议项目里维护一个requirements.txt,锁定依赖版本:
airtest==1.3.0 pocoui==1.0.85CI执行时按以下顺序:
- 拉取代码,创建虚拟环境。
- 安装依赖。
- 执行
airtest run运行用例。 - 执行
airtest report生成报告。 - 把报告上传到文件服务或发送通知。
这里有一个容易忽略的点:CI机器上的Windows账号不能自动锁屏,锁屏状态下Airtest无法截取桌面画面,脚本会直接失败。我给CI机器设置了长时间不锁屏,才保证定时任务可以顺畅运行。
7.5 日志与数据规范
日志目录一定要规划好。auto_setup里设置logdir=True之后,每一次运行都会生成截图记录;如果后续要对比历史结果,最好在--log参数里带上时间戳目录。
测试数据方面,不要把敏感信息写死在脚本里。登录这类用例,建议通过命令行参数或配置文件传入账号密码,脚本里只读参数,这样换环境跑的时候不用改脚本代码。
最后再分享一个我自己的习惯:每次改动素材图片后,我都会先用IDE手动跑一遍单步调试,确认点击位置在目标元素上,再交付给CI执行。图像识别类自动化最怕的就是素材和脚本脱节,把素材验收这一步前置,能省掉很多后期排查成本。