ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

iOS自动化测试入门:WebDriverAgent搭建与远程控制实战

iOS自动化测试入门:WebDriverAgent搭建与远程控制实战

说起iOS自动化测试,绕不开WebDriverAgent这个名字。我第一次接触它时,一直以为iOS设备想被远程控制是一件很麻烦的事,直到真正把WDA跑起来,用一条curl命令让手机完成点击、滑动、甚至输入,才意识到它本质上是给设备装了一个“可编程的遥控器”。WDA的全称是WebDriverAgent,它跑在iOS设备内部,把设备的UI状态和触摸能力通过HTTP接口暴露给电脑端。电脑端不管是手工curl还是用Appium,都可以向它下发指令,让iOS设备执行点击、滑动、截图、获取页面结构这些操作。

这篇教程面向刚开始接触iOS自动化测试、想快速给真机或模拟器做远程控制的测试工程师和开发者。我会把整个上手的链路拆开:先讲清楚它到底是怎么工作的,再带你搭环境、构建、启动、调用,最后把我在实际配置中踩过的坑整理成故障排查清单。整个过程不需要你懂太多Xcode的高级用法,但需要在macOS环境下手动操作一遍,之后你就可以把这一套沉淀成团队里的基础能力。

1. 先理解WebDriverAgent到底怎么替你把手机“遥控”起来

1.1 它其实是一个跑在设备上的HTTP服务

很多第一次接触WDA的人会被名字吓到,以为是什么高深的系统级方案。其实它的本质并不复杂:WDA是被Xcode当作一个XCTest用例跑在iOS设备上的,启动后会在设备内部开一个HTTP服务,默认监听8100端口。这个服务接收的是WebDriver协议风格的JSON请求,然后转译成XCTest框架能执行的UI操作。

这个设计非常讨巧。因为WDA本身不是一个普通安装到桌面的App,而是通过Xcode的测试机制加载到设备上的Runner程序。这也解释了为什么WDA无法像普通App那样被独立“安装”到手机上然后直接双击打开,它必须依赖Xcode的构建和启动过程。

1.2 一次点击从电脑到手指的完整链路

我习惯用一个场景来解释WDA的工作链路:假设你在一台用了很久的旧手机上,想要让电脑控制它点击屏幕上的“登录”按钮。

电脑端先向设备IP的8100端口发送一条HTTP请求,例如“找到按钮并点击”。WDA的服务端收到这个请求后,会调用XCTest的既有能力去遍历当前屏幕上的UI元素,拿到按钮的属性信息。然后WDA再把“点击这个位置”转成一次触摸事件,由iOS系统本身去执行。执行完成后,WDA会把结果返回给电脑端。

整个过程看起来像是远程控制,但实际上它不是通过屏幕共享、键盘鼠标模拟这一类方式实现的,而是把控制抽象成了“找元素、执行操作、返回结果”的自动化链路。这个区别非常重要:WDA更适合用来做测试、批量操作、UI状态采集,而不是像远程桌面一样实时操控。

1.3 为什么它比自己做一套设备控制方案更靠谱

你可能会问,为什么不直接在设备端写一个后台服务,自己调用私有API实现点击和获取UI树?因为这条路非常容易被iOS版本更新打断,而且需要处理大量系统权限问题。WDA的价值在于,它站在了XCTest这个官方测试框架的肩膀上,把UI自动化能力包装成了标准HTTP接口,同时被Appium生态长期维护。站在普通开发者的角度,我们不需要重新造轮子,只需要学会怎么把它启动起来、怎么调用接口就够了。

这个选择还有一个额外的好处:因为走的是HTTP协议,所以客户端语言不受限制。你可以用curl命令验证,用Python写脚本,用Appium做测试框架,甚至用Node.js写工具链。后面讲实操时,我会坚持先用curl把链路跑通,再考虑上框架,因为只有亲手看到HTTP请求和响应的过程,才能理解底层逻辑。

2. 上手前的环境准备:这些条件缺一个都会卡住

