与 iOS(WebDriverAgent) 集成

在使用 WebDriverAgent 连接 iOS 设备后,你可以使用 Midscene JavaScript SDK 来控制 iOS 设备。

案例展示

关于 WebDriver 和 Midscene 的关系

WebDriver 是一套由 W3C 制定的用于浏览器自动化的标准协议,它提供了一个统一的 API 来控制不同的浏览器和应用程序。WebDriver 协议定义了客户端和服务器之间的通信方式,使得自动化工具能够跨平台地控制各种用户界面。

在 Appium 团队及其他开源社区的努力下,业界已经有了许多优秀的库将桌面、移动端等设备的自动化操作转化为 WebDriver 协议。这些工具包括:

  • Appium - 跨平台移动自动化框架
  • WebDriverAgent - 专门用于 iOS 设备自动化的服务
  • Selenium - Web 浏览器自动化工具
  • WinAppDriver - Windows 应用程序自动化工具

Midscene 适配了 WebDriver 协议,这意味着开发者可以使用 AI 模型对支持 WebDriver 的任何设备进行智能化的自动化操作。通过这种设计,Midscene 不仅能够控制传统的点击、输入等基础操作,还能够:

  • 理解界面内容和上下文
  • 执行复杂的多步骤操作
  • 进行智能断言和验证
  • 提取和分析界面数据

在 iOS 平台上,Midscene 通过 WebDriverAgent 连接 iOS 设备,让你能够使用自然语言描述的方式来控制 iOS 应用和系统。

准备 WebDriver 服务

在开始之前,你需要先设置 iOS 开发环境:

  • macOS(iOS 开发必需)
  • Xcode 和 Xcode 命令行工具
  • iOS 模拟器或真机设备

配置环境

在使用 Midscene iOS 之前,需要先准备 WebDriverAgent 服务。请参考官方文档进行设置:

验证环境配置

配置完成后,可以通过访问 WebDriverAgent 的状态接口来验证 服务是否启动:

访问地址http://localhost:8100/status

正确响应示例

{
  "value": {
    "build": {
      "version": "10.1.1",
      "time": "Sep 24 2025 18:56:41",
      "productBundleIdentifier": "com.facebook.WebDriverAgentRunner"
    },
    "os": {
      "testmanagerdVersion": 65535,
      "name": "iOS",
      "sdkVersion": "26.0",
      "version": "26.0"
    },
    "device": "iphone",
    "ios": {
      "ip": "10.91.115.63"
    },
    "message": "WebDriverAgent is ready to accept commands",
    "state": "success",
    "ready": true
  },
  "sessionId": "BCAD9603-F714-447C-A9E6-07D58267966B"
}

如果能够正常访问该端点并返回类似上述的 JSON 响应,说明 WebDriverAgent 已经正确配置并运行。

配置 AI 模型服务

将你的模型配置写入环境变量,可参考 模型策略 了解更多细节。

export MIDSCENE_MODEL_BASE_URL="https://替换为你的模型服务地址/v1"
export MIDSCENE_MODEL_API_KEY="替换为你的 API Key"
export MIDSCENE_MODEL_NAME="替换为你的模型名称"
export MIDSCENE_MODEL_FAMILY="替换为你的模型系列"

更多配置信息请参考 模型策略模型配置

集成 Midscene

第一步:安装依赖

npm
yarn
pnpm
bun
deno
npm install @midscene/ios --save-dev

第二步:编写脚本

这里以使用 iOS Safari 浏览器搜索耳机为例。

编写下方代码,保存为 ./demo.ts

./demo.ts
import {
  IOSAgent,
  IOSDevice,
  agentFromWebDriverAgent,
} from '@midscene/ios';

const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
Promise.resolve(
  (async () => {
    // 方法一:直接创建设备和 Agent
    const page = new IOSDevice({
      wdaPort: 8100,
      wdaHost: 'localhost',
    });

    // 👀 初始化 Midscene agent
    const agent = new IOSAgent(page, {
      aiActionContext:
        '如果出现位置、权限、用户协议等弹窗,点击同意。如果出现登录页面,关闭它。',
    });
    await page.connect();

    // 方法二:或者使用便捷函数(推荐)
    // const agent = await agentFromWebDriverAgent({
    //   wdaPort: 8100,
    //   wdaHost: 'localhost',
    //   aiActionContext: '如果出现位置、权限、用户协议等弹窗,点击同意。如果出现登录页面,关闭它。',
    // });

    // 👀 打开 ebay.com 网页
    await page.launch('https://ebay.com');
    await sleep(3000);

    // 👀 输入关键词,执行搜索
    await agent.aiAct('在搜索框输入 "Headphones",敲回车');

    // 👀 等待加载完成
    await agent.aiWaitFor('页面中至少有一个耳机商品');
    // 或者你也可以使用一个普通的 sleep:
    // await sleep(5000);

    // 👀 理解页面内容,提取数据
    const items = await agent.aiQuery(
      '{itemTitle: string, price: Number}[], 找到列表里的商品标题和价格',
    );
    console.log('耳机商品信息', items);

    // 👀 用 AI 断言
    await agent.aiAssert('界面中有多个耳机产品');

    await page.destroy();
  })(),
);

