做iOS自动化测试这几年,见过太多人卡在同一个地方:脚本还没写,设备先连不上。安卓那边插上USB、开个USB调试,几分钟就能跑起来;iOS这边要Xcode、要证书、要XCTest那套体系,链路一长,新手很容易一上来就心态爆炸。这篇继续Airtest实战系列,前面已经把工具安装和安卓端脚本基础讲完了,这次专门讲怎么用Airtest编写并跑通第一个iOS设备脚本,从真机连接到脚本骨架,再到我踩过的断连问题,一次说透。适合已经装好Airtest、看过安卓脚本样例,但第一次面对iOS真机的同学。
1. 为什么iOS脚本总先死在“连接”这一步
先说结论:iOS的自动化通道,本质上是“曲线救国”。安卓有官方提供的ADB通道,开发者模式一开,电脑就能对设备发指令;iOS这边没有对UI自动化完全开放的通用通道。Airtest要控制iOS真机,实际是把一个叫WDA的测试Runner跑到设备上,通过它暴露HTTP接口,脚本再通过这个接口去发点击、滑动、截图这些指令。所以你会看到Airtest连安卓时一个connect_device就完事,连iOS时却总冒出证书、信任、端口之类的问题。因为你的电脑不是直接操作设备,而是先要在设备上“安装”一个自动化代理,再和它对话。这一步没理清,后面所有报错都会显得毫无规律。
我把安卓和iOS在Airtest里的连接差异整理成一张表,方便你对比着看。
| 对比项 | 安卓 | iOS |
|---|---|---|
| 控制通道 | ADB直连 | WDA + XCTest |
| 设备授权 | USB调试开关 | 开发者模式 + 信任证书 |
| 连接方式 | USB即可 | USB或网络代理端口 |
| 控件能力 | 控件树较完整 | 控件树依赖额外服务 |
| 最常见卡点 | 驱动缺失 | 证书失效 / WDA未信任 |
1.1 iOS自动化链路:多出来的一层桥
WDA这个东西,说起来不算复杂。它本质上是跑到iOS设备里的一个测试进程,系统把U它当成一个日常的UI测试Runner来调度。WDA启动之后会在设备上开一个HTTP服务,Airtest脚本发过去的点击、滑动、截图请求,由它翻译成XCTest能理解的操作,再作用到屏幕上。也就是说,脚本与设备之间永远夹着这层代理。它在,链路就通;它不在,脚本什么都做不了。
理解这层关系很重要。后面遇到“连接不上”“中途断开”这类问题,第一反应不该是重写脚本,而是先问一句:WDA还活着吗?HTTP服务还回话吗?端口转发还在吗?我见过不少同事,报错之后一遍一遍调脚本,结果问题出在设备上的代理进程早就被系统回收了,纯做无用功。
1.2 开始写脚本前要满足的真机条件
在打开Airtest之前,请先把下面的条件一项一项过一遍,缺一项后面都会以各种诡异的方式报错。
- 一台Mac电脑,装着Xcode:WDA基本绕不开编译、签名、安装这几步,Windows上没法直接跑这套流程。
- 一台iOS真机:系统设置里的开发者模式必须先打开,这里最容易被忽略,iOS 16以后默认是关闭的,不手动开,连多久都连不上。
- 一个可用开发者身份:个人免费证书也能跑,但有个坑,后面会详细说,总之先准备一个能签名的身份。
- 一根靠谱的数据线:尽量用原装或带数据传输能力的线,很多“识别不了设备”其实只是线只能充电。
- 设备首次连接时需要点击“信任这台电脑”弹窗:这个弹窗不点,后续所有USB通讯都会被系统拦掉。
第一次连的时候,最容易被忽略的就是“开发者模式”这个开关。很多人把证书、WDA、代理全弄好了,连Airtest还是黑屏画面,最后发现只是手机上的开发者模式没开。我见过至少三次这样的现场,每次排查都要花半小时。
2. 一套能跑通的真机环境:安装、设备识别与WDA
环境准备这种事,第一次做最烦,因为每一步都可能失败。但只要你把“截图能出来”作为链路打通的标志,判断起来就简单很多。Airtest脚本再复杂,最终都依赖这条链路能不能用。
2.1 安装Airtest并让设备出现在连接列表里
Airtest推荐用IDE版本,不只是因为它可视化,更因为IDE把WDA的安装引导和设备列表做成了界面操作,对新手友好很多。纯命令行安装虽然也能跑,但WDA的准备环节很容易和电脑本身的代理端口、Xcode版本纠缠在一起,踩坑成本高。先用IDE把流程跑通,后面熟悉了再切命令行,会舒服很多。
安装完成之后,打开IDE的设备连接面板,选择iOS设备。如果看到设备出现在列表里,先别急着写脚本,用下面这段代码做一次连接验证:
from airtool.core.api import connect_device connect_device("iOS:///")不同版本的Airtest对iOS连接串的写法略有不同。新版一般可以直接写iOS:///,让它自动发现USB设备;老版本可能会需要先做端口转发,再写成iOS:///127.0.0.1:8100。你如果发现一行connect_device跑不通,别硬刚,去确认一下手头Airtest版本对应的连接方式。这类坑通常不是语法问题,而是版本差异。
2.2 WDA的首次安装与信任流程
WDA的首次安装,可以直接在Airtest IDE里通过引导完成。点击连接iOS设备后,IDE一般会提示需要安装WDA,按提示走。如果你想自己用Xcode装,也完全可以:打开WDA工程,选择Runner这个target,把签名身份选好,让Xcode把它跑到设备上。第一次安装成功后,必须去手机的“设置-通用-设备管理”里信任对应的开发者App,否则WDA根本启动不了。
这一步的报错通常长这样:脚本一启动就退出,日志里出现“unable to launch”之类的提示。如果你看到这类错误,不要急着去改脚本,先检查设备管理里有没有完成信任。这个问题在免费开发者身份上尤为明显,因为免费证书有效期只有7天,过期之后WDA就会启动失败,表现出来的就是一切正常,但就是连不上。
2.3 用一次截屏验证链路已通
环境到底装没装好,不用看一堆日志,直接截一张图最直观。验证脚本很短:
from airtest.core.api import * connect_device("iOS:///") snapshot(filename="first_check.png")跑完之后,去脚本目录看看first_check.png有没有生成。如果生成的是正常桌面画面,说明电脑到WDA再到屏幕的整条链路已经通了。这时候你才算真正拥有了编写iOS脚本的资格。很多第一次接触iOS自动化的人,会跳过这一步直接写业务脚本,结果一个简单的touch失败,分不清是环境问题还是脚本问题。
还有个小提醒:首次验证的时候,手机尽量保持解锁状态,屏幕常亮选项也顺手开一下。自动化脚本运行期间一旦锁屏,很多操作会直接失效,但报错信息非常不直观。
3. 第一个iOS脚本:截图匹配的启动、滑动与断言
环境通了,接下来才是真正写脚本。我强烈建议你第一个iOS脚本不要写业务细节,只做一个30秒能跑完的冒烟用例:启动App、等待首页、截图、滑动手势、断言一个关键元素。这套动作虽简单,却能覆盖Airtest在iOS上80%的基础能力,后续所有业务脚本基本就是这个骨架的扩展。
3.1 脚本目标与设计思路
这个脚本的任务是:启动被测App,确认首页加载出来,然后模拟一次滑动,再点击一个目标位置,最后截图并断言操作结果。被测的App你随意选,最好是自己熟悉页面的那种,哪怕只是一个自写Demo都行。
设计思路是这样的:先用截图匹配的方式做操作定位,因为它是Airtest在iOS上最稳定的定位手段。控件树能不能拿到,取决于App本身和Poco的部署情况,但屏幕上的画面一定存在,只要画面存在,截图匹配就一定可用。把这个最稳定的姿态作为第一个脚本的基础,能最大程度减少变量。
3.2 完整脚本骨架与API解释
直接给一份能跑的骨架,你把自己App的bundle id和两张截图模板替换掉就行。模板图片怎么来?在Airtest IDE里连接设备后,可以直接框选屏幕上的目标区域,保存成模板文件,放到脚本目录里。
from airtest.core.api import * def main(): connect_device("iOS:///") # 启动被测App,参数是bundle id,可在Airtest IDE设备面板查询 start_app("your.app.bundle.id") sleep(2) # 断言首页出现,tpl_home.png 是首页关键区域截图 assert_exists(Template(r"tpl_home.png", threshold=0.8), "首页加载成功") # 模拟从右往左的滑动,坐标按当前设备逻辑分辨率简单示意 swipe((400, 700), (80, 700), duration=0.5, steps=10) sleep(1) # 滑完后截图,便于后面看效果 snapshot(filename="after_swipe.png") # 点击目标按钮,tpl_button.png 是需要点击的位置截图 touch(Template(r"tpl_button.png", threshold=0.8), "点击目标按钮") sleep(2) # 断言点击后的结果页出现 assert_exists(Template(r"tpl_result.png", threshold=0.8), "操作成功") print("first ios script done") if __name__ == "__main__": main()几个关键的API,我展开说说。start_app接收的是bundle id,不是App名称,Airtest IDE的设备面板里能直接查到已安装应用对应的bundle id。Template是截图匹配的核心,图片路径一般写相对路径,模板文件放在脚本目录下最省事。threshold是相似度阈值,0.8是个比较常用的起点,太低容易误匹配,太高容易匹配不上,这个参数值得在你自己的App上多调几次。assert_exists在断言失败时,Airtest会自动保存一张现场截图到日志目录,这对后面排查问题非常有用。
3.3 为什么第一个脚本不急着上Poco
很多从安卓转到iOS的人,会习惯性地打开Poco取控件树,然后发现iOS这边Poco的接入成本高了一大截。原因在于,iOS端Poco同样依赖WDA提供页面快照,但不少第三方App,尤其是游戏和混合应用,页面树并不完整。你要先花精力把Poco本身调通,又得处理WebView、原生控件的差异,等于在业务脚本之外额外多扛一套复杂度。
第一个脚本的核心目标是建立最稳定的闭环,所以先用截图匹配。这套机制不依赖控件树,只要眼睛能看到界面,它就能工作。等你把基础链路跑稳了,再评估当前项目要不要上Poco,那时候你对设备和脚本的掌控力已经不一样了,不会因为Poco不工作而怀疑整个方案。
4. 跑通后的头号大坑:连接中断与WDA失联排查
脚本跑通不是终点,能稳定跑完一批才是。我在iOS脚本上遇到过最典型的问题,就是脚本跑得好好的,突然报连接丢失。表面看是网络或USB问题,实际上背后通常是一连串环境状态的叠加。这里有完整的排查链路,我把它梳理出来。
4.1 现场现象与日志特征
最常见的报错有两类。一类是connection lost,另一类是socket closed或Connection aborted。出现这类报错时,脚本执行到一半就停了,后续的断言和截图全部不会执行。如果你用的是Airtest IDE,界面上会直接断开设备连接;命令行跑的话,日志最后几行会出现明显的断开关键字。
这里有个容易忽略的点:跑脚本之前明明连接是好的,为什么跑到一半会断开?大概率不是初始连接的问题,而是运行过程中某件环境事件破坏了链路,比如电脑休眠、手机锁屏、Mac切换网络、WDA进程被系统回收。先别急着重写脚本,而是往下走排查流程。
4.2 从端口到证书的排查链路
我个人的排查顺序是固定的,从最底层往上查,基本五分钟内能定位。
第一步,看日志最后几行。Airtest的日志会记录连接状态和设备反馈,如果日志里出现了HTTP层面的异常,比如status code 500,说明设备端WDA可能已经不在正常工作状态。如果日志只停留在断连前的一条指令,那更偏向于物理链路问题。
第二步,检查端口转发和WDA状态。手动验证时,可以先停掉Airtest的连接,单独做一次端口转发,然后请求WDA的状态页。可以用下面这两条命令:
iproxy 8100 8100 -u 你的设备UDID curl http://127.0.0.1:8100/status如果status返回了正常的JSON信息,说明WDA活着,问题更多在脚本与设备的会话上;如果请求直接失败或返回一堆错误码,说明设备端的自动化代理已经没了,需要重新拉起WDA。注意端口不能和Airtest IDE同时占用,否则会互相冲突。
第三步,检查证书有效期。免费开发者身份签出的WDA,7天就过期。过期后最典型的表征是:Xcode能打开、连接能建立,但一切指令都发不出去,日志里报启动失败。这时候最快的处理方式是用Xcode重新把WDA跑到设备上,并去设备管理里重新信任一次。
下面这张表汇总了我遇到过的几种典型报错,方便你对照。
| 报错现象 | 最大可能原因 | 第一动作 |
|---|---|---|
| 无法连接设备或找不到设备 | USB未信任 / 开发者模式未开 | 看手机屏幕弹窗,确认开发者模式 |
| 脚本启动后立刻断开 | WDA未信任 / 证书失效 | 去设备管理信任,或重签WDA |
| 跑几个用例后socket closed | 长连接被回收 / 电脑休眠 | 关闭休眠,保持屏幕常亮 |
| 截图一直是黑屏 | 设备锁屏 / 屏幕常亮被关 | 解锁设备,重新点亮屏幕 |
4.3 让长跑脚本更稳的三个预防动作
排查只是事后补救,真正要做的还是预防。我自己在长期跑iOS脚本后总结出三条纪律。
第一,电脑休眠必须关掉。跑脚本期间Mac休眠,USB链路和网络通道都会中断,恢复后WDA不会自动重连,这是断连的第一大诱因。第二,手机屏幕常亮开着,并保持充电状态。iOS设备息屏后,后台进程很容易被系统调度回收,WDA一旦被回收,等再亮屏时链路已经断了。第三,长时间回归批次里不要一直复用同一个WDA会话,跑完一批用例后主动断开重连一次。长连接挂久了,iOS端容易出各种不可预期的状态,定期重置反而更稳。
5. 从“能跑”变成“好用”:报告、多机型适配与CI接入
第一个脚本能稳定跑通之后,你会很快遇到新的需求:这个脚本怎么给别人看?换个机型还能不能跑?怎么在持续集成里每天自动执行?这些问题都能解决,但需要注意的细节不少。
5.1 出问题后怎么从报告里快速定位
Airtest自带一套报告机制,每个步骤会关联截图和日志。在IDE里运行完脚本,点查看报告,可以看到每一步的操作截图、断言结果、失败现场。命令行跑脚本时,也可以自己生成报告:
airtest run untitled.air --device iOS:/// --log log/ airtest report untitled.air --log_root log/ --out report.html这套报告最实用的地方在于,断言失败时Airtest会把失败时刻的屏幕截图直接留在报告里。你不需要回看一整段录屏,只需要看断言对应的那一步,就能判断是页面没加载出来,还是相似度阈值不够导致误判。我在定位问题时,85%的情况靠报告加日志就能解决,连真机都不用重新拿起来。
5.2 多机型下截图匹配的适配习惯
截图匹配在单一机型上很稳定,一旦换成不同分辨率的设备,就可能出现匹配不到或者误匹配的情况。我的习惯是,把模板图片尽量从高分辨率设备上截取,保证画面细节更充分。对于不同屏幕尺寸的机型,单纯调大调小阈值不一定有效,更好的做法是给主要机型分别维护一套模板,或者让脚本读取设备分辨率,按比例换算关键坐标,再结合截图做二次确认。
还有一个细节:模板截图的区域不要太大,尽量选特征明显、文案固定的区域。比如App底部的一个固定按钮、导航栏的关键标题,这些不容易受页面滚动影响。如果你截了整张屏幕当模板,一点细微差异就可能匹配失败,调试成本会高很多。
5.3 接入持续集成前必须想清楚的三件事
如果你准备把这套脚本交给持续集成,先想清楚三件事。
第一,证书管理。免费身份的证书7天过期,在本地你还能手动重签,到了CI机器上,半夜证书失效会让自动化任务看起来像产品Bug,排查起来非常痛苦。有条件的话,至少弄个不限制有效期的开发者身份,或者写好证书刷新脚本。第二,设备队列。并行跑多台iOS设备时,每个设备上的WDA实例要分开管理,端口不能共享。一台设备对应一组独立转发和连接配置,否则会出现两台设备互相抢占的诡异问题。第三,失败重跑机制。iOS环境稳定性再好,也会有系统弹窗、网络抖动这种偶发干扰。给CI脚本加一层失败重跑,能明显拉高整体通过率,也比每次失败都人工介入要省心得多。
这一篇只做到“第一个脚本能稳定跑”这步。说实话,iOS自动化的水很深,很多问题都是环境层面的,脚本本身反而简单。我自己的经验是:不要一上来就铺很多用例,先用一个冒烟脚本把设备、WDA、截图、断言、报告整条链路跑三遍,确认每一次运行都稳定,再开始扩业务用例。最后再强调一次,跑iOS脚本期间电脑别休眠,手机屏幕常亮开着,不插电的话很容易中途锁屏,别问我怎么知道的。就先到这儿,后面有空继续写iOS里的Poco和复杂手势。