2.1 必要的硬件和系统组合

开始之前,先确认你的环境满足最基础的条件。WDA目前主要面向iOS真机和模拟器,常见的组合是一台macOS电脑、一台iPhone或iPad、数据线,以及能让电脑和手机访问到的同一局域网网络。

如果只做模拟器调试,WDA会运行在电脑本机的模拟器内部,访问地址通常是127.0.0.1,链路会简单一些。但如果你想体验真正的“远程控制”场景,建议直接上真机。真机上的WDA服务会监听设备自己的局域网IP,只要电脑能访问这个IP,就能把设备当做一个可编程的测试终端。这个场景也更符合日常工作里“多台手机集中管理”的需求。

另外要注意,WDA是一个自带版本更新节奏的项目,过老的Xcode和iOS版本可能无法兼容。建议至少保证Xcode是近几年内的版本,iOS设备系统版本也不要太旧。如果你手头的代码版本和系统版本差距特别大,启动阶段大概率会出现编译错误,这通常不是你的操作问题,而是版本匹配问题。

2.2 开发者签名这个绕不过去的坎

在真机上启动WDA之前,必须在Xcode里配置开发者签名。WDA通过XCTest机制运行,而XCTest必须有合法的签名才能在真机上执行。即使你只用模拟器,有些场景下Xcode也会检查签名设置,所以这一步不能跳过。

打开Xcode后,进入项目的Signing & Capabilities页面,在Team附近选择合适的开发者账号。如果你没有付费开发者账号,用免费的个人账号也可以跑起来,但会有一个限制:签名有效期短,通常一周后需要重新设置签名并重新构建。如果你的团队有正规开发者账号,直接用团队签名就好,能省掉很多反复签名的麻烦。

还有一个小细节,WDA项目里默认的Bundle Identifier可能会在你本机和已有设备上的App冲突。我习惯在配置签名时顺手改成一个带有个人标记的唯一Bundle ID,比如格式写清团队前缀加wda runner,避免因为Bundle ID冲突导致安装失败。

2.3 拉源码并完成依赖准备

WDA是一个开源项目,获取它的常规途径就是把官方仓库clone到本地。clone下来之后,不要直接急着打开Xcode工程,先跑一下项目自带的环境准备脚本,把依赖的第三方库和配置都拉齐。

我通常的顺序是:

brew install carthage git clone <WebDriverAgent官方仓库地址> cd WebDriverAgent ./Scripts/bootstrap.sh open WebDriverAgent.xcodeproj

这里面最关键的是./Scripts/bootstrap.sh这步。它的作用是检查并安装项目所需的依赖工具和库,如果依赖不齐,后面用Xcode打开工程时会有大量的编译报错。曾经有朋友跳过这步直接编译,结果卡在缺少第三方模块的报错上绕了很久。

2.4 真机上的开发者模式

如果你用的是iOS 16或更高版本的真机,还需要提前在设备的“设置-隐私与安全-开发者模式”里打开“开发者模式”。这个选项通常会在你第一次用Xcode连接设备时出现,没打开的话,WDA启动时可能直接失败,而且报错信息不够直观。我建议在连接设备前就先确认这项已经开启,省得后续排查浪费时间。

3. 构建启动与手工调用:从PC控制手机的第一步

3.1 在Xcode里选择正确的Target和Scheme

用Xcode打开WebDriverAgent工程后,你会看到多个目录和Target。如果是做普通的UI自动化控制,认准WebDriverAgentRunner这个Target就行。它在工程里是一个测试Runner,负责启动HTTP服务并接收指令。Xcode窗口顶部的Scheme设置也要对应选到WebDriverAgentRunner,然后把Destination选成你的真机设备。

选错Target是新手最常见的错误。有些人会把WebDriverAgentLib当成运行对象,结果编译成功却看不到服务启动。WebDriverAgentLib只是被引用的核心库,不会单独对外提供服务。我自己的习惯是选好Target之后,先在Signing & Capabilities里确认签名,再选择设备,最后再跑测试,顺序尽量不要乱。