第三步:运行

使用 tsx 来运行

# run
npx tsx demo.ts

稍等片刻,你会看到如下输出:

[
 {
   itemTitle: 'AirPods Pro (2nd generation) with MagSafe Charging Case (USB-C)',
   price: 249
 },
 {
   itemTitle: 'Sony WH-1000XM4 Wireless Premium Noise Canceling Overhead Headphones',
   price: 278
 }
]

第四步:查看运行报告

当上面的命令执行成功后,会在控制台输出:Midscene - report file updated: /path/to/report/some_id.html,通过浏览器打开该文件即可看到报告。

构造函数与接口

IOSDevice 的构造函数

IOSDevice 的构造函数支持以下参数:

  • opts?: IOSDeviceOpt - 可选参数,用于初始化 IOSDevice 的配置
    • wdaPort?: number - 可选参数,WebDriverAgent 端口。默认值为 8100。
    • wdaHost?: string - 可选参数,WebDriverAgent 主机。默认值为 'localhost'。
    • autoDismissKeyboard?: boolean - 可选参数,是否在输入文本后自动关闭键盘。默认值为 true。
    • customActions?: DeviceAction<any>[] - 可选参数,自定义设备动作列表。

iOS Agent 上的更多接口

除了 API 参考 中的通用 Agent 接口,IOSAgent 还提供了一些其他接口:

agent.launch()

启动一个网页或原生 iOS 应用。

  • 类型
function launch(uri: string): Promise<void>;
  • 参数:

    • uri: string - 要打开的 uri,可以是网页 url、原生 app 的 bundle identifier 或自定义 URL scheme
  • 返回值:

    • Promise<void>
  • 示例:

import { IOSAgent, IOSDevice, agentFromWebDriverAgent } from '@midscene/ios';

// 方法一:手动创建设备和 Agent
const page = new IOSDevice();
const agent = new IOSAgent(page);
await page.connect();

// 方法二:使用便捷函数(推荐)
const agent = await agentFromWebDriverAgent();

await agent.launch('https://www.apple.com'); // 打开网页
await agent.launch('com.apple.mobilesafari'); // 启动 Safari
await agent.launch('com.apple.Preferences'); // 启动设置应用
await agent.launch('myapp://profile/user/123'); // 打开应用深度链接
await agent.launch('tel:+1234567890'); // 拨打电话
await agent.launch('mailto:example@email.com'); // 发送邮件

agent.runWdaRequest()

直接调用 WebDriverAgent 的 API 接口。

注意:该方法允许你直接调用 WebDriverAgent 提供的底层 API,适用于需要执行特定 WDA 操作的场景。

  • 类型
function runWdaRequest(
  method: string,
  endpoint: string,
  data?: Record<string, any>,
): Promise<any>;
  • 参数:

    • method: string - HTTP 请求方法(GET, POST, DELETE 等)
    • endpoint: string - WebDriver API 端点路径
    • data?: Record<string, any> - 可选的请求体数据(JSON 对象)
  • 返回值:

    • Promise<any> - 返回 API 响应结果
  • 示例:

import { IOSAgent, IOSDevice, agentFromWebDriverAgent } from '@midscene/ios';

const agent = await agentFromWebDriverAgent();

// 获取屏幕信息
const screenInfo = await agent.runWdaRequest('GET', '/wda/screen');
console.log(screenInfo); // { value: { scale: 3, ... } }

// 按下 Home 键
const result = await agent.runWdaRequest('POST', '/session/test/wda/pressButton', {
  name: 'home'
});

// 获取设备信息
const deviceInfo = await agent.runWdaRequest('GET', '/wda/device/info');
在 YAML 脚本中使用

除了在 JavaScript/TypeScript 中使用这些方法,你还可以在 YAML 脚本中使用 iOS 的平台特定动作。

要了解如何在 YAML 脚本中使用 runWdaRequestlaunch 动作,请参考 YAML 脚本中的 iOS 平台特定动作

agent.home()

返回到 iOS 主屏幕。

  • 类型
function home(): Promise<void>;
  • 参数:无

  • 返回值:Promise<void>

  • 示例:

import { IOSAgent, agentFromWebDriverAgent } from '@midscene/ios';

const agent = await agentFromWebDriverAgent();

await agent.home(); // 回到主屏幕

agent.appSwitcher()

打开 iOS 多任务切换界面。

  • 类型
function appSwitcher(): Promise<void>;
  • 参数:无

  • 返回值:Promise<void>

  • 示例:

import { IOSAgent, agentFromWebDriverAgent } from '@midscene/ios';

const agent = await agentFromWebDriverAgent();

await agent.appSwitcher(); // 打开多任务切换界面

agentFromWebDriverAgent() (推荐)

通过连接 WebDriverAgent 服务创建 IOSAgent,这是最简便的方式。

  • 类型
function agentFromWebDriverAgent(
  opts?: PageAgentOpt & IOSDeviceOpt,
): Promise<IOSAgent>;
  • 参数:

    • opts?: PageAgentOpt & IOSDeviceOpt - 可选参数,用于初始化 IOSAgent 的配置,其中 PageAgentOpt 参考 构造器,IOSDeviceOpt 的配置值参考 IOSDevice 的构造函数
  • 返回值:

    • Promise<IOSAgent> 返回一个 IOSAgent 实例
  • 示例:

import { agentFromWebDriverAgent } from '@midscene/ios';

// 使用默认 WebDriverAgent 地址 (localhost:8100)
const agent = await agentFromWebDriverAgent();

// 使用自定义 WebDriverAgent 地址
const agent = await agentFromWebDriverAgent({
  wdaHost: 'localhost',
  wdaPort: 8100,
  aiActionContext: '如果出现弹窗,点击同意',
});

扩展自定义交互动作

使用 customActions 选项,结合 defineAction 定义的自定义交互动作,可以扩展 Agent 的动作空间。这些动作会追加在内置动作之后,方便 Agent 在规划阶段调用。

import { getMidsceneLocationSchema, z } from '@midscene/core';
import { defineAction } from '@midscene/core/device';
import { IOSAgent, IOSDevice } from '@midscene/ios';

const ContinuousClick = defineAction({
  name: 'continuousClick',
  description: 'Click the same target repeatedly',
  paramSchema: z.object({
    locate: getMidsceneLocationSchema(),
    count: z
      .number()
      .int()
      .positive()
      .describe('How many times to click'),
  }),
  async call(param) {
    const { locate, count } = param;
    console.log('click target center', locate.center);
    console.log('click count', count);
    // 在这里结合 locate + count 实现自定义点击逻辑
  },
});

const agent = await agentFromWebDriverAgent({
  customActions: [ContinuousClick],
});

await agent.aiAct('点击红色按钮五次');

更多关于自定义动作的细节,请参考 集成到任意界面

更多

  • 更多 Agent 上的 API 接口请参考 API 参考

FAQ

为什么我连接了设备,但是通过 WebDriverAgent 仍然无法控制?

请检查以下几点:

  1. 开发者模式:确保在设置 > 隐私与安全性 > 开发者模式 中已开启
  2. UI 自动化:确保在设置 > 开发者 > UI 自动化,启用 UI 自动化
  3. 设备信任:确保设备已信任当前 Mac

模拟器和真机有什么区别?

特性真机模拟器
端口转发需要 iproxy不需要
开发者模式需要启用自动启用
UI 自动化设置需要手动启用自动启用
性能真实设备性能依赖 Mac 性能
传感器真实硬件模拟数据

如何使用自定义的 WebDriverAgent 端口和主机?

你可以通过 IOSDevice 的构造函数或 agentFromWebDriverAgent 来指定 WebDriverAgent 的端口和主机:

// 方法一:使用 IOSDevice
const device = new IOSDevice({
  wdaPort: 8100,        // 自定义端口
  wdaHost: '192.168.1.100', // 自定义主机
});

// 方法二:使用便捷函数(推荐)
const agent = await agentFromWebDriverAgent({
  wdaPort: 8100,        // 自定义端口
  wdaHost: '192.168.1.100', // 自定义主机
});

对于远程设备,还需要相应地设置端口转发:

iproxy 8100 8100 YOUR_DEVICE_ID

更多

  • 更多 Agent 上的 API 接口请参考 API 参考