3.2 启动WDA的完整操作

WDA是测试Target,所以启动它的方式不是点击Xcode的Run按钮,而是用Product菜单里的Test,或者直接按快捷键Command+U。Xcode会先把Runner安装到设备上,然后自动执行测试流程。命令跑起来之后,你会在Xcode的控制台里看到类似ServerURLHere->http://<设备IP>:8100的输出内容,这行输出就是在告诉你WDA服务已经就绪。

这个阶段最容易遇到的问题有两个:一个是没有签名或签名错误,导致Runner无法安装;另一个是设备没有进入开发者模式,导致测试进程启动后被系统拦截。如果你能看到ServerURLHere这行输出,说明前面几个坎都过去了。

启动之后,还需要注意一点:要让WDA一直保持运行,Xcode的Test任务尽量保持在前台或不要主动停止。否则测试Runner一结束,HTTP服务也会跟着关闭。我们在平时自动化测试中,可以在命令行里用xcodebuild test配合后台任务做长期启动,但初学阶段用Xcode图形界面跑通流程更直观。

3.3 用curl确认服务是否真的活着

WDA启动后,第一件要做的事情不是急着控制设备,而是确认服务能正常访问。打开电脑终端,执行下面这条命令:

curl -s http://<设备IP>:8100/status | python3 -m json.tool

如果返回的是包含ready或message字段的JSON,就说明服务已经在正常工作了。这个过程非常关键,因为很多后续问题其实都可以归结为“服务没起来”或者“电脑访问不到设备IP”。

这里还要提醒一个访问地址的细节:在真机上不要只看localhost,因为WDA服务监听的是真机的服务端口,不是电脑本机的端口。电脑端必须通过设备的局域网IP访问,格式是http://<设备IP>:8100。如果你把设备IP搞错,或者电脑和设备不在同一个网段,curl就会一直卡住。

3.4 真正控制设备:创建会话、点击和截图

访问status只是探测服务,真正要让设备执行操作,需要先创建一个WebDriver会话。创建会话的目的是告诉WDA“我准备开始持续发指令了”,之后WDA会返回一个sessionId,后续操作都带着这个ID走。

创建会话的命令大概是这样的:

curl -s -X POST http://<设备IP>:8100/session \ -H 'Content-Type: application/json' \ -d '{"capabilities": {}}'

返回结果里通常有一个sessionId字段,你可以把它记下来,或者用python3解析保存到环境变量里。我自己更习惯用下面这种方式拿到干净的sessionId:

SESSION=$(curl -s -X POST http://<设备IP>:8100/session \ -H 'Content-Type: application/json' \ -d '{"capabilities": {}}' | python3 -c 'import sys, json; print(json.load(sys.stdin)["sessionId"])') echo $SESSION

拿到session之后,就可以执行真正的点击了。WDA支持标准WebDriver的actions接口,我用它模拟一次100,200坐标位置的触摸点击:

curl -s -X POST http://<设备IP>:8100/session/$SESSION/actions \ -H 'Content-Type: application/json' \ -d '[{"type":"pointer","id":"finger1","parameters":{"pointerType":"touch"},"actions":[{"type":"pointerMove","duration":0,"x":100,"y":200},{"type":"pointerDown","button":0},{"type":"pause","duration":120},{"type":"pointerUp","button":0}]}]'

这条命令看着长,但逻辑很直白:先把虚拟手指移动到指定坐标,然后按下,停顿120毫秒,再抬起。这一段就是“从电脑到手指”的完整闭环。点击之后,我还建议立刻截图确认效果:

curl -s http://<设备IP>:8100/session/$SESSION/screenshot \ | python3 -c 'import sys, base64; data=json.load(sys.stdin)["value"]; open("screen.png","wb").write(base64.b64decode(data))'

这张截图会保存在当前目录下。我强烈建议你每次手工实验后都截一张图,因为肉眼看到截图和控制指令的结果对不上,基本就能定位到是定位错了还是执行错了。

除了坐标点击,更稳定的是元素点击。先通过接口找到按钮元素,再针对元素点击。比如查找名为“登录”的按钮:

curl -s -X POST http://<设备IP>:8100/session/$SESSION/element \ -H 'Content-Type: application/json' \ -d '{"using":"xpath","value":"//XCUIElementTypeButton[@name=\"登录\"]"}'

拿到元素ID后,把ID拼到点击接口里就行。相比坐标点击,元素点击不依赖屏幕尺寸,自动化脚本的可移植性会好很多。

4. 接入Appium和现有测试框架:不用再手撕JSON

4.1 为什么要再做一层封装

手工curl可以帮你验证WDA底层能力,但真正做回归测试时,没人愿意写一堆原生HTTP请求。Appium这类框架的价值在于,它把WebDriver协议包装成了面向测试的API,你只需要写高层次的“查找元素”“点击元素”“滑动页面”这样的代码,不需要关心HTTP细节。

Appium也内置了对WDA的管理能力。你让Appium自动编译并启动WDA,或者让Appium复用你已经手工启动的WDA都可以。我的建议是初期先用手工启动的方式,因为这样更容易观察WDA服务本身的状态,排查问题时也能少一个变量。

4.2 让Appium复用你已经启动的WDA

安装Appium本体并不是什么难事,关键在于配置。先确认电脑上装了Node.js环境,然后安装Appium和iOS驱动:

npm install -g appium appium driver install xcuitest

Appium启动后,我一般会在代码里这样配置Capabilities,让它去连接已经启动好的WDA服务:

{ "platformName": "iOS", "appium:automationName": "XCUITest", "appium:deviceName": "某测试机", "appium:platformVersion": "16.6", "appium:webDriverAgentUrl": "http://<设备IP>:8100", "appium:useNewWDA": False, }

这里的重点是最下面两个参数。webDriverAgentUrl告诉Appium“设备上已经有一个WDA服务了,直接连这个地址”;useNewWDA设置为False则是让Appium不要自己重新编译、安装和启动一套新的WDA。如果忘了设置这两个字段,Appium会默认自己管理WDA,那时你可能会遇到重复签名、重复安装的冲突,排查起来非常痛苦。

4.3 用一小段Python脚本完成远程控制

如果你习惯用Python写自动化脚本,下面这段代码可以作为一个最小可用的模板。它做的事情和前面curl命令完全一致,但代码看起来就清晰多了:

from appium import webdriver caps = { "platformName": "iOS", "appium:automationName": "XCUITest", "appium:deviceName": "某测试机", "appium:platformVersion": "16.6", "appium:webDriverAgentUrl": "http://<设备IP>:8100", "appium:useNewWDA": False, } driver = webdriver.Remote("http://127.0.0.1:4723", caps) login_btn = driver.find_element("xpath", "//XCUIElementTypeButton[@name='登录']") login_btn.click() driver.get_screenshot_as_file("after_click.png") driver.quit()

这套写法最大的优势是:底层怎么发请求、怎么解析session、怎么处理超时,都由Appium帮你藏起来了。你可以把精力集中在测试逻辑本身。

4.4 远程控制的权限边界和底线

能做“远程控制”的工具往往也容易被滥用,这一点在使用WDA时务必清楚。WDA只应该在可控的局域网环境、测试设备或专用工具链里使用,不要把WDA的端口直接暴露到公网,也不要随意在不可信的设备上启动服务。一旦设备上的8100端口能被外部网络访问,任何能访问该端口的人都能对设备下发触摸和截图指令,风险极高。

我见过一些团队为了调试方便,把WDA直接绑定到公网端口,结果被扫描工具盯上,最后不得不紧急下线处理。正确的做法是只在测试局域网内使用,必要时用防火墙规则限制访问来源,设备用完就主动停掉WDA服务。

5. 从头到尾最容易踩的坑和排查记录

5.1 签名与安装失败

签名是新手入门最大的拦路虎。最常见的情况是Xcode提示Signing for "WebDriverAgentRunner" requires a development team。解决办法就是在Signing & Capabilities里给WebDriverAgentRunner配置Team,并把Bundle ID改成唯一值。

如果用的是免费个人账号,还要注意签名有效期。过几天之后再次跑自动化,设备可能会弹出“未受信任的开发者”或者安装失败,这时候重新到Xcode里跑一次Test,让Xcode重新签名并安装即可。不要以为是设备坏了或者WDA版本有问题。

5.2 WDA起不来、起来就退

WDA服务启动后立刻退出,通常是测试进程被系统中断。优先检查两件事:一是真机是否开启开发者模式;二是Xcode的Test任务是否一直在执行。如果Test任务跑完正常结束,WDA服务也会随之结束,这不一定是崩溃,而是预期行为。

在自动化测试场景里,我更推荐用命令行的方式长时间保持WDA运行。比如用xcodebuild test配合指定设备和scheme,让它作为一个后台任务持续跑。这样不会因为手滑关掉Xcode窗口而把整个服务带走。

5.3 端口和网络不通

手机上的WDA已经出现ServerURLHere,但电脑端curl却连不上,这种情况八成是网络问题。先确认电脑和手机连的是同一个Wi-Fi,再看设备IP是否真的就是启动日志里显示的那个IP。很多公司内网会开启AP隔离,设备之间互相访问被禁止,也会导致电脑访问不到设备端口。

另外一个容易被忽略的点是设备同时开了Wi-Fi和蜂窝数据,某些网络环境下IP路由会异常。我建议把蜂窝数据关掉,只保留Wi-Fi,能减少很多奇怪的问题。

5.4 元素定位不稳定

元素定位不稳定是最影响自动化体验的问题。XPath定位在iOS上虽然能用,但层级一变就挂。我的经验是优先给控件加上accessibilityIdentifier,然后用accessibility id定位。这样即使UI布局调整,只要语义标识不变,用例就不会断。

如果你没有权限改App代码,只能靠XPath,那至少要给查找逻辑增加显式等待。不要一启动就立刻找元素,先用等待接口确认元素存在后再操作,可以有效降低瞬时的加载抖动。

5.5 常见问题速查

我把实际调试中常遇到的问题整理成了一个表格,方便你后续快速定位:

现象可能原因排查方向和解决方式
编译时找不到依赖没有执行bootstrap脚本回到项目目录运行./Scripts/bootstrap.sh重新拉依赖
提示需要development teamTarget没有配置签名在Signing & Capabilities里选择Team并设置唯一Bundle ID
设备提示未受信任的开发者免费签名过期或首次安装在Xcode里重新执行Test,让签名重新生成
启动后马上退出开发者模式未开启到设备“设置-隐私与安全-开发者模式”打开后重启WDA
curl连不上8100端口电脑和手机不在同一网络检查IP是否一致,关闭蜂窝数据,确认局域网没有AP隔离
元素定位总是失败使用XPath依赖UI层级改用accessibility id,并增加显示等待
点击坐标位置偏移屏幕旋转或分辨率不同避免硬编码坐标,优先用元素点击
Appium重复安装WDA没有指定webDriverAgentUrl设置appium:webDriverAgentUrl并关闭useNewWDA

回到我自己最开始用WDA的经历,真正让我觉得“上手了”的时刻,不是Appium脚本跑通,而是我用一条curl命令从电脑端点了一下手机屏幕,然后看着手机自己动了。那一刻我才真正理解了WDA的定位:它把iOS设备的控制能力开放成了一个简洁的HTTP接口,接下来能拿它做出多少东西,就看你自己的想象力了。

如果你正在团队里搭建移动设备的自动化平台,我的建议是先把这套“手工启动WDA+curl控制”的流程沉淀成文档,再逐步接入Appium或其他测试框架。别一上来就堆框架,因为底层链路没摸透时,出问题你会同时怀疑网络、签名、框架、系统好多个环节。先把最小链路玩熟,后面所有自动化都会变得顺理成章。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进