--- url: /zh/advanced/bdd-style-scripts-with-gherkin.md --- # BDD 风格脚本(Gherkin) > 此特性从 Midscene 1.10 开始支持。 > > BDD 相关能力仍处于 Beta 阶段,未来 API 可能发生变化。 Gherkin 是一种用纯文本描述行为示例的语法,常用于 BDD(Behavior-Driven Development,行为驱动开发)流程。一个 BDD 场景通常包含三类步骤:`Given` 描述前置条件,`When` 描述用户操作,`Then` 描述预期结果。 举例来说,当需要测试待办事项页面的新增功能时,可以把用例写成下面的 Gherkin 脚本。 ```gherkin Scenario: 添加待办事项 Given 待办事项页面已经打开 When 添加一条名为“买牛奶”的待办事项 Then 待办事项列表中应该包含“买牛奶” ``` 较长的 GUI 用例也可以写在同一个 `Scenario` 中。你可以按业务阶段组织步骤,每个阶段都使用一组 `Given`、`When`、`Then`。 ```gherkin Scenario: 使用保存的地址下单 Given 购物车中已有“Midscene Mug” When 我打开购物车页面 Then 购物车中应该显示“Midscene Mug” And 小计金额应该可见 Given 结算页面已经打开 When 我选择已保存的收货地址 Then 收货地址区域应该显示所选地址 Given 支付区域已经可见 When 我选择已保存的信用卡并提交订单 Then 页面应该显示订单确认结果 And 确认页面应该显示订单号 ``` 这种写法适合一个业务 scenario 自然包含多个 UI 阶段的情况,例如购物车确认、配送地址选择、支付和最终确认。 在 AI 通过自然语言驱动 GUI 操作的场景中,Gherkin 很适合描述操作用例。它让脚本保留自然语言的可读性,同时提供稳定的步骤结构。这种结构也有助于模型生成更规范的用例。 Gherkin 的完整语法可以参考官方的 [Cucumber Gherkin reference](https://cucumber.io/docs/gherkin/reference)。 ## 支持规则 Midscene 只支持 Gherkin 的一个子集。这个子集围绕单个场景(Scenario)设计,适合描述一段完整的 GUI 操作流程。 ### 步骤映射 * `Given`:调用 `aiAct`,用于准备前置状态。 * `When`:调用 `aiAct`,用于执行用户操作。 * `Then`:调用 `aiAssert`,用于验证页面结果。 * `And`、`But`:沿用前一个主关键字。例如,`Then` 后面的 `And` 也会调用 `aiAssert`。 关键字匹配不区分大小写。因此,`AND`、`and` 和 `And` 都可以识别。 ### 场景限制 每次只支持一个 `Scenario:`。如果输入里包含多个 `Scenario:`,Midscene 会报错。 你也可以省略 `Scenario:`,直接写步骤。 ```gherkin Given 待办事项页面已经打开 When 我添加一条名为“买牛奶”的待办事项 Then 待办事项列表中应该包含“买牛奶” ``` ### 注意事项 Midscene 会把每个 Gherkin 步骤拆成一次独立的 `aiAct` 或 `aiAssert` 调用。每一步都应该写完整,明确说明操作对象、动作和预期结果。 不要在后续步骤中依赖上一条步骤里的省略信息或模糊指代。比如,不要只写“点击它”或“检查结果”,而应该写清楚要点击的对象和要检查的结果。 下面的写法无法正确执行。原因是步骤之间的上下文相互隔离,后续步骤无法知道“它”和“结果”分别指什么。 ```gherkin Given 待办事项页面已经打开 When 我添加一条名为“买牛奶”的待办事项 And 点击它 Then 检查结果 ``` 应该写成下面这样。 ```gherkin Given 待办事项页面已经打开 When 我添加一条名为“买牛奶”的待办事项 And 我点击“买牛奶”这条待办事项 Then “买牛奶”这条待办事项应该显示为已完成 ``` ### 暂不支持 下面这些 Gherkin 特性暂不支持: * `Feature` * `Background` * `Scenario Outline` 或 `Scenario Template` * `Examples` * `Rule` * data tables * doc strings * 变量替换、占位符展开或模板语法 * 在同一段输入中包含多个 `Scenario:` Midscene 不会把 Gherkin 扩展成一种脚本语言。它只读取 `Scenario` 和步骤关键字,再把每个步骤交给 Agent 执行。 循环、条件、变量替换和批量生成等逻辑,应该在调用 Midscene 之前完成。如果需要复用模板,可以先在项目中实现一个 Gherkin 生成器。生成器输出最终的纯文本 `Scenario`,再交给 `runGherkinScenario` 执行。 ## 在 JavaScript 中使用 在 JavaScript 或 TypeScript 脚本中,可以直接调用 `agent.runGherkinScenario()`。它接收一段 Gherkin 文本,并按顺序执行其中的步骤。 下面的示例使用 Playwright fixture 获取 Agent。 ```ts import { test } from '@midscene/web/playwright'; test('add a todo item', async ({ page, agentForPage }) => { await page.goto('http://localhost:3000/todos'); const agent = await agentForPage(page); await agent.runGherkinScenario(` Scenario: 添加待办事项 Given 待办事项页面已经打开 When 我添加一条名为“买牛奶”的待办事项 Then 待办事项列表中应该包含“买牛奶” And 待办事项数量应该为 1 `); }); ``` 第二个参数可以传入运行选项。例如,可以为这次执行提供临时上下文。 ```ts await agent.runGherkinScenario( ` Scenario: 添加待办事项 Given 待办事项页面已经打开 When 我添加一条名为“买牛奶”的待办事项 Then 待办事项列表中应该包含“买牛奶” `, { context: '这是一个待办事项示例应用。请使用页面上的输入框和添加按钮完成操作。', }, ); ``` ## 在 YAML 中使用 在 YAML flow 中使用 `runGherkinScenario` 步骤。它的值应该是一段块字符串,里面放一个 scenario。 ```yaml web: url: http://localhost:3000/todos tasks: - name: Add a todo item flow: - runGherkinScenario: | Scenario: 添加待办事项 Given 待办事项页面已经打开 When 我添加一条名为“买牛奶”的待办事项 Then 待办事项列表中应该包含“买牛奶” And 待办事项数量应该为 1 ``` YAML 执行时,Midscene 会按步骤运行这个 scenario。`Given` 和 `When` 步骤会调用 `aiAct`,`Then` 以及后面这个 `And` 步骤会调用 `aiAssert`。 ## 备注 `runGherkinScenario` 会关闭场景内部的缓存。也就是说,`Given` 和 `When` 映射到 `aiAct` 后,不会使用 `aiAct` 的规划缓存。 这样做是为了让每个 Gherkin 步骤都基于当前页面状态重新解释,避免复用旧步骤带来的误判。 --- url: /zh/android-world-benchmark-report.md --- # Midscene AndroidWorld Benchmark 测试报告 import { BenchmarkReportPreview } from '@theme'; 本文是 Midscene 针对 AndroidWorld benchmark 的测试报告。本次测试中,Midscene 取得了 **Pass@1 93.10%**、**Pass@2 95.69%**、**Pass@3 97.41%** 的结果。 :::info 关于 AndroidWorld [AndroidWorld](https://github.com/google-research/android_world) 是 Google Research 提供的 Android Agent benchmark。它运行在真实 Android 模拟器上,用 20 个真实 Android 应用中的 116 个程序化任务来评估 Agent,并由 benchmark 负责初始化任务和校验结果。 ::: ## 运行配置 | 字段 | 值 | | --- | --- | | Model Name | `Gemini-3.5-Flash` | | Midscene version | `1.9.5` | | DeepThink | on | | `MIDSCENE_REPLANNING_CYCLE_LIMIT` | 120 | | AndroidWorld 工程 | 为了降低 benchmark 过程中的偶发波动,我们对 AndroidWorld 工程做了若干稳定性修复。具体例子见下方。 | | 验收规则 | 少量 AndroidWorld validator 按任务意图做了校准,涉及的 case 见下方列表。 | ## 稳定性提升 以下改动不改变任务意图,主要用于降低浏览器渲染、accessibility tree 刷新、任务 setup 时序等带来的偶发失败。 | 修改内容 | 涉及的 case | | --- | --- | | BrowserDraw 绘制后强制刷新 canvas 像素,并加粗、圆角化画笔,让目标颜色更稳定地落到最终 canvas 像素里。 | `BrowserDraw` | | Browser task 完成后,读取 accessibility tree 中的 `Success!` 文本时增加重试,避免 stale accessibility tree 误判。 | `BrowserMaze`
`BrowserMultiply`
`BrowserDraw` | | SMS 任务开始前会先准备好需要的短信和联系人;现在会确认这些短信能在收件箱查到、联系人能在通讯录查到,再开始让 agent 执行。 | `SimpleSmsReplyMostRecent`
`SimpleSmsSendReceivedAddress` | | Expense 类任务启动 Pro Expense 后等待数据库表创建完成,再由 validator 写入测试数据,避免初始数据库未初始化导致失败。 | `ExpenseAddMultiple`
`ExpenseAddMultipleFromGallery`
`ExpenseAddMultipleFromMarkor`
`ExpenseAddSingle`
`ExpenseDeleteDuplicates`
`ExpenseDeleteDuplicates2`
`ExpenseDeleteMultiple`
`ExpenseDeleteMultiple2`
`ExpenseDeleteSingle` | | OsmAnd 任务提前把离线地图文件放入应用数据目录,并等待 OsmAnd 解压内置底图,确保地图数据 ready 后再执行。 | `OsmAndFavorite`
`OsmAndMarker`
`OsmAndTrack` | ## 验证条件调整 本次 benchmark 使用的 AndroidWorld `main` 分支中,以下验证条件做过调整: | 修改内容 | 涉及的 case | | --- | --- | | Calendar 的 "after start time" 验收目标改为边界点之后 1 分钟的事件,避免事件正好落在边界点时,不同模型对 `after` 是否包含边界点的理解差异影响判定。 | `SimpleCalendarFirstEventAfterStartTime` | | Expense 从 Markor 导入时,接受 Markor note 中额外带上的 `Reimbursable.` 后缀;对比 note 文本时会忽略这个后缀及末尾句号差异。 | `ExpenseAddMultipleFromMarkor` | | Markor 合并 notes 时,允许单换行或空行分隔,并兼容 Markor 自动补上的 `.md` 扩展名。 | `MarkorMergeNotes` | | Recipe 的数量、时长和 ingredient count 允许省略单位,但错误数量或错误单位仍会失败。 | `RecipeAddSingleRecipe`
`RecipeAddMultipleRecipes`
`RecipeAddMultipleRecipesFromMarkor`
`RecipeAddMultipleRecipesFromMarkor2`
`RecipeAddMultipleRecipesFromImage`
`NotesRecipeIngredientCount` | | 最小亮度按 Android 实际 setting 值 `0` 验证,而不是旧的 `1`。 | `SystemBrightnessMin`
`SystemBrightnessMinVerify` | ## 报告文件 详细的报告如下,供大家参考。
Round 1(115 份报告 · 108 PASS · 7 FAIL) | # | 任务 | 状态 | 报告 | | --- | --- | --- | --- | | 1 | AudioRecorderRecordAudio | PASS | 报告 | | 2 | AudioRecorderRecordAudioWithFileName | PASS | 报告 | | 3 | BrowserDraw | PASS | 报告 | | 4 | BrowserMaze | PASS | 报告 | | 5 | BrowserMultiply | PASS | 报告 | | 6 | CameraTakePhoto | PASS | 报告 | | 7 | CameraTakeVideo | PASS | 报告 | | 8 | ClockStopWatchPausedVerify | PASS | 报告 | | 9 | ClockStopWatchRunning | PASS | 报告 | | 10 | ClockTimerEntry | PASS | 报告 | | 11 | ContactsAddContact | PASS | 报告 | | 12 | ContactsNewContactDraft | PASS | 报告 | | 13 | ExpenseAddMultiple | PASS | 报告 | | 14 | ExpenseAddMultipleFromGallery | PASS | 报告 | | 15 | ExpenseAddMultipleFromMarkor | FAIL | 报告 | | 16 | ExpenseAddSingle | PASS | 报告 | | 17 | ExpenseDeleteDuplicates | PASS | 报告 | | 18 | ExpenseDeleteDuplicates2 | PASS | 报告 | | 19 | ExpenseDeleteMultiple | PASS | 报告 | | 20 | ExpenseDeleteMultiple2 | PASS | 报告 | | 21 | ExpenseDeleteSingle | PASS | 报告 | | 22 | FilesDeleteFile | PASS | 报告 | | 23 | FilesMoveFile | PASS | 报告 | | 24 | MarkorAddNoteHeader | PASS | 报告 | | 25 | MarkorChangeNoteContent | PASS | 报告 | | 26 | MarkorCreateFolder | PASS | 报告 | | 27 | MarkorCreateNote | PASS | 报告 | | 28 | MarkorCreateNoteAndSms | PASS | 报告 | | 29 | MarkorCreateNoteFromClipboard | PASS | 报告 | | 30 | MarkorDeleteAllNotes | PASS | 报告 | | 31 | MarkorDeleteNewestNote | PASS | 报告 | | 32 | MarkorDeleteNote | PASS | 报告 | | 33 | MarkorEditNote | PASS | 报告 | | 34 | MarkorMergeNotes | PASS | 报告 | | 35 | MarkorMoveNote | PASS | 报告 | | 36 | MarkorTranscribeReceipt | PASS | 报告 | | 37 | MarkorTranscribeVideo | FAIL | 报告 | | 38 | OpenAppTaskEval | PASS | 报告 | | 39 | OsmAndFavorite | PASS | 报告 | | 40 | OsmAndMarker | FAIL | 报告 | | 42 | RecipeAddMultipleRecipes | PASS | 报告 | | 43 | RecipeAddMultipleRecipesFromImage | FAIL | 报告 | | 44 | RecipeAddMultipleRecipesFromMarkor | PASS | 报告 | | 45 | RecipeAddMultipleRecipesFromMarkor2 | PASS | 报告 | | 46 | RecipeAddSingleRecipe | PASS | 报告 | | 47 | RecipeDeleteDuplicateRecipes | PASS | 报告 | | 48 | RecipeDeleteDuplicateRecipes2 | FAIL | 报告 | | 49 | RecipeDeleteDuplicateRecipes3 | FAIL | 报告 | | 50 | RecipeDeleteMultipleRecipes | PASS | 报告 | | 51 | RecipeDeleteMultipleRecipesWithConstraint | PASS | 报告 | | 52 | RecipeDeleteMultipleRecipesWithNoise | PASS | 报告 | | 53 | RecipeDeleteSingleRecipe | PASS | 报告 | | 54 | RecipeDeleteSingleWithRecipeWithNoise | PASS | 报告 | | 55 | RetroCreatePlaylist | PASS | 报告 | | 56 | RetroPlayingQueue | PASS | 报告 | | 57 | RetroPlaylistDuration | PASS | 报告 | | 58 | RetroSavePlaylist | PASS | 报告 | | 59 | SaveCopyOfReceiptTaskEval | PASS | 报告 | | 60 | SimpleCalendarAddOneEvent | PASS | 报告 | | 61 | SimpleCalendarAddOneEventInTwoWeeks | PASS | 报告 | | 62 | SimpleCalendarAddOneEventRelativeDay | PASS | 报告 | | 63 | SimpleCalendarAddOneEventTomorrow | PASS | 报告 | | 64 | SimpleCalendarAddRepeatingEvent | PASS | 报告 | | 65 | SimpleCalendarDeleteEvents | PASS | 报告 | | 66 | SimpleCalendarDeleteEventsOnRelativeDay | PASS | 报告 | | 67 | SimpleCalendarDeleteOneEvent | PASS | 报告 | | 68 | SimpleDrawProCreateDrawing | PASS | 报告 | | 69 | SimpleSmsReply | PASS | 报告 | | 70 | SimpleSmsReplyMostRecent | PASS | 报告 | | 71 | SimpleSmsResend | PASS | 报告 | | 72 | SimpleSmsSend | PASS | 报告 | | 73 | SimpleSmsSendClipboardContent | PASS | 报告 | | 74 | SimpleSmsSendReceivedAddress | PASS | 报告 | | 75 | SystemBluetoothTurnOff | PASS | 报告 | | 76 | SystemBluetoothTurnOffVerify | PASS | 报告 | | 77 | SystemBluetoothTurnOn | PASS | 报告 | | 78 | SystemBluetoothTurnOnVerify | PASS | 报告 | | 79 | SystemBrightnessMax | PASS | 报告 | | 80 | SystemBrightnessMaxVerify | PASS | 报告 | | 81 | SystemBrightnessMin | PASS | 报告 | | 82 | SystemBrightnessMinVerify | PASS | 报告 | | 83 | SystemCopyToClipboard | FAIL | 报告 | | 84 | SystemWifiTurnOff | PASS | 报告 | | 85 | SystemWifiTurnOffVerify | PASS | 报告 | | 86 | SystemWifiTurnOn | PASS | 报告 | | 87 | SystemWifiTurnOnVerify | PASS | 报告 | | 88 | TurnOffWifiAndTurnOnBluetooth | PASS | 报告 | | 89 | TurnOnWifiAndOpenApp | PASS | 报告 | | 90 | VlcCreatePlaylist | PASS | 报告 | | 91 | VlcCreateTwoPlaylists | PASS | 报告 | | 92 | NotesIsTodo | PASS | 报告 | | 93 | NotesMeetingAttendeeCount | PASS | 报告 | | 94 | NotesRecipeIngredientCount | PASS | 报告 | | 95 | NotesTodoItemCount | PASS | 报告 | | 96 | SimpleCalendarAnyEventsOnDate | PASS | 报告 | | 97 | SimpleCalendarEventOnDateAtTime | PASS | 报告 | | 98 | SimpleCalendarEventsInNextWeek | PASS | 报告 | | 99 | SimpleCalendarEventsInTimeRange | PASS | 报告 | | 100 | SimpleCalendarEventsOnDate | PASS | 报告 | | 101 | SimpleCalendarFirstEventAfterStartTime | PASS | 报告 | | 102 | SimpleCalendarLocationOfEvent | PASS | 报告 | | 103 | SimpleCalendarNextEvent | PASS | 报告 | | 104 | SimpleCalendarNextMeetingWithPerson | PASS | 报告 | | 105 | SportsTrackerActivitiesCountForWeek | PASS | 报告 | | 106 | SportsTrackerActivitiesOnDate | PASS | 报告 | | 107 | SportsTrackerActivityDuration | PASS | 报告 | | 108 | SportsTrackerLongestDistanceActivity | PASS | 报告 | | 109 | SportsTrackerTotalDistanceForCategoryOverInterval | PASS | 报告 | | 110 | SportsTrackerTotalDurationForCategoryThisWeek | PASS | 报告 | | 111 | TasksCompletedTasksForDate | PASS | 报告 | | 112 | TasksDueNextWeek | PASS | 报告 | | 113 | TasksDueOnDate | PASS | 报告 | | 114 | TasksHighPriorityTasks | PASS | 报告 | | 115 | TasksHighPriorityTasksDueOnDate | PASS | 报告 | | 116 | TasksIncompleteTasksOnDate | PASS | 报告 |
Round 2(7 份报告 · 3 PASS · 4 FAIL) | # | 任务 | 状态 | 报告 | | --- | --- | --- | --- | | 37 | MarkorTranscribeVideo | FAIL | 报告 | | 40 | OsmAndMarker | FAIL | 报告 | | 41 | OsmAndTrack | PASS | 报告 | | 43 | RecipeAddMultipleRecipesFromImage | PASS | 报告 | | 48 | RecipeDeleteDuplicateRecipes2 | FAIL | 报告 | | 49 | RecipeDeleteDuplicateRecipes3 | FAIL | 报告 | | 83 | SystemCopyToClipboard | PASS | 报告 |
Round 3(5 份报告 · 2 PASS · 3 FAIL) | # | 任务 | 状态 | 报告 | | --- | --- | --- | --- | | 15 | ExpenseAddMultipleFromMarkor | PASS | 报告 | | 37 | MarkorTranscribeVideo | FAIL | 报告 | | 40 | OsmAndMarker | PASS | 报告 | | 48 | RecipeDeleteDuplicateRecipes2 | FAIL | 报告 | | 49 | RecipeDeleteDuplicateRecipes3 | FAIL | 报告 |
--- url: /zh/app-control-bench-report.md --- # Midscene AppControlBench Benchmark 测试报告 import { AppControlBenchComparison, AppControlBenchReport } from '@theme'; 本文是 Midscene 针对 AppControlBench benchmark 的测试报告。本次测试中,每个模型均覆盖同一批 60 个任务,Midscene 取得了如下成绩: > 注:Doubao Seed 2.1 Turbo 使用[火山引擎官方价格](https://ark.volcengine.com/region:cn-beijing/model/detail?name=doubao-seed-2-1-turbo)计算成本:推理输入 ¥3 / 百万 tokens、缓存命中 ¥0.6 / 百万 tokens、推理输出 ¥15 / 百万 tokens。其他模型成本按 OpenRouter 价格计算,美元按 ¥6.8 / $1 换算。 **可以发现,Midscene + GUI 视觉的路线在成本、通过率上都有很强的竞争力。** :::info 关于 AppControlBench [AppControlBench](https://github.com/software-mansion/app-control-bench) 用于评估 Agent 操作真实 iOS 应用的能力。每个任务都在隔离的 Simulator 中运行;任务结束后,根据任务专属的目标界面描述对最终截图进行评分。每组运行包含 30 个 Bluesky 任务和 30 个 Element iOS 任务。 ::: ## 运行配置 | 字段 | 值 | | --- | --- | | 测试日期 | 2026-08-25 至 2026-08-27 | | Model Name | Doubao Seed 2.1 Turbo、Qwen3.7 Plus、DeepSeek V4 Flash Vision Exp | | Midscene 版本 | `1.12.0` | | DeepThink | 关闭 | | Device | iOS Simulator | | Bluesky 版本 | `1.122.0` | | Element iOS 版本 | `1.11.40` | | 任务数 | 每个模型 60 个 | | AppControlBench 工程 | Midscene 适配层把 Agent 执行委托给 `@midscene/ios`;AppControlBench 继续负责任务重置、Simulator 生命周期、最终截图采集和评分。 | | 验收规则 | 沿用原有目标状态和 judge 规则;对 1 个 case 的操作意图进行校准。 | ## Case 意图校准 本次评测针对固定版本 Element iOS 的实际界面,对 1 个 case 的操作路径描述进行了校准。校准只消除旧版或不存在的 UI 入口描述,不改变 case 的最终目标状态和 judge 规则。 | Case | Before | After | 变更理由 | | --- | --- | --- | --- | | `element-07` | Open the "Project Phoenix" room and open its room-options menu (the '...' in the header). | Open the "Project Phoenix" room info/options screen. | 固定版本 Element iOS 不存在 header `...` 入口;仅校准操作路径,目标状态和 judge 规则不变。 | ## 报告文件 --- url: /zh/automate-with-scripts-in-yaml.md --- # 使用 YAML 格式的自动化脚本 :::warning 下一代方案升级提示 本文档介绍的是老版 YAML 自动化运行方案。我们已推出了全新、面向未来的 **[Test Runner](/zh/test-runner-overview.md) (Beta)**。 新方案采用“自然语言驱动主线,可编程可定制 Node 作为辅助”的全新测试范式,支持完备的测试生命周期、多环境并发隔离以及标准化运行报告,是老版方案的**官方替代升级版**。 目前新方案正处于 **Beta 开放阶段**,我们强烈建议您阅读 **[Test Runner 概览](/zh/test-runner-overview.md)** 并基于新方案进行项目实践与迁移。 ::: 在大多数情况下,开发者编写自动化脚本只是为了执行一些简单流程,比如检查某些内容是否出现,或者验证某个关键用户路径是否可用。此时维护一个大型测试项目会显得毫无必要。 ⁠Midscene 提供了一种基于 `.yaml` 文件的自动化测试方法,这有助于你专注于编写流程,而不是测试框架。 这里有一个示例,通过阅读它的内容,你应该已经理解了它的工作原理。 ```yaml page: url: https://www.bing.com tasks: - name: 搜索天气 flow: - ai: 搜索 "今日天气" - sleep: 3000 - name: 检查结果 flow: - aiAssert: 结果中展示了天气信息 ``` :::info 样例项目 你可以在这里找到使用 YAML 脚本做自动化的样例项目 * [Web](https://github.com/web-infra-dev/midscene-example/tree/main/yaml-scripts-demo) * [Android](https://github.com/web-infra-dev/midscene-example/tree/main/android/yaml-scripts-demo) * [Computer(Mac/Windows/Linux)](https://github.com/web-infra-dev/midscene-example/tree/main/computer/yaml-scripts-demo) ::: ## 配置 AI 模型服务 下面以豆包 Seed 2.1 Turbo 为例: ```bash export MIDSCENE_MODEL_BASE_URL="https://ark.cn-beijing.volces.com/api/v3" export MIDSCENE_MODEL_API_KEY="your-api-key" export MIDSCENE_MODEL_NAME="doubao-seed-2-1-turbo-260628" export MIDSCENE_MODEL_FAMILY="doubao-seed" ``` 将 `your-api-key` 替换为你的 API Key。 > 如需使用千问(Qwen)、GLM、Gemini 或 GPT-5 等其他模型,请参考[支持的模型与配置](/model-common-config.md)。 全部配置项请参考[模型配置](/model-config.md)。 如果需要通过命令行执行 YAML 工作流,请查看 [YAML 脚本运行器](/zh/yaml-script-runner.md),了解安装、`.env` 支持以及 `midscene` 命令的用法。 ## 脚本文件结构 脚本文件使用 YAML 格式来描述自动化任务。它定义了要操作的目标(如网页或安卓应用)以及一系列要执行的步骤。 一个标准的 `.yaml` 脚本文件包含 `page`、`browser`、`web`、`android`、`ios`、`harmony` 或 `computer` 部分配置环境,可选的 `agent` 部分配置 AI Agent 行为,以及一个 `tasks` 部分来定义自动化任务。 ```yaml page: url: https://www.bing.com # tasks 部分定义了要执行的一系列步骤 tasks: - name: 搜索天气 flow: - ai: 搜索 "今日天气" - sleep: 3000 - aiAssert: 结果显示天气信息 ``` 使用 `page:` 表示 page-level Agent。使用 `browser:` 表示一个 Agent 管理 browser 和它的 active page。`web:` 仍作为兼容入口保留;`web.mode: browser` 会映射到 BrowserAgent,普通 `web:` 会映射到 PageAgent。不要在同一份脚本里同时使用 `page`、`browser`、`web` 或已废弃的 `target`。 ### `agent` 部分 `agent` 部分用于配置 AI Agent 的行为和测试报告相关选项。所有字段都是可选的。 ```yaml # AI agent 配置 agent: # 测试标识符,用于报告和缓存识别,可选 testId: # 报告组名称,可选 groupName: # 报告组描述,可选 groupDescription: # 是否生成测试报告,可选,默认 true generateReport: # 是否自动打印报告消息,可选,默认 true autoPrintReportMsg: # 自定义报告文件名,可选 reportFileName: # AI 最大重规划循环次数,可选,默认 20(UI-TARS 模型为 40) replanningCycleLimit: # 提供给 AI 的补充信息,可选;default 是共享的缺省回退值 aiContexts: default: aiAct: aiQuery: # aiContexts.aiAct 的已废弃兼容字段,可选 aiActContext: # 更早的已废弃字段 aiActionContext 仅为兼容而保留 # 缓存配置,可选 cache: # 缓存策略,可选,可选值:'read-only' | 'read-write' | 'write-only' strategy: # 缓存 ID,必填 id: ``` :::info Agent 配置说明 * **适用环境**:Web、iOS 和 Android 环境都支持 `agent` 配置 * **testId 优先级**:CLI 参数 > YAML agent.testId > 文件名 * **aiContexts**:提供给 AI 的补充信息,例如业务事实、规则、约束或输出要求。如果配置了 `default`,仅当本次调用和对应 API 都没有自己的 context 时才使用它。单次调用的 `context` 覆盖 API 级值,API 级值覆盖 `default`,这些值不会自动合并;空字符串会显式清空继承的用户 context。 * **aiActContext**:`aiContexts.aiAct` 的已废弃兼容字段。两者同时存在时以 `aiContexts.aiAct` 为准;更早的 `aiActionContext` 字段也已废弃,仅为兼容而保留。 * **缓存配置**:详细用法请参考 [缓存功能文档](/zh/caching.md) ::: #### 使用示例 ```yaml # agent 配置,适用于所有环境 agent: testId: "checkout-test" groupName: "E2E 测试套件" groupDescription: "完整的购物流程测试" generateReport: true autoPrintReportMsg: false reportFileName: "checkout-report" replanningCycleLimit: 30 aiContexts: default: "页面中的价格单位是美元。" aiAct: "如果出现弹窗,点击同意。如果出现登录页面,跳过它。" cache: id: "checkout-cache" strategy: "read-write" # iOS 环境配置 ios: launch: https://www.bing.com wdaPort: 8100 # 或 Android 环境配置 android: deviceId: s4ey59 launch: https://www.bing.com tasks: - name: 搜索天气 flow: - ai: 搜索 "今日天气" - aiAssert: 结果显示天气信息 ``` ### Web target 部分 推荐的 page-level target: ```yaml page: url: https://example.com ``` 推荐的 browser-level target: ```yaml browser: url: https://example.com autoFollowNewPage: true ``` 兼容写法: ```yaml web: mode: browser url: https://example.com autoFollowNewPage: true ``` 共享配置: ```yaml page: # 访问的 URL,必填。如果提供了 `serve` 参数,则提供相对路径 url: # 在本地路径下启动一个静态服务,可选 serve: # 浏览器 UA,可选 userAgent: # 浏览器视口宽度,可选,默认 1440 viewportWidth: # 浏览器视口高度,可选,默认 800 viewportHeight: # 浏览器设备像素比,可选,默认跟随系统 deviceScaleFactor: # JSON 格式的浏览器 Cookie 文件路径,可选 cookie: # Chrome 下载目录(仅 Puppeteer 模式),可选 # 相对路径会基于当前工作目录解析 # bridgeMode 不支持该选项 downloadPath: # 随每个请求发送的额外 HTTP 请求头(仅 Puppeteer 模式),可选 # 适用于服务器会校验自定义请求头的场景 # 值必须是字符串;像 true、false、纯数字这类会被 YAML 解析为非字符串的值需加引号,如 "true" extraHTTPHeaders: X-Custom-Token: my-token Accept-Language: en-US # Puppeteer 模式下等待网络空闲的策略,可选 # `timeout` 会作用于初始打开 YAML 中的 `web.url`,以及后续 `aiTap`、`aiInput` 等操作后的等待 # `continueOnNetworkIdleError` 只作用于初始打开 YAML 中的 `web.url` waitForNetworkIdle: # 每次网络空闲等待的超时时间,可选,默认 2000ms timeout: # 初始打开 YAML 中的 `web.url` 时,如果网络空闲等待超时,是否继续执行,可选,默认 true # 后续脚本执行中的等待即使超时也总是继续执行 continueOnNetworkIdleError: # 输出 aiQuery/aiAssert 结果的 JSON 文件路径,可选 output: # 是否保存日志内容到 JSON 文件,可选,默认 `false`。如果为 true,保存到 `unstableLogContent.json` 文件中。如果为字符串,则保存到该字符串指定的路径中。日志内容的结构可能会在未来发生变化。 unstableLogContent: # 是否限制页面在当前 tab 打开,可选,默认 true # 仅 page mode 可用,不要与 `browser:` 或 `web.mode: browser` 同时使用 forceSameTabNavigation: # BrowserAgent 是否自动继续在新打开的页面中执行,可选,默认 false # 仅 browser mode 可用。请使用 `browser:` 或 `web.mode: browser` autoFollowNewPage: # CDP 连接端点,可选。设置后通过 CDP 连接到已有浏览器实例,而非启动新浏览器。 # 与 bridgeMode 互斥,不可同时使用。 cdpEndpoint: ws://localhost:9222/devtools/browser # 桥接模式,可选,默认 false,可以为 'newTabWithUrl' 或 'currentTab'。更多详情请参阅后文 bridgeMode: false | 'newTabWithUrl' | 'currentTab' # 是否在桥接断开时关闭新创建的标签页,可选,默认 false closeNewTabsAfterDisconnect: # 是否忽略 HTTPS 证书错误,可选,默认 false acceptInsecureCerts: # 自定义 Chrome 启动参数(仅 Puppeteer 模式,不支持桥接模式),可选 # 用于自定义 Chrome 浏览器行为,例如禁用第三方 Cookie 阻止 # ⚠️ 安全警告:某些参数(如 --no-sandbox、--disable-web-security)可能降低浏览器安全性 # 仅在受控的测试环境中使用 chromeArgs: - '--disable-features=ThirdPartyCookiePhaseout' - '--disable-features=SameSiteByDefaultCookies' - '--window-size=1920,1080' ``` ### `android` 部分 ```yaml android: # 设备 ID,可选,默认使用第一个连接的设备 deviceId: # 启动 URL,可选,默认使用设备当前页面 launch: # 输出 aiQuery/aiAssert 结果的 JSON 文件路径,可选 output: # 其他 AndroidDevice 构造函数支持的所有选项 # 例如:androidAdbPath, remoteAdbHost, remoteAdbPort, # imeStrategy, displayId, autoDismissKeyboard, keyboardDismissStrategy, # keyboardTypeDelay, minScreenshotBufferSize, alwaysRefreshScreenInfo 等 # 完整配置项请参考 AndroidDevice 的构造函数文档 ``` :::info 查看完整的 Android 配置项 YAML 脚本现在支持 `AndroidDevice` 构造函数的所有配置选项。完整的配置项列表请参考 [Android API 参考中的 AndroidDevice](/zh/reference.md#androiddevice)。 ::: #### Android 平台特定动作 Android 平台提供了一些特定的动作,可以在 YAML 脚本的 `flow` 中使用: **`runAdbShell` - 执行 ADB Shell 命令** 在 Android 设备上执行 ADB shell 命令。传入的内容只需要包含 shell 命令本身,不要包含 `adb shell` 前缀。如需设置命令超时,请将 `timeout` 作为 `runAdbShell` 的同级字段。 ```yaml android: deviceId: 'test-device' tasks: - name: 清除应用数据 flow: - runAdbShell: 'pm clear com.example.app' - name: 获取电池信息 flow: - runAdbShell: 'dumpsys battery' - name: 点击屏幕坐标 flow: - runAdbShell: 'input tap 100 200' - name: 执行带超时的命令 flow: - runAdbShell: 'dumpsys activity services' timeout: 60000 ``` 常用 ADB Shell 命令: * `pm clear ` - 清除应用数据 * `dumpsys battery` - 获取电池信息 * `dumpsys window` - 获取窗口信息 * `settings get secure android_id` - 获取设备 ID * `input tap ` - 点击屏幕坐标 * `input keyevent ` - 发送按键事件 **`launch` - 启动应用或 URL** 启动 Android 应用或打开 URL。 ```yaml android: deviceId: 'test-device' tasks: - name: 启动设置应用 flow: - launch: com.android.settings - name: 打开网页 flow: - launch: https://www.example.com ``` **`terminate` - 终止应用** 通过包名终止(强制停止)正在运行的 Android 应用。 ```yaml android: deviceId: 'test-device' tasks: - name: 终止设置应用 flow: - terminate: com.android.settings ``` ### `ios` 部分 ```yaml ios: # WebDriverAgent 端口,可选,默认 8100 wdaPort: # WebDriverAgent 主机地址,可选,默认 localhost wdaHost: # 是否自动关闭键盘,可选,默认 false autoDismissKeyboard: # 启动 URL 或应用包名,可选,默认使用设备当前页面 launch: # 输出 aiQuery/aiAssert 结果的 JSON 文件路径,可选 output: # 是否保存日志内容到 JSON 文件,可选,默认 `false`。如果为 true,保存到 `unstableLogContent.json` 文件中。如果为字符串,则保存到该字符串指定的路径中。日志内容的结构可能会在未来发生变化。 unstableLogContent: # 其他 IOSDevice 构造函数支持的所有选项 # 完整配置项请参考 IOSDevice 的构造函数文档 ``` :::info 查看完整的 iOS 配置项 YAML 脚本现在支持 `IOSDevice` 构造函数的所有配置选项。完整的配置项列表请参考 [iOS API 参考中的 IOSDevice](/zh/reference.md#iosdevice)。 ::: #### iOS 平台特定动作 iOS 平台提供了一些特定的动作,可以在 YAML 脚本的 `flow` 中使用: **`runWdaRequest` - 执行 WebDriverAgent API 请求** 在 iOS 设备上直接执行 WebDriverAgent API 请求。 ```yaml ios: launch: 'com.apple.mobilesafari' tasks: - name: 通过 WDA 按下主屏幕按钮 flow: - runWdaRequest: method: POST endpoint: /session/test/wda/pressButton data: name: home - name: 获取设备信息 flow: - runWdaRequest: method: GET endpoint: /wda/device/info ``` 参数: * `method`(字符串,必需):HTTP 方法(GET、POST、DELETE 等) * `endpoint`(字符串,必需):WebDriverAgent API 端点 * `data`(任意类型,可选):请求体数据 常用 WebDriverAgent 端点: * `/wda/screen` - 获取屏幕信息 * `/wda/device/info` - 获取设备信息 * `/session/{sessionId}/wda/pressButton` - 按硬件按钮 * `/session/{sessionId}/wda/apps/launch` - 启动应用 * `/session/{sessionId}/wda/apps/terminate` - 终止应用 * `/session/{sessionId}/wda/apps/activate` - 激活应用 **`launch` - 启动应用或 URL** 启动 iOS 应用或打开 URL。 ```yaml ios: wdaPort: 8100 tasks: - name: 启动设置应用 flow: - launch: com.apple.Preferences - name: 打开网页 flow: - launch: https://www.example.com ``` **`terminate` - 终止应用** 通过 Bundle ID 终止(关闭)正在运行的 iOS 应用。 ```yaml ios: wdaPort: 8100 tasks: - name: 终止设置应用 flow: - terminate: com.apple.Preferences ``` ### `harmony` 部分 `harmony` 部分用于通过 HDC 进行 HarmonyOS 设备自动化。设备通过 `hdc` 连接,运行前请先确认 `hdc list targets` 能列出你的设备。 ```yaml harmony: # 要连接的 HarmonyOS 设备 ID,可选,默认使用第一个已连接的设备。 deviceId: # 要启动的应用,可选,默认使用设备当前屏幕。 launch: # HDC 可执行文件路径,可选。 hdcPath: # 输入完成后是否自动收起键盘,可选,默认为 true。 autoDismissKeyboard: # 应用名到 bundle name 或显式 bundle/Ability 目标的自定义映射,可选。 # 用户提供的映射优先级高于默认映射。 appNameMapping: : # 用于输出 aiQuery/aiAssert 结果的 JSON 文件路径,可选。 output: # HarmonyDevice 构造函数支持的其他所有选项 ``` #### HarmonyOS 平台专属动作 **`launch` - 启动应用** 通过 bundle name 或显式 `bundle/Ability` 目标启动 HarmonyOS 应用。 ```yaml harmony: deviceId: 'test-device' appNameMapping: 视频: com.example.video/PhoneAbility tasks: - name: 启动应用 flow: - launch: 视频 ``` **`terminate` - 终止应用** 通过 Bundle 名或映射的应用名终止(强制停止)正在运行的 HarmonyOS 应用。 ```yaml harmony: deviceId: 'test-device' tasks: - name: 终止应用 flow: - terminate: com.example.app ``` **`runHdcShell` - 执行 HDC Shell 命令** 在 HarmonyOS 设备上执行 HDC shell 命令。 ```yaml harmony: deviceId: 'test-device' tasks: - name: 导出窗口信息 flow: - runHdcShell: command: 'hidumper -s WindowManagerService -a' ``` ### `computer` 部分 `computer` 部分用于 PC 桌面自动化。它允许你控制桌面环境,包括鼠标移动、键盘输入和屏幕查询。 ```yaml computer: # 使用的显示器 ID,可选,默认使用主显示器 displayId: # 输出 aiQuery/aiAssert 结果的 JSON 文件路径,可选 output: ``` #### 使用示例 ```yaml computer: {} tasks: - name: 打开浏览器并搜索 flow: - aiAct: 按下 Cmd+Space - sleep: 500 - aiAct: 输入 "Safari" 并按回车 - sleep: 2000 - aiAct: 按下 Cmd+L 聚焦地址栏 - aiAct: 输入 "https://www.bing.com" - aiAct: 按下回车 - sleep: 3000 - aiAct: 在搜索框中输入 "今日天气" 并按回车 - aiAssert: 结果显示天气信息 ``` :::info 平台说明 上面的示例脚本使用 macOS 命令。对于 Windows,请修改脚本: * 使用 `按下 Windows 键` 代替 `按下 Cmd+Space` * 使用 `输入 "Chrome"` 代替 `输入 "Safari"` * 使用 `按下 Ctrl+L` 代替 `按下 Cmd+L` ::: ### `tasks` 部分 `tasks` 部分是一个数组,定义了脚本执行的步骤。记得在每个步骤前添加 `-` 符号,表明这些步骤是个数组。 `flow` 部分的接口与 [API](/zh/reference.md#common) 几乎相同,除了一些参数的嵌套层级。 ```yaml tasks: - name: continueOnError: # 可选,错误时是否继续执行下一个任务,默认 false flow: # 自动规划(Auto Planning, .ai) # ---------------- # 执行一个交互,`ai` 是 `aiAct` 的简写方式 - ai: cacheable: # 可选,当启用 [缓存功能](./caching.mdx) 时,是否允许缓存当前 API 调用结果。默认值为 True deepThink: # 可选,引导 aiAct 注重任务拆解,并将任务 Planning 和 UI 元素定位拆解为不同的模型调用。默认值为 false。 deepLocate: # 可选,在 aiAct 执行过程中为 UI 元素定位开启深度定位。默认值为 false。 # 这种用法与 `ai` 相同 # 注意:在之前版本中也被写作 `aiAction`,当前版本兼容两种写法 - aiAct: cacheable: # 可选,当启用 [缓存功能](./caching.mdx) 时,是否允许缓存当前 API 调用结果。默认值为 True deepThink: # 可选,引导 aiAct 注重任务拆解,并将任务 Planning 和 UI 元素定位拆解为不同的模型调用。默认值为 false。 deepLocate: # 可选,在 aiAct 执行过程中为 UI 元素定位开启深度定位。默认值为 false。 # 即时操作(Instant Action, .aiTap, .aiHover, .aiInput, .aiKeyboardPress, .aiScroll) # ---------------- # 点击一个元素,用 prompt 描述元素位置 - aiTap: deepLocate: # 可选,是否为该元素开启深度定位。默认值为 False xpath: # 可选,目标元素的 xpath 路径,用于执行当前操作。如果提供了这个 xpath,Midscene 会优先使用该 xpath 来找到元素,然后依次使用缓存和 AI 模型。默认值为空 cacheable: # 可选,当启用 [缓存功能](./caching.mdx) 时,是否允许缓存当前 API 调用结果。默认值为 True fileChooserAccept: | [, ] # 可选,当点击触发文件选择器时,指定要上传的文件路径;仅在 web 中可用 # 鼠标悬停一个元素,用 prompt 描述元素位置 - aiHover: deepLocate: # 可选,是否为该元素开启深度定位。默认值为 False xpath: # 可选,目标元素的 xpath 路径,用于执行当前操作。如果提供了这个 xpath,Midscene 会优先使用该 xpath 来找到元素,然后依次使用缓存和 AI 模型。默认值为空 cacheable: # 可选,当启用 [缓存功能](./caching.mdx) 时,是否允许缓存当前 API 调用结果。默认值为 True # 输入文本到一个元素,用 prompt 描述元素位置 - aiInput: # 要输入文本的元素 value: <输入框的最终文本内容> deepLocate: # 可选,是否为该元素开启深度定位。默认值为 False xpath: # 可选,目标元素的 xpath 路径,用于执行当前操作。如果提供了这个 xpath,Midscene 会优先使用该 xpath 来找到元素,然后依次使用缓存和 AI 模型。默认值为空 cacheable: # 可选,当启用 [缓存功能](./caching.mdx) 时,是否允许缓存当前 API 调用结果。默认值为 True # 在元素上按下某个按键(如 Enter,Tab,Escape 等),用 prompt 描述元素位置 - aiKeyboardPress: # 要按键的目标元素 keyName: <按键> deepLocate: # 可选,是否为该元素开启深度定位。默认值为 False xpath: # 可选,目标元素的 xpath 路径,用于执行当前操作。如果提供了这个 xpath,Midscene 会优先使用该 xpath 来找到元素,然后依次使用缓存和 AI 模型。默认值为空 cacheable: # 可选,当启用 [缓存功能](./caching.mdx) 时,是否允许缓存当前 API 调用结果。默认值为 True # 全局滚动,或滚动 prompt 描述的元素 - aiScroll: # 可选,执行滚动的元素 scrollType: 'singleAction' # 或 'scrollToBottom' | 'scrollToTop' | 'scrollToRight' | 'scrollToLeft',默认值为 'singleAction' direction: 'down' # 或 'up' | 'left' | 'right',默认值为 'down'。仅在 scrollType 为 singleAction 时生效 distance: # 可选,滚动距离,单位为像素。设置为 null 表示由 Midscene 自动决定。 deepLocate: # 可选,是否为该元素开启深度定位。默认值为 False xpath: # 可选,目标元素的 xpath 路径,用于执行当前操作。如果提供了这个 xpath,Midscene 会优先使用该 xpath 来找到元素,然后依次使用缓存和 AI 模型。默认值为空 cacheable: # 可选,当启用 [缓存功能](./caching.mdx) 时,是否允许缓存当前 API 调用结果。默认值为 True # 在报告文件中记录当前截图,并添加描述 - recordToReport: # 可选,截图的标题,如果未提供,则标题为 'untitled' content: <content> # 可选,截图的描述 # 数据提取 # ---------------- # 执行一个查询,返回一个 JSON 对象 - aiQuery: <prompt> # 记得在提示词中描述输出结果的格式 name: <name> # 查询结果在 JSON 输出中的 key # 更多 API # ---------------- # 等待某个条件满足,并设置超时时间(ms,可选,默认 30000) - aiWaitFor: <prompt> timeout: <ms> # 执行一个断言 - aiAssert: <prompt> errorMessage: <error-message> # 可选,当断言失败时打印的错误信息。 name: <name> # 可选,给断言一个名称,会在 JSON 输出中作为 key 使用 - aiBoolean: <prompt> name: <name> # 可选,给布尔结果一个名称,会在 JSON 输出中作为 key 使用 # 运行一个 Gherkin Scenario。此功能从 Midscene 1.10 开始支持,目前仍处于 Beta 阶段。 - runGherkinScenario: | Scenario: 添加待办事项 Given 待办事项页面已经打开 When 我添加一条名为“买牛奶”的待办事项 Then 待办事项列表中应该包含“买牛奶” # 等待一定时间 - sleep: <ms> # 在 web 页面上下文中执行一段 JavaScript 代码 - javascript: <javascript> name: <name> # 可选,给返回值一个名称,会在 JSON 输出中作为 key 使用 - name: <name> flow: # ... ``` `runGherkinScenario` 从 Midscene 1.10 开始支持,目前仍处于 Beta 阶段。支持规则和限制请参考 [BDD 风格脚本(Gherkin)](/zh/advanced/bdd-style-scripts-with-gherkin.md)。 #### 步骤结果名称 带有 `name` 的步骤会把结果写入当前 YAML 运行结果和 JSON 输出中。使用 `name` 标记需要出现在运行结果里的值。 ```yaml tasks: - name: 保存提取结果 flow: - aiString: 读取页面中的商品 id name: product_id - aiQuery: 提交商品 id 后,获取搜索结果 name: search_result ``` #### 使用 `aiTap` 上传文件 当点击某个按钮会弹出文件选择器时,可以在 `aiTap` 步骤上直接设置 `fileChooserAccept`。它支持单个路径,也支持路径数组。 ```yaml tasks: - name: 上传单个文件 flow: - aiTap: 选择文件按钮 fileChooserAccept: ./fixtures/document.pdf - name: 上传多个文件 flow: - aiTap: 上传图片按钮 fileChooserAccept: - ./fixtures/image1.jpg - ./fixtures/image2.png ``` 如果你已经在用 `locate` 对象写 `prompt`、`images` 等定位信息,`fileChooserAccept` 仍然要和 `locate` 保持同级,不要放到 `locate` 或 `aiTap` 的嵌套对象里: ```yaml tasks: - name: 带 locate 的文件上传 flow: - aiTap: locate: prompt: 点击上传按钮 fileChooserAccept: ./fixtures/document.pdf ``` 注意: * `fileChooserAccept` 仅在 web 页面(Playwright、Puppeteer 或 Chrome extension Bridge mode)中可用。 * 相对路径会基于当前命令执行目录解析,而不是基于 YAML 文件所在目录解析。 * 如果文件不存在,脚本会在执行点击前直接报错。 * Chrome extension Bridge mode 上传本地文件时,需要在 `chrome://extensions` > Midscene > “Details” 中开启插件的 “Allow access to file URLs” 权限。开启后请从目标 `http(s)://` 页面重新连接桥接模式。 * Chrome extension Bridge mode 不支持目录上传输入框(`webkitdirectory` / `directory`)。如需上传目录,请使用 Playwright。 #### 使用图像提示 对于支持在提示词中附带图像的步骤(参见 [API 参考](/zh/reference.md#prompting-with-images)),可以把提示词改写为对象,并通过设置 `images` 字段(一个包含 `name` 和 `url` 的对象数组)来附加图像。该对象包含以下字段: * `prompt`:发送给模型的文本描述。 * `images`(可选):提示词引用的参考图像,每一项需要提供 `name` 和 `url`。 * `convertHttpImage2Base64`(可选):在图片链接无法公开访问时,将 HTTP 链接转换为 Base64 再发送给模型。 图片 URL 可以是本地路径、Base64 字符串或远程链接。如果图片链接无法被模型访问,请设置 `convertHttpImage2Base64: true`,Midscene 会将图像下载后以 Base64 字符串的形式发送给模型。 对于 `aiTap`、`aiHover`、`aiDoubleClick`、`aiRightClick` 等交互操作,请把文本和图像配置写在 `locate` 字段中,`locate` 与操作指令同级。 ```yaml tasks: - name: 校验品牌一致性 flow: - aiHover: locate: prompt: 将鼠标移动到包含 GitHub 标志的区域。 images: - name: GitHub 标志 url: https://github.githubassets.com/assets/GitHub-Mark-ea2971cee799.png convertHttpImage2Base64: true - aiTap: locate: prompt: 点击包含 GitHub 标志的区域。 images: - name: GitHub 标志 url: https://github.githubassets.com/assets/GitHub-Mark-ea2971cee799.png convertHttpImage2Base64: true ``` > 旧的嵌套写法(将 `locate` 缩进到操作指令内部,例如 `aiTap: \n locate: ...`)仍然支持,但不推荐使用。 对于 `aiAct`(及其简写 `ai`),以及 `aiAsk`、`aiQuery`、`aiBoolean`、`aiNumber`、`aiString`、`aiAssert` 等视觉问答类步骤,都可以直接在操作指令下设置 `prompt` 和 `images` 字段。 ```yaml tasks: - name: 校验品牌一致性 flow: - aiAssert: prompt: 判断页面上是否出现该图像。 images: - name: 目标标志 url: https://github.githubassets.com/assets/GitHub-Mark-ea2971cee799.png convertHttpImage2Base64: true ``` ## 注意事项 ### `agent.runYaml` 只会解析 `tasks` 字段 当使用 `agent.runYaml()` api 时,YAML 文件中只有 `tasks` 字段会被解析和执行。 因为此时 agent 已经在 js 脚本中完成初始化,无法根据 YAML 中的 `agent` 配置再次初始化。 --- url: /zh/awesome-midscene.md --- # Awesome Midscene 基于 Midscene.js 开发的社区项目精选列表,涵盖不同平台和编程语言的扩展功能。 ## 社区项目 ### iOS 自动化 * **[midscene-ios](https://github.com/lhuanyu/midscene-ios)** - iOS Mirror 应用的自动化支持工具 * 支持 iOS 应用程序的自动化测试和交互 * 将 Midscene 的跨平台能力扩展到苹果移动生态系统 ### PC 自动化 * **[midscene-pc](https://github.com/Mofangbao/midscene-pc)** - 支持 Windows、macOS 和 Linux 的 PC 操作设备 * 支持跨所有主流平台的桌面应用程序自动化测试和交互 * 支持本地和远程操作能力 * **[midscene-pc-docker](https://github.com/Mofangbao/midscene-pc-docker)** - 预装 Midscene-PC 服务器的 Docker 容器镜像 * 基于 Ubuntu 20 和 GNOME 桌面,最大化应用程序兼容性 * 内置 VNC 服务,支持通过浏览器监控桌面操作 * 一键命令即可在标准服务器上部署自动化客户端 ### Python SDK * **[Midscene-Python](https://github.com/Python51888/Midscene-Python)** - Python 版本的 Midscene SDK * 为 Python 开发者提供 Midscene 的 AI 驱动自动化能力 * 支持与现有 Python 测试和自动化工作流程的集成 ### Java SDK * **[midscene-java](https://github.com/Master-Frank/midscene-java)** by @Master-Frank - Java 版本的 Midscene SDK * 提供与 Python 版本类似的体验,适配 JVM 生态 * 易于整合到现有的 Java 自动化或测试流程 * **[midscene-java](https://github.com/alstafeev/midscene-java)** by @alstafeev - Java 版本的 Midscene SDK * 提供用于脚本化 Midscene 的 JVM 原生接口 * 无缝整合至现有的 Java 测试框架与自动化工作流程 ## 如何贡献 创建了扩展 Midscene.js 功能的项目?我们很乐意在这里展示! 要将你的项目添加到这个列表,请在 [Midscene 仓库](https://github.com/web-infra-dev/midscene) 中提交 issue,告知我们你的 awesome midscene 项目。 ## 收录标准 Awesome Midscene 应当满足: * 扩展或集成 Midscene.js 功能 * 积极维护中 * 有清晰的文档和使用示例 * 为 Midscene 社区提供价值 *** *没有看到你喜欢的平台或语言支持?考虑创建一个社区项目或为现有项目贡献代码!* --- url: /zh/basics.md --- # 基本概念 本文概述 Midscene 的核心概念,包括 Agent 的用法、架构、抽象方式和能力边界。你将了解 Agent 如何连接 AI 模型与目标界面,以及如何实现交互、断言等操作。 :::info 你可以在 Playground 中零代码试用下文介绍的所有 API,并直接查看运行效果。开始使用请参考[快速开始](./quick-start#chrome-extension)。 ::: ## 规划并交互 ### `aiAct` [`aiAct`](./reference/#agentaiact) 接收用自然语言描述的目标。它会观察界面,规划操作步骤,定位目标元素并执行操作,直至完成目标。提示词也可以包含断言条件。Midscene 会在执行过程中验证这些条件,并在断言失败时及时抛出错误。 `aiAct` 灵活且自主,适合处理多步骤、包含条件分支或执行路径不确定的任务。执行期间,`aiAct` 会基于最新的界面状态持续进行 AI 规划,因此它通常比即时交互消耗更多时间和 token。 典型用法: ```typescript await agent.aiAct( '搜索耳机,将第一件商品加入购物车,并确认购物车数量变为 1', ); ``` context 是提供给 AI 的补充信息,可以包含业务事实、判断或操作规则、约束以及输出要求。它用于补充主提示词,而不是替代主提示词。 如需让所有需要调用 AI 模型的 Agent API 共享同一份补充信息,可以在创建 Agent 时设置 `aiContexts.default`。`aiContexts.default` 只是缺省回退值:如果配置了该值,只有当本次调用和对应 API 都没有自己的 context 时才会使用它。API 级和单次调用的 context 会覆盖它,不会与它自动合并。 ```typescript const agent = new PlaywrightAgent(page, { aiContexts: { default: '页面中的价格单位是美元。', aiQuery: '金额只返回数字,不包含货币符号。', }, }); ``` 如需在创建 Agent 后修改 context,可以使用 [`agent.setAIContext()`](./reference/#agentsetaicontext): ```typescript agent.setAIContext( 'aiAct', '如果出现 Cookie 授权弹窗,请先关闭。页面中的价格单位是美元。', ); ``` `aiAct` 提供以下单次调用配置: - `deepThink`:加强任务拆解,并通过不同的模型调用分别完成规划和元素定位。该选项可以提高复杂任务的稳定性,但会增加模型调用次数和延迟。 - `deepLocate`:增加一次模型调用,提高元素定位的准确性。当目标元素较小或容易与周围元素混淆时,可以启用该选项。 - `context`:仅为本次调用提供补充信息,例如业务事实、规则、约束或输出要求。它会覆盖 `aiContexts.aiAct`,后者又会覆盖 `aiContexts.default` 这个共享回退值。显式传入空字符串会让本次调用不使用继承的用户 context。 ```typescript await agent.aiAct('完成结账表单,在下单前停止', { deepThink: true, deepLocate: true, context: '如果出现地址确认弹窗,请选择默认收货地址。', }); ``` ## 即时交互 即时交互类 API 每次只执行一个指定操作:先定位 UI 元素,再对该元素执行固定动作。 这类 API 不会规划多个步骤。对于“如果出现弹窗,先关闭弹窗,然后点击结账按钮”这类需求,请使用 `aiAct`。即时交互 API 会将提示词视为目标元素的描述,而非工作流。 ### `aiTap` [`aiTap`](./reference/#agentaitap) 用于定位并点击元素。 典型用法: ```typescript await agent.aiTap('购物车中的结账按钮'); ``` 当目标较小或视觉特征不明显时,可以启用 `deepLocate` (多轮深度定位): ```typescript await agent.aiTap('右上角的购物车图标', { deepLocate: true, }); ``` ### `aiInput` [`aiInput`](./reference/#agentaiinput) 用于定位输入框并输入指定内容。它默认使用 `replace` 模式:先清空输入框中的现有内容,再输入新内容。 典型用法: ```typescript await agent.aiInput('邮箱地址输入框', { value: 'user@example.com', }); ``` 其他输入模式包括 `typeOnly` 和 `clear`。`typeOnly` 会保留现有内容,`clear` 仅清空输入框。 即时交互 API 还包括 `aiHover`、`aiClearInput`、`aiKeyboardPress`、`aiScroll`、`aiPinch`、`aiLongPress`、`aiDoubleClick` 和 `aiRightClick`。各 API 支持的平台不同,详见 API 参考中的[规划与交互](./reference/#planning-interaction)。 ## 界面理解(Insight) 界面理解类 API 只观察界面并返回分析结果,不会操作界面。它们默认使用当前截图。在 Web 页面中,如果任务需要读取截图中不可见的 DOM 信息,可以传入 `domIncluded`。 ### `aiAssert` [`aiAssert`](./reference/#agentaiassert) 用于检查自然语言描述的条件。条件成立时,该方法正常结束;条件不成立时,该方法会抛出错误,并在错误信息中说明模型返回的原因。 典型用法: ```typescript await agent.aiAssert('购物车中有一件商品,并且页面显示了小计金额'); ``` ### `aiQuery` [`aiQuery`](./reference/#agentaiquery) 用于从界面中提取结构化数据。请在提示词中描述所需数据,以及数据的预期类型或结构。 典型用法: ```typescript const items = await agent.aiQuery< Array<{ name: string; price: number }> >('购物车中的商品,{name: string, price: number}[]'); // items 示例:[{ name: '无线耳机', price: 99.9 }] ``` ### `aiBoolean` [`aiBoolean`](./reference/#agentaiboolean) 用于询问与界面有关的问题,并返回布尔值。 典型用法: ```typescript const loginDialogVisible = await agent.aiBoolean('登录对话框是否可见'); // loginDialogVisible 示例:true ``` 其他便捷方法包括:返回数字的 [`aiNumber`](./reference/#agentainumber),以及返回字符串的 [`aiString`](./reference/#agentaistring) 和 [`aiAsk`](./reference/#agentaiask)。 ## 用 JavaScript 编排工作流 {#javascript-orchestration} 使用 Midscene 编排自动化时,有两种基本方式:使用 `aiAct`,或使用 JavaScript 编排。 以下代码展示了这两种方式完成同一个任务。 使用 `aiAct`: ```typescript await agent.aiAct('检查列表中的所有记录,将未完成的记录标记为已完成'); ``` 使用 JavaScript 编排: ```typescript const recordNames = await agent.aiQuery<string[]>('列表中的所有记录名称'); for (const recordName of recordNames) { const completed = await agent.aiBoolean( `名为“${recordName}”的记录是否标记为“已完成”`, ); if (!completed) { await agent.aiTap(`名为“${recordName}”的记录`); } } ``` 从上面的示例可以看出,在 `aiAct` 模式下,Agent 负责规划执行路径。使用 JavaScript 编排时,开发者需要在代码中定义条件、循环和步骤顺序。 JavaScript 编排的执行路径明确,开发者可以精确控制每个分支的行为。因此,这种方式具有很强的确定性。 但是,JavaScript 编排只能响应代码已经覆盖的 UI 变化。例如,分辨率变化可能导致页面需要额外滚动。如果代码没有处理这类变化,工作流就会失败。 选择编排方式时,建议遵循以下原则: 1. 默认使用 `aiAct` 执行操作目标。Agent 会根据最新的界面状态决定具体步骤,因此能更好地适应页面变化。 2. 仅当操作流程明确且稳定时,才使用 JavaScript 编排。 3. 如果 JavaScript 编排代码越来越难以维护,或成功率持续下降,建议改用 `aiAct`。 --- url: /zh/blog-introducing-instant-actions-and-deep-think.md --- # 即时操作和深度思考 从 Midscene v0.14.0 开始,我们引入了两个新功能:即时操作(Instant Actions)和深度思考(Deep Think)。 ## 即时操作(Instant Actions)- 让交互表现更稳定 你可能已经熟悉我们的 `.ai` 接口。它是一个自动规划接口,用于与网页进行交互。例如,当进行搜索时,你可以这样做: ```typescript await agent.ai('在搜索框中输入 "Headphones",按下回车键'); ``` 在接口的背后,Midscene 会调用 LLM 来规划步骤并执行它们。你可以在报告中看到整个过程。这是一个非常常见的 AI Agent 运行模式。 ![](/blog/report-planning.png) 与此同时,许多测试工程师希望有一个更快的方式来执行 UI 操作。当在 AI 模型中使用复杂 prompt 时,一些 LLM 模型可能规划出错误的步骤,或者返回元素的坐标不准确。这些不可预测的过程时常常会让人感受到挫败。 为了解决这个问题,我们引入了 `aiTap()`, `aiHover()`, `aiInput()`, `aiKeyboardPress()`, `aiScroll()` 接口。这些接口会直接执行指定的操作,而 AI 模型只负责底层任务,如定位元素等。使用这些接口后,整个过程可以明显更快和更可靠。 例如,上面的搜索操作可以重写为: ```typescript await agent.aiInput('耳机', '搜索框'); await agent.aiKeyboardPress('Enter'); ``` 在报告中,你会看到现在已经没有了规划 (Planning) 过程: ![](/blog/report-instant-action.png) 使用这些接口的脚本看起来有点冗余(或者不太“智能”),但请相信,使用这些结构化的接口确实是一个节省时间的好方法,尤其是在操作已经非常明确的时候。 ## 深度思考(Deep Think)- 让元素定位更准确 当使用 Midscene 与一些复杂的 UI 控件交互时,LLM 可能很难定位目标元素。我们引入了一个新的选项 `deepThink`(深度思考)到即时操作接口中。 启用 `deepThink` 的即时操作函数签名如下: ```typescript await agent.aiTap('target', { deepThink: true }); ``` `deepThink` 是一种策略。它会首先找到一个包含目标元素的区域,然后“聚焦”在这个区域中再次搜索元素。通过这种方式,目标元素的坐标会更准确。 让我们以 Coze.com 的工作流编辑页面为例。这个页面有许多自定义的图标在侧边栏。这对于 LLM 来说很难区分目标元素和它的周围元素。 ![](/blog/coze-sidebar.png) 在即时操作中使用 `deepThink` 后,脚本会变成这样(当然,你也可以使用 javascript 接口): ```yaml tasks: - name: edit input panel flow: - aiTap: the triangle icon on the left side of the text "Input" deepThink: true - aiTap: the first checkbox in the Input form deepThink: true - aiTap: the expand button on the second row of the Input form (on the right of the checkbox) deepThink: true - aiTap: the delete button on the second last row of the Input form deepThink: true - aiTap: the add button on the last row of the Input form (second button from the right) deepThink: true ``` 通过查看报告文件,你会看到 Midscene 已经找到了页面中的每个目标元素。 ![](/blog/report-coze-deep-think.png) 就像上面的例子一样,精细的 `deepThink` 提示词是保持结果稳定的关键。 `deepThink` 只适用于支持视觉定位的模型,如 qwen2.5-vl。如果你使用的是像 gpt-4o 这样的模型,`deepThink` 将无法发挥作用。 --- url: /zh/bridge-mode.md --- import { PackageManagerTabs } from '@theme'; # Chrome 桥接模式(Bridge Mode) Midscene Chrome 插件的桥接模式允许你使用本地脚本来控制桌面版 Chrome。脚本既能连接新标签页,也可以附着到当前激活的标签页。 这种方式能复用本地浏览器的 cookies、插件和页面状态,与自动化脚本协作完成任务;在自动化领域也被称作 “man-in-the-loop”。 ![bridge mode](/midscene-bridge-mode.png) :::info Demo Project 查看桥接模式的示例项目:[https://github.com/web-infra-dev/midscene-example/blob/main/bridge-mode-demo](https://github.com/web-infra-dev/midscene-example/blob/main/bridge-mode-demo) ::: ## 配置 AI 模型服务 下面以豆包 Seed 2.1 Turbo 为例: ```bash export MIDSCENE_MODEL_BASE_URL="https://ark.cn-beijing.volces.com/api/v3" export MIDSCENE_MODEL_API_KEY="your-api-key" export MIDSCENE_MODEL_NAME="doubao-seed-2-1-turbo-260628" export MIDSCENE_MODEL_FAMILY="doubao-seed" ``` 将 `your-api-key` 替换为你的 API Key。 > 如需使用千问(Qwen)、GLM、Gemini 或 GPT-5 等其他模型,请参考[支持的模型与配置](/model-common-config.md)。 全部配置项请参考[模型配置](/model-config.md)。 > 桥接模式下,AI 模型配置需要写在 Node.js 侧(终端环境变量),而不是浏览器侧。 ## 快速开始 ### 第一步:在 Chrome 应用商店安装 Midscene 插件 安装 [Midscene Chrome 插件](https://chromewebstore.google.com/detail/midscene/gbldofcpkknbggpkmbdaefngejllnief)。 :::info 文件上传权限 如果桥接模式脚本需要上传本地文件,请打开 `chrome://extensions`,找到 Midscene 插件,进入 “Details”,开启 “Allow access to file URLs”。开启后请切回目标 `http(s)://` 页面,再重新连接桥接模式。 ::: ### 第二步:安装依赖 <PackageManagerTabs command="install @midscene/web tsx --save-dev" /> ### 第三步:编写脚本 将以下代码保存为 `./demo-new-tab.ts`。 ```typescript import { AgentOverChromeBridge } from "@midscene/web/bridge-mode"; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); Promise.resolve( (async () => { const agent = new AgentOverChromeBridge(); // 连接到桌面 Chrome 的新标签页 await agent.connectNewTabWithUrl("https://www.bing.com"); // 与普通 Midscene agent 的 API 相同 await agent.ai('type "AI 101" and hit Enter'); await sleep(3000); await agent.aiAssert("there are some search results"); await agent.destroy(); })() ); ``` ### 第四步:运行脚本 运行脚本: ```bash tsx demo-new-tab.ts ``` 脚本运行后,Chrome 扩展会弹出一个确认窗口,询问是否允许连接。点击 "Allow" 允许本次连接,或点击 "Always Allow" 以后自动允许所有连接请求(可在 Bridge Mode 面板中重置)。确认后你会看到桌面 Chrome 打开一个新标签页并交由脚本控制。 <p align="center"> <img src="/bridge_in_extension.png" alt="bridge in extension" width="400" /> </p> :::info 扩展安装后默认在后台持续监听连接请求,无需手动操作。扩展图标会显示状态徽标:黄点表示正在监听,绿点表示已连接。 ::: ## 在 YAML 自动化脚本中使用桥接模式 [YAML 自动化脚本](/zh/automate-with-scripts-in-yaml.md) 让你用更易读的方式描述流程。要启用桥接模式,在 `web` 中设置 `bridgeMode`:使用当前标签页填 `currentTab`,新建标签页填 `newTabWithUrl`。如需销毁时自动关闭新建标签页,可配置 `closeNewTabsAfterDisconnect: true`。 ```diff web: url: https://www.bing.com + bridgeMode: newTabWithUrl + closeNewTabsAfterDisconnect: true tasks: ``` ```bash midscene ./bing.yaml ``` 运行脚本后,在弹出的确认窗口中点击 "Allow" 即可。 ### 不支持的选项 桥接模式会复用桌面浏览器配置,以下选项将被忽略: * `userAgent` * `viewportWidth` * `viewportHeight` * `deviceScaleFactor` * `waitForNetworkIdle` * `cookie` * `extraHTTPHeaders` * `downloadPath` * `chromeArgs` ## 远程访问配置 默认情况下,Bridge Server 只监听 `127.0.0.1`,仅允许本机 Chrome 扩展连接。如需跨机器通信(例如脚本运行在机器 A,浏览器在机器 B),可启用远程访问。 **Server 端(Node.js 脚本)** ```typescript // 启用远程访问(推荐) const agent = new AgentOverChromeBridge({ allowRemoteAccess: true, // 监听 0.0.0.0:3766 }); // 或指定特定网卡 const agent = new AgentOverChromeBridge({ host: '192.168.1.100', port: 3766, }); ``` **Client 端(Chrome 插件)** 1. 打开插件的 Bridge Mode 页面 2. 在 "Bridge Server URL" 输入框中填写服务器地址 * 本地:`ws://localhost:3766`(默认) * 远程:`ws://192.168.1.100:3766`(替换成你的服务器 IP) 3. 运行脚本后,在弹出的确认窗口中点击 "Allow" 即可 <p align="center"> <img src="/bridge_remote_config.png" alt="bridge remote config" width="400" /> </p> :::warning 安全提示 开启远程访问后 Bridge Server 将暴露在网络中,请确保: * 仅在可信网络环境使用 * 使用防火墙限制访问 * 不要在公网场景开启,避免安全风险 ::: ## FAQ * **模型配置(如 `MIDSCENE_MODEL_API_KEY`)应该配置在浏览器还是终端?** 使用桥接模式时,请在终端(Node.js 环境)中配置模型参数。支持的模型和配置示例请参考[支持的模型与配置](/zh/model-common-config.md)。 ## 更多 * 更多 Agent 的 API 请参考 [API 参考](/zh/reference.md#interaction-methods)。 * 完整的 Chrome 桥接 API 可参阅 [API 参考(Web)](/zh/reference.md#chrome-bridge-agent)。 * 样例项目 * 桥接模式示例:[https://github.com/web-infra-dev/midscene-example/blob/main/bridge-mode-demo](https://github.com/web-infra-dev/midscene-example/blob/main/bridge-mode-demo) --- url: /zh/caching.md --- # 缓存 AI 规划和定位 Midscene 支持缓存两类内容:AI 规划的步骤,以及匹配到的元素定位信息。前者可用于各类自动化任务,以减少 AI 模型调用次数并提升执行效率;后者中的 DOM 元素定位信息(XPath)在 Web 自动化任务中可显著减少重复定位开销,不过目前仅适用于 Web 场景,并且存在[一定局限性](#使用-xpath-缓存元素定位信息的局限性)。 **效果** 当缓存命中时,脚本的执行时间会显著降低。例如在如下案例中,执行耗时从51秒降低到了28秒。 * **before** ![](/cache/no-cache-time.png) * **after** ![](/cache/use-cache-time.png) ## 缓存文件和存储 Midscene 的缓存机制基于输入的稳定性和输出的可复用性。当相同的任务指令在相似的页面环境下重复执行时,Midscene 会优先使用已缓存的结果,避免重复调用 AI 模型,从而显著提升执行效率。 缓存的核心机制包括: * **任务指令缓存**:对于规划类操作(如 `ai`、`aiAct`),Midscene 会将 prompt 指令作为缓存键,存储 AI 返回的执行计划 * **元素定位缓存(仅 Web)**:对于定位类操作(如 `aiLocate`、`aiTap`),系统会将定位 prompt 作为缓存键,存储元素的 XPath 信息,下次执行时先验证 XPath 是否仍然有效 * **失效机制**:当缓存失效时,系统会自动回退到 AI 模型重新分析 * **规划缓存回退**:如果缓存的 `aiAct` 规划在运行时失败,例如偶现弹窗本次没有出现,Midscene 会在本次运行中回退到正常 AI 规划,并清空这条过期规划缓存的 flow * **永不缓存查询结果**:查询类操作(如 `aiBoolean`、`aiQuery`、`aiAssert`)不会被缓存 当发生规划缓存回退时,即使本次回退规划成功,Midscene 也不会把回退生成的 flow 写回原 prompt 的规划缓存。因为失败前的缓存步骤可能已经部分改变页面状态,回退规划得到的 flow 不一定适用于从初始页面重新执行。下一次运行相同 prompt 时,这条空 flow 会被视为不可用缓存,并重新生成完整的规划缓存。 缓存内容会保存到 `./midscene_run/cache` 目录下,以 `.cache.yaml` 为扩展名。 如果缓存未命中,Midscene 将会重新调用 AI 模型,并更新缓存文件。 ## 缓存策略 通过配置 `cache` 选项,你可以为 Agent 启用缓存。 ### 禁用缓存 配置方式:`cache: false` 或不配置 `cache` 选项 完全禁用缓存功能,每次都重新调用 AI 模型。适合需要实时结果或调试时使用。默认情况下,如果不配置 `cache` 选项,缓存是禁用状态。 ```javascript // 直接创建 Agent const agent = new PuppeteerAgent(page, { cache: false, }); ``` ```yaml # YAML 配置 agent: cache: false ``` ### 读写模式 配置方式:`cache: { id: "my-cache-id" }` 或 `cache: { strategy: "read-write", id: "my-cache-id" }` 自动读取已有缓存,执行过程中自动更新缓存文件。`strategy` 的默认值是 `read-write`。 ```javascript // 直接创建 Agent - 显式设置 cache ID const agent = new PuppeteerAgent(page, { cache: { id: "my-cache-id" }, }); // 显式指定 strategy const agent = new PuppeteerAgent(page, { cache: { strategy: "read-write", id: "my-cache-id" }, }); ``` ```yaml # YAML 配置 - 显式设置 cache ID agent: cache: id: "my-cache-test" # 显式指定 strategy agent: cache: id: "my-cache-test" strategy: "read-write" ``` YAML 模式还支持配置 `cache: true`,自动使用文件名作为 cache ID。 ### 只读,手动写入 配置方式:`cache: { strategy: "read-only", id: "my-cache-id" }` 只读取缓存,不自动写入缓存文件,需要手动调用 `agent.flushCache()` 写入缓存文件,适合生产环境,确保缓存的一致性 ```javascript // 直接创建 Agent const agent = new PuppeteerAgent(page, { cache: { strategy: "read-only", id: "my-cache-id" }, }); // 需要手动写入缓存 await agent.flushCache(); ``` ```yaml # YAML 配置 agent: cache: id: "my-cache-test" strategy: "read-only" ``` ### 只写模式 配置方式:`cache: { strategy: "write-only", id: "my-cache-id" }` 只写入缓存,不读取已有缓存内容。每次执行时都会调用 AI 模型,并将结果写入缓存文件。适合初次建立缓存或更新缓存时使用。 ```javascript // 直接创建 Agent const agent = new PuppeteerAgent(page, { cache: { strategy: "write-only", id: "my-cache-id" }, }); ``` ```yaml # YAML 配置 agent: cache: id: "my-cache-test" strategy: "write-only" ``` ### 兼容方式(不推荐) 通过环境变量 `MIDSCENE_CACHE=1` 配合 cacheId 配置,等同于读写模式。 ```javascript // 旧方式,需要 MIDSCENE_CACHE=1 环境变量和 cacheId const agent = new PuppeteerAgent(originPage, { cacheId: 'puppeteer-swag-sab' }); ``` ```bash MIDSCENE_CACHE=1 tsx demo.ts ``` ## 使用 Midscene 的 Playwright AI Fixture 在使用 `@midscene/web/playwright` 中的 `PlaywrightAiFixture` 时,可以通过相同的 `cache` 配置来管理缓存行为。 ### 禁用缓存 ```typescript // fixture.ts in sample code export const test = base.extend<PlayWrightAiFixtureType>( PlaywrightAiFixture({ cache: false, }), ); ``` ### 读写模式 ```typescript // 对应样例代码中的 fixture.ts // 自动生成 cache ID(基于测试信息) export const test = base.extend<PlayWrightAiFixtureType>( PlaywrightAiFixture({ cache: true, }), ); // 对应样例代码中的 fixture.ts // 显式指定 cache ID export const test = base.extend<PlayWrightAiFixtureType>( PlaywrightAiFixture({ cache: { id: "my-fixture-cache" }, }), ); ``` ### 只读,手动写入 ```typescript // 对应样例代码中的 fixture.ts export const test = base.extend<PlayWrightAiFixtureType>( PlaywrightAiFixture({ cache: { strategy: "read-only", id: "readonly-cache" }, }), ); ``` 在只读模式下,需要在测试步骤完成后手动将缓存写入文件。可以通过 fixture 提供的 `agentForPage` 方法获取底层 agent,然后在需要持久化的时刻调用 `agent.flushCache()`: ```typescript test.afterEach(async ({ page, agentForPage }, testInfo) => { // Only flush cache if the test passed if (testInfo.status === 'passed') { console.log('Test passed, flushing Midscene cache...'); const agent = await agentForPage(page); await agent.flushCache(); } else { console.log(`Test ${testInfo.status}, skipping Midscene cache flush.`); } }); test('manual cache flush', async ({ agentForPage, page, aiTap, aiWaitFor }) => { const agent = await agentForPage(page); await aiTap('first highlighted link in the hero section'); await aiWaitFor('the detail page loads completely'); await agent.flushCache(); }); ``` ### 只写模式 ```typescript // 对应样例代码中的 fixture.ts export const test = base.extend<PlayWrightAiFixtureType>( PlaywrightAiFixture({ cache: { strategy: "write-only", id: "write-only-cache" }, }), ); ``` 在只写模式下,每次测试都会调用 AI 模型,并将结果自动写入缓存文件,不会读取已有缓存。 ## 缓存清理 Midscene 支持在写入缓存时清理未使用的缓存记录,确保缓存文件保持精简。这个功能是**完全手动**的,需要显式调用 `agent.flushCache({ cleanUnused: true })`。 ### 手动清理机制 当调用 `agent.flushCache({ cleanUnused: true })` 时,系统会: 1. **保留使用过的缓存**:本次运行中被匹配和使用的缓存记录会被保留 2. **保留新增的缓存**:本次运行中新生成的缓存记录会被保留 3. **删除未使用的缓存**:旧的、未被使用的缓存记录会被自动删除 4. **写入文件**:清理后的缓存会被写入文件 ### 使用方式 **在测试的 afterEach 中统一调用:** ```javascript describe('test suite', () => { let resetFn: () => Promise<void>; let agent: PuppeteerAgent; afterEach(async () => { // 清理缓存并写入文件 if (agent) { await agent.flushCache({ cleanUnused: true }); } // 再关闭页面 if (resetFn) { await resetFn(); } }); it('test case', async () => { const { originPage, reset } = await launchPage('https://example.com/'); resetFn = reset; agent = new PuppeteerAgent(originPage, { cache: { id: 'my-cache-id' }, }); // ... test logic }); }); ``` **Playwright AI Fixture 用户:** ```typescript test.afterEach(async ({ page, agentForPage }) => { const agent = await agentForPage(page); await agent.flushCache({ cleanUnused: true }); }); ``` ### 清理行为说明 * **read-write 模式**:调用 `flushCache({ cleanUnused: true })` 会清理并写入文件 * **read-only 模式**:调用 `flushCache({ cleanUnused: true })` 也会清理并写入文件(手动 flush 覆盖 read-only 限制) * **write-only 模式**:不执行清理(因为不读取缓存) **注意**:如果不传 `cleanUnused: true` 参数,`flushCache()` 只会写入文件而不会清理未使用的缓存。 ## FAQ ### 没有生成缓存文件 请确认你已正确配置缓存: 1. **直接创建 Agent**: 在构造函数中设置 `cache: { id: "your-cache-id" }` 2. **Playwright AI Fixture 模式**: 在 fixture 配置中设置 `cache: true` 或 `cache: { id: "your-cache-id" }` 3. **YAML 脚本模式**: 在 YAML 文件中设置 `agent.cache.id` 4. **只读模式**: 确保调用了 `agent.flushCache()` 方法 5. **旧方式**: 设置了 `cacheId` 并启用了 `MIDSCENE_CACHE=1` 环境变量 ### 如何检查缓存是否命中? 你可以查看报告文件。如果缓存命中,你将看到 `cache` 提示,并且执行时间大幅降低。 ### 为什么在 CI 中无法命中缓存? 你需要在 CI 中将缓存文件提交到仓库中,并再次检查缓存命中的条件。 ### 如果有了缓存,是否就不需要 AI 服务了? 不是的。 缓存是加速脚本执行的手段,但它不是确保脚本长期稳定执行的工具。我们注意到,当页面发生变化时,缓存可能会失效(例如当元素 DOM 结构发生变化时)。在缓存失效时,Midscene 仍然需要调用 AI 服务来重新执行任务。 ### 如何手动删除缓存? 你可以删除 `./midscene_run/cache` 目录中的缓存文件,或者编辑缓存文件的内容。 ### 如果我想禁用单个 API 的缓存,怎么办? 你可以使用 `cacheable` 选项来禁用单个 API 的缓存。 具体用法请参考对应 [API](/zh/reference.md#common) 的文档。 ### 使用 XPath 缓存元素定位信息的局限性 Midscene 使用 [XPath](https://developer.mozilla.org/en-US/docs/Web/XML/XPath) 来缓存元素定位信息。我们使用相对严格的策略来防止误匹配。在以下情况下,缓存不会命中: 1. 新元素在相同的 XPath 下的文本内容与缓存元素不同。 2. 页面的 DOM 结构与缓存时的结构不同。 此外,由于元素定位缓存依赖 DOM 结构,以下场景无法使用缓存功能: 1. **Canvas 元素**:Canvas 内部的图形内容不存在 DOM 节点,无法通过 XPath 定位。 2. **跨域 iframe**:浏览器安全策略限制了对跨域 iframe 内部 DOM 的访问。 3. **Shadow DOM(closed 模式)**:封闭的 Shadow DOM 无法从外部访问其内部结构。 4. **WebGL / SVG 动态内容**:动态生成的图形内容可能没有稳定的 DOM 结构。 当缓存未命中或不可用时,Midscene 将回退到使用 AI 服务来查找元素。 ### 获取缓存相关的调试日志 在环境变量中配置 `DEBUG=midscene:cache:*`,你可以看到缓存相关的调试日志。 --- url: /zh/changelog.md --- # 更新日志 ## v1.12 - DeepSeek V4、新版 Test Runner 与报告耗时汇总 v1.12 支持 DeepSeek V4 视觉模型,推出新版 Test Runner(Beta),并为报告增加耗时汇总。 ### DeepSeek V4 视觉模型支持 * 支持 `deepseek-v4-flash-vision-exp`。配置方法参见[常用模型配置](/zh/model-common-config.md#deepseek),更多信息参见[Midscene 1.12 支持 DeepSeek V4 Flash Vision Exp](https://mp.weixin.qq.com/s/FDAPd1WrNP6Sb8SRJUen6w)。 ### 新版 Test Runner(Beta) * 新增 `@midscene/test`,支持使用 YAML 编写自然语言测试,也可通过 TypeScript Node 扩展。Test Runner 将逐步替代旧版 YAML 自动化方案,协议和 API 目前仍处于 Beta 阶段。详见 [Test Runner 概览](/zh/test-runner-overview.md)。 ### 报告与稳定性 * Report 侧边栏新增总耗时和模型调用耗时,Markdown 报告也会按模型汇总耗时和 Token 用量。 * 截图处理流程会把观察帧统一保存为 JPEG,减少运行产物占用的空间。 * 修复 Electron ASAR 包中的资源路径解析问题。受影响的资源包括 yadb 和 scrcpy 外部二进制文件。 ## v1.11 - 多平台稳定性改进 v1.11 提高了模型验证、移动端输入和报告生成的稳定性。 ### 稳定性与文档 * 修复模型验证回调丢失 Playground SDK 方法上下文的问题。 * 修复 HarmonyOS 无法正确解析应用启动目标的问题。该问题会影响已声明的 ability 和经过映射的目标。 * Android 和 HarmonyOS 清空输入框前会先全选文本,以兼容更多输入法和控件。 * Playwright 报告文件名增加长度限制和重名规避,避免长标题触发 `ENAMETOOLONG` 错误。 * 补充 Doubao Seed Android showcase 的执行成本数据。 ## v1.10 - BDD 风格脚本、Doubao-Seed-2.1 与 MCP 下线 v1.10 新增 BDD 风格的 Gherkin 脚本执行能力,升级推荐模型到 Doubao-Seed-2.1,并正式下线 MCP server 包,后续推荐通过 Skills 与各平台 CLI 让 AI Agent 驱动 Midscene。 ### BDD 风格脚本(Gherkin) * 新增 `agent.runGherkinScenario()`,可在 JavaScript / TypeScript 中直接运行 Gherkin 场景。 * YAML flow 新增 `runGherkinScenario` 步骤,可把自然语言场景写成 `Given` / `When` / `Then` 结构并按步骤执行。 * `Given` / `When` 会映射为 `aiAct`,`Then` 以及跟随它的 `And` / `But` 会映射为 `aiAssert`,让测试用例既保持自然语言可读性,又有稳定的步骤结构。 * 当前支持围绕单个 `Scenario` 的 Gherkin 子集,BDD 相关能力仍处于 Beta 阶段。详见:[BDD 风格脚本(Gherkin)](/zh/advanced/bdd-style-scripts-with-gherkin.md) ### 新增模型支持 * 推荐并支持 `Doubao-Seed-2.1-turbo`,在当前私有测评集中具备很快的定位速度和良好的定位效果。 * 豆包 Seed 系列统一使用 `MIDSCENE_MODEL_FAMILY="doubao-seed"`,同时继续兼容旧的 `doubao-vision` family。详见:[常用模型配置](/zh/model-common-config.md#doubao-seed-model)、[模型策略](/zh/model-strategy.md) ### MCP 下线 * Midscene 不再发布 MCP server 包,包括 `@midscene/web-bridge-mcp`、`@midscene/android-mcp`、`@midscene/ios-mcp`、`@midscene/harmony-mcp`、`@midscene/computer-mcp` 和 `@midscene/mcp`。 * 需要 AI 编程 Agent 操作浏览器、移动设备或桌面应用时,请改用 [Skills](/zh/skills.md) 与各平台 CLI。 * 如果仍依赖 MCP server,请将 Midscene 固定在 `1.9.8`。这是最后一个包含 MCP 支持的版本。详见:[MCP 集成已下线](/zh/mcp.md) ## v1.9 - 新增模型支持、YAML 自动化与 AndroidWorld Benchmark v1.9 版本扩展了模型支持,改进了 YAML 自动化,并提升了报告查看、Android 自动化、Web 输入和桌面自动化的稳定性。 ### AndroidWorld Benchmark Midscene 新增 AndroidWorld benchmark 报告。使用 v1.9.5 测试时,Midscene 达到 **Pass@1 93.10%**、**Pass@2 95.69%**、**Pass@3 97.41%**。详见:[AndroidWorld Benchmark 报告](/zh/android-world-benchmark-report.md) ### 新增模型支持 * 新增 Kimi 和 Xiaomi MiMo 模型支持。详见:[常用模型配置](/zh/model-common-config.md) ### 模型与规划更新 * `aiAct` 支持图片提示。 * `MIDSCENE_MODEL_REASONING_ENABLED` 支持 `default`,适配模型默认思考行为。 * Gemini thinking content 与 GPT-5 reasoning 配置处理更完整。 * `aiAct` 在缓存失效时会回退到模型规划,并清空对应缓存。 * AI 请求错误会包含重试次数信息;模型响应解析失败时会保留原始响应;解析后的 locate 结果会先校验再使用。 * 模型 dump 会暴露更多模型响应元数据,包括 raw choice message,以及 usage 中的响应模型名。 * `deepLocate` 的搜索区域会显示在报告上。 ### Chrome 扩展 * Chrome 扩展 Bridge mode 支持文件上传。 * Bridge mode 文件上传支持 file chooser accept 过滤与 WSL 文件路径。 ### YAML、CLI 与 MCP * `1.9.8` 是最后一个包含 MCP 支持的 Midscene 版本。后续版本会下线 MCP server 包,改为推荐 Skills 与各平台 CLI。 * 各平台 CLI 支持传入 agent behavior init args。 * CLI 的 YAML 脚本新增 HarmonyOS target,HarmonyOS 自动化可以通过与 Web、Android、iOS、Computer 一致的脚本运行器流程执行。详见:[YAML 脚本自动化](/zh/automate-with-scripts-in-yaml.md)、[HarmonyOS API](/zh/reference.md#harmonyos) * YAML Web config 支持自定义 HTTP headers。 * YAML Web config 支持通过 `downloadPath` 指定浏览器下载目录。 * YAML 执行会暴露真实错误,不再只落到静默的 "not executed" 结果。 * YAML 批量执行支持重试失败用例。 * YAML 成功执行后会打印报告路径。 * 显式指定的 YAML report 文件名会被正确保留。 * CLI / MCP / Skill 流程可以通过共享参数暴露 `deepLocate` / `deepThink` 控制项。 * Assert CLI / MCP 工具会转发自定义失败信息,让断言失败更清晰。 * CLI 会从 CLI 包自身解析 `@rstest/core`,并延迟加载 Rstest core,让外部启动路径下的 framework 执行更稳定。 ### 报告 * `recordToReport` 支持自定义截图。 * Report 导出会让图片路径与导出的截图保持一致。 * Report 新增 JSON tree view,方便查看结构化任务和模型数据。 * 优化 Report 截图、标签、Playground server origin 处理与上下文间距。 ### Studio 与 Recorder * 稳定 Studio recorder 描述与预览输入合并行为。 * Studio 会更安全地处理无效的模型环境变量配置。 * Recorder 工作流支持生成 Markdown replay output。 ### Android 自动化 * 改进 Android action controls 和规划提示,提升原生移动端自动化流程的稳定性。 ### Computer 自动化 * Computer desktop automation 新增 Intel packaging。 * Libnut 滚动现在每个 tick 会发出完整的一次 wheel delta。 ### 问题修复 * 修复 Web integration 中 `longPress` 时长被限制在 600ms 的问题。 * 修复 Web 输入框在输入过程中重新渲染时可能丢字符的问题。 * 修复部分环境下 HarmonyOS MCP 因 `photon` / `sharp` WASM 初始化失败而无法启动的问题。 * 修复 Computer RDP 首帧空白截图问题。 * 自动修复 Computer phased-scroll helper 缺失可执行权限的问题。 * 补充 elevated Windows 应用输入丢失警告。 * 补充 Computer 自动化的 IPv6 RDP host 支持。 ### 文档更新 * 补充 Azure OpenAI-compatible endpoint 配置说明。 ## v1.8 - Midscene Studio 桌面端与多平台增强 v1.8 版本带来全新的桌面端应用 **Midscene Studio**,新增长按/清空输入等多项 API,并对模型规划行为、设备集成、报告系统和 MCP 工具集进行了全面升级。 ### 全新桌面端应用 Midscene Studio(Beta) Midscene Studio 是一个基于 Electron 的桌面应用,把多平台 Playground 整合进一个原生界面,开箱即用。当前处于 **Beta** 阶段,可从 [latest release 页面](https://github.com/web-infra-dev/midscene/releases/latest) 选择 `midscene-studio-beta-*` 资源下载最新版 Studio,欢迎试用并反馈问题: * **多平台 Playground**:Web、Android、iOS、HarmonyOS、Computer 在同一个 Studio 应用中无缝切换 * **设备交互预览**:Android / iOS / HarmonyOS 设备预览支持手动鼠标和触控控制;Web 预览支持实时画面流式渲染 #### 下一步:在 Studio 中录制生成可回放的 Midscene 脚本 我们正在 Studio 中打造一条「录制 → 脚本 → 回放」的闭环工作流:直接在 Studio 里对真实设备进行操作录制,自动生成结构化的 Midscene 脚本,并能即时在 Studio 内重新回放、调试、导出。该能力将在后续版本中陆续开放,敬请期待。 ### YAML 工作流增强 * **Android runAdbShell timeout**:在 JavaScript API 和 YAML 脚本中都支持 `timeout` 选项。详见:[Android API](/zh/reference.md#android)、[YAML 脚本自动化](/zh/automate-with-scripts-in-yaml.md) ### 新增交互 API * **`agent.aiLongPress()`**:对指定元素执行长按操作,适用于触发长按菜单等场景。详见 [API 文档](/zh/reference.md#agentailongpress) * **`agent.aiClearInput()`**:清空指定输入框的内容,适合把清空当作独立一步的场景。详见 [API 文档](/zh/reference.md#agentaiclearinput) ### 设备与平台集成 * **iOS 连接外部 WDA 会话**:iOS 支持连接已有的 WebDriverAgent 会话,方便复用外部 WDA 环境 * **iOS 设备实现可覆盖**:允许使用自定义 iOSDevice 实现,便于深度扩展或定制 * **Computer 远程桌面**:Computer MCP / CLI 连接工具支持传入 RDP 连接选项,可直接接管远程 Windows 桌面 * **`agentForComputer` 命名修正**:新增 `agentForComputer` 作为主推 API,原有 `agentFromComputer` 保留为向后兼容别名 * **Puppeteer CLI viewport 选项**:Puppeteer CLI 新增窗口尺寸配置,方便在命令行中指定运行时的浏览器视口 ### 模型与规划行为 * **使用意图与配置槽位分离**:模型使用意图与实际解析到的配置槽位分离,多模型 Planning、定位和报告展示更清晰 * **默认关闭原生思考**:对于已支持的模型系列,Midscene 默认关闭模型原生思考,以提升执行速度和稳定性。详见:[模型原生的思考模式](/zh/model-config.md#model-native-reasoning) * **豆包低延迟模式**:支持豆包低延迟模式配置方式,可通过 `MIDSCENE_MODEL_EXTRA_BODY_JSON={"service_tier":"fast"}` 开启。详见:[常用模型配置](/zh/model-common-config.md) * **GLM-5V-Turbo 支持**:新增智谱 GLM-5V-Turbo 模型支持。详见:[常用模型配置](/zh/model-common-config.md) * **滚动选择规划优化**:优化滚动选择(scrollable select)的规划流程,提升复杂下拉与滚轮选择场景的成功率 ### MCP 与平台 CLI * **新增 `assert` MCP 工具**:MCP 新增基于 `aiAssert` 的断言工具,AI 助手可以直接调用断言能力。详见:[MCP 服务](/zh/mcp.md) * **Assert 支持图片提示**:Assert CLI / MCP 工具支持传入图片作为提示词,便于结合参考图进行断言 * **平台 CLI 接受裸初始化参数**:各平台 CLI 简化参数传递方式,直接接受平台 Agent 构造参数 * **Playwright fixture 透传 Agent 选项**:`PlaywrightAiFixture` 支持透传 `PlaywrightAgent` 构造参数,便于复用 fixture 时自定义 Agent 配置 ### 报告系统 * **CLI 合并报告**:CLI 新增 `report-tool merge` 子命令,可将多份报告文件合并为一个,便于集中查看 * **报告中记录截图工具调用**:截图工具(`take_screenshot`)的调用现在会在报告中显示,便于排查截图相关问题 ### Chrome 扩展 * **Chrome Web Store 发布自动化**:扩展发布到 Chrome Web Store 的流程已自动化,缩短发布周期 ### 问题修复 * 修复 `aiAct` 在动作真正执行前就触发完成状态的问题 * 修复 Insight prompt 在部分场景下优先使用参考图而不是当前截图的问题 * 修复新标签页导航后的 Bridge 连接问题 * 修复 iOS / HarmonyOS / Computer Playground 点击投影问题 * 修复 HarmonyOS 单次调用 `autoDismissKeyboard` 的配置不生效问题 * 修复 Android Playground 视频流内存占用过高的问题 * 修复 Computer 滚动默认距离与 Web 不一致的问题 * 修复部分模型返回归一化 \[0,1000] 坐标超出范围的边界问题 * 修复 Bridge 模式下 `aiAct` 选项未被继承的问题 * 修复 Action API 返回值与文档不一致的问题 * 修复 `maxTokens` 与意图模型配置不匹配的问题 * 修复服务端端口探测时未使用 `0.0.0.0` 与实际监听 host 不一致的问题 * 修复 `aiAct` 中 deepThink 标记在报告中丢失的问题 * 修复 iOS 输入时偶发的字符丢失问题 * 修复 HarmonyOS system action 延迟覆盖逻辑 ## v1.7 - 灵活处理报告文件、支持 Qwen 3.6 模型 ### 灵活处理报告文件 从 v1.7.0 开始,你可以把报告文件中的原始截图和 JSON 数据提取出来,或者把报告转录为 Markdown,方便其他工具继续消费这些内容。 ## 示例 你可以把报告文件解析为这样一份 Markdown 文件: ``` # Act - 搜索并播放 Midscene 相关的视频 - Execution start: 2026-04-08T02:13:04.795Z - Task count: 21 ## 1. Plan - 点击顶部的搜索框以激活输入 - Status: finished - Start: 2026-04-08T02:13:04.845Z - End: 2026-04-08T02:13:15.296Z - Cost(ms): 10451 - Screen size: 2880 x 1536 ![task-1](./screenshots/execution-1-task-1-f9fc3bf9-bdf6-48dd-abea-f8f29874d8c1.jpeg) ..... ``` 进一步,你可以结合 [Remotion Skill](https://www.remotion.dev/docs/ai/skills?utm_source=midscenejs) 解析这份 Markdown 文件,并生成一个个性化的回放视频。 视频生成结果如下: <video src="https://lf3-static.bytednsdoc.com/obj/eden-cn/vhaeh7vhabf/midscene-replay.mp4" height="300" controls /> Midscene 支持通过命令行工具或者 JavaScript SDK 来解析报告文件,使用方法详见:[解析报告文件](/zh/consume-report-file.md) ### 新增 Qwen 3.6 模型支持 适配了 Qwen 3.6 模型,可以在 Midscene 中使用最新的通义千问模型。详见:[模型配置](/zh/model-config.md) ### Chrome 扩展录制语言设置 Chrome 扩展的录制设置中新增了 YAML 输出语言选项,支持 English、Chinese、Japanese 等多种语言,也可设为 Auto 自动跟随系统语言。 ### Android / 鸿蒙端改进 * Android 和鸿蒙端新增 `terminate` 操作,支持强制停止指定应用,方便在测试中重置应用状态。详见:[Android API](/zh/reference.md#android)、[鸿蒙 API](/zh/reference.md#harmonyos) * 修复 Android 端在 X/Twitter 上输入时 placeholder 文本被意外保留的问题 * 修复 Android Playground 局域网访问问题 ### 调试体验改进 * 执行日志支持保存到磁盘,便于事后排查问题 * Playground 配置页面保存模型配置时,可运行连通性测试,及时发现配置错误 * Skill CLI 的 `run` 命令支持通过 `--image` 参数传入图片作为提示 ### 问题修复 * 修复文件选择器缺失文件时错误提示不清晰的问题 * 修复 CLI 批量运行时错误信息汇总不完整的问题 * 修复 YAML 脚本中 `aiScroll` 缩进格式错误的问题 * 修复 `aiLocate` 元素定位框不准确的问题 * 修复截图失败时缺少降级方案的问题 * 修复部分模型返回空响应时未正确处理的问题 * 修复 CDP 连接模式下标签页复用问题 * 修复 `aiQuery` 在特定数据结构下结果缺失的问题 * 修复 AutoGLM 启动应用时参数格式不正确的问题 * 修复 Playground 中部分下拉菜单显示异常的问题 * 修复模型配置中自定义请求头别名不生效的问题 ## v1.6 - CDP 连接、双指缩放与多模型增强 v1.6 版本新增了 CDP 浏览器连接模式、跨平台双指缩放手势、GPT-5/GPT-5.4 模型支持,同时对元素定位、报告系统、Chrome 扩展等进行了多项改进。 ### 新增 CDP 浏览器连接模式 支持通过 CDP (Chrome DevTools Protocol) 直接连接已有的浏览器实例进行自动化,无需由 Midscene 启动浏览器,适用于需要复用已有浏览器会话的场景。详见:[Skills - Browser Automation](/zh/skills.md)、[YAML 脚本运行器 - CDP 连接模式](/zh/yaml-script-runner.md#使用-cdp-连接模式) ### 新增跨平台双指缩放手势 在 Android、iOS、鸿蒙等移动端平台支持 pinch/zoom 双指缩放操作,可用于地图缩放、图片预览等场景。详见:[API 文档 - aiPinch](/zh/reference.md#agentaipinch) ### 新增 GPT-5 / GPT-5.4 与 Codex app-server provider 支持 适配了 GPT-5 和 GPT-5.4 模型,同时新增 Codex app-server provider,开发者可以使用最新的 OpenAI 模型进行视觉理解与自动化操作。详见:[模型配置](/zh/model-config.md)、[模型策略](/zh/model-strategy.md) ### 新增模型请求 extraBody 参数 新增 `extraBody` 配置,开发者可以在模型 API 请求中传递额外的自定义参数,满足特定模型或部署环境的需求。详见高阶配置中的 [`MIDSCENE_MODEL_EXTRA_BODY_JSON` 环境变量](/zh/model-config.md#高阶配置可选) ### `deepThink` 更名为 `deepLocate` 元素定位相关 API 中的 `deepThink` 参数正式更名为 `deepLocate`,更准确地表达其"深度定位"的含义。原有 `deepThink` 参数仍可使用,但建议逐步迁移。详见:[API 文档](/zh/reference.md#common) ### Skill CLI 与平台工具增强 * **Skill CLI 自定义接口**:Skill CLI 支持自定义接口,开发者可以更灵活地扩展 Skill 能力。详见:[Skills 文档](/zh/skills.md) * **统一 MCP 工具导出**:所有平台包(Web、Android、iOS 等)统一导出 MidsceneTools,在 MCP 场景下集成更简单。详见:[MCP 服务](/zh/mcp.md) * **iOS 终止指定应用**:iOS 端支持通过 bundleId 终止指定应用,方便测试流程中重置应用状态。详见:[iOS API](/zh/reference.md#ios) * **CLI 版本查看**:CLI 新增版本查看功能,健康检查中显示各包版本信息,便于排查环境问题 ### 任务取消支持 `aiAct` 新增 `AbortSignal` 支持,开发者可以在任务执行过程中随时取消操作,避免长时间等待。详见:[API 文档 - aiAct](/zh/reference.md#agentaiact) ### 元素定位优化 优化了 `deepLocate` 的定位流程,在复杂界面下的定位效率和准确率均有提升。 ### 报告与回放改进 * **大型报告加载更快**:报告中的截图支持懒加载,包含大量步骤的报告打开速度显著提升 * **移动端报告更直观**:报告回放时展示设备外壳,更直观地还原移动端操作场景 * **时序信息更精确**:报告中 AI 调用和操作执行的时序信息精度更高,便于定位性能瓶颈 ### 稳定性改进 * AI 规划偶发解析失败时自动重试一次,减少因网络抖动导致的测试中断 * 设备健康检查新增监控器检测,帮助排查无头环境下的显示问题 ### Chrome 扩展改进 * 修复录制停止后生成脚本时的崩溃问题 * 修复长时间录制时因消息序列化性能问题导致的卡顿 * Bridge 模式新增启停控制按钮,修复确认操作时连接断开的问题 ### 问题修复 * 修复 MCP 服务在某些情况下变成僵尸进程占用 100% CPU 的问题 * 修复页面跳转过程中截图失败未重试的问题 * 修复 Android 端部分场景下文字输入丢失的问题 * 修复 `aiNumber` 在某些格式下提取结果不正确的问题 * 修复 `aiScroll` 不传参数时的调用异常 * 修复使用 AutoGLM 模型时返回/主页操作在不同平台上的兼容问题 * 修复报告中模型名称包含 `/` 时显示异常的问题 * 修复报告回放结束后播放器未正确重置的问题 * 修复 Playground 中 Deep Think 开关未正确读取环境变量配置的问题 * 修复高分辨率设备截图中光标大小显示不正确的问题 * 修复 Linux 环境下 Chrome 启动路径解析失败的问题 * 修复页面包含 iframe 时元素定位不准确的问题 * 修复鸿蒙端在特定渲染分辨率下屏幕信息解析错误的问题 * 修复 Playground 中取消任务后设备方向显示不正确的问题 ## v1.5 - HarmonyOS(鸿蒙)自动化支持 v1.5 版本新增了 HarmonyOS 自动化支持,新增 Qwen3.5 和 doubao-seed 2.0 模型支持,同时对桌面自动化、报告系统、Chrome 扩展等进行了多项改进。 ### 新增 HarmonyOS(鸿蒙)自动化支持 新增 `@midscene/harmony` 包,正式支持 HarmonyOS 平台自动化。Midscene 的自动化能力从 Web、Android、iOS、桌面进一步扩展到鸿蒙生态。 ### 新增 Qwen3.5 与 doubao-seed 2.0 模型支持 适配了通义千问 Qwen3.5 和豆包 doubao-seed 2.0 模型,开发者可以使用更新的模型获得更好的视觉理解效果。 ### 新增通用模型推理配置 新增 `MIDSCENE_MODEL_REASONING_EFFORT` 环境变量,作为通用的模型推理强度配置参数,方便开发者在不同模型间统一控制推理行为。 ### 桌面自动化改进 * **Xvfb 虚拟显示器支持**:在无头 Linux 环境下支持 Xvfb 虚拟显示器,适用于 CI/CD 服务器等无 GUI 环境的桌面自动化 * **连接健康检查**:桌面自动化连接时新增健康检查,提升连接可靠性 * **macOS 输入优化**:macOS 上所有文本输入改用剪贴板方式,避免输入法(IME)导致的输入异常 * **鼠标控制失败检测**:自动检测鼠标控制失败并提示管理员权限需求 * **停止执行优化**:在停止执行时通过检查 destroyed 状态及时中断截图操作,避免无效等待 ### 截图与显示优化 * **自定义截图缩放**:支持自定义截图缩放比例(screenshot shrink),在保证识别准确性的前提下优化性能 * **Android 缩放比解耦**:将 scalingRatio 从 size() 方法中解耦,提升灵活性 ### 报告系统改进 * **时序信息更详细**:报告中的时序信息粒度更细,帮助开发者更精确地分析性能瓶颈 * **合并报告支持目录模式**:`mergeReports` 支持目录模式的报告文件 ### Chrome 扩展改进 * **新增始终拒绝选项**:Chrome 扩展新增"始终拒绝"选项,并修复确认弹窗的竞态条件 * **CLI 结束后关闭 Bridge 服务**:CLI 命令完成后自动关闭 Bridge 服务器,避免残留进程 ### 问题修复 * 修复表单渲染中 input mode schema 的 `z.preprocess` 处理问题 * 修复 Android 滑动参数传递问题 * 修复 Web 端尺寸计算问题 * 修复 `BASE_URL_FIX_SCRIPT` 闭合标签未被 HTML 解析器识别的问题 * 修复 PlaywrightAgent/PuppeteerAgent 构造函数中 page 为 undefined 的保护处理 ## v1.4 - Skills:让 AI 助手直接操控你的设备 v1.4 版本推出了 Midscene Skills —— 一套可安装到 Claude Code、OpenClaw 等 AI 助手中的技能包,让 AI 助手直接操控浏览器、桌面、Android 和 iOS 设备。同时本版本还包含独立桌面 MCP 服务、各平台 CLI 独立入口、AI 规划增强等多项改进。 ### Midscene Skills —— AI 助手的设备操控技能包 Midscene Skills 是一套可安装到 Claude Code、OpenClaw 等 AI 助手中的技能包。安装后,AI 助手可以通过自然语言直接操控浏览器、桌面、Android 和 iOS 设备。 各平台包(`@midscene/android`、`@midscene/ios`、`@midscene/web` 等)现在各自暴露了独立的 CLI 入口,Skills 正是基于此能力构建。 **覆盖平台:** * 浏览器(Puppeteer 无头模式) * Chrome Bridge(用户自己的桌面 Chrome) * 桌面(macOS、Windows、Linux) * Android(通过 ADB) * iOS(通过 WebDriverAgent) 详见:[Midscene Skills](https://github.com/web-infra-dev/midscene-skills) ### 独立桌面自动化 MCP 包 新增 `@midscene/computer-mcp` 包,将 PC 桌面自动化能力以独立 MCP 服务的形式提供。开发者可以直接在 Cursor、Trae 等支持 MCP 的工具中使用桌面自动化能力,无需额外集成。 详见文档:[PC 桌面自动化](/zh/platforms/desktop.md) ### Chrome 扩展支持 MCP 后台连接 Chrome 扩展新增后台 Bridge 模式的 MCP 连接支持,可以将桌面浏览器作为 MCP 工具暴露给 AI 助手,进一步打通 MCP 生态。 ### AI 规划能力增强 * **`aiAct` 新增 `deepLocate` 选项**:在执行操作时启用深度定位,提升复杂界面下的元素定位准确率 * **Swipe 与 DragAndDrop 语义区分**:模型现在能更精确地区分滑动和拖放操作,减少手势规划错误 * **LLM 规划增加页面导航限制**:防止模型在规划时生成不合理的页面跳转操作,提升任务执行稳定性 * **macOS 键盘输入改用 AppleScript**:提升桌面自动化中键盘输入的稳定性和兼容性 * **鼠标移动操作**:新增 cursor move 动作支持 ### YAML 脚本与文件上传增强 * **YAML `aiTap` 支持 `fileChooserAccept`**:在 YAML 脚本中可直接处理文件上传对话框 * **支持目录上传**:Web 端支持 `webkitdirectory` 类型的文件夹选择上传 ### Chrome 扩展 Bridge 模式缓存 Bridge 模式下新增缓存支持,复用已有的 AI 规划结果,减少重复调用,提升调试效率。 ### Android 改进 * 优化文字输入逻辑,提升输入稳定性 ### iOS 改进 * **Playground 实时画面流**:iOS Playground 新增实时画面展示,调试时可实时预览设备屏幕。 ## v1.3 - PC 桌面自动化支持 v1.3 版本带来了全新的 PC 桌面自动化能力,大幅优化了 Android 截图性能,并对报告系统和稳定性进行了多项改进。 ### 全新 PC 桌面自动化支持 Midscene 现在支持 PC 桌面自动化,在 Windows、macOS 和 Linux 上驱动原生键盘和鼠标。无论是 Electron、Qt、WPF 还是原生桌面应用,都可以通过视觉模型方案进行自动化。 <video src="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/pc-twitter2.mp4" controls /> **核心能力:** * **鼠标操作**:单击、双击、右键、移动鼠标、拖放 * **键盘输入**:文本输入、组合键(Cmd/Ctrl/Alt/Shift) * **屏幕截图**:捕获任意显示器的截图 * **多显示器支持**:同时操作多个显示器 **使用方式:** * 支持使用 Computer Playground 零代码试用 * 支持 JavaScript SDK 脚本编写 * 支持 YAML 格式的自动化脚本和命令行工具 * 支持 HTML 报告回放所有操作路径 详见文档:[PC 桌面自动化](/zh/platforms/desktop.md) ### Android 截图性能大幅提升 开启 Scrcpy 截图模式后,截图耗时从原来的 500–2000ms 降低到 **100–200ms**,显著提升 Android 自动化的响应速度,特别适用于远程设备调试和高帧率场景。 详见文档:[Scrcpy 截图模式](/zh/reference.md#scrcpy) ### 深度思考模式增强 `aiAct` 的深度思考(deepThink)模式现在不仅用于元素定位,还能优化整体任务规划,在复杂表单、多步骤流程等场景下获得更好的执行效果。 ### 报告体验优化 * **时间线折叠**:新增折叠切换按钮,方便查看长任务流程 * **时间单位改为秒**:更易读 * **步骤同步高亮**:侧边栏步骤高亮与播放器回放实时同步 * **内存占用降低**:优化报告生成机制,有效降低运行时内存占用 ### 移动端改进 #### Android * 特殊字符和 Unicode 输入更稳定 * Launch 操作时应用包名匹配更宽松(忽略大小写和空格) * 部分设备截图异常时自动重试 #### iOS * Bundle ID 匹配更宽松(忽略大小写和空格) ### Web 自动化改进 * 修复 Puppeteer 在非活动标签页截图时可能挂起的问题 * 修复 headed 模式下窗口尺寸不准确的问题 * `shareBrowserContext` 模式下支持保留 localStorage 和 sessionStorage * Playwright 多项目配置下,报告中自动区分不同浏览器的测试用例 * 修复 YAML 脚本中 input 操作的 `typeOnly` 模式不生效的问题 ### 其他改进 * 图片处理性能提升 * SVG 图标缓存问题修复 * Playground 模型配置错误现在会显示具体原因 ## v1.2 - 智谱 AI 开源模型支持与文件上传支持 v1.2 版本中我们加入了对智谱 AI 开源模型的支持,新增了文件上传功能,修复了多个影响使用体验的问题,让自动化测试更加可靠。 ### 新增智谱 AI 开源模型支持 #### 智谱 GLM-V 视觉模型 * 智谱 GLM-V 系列模型是智谱 AI 推出的开源视觉模型,有多种参数的版本,支持云端部署和本地部署。 * 详见:[GLM-V 模型配置](/zh/model-common-config.md#glm-v) #### 智谱 AutoGLM 移动端自动化模型 * 智谱 AutoGLM 是智谱 AI 推出的开源移动端自动化模型,能够根据自然语言指令理解手机屏幕内容,并结合智能规划能力生成操作流程完成用户需求。 * 详见:[AutoGLM 模型配置](/zh/model-common-config.md#auto-glm) ### 文件上传功能上线 在 Web 自动化场景中,文件上传是一个常见需求。v1.2 版本为 web 端新增了文件上传能力,支持通过自然语言操作文件输入框,让表单自动化更加完整。 详见:[aiTap 文件上传](/zh/reference.md#agentaitap) ### 缓存机制优化 修复了缓存在 DOM 变更后未能及时更新的问题。当页面 DOM 发生变化导致缓存验证失败时,系统现在会自动更新缓存,避免因使用过期缓存而导致的操作失败,提升自动化脚本的稳定性。 ### 报告与 Playground 改进 #### 深度思考标记优化 * 修复了 `.aiAct()` 方法使用深度思考(deepThink)时,报告中未正确显示标记的问题。现在你可以在报告中清晰地看到哪些操作使用了深度思考能力 * 优化了报告中 summary 行的样式,提升整体可读性 #### Playground 稳定性提升 * 修复了 Playground 在使用 agentFactory 模式时,未在 `getActionSpace` 中正确创建 agent 实例的问题,确保各种使用模式下的正常运行 * 优化了 Playground 输出展示,防止超长的 reportHTML 内容影响界面显示 ### 模型配置更新 针对通义千问(Qwen)模型的深度思考功能,更新了相关配置参数,确保与模型最新版本的兼容性。 ## v1.1 - `aiAct`深度思考与可扩展的 MCP SDK v1.1 版本在模型规划能力与 MCP 扩展性上实现优化,让复杂场景的自动化更稳定,同时为企业级 MCP 服务部署提供更灵活的方案。 ### `aiAct` 可开启深度思考能力(deepThink) 在 `aiAct` 时开启深度思考能力后,模型会更加深入地理解用户意图、优化规划结果,适用于复杂表单、多步骤流程等场景。它会带来更高的准确率,但也会增加规划耗时。 目前已支持阿里云的 Qwen3-vl 与火山引擎的 Doubao-vision 模型,具体请参考 [模型策略](/zh/model-strategy.md)。 示例用法: ```typescript await agent.aiAct('如果界面上展示“添加收货地址”按钮,那么展开已有的“收货地址”列表,并选择最后一项', { deepThink: true }); ``` ### MCP 扩展与 SDK 开放 开发者可以使用 Midscene 暴露的 MCP SDK 灵活部署自己的公共 MCP 服务。此能力适用于任意平台的 Agent 实例。 典型应用场景: * 在企业内网中运行 MCP 控制私有设备池 * 将 Midscene 能力封装为内部微服务供多团队使用 * 扩展自定义自动化工具链 详见文档:[MCP 服务](/zh/mcp.md) ### Chrome 扩展优化 * 修复录制期间的潜在事件丢失问题,提升录制稳定性 * 优化 `describeElement` 的坐标传递,提高元素描述准确性 ### CLI 与配置增强 * **文件参数支持**: 修复 CLI 在同时指定 `--config` 时未正确处理 `--files` 参数的问题,现在可灵活组合使用 * **动态配置**: 修复 Playground 中环境变量 `MIDSCENE_REPLANNING_CYCLE_LIMIT` 未正确读取的问题 ### iOS Agent兼容性提升 * 优化 `getWindowSize` 方法,在新版本 API 不可用时自动回退到 legacy endpoint,提升对 WebDriverAgent 版本的兼容性 ### 报告与 Playground 改进 * 修复报告在访问屏幕属性前未正确初始化的问题 * 修复 Playground 中 stop 函数的异常行为 * 优化视频导出时的错误处理,避免 frame cancel 导致的崩溃 感谢贡献者:@FriedRiceNoodles ## v1.0 - Midscene v1.0 正式发布! Midscene v1.0 已发布!欢迎体验,看看它如何帮助你自动化你的工作流程。 ### 查看我们全新的[案例展示](/zh/showcases.md) 在 Web 浏览器中自主注册 Github 表单,通过所有字段校验: <video src="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/github2.mp4" height="300" controls /> 此外还有这些实战案例: * [iOS 自动化 - 美团下单咖啡](/zh/showcases.md#ios) * [iOS 自动化 - Twitter 自动点赞 @midscene\_ai 首条推文](/zh/showcases.md#ios) * [Android 自动化 - 懂车帝查看小米 SU7 参数](/zh/showcases.md#android) * [Android 自动化 - Booking 预订圣诞酒店](/zh/showcases.md#android) * [MCP 集成 - Midscene MCP 操作界面发布 prepatch 版本](/zh/showcases.md#mcp) 有社区开发者成功基于 Midscene 与[任意界面集成](/zh/integrate-with-any-interface.md)的特性,扩展了机械臂 + 视觉模型 + 语音模型等模块,运用于车机大屏测试场景中,请看下方视频。 <video src="https://lf3-static.bytednsdoc.com/obj/eden-cn/vhaeh7vhabf/AI_Vision_Powered_Robotic_Arm.mp4" height="300" controls /> ### 🚀 纯视觉路线 从 V1.0 开始,Midscene 全面转向视觉理解方案,提供更稳定可靠的 UI 自动化能力。 视觉模型有以下特点: * **效果稳定**:业界领先的视觉模型(如 Doubao Seed 1.6、Qwen3-VL 等)表现足够稳定,已经可以满足大多数业务需求 * **UI 操作规划**:视觉模型通常具备较强的 UI 操作规划能力,能够完成不少复杂的任务流程 * **适用于任意系统**:自动化框架不再依赖 UI 渲染的技术栈。无论是 Android、iOS、桌面应用,还是浏览器中的 `<canvas>`,只要能获取截图,Midscene 即可完成交互操作 * **易于编写**:抛弃各类 selector 和 DOM 之后,开发者与模型的“磨合”会变得更简单,不熟悉渲染技术的新人也能很快上手 * **token 量显著下降**:在去除 DOM 提取之后,视觉方案的 token 使用量可以减少 80%,成本更低,且本地运行速度也变得更快 * **有开源模型解决方案**:开源模型表现渐佳,开发者开始有机会进行私有化部署模型,如 Qwen3-VL 提供的 8B、30B 等版本在不少项目中都有着不错的效果 详情请阅读我们更新版的[模型策略](/zh/model-strategy.md) ### 🚀 多模型组合,为复杂任务带来更好效果 除了默认的交互场景,Midscene 还定义了 Planning(规划)和 Insight(洞察)两种意图,开发者可以按需为它们启用独立的模型。例如,用 GPT 模型做规划,同时使用默认的 Doubao 模型做元素定位。 多模型组合让开发者可以按需提升复杂需求的处理能力。 ### 🚀 运行时架构优化 针对 Midscene 的运行时表现,我们进行了以下优化: * 减少对设备信息接口的调用,在确保安全的情况下复用部分上下文信息,提升运行时性能,让大多数的时间消耗集中在模型端 * 优化 Web 及移动端环境下的 Action Space 组合,向模型开放更合理、更清晰的工具集 ### 🚀 回放报告优化 回放报告是 Midscene 开发者非常依赖的一个特性,它能有效提升脚本的调试效率。 在 v1.0 中,我们更新了回放报告: * 参数视图:标记出交互参数的位置信息,合并截图信息,快速识别模型的规划结果 * 样式调整:支持以深色模式展示报告,更美观 * Token 消耗的展示:支持按模型汇总 Token 消耗量,分析不同场景的成本情况 ### 🚀 MCP 架构重构 我们重新定义了 Midscene MCP 服务的定位。Midscene MCP 的职责是围绕着视觉驱动的 UI 操作展开,将 iOS / Android / Web 设备 Action Space 中的每个 Action 操作暴露为 MCP 工具,也就是提供各类“原子操作”。 通过这种形式,开发者可以更专注于构建自己的高阶 Agent,而无需关心底层 UI 操作的实现细节,并且时刻获得满意的成功率。 详情请阅读 [MCP 文档](/zh/mcp.md) ### 🚀 移动端能力增强 #### iOS 改进 * 新增 WebDriverAgent 5.x-7.x 全版本兼容 * 新增 WebDriver Clear API 支持,解决动态输入框问题 * 提升设备兼容性 #### Android 改进 * 新增截图轮询回退机制,提升远程设备稳定性 * 新增屏幕方向自动适配(displayId 截图) * 新增 YAML 脚本 `runAdbShell` 支持 #### 跨平台 * 在 Agent 实例上暴露系统操作接口,包括 Home、Back、RecentApp 等 ### 🚧 API 变更 方法重命名(向后兼容) * 改名 `aiAction()` → `aiAct()`(旧方法保留,有弃用警告) * 改名 `logScreenshot()` → `recordToReport()`(旧方法保留,有弃用警告) 环境变量重命名(向后兼容) * 改名 `OPENAI_API_KEY` → `MIDSCENE_MODEL_API_KEY`(新变量优先,旧变量作为备选) * 改名 `OPENAI_BASE_URL` → `MIDSCENE_MODEL_BASE_URL`(新变量优先,旧变量作为备选) ### ⬆️ 升级到最新版 升级项目中的依赖,例如: `npm install @midscene/web@latest --save-dev` `npm install @midscene/android@latest --save-dev` `npm install @midscene/ios@latest --save-dev` 如果使用全局安装的命令行版本: `npm i -g @midscene/cli` ## V0.30 - 缓存管理升级与移动端体验优化 ### 更灵活的缓存策略 v0.30 版本改进了缓存系统,让你可以根据实际需求控制缓存行为: * **多种缓存模式可选**: 支持只读(read-only)、只写(write-only)、读写(read-write)等策略。例如在 CI 环境中使用只读模式复用缓存,在本地开发时使用只写模式更新缓存 * **自动清理无用缓存**: Agent 销毁时可自动清理未使用的缓存记录,避免缓存文件越积越多 * **配置更简洁统一**: CLI 和 Agent 的缓存配置参数已统一,无需记忆不同的配置方式 ### 报告管理更便捷 * **支持合并多个报告**: 除了 playwright 场景,现在任意场景均支持将多次自动化执行的报告合并为单个文件,方便集中查看和分享测试结果 ### 移动端自动化优化 #### iOS 平台改进 * **真机支持改进**: 移除了 simctl 检查限制,iOS 真机设备的自动化更流畅 * **自动适配设备显示**: 实现设备像素比自动检测,确保在不同 iOS 设备上元素定位准确 #### Android 平台增强 * **灵活的截图优化**: 新增 `screenshotResizeRatio` 选项,你可以在保证视觉识别准确性的前提下自定义截图尺寸,减少网络传输和存储开销 * **屏幕信息缓存控制**: 通过 `alwaysRefreshScreenInfo` 选项控制是否每次都获取屏幕信息,在稳定环境下可复用缓存提升性能 * **直接执行 ADB 命令**: AndroidAgent 新增 `runAdbCommand` 方法,方便执行自定义的设备控制命令 #### 跨平台一致性 * **ClearInput 全平台支持**: 解决 AI 无法准确规划各平台清空输入的操作问题 ### 功能增强 * **失败分类**: CLI 执行结果现在可以区分「跳过的失败」和「真正的失败」,帮助定位问题原因 * **aiInput 追加输入**: 新增 `append` 选项,在保留现有内容的基础上追加输入,适用于编辑场景 * **Chrome 扩展改进**: * 弹窗模式偏好会保存到 localStorage,下次打开记住你的选择 * Bridge 模式支持自动连接,减少手动操作 * 支持 GPT-4o 和非视觉语言模型 ### 类型安全改进 * **Zod 模式验证**: 为 action 参数引入类型检查,在开发阶段发现参数错误,避免运行时问题 * **数字类型支持**: 修复了 `aiInput` 对 number 类型值的支持,类型处理更健壮 ### 问题修复 * 修复了 Playwright 循环依赖导致的潜在问题 * 修复了 `aiWaitFor` 作为首个语句时无法生成报告的问题 * 改进视频录制器延迟逻辑,确保最后的画面帧也能被捕获 * 优化报告展示逻辑,现在可以同时查看错误信息和元素定位信息 * 修复了 `aiAction` 子任务中 `cacheable` 选项未正确传递的问题 ### 社区 * Awesome Midscene 板块新增 [midscene-java](/zh/awesome-midscene.md) 社区项目 ## v0.29 - 新增 iOS 平台支持 ### 新增 iOS 平台支持 v0.29 版本最大的亮点是正式引入了对 iOS 平台的支持!现在,你可以通过 WebDriver 连接并自动化 iOS 设备,将 Midscene 的强大 AI 自动化能力扩展到苹果生态系统,了解详情: [支持 iOS 自动化](/zh/platforms/ios.md) ### 适配 Qwen3-VL 模型 我们适配了最新的通义千问 `Qwen3-VL` 模型,开发者可以体验到更快的、更准确的视觉理解能力。详见 [模型策略](/zh/model-strategy.md) ### AI 核心能力增强 * **优化 UI-TARS 模型下的表现**:优化 aiAct 规划,改进对话历史管理,提供了更好的上下文感知能力 * **优化 AI 断言与动作**:我们更新了 `aiAssert` 的提示词(Prompt)并优化了 `aiAct` 的内部实现,使 AI 驱动的断言和动作执行更加精准可靠 ### 报告与调试体验优化 * **URL 参数控制回放**:为了改善调试体验,现在可以通过 URL 参数直接控制报告回放的默认行为 ### 文档 * 更新了文档部署的缓存策略,确保用户能够及时访问到最新的文档内容 ## v0.28 - 扩展界面操作能力,构建你自己的 GUI 自动化 Agent(预览特性) ### 支持与任意界面集成(预览特性) v0.28 版本推出了与任意界面集成的功能。定义符合 `AbstractInterface` 定义的界面控制器类,即可获得一个功能齐全的 Midscene Agent。 该功能的典型用途是构建一个针对你自己界面的 GUI 自动化 Agent,比如 IoT 设备、内部应用、车载显示器等! 配合通用 Playground 架构和 SDK 增强功能,开发者能方便地调试自定义设备。 更多请参考 [与任意界面集成(预览特性)](/zh/integrate-with-any-interface.md) ### Android 平台优化 * **规划缓存支持**:为 Android 平台添加了规划缓存功能,提升执行效率 * **输入策略增强**:基于 IME 设置优化了输入清除策略,提升 Android 平台的输入体验 * **滚动计算改进**:优化了 Android 平台的滚动终点计算算法 ### 手势操作扩展 * **双击操作支持**:新增双击动作支持 * **长按与滑动手势**:新增长按和滑动手势支持 ### 核心功能增强 * **Agent 配置隔离**:实现了不同 agent 间的模型配置隔离,避免配置冲突 * **在运行时设置环境变量**:为 Agent 新增 useCache 和 replanningCycleLimit 配置选项,提供更精细的控制 * **YAML 脚本支持**:支持通过 YAML 脚本运行通用的自定义设备,提升自动化能力 ### 问题修复 * 修复了 Qwen 模型的搜索区域大小问题 * 优化了 deepThink 参数处理和矩形尺寸计算 * 解决了 Playwright 双击操作的相关问题 * 改进了 TEXT 动作类型的处理逻辑 ### 文档与社区 * 新增自定义接口文档,帮助开发者更好地扩展功能 * 在 README 中添加了 [Awesome Midscene](/zh/awesome-midscene.md) 板块,展示社区项目 ## v0.27 - 核心模块重构,断言与报告功能全面升级 ### 核心模块重构 在 v0.26 引入 [Rslib](https://github.com/web-infra-dev/rslib) 提升开发体验、降低贡献门槛的基础上,v0.27 更进一步,对核心模块进行了大规模重构。这使得扩展新设备、添加新 AI 操作的成本变得极低,我们诚挚地欢迎社区开发者踊跃贡献! **由于本次重构涉及面较广,升级后如遇到任何问题,请随时向我们反馈,我们将第一时间跟进处理。** ### 接口优化 * **`aiAssert` 功能全面增强** * 新增 `name` 字段,允许为不同的断言任务命名,方便在 JSON 格式的输出结果中进行识别和解析 * 新增 `domIncluded` 和 `screenshotIncluded` 选项,可在断言中灵活控制是否向 AI 发送 DOM 快照和页面截图 ### Chrome 扩展 Playground 升级 * 所有 Agent API 都能在 Playground 上直接调试和运行!交互、提取、验证三大类方法全覆盖,可视化操作和验证,让你的自动化开发效率飙升 ### 报告功能优化 * **新增标记浮层开关**:报告播放器增加了隐藏标记浮层的开关,方便用户在回放时查看无遮挡的原始页面视图 ### 问题修复 * 修复了 `aiWaitFor` 在偶现错误导致报告未生成问题 * 降低 Playwright 插件的内存消耗 ## v0.26 - 工具链全面接入 [Rslib](https://github.com/web-infra-dev/rslib),大幅提高开发体验、降低贡献门槛 ### Web 集成优化 * 支持冻结页面上下文([freezePageContext](/zh/reference.md#agentfreezepagecontext)/[unfreezePageContext](/zh/reference.md#agentunfreezepagecontext)),使后续所有的操作都复用同一个页面快照,避免多次重复获取页面状态 * 为 Playwright fixture 补全所有 agent api,简化测试脚本编写,解决使用 agentForPage 无法生成报告的问题 ### Android 自动化增强 * 新增隐藏键盘策略([keyboardDismissStrategy](/zh/reference.md#androiddevice)),允许指定自动隐藏键盘的方式 ### 报告功能优化 * 报告内容引入懒解析,解决大体积报告的崩溃问题 * 报告播放器新增自动缩放开关,方便查看全局视角的回放 * 支持 aiAssert / aiQuery 等任务在报告中播放,以完整展示整个页面变动过程 * 修复断言失败时的侧栏状态未显示为失败图标的问题 * 修复报告中下拉筛选器不能切换筛选的问题 ### 构建与工程化 * 构建工具迁移至 [Rslib](https://github.com/web-infra-dev/rslib) 库开发工具,提升构建效率和开发体验 * 全仓库开启源码跳转,方便开发者查看源码 * MCP npm 包产物体积优化,从 56M 减少到 30M,大幅提高加载速度 ### 问题修复 * CLI 在 keepWindow 为 true 时将自动开启 headed 模式 * 修复 getGlobalConfig 的实现问题,解决环境变量初始化异常问题 * 确保 base64 编码中的 mime-type 正确 * 修复 aiAssert 任务返回值类型 ## v0.25 - 支持使用图像作为 AI prompt 输入 ### 核心功能增强 * 新增运行环境,支持运行在 Worker 环境 * 支持使用图像作为 AI prompt 输入,详见 [使用图片作为提示词](/zh/reference.md#%E4%BD%BF%E7%94%A8%E5%9B%BE%E7%89%87%E4%BD%9C%E4%B8%BA%E6%8F%90%E7%A4%BA%E8%AF%8D) * 图像处理升级,采用 Photon & Sharp 进行高效图片裁剪 ### Web 集成优化 * 通过坐标获取 XPath,提高缓存可复现性 * 缓存文件将 plan 模块提到最前面,增加可读性 * Chrome Recorder 支持导出所有事件到 markdown 文档 * agent 支持指定 HTML 报告名称,详见 [reportFileName](/zh/reference.md#common) ### Android 自动化增强 * 长按手势支持 * 下拉刷新支持 ### 问题修复 * 使用全局配置处理环境变量,避免因多打包导致环境无法覆盖的问题 * 当错误对象序列化失败时,手动构造错误信息 * 修复 playwright 报告类型依赖声明顺序问题 * 修复 MCP 打包问题 ### 文档 AI 友好 * [LLMs.txt](/zh/llm-txt.md) 区分中文与英文,方便 AI 理解 * 每篇文档顶部新增按钮,支持复制为 markdown,方便喂给 AI 使用 ### 其它功能增强 * Chrome Recorder 支持 aiScroll 功能 * 重构 aiAssert 使其与 aiBoolean 实现一致 ## v0.24 - Android 自动化支持 MCP 调用 ### Android 自动化支持 MCP 调用 * Android 自动化已全面支持 MCP 调用,为 Android 开发者提供更完善的自动化工具集。详情请参考:[MCP 服务](/zh/mcp.md) ### 优化输入清空机制 * 针对 Mac 平台的 Puppeteer 增加了双重输入清空机制,保证输入之前清空输入框 ### 开发体验 * 简化本地构建 `htmlElement.js` 的方式,避免循环依赖导致的报告模板构建问题 * 优化了开发工作流,只需要执行 `npm run dev` 即可进入 Midscene 工程开发 ## v0.23 - 全新报告样式与 YAML 脚本能力增强 ### 报告系统升级 #### 全新报告样式 * 重新设计的测试报告界面,提供更清晰、更美观的测试结果展示 * 优化报告布局和视觉效果,提升用户阅读体验 * 增强报告的可读性和信息层次结构 ![](https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/new%20report.png) ### YAML 脚本能力增强 #### 支持多 YAML 文件批量执行 * 新增配置模式,支持配置 YAML 文件运行顺序、浏览器复用策略、并行度 * 支持获取 JSON 格式的运行结果 ![](https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/Tuji_20250722_161353.338.png) ### 测试覆盖提升 #### Android 测试增强 * 新增 Android 平台相关测试用例,提升代码质量和稳定性 * 完善测试覆盖率,确保 Android 功能的可靠性 ## v0.22 - Chrome 扩展录制功能上线 ### Web集成增强 #### 全新的录制功能 * Chrome 扩展新增录制功能,可以记录用户在页面上的操作并生成自动化脚本 * 支持录制点击、输入、滚动等常见操作,大大降低自动化脚本编写门槛 * 录制的操作可以直接在 Playground 中回放和调试 #### 存储升级到 IndexedDB * Chrome 扩展的 Playground 和 Bridge 改为使用 IndexedDB 进行数据存储 * 相比之前的存储方案,提供更大的存储容量和更好的性能 * 支持存储更复杂的数据结构,为未来功能扩展奠定基础 #### 自定义重新规划循环限制 * 设置 `MIDSCENE_REPLANNING_CYCLE_LIMIT` 环境变量,可以自定义在执行操作(aiAct)时允许的最大重新规划循环次数 * 默认值为 10,当 AI 需要重新规划超过这个限制时,会抛出错误建议将任务拆分 * 提供更灵活的任务执行控制,适应不同复杂度的自动化场景 ```bash export MIDSCENE_REPLANNING_CYCLE_LIMIT=10 # 默认值为 10 ``` ### Android 功能增强 #### 截图路径区分 * 为每个截图生成唯一的文件路径,避免文件覆盖问题 * 提升了并发测试场景下的稳定性 ## v0.21 - Chrome 扩展界面升级 ### Web集成增强 #### 全新的 Chrome 扩展界面 * 全新的聊天式用户界面设计,提供更好的使用体验 * 界面布局优化,操作更加直观便捷 <video src="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/recording_2025-07-07_08-16-16.mp4" controls /> #### 超时配置灵活性提升 * 支持从测试 fixture 中覆盖超时设置,提供更灵活的超时控制 * 适用场景:不同测试用例需要不同超时时间的场景 #### 统一 Puppeteer 和 Playwright 配置 * 为 Playwright 新增 `waitForNavigationTimeout` 和 `waitForNetworkIdleTimeout` 参数 * 统一了 Puppeteer 和 Playwright 的 timeout 选项配置,提供一致的 API 体验,降低学习成本 #### 新增数据导出回调机制 * 新增 `agent.onDumpUpdate` 回调函数,可在数据导出时获得实时通知 * 重构了任务结束后的处理流程,确保异步操作的正确执行 * 适用场景:需要监控或处理导出数据的场景 ### Android 交互优化 #### 输入体验改进 * 将点击输入改为滑动操作,提升交互响应性和稳定性 * 减少因点击不准确导致的操作失败 ## v0.20 - 支持传入 XPath 定位元素 ### Web集成增强 #### 新增 aiAsk 方法 * 可直接向 AI 模型提问,获取当前页面的字符串形式答案 * 适用场景:页面内容问答、信息提取等需要 AI 推理的任务 * 示例: ```typescript await agent.aiAsk('问题描述') ``` #### 支持传入 XPath 定位元素 * 定位优先级:指定的 XPath > 缓存 > AI 大模型定位 * 适用场景:已知元素 XPath,需要跳过 AI 大模型定位 * 示例: ```typescript await agent.aiTap('提交按钮', { xpath: '//button[@id="submit"]' }) ``` ### Android 改进 #### Playground 任务可取消 * 支持中断正在执行的自动化任务,提升调试效率 #### aiLocate API 增强 * 返回设备像素比(Device Pixel Ratio),通常用于计算元素真实坐标 ### 报告生成优化 改进报告生成机制,从批量存储改为单次追加,有效降低内存占用,避免用例数量大时造成的内存溢出 ## v0.19 - 支持获取完整的执行过程数据 ### 新增 API 获取 Midscene 执行过程数据 为 agent 添加 `_unstableLogContent` API,即可获取 Midscene 执行过程数据,比如每个步骤的耗时、AI Tokens 消耗情况、页面截图等! 对了,Midscene 的报告就是根据这份数据生成了,也就是说,使用这份数据,你甚至可以定制一个属于你自己的报告! 详情请参考:[API 文档](/zh/reference.md#agent_unstablelogcontent) ### CLI 新增参数支持调整 Midscene 环境变量优先级 默认情况下,`dotenv` 不会覆盖 `.env` 文件中同名的全局环境变量。如果希望覆盖,你可以使用 `--dotenv-override` 选项。 详情请参考:[使用 YAML 格式的自动化脚本](/zh/automate-with-scripts-in-yaml.md#%E4%BD%BF%E7%94%A8-env-%E4%B8%AD%E7%9A%84%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F%E8%A6%86%E7%9B%96%E5%90%8C%E5%90%8D%E7%9A%84%E5%85%A8%E5%B1%80%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F) ### 大幅减少报告文件大小 裁剪生成的报告中冗余的数据,大幅减少复杂页面的报告文件大小,用户的典型复杂页面报告大小从 47.6M 减小到 15.6M! ## v0.18 - 回放报告功能增强 🚀 Midscene 又有更新啦!为你带来高质量的 UI 自动化体验。 ### 在报告中增加自定义节点 * 为 agent 添加 `recordToReport` API,将当前页面的截图作为报告节点。支持设置节点标题和描述,使报告内容更加丰富。适用于关键步骤截图记录、错误状态捕获、UI 验证等。 * 示例: ```typescript test('login github', async ({ ai, aiAssert, aiInput, recordToReport }) => { if (CACHE_TIME_OUT) { test.setTimeout(200 * 1000); } await ai('Click the "Sign in" button'); await aiInput('quanru', 'username'); await aiInput('123456', 'password'); // 自定义记录 await recordToReport('Login page', { content: 'Username is quanru, password is 123456', }); await ai('Click the "Sign in" button'); await aiAssert('Login success'); }); ``` ### 支持将报告下载为视频 * 支持从报告播放器直接导出视频,点击播放器界面的下载按钮即可保存。 ![](/blog/export-video.png) * 适用场景:分享测试结果、存档重现步骤、演示问题复现 ### Android 暴露更多配置 * 支持使用远程 adb 主机,配置键盘策略 * `autoDismissKeyboard?: boolean` - 可选参数,是否在输入文本后自动关闭键盘 * `androidAdbPath?: string` - 可选参数,用于指定 adb 可执行文件的路径 * `remoteAdbHost?: string` - 可选参数,用于指定远程 adb 主机 * `remoteAdbPort?: number` - 可选参数,用于指定远程 adb 端口 * 示例: ```typescript await agent.aiInput('搜索框', '测试内容', { autoDismissKeyboard: true }) ``` ```typescript const agent = await agentFromAdbDevice('s4ey59', { autoDismissKeyboard: false, // 可选参数,是否在输入文本后自动关闭键盘。默认值为 true。 androidAdbPath: '/usr/bin/adb', // 可选参数,用于指定 adb 可执行文件的路径 remoteAdbHost: '192.168.10.1', // 可选参数,用于指定远程 adb 主机 remoteAdbPort: '5037' // 可选参数,用于指定远程 adb 端口 }) ``` 立即升级版本,体验这些强大新功能! * [自定义报告节点 API 文档](/zh/reference/index.md#agentlogscreenshot) * [Android 更多配置项 API 文档](/zh/reference/index.md#androiddevice) ## v0.17 - 让 AI 看见页面 DOM ### 数据查询 API 全面增强 为满足更多自动化和数据提取场景,以下 API 新增了 options 参数,支持更灵活的 DOM 信息和截图传递: * `agent.aiQuery(dataDemand, options)` * `agent.aiBoolean(prompt, options)` * `agent.aiNumber(prompt, options)` * `agent.aiString(prompt, options)` #### 新增 `options` 参数 * `domIncluded`:是否向模型发送精简后的 DOM 信息,默认值为 false。一般用于提取 UI 中不可见的属性,比如图片的链接。 * `screenshotIncluded`:是否向模型发送截图。默认值为 true。 #### 代码示例 ```typescript // 提取通讯录中所有联系人的完整信息(包含隐藏的头像链接) const contactsData = await agent.aiQuery( "{name: string, id: number, company: string, department: string, avatarUrl: string}[], extract all contact information including hidden avatarUrl attributes", { domIncluded: true } ); // 检查通讯录中第一个联系人的 id 属性是否为 1 const isId1 = await agent.aiBoolean( "Is the first contact's id is 1?", { domIncluded: true } ); // 获取第一个联系人的 ID(隐藏属性) const firstContactId = await agent.aiNumber("First contact's id?", { domIncluded: true }); // 获取第一个联系人的头像 URL(页面上不可见的属性) const avatarUrl = await agent.aiString( "What is the Avatar URL of the first contact?", { domIncluded: true } ); ``` ### 新增右键点击能力 你有没有遇到过需要自动化右键操作的场景?现在,Midscene 支持了全新的 `agent.aiRightClick()` 方法! #### 功能 使用右键点击页面元素,适用于那些自定义了右键事件的场景。注意:Midscene 无法与浏览器原生菜单交互。 #### 参数说明 * `locate`: 用自然语言描述你要操作的元素 * `options`: 可选,支持 `deepThink`(AI精细定位)、`cacheable`(结果缓存) #### 示例 ```typescript // 在通讯录应用中右键点击联系人,触发自定义上下文菜单 await agent.aiRightClick("Alice Johnson", { deepThink: true }); // 然后可以点击菜单中的选项 await agent.aiTap("Copy Info"); // 复制联系人信息到剪贴板 ``` ### 示例及其报告 #### 示例页面 <iframe src="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/contacts3.html" width="100%" height="800" /> #### 示例报告 <iframe src="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/puppeteer-2025-06-04_20-41-45-be8ibktz.html" width="100%" height="800" /> ### 一个完整示例 在下面的报告文件中,我们展示了一个完整的示例,展示了如何使用新的 `aiRightClick` API 和新的查询参数来提取包含隐藏属性的联系人数据。 报告文件:[puppeteer-2025-06-04\_20-41-45-be8ibktz.html](https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/puppeteer-2025-06-04_20-41-45-be8ibktz.html) 对应代码可以参考我们的示例仓库:[puppeteer-demo/extract-data.ts](https://github.com/web-infra-dev/midscene-example/blob/main/puppeteer-demo/extract-data.ts) ### 重构缓存能力 使用 xpath 缓存,而不是基于坐标,提高缓存命中概率。 缓存文件格式使用 yaml 替换 json,提高可读性。 ## v0.16 - 支持 MCP ### Midscene MCP 🤖 使用 Cursor / Trae 帮助编写测试用例。 🕹️ 快速实现浏览器操作,媲美 Manus 平台。 🔧 快速集成 Midscene 能力,融入你的平台和工具。 了解详情: [MCP](/zh/mcp.md) ### 支持结构化 API APIs: `aiBoolean`, `aiNumber`, `aiString`, `aiLocate` 了解详情: [使用结构化 API 优化自动化代码](/zh/basics.md#javascript-orchestration) ## v0.15 - Android 自动化上线! ### Android 自动化上线! 🤖 AI 调试:自然语言调试 📱 支持原生、Lynx 和 WebView 应用 🔁 可回放运行 🛠️ YAML 或 JS SDK ⚡ 自动规划 & 即时操作 API ### 更多功能 * 支持自定义 midscene\_run 目录 * 增强报告文件名生成,支持唯一标识符和分段模式 * 增强超时配置和日志记录,支持网络空闲和导航超时 * 适配 gemini-2.5-pro 了解详情: [支持 Android 自动化](/zh/platforms/android.md) ## v0.14 - 即时操作 API ### 即时操作 API * 新增即时操作 API,增强 AI 操作的准确性 了解详情: [即时操作 API](/zh/blog-introducing-instant-actions-and-deep-think.md) ## v0.13 - 深度思考模式 ### 原子 AI 交互方法 * 支持 aiTap, aiInput, aiHover, aiScroll, aiKeyboardPress 等原子操作 ### 深度思考模式 * 增强点击准确性,提供更深层次的上下文理解 ![](/blog/0.13.0.jpeg) ## v0.12 - 集成 Qwen 2.5 VL ### 集成 Qwen 2.5 VL 的本地能力 * 保持输出准确性 * 支持更多元素交互 * 成本降低 80% 以上 ## v0.11.0 - UI-TARS 模型缓存 ### UI-TARS 模型支持缓存 * 通过文档开启缓存 👉: [开启缓存](/zh/caching.md) * 开启效果 <video src="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/antd-form-cache.mp4" controls /> ![](/blog/0.11.0.png) ### 优化 DOM 树提取策略 * 优化了 dom 树的信息能力,加速了 GPT 4o 等模型的推理过程 ![](/blog/0.11.0-2.png) ## v0.10.0 - UI-TARS 模型上线 UI-TARS 是由 **Seed** 团队开源的 Native GUI agent 模型。UI-TARS 起名源之[星际穿越](https://zh.wikipedia.org/zh-cn/%E6%98%9F%E9%99%85%E7%A9%BF%E8%B6%8A)电影中的 [TARS 机器人](https://interstellarfilm.fandom.com/wiki/TARS),它具备高度的智能和自主思考能力。UI-TARS **将图片和人类指令作为输入信息**,可以正确的感知下一步的行动,从而逐渐接近人类指令的目标,在 GUI 自动化任务的各项基准测试中均领先于各类开源模型、闭源商业模型。 ![](/blog/0.10.0.png) UI-TARS:Pioneering Automated GUI Interaction with Native Agents - Figure 1 ![](/blog/0.10.0-2.png) UI-TARS:Pioneering Automated GUI Interaction with Native - Figure 4 ### 模型优势 UI-TARS 模型在 GUI 任务中有以下优势: * **目标驱动** * **推理速度快** * **Native GUI agent 模型** * **模型开源** * **公司内部私有化部署无数据安全问题** ## v0.9.0 - 桥接模式上线! 通过 Midscene 浏览器插件,你可以用脚本联动桌面浏览器进行自动化操作了! 我们把它命名为“桥接模式”(Bridge Mode)。 相比于之前各种 CI 环境调试,优势在于: 1. 可以复用桌面浏览器,尤其是 Cookie、登录态、前置界面状态等,即刻开启自动化,而不用操心环境搭建 2. 支持人工与脚本配合操作界面,提升自动化工具的灵活性 3. 简单的业务回归,Bridge Mode 本地跑一下就行 ![](/blog/0.9.0.png) 文档:[通过 Chrome 插件快速体验](/zh/bridge-mode.md) ## v0.8.0 - Chrome 插件 ### 新增 Chrome 插件,任意页面随时运行 Midscene 通过 Chrome 插件,你可以零代码、任意页面随时运行 Midscene,体验它的 Action \ Query \ Assert 等能力。 体验方式:[ 使用 Chrome 插件体验 Midscene](/zh/quick-start.md#chrome-extension) <video src="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/Midscene_extension.mov" controls /> ## v0.7.0 - Playground 能力 ### 新增 Playground 能力,随时发起调试 再也不用频繁重跑脚本调试 Prompt 了! 在全新的测试报告页上,你可以随时对 AI 执行结果进行调试,包括页面操作、页面信息提取、页面断言。 <video src="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/midscene-playground.mov" controls /> ## v0.6.0 - 支持字节豆包模型 ### 模型:\*\*支持字节豆包 全新支持调用豆包模型调用,参考下方环境变量即可体验。 ```bash MIDSCENE_OPENAI_INIT_CONFIG_JSON='{"baseURL":"https://xxx.net/api/v3","apiKey":"xxx"}' MIDSCENE_MODEL_NAME='ep-20240925111815-mpfz8' MIDSCENE_MODEL_TEXT_ONLY='true' ``` 总结目前豆包模型的可用性: * 目前豆包只有纯文本模型,也就是“看”不到图片。在纯粹通过界面文本进行推理的场景中表现尚可。 * 如果用例需要结合分析界面 UI,它完全不可用 举例: ✅ 多肉葡萄的价格 (可以通过界面文字的顺序猜出来) ✅ 切换语言文本按钮(可以是:中文,英文文本) (可以通过界面文字内容猜出来) ❌ 左下角播放按钮 (需要图像理解,失败) ### 模型:支持 GPT-4o 结构化输出、成本继续下降 通过使用 gpt-4o-2024-08-06 模型,Midscene 已支持结构化输出(structured-output)特性,确保了稳定性增强、成本下降了 40%+。 Midscene 现已支持命中 GPT-4o prompt caching 特性,待公司 GPT 平台跟进部署后,AI 调用成本将继续下降。 ### 测试报告:支持动画回放 现在你可以在测试报告中查看每个步骤的动画回放,快速调试自己的运行脚本 <video src="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/midscene-play-all.mp4" controls /> ### 提速:合并执行流程,响应提速 30% 新版本中,我们将 Plan 和 Locate 操作在 prompt 执行上进行一定程度合并,使得 AI 响应速度提升 30% > Before ![](/blog/0.6.0.png) > after ![](/blog/0.6.0-2.png) ### 测评报告:不同模型在 Midscene 场景下的表现 * GPT 4o 系列模型,接近 100% 正确率 * doubao-pro-4k 纯文本模型,接近可用状态 ![](/blog/0.6.0-3.png) ![](/blog/0.6.0-4.png) ### 问题修复 优化了页面信息提取,避免遮挡元素被收集,以此优化成功率、速度、AI 调用成本 🚀 > before ![](/blog/0.6.0-5.png) > after ![](/blog/0.6.0-6.png) ## v0.5.0 - 支持 GPT-4o 结构化输出 ### 新功能 * 支持了 gpt-4o-2024-08-06 模型提供 100% JSON 格式限制,降低了 Midscene 任务规划时的幻觉行为 ![](/blog/0.5.0.png) * 支持了 Playwright AI 行为实时可视化,提升排查问题的效率 ![](/blog/0.5.0-2.png) * 缓存通用化,缓存能力不再仅仅局限于 playwright,pagepass、puppeteer 都可以使用缓存 ```diff - playwright test --config=playwright.config.ts # 开启缓存 + MIDSCENE_CACHE=true playwright test --config=playwright.config.ts ``` * 支持了 azure openAI 的调用方式 * 支持了 AI 对于 Input 现有基础之上的增删改行为 ### 问题修复 * 优化了对于非文本、input、图片元素的识别,提升 AI 任务正确性 * 在 AI 交互过程中裁剪了不必要的属性字段,降低了 token 消耗 * 优化了 KeyboardPress、Input 事件在任务规划时容易出现幻觉的情况 * 针对 pagepass 通过 Midscene 执行过程中出现的闪烁行为,提供了优化方案 ```javascript // 目前 pagepass 依赖的 puppeteer 版本太低,截图可能会导致界面闪动、光标丢失,通过下面方式可以解决 const originScreenshot = puppeteerPage.screenshot; puppeteerPage.screenshot = async (options) => { return await originScreenshot.call(puppeteerPage, { ...options, captureBeyondViewport: false }); }; ``` ## v0.4.0 - 支持使用 Cli ### 新功能 * Midscene 支持 Cli 的使用方式,降低 Midscene 使用门槛 ```bash # headed 模式(即可见浏览器)访问 baidu.com 并搜索“天气” npx @midscene/cli --headed --url https://www.baidu.com --action "输入 '天气', 敲回车" --sleep 3000 # 访问 Github 状态页面并将状态保存到 ./status.json npx @midscene/cli --url https://www.githubstatus.com/ \ --query-output status.json \ --query '{serviceName: string, status: string}[], github 页面的服务状态,返回服务名称' ``` * 支持 AI 执行等待能力,让 AI 等到某个时候继续后续任务执行 * Playwright AI 任务报告展示整体耗时,并按测试组进行聚合 AI 任务 ### 问题修复 * 修复 AI 在连续性任务时容易出现幻觉导致任务规划失败 ## v0.3.0 - 支持 AI HTML 报告 ### 新功能 * AI 报告 html 化,将测试报告按测试组聚合,方便测试报告分发 ### 问题修复 * 修复 AI 报告滚动预览问题 ## v0.2.0 - 通过自然语言控制 puppeteer ### 新功能 * 支持通过自然语言控制 puppeteer 实现页面操作自动化🗣️💻 * 在 playwright 框架中提供 AI 缓存能力,提高稳定性和执行效率 * AI 报告可视化按照测试组进行合并,优化聚合展示 * 支持 AI 断言能力,让 AI 判断页面是否满足某种条件 ## v0.1.0 - 通过自然语言控制 playwright ### 新功能 * 通过自然语言控制 playwright 实现页面操作自动化 🗣️💻 * 通过自然语言提取页面信息 🔍🗂️ * AI 报告,AI 行为、思考可视化 🛠️👀 * 直接使用 GPT-4o 模型,无需任何训练 🤖🔧 --- url: /zh/common/get-cdp-url.md --- #### 获取 CDP WebSocket URL 你可以从多种来源获取 CDP WebSocket URL: * **BrowserBase**:在 https://browserbase.com 注册并获取你的 CDP URL * **Browserless**:使用 https://browserless.io 或运行你自己的实例 * **本地 Chrome**:使用 `--remote-debugging-port=9222` 参数运行 Chrome,然后使用 `ws://localhost:9222/devtools/browser/...` * **Docker**:在 Docker 容器中运行 Chrome 并暴露调试端口 --- url: /zh/common/prepare-ios.md --- #### 安装 Node.js 安装 [Node.js 18 或以上版本](https://nodejs.org/en/download/)。 #### 配置 WebDriverAgent 在开始之前,你需要先设置 iOS 开发环境: * macOS(iOS 开发必需) * Xcode 和 Xcode 命令行工具 * iOS 模拟器或真机设备 **配置 WebDriverAgent** 在使用 Midscene iOS 之前,需要先准备 WebDriverAgent 服务。 :::note 版本要求 WebDriverAgent 版本需要 **>= 7.0.0** ::: 请参考官方文档进行设置: * **模拟器配置**:[Run Prebuilt WDA](https://appium.github.io/appium-xcuitest-driver/latest/guides/run-prebuilt-wda/) * **真机配置**:[Real Device Configuration](https://appium.github.io/appium-xcuitest-driver/latest/getting-started/device-setup/) **验证 WebDriverAgent** 配置完成后,可以通过访问 WebDriverAgent 的状态接口来验证 服务是否启动: **访问地址**:`http://localhost:8100/status` **正确响应示例**: ```json { "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 已经正确配置并运行。 --- url: /zh/common/setup-env.md --- 下面以豆包 Seed 2.1 Turbo 为例: ```bash export MIDSCENE_MODEL_BASE_URL="https://ark.cn-beijing.volces.com/api/v3" export MIDSCENE_MODEL_API_KEY="your-api-key" export MIDSCENE_MODEL_NAME="doubao-seed-2-1-turbo-260628" export MIDSCENE_MODEL_FAMILY="doubao-seed" ``` 将 `your-api-key` 替换为你的 API Key。 > 如需使用千问(Qwen)、GLM、Gemini 或 GPT-5 等其他模型,请参考[支持的模型与配置](/zh/model-common-config.md)。 全部配置项请参考[模型配置](/zh/model-config.md)。 --- url: /zh/consume-report-file.md --- # 解析报告文件 Midscene 的 HTML 报告文件记录了单个 Agent 运行过程中的完整信息,用以回放和调试。 从 v1.7.0 开始,你可以把报告文件中的原始截图和 JSON 数据提取出来,或者把报告转录为 Markdown,方便其他工具继续消费这些内容。 ## 示例 你可以把报告文件解析为这样一份 Markdown 文件: ```markdown # Act - 搜索并播放 Midscene 相关的视频 - Execution start: 2026-04-08T02:13:04.795Z - Task count: 21 ## 1. Plan - 点击顶部的搜索框以激活输入 - Status: finished - Start: 2026-04-08T02:13:04.845Z - End: 2026-04-08T02:13:15.296Z - Cost(ms): 10451 - Screen size: 2880 x 1536 ![task-1](./screenshots/execution-1-task-1-f9fc3bf9-bdf6-48dd-abea-f8f29874d8c1.jpeg) ### Recorder - #1 type=screenshot, ts=2026-04-08T02:13:15.296Z, timing=after-calling ![task-1](./screenshots/execution-1-task-1-c521b130-5037-4ed2-b70f-705e181d981a.jpeg) ## 2. Locate - 顶部带有“李维刚的日常”占位文字的搜索输入框 - Status: finished - Start: 2026-04-08T02:13:15.305Z - End: 2026-04-08T02:13:15.306Z - Cost(ms): 1 - Screen size: 2880 x 1536 - Locate center: (1489, 71) ..... ``` 进一步,你可以结合 [Remotion Skill](https://www.remotion.dev/docs/ai/skills?utm_source=midscenejs) 解析这份 Markdown 文件,并生成一个个性化的回放视频。 视频生成结果如下: <video src="https://lf3-static.bytednsdoc.com/obj/eden-cn/vhaeh7vhabf/midscene-replay.mp4" height="300" controls></video> ## 使用命令行工具解析 报告解析工具包含在各个平台的命令行工具中,例如 `@midscene/web`、`@midscene/android` 等,对应的子命令为 `report-tool`。 将报告文件提取为 JSON 格式,并导出对应截图到 `output-data` 目录: ```shell npx @midscene/web report-tool --action split --htmlPath ./midscene_run/report/puppeteer-2026/index.html --outputDir ./output-data ``` 将报告文件转换为 Markdown 格式,并输出到 `output-markdown` 目录: ```shell npx @midscene/web report-tool --action to-markdown --htmlPath ./midscene_run/report/puppeteer-2026/index.html --outputDir ./output-markdown ``` 将多个报告文件合并为一份汇总报告: ```shell npx @midscene/web report-tool --action merge-html \ --htmlReport ./midscene_run/report/case-a/index.html \ --htmlReport ./midscene_run/report/case-b.html \ --outputDir ./merged --outputName all-cases ``` 每多合并一份报告就重复一次 `--htmlReport`。`--outputDir` 和 `--outputName` 都是可选项,留空时合并后的报告会写入 Midscene 默认的报告目录、并生成自动文件名。已存在同名文件时使用 `--overwrite` 进行覆盖。 ## 使用 JavaScript SDK 解析 如果你希望在代码里控制报告解析,可以使用 `@midscene/core` 提供的 `splitReportFile`、`reportFileToMarkdown` 和 `mergeReportFiles`。 ```ts import { mergeReportFiles, reportFileToMarkdown, splitReportFile, } from '@midscene/core'; const splitResult = splitReportFile({ htmlPath: './midscene_run/report/puppeteer-2026/index.html', outputDir: './output-data', }); console.log(splitResult.executionJsonFiles); const markdownResult = await reportFileToMarkdown({ htmlPath: './midscene_run/report/puppeteer-2026/index.html', outputDir: './output-markdown', }); console.log(markdownResult.markdownFiles); const mergedResult = mergeReportFiles({ htmlPaths: [ './midscene_run/report/case-a/index.html', './midscene_run/report/case-b.html', ], outputDir: './merged', outputName: 'all-cases', }); console.log(mergedResult.mergedReportPath); ``` `splitReportFile`、`reportFileToMarkdown` 和 `mergeReportFiles` 的用途不同: * `splitReportFile` 会产出“原始对象”对应的 JSON 文件(每个 execution 一个 `*.execution.json`),内容是 `ReportActionDump` 的原始结构化数据,同时会导出截图文件。返回值中的 `executionJsonFiles` 和 `screenshotFiles` 都是生成后的文件路径列表。 * `reportFileToMarkdown` 会把同一份报告转成更易读、便于给其他工具继续处理的 Markdown 文本,并导出 Markdown 里引用到的截图。返回值里的 `markdownFiles` 对应 Markdown 文件路径。 * `mergeReportFiles` 会把多份报告合并成一份汇总 HTML 报告,是 [`ReportMergingTool`](/zh/reference.md#new-reportmergingtool) 的轻量封装:会自动从每份源报告里读取 `groupName` 作为 `testTitle`/`testDescription`,省去了手工准备 `reportAttributes` 的步骤。命令行多次调用或多个测试用例产生多份报告后,使用它进行汇总最为合适。 ## 关于 JSON 和 Markdown 的内容字段 解析得到的 JSON 和 Markdown 数据结构可能会随着 Midscene 版本演进而变化,请以实际转换结果为准。 --- url: /zh/data-privacy.md --- # 数据隐私 Midscene.js 是一个开源项目(GitHub: [Midscene](https://github.com/web-infra-dev/midscene/)),遵循 MIT 许可证。你可以在公开仓库中查看到所有代码。 当使用 Midscene.js 时,你的页面数据(包括截图)将直接发送到你配置的 AI 模型提供商。没有第三方平台会访问这些数据。你需要关注的是模型提供商的数据隐私政策。 如果你希望在你自己的环境中构建 Midscene.js 和它的 Chrome 扩展(而不是使用我们已发布的版本),你可以参考 [贡献指南](https://github.com/web-infra-dev/midscene/blob/main/CONTRIBUTING.md) 以找到构建说明。 --- url: /zh/extend-test-runner.md --- # 扩展和维护 Test Runner `@midscene/test` 支持注册业务 Node(节点)、管理运行资源和定义执行生命周期。框架维护者可以使用这些能力,接入浏览器、Agent、外部工具和业务接口,打造面向团队的定制化测试底座。 如果你想先了解整体设计,请阅读 [Test Runner 概览](/zh/test-runner-overview.md)。 ## 快速开始 下面通过一个简单的示例项目,展示如何快速搭建一个测试项目,并在其中切换浏览器 UA 语言。 ### 1. 安装依赖 创建一个空项目,并安装 Test Runner 的核心依赖及驱动工具: ```bash pnpm add -D @midscene/test @midscene/web playwright ``` > **注意**:在使用 Midscene Agent 之前,请先参考 [模型配置](/zh/model-config.md) 妥善设置相应的模型环境变量(如 API Key)。 ### 2. 创建项目文件 建议采用以下基本目录结构: ```text team-test-runner/ ├── cases/ │ └── midscene.yaml └── midscene.config.ts ``` ### 3. 配置 Test Project 与注册 Node 在项目根目录下创建 `midscene.config.ts`,这个配置文件负责注册可复用的 Node,并定义执行环境(Project): ```ts filename=midscene.config.ts import { defineNode, z } from '@midscene/test'; import { defineProjectSetup, defineTestProject, } from '@midscene/test/config'; import { createMidsceneNodes } from '@midscene/test/midscene'; import { createPlaywrightNodes } from '@midscene/test/playwright'; import { PlaywrightAgent } from '@midscene/web/playwright'; import { chromium, type Browser, type Page } from 'playwright'; interface ProjectContext { browser: Browser; page: Page; agent?: PlaywrightAgent; } const geoInputSchema = z.strictObject({ latitude: z.number().describe('模拟位置的纬度。'), longitude: z.number().describe('模拟位置的经度。'), }); // 1. 注册自定义 Node:模拟地理位置 (GPS 定位) const mockLocation = defineNode< typeof geoInputSchema, unknown, ProjectContext >({ name: 'browser.mockLocation', title: '模拟地理位置', description: '伪造浏览器的 GPS 地理位置定位。', inputSchema: geoInputSchema, async execute({ input, context }) { const browserContext = context.page.context(); // 注入地理位置模拟权限并设置经纬度 await browserContext.grantPermissions(['geolocation']); await browserContext.setGeolocation({ latitude: input.latitude, longitude: input.longitude, }); }, }); // 集成 Midscene 内置的 AI 能力节点(如 aiAct, aiAssert 等) const midsceneNodes = createMidsceneNodes<ProjectContext>({ getAgent: ({ context }) => { context.agent ??= new PlaywrightAgent(context.page); return context.agent; }, includeLaunch: false, }); const playwrightNodes = createPlaywrightNodes<ProjectContext>({ getPage: ({ context }) => context.page, }); // 2. 声明浏览器环境的启动与清理 const playwrightSetup = defineProjectSetup<ProjectContext>({ name: 'playwright', platform: 'web', async setup({ onTeardown }) { const browser = await chromium.launch({ headless: true }); onTeardown(() => browser.close()); const browserContext = await browser.newContext(); const page = await browserContext.newPage(); return { browser, page }; }, }); // 3. 导出项目配置 export default defineTestProject<ProjectContext>({ projects: [ { name: 'chromium', platform: 'web', setup: playwrightSetup, files: { include: ['cases/**/*.{yaml,yml}'] }, }, ], nodes: [mockLocation, ...midsceneNodes, ...playwrightNodes], }); ``` ### 4. 编写测试用例并运行 创建 `cases/midscene.yaml` 文件: ```yaml filename=cases/midscene.yaml cases: - name: 模拟地理位置并展示特定地区门店 tags: [smoke] steps: - browser.mockLocation: latitude: 35.6762 longitude: 139.6503 # 模拟在东京 (Tokyo) - gotoUrl: url: https://yoursite.com/stores - aiAssert: prompt: 页面上成功加载并展示了“东京”或其临近区域的门店推荐列表 message: 地理位置 Mock 未能正确生效 ``` 在项目根目录下执行测试: ```bash pnpm exec midscene-test ``` 运行器会自动加载 `midscene.config.ts`,查找满足条件的测试用例并执行。 ## 注册自定义业务 Node 通过 `defineNode()`,你可以将复杂的接口调用、数据库操作、清理任务或特定的浏览器交互封装为具名 Node。封装后,用例作者可以直接在测试用例中调用它们。 ### 基本业务 Node 示例 以下示例展示了如何封装一个通过 HTTP 接口创建测试订单的 Node: ```ts filename=midscene.config.ts import { defineNode, z } from '@midscene/test'; const orderInputSchema = z.strictObject({ sku: z.string().min(1).describe('要下单的商品 SKU。'), quantity: z.number().int().positive().describe('购买数量。'), }); interface ProjectContext { apiBaseUrl: string; } const createOrder = defineNode< typeof orderInputSchema, { id: string }, ProjectContext >({ name: 'order.create', title: '创建订单', description: '通过测试接口创建一个商品订单。', inputSchema: orderInputSchema, async execute({ input, context, signal }) { const response = await fetch(`${context.apiBaseUrl}/test/orders`, { method: 'POST', headers: { 'content-type': 'application/json', }, body: JSON.stringify({ sku: input.sku, quantity: input.quantity, }), signal, }); if (!response.ok) { throw new Error(`创建订单失败:HTTP ${response.status}`); } const order = (await response.json()) as { id: string }; return { summary: `已创建订单 ${order.id}`, data: order, }; }, }); ``` 把 Node 加入到配置文件中的 `nodes` 数组后,用例作者就可以在用例中直接消费它: ```yaml cases: - name: 下单流程测试 steps: - order.create: sku: midscene-mug quantity: 1 ``` ### 使用 Zod 声明输入与强校验 `inputSchema` 是可选字段,我们强烈建议你声明此字段。 1. **类型推导与校验**:声明后,运行器会在进入 `execute()` 前自动执行 Zod 校验。若输入不匹配,将直接抛出 `NodeInputValidationError` 异常,并在编译期提供强类型推导(无需额外声明 TypeScript 接口)。 2. **拒绝未知参数**:推荐使用 `z.strictObject()`。若用例传入了多余的未知字段,运行器会及时拦截并报错。 3. **说明书自动集成**:字段上的 `.describe()` 信息会直接编译进自动生成的 Node 说明书,作为 AI Agent 或人类用例编写者的参考手册。 ```ts const refundInputSchema = z.strictObject({ orderId: z.string().min(1).describe('需要退款的订单 ID。'), reason: z.string().optional().describe('退款原因。'), }); const refundOrder = defineNode({ name: 'order.refund', description: '为已有订单发起退款。', inputSchema: refundInputSchema, async execute({ input }) { // input 会被推导为 { orderId: string; reason?: string } await refund(input.orderId, input.reason); }, }); ``` ### Node 执行上下文环境 `execute(ctx)` 接收的 `ctx` 包含以下常用字段: * `input`:从 YAML 传入并经过 Zod 校验后的业务参数。 * `$`:由运行器控制的通用 Step 属性(如规范化后的 `timeout` 和 `continue-on-error`)。 * `signal`:超时或运行取消时触发的 `AbortSignal`,建议在内部异步请求或长耗时任务中使用它以实现提前优雅退出。 * `context`:在 `defineProjectSetup()` 中返回并共享的项目级运行时资源。 * `onTeardown()`:注册当前 Node 所创建资源的清理函数,支持 attempt 级别或 Document 级,按 LIFO(后进先出)顺序执行。 * `scope`:标记当前 Node 执行的上下文边界,值为 `case` 或 `document`。 * `case` 或 `document`:当前执行位置的详细运行信息。 Node 不会自动收到前序 Node 的结果。如果后续 Node 需要某个值,请按下文所述将它显式写入项目 `context`。 ## 多节点协作与状态共享 在实际业务测试中,多个 Node 常常需要共享上下文状态。例如,订单退款用例需要在 `beforeEach` 中创建订单并记录订单 ID,在 `steps` 中访问该 ID 进行退款,最后在 `afterEach` 中进行数据清理。 通过在自定义 `ProjectContext` 中定义状态属性,可以轻松实现这种协作: ```ts filename=midscene.config.ts import { defineNode, z } from '@midscene/test'; import type { PlaywrightAgent } from '@midscene/web/playwright'; import type { Page } from 'playwright'; interface ProjectContext { agent: PlaywrightAgent; appBaseUrl: string; page: Page; orderId?: string; // 用于在 Node 之间共享测试状态 orderService: { create(input: { status: 'paid' }): Promise<{ id: string }>; remove(orderId: string): Promise<void>; }; } const emptyInputSchema = z.strictObject({}); const prepareOrderInputSchema = z.strictObject({ status: z.literal('paid').describe('待创建订单的状态。'), }); const getOrderId = (context: ProjectContext) => { if (!context.orderId) { throw new Error('测试订单尚未创建。'); } return context.orderId; }; // 1. 准备订单环境 const prepareOrder = defineNode< typeof prepareOrderInputSchema, { orderId: string }, ProjectContext >({ name: 'order.prepare', description: '调用订单服务创建测试订单。', inputSchema: prepareOrderInputSchema, async execute({ input, context }) { const order = await context.orderService.create(input); context.orderId = order.id; // 将 ID 保存至上下文 return { summary: `已创建测试订单 ${order.id}`, data: { orderId: order.id }, }; }, }); // 2. 访问已保存的订单状态 const openRefundPage = defineNode< typeof emptyInputSchema, unknown, ProjectContext >({ name: 'browser.openRefundPage', description: '打开当前测试订单的退款页面。', inputSchema: emptyInputSchema, async execute({ context }) { const orderId = getOrderId(context); await context.page.goto(`${context.appBaseUrl}/orders/${orderId}/refund`); }, }); // 3. 业务环境清理 const cleanupOrder = defineNode< typeof emptyInputSchema, unknown, ProjectContext >({ name: 'order.cleanup', description: '删除当前测试订单。', inputSchema: emptyInputSchema, async execute({ context }) { const orderId = getOrderId(context); await context.orderService.remove(orderId); delete context.orderId; // 恢复上下文干净状态 }, }); export const refundNodes = [prepareOrder, openRefundPage, cleanupOrder]; ``` ## 集成 Midscene Agent `@midscene/test/midscene` 导出了六个系统内置 Node:`aiAct`、`aiAssert`、`recordToReport`、`launch`、`wait` 和 `agent`。 你可以通过调用 `createMidsceneNodes()` 并传入 `getAgent` 回调,将其方便地集成到你的测试运行器配置中: ```ts import { createMidsceneNodes } from '@midscene/test/midscene'; const midsceneNodes = createMidsceneNodes<ProjectContext>({ getAgent: ({ context }) => { context.agent ??= new PlaywrightAgent(context.page); return context.agent; }, // Web 使用 gotoUrl,不注册兼容旧 Agent 的 launch Node。 includeLaunch: false, }); ``` `createMidsceneNodes()` 保留 `launch`,用于兼容已有的 Agent 集成。Android 和 iOS 项目应使用各自平台预置提供的生命周期 Node,并在这里设置 `includeLaunch: false`, 避免重复注册 `launch`。 ## 注册平台预置 Node Test Runner 通过独立入口发布各平台的预置 Node factory。Factory 使用 getter 获取 运行时资源,不要求 Project Context 使用固定字段名。 Playwright factory 注册 `gotoUrl`、`setCookies`、`clearCookies` 和 `setViewportSize`。`playwright` 是 `@midscene/test` 的 optional peer dependency,使用该预置能力的项目需要安装它: ```bash pnpm add -D playwright ``` 然后创建预置 Node: ```ts import { createPlaywrightNodes } from '@midscene/test/playwright'; const playwrightNodes = createPlaywrightNodes<ProjectContext>({ getPage: ({ context }) => context.page, getBaseUrl: ({ context }) => context.baseUrl, getEnv: () => process.env, }); ``` `setCookies` 不允许在 YAML 中直接填写 Cookie value。Test Runner 会把 Node 输入保存到 运行结果。如果直接填写 Cookie,这些敏感信息也会被保存。 请使用 `cookiesEnv`、`profile` 或 `storageStatePath` 引用 Cookie,三者必须选择一个。 Node 只在执行时读取实际的 Cookie,并将它直接传给 Playwright BrowserContext。Node 结果只记录引用名称和 Cookie 数量,不会记录 Cookie 的名称、value 和作用域。因此, Cookie 不会进入 Test Runner 的运行结果。 环境变量可以包含 Cookie header、Cookie JSON 数组或 Playwright storage-state JSON。 相对的 storage-state 路径默认从当前工作目录解析。如果项目需要使用其他根目录,请配置 `resolveStorageStatePath`。引用方式只能避免 Cookie 进入 Test Runner 的持久化数据; 环境变量、profile 和 storage-state 文件本身仍需妥善保管。请勿将包含真实 Cookie 的 storage-state 文件提交到代码仓库。 ```yaml beforeEach: - clearCookies: {} - setCookies: cookiesEnv: E2E_COOKIES url: https://example.com - setViewportSize: width: 1440 height: 900 - gotoUrl: url: /chat waitUntil: domcontentloaded ``` `gotoUrl` 遵循 Playwright 的导航语义。只要导航完成,HTTP 4xx 和 5xx response 也会作为成功的 Node 结果返回,并保留状态码,后续步骤可以继续检查错误页面。网络错误和 导航超时仍会让 Node 失败。 Android 平台预置会注册 `launch`、`terminate` 和 `runAdbShell`,并要求 Agent 同时提供这三个能力: ```ts import { createAndroidNodes } from '@midscene/test/android'; const androidNodes = createAndroidNodes<ProjectContext>({ getAgent: ({ context }) => context.agent, }); ``` ```yaml beforeEach: - runAdbShell: command: pm clear com.example.app - launch: uri: com.example.app ``` iOS 平台预置会注册 `launch`、`terminate` 和 `runWdaRequest`,并要求 Agent 同时提供这三个能力: ```ts import { createIOSNodes } from '@midscene/test/ios'; const iosNodes = createIOSNodes<ProjectContext>({ getAgent: ({ context }) => context.agent, }); ``` ```yaml steps: - launch: uri: com.example.app - runWdaRequest: method: GET endpoint: /status - terminate: uri: com.example.app ``` `runAdbShell` 和 `runWdaRequest` 会在 Node 结果中保留完整 response,但 Test Runner 不会自动把该 response 传给后续 Node 或 Midscene Agent 调用。如果运行结果不需要完整 输出,请直接在命令中进行过滤;如果后续 Node 需要其中某个值,请只将该值显式写入项目 `context`。 `launch` 和 `gotoUrl` 不互为 alias。`launch` 通过设备 Agent 启动 App、URL 或 URI; `gotoUrl` 在当前 Playwright Page 中导航,并提供 Web 专属的 `baseUrl`、生命周期和 HTTP response 语义。 ## 一键生成 Node 说明书 为了让测试用例编写者(包括 AI Agent)清晰、直观地检索当前项目注册了哪些 Node 及其参数规范,运行器提供了 `describe-nodes` 描述工具。它会自动将 Node 上的 `title`、`description` 和 Zod `inputSchema` 自动编译生成一份标准的 Markdown 说明书文档。 执行以下命令直接生成团队专属说明书: ```bash pnpm exec midscene-test describe-nodes > midscene-nodes.md ``` 或者指定测试目录或专属配置文件: ```bash pnpm exec midscene-test describe-nodes ./e2e --config ./config/midscene.config.ts ``` 生成的说明书包含当前 Test Project 实际注册的全部 Node(包含 `createMidsceneNodes()` 返回的六个系统内置 Node),按名称排序。运行器会自动将 Zod `inputSchema` 转换为标准的 JSON Schema。 ## 配置和管理 Test Project `defineTestProject()` 是测试底座的核心配置入口,支持声明一个或多个 Execution Project: ```ts filename=midscene.config.ts export default defineTestProject<ProjectContext>({ projects: [ { name: 'android-smoke', platform: 'android', setup: doraAndroidSetup, files: { include: ['cases/**/*.{yaml,yml}'], exclude: ['cases/**/*.draft.yaml'], }, tags: { include: ['smoke'], exclude: ['manual'] }, retry: 1, variables: { appUri: 'com.example.app' }, }, ], test: { maxConcurrency: 1, // 控制 active 的 Execution Project 的最大并发数 bail: 0, // 失败阈值拦截。配置为 > 0 时,达到失败用例数会自动停止新任务调度 testTimeout: 120_000, }, output: { reportDir: './midscene_run/report', }, nodes: [createOrder, ...midsceneNodes], }); ``` ### 关键配置策略 1. **环境与资源隔离**:每个 Execution Project 有其独立的 `setup` 运行环境(如启动单独的浏览器或绑定特定测试设备)。 2. **多 Project 并发与生命周期槽**:`test.maxConcurrency` 参数控制同时活跃(Active)的 Project 数量(默认值为 `1`)。 * 一个并发槽(slot)覆盖从 Project setup 开始到 teardown 完成的完整生命周期。 * 单个 Project 内部,所有的工作流文档(Workflow Documents)、用例(Cases)和步骤(Steps)依然**严格串行**执行,以保证测试的确定性。 * 当你需要同时驱动多台手机设备或多个浏览器实例时,应当创建多个不同的 `projects` 声明并增大并发数。 3. **生命周期清理**:使用 `defineProjectSetup()` 声明环境准备动作。利用 `onTeardown()` 注册销毁钩子,确保即便测试意外中断或运行中途出错,已获取的长生命周期资源也能按照后进先出的逆序被安全释放。 ## 编程式 API 大多数团队只需使用项目配置、YAML 和 CLI。如果需要将运行器嵌入其他工具(如开发一个本地的 GUI 运行面板),可以使用以下导出的编程式 API: * **`loadTestProject()`**:异步加载 `midscene.config.ts` 中的 TypeScript 项目配置(运行器不支持同步加载)。 * **`runTestProject()`**:异步发现、运行并汇总整个项目(从 `@midscene/test/config` 导出)。 * **`CaseRunner` / `createCaseRunner()`**:直接执行纯对象形式的单个用例(不含文件解析和生命周期控制)。 * **`runWorkflowDocument()`**:执行单个文档的完整生命周期及内部全部 Case。 配置完成后,请继续阅读 [编写和运行测试用例](/zh/use-test-runner.md),熟悉具体的用例语法与参数。 --- url: /zh/faq.md --- # 常见问题 FAQ ## 各平台常见问题 以下平台的常见问题已整合到各自的文档中: * [Web 浏览器 - Playwright](/zh/integrate-with-playwright.md#faq) * [Web 浏览器 - Puppeteer](/zh/integrate-with-puppeteer.md#faq) * [Web 浏览器 - Chrome 插件](/zh/quick-start.md#chrome-extension-faq) * [Web 浏览器 - 桥接模式](/zh/bridge-mode.md#faq) * [Android](/zh/platforms/android.md#常见问题) * [iOS](/zh/platforms/ios.md#常见问题) * [HarmonyOS](/zh/platforms/harmonyos.md#常见问题) * [PC 桌面](/zh/platforms/desktop.md#常见问题) ## 会有哪些信息发送到 AI 模型? Midscene 会发送页面截图到 AI 模型。在某些场景下,例如调用 `aiAsk` 或 `aiQuery` 时传入 `domIncluded: true`,页面的 DOM 信息也会被发送。 如果你担心数据隐私问题,请参阅 [数据隐私](/zh/data-privacy.md)。 ## 我的模型服务商需要在请求中添加指定的 header 你可以通过环境变量 `MIDSCENE_MODEL_INIT_CONFIG_JSON` 中的 `defaultHeaders` 来指定请求时附带的 header,例如: ```bash # 在请求头中添加 key 为 "foo",值为 "bar" 的 header MIDSCENE_MODEL_INIT_CONFIG_JSON='{"defaultHeaders":{"foo":"bar"}}' ``` 如果你的模型服务商文档里把这个字段写成 `extra_headers` 或 `extraHeaders`,Midscene 也会兼容这两种别名,并自动归一化到 `defaultHeaders`。多个别名同时存在时,优先级为:`defaultHeaders` > `extra_headers` > `extraHeaders`。 你可以通过 JSON 序列化来生成这个 JSON 的文本以避免手动拼接出错: ```javascript JSON.stringify({ defaultHeaders: { foo: 'bar' } }) ``` ## 如何使用 Azure OpenAI Service? 使用 Azure OpenAI Service 时,请先按照[支持的模型与配置](/zh/model-common-config.md)选择模型,并填写常规配置。Azure 只需要把模型服务地址和 API Key 换成 Azure 的写法: ```bash MIDSCENE_MODEL_BASE_URL="https://<your-resource>.services.ai.azure.com/openai/v1" # 或 https://<your-resource>.openai.azure.com/openai/v1 MIDSCENE_MODEL_API_KEY="<your-azure-api-key>" ``` `MIDSCENE_MODEL_NAME` 和 `MIDSCENE_MODEL_FAMILY` 等配置,仍应按照[支持的模型与配置](/zh/model-common-config.md)中的对应模型说明填写。Azure 只是鉴权方式不同的模型供应商,并非一种特殊模型。 这会走普通 OpenAI-compatible 路径,以 `Authorization: Bearer ...` 请求头发送 `POST /openai/v1/chat/completions`。`MIDSCENE_MODEL_BASE_URL` 不要追加 `/chat/completions`。大多数 `/openai/v1` 端点不需要 `api-version`。 如果你的资源仍然以 `400 Missing required query parameter: api-version` 报错,说明该资源的 `/openai/v1` surface 尚未 GA。可以通过 `defaultQuery` 注入这个查询参数: ```bash MIDSCENE_MODEL_INIT_CONFIG_JSON='{"defaultQuery":{"api-version":"preview"}}' ``` `api-version` 的值按你的资源要求填写(`preview`,或 Azure 门户里显示的带日期版本,如 `2025-01-01-preview`)。这样每个请求都会变成 `.../openai/v1/chat/completions?api-version=preview`。 如果某个 Azure-compatible 网关只接受 `api-key` 请求头,可以额外添加下面的配置,通过 header 发送真实 API Key: ```bash MIDSCENE_MODEL_API_KEY="placeholder" MIDSCENE_MODEL_INIT_CONFIG_JSON='{"defaultHeaders":{"api-key":"<your-azure-api-key>"}}' ``` 这里的 `MIDSCENE_MODEL_API_KEY="placeholder"` 只是为了满足 OpenAI SDK 的初始化要求,真实 API Key 会通过 `defaultHeaders.api-key` 发送。 当某个资源同时需要 `api-version` 和 `api-key` 请求头时,可以把两种兜底配置合并: ```bash MIDSCENE_MODEL_API_KEY="placeholder" MIDSCENE_MODEL_INIT_CONFIG_JSON='{"defaultQuery":{"api-version":"preview"},"defaultHeaders":{"api-key":"<your-azure-api-key>"}}' ``` Azure AD / keyless 鉴权(`DefaultAzureCredential`)的方式现在已经不再支持,请使用 API Key 的方式。 ## 使用 Azure OpenAI 时点击坐标偏移 在使用 GPT-5 系列模型时,你可能会发现:同一份脚本在 OpenAI 官方 API 上点击位置正确,但切到 Azure OpenAI 后点击位置出现固定比例的偏移。这个偏移和分辨率相关:截图较大时(如 `1920x1080`)出现,截图较小时(如 `1280x600`)则正常。 原因在于 Azure 端的图片处理。GPT-5 返回的是基于它实际看到的截图尺寸的绝对坐标,而 Midscene 发送图片时带上了 `"detail": "original"`,让模型看到原始分辨率的图片(参见 [GPT-5 说明](/zh/model-common-config.md#gpt))。Azure 没有正确处理 `"detail": "original"`,会在服务端对大图进行缩放(短边被压缩到 768)。于是模型在缩放后的坐标系里作答,而 Midscene 仍按原始分辨率还原坐标,最终产生按比例的偏移。可以通过 token 消耗来验证 `original` 是否生效:如果 `original` 生效,图片的 token 消耗会明显更高。 有两种规避办法: 1. 使用 OpenAI 官方的 GPT-5,或配置其他模型单独用于定位,而只把 Azure 平台的 GPT-5 作为规划模型。 2. 通过 Agent 参数 `screenshotShrinkFactor` 把截图预先缩放到较小尺寸,使图片不触发 Azure 的服务端缩放阈值。详见 [`screenshotShrinkFactor`](/zh/reference.md#common)。 ## 如何配置 midscene\_run 目录? Midscene 会将运行产物(报告、日志、缓存等)保存在 `midscene_run` 目录下。默认情况下,该目录会创建在当前工作目录下。 你可以通过环境变量 `MIDSCENE_RUN_DIR` 来自定义该目录的位置,支持相对路径或绝对路径: ```bash # 使用相对路径 export MIDSCENE_RUN_DIR="./my_custom_dir" # 使用绝对路径 export MIDSCENE_RUN_DIR="/tmp/midscene_output" ``` 该目录包含以下子目录: * `report/` - 测试报告文件(HTML 格式) * `log/` - 调试日志文件 * `cache/` - 缓存文件(详见 [缓存](/zh/caching.md)) 更多全局运行参数请参考[运行时配置](/zh/reference.md#runtime-configuration)。 ## 如何提升运行效率? 有几种方法可以提高运行效率: 1. 使用即时操作接口,如 `agent.aiTap('Login Button')` 代替 `agent.ai('Click Login Button')`。 2. 尽量使用较低的分辨率,降低输入 token 成本。 3. 更换更快的模型服务。 4. 使用缓存来加速调试过程。更多详情请参阅 [缓存](/zh/caching.md)。 ## 如何通过链接控制报告中播放器的默认回放样式? 在报告页面的链接后添加查询参数即可覆盖 **Focus on cursor** 和 **Show element markers** 开关的默认值,决定是否在报告中聚焦鼠标位置和元素标记。使用 `focusOnCursor` 和 `showElementMarkers`,参数值支持 `true`、`false`、`1` 或 `0`,例如:`...?focusOnCursor=false&showElementMarkers=true`。 ## 如何把报告以纯播放器的形式嵌入其它页面? 当你需要把报告嵌入到别的页面(例如放进 `iframe`)时,在报告链接后添加 `player-only=1` 查询参数,即可隐藏所有外围界面(顶部栏、侧边栏、时间线和详情面板),只保留回放播放器。另外两个参数用来调整播放器: * `play-control=1` —— 在 player-only 模式下显示底部播放控制条(默认隐藏)。仅接受 `=1` 开启。 * `auto-play` —— 是否在加载后自动播放。它独立于 `player-only`,对所有报告播放器都生效。**默认开启**;添加 `auto-play=0` 可关闭自动播放。 典型的嵌入形如 `...?player-only=1&play-control=1`。它同样可以和 `#task-<id>` 锚点组合使用,从而深链到某一具体步骤并只展示该步骤的播放器:`...?player-only=1#task-0-5`。若想让任意报告(无论是否嵌入)打开时不自动播放,使用 `...?auto-play=0`。 ## 元素定位出现偏移 如果在使用 Midscene 时遇到元素定位不准确的问题,可以按照以下步骤排查和解决: ### 1. 升级到最新版本 确保你使用的是最新版本的 Midscene,新版本通常包含定位准确性的优化和改进。 ```bash # Web 自动化 npm install @midscene/web@latest # iOS 自动化 npm install @midscene/ios@latest # CLI 工具 npm install @midscene/cli@latest # 或者其他和你平台对应的 package ``` ### 2. 使用更好的视觉模型 Midscene 的元素定位能力依赖于 AI 模型的视觉理解能力,所以请务必选择支持视觉能力的模型。 通常来说新版本、参数大的模型会比老版本、参数小的模型表现更好。比如 Qwen3-VL 会好于 Qwen2.5-VL,它的 plus 版本会好于 flash 版本。 当前的模型建议请参考[支持的模型与配置](/zh/model-common-config.md)。 ### 3. 检查 Model Family 配置 确认你的模型配置中 `MIDSCENE_MODEL_FAMILY` 参数设置是否正确,`MIDSCENE_MODEL_FAMILY` 配置错误会影响 Midscene 对模型的适配逻辑。详见 [模型配置](/zh/model-config.md)。 ### 4. 优化提示词,结合视觉特征和位置信息 如果定位结果随机落在不相关的元素上,而且每次执行结果差异较大,通常说明模型无法理解图标按钮背后的语义。 以 `aiTap('个人中心')` 为例,这是一个功能性描述,模型可能并不了解个人中心图标具体的样式;而 `aiTap('人形头像 icon')` 是一个视觉性的描述,模型可以根据其视觉特征完成元素定位。 解决方法:优化提示词,结合视觉特征和位置信息来描述元素。 ```typescript // ❌ 仅使用功能性描述 await agent.aiTap('个人中心'); // ✅ 使用视觉性描述 await agent.aiTap('人形头像 icon'); // ✅ 结合视觉特征和位置信息 await agent.aiTap('页面右上角的人形头像图标'); ``` ### 5. 开启 `deepLocate` 如果定位结果落在目标元素附近,但有若干像素的偏移,说明模型大概率已经识别对了目标,只是在定位时仍有偏差。 解决方法:开启 `deepLocate` 会对定位效果有明显提升。 ```typescript await agent.aiTap('登录按钮', { deepLocate: true }); ``` 更多关于 `deepLocate` 的说明,请参阅 [API 文档](/zh/reference/index.md#深度定位deeplocate)。 ### 6. 在 web 浏览器中将 dpr 提高到 2 如果你是在 web 浏览器里运行 Midscene,可以尝试将 dpr 提高到 `2`。一般 CI 环境中的默认 dpr 往往是 `1`,提高到 `2` 后页面会更清晰,对小元素的定位效果通常会更好。 需要注意的是,这会消耗更多 token。 ## 豆包手机是否使用了 Midscene 作为底层方案? 没有。 --- url: /zh/index.md --- --- url: /zh/integrate-with-any-interface.md --- # 与任意界面集成 你可以使用 Midscene 的 Agent 来控制任意界面,比如 IoT 设备、内部应用、车载显示器等,只需要实现一个符合 `AbstractInterface` 定义的 UI 操作类。 在实现了 UI 操作类之后,你可以获得 Midscene Agent 的全部特性: * TypeScript 的 GUI 自动化 Agent SDK,支持与任意界面集成 * 用于调试的 Playground * 通过 yaml 脚本控制界面 * 通过 CLI 命令接入 Skills ## 演示和社区项目 我们已经为你准备了一个演示项目,帮助你学习如何定义自己的界面类。强烈建议你查看一下。 * [演示项目](https://github.com/web-infra-dev/midscene-example/tree/main/custom-interface) - 一个简单的演示项目,展示如何定义自己的界面类 * [Android (adb) Agent](https://github.com/web-infra-dev/midscene/blob/main/packages/android/src/device.ts) - 这是 Midscene Android (adb) Agent,同样依赖此特性实现 * [iOS (WebDriverAgent) Agent](https://github.com/web-infra-dev/midscene/blob/main/packages/ios/src/device.ts) - 这是 Midscene iOS (WebDriverAgent) Agent,同样依赖此特性实现 还有一些使用此功能的社区项目: * [midscene-ios](https://github.com/lhuanyu/midscene-ios) - 使用 Midscene 驱动 "iPhone 镜像" 应用的项目 ## 配置 AI 模型服务 下面以豆包 Seed 2.1 Turbo 为例: ```bash export MIDSCENE_MODEL_BASE_URL="https://ark.cn-beijing.volces.com/api/v3" export MIDSCENE_MODEL_API_KEY="your-api-key" export MIDSCENE_MODEL_NAME="doubao-seed-2-1-turbo-260628" export MIDSCENE_MODEL_FAMILY="doubao-seed" ``` 将 `your-api-key` 替换为你的 API Key。 > 如需使用千问(Qwen)、GLM、Gemini 或 GPT-5 等其他模型,请参考[支持的模型与配置](/model-common-config.md)。 全部配置项请参考[模型配置](/model-config.md)。 ## 实现你自己的界面类 ### 关键概念 * `AbstractInterface` 类:一个预定义的抽象类,可以连接到 Midscene 智能体 * **动作空间**:描述可以在界面上执行的动作集合。这将影响 AI 模型如何规划和执行动作 ### 步骤 1. 从 demo 项目开始 我们提供了一个演示项目,运行了本文档中的所有功能。这是最快的启动方式。 ```bash # 准备项目 git clone https://github.com/web-infra-dev/midscene-example.git cd midscene-example/custom-interface npm install npm run build # 运行演示 npm run demo ``` ### 步骤 2. 实现你的界面类 定义一个继承 `AbstractInterface` 类的类,并实现所需的方法。 你可以从 [`./src/sample-device.ts`](https://github.com/web-infra-dev/midscene-example/blob/main/custom-interface/src/sample-device.ts) 文件中获取示例实现。让我们快速浏览一下。 ```typescript import type { DeviceAction, Size } from '@midscene/core'; import { getMidsceneLocationSchema, z } from '@midscene/core'; import { type AbstractInterface, defineAction, defineActionTap, defineActionInput, // ... 其他动作导入 } from '@midscene/core/device'; export interface SampleDeviceConfig { deviceName?: string; width?: number; height?: number; } /** * SampleDevice - AbstractInterface 的模板实现 */ export class SampleDevice implements AbstractInterface { interfaceType = 'sample-device'; private config: Required<SampleDeviceConfig>; constructor(config: SampleDeviceConfig = {}) { this.config = { deviceName: config.deviceName || 'Sample Device', width: config.width || 1920, height: config.height || 1080, }; } /** * 必需:截取屏幕截图并返回 base64 字符串 */ async screenshotBase64(): Promise<string> { // TODO:实现实际的屏幕截图捕获 console.log('📸 Taking screenshot...'); return 'data:image/png;base64,...'; // 你的屏幕截图实现 } /** * 必需:获取界面尺寸 * 这里的宽高是指界面的逻辑尺寸(logical size),不需要考虑设备像素比(dpr)。defineActionTap 等动作得到的坐标也是基于这个逻辑尺寸的坐标系。你可以在动作实现中根据需要将逻辑坐标转换为物理坐标。 */ async size(): Promise<Size> { return { width: this.config.width, height: this.config.height, }; } /** * 必需:定义 AI 模型的可用动作 */ actionSpace(): DeviceAction[] { return [ // 基础点击动作 defineActionTap(async (param) => { // TODO:实现在 param.locate.center 坐标的点击 await this.performTap(param.locate.center[0], param.locate.center[1]); }), // 文本输入动作 defineActionInput(async (param) => { // TODO:实现文本输入 await this.performInput(param.locate.center[0], param.locate.center[1], param.value); }), // 自定义动作示例 defineAction({ name: 'CustomAction', description: '你的自定义设备特定动作', paramSchema: z.object({ locate: getMidsceneLocationSchema(), // ... 自定义参数 }), call: async (param) => { // TODO:实现自定义动作 }, }), ]; } async destroy(): Promise<void> { // TODO:清理资源 } // 私有实现方法 private async performTap(x: number, y: number): Promise<void> { // TODO:你的实际点击实现 } private async performInput(x: number, y: number, text: string): Promise<void> { // TODO:你的实际输入实现 } } ``` 需要实现的关键方法有: * `screenshotBase64()`、`size()`:帮助 AI 模型获取界面上下文 * `actionSpace()`:一个由 `DeviceAction` 组成的数组,定义了在界面上可以执行的动作。AI 模型将使用这些动作来执行操作。Midscene 已为常见界面与设备提供了预定义动作空间,同时也支持定义任何自定义动作。 使用这些命令运行 Agent: * `npm run build` 重新编译 Agent 代码 * `npm run demo` 使用 JavaScript 运行智能体 * `npm run demo:yaml` 使用 yaml 脚本运行智能体 ### 步骤 3. 使用 Playground 测试 Agent 为 Agent 附加一个 Playground 服务,即可在浏览器中测试你的 Agent。 ```ts import 'dotenv/config'; // 从 .env 文件里读取 Midscene 环境变量 import { playgroundForAgent } from '@midscene/playground'; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); // 实例化 device 和 agent const device = new SampleDevice(); await device.launch(); const agent = new Agent(device); // 启动 playground const server = await playgroundForAgent(agent).launch(); // 关闭 Playground await sleep(10 * 60 * 1000); await server.close(); console.log('Playground 已关闭!'); ``` ### 步骤 4. 添加 Skill 支持(可选) [Agent Skills](https://github.com/anthropics/skills) 让 AI 编程助手(如 Claude Code、Cline 等)可以通过 CLI 命令驱动你的自定义界面。了解更多请参考 [Skills 文档](/zh/skills.md)。 在你的 npm 包中添加一个 CLI 入口文件(如 `./src/cli.ts`): ```ts #!/usr/bin/env node import { runSkillCLI } from '@midscene/core/skill'; import { SampleDevice } from './sample-device'; runSkillCLI({ DeviceClass: SampleDevice, scriptName: 'my-device', }); ``` 然后在 `package.json` 中配置 `bin` 字段: ```json { "bin": { "my-device": "./dist/cli.js" } } ``` 发布后,AI 编程助手即可通过 `npx my-device` 命令来控制你的自定义界面。 关于 Skill 的编写规范和更多示例,请参考 [midscene-skills](https://github.com/web-infra-dev/midscene-skills) 仓库。 ### 步骤 5. 发布 npm 包,让你的用户使用它 `./index.ts` 文件已经导出了你的 Agent 与界面类。现在可以发布到 npm。 在 `package.json` 文件中填写 `name` 和 `version`,然后运行以下命令: ```bash npm publish ``` 你的 npm 包的典型用法如下: ```typescript import 'dotenv/config'; // 从 .env 文件里读取 Midscene 环境变量 import { playgroundForAgent } from '@midscene/playground'; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); // 实例化 device 和 agent const device = new SampleDevice(); await device.launch(); const agent = new Agent(device); await agent.aiAct('click the button'); ``` ### 步骤 6. 在 Midscene CLI 和 YAML 脚本中调用你的类 编写一个包含 `interface` 字段的 yaml 脚本来调用你的类: ```yaml interface: module: 'my-pkg-name' # export: 'MyDeviceClass' # 如果是具名导出,使用该字段 config: output: './data.json' ``` 该配置等价于: ```typescript import MyDeviceClass from 'my-pkg-name'; const device = new MyDeviceClass(); const agent = new Agent(device, { output: './data.json', customActions: [], // optional additional custom DeviceAction list }); ``` YAML 的其他字段与[自动化脚本](/zh/automate-with-scripts-in-yaml.md)文档一致。 ## API 参考 ### `AbstractInterface` 类 ```typescript import { AbstractInterface } from '@midscene/core'; ``` `AbstractInterface` 是智能体控制界面的关键类。 以下是你需要实现的必需方法: * `interfaceType: string`:为界面定义一个名称,这不会提供给 AI 模型 * `screenshotBase64(): Promise<string>`:截取界面的屏幕截图并返回带有 `'data:image/` 前缀的 base64 字符串 * `size(): Promise<Size>`:界面的大小,它是一个具有 `width` 和 `height` 属性的对象 * `actionSpace(): DeviceAction[] | Promise<DeviceAction[]>`:界面的动作空间,它是一个 `DeviceAction` 对象数组。在这里你可以使用预定义动作,或是自定义交互操作。 类型签名: ```ts import type { DeviceAction, Size, UIContext } from '@midscene/core'; import type { ElementNode } from '@midscene/shared/extractor'; abstract class AbstractInterface { // 必选 abstract interfaceType: string; abstract screenshotBase64(): Promise<string>; abstract size(): Promise<Size>; abstract actionSpace(): DeviceAction[] | Promise<DeviceAction[]>; // 可选:生命周期/钩子 abstract destroy?(): Promise<void>; abstract describe?(): string; abstract beforeInvokeAction?(actionName: string, param: any): Promise<void>; abstract afterInvokeAction?(actionName: string, param: any): Promise<void>; } ``` 以下是你可以实现的可选方法: * `destroy?(): Promise<void>`:销毁 * `describe?(): string`:界面描述,这可能会用于报告和 Playground,但不会提供给 AI 模型 * `beforeInvokeAction?(actionName: string, param: any): Promise<void>`:在动作空间中调用动作之前的钩子函数 * `afterInvokeAction?(actionName: string, param: any): Promise<void>`:在调用动作之后的钩子函数 ### 动作空间(Action Space) 动作空间是界面上可执行动作的集合。AI 模型将使用这些动作来执行操作。所有动作的描述和参数模式都会提供给 AI 模型。 为了帮助你轻松定义动作空间,Midscene 为最常见的界面和设备提供了一组预定义的动作,同时也支持定义任意自定义动作。 以下是如何导入工具来定义动作空间: ```typescript import { type ActionTapParam, defineAction, defineActionTap, } from "@midscene/core/device"; ``` #### 预定义的动作 这些是最常见界面和设备的预定义动作空间。你可以通过实现动作的调用方法将它们暴露给定制化界面。 你可以在这些函数的类型定义中找到动作的参数。 * `defineActionTap()`:定义点击动作。这也是 `aiTap` 方法的调用函数。 * `defineActionDoubleClick()`:定义双击动作 * `defineActionInput()`:定义输入动作。这也是 `aiInput` 方法的调用函数。这也是 `aiInput` 方法的调用函数。 * `defineActionKeyboardPress()`:定义键盘按下动作。这也是 `aiKeyboardPress` 方法的调用函数。 * `defineActionScroll()`:定义滚动动作。这也是 `aiScroll` 方法的调用函数。 * `defineActionDragAndDrop()`:定义拖放动作 * `defineActionLongPress()`:定义长按动作 * `defineActionSwipe()`:定义滑动动作 #### 定义一个自定义动作 你可以使用 `defineAction()` 函数定义自己的动作。你也可以使用这种方式为 [PuppeteerAgent](/zh/integrate-with-puppeteer.md)、[AgentOverChromeBridge](/zh/bridge-mode.md#constructor) 和 [AndroidAgent](/zh/platforms/android.md) 定义更多动作。 API 签名: ```typescript import type { ExecutorContext } from "@midscene/core"; import { defineAction } from "@midscene/core/device"; defineAction( { name: string, description: string, paramSchema: z.ZodType<T>; call: ( param: z.infer<z.ZodType<T>>, context?: ExecutorContext, ) => Promise<void>; } ) ``` * `name`:动作的名称,AI 模型将使用此名称调用动作 * `description`:动作的描述,AI 模型将使用此描述来理解动作的作用。对于复杂动作,你可以在这里给出更详细的示例说明 * `paramSchema`:动作参数的 [Zod](https://www.npmjs.com/package/zod) 模式,AI 模型将根据此模式帮助填充参数 * `call`:调用动作的函数,你可以从符合 `paramSchema` 的 `param` 参数中获取参数 * `context`:可选的执行上下文。动作的返回值会写入 `task.output`(供你的 API 调用方和报告使用),**不会**发给规划器。若想给下一轮规划传一段简短的、面向规划器的反馈,请设置 `context.task.planningFeedback`;core 的规划层会对其做截断,以免撑爆模型上下文。 示例: ```typescript defineAction({ name: 'MyAction', description: 'My action', paramSchema: z.object({ name: z.string(), }), call: async (param) => { console.log(param.name); }, }); ``` 如果你想要获取某个元素位置相关的参数,可以使用 `getMidsceneLocationSchema()` 函数获取特定的 zod 模式。 一个更复杂的示例,关于如何定义自定义动作: ```typescript import { getMidsceneLocationSchema } from "@midscene/core/device"; defineAction({ name: 'LaunchApp', description: '启动屏幕上的应用', paramSchema: z.object({ name: z.string().describe('要启动的应用名称'), locate: getMidsceneLocationSchema().describe('要启动的应用图标'), }), call: async (param) => { console.log(`launching app: ${param.name}, ui located at: ${JSON.stringify(param.locate.center)}`); }, }); ``` ### `playgroundForAgent` 函数 ```typescript import { playgroundForAgent } from '@midscene/playground'; ``` `playgroundForAgent` 函数用于为特定的 Agent 创建一个 Playground 启动器,让你可以在浏览器中测试和调试你的自定义界面 Agent。 #### 函数签名 ```typescript function playgroundForAgent(agent: Agent): { launch(options?: LaunchPlaygroundOptions): Promise<LaunchPlaygroundResult> } ``` #### 参数 * `agent: Agent`:要为其启动 Playground 的 Agent 实例 #### 返回值 返回一个包含 `launch` 方法的对象。 #### `launch` 方法选项 ```typescript interface LaunchPlaygroundOptions { /** * Playground 服务器端口 * @default 5800 */ port?: number; /** * 是否自动在浏览器中打开 Playground * @default true */ openBrowser?: boolean; /** * 自定义浏览器打开命令 * @default macOS 使用 'open',Windows 使用 'start',Linux 使用 'xdg-open' */ browserCommand?: string; /** * 是否显示服务器日志 * @default true */ verbose?: boolean; /** * Playground 服务器实例的唯一标识 ID * 同一个 ID 共用 Playground 对话历史 * @default undefined(生成随机 UUID) */ id?: string; } ``` #### `launch` 方法返回值 ```typescript interface LaunchPlaygroundResult { /** * Playground 服务器实例 */ server: PlaygroundServer; /** * 服务器端口 */ port: number; /** * 服务器主机地址 */ host: string; /** * 关闭 Playground 的函数 */ close: () => Promise<void>; } ``` #### 使用示例 ```typescript import 'dotenv/config'; import { playgroundForAgent } from '@midscene/playground'; import { SampleDevice } from './sample-device'; import { Agent } from '@midscene/core/agent'; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); // 创建设备和 Agent 实例 const device = new SampleDevice(); const agent = new Agent(device); // 启动 Playground const result = await playgroundForAgent(agent).launch({ port: 5800, openBrowser: true, verbose: true }); console.log(`Playground 已启动:http://${result.host}:${result.port}`); // 在需要时关闭 Playground await sleep(10 * 60 * 1000); // 等待 10 分钟 await result.close(); console.log('Playground 已关闭!'); ``` ## 常见问题(FAQ) **我的 interface-controller 是通用的,可以收录到本文档中吗?** 可以,我们很乐意收集有创意的项目并将它们列在本文档中。 当项目准备好后,[给我们提一个 issue](https://github.com/web-infra-dev/midscene/issues)。 --- url: /zh/integrate-with-playwright.md --- import { PackageManagerTabs } from '@theme'; # 集成到 Playwright [Playwright.js](https://playwright.com/) 是由微软开发的一个开源自动化库,主要用于对网络应用程序进行端到端测试(end-to-end test)和网页抓取。 与 Playwright 的集成方式有以下两种方式: * 直接用脚本方式集成和调用 Midscene Agent,适合快速体验、原型开发、数据抓取和自动化脚本等场景。 * 在 Playwright 的测试用例中集成 Midscene,适合需要执行 UI 测试的场景。 ## 配置 AI 模型服务 下面以豆包 Seed 2.1 Turbo 为例: ```bash export MIDSCENE_MODEL_BASE_URL="https://ark.cn-beijing.volces.com/api/v3" export MIDSCENE_MODEL_API_KEY="your-api-key" export MIDSCENE_MODEL_NAME="doubao-seed-2-1-turbo-260628" export MIDSCENE_MODEL_FAMILY="doubao-seed" ``` 将 `your-api-key` 替换为你的 API Key。 > 如需使用千问(Qwen)、GLM、Gemini 或 GPT-5 等其他模型,请参考[支持的模型与配置](/model-common-config.md)。 全部配置项请参考[模型配置](/model-config.md)。 ## 直接集成 Midscene Agent :::info 样例项目 你可以在这里看到向 Playwright 集成的样例项目:[https://github.com/web-infra-dev/midscene-example/blob/main/playwright-demo](https://github.com/web-infra-dev/midscene-example/blob/main/playwright-demo) ::: ### 第一步:安装依赖 <PackageManagerTabs command="install @midscene/web playwright @playwright/test tsx --save-dev" /> ### 第二步:编写脚本 编写下方代码,保存为 `./demo.ts` ```typescript import { chromium } from 'playwright'; import { PlaywrightAgent } from '@midscene/web/playwright'; import 'dotenv/config'; // read environment variables from .env file const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); Promise.resolve( (async () => { const browser = await chromium.launch({ headless: true, // 'true' means we can't see the browser window args: ['--no-sandbox', '--disable-setuid-sandbox'], }); const page = await browser.newPage(); await page.setViewportSize({ width: 1280, height: 768, }); await page.goto('https://www.ebay.com'); await sleep(5000); // 👀 init Midscene agent const agent = new PlaywrightAgent(page); // 👀 type keywords, perform a search await agent.aiAct('type "Headphones" in search box, hit Enter'); // 👀 wait for the loading await agent.aiWaitFor('there is at least one headphone item on page'); // or you may use a plain sleep: // await sleep(5000); // 👀 understand the page content, find the items const items = await agent.aiQuery( '{itemTitle: string, price: Number}[], find item in list and corresponding price', ); console.log('headphones in stock', items); const isMoreThan1000 = await agent.aiBoolean( 'Is the price of the headphones more than 1000?', ); console.log('isMoreThan1000', isMoreThan1000); const price = await agent.aiNumber( 'What is the price of the first headphone?', ); console.log('price', price); const name = await agent.aiString( 'What is the name of the first headphone?', ); console.log('name', name); const location = await agent.aiLocate( 'What is the location of the first headphone?', ); console.log('location', location); // 👀 assert by AI await agent.aiAssert('There is a category filter on the left'); // 👀 click on the first item await agent.aiTap('the first item in the list'); await browser.close(); })(), ); ``` 更多 Agent 的 API 讲解请参考 [API 参考](/zh/reference.md#interaction-methods)。 ### 第三步:运行 使用 `tsx` 来运行,你会看到命令行打印出了耳机的商品信息: ```bash # run npx tsx demo.ts # 命令行应该有如下输出 # [ # { # itemTitle: 'JBL Tour Pro 2 - True wireless Noise Cancelling earbuds with Smart Charging Case', # price: 551.21 # }, # { # itemTitle: 'Soundcore Space One无线耳机40H ANC播放时间2XStronger语音还原', # price: 543.94 # } # ] ``` ### 第四步:查看运行报告 当上面的命令执行成功后,会在控制台输出:`Midscene - report file updated: /path/to/report/some_id.html`,通过浏览器打开该文件即可看到报告。 ## 在 Playwright 的测试用例中集成 Midscene 这里我们假设你已经拥有一个集成了 Playwright 的测试项目。 :::info 样例项目 你可以在这里看到向 Playwright 集成的样例项目:[https://github.com/web-infra-dev/midscene-example/blob/main/playwright-testing-demo](https://github.com/web-infra-dev/midscene-example/blob/main/playwright-testing-demo) ::: ### 第一步:新增依赖,更新配置文件 新增依赖 <PackageManagerTabs command="install @midscene/web --save-dev" /> 更新 playwright.config.ts ```diff export default defineConfig({ testDir: './e2e', + timeout: 90 * 1000, + reporter: [["list"], ["@midscene/web/playwright-reporter", { type: "merged" }]], }); ``` Reporter 配置项说明: * `type`: 报告模式,可选值为 `merged`(默认)或 `separate`。`merged` 表示多个测试用例生成一个合并报告,`separate` 表示为每个测试用例生成独立报告。 * `outputFormat`: 控制报告的生成格式。`'single-html'`(默认)将所有截图作为 base64 内嵌到单个 HTML 文件中。`'html-and-external-assets'` 将截图保存为独立的 PNG 文件到子目录,适用于报告文件过大的场景。**注意**:使用 `'html-and-external-assets'` 时,报告必须通过 HTTP 服务器访问,无法直接使用 `file://` 协议打开(因为浏览器的 CORS 限制会阻止从 file 协议加载相对路径的本地图片)。进入报告目录后运行以下命令之一: * 使用 Node.js:`npx serve` * 使用 Python:`python -m http.server` 或 `python3 -m http.server` 然后通过 `http://localhost:3000`(或终端显示的端口)访问报告。 ### 第二步:扩展 `test` 实例 把下方代码保存为 `./e2e/fixture.ts`; ```typescript import { test as base } from '@playwright/test'; import type { PlayWrightAiFixtureType } from '@midscene/web/playwright'; import { PlaywrightAiFixture } from '@midscene/web/playwright'; export const test = base.extend<PlayWrightAiFixtureType>( PlaywrightAiFixture({ waitForNetworkIdleTimeout: 2000, // 可选, 交互过程中等待网络空闲的超时时间, 默认值为 2000ms, 设置为 0 则禁用超时 replanningCycleLimit: 30, // 可选,覆盖 aiAct 默认的重规划次数上限 }), ); ``` `PlaywrightAiFixture()` 也支持传入共享的 `PlaywrightAgent` 配置,因此你可以在创建 fixture 时统一配置 `replanningCycleLimit`、`waitAfterAction`、`modelConfig` 等 Agent 行为。`testId`、`reportFileName`、`groupName`、`groupDescription` 这类由 fixture 管理的元信息仍会自动生成。 ### 第三步:编写测试用例 完整的交互、查询和辅助 API 请参考 [Agent API 参考](/zh/reference.md#interaction-methods)。如果需要调用更底层的能力,可以使用 `agentForPage` 获取 `PageAgent` 实例,再直接调用对应的方法: ```typescript test('case demo', async ({ agentForPage, page }) => { const agent = await agentForPage(page); await agent.recordToReport(); const logContent = agent._unstableLogContent(); console.log(logContent); }); ``` #### 示例代码 ```typescript title="./e2e/ebay-search.spec.ts" import { expect } from '@playwright/test'; import { test } from './fixture'; test.beforeEach(async ({ page }) => { page.setViewportSize({ width: 400, height: 905 }); await page.goto('https://www.ebay.com'); await page.waitForLoadState('networkidle'); }); test('search headphone on ebay', async ({ ai, aiQuery, aiAssert, aiInput, aiTap, aiScroll, aiWaitFor, aiRightClick, recordToReport, }) => { // 使用 aiInput 输入搜索关键词 await aiInput('Headphones', '搜索框'); // 使用 aiTap 点击搜索按钮 await aiTap('搜索按钮'); // 等待搜索结果加载 await aiWaitFor('搜索结果列表已加载', { timeoutMs: 5000 }); // 使用 aiScroll 滚动到页面底部 await aiScroll( { scrollType: 'untilBottom', }, '搜索结果列表', ); // 使用 aiQuery 获取商品信息 const items = await aiQuery<Array<{ title: string; price: number }>>( '获取搜索结果中的商品标题和价格', ); console.log('headphones in stock', items); expect(items?.length).toBeGreaterThan(0); // 使用 aiAssert 验证筛选功能 await aiAssert('界面左侧有类目筛选功能'); // 使用 recordToReport 记录当前状态 await recordToReport('搜索结果', { content: '耳机搜索的最终结果' }); }); ``` 更多 Agent 的 API 讲解请参考 [API 参考](/zh/reference.md#interaction-methods)。 ### 第四步:运行测试用例 ```bash npx playwright test ./e2e/ebay-search.spec.ts ``` ### 第五步:查看测试报告 当上面的命令执行成功后,会在控制台输出:`Midscene - report file updated: ./current_cwd/midscene_run/report/some_id.html`,通过浏览器打开该文件即可看到报告。 ## Advanced ### 关于在新标签页打开 `PlaywrightAgent` 是 page-level Agent:每个实例都与对应的页面唯一绑定。为了方便开发者调试,Midscene 默认拦截了新 tab 的页面(如点击一个带有 `target="_blank"` 属性的链接),将其改为在当前页面打开。 如果你想恢复在新标签页打开的行为,同时让当前 Agent 仍留在原页面,可以设置 `forceSameTabNavigation` 为 `false`,并自行为每个新标签页创建新的 Agent 实例。 如果一个 Agent 需要管理整个 browser context 内的页面切换,请使用 `PlaywrightBrowserAgent`。如果后续操作要自动继续在新打开的标签页中执行,请开启 `autoFollowNewPage`。 ```typescript const mid = new PlaywrightBrowserAgent(context, page, { autoFollowNewPage: true, }); ``` 当你要显式指定初始 active page 时,使用 `new PlaywrightBrowserAgent(context, page, options)`。当你希望 Midscene 自动选择或创建初始 active page 时,使用 `PlaywrightBrowserAgent.create(context, options)`;这个工厂会优先使用 `initialPage`,否则复用 context 里的第一个页面,或者创建一个新页面。 ### 浏览器支持说明 Midscene 的部分 Web 自动化能力依赖 Chromium-based browser 提供的 Chrome DevTools Protocol(CDP),例如浏览器级事件、触摸手势,以及一些交互操作中使用的 CDP fallback 路径。 使用 Playwright 时,推荐使用 Chromium。Firefox 和 WebKit 可能可以覆盖基础的 Playwright-native 操作,但依赖 CDP 的 Midscene 能力可能会在这些浏览器内核上报错。 ### 连接远程 Playwright 浏览器并接入 Midscene Agent :::info 示例项目 你可以在这里找到远程 Playwright 集成的示例项目:[https://github.com/web-infra-dev/midscene-example/tree/main/remote-playwright-demo](https://github.com/web-infra-dev/midscene-example/tree/main/remote-playwright-demo) ::: 当你已经在自有基础设施或供应商服务里运行浏览器时,可通过连接远程 Playwright 服务复用这些浏览器,让实例更贴近目标环境、避免重复启动,同时保持相同的 Midscene AI 自动化能力。 #### 前置依赖 <PackageManagerTabs command="install playwright @playwright/test @midscene/web --save-dev" /> #### 获取 CDP WebSocket URL 你可以从多种来源获取 CDP WebSocket URL: * **BrowserBase**:在 https://browserbase.com 注册并获取你的 CDP URL * **Browserless**:使用 https://browserless.io 或运行你自己的实例 * **本地 Chrome**:使用 `--remote-debugging-port=9222` 参数运行 Chrome,然后使用 `ws://localhost:9222/devtools/browser/...` * **Docker**:在 Docker 容器中运行 Chrome 并暴露调试端口 #### 代码示例 ```typescript import { chromium } from 'playwright'; import { PlaywrightAgent } from '@midscene/web/playwright'; // 来自远程浏览器服务的 CDP WebSocket URL const cdpWsUrl = 'ws://your-remote-browser.com/devtools/browser/your-session-id'; // 连接并选取页面 const browser = await chromium.connectOverCDP(cdpWsUrl); const context = browser.contexts()[0]; const page = context.pages()[0] || await context.newPage(); // 创建 Midscene Agent(用法与本地 Playwright agent 一致) const agent = new PlaywrightAgent(page); // 像平常一样调用 AI 方法 await agent.aiAction('跳转到 https://example.com'); await agent.aiAction('点击登录按钮'); // 清理 await agent.destroy(); await browser.close(); ``` 连接完成后,后续的 `PlaywrightAgent` 使用方式与本地启动的浏览器保持一致。 ### 扩展自定义交互动作 使用 `defineAction()` 定义自定义动作。构造 Agent 时,通过 `customActions` 传入这些动作。Midscene 会把这些动作追加到规划器中,让 Agent 可以调用你定义的领域特定动作。 ```typescript import { getMidsceneLocationSchema, z } from '@midscene/core'; import { defineAction } from '@midscene/core/device'; 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 = new PlaywrightAgent(page, { customActions: [ContinuousClick], }); await agent.aiAct('点击红色按钮五次'); ``` 更多关于自定义动作的细节,请参考 [集成到任意界面](/zh/integrate-with-any-interface.md)。 ## FAQ ### Playwright 下载浏览器耗时太久 Playwright 在 `npm install` 时默认不会下载浏览器镜像,需要单独执行 `npx playwright install`。这个过程如果网络较慢,耗时会比较久。 可以用下面两种方式优化: 1. 使用代理镜像,例如 `npmmirror.com` ```bash PLAYWRIGHT_DOWNLOAD_HOST="https://npmmirror.com/mirrors/playwright" npx playwright install ``` 2. 只下载常用的 `chromium` ```bash npx playwright install --with-deps chromium ``` ### 下拉框点击不到 这通常是因为页面使用了原生 `select` 标签来实现下拉框。浏览器会调用操作系统的原生控件来渲染展开后的选项面板,因此下拉框实际上并没有渲染在浏览器页面里,也就无法被 Playwright 截图捕捉到。 建议先检查报告中的截图:如果点击下拉框后,报告截图里确实没有出现下拉选项,基本就可以判断是这个问题。 Midscene 默认开启 `forceChromeSelectRendering` 选项,强制由 Chrome 来渲染 `select` 下拉框,这样下拉框就会出现在页面截图中,也能被 Playwright 正常识别。开启后,下拉框样式通常会和操作系统默认样式有明显区别。如果需要恢复系统原生渲染,可将 `forceChromeSelectRendering` 设为 `false`。 ### 浏览器界面持续闪动 在本地可视化界面中遇到持续闪烁,通常是因为 viewport 的 `deviceScaleFactor` 与系统/浏览器的像素比不匹配(常见于高分辨率或 Retina 屏幕)。 该闪动不会影响 Midscene 的截图或自动化运行,但会影响本地预览体验。解决方法:将 `deviceScaleFactor` 设置为与浏览器的 `window.devicePixelRatio` 一致。 ```typescript // Playwright:不支持像 Puppeteer 一样使用 0 表示自动适配 const page = await browser.newPage({ deviceScaleFactor: 2, // 请把这里的数字 2 替换为你的 window.devicePixelRatio }) ``` 如果不确定浏览器的像素比,可在任意页面按下 F12 打开控制台,输入 `window.devicePixelRatio` 查看;或在 Chrome 地址栏粘贴下面内容并回车以弹窗显示当前值: ```plain data:text/html,<script>alert(`deviceScaleFactor of your browser: ${devicePixelRatio}`)</script> ``` ### 自定义网络超时 当在网页上执行某个操作后,Midscene 会自动等待网络空闲。这是为了确保自动化过程的稳定性。如果等待超时,不会发生任何事情。 默认的超时时间配置如下: 1. 如果是页面跳转,则等待页面加载完成,默认超时时间为 5000ms 2. 如果是点击、输入等操作,则等待网络空闲,默认超时时间为 2000ms 当然,你可以通过配置参数修改默认超时时间,或者关闭这个功能: * 使用 [Agent](/zh/reference/index.md#%E6%9E%84%E9%80%A0%E5%99%A8) 上的 `waitForNetworkIdleTimeout` 和 `waitForNavigationTimeout` 参数 * 使用 [Yaml](/zh/automate-with-scripts-in-yaml.md#web-%E9%83%A8%E5%88%86) 脚本和 [PlaywrightAiFixture](/zh/integrate-with-playwright.md#%E7%AC%AC%E4%BA%8C%E6%AD%A5%E6%89%A9%E5%B1%95-test-%E5%AE%9E%E4%BE%8B) 中的 `waitForNetworkIdle` 参数 ### 截图时报 `waiting for fonts to load` 或 `page.screenshot: Timeout ... exceeded` 如果你在 Playwright 环境里看到类似下面的报错: ```plain page.screenshot: Timeout 10000ms exceeded. Call log: - taking page screenshot - waiting for fonts to load... ``` 这通常不是 Midscene 自身逻辑的问题,而是 Playwright 在截图时默认会等待页面字体加载完成。在某些 CI、容器或网络环境中,字体资源可能加载很慢,甚至一直无法完成,最终导致截图超时。 可以通过添加下面的环境变量来规避: ```bash export PW_TEST_SCREENSHOT_NO_FONTS_READY=1 ``` 如果你是在一条命令里临时执行,也可以这样写: ```bash PW_TEST_SCREENSHOT_NO_FONTS_READY=1 <你的命令> ``` 更多背景可参考 Playwright 的 issue:[\[BUG\] Page.screenshot method hangs indefinitely](https://github.com/microsoft/playwright/issues/28995)。 ## 更多 * 更多 Agent 的 API 文档请参考 [API 参考](/zh/reference.md#interaction-methods)。 * Playwright 的 API 文档请参考 [Playwright Agent API](/zh/reference.md#playwright-agent)。 * 样例项目:[直接集成 Playwright](https://github.com/web-infra-dev/midscene-example/blob/main/playwright-demo),[Playwright 测试集成](https://github.com/web-infra-dev/midscene-example/blob/main/playwright-testing-demo),[远程 Playwright 集成](https://github.com/web-infra-dev/midscene-example/tree/main/remote-playwright-demo) --- url: /zh/integrate-with-puppeteer.md --- # 集成到 Puppeteer import { PackageManagerTabs } from '@theme'; [Puppeteer](https://pptr.dev/) 是一个 Node.js 库,它通过 DevTools 协议或 WebDriver BiDi 提供控制 Chrome 或 Firefox 的高级 API。Puppeteer 默认在无界面模式(headless)下运行,但可以配置为在可见的浏览器模式(headed)中运行。 :::info 样例项目 你可以在这里看到向 Puppeteer 集成的样例项目:[https://github.com/web-infra-dev/midscene-example/blob/main/puppeteer-demo](https://github.com/web-infra-dev/midscene-example/blob/main/puppeteer-demo) 这里还有一个 Playwright 和 Vitest 结合的样例项目:[https://github.com/web-infra-dev/midscene-example/tree/main/playwright-with-vitest-demo](https://github.com/web-infra-dev/midscene-example/tree/main/playwright-with-vitest-demo) ::: ## 配置 AI 模型服务 下面以豆包 Seed 2.1 Turbo 为例: ```bash export MIDSCENE_MODEL_BASE_URL="https://ark.cn-beijing.volces.com/api/v3" export MIDSCENE_MODEL_API_KEY="your-api-key" export MIDSCENE_MODEL_NAME="doubao-seed-2-1-turbo-260628" export MIDSCENE_MODEL_FAMILY="doubao-seed" ``` 将 `your-api-key` 替换为你的 API Key。 > 如需使用千问(Qwen)、GLM、Gemini 或 GPT-5 等其他模型,请参考[支持的模型与配置](/model-common-config.md)。 全部配置项请参考[模型配置](/model-config.md)。 ## 集成 Midscene Agent ### 第一步:安装依赖 <PackageManagerTabs command="install @midscene/web puppeteer tsx dotenv --save-dev" /> ### 第二步:编写脚本 编写下方代码,保存为 `./demo.ts` ```typescript title="./demo.ts" import 'dotenv/config'; // 通过 dotenv/config 自动加载 .env 文件中的环境变量 import puppeteer from "puppeteer"; import { PuppeteerAgent } from "@midscene/web/puppeteer"; const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); Promise.resolve( (async () => { const browser = await puppeteer.launch({ headless: false, // here we use headed mode to help debug }); const page = await browser.newPage(); await page.setViewport({ width: 1280, height: 800, deviceScaleFactor: 1, }); await page.goto("https://www.ebay.com"); await sleep(5000); // 👀 初始化 Midscene agent const agent = new PuppeteerAgent(page); // 👀 执行搜索 // 注:尽管这是一个英文页面,你也可以用中文指令控制它 await agent.aiAct('在搜索框输入 "Headphones",敲回车'); await sleep(5000); // 👀 理解页面,提取数据 const items = await agent.aiQuery( '{itemTitle: string, price: Number}[], 找到列表里的商品标题和价格', ); console.log("耳机商品信息", items); // 👀 用 AI 断言 await agent.aiAssert("界面左侧有类目筛选功能"); await browser.close(); })() ); ``` ### 第三步:运行 使用 `tsx` 来运行,你会看到命令行打印出了耳机的商品信息: ```bash # run npx tsx demo.ts # 命令行应该有如下输出 # [ # { # itemTitle: 'Beats by Dr. Dre Studio Buds Totally Wireless Noise Cancelling In Ear + OPEN BOX', # price: 505.15 # }, # { # itemTitle: 'Skullcandy Indy Truly Wireless Earbuds-Headphones Green Mint', # price: 186.69 # } # ] ``` 更多 Agent 的 API 讲解请参考 [API 参考](/zh/reference.md#interaction-methods)。 ### 第四步:查看运行报告 当上面的命令执行成功后,会在控制台输出:`Midscene - report file updated: /path/to/report/some_id.html`,通过浏览器打开该文件即可看到报告。 <a id="puppeteeragent" /> ## Advanced ### 关于在新标签页打开 `PuppeteerAgent` 是 page-level Agent:每个实例都与对应的页面唯一绑定。为了方便开发者调试,Midscene 默认拦截了新 tab 的页面(如点击一个带有 `target="_blank"` 属性的链接),将其改为在当前页面打开。 如果你想恢复在新标签页打开的行为,同时让当前 Agent 仍留在原页面,可以设置 `forceSameTabNavigation` 为 `false`,并自行为每个新标签页创建新的 Agent 实例。 如果一个 Agent 需要管理整个 browser 内的页面切换,请使用 `PuppeteerBrowserAgent`。如果后续操作要自动继续在新打开的标签页中执行,请开启 `autoFollowNewPage`。 ```typescript const mid = new PuppeteerBrowserAgent(browser, page, { autoFollowNewPage: true, }); ``` 当你要显式指定初始 active page 时,使用 `new PuppeteerBrowserAgent(browser, page, options)`。当你希望 Midscene 自动选择或创建初始 active page 时,使用 `PuppeteerBrowserAgent.create(browser, options)`;这个工厂会优先使用 `initialPage`,否则复用浏览器里的第一个页面,或者创建一个新页面。 ### 浏览器支持说明 Midscene 的部分 Web 自动化能力依赖 Chromium-based browser 提供的 Chrome DevTools Protocol(CDP),例如浏览器级事件、触摸手势,以及一些交互操作中使用的 CDP fallback 路径。 使用 Puppeteer 时,推荐使用 Chrome、Chromium 或其他 Chromium-based browser。不提供兼容 CDP 能力的浏览器,可能会在 Midscene 使用 CDP-backed features 时报错。 ### 连接远程 Puppeteer 浏览器并接入 Midscene Agent :::info 示例项目 你可以在这里找到远程 Puppeteer 集成的示例项目:[https://github.com/web-infra-dev/midscene-example/tree/main/remote-puppeteer-demo](https://github.com/web-infra-dev/midscene-example/tree/main/remote-puppeteer-demo) ::: 当你想复用已有的远程浏览器(例如云端常驻的 worker、第三方浏览器网格或本地内网桌面)时,可以通过此流程把 Midscene 接到远程 Puppeteer 实例上。这样做能让浏览器靠近目标环境、降低重复启动成本,并统一管理浏览器资源,同时保持一致的 AI 自动化能力。 实践中你需要手动: 1. 从远程浏览器服务获取 CDP WebSocket URL 2. 使用 Puppeteer 连接到远程浏览器 3. 创建 Midscene Agent 进行 AI 驱动的自动化 #### 前置依赖 <PackageManagerTabs command="install puppeteer @midscene/web --save-dev" /> #### 获取 CDP WebSocket URL 你可以从多种来源获取 CDP WebSocket URL: * **BrowserBase**:在 https://browserbase.com 注册并获取你的 CDP URL * **Browserless**:使用 https://browserless.io 或运行你自己的实例 * **本地 Chrome**:使用 `--remote-debugging-port=9222` 参数运行 Chrome,然后使用 `ws://localhost:9222/devtools/browser/...` * **Docker**:在 Docker 容器中运行 Chrome 并暴露调试端口 #### 基础示例 ```typescript import puppeteer from 'puppeteer'; import { PuppeteerAgent } from '@midscene/web/puppeteer'; // 假设你已经有了一个 CDP WebSocket URL const cdpWsUrl = 'ws://your-remote-browser.com/devtools/browser/your-session-id'; // 连接到远程浏览器 const browser = await puppeteer.connect({ browserWSEndpoint: cdpWsUrl }); // 获取或创建页面 const pages = await browser.pages(); const page = pages[0] || await browser.newPage(); // 创建 Midscene Agent const agent = new PuppeteerAgent(page); // 使用 AI 方法 await agent.aiAction('跳转到 https://example.com'); await agent.aiAction('点击登录按钮'); const result = await agent.aiQuery('获取页面标题: {title: string}'); // 清理 await agent.destroy(); await browser.disconnect(); ``` ### 提供自定义动作 使用 `defineAction()` 定义自定义动作。构造 Agent 时,通过 `customActions` 传入这些动作。Midscene 会把这些动作追加到规划器中,让 Agent 可以调用你定义的领域特定动作。 ```typescript import { getMidsceneLocationSchema, z } from '@midscene/core'; import { defineAction } from '@midscene/core/device'; 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 = new PuppeteerAgent(page, { customActions: [ContinuousClick], }); await agent.aiAct('点击红色按钮五次'); ``` 更多关于自定义动作的细节,请参考 [集成到任意界面](/zh/integrate-with-any-interface.md)。 ## FAQ ### 安装 Puppeteer 很慢,或安装时卡住 Puppeteer 会在安装时通过 `postinstall` 自动下载浏览器镜像。下载慢或网络不通时,安装过程就会卡住。 ```bash # 先跳过浏览器下载,让依赖安装完成 PUPPETEER_SKIP_DOWNLOAD=true npm install # 再单独从镜像站下载 Chrome # 下面以 https://registry.npmmirror.com 为例,不同镜像平台的 base-url 规则可能不同 npx puppeteer browsers install chrome --base-url="https://registry.npmmirror.com/-/binary/chrome-for-testing" ``` ### 下拉框点击不到 如果点击下拉框后,报告中的截图没有出现下拉选项,通常是页面使用了原生 `select` 标签,导致展开后的下拉面板由操作系统原生控件渲染,没有真正出现在浏览器页面里。 Midscene 默认开启 `forceChromeSelectRendering` 选项,强制由 Chrome 来渲染下拉框;如需关闭可将其设为 `false`。详细原因、判断方式和效果说明,请参考 [Playwright FAQ — 下拉框点击不到](/zh/integrate-with-playwright.md#下拉框点击不到)。 ### 浏览器界面持续闪动 通常是 viewport 的 `deviceScaleFactor` 与系统像素比不匹配所致。将 `deviceScaleFactor` 设为 `0` 可自动适配: ```typescript await page.setViewport({ deviceScaleFactor: 0, }); ``` 更多详情请参考 [Playwright FAQ — 浏览器界面持续闪动](/zh/integrate-with-playwright.md#浏览器界面持续闪动)。 ### 自定义网络超时 Midscene 在执行操作后会自动等待网络空闲,你可以自定义或关闭超时时间——详见 [Playwright FAQ — 自定义网络超时](/zh/integrate-with-playwright.md#自定义网络超时)。 ## 更多 * 更多 Agent 的 API 文档请参考 [API 参考](/zh/reference.md#interaction-methods)。 * Puppeteer 的 API 文档请参考 [Puppeteer Agent API](/zh/reference.md#puppeteer-agent)。 * 样例项目 * Puppeteer:[https://github.com/web-infra-dev/midscene-example/blob/main/puppeteer-demo](https://github.com/web-infra-dev/midscene-example/blob/main/puppeteer-demo) * Playwright + Vitest:[https://github.com/web-infra-dev/midscene-example/tree/main/playwright-with-vitest-demo](https://github.com/web-infra-dev/midscene-example/tree/main/playwright-with-vitest-demo) --- url: /zh/introduction.md --- # Midscene.js - 面向 E2E 测试的 GUI Agent **AI 视觉驱动。全平台覆盖。开箱即用。** Midscene 是一个用于视觉驱动 UI 测试与自动化的开源 SDK。你用自然语言描述操作目标,Midscene 会驱动多模态模型为你规划并操作界面。它覆盖 Web、移动端、桌面端,甚至 `<canvas>` 场景。 ## 为什么选择 Midscene 大多数 UI 自动化都依赖页面结构,包括读取 DOM 或无障碍树的 AI 工具。页面结构既脆弱又不完整:选择器一重构就失效;缺少语义化标注的元素,如纯图标按钮、自定义渲染的控件和 `<canvas>`,对它们是“看不见”的;原生应用和跨域 iframe 也难以触达;页面结构还无法判断界面实际看起来是否正确。 Midscene 走了一条不同的路:它**仅凭截图**、借助多模态模型工作,你只需像真人测试那样用自然语言描述操作目标和验证条件。这会改变 UI 测试的体验: * **不再因每次重构而失效。** 标记或样式变化时无需再追着改选择器,用例的维护成本大幅下降。 * **触达每个元素、每种界面。** 只要人眼能看到,Midscene 就能定位。没有语义化标注的元素、`<canvas>`、原生应用,以及基于结构的工具够不到的跨域 iframe,都可以处理。 * **校验用户真正看到的效果。** 验证视觉结果,包括颜色、高亮、布局和渲染状态,而不只是判断某个节点是否存在于 DOM 中。 * **两种测试方式。** 把 Midscene 接入你现有的 [Playwright](/zh/integrate-with-playwright.md) 或 Vitest 测试,或让 AI Agent 通过 [Skills](/zh/skills.md) 自主测试你的应用。 * **可读的失败信息。** 每次运行都会生成可逐步回放的可视化报告。 > Midscene 首先为 UI 测试而生,但同一套视觉驱动引擎也能胜任任意 UI 自动化任务。你可以按自己的工作流使用它。 ## 能自动化什么 只要能截图,Midscene 就能工作。Web 浏览器、Android、iOS、HarmonyOS、桌面应用,以及[任意自定义界面](/zh/integrate-with-any-interface.md),全部通过同一套 API。每个平台在侧边栏都有各自的上手指南。 你可以用 JavaScript SDK 或 YAML 编写自动化,并在 [API 参考](/zh/reference.md#common) 中查阅 `aiAct`、`aiQuery`、`aiAssert` 等所有方法。如需了解各类 API 的职责,以及如何在 `aiAct` 与 JavaScript 编排之间选择,请参阅[基本概念](/zh/basics.md)。 ## 多模态模型驱动 Midscene 支持众多具备极强 UI 定位能力的常用多模态模型。你可以选用最容易获取的模型,也可以选择可自托管的开源模型:`Qwen3.x`、`Doubao-Seed-2.1`、`GLM-4.6V`、`gemini-3.5-flash`、`UI-TARS`。 请在[支持的模型与配置](/zh/model-common-config.md)中选择模型并复制对应配置。 ## 案例展示 在 Web 浏览器中自主注册 GitHub 表单,并通过所有字段校验: <video src="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/github2.mp4" height="300" controls /> 更多 iOS、Android、桌面端与自定义界面的实战案例,见 [案例展示](/zh/showcases.md)。 ## Benchmark 成绩 Midscene 在两个 Android Agent benchmark 中取得了以下成绩: | Benchmark | 成绩 | 测试配置 | 完整报告 | | --- | --- | --- | --- | | AndroidWorld | Pass@1 **93.10%**、Pass@2 **95.69%**、Pass@3 **97.41%** | Midscene 1.9.5、Gemini-3.5-Flash | [查看报告](/zh/android-world-benchmark-report.md) | | MobileWorld | Pass@1 **78.63%(92/117)** | Midscene 1.10.3、Gemini-3.6-Flash | [查看报告](/zh/mobile-world-benchmark-report.md) | 完整报告包含运行配置、验证条件说明和每项任务的执行记录。 ## 资源与社区 * 示例项目:[midscene-example](https://github.com/web-infra-dev/midscene-example) * GitHub:[web-infra-dev/midscene](https://github.com/web-infra-dev/midscene) * [微信公众号](https://lf3-static.bytednsdoc.com/obj/eden-cn/vhaeh7vhabf/web-infra-wechat.jpg) · [Discord](https://discord.gg/2JyBHxszE4) · [X](https://x.com/midscene_ai) · [飞书交流群](https://applink.larkoffice.com/client/chat/chatter/add_by_link?link_token=693v0991-a6bb-4b44-b2e1-365ca0d199ba) ## 致谢 Midscene 构建于众多优秀的开源项目之上,包括 UI-TARS、Qwen、Playwright、Puppeteer、scrcpy、appium、WebDriverAgent、YADB 和 libnut-core。完整列表请见 [README](https://github.com/web-infra-dev/midscene)。 ## License Midscene.js 使用 [MIT 许可协议](https://github.com/web-infra-dev/midscene/blob/main/LICENSE)。 --- url: /zh/llm-txt.md --- # LLMs.txt 文档 如何让 Cursor、Windstatic、GitHub Copilot、ChatGPT 和 Claude 等工具理解 Midscene.js。 我们支持 LLMs.txt 文件,使 Midscene.js 的文档可供大型语言模型使用。 ## 目录概览 以下文件可供使用: * [llms.txt](https://midscenejs.com/zh/llms.txt):主要的 LLMs.txt 文件 * [llms-full.txt](https://midscenejs.com/zh/llms-full.txt):Midscene.js 的完整文档 ## 使用方法 ### Cursor 在 Cursor 中使用 `@Docs` 功能来将 LLMs.txt 文件包含到你的项目中。 [阅读更多](https://docs.cursor.com/context/@-symbols/@-docs) ### Windstatic 使用 `@` 或在你的 `.windsurfrules` 文件中引用 LLMs.txt 文件。 [阅读更多](https://docs.windsurf.com/windsurf/getting-started#memories-and-rules) --- url: /zh/mcp.md --- # MCP 集成已下线 Midscene 不再发布 MCP server。请改用 [Skills](/zh/skills.md),让 AI 编程 Agent 通过各平台 CLI 驱动 Midscene。 如果仍然需要 MCP server 包,请将 Midscene 固定在 `1.9.8`。这是最后一个包含 MCP 支持的版本。 请从 Agent 配置中移除以下已退役的 MCP 包: * `@midscene/web-bridge-mcp` * `@midscene/android-mcp` * `@midscene/ios-mcp` * `@midscene/harmony-mcp` * `@midscene/computer-mcp` * `@midscene/mcp` 如果之前的 MCP 配置中设置了 `MIDSCENE_MCP_CHROME_PATH`,请迁移为 Skills 和 CLI 使用的 `MIDSCENE_CHROME_PATH`。旧变量会暂时作为迁移别名继续生效。 如果需要代码级自动化,请使用 JavaScript SDK、YAML runner,或 [Skills](/zh/skills.md) 中列出的各平台 CLI。 --- url: /zh/mobile-world-benchmark-report.md --- # Midscene MobileWorld Benchmark 测试报告 <style> {` .benchmark-status { display: inline-flex; min-width: 48px; align-items: center; justify-content: center; border-radius: 999px; padding: 2px 8px; font-size: 12px; font-weight: 600; line-height: 18px; } .benchmark-status-pass { color: #15803d; background: rgb(220 252 231 / 70%); border: 1px solid rgb(187 247 208 / 80%); } .benchmark-status-fail { color: #b91c1c; background: rgb(254 226 226 / 75%); border: 1px solid rgb(254 202 202 / 85%); } .dark .benchmark-status-pass { color: #86efac; background: rgb(22 101 52 / 35%); border-color: rgb(34 197 94 / 35%); } .dark .benchmark-status-fail { color: #fca5a5; background: rgb(127 29 29 / 35%); border-color: rgb(248 113 113 / 35%); } .benchmark-round table { width: 100%; table-layout: fixed; } .benchmark-round th, .benchmark-round td { vertical-align: middle; } .benchmark-round th:nth-child(1), .benchmark-round td:nth-child(1) { width: 56px; text-align: center; } .benchmark-round th:nth-child(2), .benchmark-round td:nth-child(2) { width: auto; word-break: break-word; } .benchmark-round th:nth-child(3), .benchmark-round td:nth-child(3) { width: 104px; text-align: center; } .benchmark-round th:nth-child(4), .benchmark-round td:nth-child(4) { width: 112px; } .benchmark-report-link { display: inline-flex; align-items: center; gap: 4px; font-weight: 600; white-space: nowrap; } .benchmark-report-link::after { content: "↗"; font-size: 0.9em; line-height: 1; transform: translateY(-1px); } `} </style> 本文是 Midscene 针对 MobileWorld benchmark 的测试报告。本次测试覆盖 117 个任务,Midscene 取得了 **Pass@1 78.63%(92/117)** 的结果。 :::info 关于 MobileWorld [MobileWorld](https://github.com/Tongyi-MAI/MobileWorld) 是面向真实移动场景的 Android Agent benchmark,覆盖跨应用、长链路、用户交互和工具增强等任务,并通过可复现的 Android 环境验证 Agent 的执行结果。 ::: ## 运行配置 | 字段 | 值 | | --- | --- | | 测试日期 | 2026-07-28 | | Model Name | `Gemini-3.6-Flash` | | Midscene version | `1.10.3` | | Device | `DockerEmulator` | | 任务数量 | 117 | | `MIDSCENE_REPLANNING_CYCLE_LIMIT` | 50 | | MobileWorld 工程 | MobileWorld 使用 Midscene benchmark 适配层,通过 Midscene RPC 执行 Agent,同时保留 MobileWorld 的任务初始化和验证流程。 | | 验收规则 | 一个 MobileWorld validator 按任务意图做了校准,涉及的 case 见下方列表。 | ## 验证条件调整 本次 benchmark 对以下 MobileWorld 验证条件做了调整: | 修改内容 | 涉及的 case | | --- | --- | | 旧 validator 要求 `created_at` 到 `expires_at` 必须间隔完整的 5 天。例如初始日期是 16 日,“五天后”是 21 日;但日期转换后的时分秒可能让实际间隔不足 5 × 24 小时,导致旧规则判定失败。现在改为接受超过 4 天且不超过 5 天的间隔。 | `MastodonNewFilterTask` | ## 报告文件 以下为本轮 117 个任务的详细报告。报告均为压缩后的自包含 HTML,点击“报告”会在新页面打开对应执行记录。 <details open className="benchmark-round"> <summary>Round 1(117 份报告 · 92 PASS · 25 FAIL)</summary> | # | 任务 | 状态 | 报告 | | --- | --- | --- | --- | | 1 | AcceptMeetingTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-1-AcceptMeetingTask__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 2 | AdjustBrightnessMaximumTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-2-AdjustBrightnessMaximumTask__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 3 | AdjustBrightnessMinimumTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-3-AdjustBrightnessMinimumTask__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 4 | AdjustFontIconMaximumTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-4-AdjustFontIconMaximumTask__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 5 | AdjustFontIconMinimumTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-5-AdjustFontIconMinimumTask__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 6 | BidFileRenameTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-6-BidFileRenameTask__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 7 | CVEmailTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-7-CVEmailTask__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 8 | CancelMeetingTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-8-CancelMeetingTask__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 9 | CartInfoNotificationTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-9-CartInfoNotificationTask__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 10 | CartManagementTask | <span className="benchmark-status benchmark-status-fail">FAIL</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-10-CartManagementTask__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Fail.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 11 | ChangeWallpaperTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-11-ChangeWallpaperTask__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 12 | CheckCartPriceTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-12-CheckCartPriceTask__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 13 | CheckConferenceAndSendSmsTask1 | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-13-CheckConferenceAndSendSmsTask1__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 14 | CheckConferenceAndSendSmsTask2 | <span className="benchmark-status benchmark-status-fail">FAIL</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-14-CheckConferenceAndSendSmsTask2__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Fail.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 15 | CheckConferenceDurationTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-15-CheckConferenceDurationTask__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 16 | CheckConferenceLocationTask | <span className="benchmark-status benchmark-status-fail">FAIL</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-16-CheckConferenceLocationTask__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Fail.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 17 | CheckDeduplicatedEventsTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-17-CheckDeduplicatedEventsTask__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 18 | CheckDepartTimeTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-18-CheckDepartTimeTask__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 19 | CheckEventTimeTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-19-CheckEventTimeTask__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 20 | CheckGithubInfoTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-20-CheckGithubInfoTask__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 21 | CheckInterviewTimesTask | <span className="benchmark-status benchmark-status-fail">FAIL</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-21-CheckInterviewTimesTask__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Fail.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 22 | CheckInvoiceTask1 | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-22-CheckInvoiceTask1__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 23 | CheckInvoiceTask2 | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-23-CheckInvoiceTask2__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 24 | CheckInvoiceTask3 | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-24-CheckInvoiceTask3__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 25 | CheckPuchasedItem | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-25-CheckPuchasedItem__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 26 | CheckRegistrationTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-26-CheckRegistrationTask__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 27 | CheckSetMeetTimeTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-27-CheckSetMeetTimeTask__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 28 | ChromeSearchBeijingWeatherTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-28-ChromeSearchBeijingWeatherTask__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 29 | CloseFlightModeTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-29-CloseFlightModeTask__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 30 | CountFileLinesTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-30-CountFileLinesTask__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 31 | DownloadSendReceiptTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-31-DownloadSendReceiptTask__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 32 | GoogleMapsAlibabaPhoneContactTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-32-GoogleMapsAlibabaPhoneContactTask__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 33 | GoogleMapsAlibabaSouthNeighborTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-33-GoogleMapsAlibabaSouthNeighborTask__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 34 | GraduationMassEmailTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-34-GraduationMassEmailTask__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 35 | InvoiceReceiptCopyAskUserTask | <span className="benchmark-status benchmark-status-fail">FAIL</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-35-InvoiceReceiptCopyAskUserTask__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Fail.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 36 | InvoiceReceiptCopyTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-36-InvoiceReceiptCopyTask__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 37 | ItemCheckoutTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-37-ItemCheckoutTask__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 38 | LocalFileManagementTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-38-LocalFileManagementTask__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 39 | LocalFileManagementTask2 | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-39-LocalFileManagementTask2__group-0-b3c13bb1-7b21-4fbf-8d3f-65fcdeb309e0-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 40 | MastodonAddBookmarkTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-40-MastodonAddBookmarkTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 41 | MastodonAddFeaturedHashtagsTask | <span className="benchmark-status benchmark-status-fail">FAIL</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-41-MastodonAddFeaturedHashtagsTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Fail.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 42 | MastodonAdjustTootsTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-42-MastodonAdjustTootsTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 43 | MastodonCalendarMultiMemosTask | <span className="benchmark-status benchmark-status-fail">FAIL</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-43-MastodonCalendarMultiMemosTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Fail.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 44 | MastodonChangeHeaderTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-44-MastodonChangeHeaderTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 45 | MastodonChangeLanguageTask | <span className="benchmark-status benchmark-status-fail">FAIL</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-45-MastodonChangeLanguageTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Fail.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 46 | MastodonConditionalFavoTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-46-MastodonConditionalFavoTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 47 | MastodonCreateListTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-47-MastodonCreateListTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 48 | MastodonCreateMemoTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-48-MastodonCreateMemoTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 49 | MastodonExportFollowsTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-49-MastodonExportFollowsTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 50 | MastodonFavoriteTootsTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-50-MastodonFavoriteTootsTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 51 | MastodonFilterLanguageTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-51-MastodonFilterLanguageTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 52 | MastodonFollowTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-52-MastodonFollowTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 53 | MastodonGetServerInfoTask | <span className="benchmark-status benchmark-status-fail">FAIL</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-53-MastodonGetServerInfoTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Fail.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 54 | MastodonImportMutedUsersTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-54-MastodonImportMutedUsersTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 55 | MastodonInviteTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-55-MastodonInviteTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 56 | MastodonMallPurchaseCommodityTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-56-MastodonMallPurchaseCommodityTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 57 | MastodonMallShareOrderTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-57-MastodonMallShareOrderTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 58 | MastodonManageHashtagsTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-58-MastodonManageHashtagsTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 59 | MastodonManageMultiListTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-59-MastodonManageMultiListTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 60 | MastodonMattermostPostNoticeTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-60-MastodonMattermostPostNoticeTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 61 | MastodonMultiInviteTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-61-MastodonMultiInviteTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 62 | MastodonNewFilterTask | <span className="benchmark-status benchmark-status-fail">FAIL</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-62-MastodonNewFilterTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Fail.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 63 | MastodonNewPostTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-63-MastodonNewPostTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 64 | MastodonOpenAutomatedDeletionTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-64-MastodonOpenAutomatedDeletionTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 65 | MastodonPinTootsTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-65-MastodonPinTootsTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 66 | MastodonPostEditedPhotoTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-66-MastodonPostEditedPhotoTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 67 | MastodonPostPollTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-67-MastodonPostPollTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 68 | MastodonRemoveBookmarkTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-68-MastodonRemoveBookmarkTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 69 | MastodonReplyTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-69-MastodonReplyTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 70 | MastodonReportTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-70-MastodonReportTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 71 | MastodonRevisePhotoAltTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-71-MastodonRevisePhotoAltTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 72 | MastodonRevisePollTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-72-MastodonRevisePollTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 73 | MastodonSavePhotosTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-73-MastodonSavePhotosTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 74 | MastodonServerInfoReportTask | <span className="benchmark-status benchmark-status-fail">FAIL</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-74-MastodonServerInfoReportTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Fail.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 75 | MastodonShareLocationTask | <span className="benchmark-status benchmark-status-fail">FAIL</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-75-MastodonShareLocationTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Fail.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 76 | MastodonUnfollowTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-76-MastodonUnfollowTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 77 | MastodonUpdateContactsTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-77-MastodonUpdateContactsTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 78 | MattermostBudgetApprovalPipelineTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-78-MattermostBudgetApprovalPipelineTask__group-1-0a3fa62d-94f0-4f16-ba4d-7ac1fb2a8596-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 79 | MattermostCreateChannelTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-79-MattermostCreateChannelTask__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 80 | MattermostCustomerFeedbackAnalysisTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-80-MattermostCustomerFeedbackAnalysisTask__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 81 | MattermostDeadlineReconciliationTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-81-MattermostDeadlineReconciliationTask__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 82 | MattermostEmailTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-82-MattermostEmailTask__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 83 | MattermostIncidentEscalationTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-83-MattermostIncidentEscalationTask__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 84 | MattermostProjectHandoverTask | <span className="benchmark-status benchmark-status-fail">FAIL</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-84-MattermostProjectHandoverTask__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Fail.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 85 | MattermostProjectStatusReportTask | <span className="benchmark-status benchmark-status-fail">FAIL</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-85-MattermostProjectStatusReportTask__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Fail.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 86 | MattermostReadingGroupTask | <span className="benchmark-status benchmark-status-fail">FAIL</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-86-MattermostReadingGroupTask__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Fail.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 87 | MattermostReplyToMessageTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-87-MattermostReplyToMessageTask__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 88 | MattermostResourceConflictResolutionTask | <span className="benchmark-status benchmark-status-fail">FAIL</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-88-MattermostResourceConflictResolutionTask__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Fail.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 89 | MattermostSendFileTask | <span className="benchmark-status benchmark-status-fail">FAIL</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-89-MattermostSendFileTask__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Fail.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 90 | MattermostShiftCoverageTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-90-MattermostShiftCoverageTask__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 91 | MattermostTechnicalDebtTriageTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-91-MattermostTechnicalDebtTriageTask__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 92 | MattermostVisualInstructionResponseTask | <span className="benchmark-status benchmark-status-fail">FAIL</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-92-MattermostVisualInstructionResponseTask__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Fail.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 93 | OpenFlightModeTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-93-OpenFlightModeTask__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 94 | PhotoManagementTask | <span className="benchmark-status benchmark-status-fail">FAIL</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-94-PhotoManagementTask__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Fail.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 95 | ReadQwen3PaperTask1 | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-95-ReadQwen3PaperTask1__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 96 | ReadQwen3PaperTask2 | <span className="benchmark-status benchmark-status-fail">FAIL</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-96-ReadQwen3PaperTask2__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Fail.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 97 | ReadQwen3PaperTask3 | <span className="benchmark-status benchmark-status-fail">FAIL</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-97-ReadQwen3PaperTask3__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Fail.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 98 | ReadQwen3PaperTask4 | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-98-ReadQwen3PaperTask4__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 99 | ReadQwen3PaperTask5 | <span className="benchmark-status benchmark-status-fail">FAIL</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-99-ReadQwen3PaperTask5__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Fail.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 100 | RecentTotalExpenseTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-100-RecentTotalExpenseTask__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 101 | RequestCarpoolingTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-101-RequestCarpoolingTask__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 102 | ReviewPaperEmailTask | <span className="benchmark-status benchmark-status-fail">FAIL</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-102-ReviewPaperEmailTask__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Fail.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 103 | SMSManagement | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-103-SMSManagement__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 104 | ScheduleCoffeeTimeViaSmsTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-104-ScheduleCoffeeTimeViaSmsTask__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 105 | ScheduleLunchViaSmsTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-105-ScheduleLunchViaSmsTask__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 106 | SearchItemAndCheckoutTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-106-SearchItemAndCheckoutTask__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 107 | SendFormsTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-107-SendFormsTask__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 108 | SendInterviewEmailTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-108-SendInterviewEmailTask__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 109 | SendInterviewInvitationTask | <span className="benchmark-status benchmark-status-fail">FAIL</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-109-SendInterviewInvitationTask__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Fail.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 110 | SendWaiverTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-110-SendWaiverTask__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 111 | SetAlarmTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-111-SetAlarmTask__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 112 | SharePhotosTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-112-SharePhotosTask__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 113 | SuggestPaperTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-113-SuggestPaperTask__group-0-b91cc3df-394d-47e4-8a5b-f61c5beddb79-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 114 | SumFileLinesTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-114-SumFileLinesTask__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 115 | TakeSelfieTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-115-TakeSelfieTask__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 116 | TextArrivalTimeTask | <span className="benchmark-status benchmark-status-fail">FAIL</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-116-TextArrivalTimeTask__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Fail.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | | 117 | ThanksgivingPrepTask | <span className="benchmark-status benchmark-status-pass">PASS</span> | <a href="https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/benchmark/MobileWorld/20260728/20260728/Task-117-ThanksgivingPrepTask__group-2-6f07b306-f600-41bd-a7b0-f5cd7a21335d-Pass.html" className="benchmark-report-link" target="_blank" rel="noreferrer" title="在新页面打开报告">报告</a> | </details> --- url: /zh/model-common-config.md --- import { ModelConfigTab, ModelConfigTabs, PackageManagerTabs } from '@theme'; # 支持的模型与配置 本文帮助你选择 Midscene 支持的模型、完成首次配置,并验证模型连接。 本页列出的模型配置,本质上都是环境变量。你可以根据使用方式提供这些配置: - 使用 Playground 时,在设置页面直接粘贴本页的配置文本。 - 使用 SDK 或命令行工具时,按照[设置环境变量](#设置环境变量)中的说明加载配置。 如需了解模型职责,请阅读[模型策略](./model-strategy)。如需查询完整参数,请阅读[模型配置参考](./model-config)。 ## 支持的模型 Midscene 支持使用以下多模态模型操作界面。每份配置都需要 Base URL、API Key、模型名称和 `MIDSCENE_MODEL_FAMILY`。模型系列(`MIDSCENE_MODEL_FAMILY`)决定了 Midscene 如何适配所选模型。 ### 豆包 Seed 系列 {#doubao-seed-model} - 常用模型供应商:[火山引擎](https://volcengine.com/) <div className="model-series-table"> | 模型版本 | 常用模型名称 | `MIDSCENE_MODEL_FAMILY` | 备注 | | --- | --- | --- | --- | | 2.x 系列 | `Doubao-Seed-2.1-turbo`、`Doubao-Seed-2.0-Lite` | `doubao-seed` | `Doubao-Seed-2.1-turbo` 目前私有测评集中定位速度最快,且定位效果也很好,推荐使用。 | | 1.x 系列 | `Doubao-Seed-1.6-Vision`、`Doubao-Seed-1.8` | `doubao-seed` | 1.x 系列为豆包的旧版本模型,综合表现已不具竞争力,建议优先使用 2.x 系列。为兼容已有配置,仍支持 `MIDSCENE_MODEL_FAMILY="doubao-vision"`;新配置建议使用 `doubao-seed`。 | </div> 环境变量配置示例,以 `doubao-seed-2.1-turbo` 为例: <ModelConfigTabs> <ModelConfigTab type="default"> ```bash MIDSCENE_MODEL_BASE_URL="https://ark.cn-beijing.volces.com/api/v3" # 火山引擎地址 MIDSCENE_MODEL_API_KEY="...." MIDSCENE_MODEL_NAME="doubao-seed-2-1-turbo-260628" MIDSCENE_MODEL_FAMILY="doubao-seed" ``` </ModelConfigTab> <ModelConfigTab type="planning"> ```bash MIDSCENE_PLANNING_MODEL_BASE_URL="https://ark.cn-beijing.volces.com/api/v3" # 火山引擎地址 MIDSCENE_PLANNING_MODEL_API_KEY="...." MIDSCENE_PLANNING_MODEL_NAME="doubao-seed-2-1-turbo-260628" MIDSCENE_PLANNING_MODEL_FAMILY="doubao-seed" ``` </ModelConfigTab> <ModelConfigTab type="insight"> ```bash MIDSCENE_INSIGHT_MODEL_BASE_URL="https://ark.cn-beijing.volces.com/api/v3" # 火山引擎地址 MIDSCENE_INSIGHT_MODEL_API_KEY="...." MIDSCENE_INSIGHT_MODEL_NAME="doubao-seed-2-1-turbo-260628" MIDSCENE_INSIGHT_MODEL_FAMILY="doubao-seed" ``` </ModelConfigTab> </ModelConfigTabs> 如果你的火山引擎账号已开通低延迟模式(Fast Tier),可以追加以下请求体参数来使用该能力。通常可将模型响应速度提升约 30% 至 50%。 ```bash MIDSCENE_MODEL_EXTRA_BODY_JSON={"service_tier":"fast"} ``` ### 千问 Qwen 系列 {#qwen} <span id="qwen3x" /> <span id="qwen3-vl" /> <span id="qwen25-vl" /> - 常用模型供应商:[阿里云](https://www.aliyun.com/) <div className="model-series-table"> | 模型版本 | 常用模型名称 | `MIDSCENE_MODEL_FAMILY` | 备注 | | --- | --- | --- | --- | | Qwen3.x 系列 | `qwen3.7-plus`、`qwen3.5-plus`、`qwen3.6-plus` | `qwen3` | 从定位测评的结果看,推荐顺序为 Qwen3.7 > Qwen3.5 > Qwen3.6。`qwen3.5`、`qwen3.6` 作为旧 family 仍然兼容。 | | Qwen3-VL 系列 | `qwen3-vl-plus` | `qwen3-vl` | 作为旧版本模型,不推荐使用。建议优先使用 Qwen3.x 系列。 | | Qwen2.5-VL 系列 | `qwen-vl-max-latest` | `qwen2.5-vl` | 作为旧版本模型,不推荐使用。建议优先使用 Qwen3.x 系列。 | </div> 环境变量配置示例,以 `qwen3.7-plus` 为例: <ModelConfigTabs> <ModelConfigTab type="default"> ```bash MIDSCENE_MODEL_BASE_URL="https://dashscope.aliyuncs.com/compatible-mode/v1" # 阿里云地址 MIDSCENE_MODEL_API_KEY="......" MIDSCENE_MODEL_NAME="qwen3.7-plus" MIDSCENE_MODEL_FAMILY="qwen3" # 如果你使用了其他版本的 Qwen,需要替换为对应的 model family ``` </ModelConfigTab> <ModelConfigTab type="planning"> ```bash MIDSCENE_PLANNING_MODEL_BASE_URL="https://dashscope.aliyuncs.com/compatible-mode/v1" # 阿里云地址 MIDSCENE_PLANNING_MODEL_API_KEY="......" MIDSCENE_PLANNING_MODEL_NAME="qwen3.7-plus" MIDSCENE_PLANNING_MODEL_FAMILY="qwen3" # 如果你使用了其他版本的 Qwen,需要替换为对应的 model family ``` </ModelConfigTab> <ModelConfigTab type="insight"> ```bash MIDSCENE_INSIGHT_MODEL_BASE_URL="https://dashscope.aliyuncs.com/compatible-mode/v1" # 阿里云地址 MIDSCENE_INSIGHT_MODEL_API_KEY="......" MIDSCENE_INSIGHT_MODEL_NAME="qwen3.7-plus" MIDSCENE_INSIGHT_MODEL_FAMILY="qwen3" # 如果你使用了其他版本的 Qwen,需要替换为对应的 model family ``` </ModelConfigTab> </ModelConfigTabs> ### DeepSeek 系列 {#deepseek} Midscene 从 v1.12.0 开始支持 DeepSeek。 - 常用模型供应商:[DeepSeek - 图像理解](https://api-docs.deepseek.com/zh-cn/guides/vision/) <div className="model-series-table"> | 模型版本 | 常用模型名称 | `MIDSCENE_MODEL_FAMILY` | 备注 | | --- | --- | --- | --- | | DeepSeek V4 系列 | `deepseek-v4-flash-vision-exp` | `deepseek` | 目前只有 `deepseek-v4-flash-vision-exp` 支持 Midscene 所需的多模态视觉输入。包括 `deepseek-v4-pro` 和普通 `deepseek-v4-flash` 在内的其他 DeepSeek V4 模型均不适用于 Midscene。 | </div> 环境变量配置示例,以 `deepseek-v4-flash-vision-exp` 为例: <ModelConfigTabs> <ModelConfigTab type="default"> ```bash MIDSCENE_MODEL_BASE_URL="https://api.deepseek.com" # DeepSeek API 地址 MIDSCENE_MODEL_API_KEY="......" MIDSCENE_MODEL_NAME="deepseek-v4-flash-vision-exp" MIDSCENE_MODEL_FAMILY="deepseek" ``` </ModelConfigTab> <ModelConfigTab type="planning"> ```bash MIDSCENE_PLANNING_MODEL_BASE_URL="https://api.deepseek.com" # DeepSeek API 地址 MIDSCENE_PLANNING_MODEL_API_KEY="......" MIDSCENE_PLANNING_MODEL_NAME="deepseek-v4-flash-vision-exp" MIDSCENE_PLANNING_MODEL_FAMILY="deepseek" ``` </ModelConfigTab> <ModelConfigTab type="insight"> ```bash MIDSCENE_INSIGHT_MODEL_BASE_URL="https://api.deepseek.com" # DeepSeek API 地址 MIDSCENE_INSIGHT_MODEL_API_KEY="......" MIDSCENE_INSIGHT_MODEL_NAME="deepseek-v4-flash-vision-exp" MIDSCENE_INSIGHT_MODEL_FAMILY="deepseek" ``` </ModelConfigTab> </ModelConfigTabs> ### Google Gemini 系列 {#gemini} - 常用模型供应商:[Google Gemini](https://gemini.google.com/) <div className="model-series-table"> | 模型版本 | 常用模型名称 | `MIDSCENE_MODEL_FAMILY` | 备注 | | --- | --- | --- | --- | | Gemini 3.x 系列 | `gemini-3.5-flash`、`gemini-3-flash-preview` | `gemini` | `gemini-3.5-flash` 是目前我们私有测评集中定位表现最好的模型。 | </div> 环境变量配置示例,以 `gemini-3.5-flash` 为例: <ModelConfigTabs> <ModelConfigTab type="default"> ```bash MIDSCENE_MODEL_BASE_URL="https://generativelanguage.googleapis.com/v1beta/openai/" # Google Gemini API 地址 MIDSCENE_MODEL_API_KEY="......" MIDSCENE_MODEL_NAME="gemini-3.5-flash" MIDSCENE_MODEL_FAMILY="gemini" ``` </ModelConfigTab> <ModelConfigTab type="planning"> ```bash MIDSCENE_PLANNING_MODEL_BASE_URL="https://generativelanguage.googleapis.com/v1beta/openai/" # Google Gemini API 地址 MIDSCENE_PLANNING_MODEL_API_KEY="......" MIDSCENE_PLANNING_MODEL_NAME="gemini-3.5-flash" MIDSCENE_PLANNING_MODEL_FAMILY="gemini" ``` </ModelConfigTab> <ModelConfigTab type="insight"> ```bash MIDSCENE_INSIGHT_MODEL_BASE_URL="https://generativelanguage.googleapis.com/v1beta/openai/" # Google Gemini API 地址 MIDSCENE_INSIGHT_MODEL_API_KEY="......" MIDSCENE_INSIGHT_MODEL_NAME="gemini-3.5-flash" MIDSCENE_INSIGHT_MODEL_FAMILY="gemini" ``` </ModelConfigTab> </ModelConfigTabs> ### OpenAI GPT 系列 {#gpt} - 常用模型供应商:[OpenAI](https://openai.com/) <div className="model-series-table"> | 模型版本 | 常用模型名称 | `MIDSCENE_MODEL_FAMILY` | 备注 | | --- | --- | --- | --- | | GPT-5 系列 | `gpt-5.4`、`gpt-5.5`、`gpt-5.6-sol`、`gpt-5.6-terra`、`gpt-5.6-luna` | `gpt-5` | GPT-5.4 之前的模型不支持视觉定位,仅可用作 Planning 模型或 Insight 模型。在实际定位测试中,GPT-5.5 和 GPT-5.6 的定位效果明显优于 GPT-5.4,建议优先使用 GPT-5.5 或 GPT-5.6。 | </div> 环境变量配置示例,以 `gpt-5.5` 为例: <ModelConfigTabs> <ModelConfigTab type="default"> ```bash MIDSCENE_MODEL_BASE_URL="https://api.openai.com/v1" # OpenAI API 地址;或你的兼容服务地址 MIDSCENE_MODEL_API_KEY="sk-..." MIDSCENE_MODEL_NAME="gpt-5.5" MIDSCENE_MODEL_FAMILY="gpt-5" ``` </ModelConfigTab> <ModelConfigTab type="planning"> ```bash MIDSCENE_PLANNING_MODEL_BASE_URL="https://api.openai.com/v1" # OpenAI API 地址;或你的兼容服务地址 MIDSCENE_PLANNING_MODEL_API_KEY="sk-..." MIDSCENE_PLANNING_MODEL_NAME="gpt-5.5" MIDSCENE_PLANNING_MODEL_FAMILY="gpt-5" ``` </ModelConfigTab> <ModelConfigTab type="insight"> ```bash MIDSCENE_INSIGHT_MODEL_BASE_URL="https://api.openai.com/v1" # OpenAI API 地址;或你的兼容服务地址 MIDSCENE_INSIGHT_MODEL_API_KEY="sk-..." MIDSCENE_INSIGHT_MODEL_NAME="gpt-5.5" MIDSCENE_INSIGHT_MODEL_FAMILY="gpt-5" ``` </ModelConfigTab> </ModelConfigTabs> <span id="use-codex-app-server-oauth-no-api-key" /> **使用 Codex App Server(OAuth,无需 API Key)** 如果你已经通过 Codex CLI 登录(`codex login`),并希望 Midscene 直接复用该 OAuth 会话,可设置: ```bash export MIDSCENE_MODEL_BASE_URL="codex://app-server" export MIDSCENE_MODEL_NAME="gpt-5.4" # 或者使用 Codex model/list 可见的其他模型 export MIDSCENE_MODEL_FAMILY="gpt-5" ``` 说明: - 该模式下不需要 `MIDSCENE_MODEL_API_KEY`。 - Midscene 会通过 stdio 调用 `codex app-server`。 - 请确保 `codex` 在 PATH 中可用,并通过 `codex login status` 确认登录状态。 :::warning 已知限制 相比于直接调用 OpenAI 兼容 API,我们观察到这种方式可能耗时更长、token 消耗更多,具体原因还在排查中。 ::: 使用 GPT-5 时,请注意以下事项: - 使用 GPT 做 UI 定位时,目前只支持使用 `gpt-5.4` 及以后的模型。因为为了获得最佳的定位效果,需要在发送图片时指定 `"detail": "original"` 参数,这一参数仅在 `gpt-5.4` 及后续模型上可用,`gpt-5.4-mini`、`gpt-5.4-nano` 等更小的 GPT-5 变体以及前代模型不支持 `original` 参数,会导致报错。详情请参考 [Images and Vision guide](https://developers.openai.com/api/docs/guides/images-vision) 和 [Computer use guide](https://developers.openai.com/api/docs/guides/tools-computer-use)。 - 按照 OpenAI 的文档,GPT-5 在处理非拉丁字母文本、字号太小的文本时效果可能不理想,参见 [Images and Vision guide](https://developers.openai.com/api/docs/guides/images-vision)。 - OpenAI 在 computer use 文档中提到,他们观察到 `1440x900` 和 `1600x900` 这两种截图尺寸上通常能获得比较好的效果,详见 [Computer use guide](https://developers.openai.com/api/docs/guides/tools-computer-use)。因此,建议按照 OpenAI 的推荐对截图尺寸进行调整。在 Midscene 中,你可以通过 Agent 参数里的 `screenshotShrinkFactor` 控制截图压缩倍率。如果是浏览器自动化,还可以通过浏览器 `viewport` 指定页面的尺寸和比例。 - 使用 Azure OpenAI 时,Azure 可能不会正确处理 `"detail": "original"`,从而造成点击坐标偏移。详见 [使用 Azure OpenAI 时点击坐标偏移](./faq#使用-azure-openai-时点击坐标偏移)。 - 如果你使用的是更老版本的 GPT-5,建议只将其用作规划模型,并搭配其他多模态模型完成定位,参考[多模型组合示例](#multi-model-combination-example)。 ### 月之暗面 Kimi 系列 {#kimi} - 常用模型供应商:[Moonshot AI 开放平台](https://platform.moonshot.cn/) <div className="model-series-table"> | 模型版本 | 常用模型名称 | `MIDSCENE_MODEL_FAMILY` | 备注 | | --- | --- | --- | --- | | K3 系列 | `kimi-k3` | `kimi3` | 根据 Kimi 的文档,K3 始终开启思考模式,无法关闭,且推理强度默认为 `max` | | K2.x 系列 | `kimi-k2.5`、`kimi-k2.6` | `kimi` | — | </div> 环境变量配置示例,以 `kimi-k3` 为例: <ModelConfigTabs> <ModelConfigTab type="default"> ```bash MIDSCENE_MODEL_BASE_URL="https://api.moonshot.cn/v1" # Moonshot AI API 地址 MIDSCENE_MODEL_API_KEY="......" MIDSCENE_MODEL_NAME="kimi-k3" MIDSCENE_MODEL_FAMILY="kimi3" # 如果使用 kimi-k2.6,请改为 "kimi" ``` </ModelConfigTab> <ModelConfigTab type="planning"> ```bash MIDSCENE_PLANNING_MODEL_BASE_URL="https://api.moonshot.cn/v1" # Moonshot AI API 地址 MIDSCENE_PLANNING_MODEL_API_KEY="......" MIDSCENE_PLANNING_MODEL_NAME="kimi-k3" MIDSCENE_PLANNING_MODEL_FAMILY="kimi3" # 如果使用 kimi-k2.6,请改为 "kimi" ``` </ModelConfigTab> <ModelConfigTab type="insight"> ```bash MIDSCENE_INSIGHT_MODEL_BASE_URL="https://api.moonshot.cn/v1" # Moonshot AI API 地址 MIDSCENE_INSIGHT_MODEL_API_KEY="......" MIDSCENE_INSIGHT_MODEL_NAME="kimi-k3" MIDSCENE_INSIGHT_MODEL_FAMILY="kimi3" # 如果使用 kimi-k2.6,请改为 "kimi" ``` </ModelConfigTab> </ModelConfigTabs> ### 小米 MiMo 系列 {#xiaomi-mimo} - 常用模型供应商:[小米 MiMo API 开放平台](https://platform.xiaomimimo.com/) <div className="model-series-table"> | 模型版本 | 常用模型名称 | `MIDSCENE_MODEL_FAMILY` | 备注 | | --- | --- | --- | --- | | V2.x 系列 | `mimo-v2.5` | `xiaomi-mimo` | 仅 Omni 系列支持多模态输入;Pro 系列是文本模型,不能用于 Midscene 视觉任务。 | </div> 环境变量配置示例,以 `mimo-v2.5` 为例: <ModelConfigTabs> <ModelConfigTab type="default"> ```bash MIDSCENE_MODEL_BASE_URL="https://api.xiaomimimo.com/v1" # 小米 MiMo API 地址 MIDSCENE_MODEL_API_KEY="......" MIDSCENE_MODEL_NAME="mimo-v2.5" MIDSCENE_MODEL_FAMILY="xiaomi-mimo" ``` </ModelConfigTab> <ModelConfigTab type="planning"> ```bash MIDSCENE_PLANNING_MODEL_BASE_URL="https://api.xiaomimimo.com/v1" # 小米 MiMo API 地址 MIDSCENE_PLANNING_MODEL_API_KEY="......" MIDSCENE_PLANNING_MODEL_NAME="mimo-v2.5" MIDSCENE_PLANNING_MODEL_FAMILY="xiaomi-mimo" ``` </ModelConfigTab> <ModelConfigTab type="insight"> ```bash MIDSCENE_INSIGHT_MODEL_BASE_URL="https://api.xiaomimimo.com/v1" # 小米 MiMo API 地址 MIDSCENE_INSIGHT_MODEL_API_KEY="......" MIDSCENE_INSIGHT_MODEL_NAME="mimo-v2.5" MIDSCENE_INSIGHT_MODEL_FAMILY="xiaomi-mimo" ``` </ModelConfigTab> </ModelConfigTabs> ### 智谱 GLM-V 系列 {#glm-v} - 常用模型供应商:[Z.AI(国际)](https://z.ai/manage-apikey/apikey-list)、[BigModel(国内)](https://bigmodel.cn/usercenter/proj-mgmt/apikeys) <div className="model-series-table"> | 模型版本 | 常用模型名称 | `MIDSCENE_MODEL_FAMILY` | 备注 | | --- | --- | --- | --- | | GLM-5V 系列 | `glm-5v-turbo` | `glm-v` | — | | GLM-4.6 系列 | `glm-4.6v` | `glm-v` | `glm-4.6v` 是开源模型。 | </div> 环境变量配置示例,以 `glm-5v-turbo` 为例: <ModelConfigTabs> <ModelConfigTab type="default"> ```bash MIDSCENE_MODEL_BASE_URL="https://open.bigmodel.cn/api/paas/v4" # BigModel API 地址;Z.AI 使用 https://api.z.ai/api/paas/v4 MIDSCENE_MODEL_API_KEY="......" MIDSCENE_MODEL_NAME="glm-5v-turbo" MIDSCENE_MODEL_FAMILY="glm-v" ``` </ModelConfigTab> <ModelConfigTab type="planning"> ```bash MIDSCENE_PLANNING_MODEL_BASE_URL="https://open.bigmodel.cn/api/paas/v4" # BigModel API 地址;Z.AI 使用 https://api.z.ai/api/paas/v4 MIDSCENE_PLANNING_MODEL_API_KEY="......" MIDSCENE_PLANNING_MODEL_NAME="glm-5v-turbo" MIDSCENE_PLANNING_MODEL_FAMILY="glm-v" ``` </ModelConfigTab> <ModelConfigTab type="insight"> ```bash MIDSCENE_INSIGHT_MODEL_BASE_URL="https://open.bigmodel.cn/api/paas/v4" # BigModel API 地址;Z.AI 使用 https://api.z.ai/api/paas/v4 MIDSCENE_INSIGHT_MODEL_API_KEY="......" MIDSCENE_INSIGHT_MODEL_NAME="glm-5v-turbo" MIDSCENE_INSIGHT_MODEL_FAMILY="glm-v" ``` </ModelConfigTab> </ModelConfigTabs> **了解更多关于 GLM-4.6V 开源模型** - GitHub: [https://github.com/zai-org/GLM-V](https://github.com/zai-org/GLM-V) - Hugging Face: [https://huggingface.co/zai-org/GLM-4.6V](https://huggingface.co/zai-org/GLM-4.6V) ## 设置环境变量 Midscene 从环境变量读取模型配置。请选择与你的使用方式对应的方法,也可以沿用项目已有的环境变量管理方式。 ### 在当前 Shell 中设置环境变量 以下示例适用于 Bash 和 Zsh。变量只在当前 Shell 会话及其启动的子进程中有效。 ```bash # 将全部值替换为所选模型服务商提供的配置 export MIDSCENE_MODEL_BASE_URL="https://替换为你的模型服务地址/v1" export MIDSCENE_MODEL_API_KEY="替换为你的 API Key" export MIDSCENE_MODEL_NAME="替换为你的模型名称" export MIDSCENE_MODEL_FAMILY="替换为你的模型对应的 family" ``` ### 通过 CLI 加载 `.env` 在运行 `midscene` 命令时所在的目录中创建 `.env` 文件。`@midscene/cli` 会自动读取这个文件。 ```bash # 将全部值替换为所选模型服务商提供的配置 MIDSCENE_MODEL_BASE_URL="https://替换为你的模型服务地址/v1" MIDSCENE_MODEL_API_KEY="替换为你的 API Key" MIDSCENE_MODEL_NAME="替换为你的模型名称" MIDSCENE_MODEL_FAMILY="替换为你的模型对应的 family" ``` `.env` 中的每一行都不需要添加 `export`。运行 YAML 任务时,当前 Shell 中已有的同名变量默认优先于 `.env`。如需让 `.env` 覆盖它们,请使用 `--dotenv-override`。 `midscene model verify` 是例外。该命令会使用 `.env` 中的值覆盖当前 Shell 中的同名变量。 ### 通过 dotenv 为 JavaScript SDK 加载 `.env` Midscene JavaScript SDK 直接读取 Node.js 进程中的模型配置环境变量(`process.env`)。如果 Shell、容器或部署平台已经提供这些变量,无需安装 dotenv。如果配置保存在 `.env` 文件中,可以使用 [dotenv](https://www.npmjs.com/package/dotenv) 将文件中的变量加载到 `process.env`。 <PackageManagerTabs command="install dotenv" /> 在运行脚本时所在的目录中创建 `.env` 文件: ```bash # 将全部值替换为所选模型服务商提供的配置 MIDSCENE_MODEL_BASE_URL="https://替换为你的模型服务地址/v1" MIDSCENE_MODEL_API_KEY="替换为你的 API Key" MIDSCENE_MODEL_NAME="替换为你的模型名称" MIDSCENE_MODEL_FAMILY="替换为你的模型对应的 family" ``` 在创建 Midscene Agent 之前导入 dotenv: ```typescript import 'dotenv/config'; ``` 如果当前 Shell 已有同名变量,dotenv 默认保留现有值。[Midscene 示例项目](https://github.com/web-infra-dev/midscene-example)也使用了这种加载方式。 ## 验证配置 设置环境变量后,运行以下任一命令: ```bash # 使用当前项目中安装的 CLI npx midscene model verify # 或使用最新版本的 CLI npx @midscene/cli@latest model verify ``` 如果验证失败,请阅读[模型调试与可观测性](./model-debugging-observability)。 ## 配置多个模型(可选) {#multi-model-combination-example} 多模型配置不是必需项。大多数场景只需配置默认模型,即可完成 UI 定位和操作。仅当复杂规划或页面理解需要使用独立模型时,才需要配置 Planning 模型或 Insight 模型。你可以只配置其中一个,也可以同时配置两者。 如需了解何时组合多个模型,请阅读[模型策略](./model-strategy)。 以下示例使用 Qwen 3.5 作为默认模型,负责视觉定位。GPT-5.4 作为 Planning 模型和 Insight 模型,负责复杂推理。 ```bash # 默认多模态模型:Qwen 3.5 export MIDSCENE_MODEL_BASE_URL="https://..." # Qwen 3.5 接口地址 export MIDSCENE_MODEL_API_KEY="..." # 你的 Qwen 3.5 API Key export MIDSCENE_MODEL_NAME="qwen3.5-plus" export MIDSCENE_MODEL_FAMILY="qwen3.5" # Planning 模型:GPT-5.4 export MIDSCENE_PLANNING_MODEL_API_KEY="sk-..." # 你的 GPT-5.4 API Key export MIDSCENE_PLANNING_MODEL_BASE_URL="https://..." export MIDSCENE_PLANNING_MODEL_NAME="gpt-5.4" export MIDSCENE_PLANNING_MODEL_FAMILY="gpt-5" # Insight 模型:GPT-5.4 export MIDSCENE_INSIGHT_MODEL_API_KEY="sk-..." # 你的 GPT-5.4 API Key export MIDSCENE_INSIGHT_MODEL_BASE_URL="https://..." export MIDSCENE_INSIGHT_MODEL_NAME="gpt-5.4" export MIDSCENE_INSIGHT_MODEL_FAMILY="gpt-5" ``` ## 其他兼容模型 以下小参数模型也与 Midscene 兼容,主要面向自动化场景。它们对部署硬件的要求较低,但处理复杂任务或大尺寸截图的能力可能受限。选择前,请结合实际任务和部署条件进行评估。 ### 智谱 AutoGLM 系列 {#auto-glm} 智谱 AutoGLM 是智谱 AI 推出的开源移动端 UI 自动化模型,模型尺寸为 9B。 从 [Z.AI(国际)](https://z.ai/manage-apikey/apikey-list) 或 [BigModel(国内)](https://bigmodel.cn/usercenter/proj-mgmt/apikeys) 获取 API Key 后,可以使用以下配置: ```bash MIDSCENE_MODEL_BASE_URL="https://open.bigmodel.cn/api/paas/v4" # 或 https://api.z.ai/api/paas/v4 MIDSCENE_MODEL_API_KEY="......" MIDSCENE_MODEL_NAME="autoglm-phone" # 模型名以平台实际模型名为准 MIDSCENE_MODEL_FAMILY="auto-glm" # 或 "auto-glm-multilingual" ``` **关于 `MIDSCENE_MODEL_FAMILY` 配置** AutoGLM 提供了两个版本的模型,通过 `MIDSCENE_MODEL_FAMILY` 区分: - `auto-glm` - 对应 AutoGLM-Phone-9B,针对**中文环境**优化 - `auto-glm-multilingual` - 对应 AutoGLM-Phone-9B-Multilingual,支持**英语等其他语言**场景 请根据你的应用语言选择合适的版本。 :::info AutoGLM 更适合移动端交互。`aiAssert` 和 `aiQuery` 等 API 需要理解页面。使用这些 API 时,请通过 `MIDSCENE_INSIGHT_MODEL_...` 环境变量配置独立的 Insight 模型。详情请参考[模型策略](./model-strategy)。 ::: **了解更多关于智谱 AutoGLM** - GitHub: [https://github.com/zai-org/Open-AutoGLM](https://github.com/zai-org/Open-AutoGLM) - Hugging Face: [https://huggingface.co/zai-org/AutoGLM-Phone-9B](https://huggingface.co/zai-org/AutoGLM-Phone-9B) ### UI-TARS 系列 {#ui-tars} 你可以在 [火山引擎](https://volcengine.com) 上使用已部署的 `doubao-1.5-ui-tars`。 ```bash MIDSCENE_MODEL_BASE_URL="https://ark.cn-beijing.volces.com/api/v3" MIDSCENE_MODEL_API_KEY="...." MIDSCENE_MODEL_NAME="ep-2025..." # 来自火山引擎的推理接入点 ID 或模型名称 MIDSCENE_MODEL_FAMILY="vlm-ui-tars-doubao-1.5" ``` **关于 `MIDSCENE_MODEL_FAMILY` 配置** `MIDSCENE_MODEL_FAMILY` 用于指定 UI-TARS 版本,使用以下值之一: - `vlm-ui-tars`:用于模型版本 `1.0` - `vlm-ui-tars-doubao`:用于在火山引擎上部署的模型版本 `1.5`(与 `vlm-ui-tars-doubao-1.5` 等效) - `vlm-ui-tars-doubao-1.5`:用于在火山引擎上部署的模型版本 `1.5` :::info 旧版本使用 `MIDSCENE_USE_VLM_UI_TARS=DOUBAO` 或 `MIDSCENE_USE_VLM_UI_TARS=1.5` 配置,该配置仍然兼容但已废弃,建议迁移到 `MIDSCENE_MODEL_FAMILY`。 迁移对应关系: - `MIDSCENE_USE_VLM_UI_TARS=1.0` → `MIDSCENE_MODEL_FAMILY="vlm-ui-tars"` - `MIDSCENE_USE_VLM_UI_TARS=1.5` → `MIDSCENE_MODEL_FAMILY="vlm-ui-tars-doubao-1.5"` - `MIDSCENE_USE_VLM_UI_TARS=DOUBAO` → `MIDSCENE_MODEL_FAMILY="vlm-ui-tars-doubao"` ::: ## 下一步 - 在[模型策略](./model-strategy)中了解何时使用 Default、Planning 和 Insight 模型。 - 在[模型配置参考](./model-config)中查询全部环境变量。 - 在[模型调试与可观测性](./model-debugging-observability)中排查连接和兼容性问题。 --- url: /zh/model-config.md --- # 模型配置参考 本页汇总 Midscene 的全部模型配置。如需查看支持的模型名称、模型 family 和可复制的配置示例,请参考[支持的模型与配置](./model-common-config)。如需了解模型职责和组合方式,请参考[模型策略](./model-strategy)。如需排查连接问题、查看日志、采集 Trace 或记录调用,请参考[模型调试与可观测性](./model-debugging-observability)。 ## 必选配置 你需要为 Midscene 配上一个默认模型,详见 [模型策略](./model-strategy) 文档。 | 名称 | 描述 | |------|-------------| | `MIDSCENE_MODEL_API_KEY` | OpenAI 兼容 HTTP 服务需要的模型 API Key,如 "sk-abcd..."。当 `MIDSCENE_MODEL_BASE_URL="codex://app-server"` 时不需要,具体设置请参考[使用 Codex App Server](./model-common-config#use-codex-app-server-oauth-no-api-key) | | `MIDSCENE_MODEL_BASE_URL` | API 的接入 URL,常见以版本号结尾(如`/v1`);不需要编写最后的 `/chat/completion` 部分,底层 SDK 会自动添加 | | `MIDSCENE_MODEL_NAME` | 模型名称 | | `MIDSCENE_MODEL_FAMILY` | 模型系列,用于确定坐标处理方式 | ## 高阶配置(可选) 如果你为 Insight 或 Planning 配置了独立模型,那么本节中的模型相关 `MIDSCENE_MODEL_*` 配置需要使用对应的 `MIDSCENE_INSIGHT_MODEL_*` 或 `MIDSCENE_PLANNING_MODEL_*` 配置才能在 Insight 或 Planning 意图时生效。 | 名称 | 描述 | |------|-------------| | `MIDSCENE_MODEL_TIMEOUT` | AI 接口调用硬超时(毫秒,默认意图),默认 `180000`(180 秒)。设为 `0` 可禁用硬超时,只有调用方传入的 `AbortSignal` 能取消请求。说明:Midscene 控制的是完整请求生命周期,而不只是首个响应 header 返回的时间,因此即使响应体读取阶段卡住,也能被及时终止 | | `MIDSCENE_MODEL_TEMPERATURE` | 模型采样温度 | | `MIDSCENE_MODEL_RETRY_COUNT` | AI 调用失败时的重试次数,默认 1(即失败后重试 1 次)。模型请求遇到 HTTP 错误,或模型返回值无法被结构化解析时,都会发起重试 | | `MIDSCENE_MODEL_RETRY_INTERVAL` | AI 调用重试间隔毫秒数,默认 2000 | | `MIDSCENE_MODEL_REASONING_ENABLED` | 控制是否启用模型的原生思考能力,Midscene 默认关闭。详见[模型原生思考](#model-native-reasoning) | | `MIDSCENE_MODEL_REASONING_EFFORT` | 控制模型的原生思考力度,部分模型支持。常用值:`low`、`medium`、`high`。详见[模型原生思考](#model-native-reasoning) | | `MIDSCENE_MODEL_REASONING_BUDGET` | 思考 Token 预算(数字),部分模型支持。详见[模型原生思考](#model-native-reasoning) | | `MIDSCENE_MODEL_RESPONSE_FORMAT` | 结构化响应策略:`auto`(默认)表示 Midscene 会在合适的场景自动使用 `response_format` 参数指定模型输出的结构化格式(一般是 JSON),来尽可能保证模型返回值能够被结构化解析;`none` 表示不指定 `response_format` 参数,适用于模型不支持结构化输出的情况。 | | `MIDSCENE_MODEL_HTTP_PROXY` | HTTP/HTTPS 代理配置,如 `http://127.0.0.1:8080` 或 `https://proxy.example.com:8080`,优先级高于 `MIDSCENE_MODEL_SOCKS_PROXY` | | `MIDSCENE_MODEL_SOCKS_PROXY` | SOCKS 代理配置,如 `socks5://127.0.0.1:1080` | | `MIDSCENE_MODEL_INIT_CONFIG_JSON` | 覆盖 OpenAI SDK 初始化配置的 JSON。自定义鉴权 header 请使用 `defaultHeaders`;`extra_headers` 和 `extraHeaders` 也会作为别名兼容 | | `MIDSCENE_MODEL_EXTRA_BODY_JSON` | 合并到每次 chat completion 请求体中的 JSON。与 `MIDSCENE_MODEL_INIT_CONFIG_JSON`(配置 SDK 客户端)不同,此参数会展开到每次发送到模型的 `completion.create()` 调用体中,例如在 vLLM 中启用思考模式:`'{"chat_template_kwargs":{"enable_thinking":true}}'` | > 提示:通过 Agent 的 `replanningCycleLimit` 入参控制重规划次数(默认 20,`vlm-ui-tars` 为 40),不再使用环境变量。 ### 为 Insight 意图单独配置模型 如果你想为 Insight 意图单独配置模型,需额外配置以下字段: | 名称 | 描述 | |------|-------------| | `MIDSCENE_INSIGHT_MODEL_API_KEY` | API Key | | `MIDSCENE_INSIGHT_MODEL_BASE_URL` | API 的接入 URL,常见以版本号结尾(如`/v1`);不需要编写最后的 `/chat/completion` 部分 | | `MIDSCENE_INSIGHT_MODEL_NAME` | 模型名称 | | `MIDSCENE_INSIGHT_MODEL_FAMILY` | 模型 family | | `MIDSCENE_INSIGHT_MODEL_TIMEOUT` | 可选,Insight 意图的 AI 接口调用超时时间(毫秒)| | `MIDSCENE_INSIGHT_MODEL_TEMPERATURE` | 可选,Insight 意图的模型采样温度 | | `MIDSCENE_INSIGHT_MODEL_RETRY_COUNT` | 可选,效果等同于 `MIDSCENE_MODEL_RETRY_COUNT` | | `MIDSCENE_INSIGHT_MODEL_RETRY_INTERVAL` | 可选,效果等同于 `MIDSCENE_MODEL_RETRY_INTERVAL` | | `MIDSCENE_INSIGHT_MODEL_HTTP_PROXY` | 可选,效果等同于 `MIDSCENE_MODEL_HTTP_PROXY` | | `MIDSCENE_INSIGHT_MODEL_SOCKS_PROXY` | 可选,效果等同于 `MIDSCENE_MODEL_SOCKS_PROXY` | | `MIDSCENE_INSIGHT_MODEL_INIT_CONFIG_JSON` | 可选,效果等同于 `MIDSCENE_MODEL_INIT_CONFIG_JSON` | | `MIDSCENE_INSIGHT_MODEL_EXTRA_BODY_JSON` | 可选,效果等同于 `MIDSCENE_MODEL_EXTRA_BODY_JSON` | | `MIDSCENE_INSIGHT_MODEL_RESPONSE_FORMAT` | 可选,在合适的 Insight 场景中控制结构化响应策略 | ### 为 Planning 意图单独配置模型 如果你想为 Planning 意图单独配置模型,需额外配置以下字段: | 名称 | 描述 | |------|-------------| | `MIDSCENE_PLANNING_MODEL_API_KEY` | API Key | | `MIDSCENE_PLANNING_MODEL_BASE_URL` | API 的接入 URL,常见以版本号结尾(如`/v1`);不需要编写最后的 `/chat/completion` 部分 | | `MIDSCENE_PLANNING_MODEL_NAME` | 模型名称 | | `MIDSCENE_PLANNING_MODEL_FAMILY` | 模型 family | | `MIDSCENE_PLANNING_MODEL_TIMEOUT` | 可选,Planning 意图的 AI 接口调用超时时间(毫秒)| | `MIDSCENE_PLANNING_MODEL_TEMPERATURE` | 可选,Planning 意图的模型采样温度 | | `MIDSCENE_PLANNING_MODEL_RETRY_COUNT` | 可选,效果等同于 `MIDSCENE_MODEL_RETRY_COUNT` | | `MIDSCENE_PLANNING_MODEL_RETRY_INTERVAL` | 可选,效果等同于 `MIDSCENE_MODEL_RETRY_INTERVAL` | | `MIDSCENE_PLANNING_MODEL_HTTP_PROXY` | 可选,效果等同于 `MIDSCENE_MODEL_HTTP_PROXY` | | `MIDSCENE_PLANNING_MODEL_SOCKS_PROXY` | 可选,效果等同于 `MIDSCENE_MODEL_SOCKS_PROXY` | | `MIDSCENE_PLANNING_MODEL_INIT_CONFIG_JSON` | 可选,效果等同于 `MIDSCENE_MODEL_INIT_CONFIG_JSON` | | `MIDSCENE_PLANNING_MODEL_EXTRA_BODY_JSON` | 可选,效果等同于 `MIDSCENE_MODEL_EXTRA_BODY_JSON` | | `MIDSCENE_PLANNING_MODEL_RESPONSE_FORMAT` | 可选,在合适的 Planning 场景中控制结构化响应策略 | ### 模型原生思考 {#model-native-reasoning} Midscene 默认关闭模型原生思考,以获得更好的执行速度和稳定性。如果模型不支持关闭原生思考,Midscene 会通过控制思考粒度或限制思考额度,尽可能减少模型的原生思考。 以下环境变量是 Midscene 对不同模型服务商参数的统一抽象。实际发送给模型的参数由 `MIDSCENE_MODEL_FAMILY` 决定。 `MIDSCENE_MODEL_REASONING_ENABLED` 显式控制是否启用模型原生思考: - `false`:强制关闭模型原生思考,也是 Midscene 的默认行为。 - `true`:强制启用模型原生思考。 - `default`:遵循模型的默认行为。Midscene 不发送启用或关闭思考的参数,并忽略显式配置的 `MIDSCENE_MODEL_REASONING_BUDGET` 和 `MIDSCENE_MODEL_REASONING_EFFORT`。 目前支持 `MIDSCENE_MODEL_REASONING_ENABLED` 的模型系列及参数映射如下: - Qwen:对应 `enable_thinking`。 - 豆包:对应 `thinking.type`。 - DeepSeek:对应 `thinking.type`。 - 智谱 GLM:对应 `thinking.type`。 - GPT-5:对应 `reasoning_effort`。启用时使用 `medium`,关闭时使用 `none`。 - Gemini:对应 `thinking_config.thinking_level`。启用时使用 `medium`,关闭时使用 `minimal`。 - Kimi K2 系列:对应 `thinking.type`。 - 小米 MiMo:对应 `thinking.type`。 `MIDSCENE_MODEL_REASONING_BUDGET` 控制模型的思考额度。目前 Qwen 系列支持该配置,对应 `thinking_budget`。 `MIDSCENE_MODEL_REASONING_EFFORT` 控制模型的思考力度。目前支持以下模型系列: - 豆包:对应 `reasoning_effort`。 - DeepSeek:对应 `reasoning_effort`。 - Gemini:对应 `thinking_config.thinking_level`。 - GPT-5:对应 `reasoning_effort`。 - Kimi K3 系列:对应 `reasoning_effort`。 不同模型服务商支持的参数和值不同。具体取值和适用模型版本请参考对应服务商的官方文档。如果当前模型不支持某项显式配置,Midscene 会忽略该配置,不会猜测服务商的私有参数。 ## 仍兼容的模型配置(不推荐) 以下环境变量已废弃但仍然兼容,建议尽快迁移到新的配置方式。 ### 旧版模型类型配置 | 名称 | 描述 | 新配置方式 | |------|-------------|-----------| | `MIDSCENE_USE_DOUBAO_VISION` | 已弃用。启用豆包视觉模型 | 使用 `MIDSCENE_MODEL_FAMILY="doubao-vision"` | | `MIDSCENE_USE_QWEN3_VL` | 已弃用。启用千问 Qwen3-VL 模型 | 使用 `MIDSCENE_MODEL_FAMILY="qwen3-vl"` | | `MIDSCENE_USE_QWEN_VL` | 已弃用。启用千问 Qwen2.5-VL 模型 | 使用 `MIDSCENE_MODEL_FAMILY="qwen2.5-vl"` | | `MIDSCENE_USE_GEMINI` | 已弃用。启用 Gemini 模型 | 使用 `MIDSCENE_MODEL_FAMILY="gemini"` | | `MIDSCENE_USE_VLM_UI_TARS` | 已弃用。启用 UI-TARS 模型 | 使用 `MIDSCENE_MODEL_FAMILY="vlm-ui-tars"` | ### 通用配置 | 名称 | 描述 | 新配置方式 | |------|-------------|-----------| | `OPENAI_API_KEY` | 已弃用但仍兼容 | 使用 `MIDSCENE_MODEL_API_KEY` | | `OPENAI_BASE_URL` | 已弃用但仍兼容 | 使用 `MIDSCENE_MODEL_BASE_URL` | | `MIDSCENE_OPENAI_INIT_CONFIG_JSON` | 已弃用但仍兼容 | 使用 `MIDSCENE_MODEL_INIT_CONFIG_JSON` | | `MIDSCENE_OPENAI_HTTP_PROXY` | 已弃用但仍兼容 | 使用 `MIDSCENE_MODEL_HTTP_PROXY` | | `MIDSCENE_OPENAI_SOCKS_PROXY` | 已弃用但仍兼容 | 使用 `MIDSCENE_MODEL_SOCKS_PROXY` | ## 调试与可观测性配置 以下配置用于启用模型诊断、Tracing 和本地调用记录。安装步骤、使用示例、问题排查和安全说明。请参考[模型调试与可观测性](./model-debugging-observability)。 ### Debug 日志 支持的 `DEBUG` 选择器和日志行为请参考[运行时配置:Debug 日志](./reference/#debug-logs)。 ### LangSmith | 名称 | 描述 | | --- | --- | | `MIDSCENE_LANGSMITH_DEBUG` | 设置为 `1`,启用 Midscene 的 LangSmith 自动集成 | | `LANGCHAIN_API_KEY` | LangSmith API Key | | `LANGCHAIN_TRACING` | 设置为 `true`,启用 LangSmith Tracing | | `LANGCHAIN_ENDPOINT` | LangSmith 服务地址 | ### Langfuse | 名称 | 描述 | | --- | --- | | `MIDSCENE_LANGFUSE_DEBUG` | 设置为 `1`,启用 Midscene 的 Langfuse 自动集成 | | `LANGFUSE_PUBLIC_KEY` | Langfuse Public Key | | `LANGFUSE_SECRET_KEY` | Langfuse Secret Key | | `LANGFUSE_BASE_URL` | Langfuse 服务地址 | ### 模型调用记录 | 名称 | 描述 | | --- | --- | | `MIDSCENE_RECORD_MODEL_CALL` | 设置为 `true`,将模型请求、响应和流式 Chunk 写入本地 JSONL 文件 | --- url: /zh/model-debugging-observability.md --- # 模型调试与可观测性 本文介绍如何排查模型连接和兼容性问题、观察延迟和 Token 使用量、采集 Trace,以及记录模型调用。 ## 验证模型连接 本节提供两种验证方法。先直接请求模型服务,确认模型 API 可以连接。再运行 Midscene 验证命令,检查模型兼容性。 ### 直接请求模型服务 以下 `curl` 请求用于检查 Base URL、API Key 和模型名称是否可用。该请求只验证模型 API 的基础连接。它不会检查模型是否满足 Midscene 的兼容性要求。 ```bash MIDSCENE_MODEL_BASE_URL='替换为你的 baseUrl' MIDSCENE_MODEL_API_KEY='替换为你的 API Key' MIDSCENE_MODEL_NAME='替换为你的 model name' curl -X POST "${MIDSCENE_MODEL_BASE_URL%/}/chat/completions" \ -H "Authorization: Bearer ${MIDSCENE_MODEL_API_KEY}" \ -H "Content-Type: application/json" \ -d '{ "model": "'"${MIDSCENE_MODEL_NAME}"'", "messages": [ { "role": "user", "content": "What is 1+1?" } ] }' ``` ### 使用 Midscene 验证命令 该命令同时检查模型连接和 Midscene 兼容性。 将模型配置放入 `.env` 文件,然后运行: ```bash # 如果当前项目已安装 @midscene/cli,可以使用本地的 midscene 命令 npx midscene model verify # 如果当前项目未安装 @midscene/cli,或需要使用最新版本 npx @midscene/cli@latest model verify ``` 该命令会读取当前工作目录下的 `.env` 文件。Dotenv 的 Debug 日志默认开启。`.env` 中的变量会覆盖已有的 Shell 环境变量。 如果 `curl` 请求成功,但 Midscene 验证命令失败,说明模型 API 可以连接。请继续检查模型能力和 Midscene 配置。 ## 常见配置错误 ### `MIDSCENE_MODEL_FAMILY` 未设置为多模态模型 如果收到 `MIDSCENE_MODEL_FAMILY is not set to a multimodal model with UI localization` 错误,请确认已正确配置多模态模型的 `MIDSCENE_MODEL_FAMILY` 环境变量。 从 1.0 版本开始,Midscene 推荐使用 `MIDSCENE_MODEL_FAMILY` 指定多模态模型类型。旧的 `MIDSCENE_USE_...` 配置仍然兼容,但已经废弃。 正确的模型 family 和完整配置示例请参考[支持的模型与配置](/zh/model-common-config.md)。 ### Base URL 或模型名称不正确 确认 `MIDSCENE_MODEL_BASE_URL` 指向服务商的 API 接入地址。该地址通常以 `/v1` 等版本号结尾。请勿添加 `/chat/completion`,底层 SDK 会自动添加请求路径。 同时确认 `MIDSCENE_MODEL_NAME` 与该接入地址提供的模型一致。 ## 模型效果不理想 如果模型可以正常连接,但定位、规划或页面理解不稳定,可以尝试以下方法: * 查看[回放报告](/zh/consume-report-file.md),确认任务执行顺序正确,并且没有进入错误页面或逻辑分支。 * 优先使用同一系列中较新的正式支持版本。 * 使用代表性任务对比不同服务商的模型,关注成功率、延迟和成本。 * 复杂任务可以单独配置 Planning 模型或 Insight 模型。具体分工请参考[模型策略](/zh/model-strategy.md)。 ## 调试能力 ### Debug 日志 需要额外的诊断信息时,可以设置 `DEBUG`。常用选择器包括: * `DEBUG=midscene:ai:profile:stats`:打印模型延迟和 Token 使用量。 * `DEBUG=midscene:ai:call`:打印 AI 响应详情。 * `DEBUG=midscene:*`:打印全部 Midscene Debug 日志。 完整的选择器列表、日志目录和使用注意事项,请参考[运行时配置:Debug 日志](/zh/reference.md#debug-logs)。 生成的[报告文件](/zh/consume-report-file.md)中也包含模型使用量统计。 ### 记录模型调用 设置 `MIDSCENE_RECORD_MODEL_CALL=true`,可以将模型请求、响应和流式 Chunk 写入 JSONL 文件: ```text midscene_run/model-requests/<启动时间>-<pid>.jsonl ``` 每个进程生成一个文件,每行对应一个 JSON 事件。只有 Node.js 和 Electron 支持写入本地文件。浏览器和 Worker 不会写入本地文件。使用 Codex App Server 时,记录还会包含可获取的协议元数据。 每个事件的 `type` 为 `request`、`chunk`、`response` 或 `error`。事件还包含 `executionId`,用于关联同一个 execution ID 下的调用及其重试。对于 HTTP 模型请求,这个值也会通过 `x-midscene-execution-id` Header 发送。不属于报告 execution 的调用(例如连接检查)会使用带 `unscoped-` 前缀的生成 ID。 :::warning 注意事项 文件包含请求 Body(包括自定义 `extraBody`)、响应 Header 和 Body、流式响应,以及可能采用 Base64 编码的截图。请求 Header 不会被记录。 这些文件可能包含敏感信息,且体积较大。请仅在排查问题时启用记录,并在使用后妥善保管或删除文件。记录格式不保证跨版本兼容。 ::: ### 请求追踪 Header Midscene 会自动为 OpenAI-compatible HTTP 模型请求添加以下 Header: | Header | 值 | 用途 | | --- | --- | --- | | `x-midscene-version` | 当前 `@midscene/core` 版本 | 标识发起请求的 Midscene 版本 | | `x-midscene-execution-id` | 当前 execution ID | 关联同一个 execution ID 下的全部模型请求及其重试,例如一次 `aiAct` 调用 | 不属于报告 execution 的调用(例如连接检查)会使用带 `unscoped-` 前缀的生成 execution ID。这两个 Header 会被默认发送,如果 `MIDSCENE_*_INIT_CONFIG_JSON` 中自定义了同名 Header,则会被 Midscene 覆盖。 ## 可观测性平台 ### LangSmith LangSmith 是用于调试大语言模型的平台。安装依赖并设置环境变量后,Midscene 可以自动接入 LangSmith。 **安装依赖** ```bash npm install langsmith ``` **设置环境变量** ```bash # 启用 Midscene 的 LangSmith 自动集成 export MIDSCENE_LANGSMITH_DEBUG=1 # LangSmith 配置 export LANGCHAIN_API_KEY="your-langchain-api-key-here" export LANGCHAIN_TRACING=true export LANGCHAIN_ENDPOINT="https://api.smith.langchain.com" # export LANGCHAIN_ENDPOINT="https://eu.api.smith.langchain.com" # 如果在欧洲区域注册 ``` 启动 Midscene 后,应该会看到类似以下内容的日志: ```log DEBUGGING MODE: langsmith wrapper enabled ``` 注意事项: * LangSmith 和 Langfuse 可以同时启用。 * 该集成仅支持 Node.js。浏览器环境会抛出错误。 * 如果使用 [`createOpenAIClient`](/zh/reference.md#自定义-openai-客户端),它会覆盖通过环境变量启用的自动集成。 如需进行更细粒度的控制,例如只对特定任务启用 LangSmith,请使用 [`createOpenAIClient`](/zh/reference.md#自定义-openai-客户端) 手动包装客户端。 ### Langfuse [Langfuse](https://langfuse.com) 是一个 LLM 可观测性平台。Midscene 集成了 Langfuse 的 `observeOpenAI` wrapper,可以自动追踪 OpenAI API 调用。 Langfuse 的追踪基于 OpenTelemetry,因此需要在应用启动时初始化 OpenTelemetry SDK。 **安装依赖** ```bash npm install @langfuse/openai @langfuse/otel @opentelemetry/sdk-node ``` **初始化 OpenTelemetry** 在应用入口文件的最顶部添加以下代码: ```typescript import { NodeSDK } from "@opentelemetry/sdk-node"; import { LangfuseSpanProcessor } from "@langfuse/otel"; const sdk = new NodeSDK({ spanProcessors: [new LangfuseSpanProcessor()], }); sdk.start(); ``` **设置环境变量** ```bash # 启用 Midscene 的 Langfuse 自动集成 export MIDSCENE_LANGFUSE_DEBUG=1 # Langfuse 配置 export LANGFUSE_PUBLIC_KEY="your-langfuse-public-key-here" export LANGFUSE_SECRET_KEY="your-langfuse-secret-key-here" export LANGFUSE_BASE_URL="https://cloud.langfuse.com" # 🇪🇺 欧洲区域 # export LANGFUSE_BASE_URL="https://us.cloud.langfuse.com" # 🇺🇸 美国区域 ``` 启动 Midscene 后,应该会看到类似以下内容的日志: ```log OpenTelemetry SDK initialized for Langfuse tracing DEBUGGING MODE: langfuse wrapper enabled ``` 更多配置和最佳实践请参考 [Langfuse OpenAI 集成文档](https://langfuse.com/integrations/model-providers/openai-js)。 注意事项: * LangSmith 和 Langfuse 可以同时启用。 * 该集成仅支持 Node.js。浏览器环境会抛出错误。 * 如果使用 [`createOpenAIClient`](/zh/reference.md#自定义-openai-客户端),它会覆盖通过环境变量启用的自动集成。 ## 安全注意事项 * 不要将 `.env` 文件、Trace、Debug 日志或模型调用记录提交到源码仓库。 * 日志和 Trace 可能包含模型输入、输出或截图,分享前请先检查内容。 --- url: /zh/model-strategy.md --- # 模型策略 Midscene 的模型策略有两个目标。一是用纯视觉理解用户实际看到的界面,让 UI 自动化不受渲染技术限制。二是提供可选的多种模型组合能力,应对复杂场景。 ## 基于可见界面的纯视觉路线 大模型驱动的 UI 自动化需要完成任务规划和元素定位。业界主要有两种定位方式:结合 DOM 与截图标注,或直接基于截图进行纯视觉定位。Midscene 采用纯视觉路线——由模型直接分析界面截图来定位目标元素,执行 UI 操作和元素定位时不依赖 DOM 或额外标注。 这一选择让用户看到的界面成为自动化的唯一依据,为 Midscene 带来以下优势: * 以一致的方式操作浏览器 Canvas、Android、iOS 和桌面应用等不同类型的界面。 * 摆脱 UI 渲染技术栈的限制,无需维护 Selector 或额外的界面标注。 * 校验颜色、高亮状态和布局等用户实际看到的效果。 * 更接近真实用户操作软件的方式,发现潜在的交互设计问题 * Token 消耗量只与页面分辨率和任务复杂度相关,不会随着页面结构(如 DOM 数量)膨胀而膨胀 使用视觉路径也有明确的不足。纯视觉定位要求模型具备视觉理解能力,只能使用指定的、对 GUI 操作比较稳定的模型,而不是任意 LLM。Midscene 用更高的模型能力要求,换取跨平台的一致性和更低的界面维护成本。 Midscene 在数据提取或页面理解场景中,仍可按需附带 DOM 信息。具体参数请参考 [API Reference](/zh/reference.md#extraction-location-assertion)。 <span id="高阶特性多模型配合" /> ## 组合多种模型应对复杂场景 Midscene 默认使用一个多模态模型,完成任务规划、元素定位和页面理解等工作。这条默认路线可以覆盖大多数 UI 自动化场景,也让用户用最少的配置开始使用 Midscene。 当复杂规划、数据提取或页面理解对模型能力有更高要求时,可以在默认模型的基础上增加 Planning 模型和界面理解(Insight)模型。不同模型按照各自擅长的能力分工,共同完成一条自动化任务链路: | 模型 | 在组合中的作用 | | --- | --- | | 默认模型 | 提供基础能力,负责元素定位(Locate),以及未交给 Planning 或 Insight 模型的其他任务 | | Planning 模型 | 增强复杂目标、多步骤任务和分支场景中的规划能力 | | Insight 模型 | 增强数据提取、断言和页面理解能力 | 这种协作方式可以扩展 Midscene 处理复杂任务的能力,但也可能增加任务耗时和 Token 消耗。建议从默认模型开始,仅在遇到明确的能力瓶颈时引入专用模型。 ## 下一步 本文只介绍 Midscene 的模型理念和选择原则。配置方法请参考[可选:配置多个模型](/zh/model-common-config.md#multi-model-combination-example)。如果任务效果不理想,请参考[模型调试与可观测性](/zh/model-debugging-observability.md)定位问题。 --- url: /zh/platforms/android.md --- import { PackageManagerTabs } from '@theme'; # Android Midscene 通过 adb 连接 Android 设备,可自动化 App 和系统界面。 本指南介绍设备连接、模型配置、Playground 体验,以及 `@midscene/android` 的 JavaScript SDK 集成。 ## 效果展示 **提示词:** 打开懂车帝,搜索 SU7 车型,查看参数配置。 <video src="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/su72.mp4" poster="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/su7.png" height="300" controls /> 查看[完整报告](https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/su7.html),或浏览更多 [Midscene 案例](/zh/showcases.md)。 ## 快速开始 ### 准备 Android 设备 在编写脚本前,先确认 adb 能够连接设备且设备信任当前电脑。 #### 安装 adb 并设置 `ANDROID_HOME` * 通过 [Android Studio](https://developer.android.com/studio) 或 [命令行工具](https://developer.android.com/studio#command-line-tools-only) 安装 adb * 验证安装是否成功: ```bash adb --version ``` 出现类似输出表示安装成功: ```log Android Debug Bridge version 1.0.41 Version 34.0.4-10411341 Installed as /usr/local/bin//adb Running on Darwin 24.3.0 (arm64) ``` * 按 [Android environment variables](https://developer.android.com/tools/variables) 设置 `ANDROID_HOME`,并验证: ```bash echo $ANDROID_HOME ``` 有输出即代表配置成功: ```log /Users/your_username/Library/Android/sdk ``` #### 启用 USB 调试并验证设备 在系统设置的开发者选项中开启 **USB 调试**(若有 **USB 调试(安全设置)** 也请一并开启),然后用数据线连接手机。 <p align="center"> <img src="/android-usb-debug-en.png" alt="android usb debug" width="400" /> </p> 验证连接: ```bash adb devices -l ``` 出现类似输出代表连接成功: ```log List of devices attached s4ey59 device usb:34603008X product:cezanne model:M2006J device:cezan transport_id:3 ``` ### 启动 Playground Playground 是验证连接的最快方式。无需编写代码,即可体验 `aiAct`、`aiQuery` 和 `aiAssert` 等核心能力。它与 `@midscene/android` 共享相同的代码实现,因此在 Playground 上验证通过的流程,用脚本运行时也完全一致。 1. 启动 Playground CLI: ```bash npx --yes @midscene/android-playground ``` 2. 点击 Playground 窗口中的齿轮按钮,粘贴你的 API Key 配置。如果还没有模型配置,请参考[支持的模型与配置](/zh/model-common-config.md)。 ![](/android-set-env.png) ## 使用 JavaScript SDK 当 Playground 运行正常后,就可以切换到可复用的 JavaScript 脚本。 ### 配置模型 下面以豆包 Seed 2.1 Turbo 为例: ```bash export MIDSCENE_MODEL_BASE_URL="https://ark.cn-beijing.volces.com/api/v3" export MIDSCENE_MODEL_API_KEY="your-api-key" export MIDSCENE_MODEL_NAME="doubao-seed-2-1-turbo-260628" export MIDSCENE_MODEL_FAMILY="doubao-seed" ``` 将 `your-api-key` 替换为你的 API Key。 > 如需使用千问(Qwen)、GLM、Gemini 或 GPT-5 等其他模型,请参考[支持的模型与配置](/zh/model-common-config.md)。 全部配置项请参考[模型配置](/zh/model-config.md)。 ### 安装依赖 <PackageManagerTabs command="install @midscene/android dotenv --save-dev" /> ### 编写脚本 下面的示例会在设备上打开浏览器、搜索 eBay,并断言结果列表。 ```typescript title="./demo.ts" import 'dotenv/config'; // 通过 dotenv/config 自动加载 .env 文件中的环境变量 import { AndroidAgent, AndroidDevice, getConnectedDevices, } from '@midscene/android'; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); Promise.resolve( (async () => { const devices = await getConnectedDevices(); const device = new AndroidDevice(devices[0].udid); const agent = new AndroidAgent(device, { aiActionContext: 'If any location, permission, user agreement, etc. popup, click agree. If login page pops up, close it.', }); await device.connect(); await agent.aiAct('open browser and navigate to ebay.com'); await sleep(5000); await agent.aiAct('type "Headphones" in search box, hit Enter'); await agent.aiWaitFor('There is at least one headphone product'); const items = await agent.aiQuery( '{itemTitle: string, price: Number}[], find item in list and corresponding price', ); console.log('headphones in stock', items); await agent.aiAssert('There is a category filter on the left'); })(), ); ``` ### 运行脚本 ```bash npx tsx demo.ts ``` 脚本运行结束后,你应该能在控制台看到 `Midscene - report file updated: /path/to/report/some_id.html`。在浏览器中打开生成的 HTML 文件,即可回放每次交互、查询和断言。 ## 进阶 本节介绍如何自定义设备行为、把 Midscene 接入独立框架,以及排查 adb 问题。更多构造函数参数位于 [API 参考的 Android 章节](/zh/reference.md#android)。 ### 扩展 Android 上的 Midscene 使用 `defineAction()` 定义自定义动作。构造 `AndroidDevice` 时,通过 `customActions` 传入这些动作。Midscene 会把这些动作追加到规划器中,让 Agent 可以调用你定义的领域特定动作。 ```typescript import { getMidsceneLocationSchema, z } from '@midscene/core'; import { defineAction } from '@midscene/core/device'; import { AndroidAgent, AndroidDevice, getConnectedDevices } from '@midscene/android'; 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); }, }); const devices = await getConnectedDevices(); const device = new AndroidDevice(devices[0].udid, { customActions: [ContinuousClick], }); await device.connect(); const agent = new AndroidAgent(device); await agent.aiAct('click the red button five times'); ``` 关于自定义动作和动作 Schema 的更多解释,请参阅 [与任意界面集成](/zh/integrate-with-any-interface.md#define-a-custom-action)。 ## 常见问题 ### 为什么连接了设备,但仍然无法控制? 一个典型的错误信息是: ``` Error: Exception occurred while executing 'tap': java.lang.SecurityException: Injecting input events requires the caller (or the source of the instrumentation, if any) to have the INJECT_EVENTS permission. ``` 请在系统设置的开发者选项中确认以下选项已开启: 1. **USB 调试** 2. **USB 调试(安全设置)**(如果存在) <p align="center"> <img src="/android-usb-debug.png" alt="android usb debug" width="400" /> </p> ### 输入文本后,输入框内容被清空或消失 Midscene 在输入文本后会自动隐藏键盘,默认行为是发送 **ESC 按键事件**。然而,部分输入框(尤其是 WebView 中的输入框)会监听 ESC 按键事件,导致以下副作用: * 清空刚输入的文本 * 关闭包含输入框的弹窗或模态框 * 导航离开当前页面 你可以按以下优先级逐步尝试解决: **方案一:改用 BACK 键(Android 返回键)隐藏键盘** 将 `keyboardDismissStrategy` 设为 `'back-first'`,用 Android BACK 键替代 ESC 键来隐藏键盘: ```typescript const device = new AndroidDevice('device-id', { keyboardDismissStrategy: 'back-first', }); ``` **方案二:关闭自动隐藏键盘** 如果你的输入框同时监听了 BACK 键,可以彻底关闭自动隐藏键盘,由 AI Agent 或后续操作自行管理键盘状态: ```typescript const device = new AndroidDevice('device-id', { autoDismissKeyboard: false, }); ``` 关闭后键盘不会自动隐藏,可能导致键盘覆盖大量屏幕区域。你可以通过以下方式应对: * 使用 `aiAct` 指令手动隐藏键盘,例如 `await agent.aiAct('点击键盘上的收起按钮')` * 安装并切换到 [ADBKeyBoard](https://github.com/senzhk/ADBKeyBoard)——这是一款极小面积的虚拟键盘,即使不隐藏也几乎不影响屏幕操作 ### 英文文本被 Android 输入法改写 如果报告中显示的输入参数是正确的,但 App 实际收到的是不同文本、缺字,或被改成中文/拼音候选词,通常是当前 Android 输入法改写了输入内容。纯 ASCII 文本如果走原生 `adb input text` 路径,在中文输入法或带自动纠错的输入法下就可能出现这个问题。 使用已有的 `imeStrategy` 选项,将所有文本输入强制改为 yadb: ```typescript const device = new AndroidDevice('device-id', { imeStrategy: 'always-yadb', }); ``` YAML 脚本中可以这样配置: ```yaml android: imeStrategy: always-yadb ``` 也可以通过环境变量设置: ```bash export MIDSCENE_ANDROID_IME_STRATEGY=always-yadb ``` 这和“输入后被清空”不是同一个问题。如果文本先正确输入、随后消失,请优先检查 `keyboardDismissStrategy` 或 `autoDismissKeyboard`。 ### 安全页面(如密码输入框)截图黑屏 部分页面(如银行、支付类 App 的密码输入页)会通过 `FLAG_SECURE` 禁止截屏。此时使用 `screencap` 截出的图片中,安全区域会显示为黑色。 yadb 工具通过 `SurfaceControl.createDisplay` 的 `secure` 参数创建虚拟显示器,在某些环境下可以截取安全页面内容。能否截取 `FLAG_SECURE` 页面取决于 Android 版本、ROM、root/hook 环境和设备配置(例如某些环境可能需要配合 root 和 Magisk hook)。请以实际设备测试结果为准。 使用 `screenshotStrategy` 选项,将截图方式强制改为 yadb: ```typescript const device = new AndroidDevice('device-id', { screenshotStrategy: 'always-yadb', }); ``` YAML 脚本中可以这样配置: ```yaml android: screenshotStrategy: always-yadb ``` 也可以通过环境变量设置: ```bash export MIDSCENE_ANDROID_SCREENSHOT_STRATEGY=always-yadb ``` 默认值为 `auto`,即先使用 `adb.takeScreenshot`,失败后回退到 shell `screencap`;如果 `screencap` 执行失败,则改用 yadb 工具。scrcpy 仅当 `scrcpyConfig.enabled` 开启时才会在这些方法之前优先尝试。`auto` 不会分析截图内容:即使某个截图方法执行成功但返回全黑图片,也不会自动切换到 yadb;只有当前一种截图方法执行失败时,才会尝试下一种方法。安全页面生成有效但全黑的图片时,请设置 `always-yadb`,绕过默认的 `auto` 截图流程(`adb.takeScreenshot`、`screencap`,以及开启时的 scrcpy),直接使用 yadb 截图。yadb 只能截取默认显示器(`displayId=0`);如果同时设置 `always-yadb` 和非零 `displayId`,Midscene 会抛出错误。 ### 为什么 scrcpy 会回退到 ADB 截图? Midscene 会拒绝无法证明拍摄时间晚于最近一次已完成操作,或不满足绝对帧龄限制的 scrcpy 帧。如果已有视频流无法提供有效帧,Midscene 会自动重启一次 scrcpy;只有新视频流仍然失败时,才会回退到 ADB 截图。 部分 Android 编码器会在画面静止时停止输出帧,视频流启动或传输中断也可能导致此现象。仅凭 freshness 告警无法判断链路带宽不足或视频码率过高,请勿只因为出现该告警就调整码率。 ### 如何配置 scrcpy 视频码率? 在 Android CLI 命令中使用 `--scrcpy-video-bit-rate <bits-per-second>`(或 `--scrcpyVideoBitRate`)。设置该参数本身就会启用 scrcpy,因此可以省略 `--use-scrcpy`: ```bash midscene-android tap \ --device-id <device-id> \ --locate '{"prompt":"目标元素"}' \ --scrcpy-video-bit-rate <bits-per-second> ``` 每条 CLI 命令都是独立进程,因此每条需要覆盖默认码率的命令都要传入该参数。使用 JavaScript SDK 或 YAML 时,只需在对应设备配置中设置一次 `scrcpyConfig.videoBitRate`。调整码率是在编码带宽和截图细节之间取舍;只有独立的链路测量表明有需要时才应调优,并验证截图与识别质量。默认值和相关选项见 [`AndroidDevice` scrcpy 参考](/zh/reference/index.md#scrcpy)。 ### 如何使用自定义的 adb 路径或远程 adb 服务器? 通过环境变量设置: ```bash export MIDSCENE_ADB_PATH=/path/to/adb export MIDSCENE_ADB_REMOTE_HOST=192.168.1.100 export MIDSCENE_ADB_REMOTE_PORT=5037 ``` 也可以通过 AndroidDevice 构造函数传入: ```typescript const device = new AndroidDevice('s4ey59', { androidAdbPath: '/path/to/adb', remoteAdbHost: '192.168.1.100', remoteAdbPort: 5037, }); ``` ## 更多 * 查看所有 Agent 方法:[API 参考(通用)](/zh/reference.md#interaction-methods) * Android 专属参数与接口:[API 参考(Android)](/zh/reference.md#android) * 使用 [YAML 自动化脚本和命令行工具](/zh/automate-with-scripts-in-yaml.md)。 * 示例项目 * Android JavaScript SDK 示例:[https://github.com/web-infra-dev/midscene-example/blob/main/android/javascript-sdk-demo](https://github.com/web-infra-dev/midscene-example/blob/main/android/javascript-sdk-demo) * Android + Vitest 示例:[https://github.com/web-infra-dev/midscene-example/tree/main/android/vitest-demo](https://github.com/web-infra-dev/midscene-example/tree/main/android/vitest-demo) --- url: /zh/platforms/desktop.md --- import { PackageManagerTabs } from '@theme'; # 桌面端 Midscene 通过原生键盘和鼠标控制,在 Windows、macOS 和 Linux 上自动化桌面应用。它支持鼠标与键盘输入、屏幕截图和多显示器。 它适用于测试 Electron、Qt 和原生应用,也可以自动化跨应用工作流。 本指南介绍平台配置、模型配置、Playground 体验,以及 `@midscene/computer` 的 JavaScript SDK 集成。 ## 效果展示 **提示词(macOS):** 打开 Safari,发布一条介绍 Midscene 支持 AutoGLM 的推文,并使用“下载”文件夹中的 AutoGLM 视频。 <video src="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/pc-twitter2.mp4" height="300" controls /> 查看[完整报告](https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/pc-twitter2-midscene_report.html),或浏览更多 [Midscene 案例](/zh/showcases.md)。 ## 快速开始 ### 准备桌面环境 #### Node.js 需要 Node.js 18.19.0 或更高版本。 #### 平台特定依赖 **macOS**:需要辅助功能权限才能控制键盘和鼠标。首次运行脚本时,macOS 会提示你授予访问权限。前往 **系统设置 > 隐私与安全性 > 辅助功能**,为运行脚本的应用程序(如 Terminal、iTerm2、VS Code、WebStorm 或其他 IDE)启用权限。更多详情请参阅 [nut.js macOS 设置](https://github.com/nut-tree/nut.js#macos)。 **Windows**:操作普通程序无需额外配置。但 Windows 会按权限级别隔离输入(UIPI):未提权的进程**无法**向以**管理员身份运行(已提权)**的窗口发送鼠标或键盘输入,输入会被静默丢弃——光标仍会移动到正确位置,但点击和按键不生效。优先尝试让目标程序不要以管理员权限运行;如果目标程序必须保持提权状态,再**同样以管理员身份运行启动 Midscene 的终端或 Node.js**,让两个进程处于相同的权限级别。详见 [Windows:点击对某些程序不生效](#windows点击对某些程序不生效)。 **Linux**:需要安装 [ImageMagick](https://imagemagick.org/script/download.php) 用于截图功能。 **无头 Linux(CI 环境)**:要在无头 Linux 服务器(如 GitHub Actions)上运行桌面自动化,需安装 Xvfb 及其依赖,然后启用 headless 模式: ```bash # 安装依赖 sudo apt-get install -y xvfb x11-xserver-utils imagemagick ``` ```typescript // 方式 1:传入 headless 选项 const agent = await agentForComputer({ headless: true }); // 方式 2:设置环境变量 // MIDSCENE_COMPUTER_HEADLESS_LINUX=true npx tsx example.ts ``` Xvfb 会创建一个虚拟显示器,使鼠标、键盘和截图操作在没有物理显示器的情况下正常工作。详见 [API 参考](/zh/reference.md#desktop)。 ### 启动 Playground Playground 是验证连接的最快方式。无需编写代码,即可体验 `aiAct`、`aiQuery` 和 `aiAssert` 等核心能力。它与 `@midscene/computer` 共享相同的核心,因此在 Playground 中通过的流程,在脚本中运行会保持一致。 1. 启动 Playground CLI: ```bash npx --yes @midscene/computer-playground ``` 2. 点击 Playground 窗口中的齿轮按钮,粘贴你的 API Key 配置。如果还没有模型配置,请参考[支持的模型与配置](/zh/model-common-config.md)。 ## 使用 JavaScript SDK 当 Playground 运行正常后,就可以切换到可复用的 JavaScript 脚本。 ### 配置模型 下面以豆包 Seed 2.1 Turbo 为例: ```bash export MIDSCENE_MODEL_BASE_URL="https://ark.cn-beijing.volces.com/api/v3" export MIDSCENE_MODEL_API_KEY="your-api-key" export MIDSCENE_MODEL_NAME="doubao-seed-2-1-turbo-260628" export MIDSCENE_MODEL_FAMILY="doubao-seed" ``` 将 `your-api-key` 替换为你的 API Key。 > 如需使用千问(Qwen)、GLM、Gemini 或 GPT-5 等其他模型,请参考[支持的模型与配置](/zh/model-common-config.md)。 全部配置项请参考[模型配置](/zh/model-config.md)。 ### 安装依赖 <PackageManagerTabs command="install @midscene/computer" /> ### 编写脚本 创建 `example.ts`: ```typescript import { agentForComputer } from '@midscene/computer'; (async () => { // 创建 agent const agent = await agentForComputer({ aiActionContext: '你正在控制一台桌面计算机。', }); // 截图并查询信息 const screenInfo = await agent.aiQuery( '{width: number, height: number}, 获取屏幕分辨率' ); console.log('屏幕分辨率:', screenInfo); // 移动鼠标到中心 await agent.aiAct('将鼠标移动到屏幕中心'); // 断言屏幕有内容 await agent.aiAssert('屏幕有可见内容'); console.log('桌面自动化完成!'); })(); ``` ### 运行脚本 ```bash npx tsx example.ts ``` 脚本运行结束后,你应该能在控制台看到 `Midscene - report file updated: /path/to/report/some_id.html`。在浏览器中打开生成的 HTML 文件,即可回放每次交互、查询和断言。 ## 自定义动作 使用 `defineAction()` 定义自定义动作。使用 `agentForComputer()` 构造 Agent 时,通过 `customActions` 传入这些动作。Midscene 会把这些动作追加到规划器中,让 Agent 可以调用你定义的领域特定动作。 ```typescript import { getMidsceneLocationSchema, z } from '@midscene/core'; import { defineAction } from '@midscene/core/device'; import { agentForComputer } from '@midscene/computer'; 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) { console.log('click target center', param.locate.center); console.log('click count', param.count); // Carry out your clicking logic using locate + count. }, }); const agent = await agentForComputer({ customActions: [ContinuousClick], }); await agent.aiAct('click the red button five times'); ``` ## 通过 RDP 连接远程 Windows 桌面 `@midscene/computer` 也可以通过专用的 `agentForRDPComputer()` 工厂,直接经由 RDP 协议控制远程 Windows 桌面。 ### 前提条件 1. 一台已开启 RDP 且网络可达的 Windows 机器。 2. 在运行脚本的机器上安装 [FreeRDP](https://www.freerdp.com/)。 ### 示例 ```typescript import { agentForRDPComputer } from '@midscene/computer'; const agent = await agentForRDPComputer({ aiActionContext: 'You are controlling a remote Windows desktop over the RDP protocol.', host: '10.75.166.249', port: 3389, username: 'Admin', password: 'replace-with-your-password', ignoreCertificate: true, }); await agent.aiWaitFor('The remote Windows desktop is visible'); await agent.aiAct('Click the Windows Start button'); await agent.aiAct('Open Settings'); await agent.aiAssert('The Windows Settings window is visible'); ``` ### 常用 RDP 选项 * `host`:远程 Windows 主机名或 IP。 * `port`:RDP 端口,默认是 `3389`。 * `username` / `password`:远程会话使用的账号凭据。 * `domain`:可选的 Windows 域。 * `ignoreCertificate`:用于跳过自签名证书校验。 * `desktopWidth` / `desktopHeight`:请求指定远程桌面分辨率。 * `adminSession`:当服务端允许时,请求远程管理员会话。 对于 Midscene 来说,一个 RDP 会话会被视为单个远程显示器。你仍然可以像本机桌面自动化一样,使用 `aiAct`、`aiQuery`、`aiAssert` 和报告能力。 ## 多显示器支持 如果您有多个显示器,可以控制特定的显示器: ```typescript import { ComputerDevice, agentForComputer } from '@midscene/computer'; // 列出所有显示器 const displays = await ComputerDevice.listDisplays(); console.log('可用显示器:', displays); // 连接到特定显示器 const agent = await agentForComputer({ displayId: displays[0].id, }); ``` ## 使用示例 ### 基本鼠标操作 ```typescript // 在屏幕中心点击 await agent.aiAct('在屏幕中心点击鼠标'); // 移动鼠标到特定位置 await agent.aiAct('将鼠标移动到左上角'); // 双击 await agent.aiAct('双击桌面图标'); // 右键 await agent.aiAct('右键打开上下文菜单'); ``` ### 键盘操作 ```typescript // 输入文本 await agent.aiAct('输入 "你好世界"'); // 按快捷键 if (process.platform === 'darwin') { await agent.aiAct('按 Cmd+Space 打开 Spotlight'); await agent.aiAct('输入 "计算器" 并按回车'); } else { await agent.aiAct('按 Windows 键'); await agent.aiAct('输入 "计算器" 并按回车'); } // 按功能键 await agent.aiAct('按 Escape'); await agent.aiAct('按 Enter'); ``` ### 查询信息 ```typescript // 提取屏幕信息 const info = await agent.aiQuery( '{hasDesktop: boolean, visibleApps: string[]}, 检查桌面是否可见并列出可见应用' ); // 定位元素 const position = await agent.aiLocate('文件菜单'); console.log('文件菜单位置:', position); ``` ### 复杂工作流 ```typescript // 打开应用并与之交互 await agent.aiAct('打开访达'); await agent.aiWaitFor('访达窗口可见'); await agent.aiAct('点击文稿文件夹'); await agent.aiAct('按 Cmd+N 创建新文件夹'); await agent.aiAct('输入 "我的项目"'); await agent.aiAct('按回车'); await agent.aiAssert('存在名为 "我的项目" 的文件夹'); ``` ## 环境检查 您可以检查系统是否正确配置: ```typescript import { checkComputerEnvironment } from '@midscene/computer'; const env = await checkComputerEnvironment(); console.log('平台:', env.platform); console.log('可用:', env.available); console.log('显示器数量:', env.displays); if (!env.available) { console.error('环境不可用:', env.error); } ``` ## 常见问题 ### macOS:脚本无法控制鼠标或键盘 macOS 需要辅助功能权限才能控制键盘和鼠标。请前往 **系统设置 > 隐私与安全性 > 辅助功能**,为运行脚本的应用程序(如 Terminal、iTerm2、VS Code 或 WebStorm)开启权限。 如果已经授权但仍然无法控制,可以尝试将该应用从辅助功能列表中移除后重新添加——macOS 有时会缓存过期的权限。 ### Windows:点击对某些程序不生效 如果光标能移动到正确位置,但对某个程序点击或按键毫无反应,而其他程序却正常,请检查目标程序是否以**管理员身份运行(已提权)**。Windows 的 UIPI 机制会拦截由低权限进程注入到高权限窗口的输入,并静默丢弃,且不报任何错误。 优先尝试降低目标程序的权限级别,例如不要使用“以管理员身份运行”启动它,或关闭总是提权启动的相关设置。如果目标程序必须保持提权状态,再**以管理员身份运行**启动 Midscene 的终端(或 Node.js),使其与目标程序处于相同的权限级别,然后重试。像 `Win+Tab` 这类系统级快捷键由 shell 处理,即使在这种情况下也照常生效——这也是为什么有时键盘快捷键看起来能用、但程序内的点击却不行。 > 仅当光标已经到达正确位置时,才需要排查管理员权限。Midscene 连接 Windows 时会执行健康检查。如果 Midscene 未以管理员身份运行,健康检查日志会显示本节链接。 ### Linux:在无头服务器上截图或交互失败 无头 Linux 环境(如 CI)没有物理显示器,需要安装 Xvfb 和 ImageMagick,并启用无头模式: ```bash sudo apt-get install -y xvfb x11-xserver-utils imagemagick ``` ```typescript const agent = await agentForComputer({ headless: true }); ``` 或通过环境变量设置: ```bash MIDSCENE_COMPUTER_HEADLESS_LINUX=true npx tsx example.ts ``` ## 更多 * [API 参考](/zh/reference.md#desktop) * [使用 YAML 格式自动化脚本](/zh/automate-with-scripts-in-yaml.md) * [YAML 脚本运行器](/zh/yaml-script-runner.md) * [缓存提高效率](/zh/caching.md) * 示例项目 * [JavaScript SDK 示例](https://github.com/web-infra-dev/midscene-example/tree/main/computer/javascript-sdk-demo) * [Vitest 示例](https://github.com/web-infra-dev/midscene-example/tree/main/computer/vitest-demo) * [通过 RDP 控制远程 Windows 桌面](https://github.com/web-infra-dev/midscene-example/tree/main/computer/rdp-demo) * [在无头 Linux CI 中测试 Obsidian](https://github.com/web-infra-dev/midscene-example/tree/main/computer/electron-demo) --- url: /zh/platforms/harmonyos.md --- import { PackageManagerTabs } from '@theme'; # HarmonyOS Midscene 通过 HarmonyOS Device Connector(HDC)连接 HarmonyOS NEXT 设备,可自动化 App 和系统界面。 本指南介绍设备连接、模型配置、Playground 体验,以及 `@midscene/harmony` 的 JavaScript SDK 集成。 ## 效果展示 **提示词:** 打开设置,找到“关于手机”,查看设备信息。 <video src="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/harmony.mp4" poster="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/harmony.png" height="300" controls /> 查看[完整报告](https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/harmony.html),或浏览更多 [Midscene 案例](/zh/showcases.md)。 ## 快速开始 ### 准备 HarmonyOS 设备 在编写脚本前,先确认 HDC 能够连接设备且设备信任当前电脑。 #### 安装 HDC HDC(HarmonyOS Device Connector)是 HarmonyOS 提供的命令行工具,用于与 HarmonyOS 设备通信。安装方式: * 通过 [DevEco Studio](https://developer.huawei.com/consumer/cn/deveco-studio/) 安装(推荐) * 通过 [HarmonyOS 命令行工具](https://developer.huawei.com/consumer/cn/download/) 单独安装 验证 HDC 是否安装成功: ```bash hdc version ``` 出现版本号表示安装成功。 :::info 配置 HDC 路径 如果 `hdc` 不在系统 PATH 中,你可以设置 `HDC_HOME` 环境变量指向 HDC 所在目录: ```bash export HDC_HOME=/path/to/hdc/directory ``` ::: #### 启用开发者模式并验证设备 在 HarmonyOS 设备的设置中进入 **开发者选项**,开启 **USB 调试**,然后用数据线连接设备。 验证连接: ```bash hdc list targets ``` 出现设备 ID 代表连接成功: ```log 0123456789ABCDEF ``` ### 启动 Playground Playground 是验证连接的最快方式。无需编写代码,即可体验 `aiAct`、`aiQuery` 和 `aiAssert` 等核心能力。它与 `@midscene/harmony` 共享相同的核心,因此在 Playground 中通过的流程,在脚本中运行会保持一致。 1. 启动 Playground CLI: ```bash npx --yes @midscene/harmony-playground ``` 2. 点击 Playground 窗口中的齿轮按钮,粘贴你的 API Key 配置。如果还没有模型配置,请参考[支持的模型与配置](/zh/model-common-config.md)。 ## 使用 JavaScript SDK 当 Playground 运行正常后,就可以切换到可复用的 JavaScript 脚本。 ### 配置模型 下面以豆包 Seed 2.1 Turbo 为例: ```bash export MIDSCENE_MODEL_BASE_URL="https://ark.cn-beijing.volces.com/api/v3" export MIDSCENE_MODEL_API_KEY="your-api-key" export MIDSCENE_MODEL_NAME="doubao-seed-2-1-turbo-260628" export MIDSCENE_MODEL_FAMILY="doubao-seed" ``` 将 `your-api-key` 替换为你的 API Key。 > 如需使用千问(Qwen)、GLM、Gemini 或 GPT-5 等其他模型,请参考[支持的模型与配置](/zh/model-common-config.md)。 全部配置项请参考[模型配置](/zh/model-config.md)。 ### 安装依赖 <PackageManagerTabs command="install @midscene/harmony dotenv --save-dev" /> ### 编写脚本 下面的示例会在设备上打开设置应用,并执行滚动操作。 ```typescript title="./demo.ts" import 'dotenv/config'; // 通过 dotenv/config 自动加载 .env 文件中的环境变量 import { HarmonyAgent, HarmonyDevice, getConnectedDevices, } from '@midscene/harmony'; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); Promise.resolve( (async () => { const devices = await getConnectedDevices(); const device = new HarmonyDevice(devices[0].deviceId, {}); const agent = new HarmonyAgent(device, { aiActionContext: '这是一台鸿蒙设备,系统语言为中文。如果出现弹窗,点击同意或关闭。', }); await device.connect(); // 打开设置应用 await agent.launch('com.huawei.hmos.settings'); await sleep(2000); // 向下滚动列表 await agent.aiAct('scroll down one screen'); // 查询页面内容 const items = await agent.aiQuery( 'string[], 列表中可见的所有设置项名称', ); console.log('设置项列表', items); // 断言 await agent.aiAssert('页面中有设置项列表'); })(), ); ``` ### 运行脚本 ```bash npx tsx demo.ts ``` 脚本运行结束后,你应该能在控制台看到 `Midscene - report file updated: /path/to/report/some_id.html`。在浏览器中打开生成的 HTML 文件,即可回放每次交互、查询和断言。 ## 进阶 本节介绍如何自定义设备行为、把 Midscene 接入独立框架,以及排查 HDC 问题。更多构造函数参数位于 [API 参考的 HarmonyOS 章节](/zh/reference.md#harmonyos)。 ### 扩展 HarmonyOS 上的 Midscene 使用 `defineAction()` 定义自定义动作。构造 `HarmonyDevice` 时,通过 `customActions` 传入这些动作。Midscene 会把这些动作追加到规划器中,让 Agent 可以调用你定义的领域特定动作。 ```typescript import { getMidsceneLocationSchema, z } from '@midscene/core'; import { defineAction } from '@midscene/core/device'; import { HarmonyAgent, HarmonyDevice, getConnectedDevices } from '@midscene/harmony'; 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); }, }); const devices = await getConnectedDevices(); const device = new HarmonyDevice(devices[0].deviceId, { customActions: [ContinuousClick], }); await device.connect(); const agent = new HarmonyAgent(device); await agent.aiAct('click the red button five times'); ``` 关于自定义动作和动作 Schema 的更多解释,请参阅 [与任意界面集成](/zh/integrate-with-any-interface.md#define-a-custom-action)。 ## 常见问题 ### 输入后键盘没有隐藏,或页面发生返回 Midscene 在输入文本后会自动隐藏键盘。HarmonyOS 默认发送 ESC,以减少触发页面返回的概率。如果你的应用里 ESC 无法关闭键盘,可以切换为优先使用 Back: ```typescript const device = new HarmonyDevice('device-id', { keyboardDismissStrategy: 'back-first', }); ``` 如果你的输入框监听了 Back 并执行清空或关闭操作,可以关闭自动隐藏键盘: ```typescript const device = new HarmonyDevice('device-id', { autoDismissKeyboard: false, }); ``` 关闭后键盘不会自动隐藏,你可以使用 `aiAct` 指令手动隐藏键盘,例如 `await agent.aiAct('隐藏键盘')`。 ### 如何使用自定义的 HDC 路径? 通过 `HDC_HOME` 环境变量指定 HDC 所在目录: ```bash export HDC_HOME=/path/to/hdc/directory ``` 也可以通过构造函数传入: ```typescript const device = new HarmonyDevice('0123456789ABCDEF', { hdcPath: '/path/to/hdc', }); ``` ## 更多 * 查看所有 Agent 方法:[API 参考(通用)](/zh/reference.md#interaction-methods) * HarmonyOS 专属参数与接口:[API 参考(HarmonyOS)](/zh/reference.md#harmonyos) * 使用 [YAML 自动化脚本和命令行工具](/zh/automate-with-scripts-in-yaml.md)。 * 示例项目 * HarmonyOS JavaScript SDK 示例:[https://github.com/web-infra-dev/midscene-example/blob/main/harmony/javascript-sdk-demo](https://github.com/web-infra-dev/midscene-example/blob/main/harmony/javascript-sdk-demo) * HarmonyOS + Vitest 示例:[https://github.com/web-infra-dev/midscene-example/tree/main/harmony/vitest-demo](https://github.com/web-infra-dev/midscene-example/tree/main/harmony/vitest-demo) --- url: /zh/platforms/index.md --- # 更多平台 除 Web 浏览器外,Midscene 还支持移动设备、HarmonyOS 设备和桌面应用。请根据目标界面和连接方式选择平台。 Midscene 使用多模态视觉模型理解截图,因此自动化过程面向最终呈现的界面,不依赖底层 UI 结构。同一套方法可以适配原生应用和跨平台技术栈。 ## 选择平台 | 平台 | npm 包 | 连接方式 | 典型用途 | | --- | --- | --- | --- | | [Android](/zh/platforms/android.md) | `@midscene/android` | adb | Android App 和系统界面 | | [iOS](/zh/platforms/ios.md) | `@midscene/ios` | WebDriverAgent | iOS App 和系统界面 | | [HarmonyOS](/zh/platforms/harmonyos.md) | `@midscene/harmony` | HDC | HarmonyOS NEXT App 和系统界面 | | [桌面端](/zh/platforms/desktop.md) | `@midscene/computer` | 原生输入或 RDP | Windows、macOS 和 Linux 应用 | 每篇平台指南都包含平台能力、环境准备、Playground、JavaScript 集成、示例和故障排查。 如果需要构造函数选项、平台专属方法或通用 Agent API,请阅读 [API 参考](/zh/reference.md)。 --- url: /zh/platforms/ios.md --- import { PackageManagerTabs } from '@theme'; # iOS Midscene 通过 WebDriverAgent 连接 iOS 设备,可自动化 App 和系统界面。 本指南介绍 WebDriverAgent 配置、模型配置、Playground 体验,以及 `@midscene/ios` 的 JavaScript SDK 集成。 ## 效果展示 **提示词:** 打开美团,帮我下单一杯 Manner 超大杯冰美式咖啡,选择加浓、少冰,并在进入结算页面后等待确认。 <video src="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/meituan2.mp4" poster="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/meituan.png" height="300" controls /> 查看[完整报告](https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/meituan.html),或浏览更多 [Midscene 案例](/zh/showcases.md)。 ## 快速开始 ### 准备 iOS 环境 WebDriver 是 W3C 制定的浏览器自动化标准协议。它提供统一的 API,用于控制不同的浏览器和应用。该协议定义了客户端与服务端之间的通信方式,使自动化工具可以跨平台控制界面。 Appium 团队和其他开源社区维护了多种 WebDriver 工具。这些工具可以把桌面端和移动端的自动化操作转换为 WebDriver 协议: * **Appium**:跨平台移动自动化框架。 * **WebDriverAgent**:用于 iOS 设备自动化的服务。 * **Selenium**:Web 浏览器自动化工具。 * **WinAppDriver**:Windows 应用自动化工具。 Midscene 支持 WebDriver 协议。你可以使用 AI 模型自动化任何兼容设备。除了点击和输入,Midscene 还可以理解界面上下文、执行多步骤操作、验证结果并提取数据。 在 iOS 上,Midscene 通过 WebDriverAgent 连接设备。连接后,你可以使用自然语言指令控制 iOS App 和系统界面。 继续之前,请确保 WebDriverAgent 可以与设备通信。 #### 安装 Node.js 安装 [Node.js 18 或以上版本](https://nodejs.org/en/download/)。 #### 配置 WebDriverAgent 在开始之前,你需要先设置 iOS 开发环境: * macOS(iOS 开发必需) * Xcode 和 Xcode 命令行工具 * iOS 模拟器或真机设备 **配置 WebDriverAgent** 在使用 Midscene iOS 之前,需要先准备 WebDriverAgent 服务。 :::note 版本要求 WebDriverAgent 版本需要 **>= 7.0.0** ::: 请参考官方文档进行设置: * **模拟器配置**:[Run Prebuilt WDA](https://appium.github.io/appium-xcuitest-driver/latest/guides/run-prebuilt-wda/) * **真机配置**:[Real Device Configuration](https://appium.github.io/appium-xcuitest-driver/latest/getting-started/device-setup/) **验证 WebDriverAgent** 配置完成后,可以通过访问 WebDriverAgent 的状态接口来验证 服务是否启动: **访问地址**:`http://localhost:8100/status` **正确响应示例**: ```json { "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 已经正确配置并运行。 ### 启动 Playground Playground 是验证连接的最快方式。无需编写代码,即可体验 `aiAct`、`aiQuery` 和 `aiAssert` 等核心能力。它与 `@midscene/ios` 共享相同的核心,因此在 Playground 中通过的流程,在脚本中运行会保持一致。 1. 启动 Playground CLI: ```bash npx --yes @midscene/ios-playground ``` 2. 点击窗口中的齿轮按钮进入配置页,粘贴你的 API Key 配置。如果还没有模型配置,请参考[支持的模型与配置](/zh/model-common-config.md)。 ## 使用 JavaScript SDK 当 Playground 工作正常后,就可以切换到可复用的 JavaScript 脚本。 ### 配置模型 下面以豆包 Seed 2.1 Turbo 为例: ```bash export MIDSCENE_MODEL_BASE_URL="https://ark.cn-beijing.volces.com/api/v3" export MIDSCENE_MODEL_API_KEY="your-api-key" export MIDSCENE_MODEL_NAME="doubao-seed-2-1-turbo-260628" export MIDSCENE_MODEL_FAMILY="doubao-seed" ``` 将 `your-api-key` 替换为你的 API Key。 > 如需使用千问(Qwen)、GLM、Gemini 或 GPT-5 等其他模型,请参考[支持的模型与配置](/zh/model-common-config.md)。 全部配置项请参考[模型配置](/zh/model-config.md)。 ### 安装依赖 <PackageManagerTabs command="install @midscene/ios dotenv --save-dev" /> ### 编写脚本 下面的示例会在设备上打开 Safari,搜索 eBay,并断言结果列表。 ```typescript title="./demo.ts" import 'dotenv/config'; // 通过 dotenv/config 自动加载 .env 文件中的环境变量 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: 'If any location, permission, user agreement, etc. popup appears, click agree. If login page appears, close it.', }); await page.connect(); // 方式二:使用便捷函数(推荐) // const agent = await agentFromWebDriverAgent({ // wdaPort: 8100, // wdaHost: 'localhost', // aiActionContext: 'If any location, permission, user agreement, etc. popup appears, click agree. If login page appears, close it.', // }); // 👀 直接打开 ebay.com(推荐做法) await page.launch('https://ebay.com'); await sleep(3000); // 👀 输入关键字并执行搜索 await agent.aiAct('Search for "Headphones"'); // 👀 等待加载完成 await agent.aiWaitFor('At least one headphone product is displayed on the page'); // 或简单地等待几秒: // await sleep(5000); // 👀 理解页面内容并提取数据 const items = await agent.aiQuery( '{itemTitle: string, price: Number}[], find product titles and prices in the list', ); console.log('Headphone product information', items); // 👀 使用 AI 断言 await agent.aiAssert('Multiple headphone products are displayed on the interface'); await page.destroy(); })(), ); ``` ### 运行脚本 ```bash npx tsx demo.ts ``` 脚本运行结束后,你应该能在控制台看到 `Midscene - report file updated: /path/to/report/some_id.html`。在浏览器中打开生成的 HTML 文件,即可回放每次交互、查询和断言。 ## 隐藏键盘 `autoDismissKeyboard` 默认值为 `true`。文本输入完成后,Midscene 会尽力定位标准的 iOS 键盘辅助工具栏、触发其中的隐藏控件,并等待 WebDriverAgent 确认键盘已经消失。部分键盘(例如 iPhone 数字键盘)不提供隐藏控件。如果无法安全识别工具栏、键盘仍然可见或隐藏请求失败,Midscene 不会因此将本次文本输入判为失败,只会记录一条警告日志。 如果自动隐藏键盘后紧接着执行 `Enter`、`Return` 或 `Tab`,Midscene 可以在发送按键前恢复一次原输入框的焦点。其他指针、触控、系统、自定义或文本输入动作都会使这个待恢复目标失效。 :::warning App 自定义的输入辅助工具栏可能包含 Save、Submit 等业务操作。对于不符合标准 iOS“左侧导航、右侧隐藏”结构的工具栏,Midscene 会主动拒绝点击。使用自定义工具栏时,请关闭自动隐藏,并向 `hideKeyboard()` 传入该控件稳定的 accessibility name。 ::: ```typescript const device = new IOSDevice({ autoDismissKeyboard: false, }); // 输入完成后,显式触发自定义键盘隐藏控件。 await device.hideKeyboard(['Close Keyboard']); ``` ## 自定义动作 使用 `defineAction()` 定义自定义动作。使用 `agentFromWebDriverAgent()` 构造 Agent 时,通过 `customActions` 传入这些动作。Midscene 会把这些动作追加到规划器中,让 Agent 可以调用你定义的领域特定动作。 ```typescript import { getMidsceneLocationSchema, z } from '@midscene/core'; import { defineAction } from '@midscene/core/device'; import { agentFromWebDriverAgent } 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) { console.log('click target center', param.locate.center); console.log('click count', param.count); // Carry out your clicking logic using locate + count. }, }); const agent = await agentFromWebDriverAgent({ customActions: [ContinuousClick], }); await agent.aiAct('click the red button five times'); ``` ## API 参考与更多资源 构造函数、辅助方法和平台专属设备 API 位于 [API 参考的 iOS 章节](/zh/reference.md#ios)。该章节还包含详细参数和自定义操作等进阶内容。跨平台共用的 API 位于[通用章节](/zh/reference.md#common)。 ## 常见问题 ### 为什么 WebDriverAgent 已连接,但仍无法控制设备? 请检查以下事项: 1. **开发者模式**:在“设置 > 隐私与安全性 > 开发者模式”中确认已开启。 2. **UI Automation**:在“设置 > 开发者 > UI Automation”中确认已开启。 3. **设备信任**:确保设备信任当前 Mac。 ### 模拟器与真机有哪些区别? | 特性 | 真机 | 模拟器 | |------|------|--------| | 端口转发 | 需要 iproxy | 不需要 | | 开发者模式 | 必须手动开启 | 默认开启 | | UI Automation 设置 | 需手动开启 | 默认开启 | | 性能 | 真实设备性能 | 取决于 Mac 性能 | | 传感器 | 真实硬件 | 模拟数据 | ### 如何自定义 WebDriverAgent 的端口和 Host? 可以通过 `IOSDevice` 构造函数或 `agentFromWebDriverAgent` 来指定端口和 Host: ```typescript // 方式一:使用 IOSDevice const device = new IOSDevice({ wdaPort: 8100, // 自定义端口 wdaHost: '192.168.1.100', // 自定义主机 }); // 方式二:使用便捷函数(推荐) const agent = await agentFromWebDriverAgent({ wdaPort: 8100, // 自定义端口 wdaHost: '192.168.1.100', // 自定义主机 }); ``` 针对远程设备,还需要按需设置端口转发: ```bash iproxy 8100 8100 YOUR_DEVICE_ID ``` ### 如何在 Playground 中获得更流畅的实时画面? Playground 的画面预览支持两种模式: * **轮询模式**(默认):逐帧调用 WDA 截图 API,帧率约 5-10fps。 * **原生 MJPEG 流**(推荐):直接代理 WDA 内置的 MJPEG Server,帧率更高、延迟更低。 要启用原生 MJPEG 流,需要将 WDA MJPEG Server 的端口(默认 9100)转发到本机: ```bash # 真机需要端口转发(模拟器不需要) iproxy 9100 9100 YOUR_DEVICE_ID ``` Playground 启动时会自动探测 9100 端口。如果可用,日志会显示 `MJPEG: streaming via native WDA MJPEG server`;否则自动回退到轮询模式。 ## 更多 * 查看所有 Agent 方法:[API 参考(通用)](/zh/reference.md#interaction-methods) * iOS 专属参数与接口:[API 参考(iOS)](/zh/reference.md#ios) * 使用 [YAML 自动化脚本和命令行工具](/zh/automate-with-scripts-in-yaml.md)。 * 示例项目 * iOS JavaScript SDK 示例:[https://github.com/web-infra-dev/midscene-example/blob/main/ios/javascript-sdk-demo](https://github.com/web-infra-dev/midscene-example/blob/main/ios/javascript-sdk-demo) * iOS + Vitest 示例:[https://github.com/web-infra-dev/midscene-example/tree/main/ios/vitest-demo](https://github.com/web-infra-dev/midscene-example/tree/main/ios/vitest-demo) --- url: /zh/quick-start.md --- import { ChromeExtensionButton } from '@theme'; import SetupEnv from './common/setup-env.mdx'; import ShowcaseWeb from './showcases-web.mdx'; # 快速开始 Chrome Extension 是 Midscene 面向 Web 的 Playground。无需搭建项目,你就可以在网页中体验交互、数据提取和界面检查等核心能力。 本指南将带你配置模型、安装 Chrome Extension,并运行第一条自然语言指令。验证指令效果后,你可以通过 Agent API 将这些指令集成到自动化代码中。文末还列出了 Android、iOS、HarmonyOS 和桌面端的上手指南。 ## 配置模型 使用 Chrome Extension 前,需要先准备一个具备 UI 定位能力的多模态模型。 <SetupEnv /> 安装 Chrome Extension 后,将这组配置粘贴到设置中。 ## 安装 Chrome Extension {#chrome-extension} 1. 点击下面的按钮,从 Chrome Web Store 安装 Midscene: <ChromeExtensionButton linkLabel="从 Chrome Web Store 安装 Midscene Chrome Extension" /> 2. 在 Chrome 的扩展列表中打开 **Midscene**。浏览器右侧会出现 Midscene 侧边栏。 3. 点击侧边栏中的设置图标,将[配置模型](#配置模型)中的完整配置粘贴到设置页并保存。 <span id="chrome-extension-faq" /> **常见问题** <details> <summary>是否可以手动安装 Chrome Extension?</summary> 如果无法访问 Chrome Web Store,可以从 [GitHub Releases 页面](https://github.com/web-infra-dev/midscene/releases) 下载安装包并手动安装。但这种方式无法获得自动更新。 </details> <details> <summary>运行失败,提示 `Cannot access a chrome-extension:// URL of different extension`</summary> 这通常由其他 Chrome Extension 与 Midscene 冲突引起。例如,其他 Chrome Extension 可能已经向页面注入 `<iframe />` 或 `<script />`。 按照以下步骤查找发生冲突的 Chrome Extension: 1. 打开页面的开发者工具,找到 URL 以 `chrome-extension://` 开头的 `<iframe />` 或 `<script />`,并复制 URL 中的扩展 ID。 2. 打开 `chrome://extensions/`,根据扩展 ID 找到并禁用对应的 Chrome Extension。 3. 刷新页面并重试。 </details> <details> <summary>使用 Ollama 模型时出现 403 错误</summary> 设置环境变量 `OLLAMA_ORIGINS="*"`,允许 Chrome Extension 访问 Ollama 模型。 </details> ## 完成第一次体验 打开任意网页,在 Midscene 侧边栏中输入符合当前页面内容的自然语言指令。例如: - 规划并交互(`aiAct`):`点击登录按钮`。 - 提取结构化数据(`aiQuery`):`页面中的商品,{name: string, price: number}[]`。 - 检查界面(`aiAssert`):`页面顶部显示导航栏`。 运行指令后,Midscene 会理解当前页面,并执行操作或返回结果。下面的案例展示了 Chrome Extension 自动填写 GitHub 注册表单的过程: <ShowcaseWeb /> ## 从 Playground 集成到代码 Chrome Extension 与 `@midscene/web` 共享核心能力。在 Playground 中验证自然语言指令后,可以通过对应的 Agent API 将这些指令集成到 UI 测试脚本中: ```typescript // 规划并执行交互 await agent.aiAct('点击登录按钮'); // 提取结构化数据 const products = await agent.aiQuery<Array<{ name: string; price: number }>>( '页面中的商品,{name: string, price: number}[]', ); // 检查界面 await agent.aiAssert('页面顶部显示导航栏'); ``` 以上代码只展示 API 的调用形式。要在浏览器项目中创建 Agent 并运行完整脚本,请继续阅读[集成到 Playwright](./integrate-with-playwright)或[集成到 Puppeteer](./integrate-with-puppeteer)。如需了解各类 API 的用途和选择方法,请阅读[基本概念](./basics);所有参数详见 [API 参考](./reference/#common)。 ## 在其他平台使用 Midscene Midscene 在 Android、iOS、HarmonyOS 和桌面端也提供完整的自动化能力,并为每个平台提供对应的 Playground。使用前,需要先完成该平台的设备环境准备。例如,Android 平台需要安装并配置 adb。具体要求、Playground 启动方法和故障排查,请参考以下文档。 | 平台 | 平台指南 | | --- | --- | | Android | [Android](./platforms/android) | | iOS | [iOS](./platforms/ios) | | HarmonyOS | [HarmonyOS](./platforms/harmonyos) | | 桌面端 | [Windows、macOS 和 Linux](./platforms/desktop) | --- url: /zh/reference/index.md --- # API 参考 本页汇总通用 Agent API,以及各平台专属的构造函数、选项、操作和辅助方法。 本页记录 API 契约。安装、端到端工作流和故障排查请参考对应指南。平台 Agent 默认继承[共享 Agent API](#common);平台章节只记录对应环境的构造方式、选项、能力差异和工具。 本页保留少量完整示例,帮助理解相关 API 如何组合使用。更完整的接入流程和最佳实践请参考各章节末尾的指南链接。 | 领域 | 内容 | | --- | --- | | [共享 Agent API](#common) | Agent 选项、交互、提取、观察、工作流、报告、共享类型和报告工具 | | [Web 浏览器](#web) | Puppeteer、Playwright 和 Chrome Bridge API | | [Android](#android) | Android Device、Agent、工厂函数和工具 API | | [iOS](#ios) | iOS Device、Agent、工厂函数和工具 API | | [HarmonyOS](#harmonyos) | HarmonyOS Device、Agent、工厂函数和工具 API | | [桌面端](#desktop) | 本机桌面和 RDP API | | [运行时配置](#runtime-configuration) | 运行产物、语言、Playground 网络和 Debug 日志等全局环境变量 | ## 共享 Agent API {#common} ### Agent 选项与配置 {#agent-options} Midscene 针对每个不同环境都有对应的 Agent。每个 Agent 的构造函数都接受一组共享的配置项(设备、报告、缓存、AI 配置、钩子等),然后再叠加平台专属的配置,比如浏览器里的导航控制或 Android 的 ADB 配置。 你可以通过下面的链接查看各 Agent 的导入路径和平台专属参数: - 在 Puppeteer 中,使用 [PuppeteerAgent](#puppeteer-agent) - 在 Playwright 中,使用 [PlaywrightAgent](#playwright-agent) - 在桥接模式(Bridge mode)中,使用 [AgentOverChromeBridge](#chrome-bridge-agent) - 在 Android 中,使用 [Android API 参考](#android) - 在 iOS 中,使用 [iOS API 参考](#ios) - 如果你要把 GUI Agent 集成到自己的界面,请参考 [自定义界面 Agent](../integrate-with-any-interface) <a id="common-parameters"></a> **参数** 这些 Agent 有一些相同的构造参数: - `generateReport: boolean`: 如果为 true,则生成报告文件。默认值为 true。 - `persistExecutionDump: boolean`: 如果为 true,Midscene 还会在报告旁边额外写出每次执行对应的 JSON dump 文件。默认值为 false。这个选项要求 `generateReport` 保持为 `true`。 - `reportFileName: string`: 报告输出名称,默认值由 midscene 内部生成。它在不同 `outputFormat` 下含义不同: - `single-html`(默认):按文件名处理。Midscene 会在 `midscene_run/report/` 下写入 `<reportFileName>.html`(如果已带 `.html` 后缀则保持不变)。 - `html-and-external-assets`:按目录名处理。Midscene 会在 `midscene_run/report/<reportFileName>/` 下写入 `index.html` 与相关静态资源。 - `autoPrintReportMsg: boolean`: 如果为 true,则打印报告消息。默认值为 true。 - `cache?: false | { id: string; strategy?: 'read-only' | 'read-write' | 'write-only'; cacheDir?: string }`: - `false`:完全禁用缓存。 - `id`:必填的缓存 ID。 - `strategy`:可选缓存策略。默认值为 `'read-write'`。 - `cacheDir`:可选缓存目录路径。配置后,缓存文件会写入该目录,而不是 `<MIDSCENE_RUN_DIR>/cache`。相对路径会基于当前工作目录解析,而不是基于 `MIDSCENE_RUN_DIR`。这样可以把缓存、日志和报告目录拆开。 - `cacheId: string | undefined`(已废弃):仅用于向后兼容。推荐使用 `cache.id`。 - `aiContexts: { default?: string; [apiName]?: string }`:Agent 级 AI 补充信息,可包含业务事实、判断或操作规则、约束以及输出要求。如果配置了 `default`,它会作为所有 AI API 共享的缺省回退值,仅当本次调用和对应 API 都没有自己的 context 时使用。解析规则为 `options.context ?? aiContexts[apiName] ?? aiContexts.default`,只使用优先级最高的已定义值,不会自动合并。空字符串会显式清空低优先级的用户 context。默认值为空。 - `aiActContext: string`(已废弃):`aiContexts.aiAct` 的兼容配置。两者同时存在时以 `aiContexts.aiAct` 为准;没有 `aiContexts.aiAct` 时,它与 `aiContexts.aiAct` 的作用相同。新代码请使用 `aiContexts.aiAct`;更早的名称 `aiActionContext` 也已废弃,仅为兼容而保留。 - `modelConfig: Record<string, string | number>`:当前 Agent 的模型配置。传入该参数后,当前 Agent 不再读取系统环境变量中的模型配置。详细用法见下文。 - `replanningCycleLimit: number`: `aiAct` 的最大重规划次数。标准模型默认 20,UI-TARS 模型默认 40,AutoGLM 模型默认 100。推荐通过 Agent 入参设置;`MIDSCENE_REPLANNING_CYCLE_LIMIT` 环境变量仅作兼容读取。 - `waitAfterAction: number`: 每次动作执行后的等待时间(毫秒)。这让 UI 有时间稳定,然后再执行下一个动作。默认值为 300 毫秒。 - `useDeviceTime: boolean`:是否使用目标设备的本地时间记录任务时间。目标接口必须实现 `getDeviceLocalTimeString`。如果未实现,Midscene 会输出警告并改用运行环境的系统时间。默认值为 `false`。 - `onTaskStartTip: (tip: string) => void | Promise<void>`:可选回调,在每个子任务执行开始前收到一条可读的任务描述提示。默认值为 undefined。 - `createOpenAIClient: (openai, options) => Promise<OpenAI | undefined>`:可选的 OpenAI 客户端包装函数,可用于接入可观测性工具或自定义中间件。详细示例见下文。 - `onLLMUsage: (usage: AIUsageInfo) => void`:可选回调。每次大模型调用的用量信息就绪后,Midscene 会调用一次该函数,可用于实时统计用量和成本。 - `outputFormat: 'single-html' | 'html-and-external-assets'`: 控制报告的生成格式。`'single-html'`(默认)将所有截图作为 base64 内嵌到单个 HTML 文件中,并把 `reportFileName` 作为 HTML 文件名。`'html-and-external-assets'` 将截图保存为独立的 PNG 文件到子目录,并把 `reportFileName` 作为该目录名,适用于报告文件过大的场景。**注意**:使用 `'html-and-external-assets'` 时,报告必须通过 HTTP 服务器或 CDN 地址访问,无法直接使用 `file://` 协议打开。这是因为浏览器的 CORS(跨源资源共享)限制会阻止从 file 协议加载相对路径的本地图片。如需在本地测试,可在报告目录下启动简易的 HTTP 服务器。进入报告目录后运行以下命令之一: - 使用 Node.js:`npx serve` - 使用 Python:`python -m http.server` 或 `python3 -m http.server` 然后通过 `http://localhost:3000`(或终端显示的端口)访问报告。 - `screenshotShrinkFactor: number`: 控制截图的缩放比例,以减少发送给 AI 模型的图像大小,从而减少 token 消耗。默认值为 1(不缩放)。如果将其设置为 2,则截图的宽高将缩小为原来的一半,面积缩小为原来的四分之一。你可以根据实际情况调整这个值,以在图像清晰度和 token 消耗之间找到最佳平衡点。 - 对于移动端设备,将 `screenshotShrinkFactor` 设置为 2 可以在保持清晰度的同时减少 token 的消耗,但不建议设置的值超过 3,否则可能会导致图像过于模糊,影响 AI 模型的理解。 - 对于 Web 页面,如果页面内容比较复杂或包含大量细节,不建议设置过高的 `screenshotShrinkFactor`,以避免截图过于模糊。通常也可以通过 Puppeteer 或 Playwright 的 `deviceScaleFactor` 在更上游控制截图尺寸。 :::info **`screenshotShrinkFactor` 与 `deviceScaleFactor` 的区别:** - `screenshotShrinkFactor` 是 Midscene 自定义的参数,用于控制拿到浏览器、手机等设备的截图后,是否对其进行尺寸压缩。目的是减少 token 消耗、加快模型响应速度。但过度压缩会导致图片模糊,影响模型理解。 - `deviceScaleFactor` 是 Puppeteer 和 Playwright 自带的参数,用于配置高清屏适配(现在很多设备都是高清屏了)时将一个 CSS 逻辑像素渲染为几倍的物理像素。这也就是为什么 `deviceScaleFactor` 和实际设备的缩放比例不一致时,非 headless 模式下可能会出现页面闪烁。同时,这一缩放逻辑也决定了 Puppeteer/Playwright 的截图尺寸(基于物理像素)。相比于 `screenshotShrinkFactor`,它是在更上游的生产端控制了截图尺寸。 **二者是否可以同时使用?** - Web 场景:二者同时使用的意义不大,应该优先使用 `deviceScaleFactor`,直接在生产端控制截图尺寸。 - 一种特殊情况:你期望配置 `deviceScaleFactor` 来避免浏览器闪烁,但同时又不期望发送给模型的截图过大,此时可以同时使用 `screenshotShrinkFactor` 控制发送给模型时的图片压缩。 - 移动端等非 Web 场景:因为没有 `deviceScaleFactor` 参数可用,所以只能通过 `screenshotShrinkFactor` 来控制模型消费时使用的截图尺寸。 ::: 设备 CLI 可以按单次调用传入这些 Agent 行为参数。把 API 的 camelCase 参数名转换成不带平台前缀的 kebab-case flag,例如 `waitAfterAction` -> `--wait-after-action`。各平台 CLI 入口请参考 [Skills](../skills)。 **自定义模型** `modelConfig: Record<string, string | number>` 可选。它允许你通过代码配置模型,而不是通过环境变量。 > 如果在 Agent 初始化时提供了 `modelConfig`,**系统环境变量中的模型配置将全部被忽略**,仅使用该对象中的值。 > 这里可配置的 key / value 与 [模型配置](../model-config) 文档中说明的内容完全一致。Default、Planning 和 Insight 模型的职责请参考[模型策略](../model-strategy)。 **自定义 OpenAI 客户端** `createOpenAIClient: (openai, options) => Promise<OpenAI | undefined>` 可选。它允许你包装 OpenAI 客户端实例,用于集成可观测性工具(如 LangSmith、Langfuse)或应用自定义中间件。 **参数说明:** - `openai: OpenAI` - Midscene 创建的基础 OpenAI 客户端实例,已包含所有必要配置(API 密钥、基础 URL、代理等) - `options: Record<string, unknown>` - OpenAI 初始化选项,包括: - `baseURL?: string` - API 接入地址 - `apiKey?: string` - API 密钥 - `dangerouslyAllowBrowser: boolean` - 在 Midscene 中始终为 true - 其他 OpenAI 配置选项 **返回值:** - 返回包装后的 OpenAI 客户端实例,或返回 `undefined` 表示使用原始实例 <a id="interaction-methods"></a> ### 规划与交互 {#planning-interaction} 这些是 Midscene 中各类 Agent 的主要 API。 `agent.ai()` 和 `agent.aiAct()` 会根据自然语言自动规划并执行多个步骤。`agent.aiTap()`、`agent.aiInput()` 等即时操作 API 直接执行指定动作,AI 模型只负责定位等底层任务。 <a id="agentaiact-或-agentai"></a> #### `aiAct()` 或 `ai()` {#agentaiact} 这个方法允许你通过自然语言描述 UI 操作目标和断言条件。执行期间,Midscene 会基于最新的界面状态持续进行 AI 规划和操作,直至完成目标。如果提示词中的断言失败,`aiAct` 会及时抛出错误。 :::info 向后兼容 这个接口在之前版本里也被写为 `aiAction()`,当前的版本兼容两种写法。为了保持代码的一致性,建议使用新的 `aiAct()` 方法。 ::: - 类型 ```typescript function aiAct( prompt: string | object, options?: { cacheable?: boolean; deepThink?: 'unset' | true | false; deepLocate?: boolean; fileChooserAccept?: string | string[]; fileChooserAllowedDir?: string; abortSignal?: AbortSignal; context?: string; }, ): Promise<string | undefined>; function ai(prompt: string, options?: Object): Promise<string | undefined>; // 简写形式 ``` - 参数: - `prompt: string | object` - 用自然语言描述的操作目标和断言条件,或[使用图片作为提示词](#使用图片作为提示词)。断言条件不是必填项。 - `options?: object` - 可选,一个配置对象,包含: - `cacheable?: boolean` - 当启用 [缓存功能](../caching.mdx) 时,是否允许缓存当前 API 调用结果。默认值为 true - `deepThink?: 'unset' | true | false` - 控制 Midscene 在 `aiAct` 执行规划时的具体实现。详见 [`deepThink` 规划模式](#aiact-deepthink)。 - `deepLocate?: boolean` - 是否开启[深度定位](#深度定位deeplocate)。默认值为 false。 - `context?: string` - 本次调用的额外上下文。详见[单次调用上下文](#单次调用上下文)。 - `fileChooserAccept?: string | string[]` - 当文件选择器弹出时,指定对应的文件路径。可以是单个文件路径或路径数组。仅在 web 页面(Playwright、Puppeteer 或 Chrome extension Bridge mode)中可用。该选项不受 `fileChooserAllowedDir` 限制。 - **注意**:如果文件输入框不支持多文件(没有 `multiple` 属性),但是传入了多个文件,会抛出错误。 - **注意**:如果点击触发了文件选择器但没有传入 `fileChooserAccept` 参数,文件选择器会被忽略,页面可以继续正常操作。 - **注意**:Chrome extension Bridge mode 上传本地文件时,需要在 `chrome://extensions` > Midscene > “Details” 中开启插件的 “Allow access to file URLs” 权限。开启后请从目标 `http(s)://` 页面重新连接桥接模式。 - **注意**:Chrome extension Bridge mode 不支持目录上传输入框(`webkitdirectory` / `directory`)。如需上传目录,请使用 Playwright。 - `fileChooserAllowedDir?: string`:显式授权当前 `aiAct` 通过提示词上传文件时可访问的目录。未配置时,模型规划的文件上传会被拒绝。配置后,提示词中的相对路径会基于此目录解析,并校验是否位于此目录下;绝对路径则直接校验是否位于此目录下。位于此目录下的 symlink 也允许上传。建议将其设置为测试用例的 `fixtures` 目录,以避免 AI 幻觉或页面提示词攻击导致 `aiAct` 上传敏感文件。 - `abortSignal?: AbortSignal` - 可选的 [AbortSignal](https://developer.mozilla.org/zh-CN/docs/Web/API/AbortSignal),用于中止 `aiAct` 的执行。当信号被触发时,Midscene 会停止当前的规划循环并抛出错误。适用于实现超时控制或用户主动取消操作的场景。 - 返回值: - 返回执行完成后的规划输出文本;如果规划没有产生输出,则返回 `undefined`。执行失败时会抛出错误。 - 示例: ```typescript // 操作并验证结果 await agent.aiAct( '搜索耳机,将第一件商品加入购物车,并确认购物车数量变为 1', ); // 使用 .ai 简写形式 await agent.ai( '点击页面顶部的登录按钮,然后在用户名输入框中输入 "test@example.com"', ); // 使用 abortSignal 设置超时 const controller = new AbortController(); setTimeout(() => controller.abort('timeout'), 30000); // 30 秒超时 await agent.aiAct('填写表单并提交', { abortSignal: controller.signal, }); // 对于复杂任务,可以启用 deepThink 参数 await agent.aiAct('完成 github 账号注册的表单填写。地区必须选择「加拿大」。确保表单上没有遗漏的字段,确保所有的表单项能够通过校验。 只需要填写表单项即可,不需要发起真实的账号注册。 最终请返回表单上实际填写的字段内容', { deepThink: true }); ``` ##### `deepThink` 规划模式 {#aiact-deepthink} `deepThink` 控制 `aiAct` 的规划实现: - 默认情况下,`aiAct` 会在同一次规划请求中完成下一步规划和目标元素定位。 - 设置为 `true` 后,`aiAct` 会更注重任务拆解,并将任务规划和元素定位分为不同的模型调用。复杂任务可能因此更加稳定,但模型调用次数和延迟也会增加。 `deepThink` 支持 `'unset' | true | false`。`'unset'` 是兼容旧写法的取值,与 `false` 的行为相同。 `deepThink` 不控制模型原生思考。相关环境变量请参考[模型原生思考](../model-config#model-native-reasoning)。 ##### 在 `aiAct` 提示词中上传文件 如需让 `aiAct` 上传提示词中提到的文件,请为该次调用显式传入 `fileChooserAllowedDir`。建议将其设置为测试用例的 `fixtures` 目录。Midscene 会在打开文件选择器的操作之前规划文件选择器配置;相对路径和绝对路径都会先解析,再校验解析结果是否仍在所选目录内。 ```typescript const agent = new PlaywrightAgent(page); await agent.aiAct( '先点击“上传头像”按钮并上传 avatar.png;然后点击“上传封面”按钮并上传 cover.png。上传完成后,请确认页面头像显示一只猫、封面显示一片海滩。', { fileChooserAllowedDir: './fixtures' }, ); ``` 如果一次 `aiAct` 需要上传多个文件,请在提示词中把每个相对路径与对应的上传操作明确写出。后出现的路径会覆盖此前配置的文件选择器路径。不要将提示词中的文件路径与 `options.fileChooserAccept` 混用:模型在规划过程中生成的后续文件选择器配置可能覆盖该 option 的值。 此能力仅适用于 web 页面(Playwright、Puppeteer 和 Chrome extension Bridge mode)。当 fixture 不在当前工作目录下,或需要让同一提示词在本地和 CI 环境中复用时,请配置 `fileChooserAllowedDir`。 在 Chrome extension Bridge mode 中上传本地文件时,需要在 `chrome://extensions` > Midscene > “Details” 中开启插件的 “Allow access to file URLs” 权限。开启后请切回目标 `http(s)://` 页面,再重新连接桥接模式。 :::info 在实际运行时,Midscene 会将用户指令规划(Planning)成多个步骤,然后逐步执行。如果 Midscene 认为无法执行,将抛出一个错误。 为了获得最佳效果,请尽可能提供清晰、详细的步骤描述。 关联文档: - [模型策略](../model-strategy) ::: #### `aiTap()` {#agentaitap} 点击某个元素 - 类型 ```typescript function aiTap(locate: string | object, options?: object): Promise<void>; ``` - 参数: - `locate: string | object` - 用自然语言描述的元素定位,或[使用图片作为提示词](#使用图片作为提示词)。 - `options?: object` - 可选,一个配置对象,包含: - `deepLocate?: boolean` - 是否开启[深度定位](#深度定位deeplocate)。该参数原来叫 `deepThink`,现已更名为 `deepLocate`。默认值为 false。 - `context?: string` - 本次调用的额外上下文。详见[单次调用上下文](#单次调用上下文)。 - `xpath?: string` - 目标元素的 xpath 路径,用于执行当前操作。如果提供了这个 xpath,Midscene 会优先使用该 xpath 来找到元素,然后依次使用缓存和 AI 模型。默认值为空 - `cacheable?: boolean` - 当启用 [缓存功能](../caching.mdx) 时,是否允许缓存当前 API 调用结果。默认值为 true - `fileChooserAccept?: string | string[]` - 当文件选择器弹出时,指定对应的文件路径。可以是单个文件路径或路径数组。仅在 web 页面(Playwright、Puppeteer 或 Chrome extension Bridge mode)中可用。 - **注意**:如果文件输入框不支持多文件(没有 `multiple` 属性),但是传入了多个文件,会抛出错误。 - **注意**:如果点击触发了文件选择器但没有传入 `fileChooserAccept` 参数,文件选择器会被忽略,页面可以继续正常操作。 - **注意**:Chrome extension Bridge mode 不支持目录上传输入框(`webkitdirectory` / `directory`)。如需上传目录,请使用 Playwright。 - 返回值: - `Promise<void>` - 示例: ```typescript await agent.aiTap('页面顶部的登录按钮'); // 使用 deepLocate 功能精确定位元素 await agent.aiTap('页面顶部的登录按钮', { deepLocate: true }); // 文件上传:点击上传按钮并选择文件 await agent.aiTap('选择文件按钮', { fileChooserAccept: ['./document.pdf'] }); await agent.aiTap('上传图片', { fileChooserAccept: ['./image1.jpg', './image2.png'] }); ``` #### `aiHover()` {#agentaihover} > 在 Web 页面和桌面端(`@midscene/computer`)中可用,在移动端(Android、iOS 或 HarmonyOS)下不可用。 鼠标悬停某个元素上。 - 类型 ```typescript function aiHover(locate: string | object, options?: object): Promise<void>; ``` - 参数: - `locate: string | object` - 用自然语言描述的元素定位,或[使用图片作为提示词](#使用图片作为提示词)。 - `options?: object` - 可选,一个配置对象,包含: - `deepLocate?: boolean` - 是否开启[深度定位](#深度定位deeplocate)。该参数原来叫 `deepThink`,现已更名为 `deepLocate`。默认值为 false。 - `context?: string` - 本次调用的额外上下文。详见[单次调用上下文](#单次调用上下文)。 - `xpath?: string` - 目标元素的 xpath 路径,用于执行当前操作。如果提供了这个 xpath,Midscene 会优先使用该 xpath 来找到元素,然后依次使用缓存和 AI 模型。默认值为空 - `cacheable?: boolean` - 当启用 [缓存功能](../caching.mdx) 时,是否允许缓存当前 API 调用结果。默认值为 true - 返回值: - `Promise<void>` - 示例: ```typescript await agent.aiHover('页面顶部的登录按钮'); ``` #### `aiInput()` {#agentaiinput} 在某个元素中输入文本。 - 类型 ```typescript // 推荐用法:定位提示在前,其他选项在 opt 中 function aiInput( locate: string | object, opt: { value: string | number; deepLocate?: boolean; xpath?: string; cacheable?: boolean; autoDismissKeyboard?: boolean; keyboardTypeDelay?: number; inputStrategy?: 'legacy' | 'sequential' | 'bulk'; mode?: 'replace' | 'clear' | 'typeOnly'; }, ): Promise<void>; // 兼容用法:保留向后兼容性 function aiInput( value: string | number, locate: string | object, options?: object, ): Promise<void>; ``` - 参数: **推荐用法**: - `locate: string | object` - 用自然语言描述的元素定位,或[使用图片作为提示词](#使用图片作为提示词)。 - `opt: object` - 配置对象,包含: - `value: string | number` - **必填**,要输入的文本内容。 - 当 `mode` 为 `'replace'` 时:文本将替换输入框中的所有现有内容。 - 当 `mode` 为 `'typeOnly'` 时:直接输入文本,不会先清空输入框。 - 当 `mode` 为 `'clear'` 时:会忽略文本内容,仅清空输入框。 - `deepLocate?: boolean` - 是否开启[深度定位](#深度定位deeplocate)。该参数原来叫 `deepThink`,现已更名为 `deepLocate`。默认值为 false。 - `context?: string` - 本次调用的额外上下文。详见[单次调用上下文](#单次调用上下文)。 - `xpath?: string` - 目标元素的 xpath 路径,用于执行当前操作。如果提供了这个 xpath,Midscene 会优先使用该 xpath 来找到元素,然后依次使用缓存和 AI 模型。默认值为空 - `cacheable?: boolean` - 当启用 [缓存功能](../caching.mdx) 时,是否允许缓存当前 API 调用结果。默认值为 true - `autoDismissKeyboard?: boolean` - 如果为 true,则键盘会在输入文本后自动关闭,仅在 Android/iOS/HarmonyOS 中有效。默认值为 true。 - `keyboardTypeDelay?: number` - 按键间延迟,单位为毫秒。取值必须是有限的非负数。该选项在 `legacy` 模式下是否生效取决于平台既有的输入路径,详见下表。支持延迟的 `legacy` 路径会在值大于 `0` 时逐个 Unicode 码点输入。适用于输入框在快速输入下丢字的场景。 - `inputStrategy?: 'legacy' | 'sequential' | 'bulk'` - 控制 Midscene 如何把文本发送给平台。默认值为 `'legacy'`。 - `'legacy'`:保留各平台现有行为。它是兼容性默认值,升级后不会改变已有测试的输入方式。 - `'sequential'`:逐个 Unicode 码点发送。即使未设置 `keyboardTypeDelay` 或将其设为 `0`,Midscene 也会逐个发送。 - `'bulk'`:在平台支持时,只调用一次平台输入操作,以发送完整字符串。Web 的 `replace` 会先选中旧值,再使用 `insertText`。这个过程只产生一次插入 `input` 事件。它不会先产生清空 `input` 事件,再逐字符产生 `input` 事件。其他平台只会收到 Midscene 发起的一次底层输入调用。不过,操作系统或驱动仍可能合成多个应用事件。 - 使用 `'bulk'` 时,必须省略 `keyboardTypeDelay`,或将它设为 `0`。如果值大于 `0`,Midscene 会抛出错误。所有策略都拒绝负数和非有限数值。动作级配置优先于 Device 或 Agent 的默认值。因此,单次动作选择 `'bulk'` 时,可以用 `keyboardTypeDelay: 0` 覆盖正数默认值。 - `mode?: 'replace' | 'clear' | 'typeOnly'` - 输入模式。(默认值: 'replace') - `'replace'`: 先清空输入框,然后输入文本。 - `'typeOnly'`: 直接输入文本,不会先清空输入框。 - `'clear'`: 清空输入框,不会输入新的文本。 所有平台的 `inputStrategy` 默认值均为 `'legacy'`。该模式会沿用先前版本中 Midscene 针对各平台的输入逻辑,具体表现可能因平台而异: | 平台 | `legacy` 行为 | | --- | --- | | Web / Playwright / Puppeteer | `replace` 模式会先清空旧值并等待 DOM 稳定,再将完整字符串传给 `keyboard.type(value)`。浏览器驱动负责处理正数 `keyboardTypeDelay`。 | | Chrome Extension / Bridge | 沿用 Web 的“先清空、再输入”流程,通过远端键盘操作发送文本。`legacy` CDP 输入沿用零延迟行为。Bridge 在 CLI 侧解析策略,再转发键盘操作。 | | Android | 文本含有非 ASCII 字符或 shell 敏感字符时,通常只调用一次 yadb。为兼容旧行为,该路径忽略 `keyboardTypeDelay`。原生 `input text` 路径默认整段发送。`keyboardTypeDelay` 大于 `0` 时,改为逐个 Unicode 码点调用。 | | iOS | 默认调用一次 WDA `typeText`。`keyboardTypeDelay` 大于 `0` 时,改为对每个 Unicode 码点调用一次 `typeRawKeys`。 | | HarmonyOS | 默认调用一次 HDC `inputText`。`keyboardTypeDelay` 大于 `0` 时,改为对每个 Unicode 码点调用一次。 | | Local Computer | 未设置 `keyboardTypeDelay` 或将其设为 `0` 时,使用剪贴板粘贴。值大于 `0` 时,改为逐个 Unicode 码点发送真实键盘事件。 | | RDP Computer | 未设置 `keyboardTypeDelay` 或将其设为 `0` 时,只调用一次后端 `typeText`。值大于 `0` 时,改为对每个 Unicode 码点调用一次后端。 | **兼容用法**(已过时,但仍然支持): - `value: string | number` - 要输入的文本内容。 - `locate: string | object` - 用自然语言描述的元素定位,或[使用图片作为提示词](#使用图片作为提示词)。 - `options?: object` - 可选的配置对象,类型与推荐用法中的 `opt` 类型相同。 - 返回值: - `Promise<void>` - 示例: ```typescript // 推荐用法 await agent.aiInput('搜索框', { value: 'Hello World' }); // 兼容用法(不推荐) await agent.aiInput('Hello World', '搜索框'); ``` :::note 关于签名变更 我们最近更新了 `aiInput` 的 API 签名,将定位提示作为第一个参数,使得参数顺序更直观。旧的签名 `aiInput(value, locate, options)` 仍然完全兼容,但建议新代码使用推荐的签名。 ::: #### `aiClearInput()` {#agentaiclearinput} 清空输入框内容。适合作为一个独立步骤使用:在输入前先清空,或只需删除现有文本而暂时不输入新内容。 - 类型 ```typescript function aiClearInput( locate: string | object, opt?: { deepLocate?: boolean; xpath?: string; cacheable?: boolean; }, ): Promise<void>; ``` - 参数: - `locate: string | object` - 要清空的输入框的自然语言描述,或[通过图像提示](#通过图像提示)。 - `opt?: object` - 可选配置对象: - `deepLocate?: boolean` - 是否开启[深度定位](#深度定位deeplocate)。默认 `false`。 - `context?: string` - 本次调用的额外上下文。详见[单次调用上下文](#单次调用上下文)。 - `xpath?: string` - 要操作元素的 xpath,默认空。 - `cacheable?: boolean` - 启用[缓存功能](../caching.mdx)时是否缓存,默认 `true`。 - 返回值: - 返回 `Promise<void>` - 示例: ```typescript // 清空搜索框 await agent.aiClearInput('搜索框'); // 先清空再输入新值 await agent.aiClearInput('邮箱输入框'); await agent.aiInput('邮箱输入框', { value: 'user@example.com' }); ``` :::info `aiClearInput` 与 `aiInput` 的取舍 `aiInput(locate, { value: '...' })` 默认会先清空输入框(`mode: 'replace'`)。只有在需要把清空当成独立一步时(例如测试空值校验,或想把清空和输入拆成两步分别控制)才使用 `aiClearInput`。 ::: #### `aiKeyboardPress()` {#agentaikeyboardpress} 按下键盘上的某个键。 - 类型 ```typescript // 推荐用法:定位提示在前,其他选项在 opt 中 function aiKeyboardPress( locate: string | object | undefined, opt: { keyName: string; deepLocate?: boolean; xpath?: string; cacheable?: boolean; }, ): Promise<void>; // 兼容用法:保留向后兼容性 function aiKeyboardPress( key: string, locate?: string | object, options?: object, ): Promise<void>; ``` - 参数: **推荐用法**: - `locate: string | object | undefined` - 可选的元素定位描述,或[使用图片作为提示词](#使用图片作为提示词)。传入 `undefined` 时,不执行 AI 定位和预先点击,按键会直接作用于当前获得焦点的元素。 - `opt: object` - 配置对象,包含: - `keyName: string` - **必填**,要按下的键,如 `Enter`、`Tab`、`Escape` 等;输入文本请使用 `aiInput()`。Web 和 Computer Device 支持 `Control+A`、`Shift+Enter` 等 modifier shortcut,使用 `+` 连接按键。可在[共享按键名称定义](https://github.com/web-infra-dev/midscene/blob/main/packages/shared/src/us-keyboard-layout.ts)中查看非移动端的候选名称,实际支持情况以平台为准。内置的 Android、iOS 和 Harmony Device 仅支持单键。Harmony 支持一组保守的具名键以及 `A`-`Z`、`0`-`9`;`?` 等没有独立键码的输出字符不属于按键名。不支持的按键和移动端组合键会抛出错误;自定义 Device 的支持情况取决于其实现。 - `deepLocate?: boolean` - 是否开启[深度定位](#深度定位deeplocate)。该参数原来叫 `deepThink`,现已更名为 `deepLocate`。默认值为 false。 - `context?: string` - 本次调用的额外上下文。详见[单次调用上下文](#单次调用上下文)。 - `xpath?: string` - 目标元素的 xpath 路径,用于执行当前操作。如果提供了这个 xpath,Midscene 会优先使用该 xpath 来找到元素,然后依次使用缓存和 AI 模型。默认值为空 - `cacheable?: boolean` - 当启用 [缓存功能](../caching.mdx) 时,是否允许缓存当前 API 调用结果。默认值为 true **兼容用法**(已过时,但仍然支持): - `key: string` - 要按下的键,如 `Enter`、`Tab`、`Escape` 等。 - `locate?: string | object` - 用自然语言描述的元素定位,或[使用图片作为提示词](#使用图片作为提示词)。 - `options?: object` - 可选的配置对象,类型与推荐用法中的 `opt` 类型相同。 - 返回值: - `Promise<void>` - 示例: ```typescript // 推荐用法 await agent.aiKeyboardPress('搜索框', { keyName: 'Enter' }); await agent.aiKeyboardPress('搜索框', { keyName: 'Control+A' }); // 对当前获得焦点的元素执行纯键盘操作 await agent.aiKeyboardPress(undefined, { keyName: 'Control+X' }); // 兼容用法(不推荐) await agent.aiKeyboardPress('Enter', '搜索框'); ``` :::note 关于签名变更 我们最近更新了 `aiKeyboardPress` 的 API 签名,将可选的定位提示作为第一个参数,使得参数顺序更直观。如果快捷键应该直接作用于当前焦点,请传入 `undefined`,这样不会执行定位或点击。此时仍需提供有效的模型配置,系统会在执行 action 前完成配置校验,但不会向模型发起定位请求。旧的签名 `aiKeyboardPress(key, locate, options)` 仍然完全兼容,但建议新代码使用推荐的签名。 ::: #### `aiScroll()` {#agentaiscroll} 滚动页面或某个元素。 - 类型 ```typescript // 推荐用法:定位提示在前,其他选项在 opt 中 function aiScroll( locate: string | object | undefined, opt: { scrollType?: 'singleAction' | 'scrollToBottom' | 'scrollToTop' | 'scrollToRight' | 'scrollToLeft'; direction?: 'down' | 'up' | 'left' | 'right'; distance?: number | null; deepLocate?: boolean; xpath?: string; cacheable?: boolean; }, ): Promise<void>; // 兼容用法:保留向后兼容性 function aiScroll( scrollParam: PlanningActionParamScroll, locate?: string | object, options?: object, ): Promise<void>; ``` - 参数: **推荐用法**: - `locate: string | object | undefined` - 用自然语言描述的元素定位,或[使用图片作为提示词](#使用图片作为提示词)。如果未传入或为 undefined,Midscene 会在当前鼠标位置滚动。 - `opt: object` - 配置对象,包含: - `scrollType?: 'singleAction' | 'scrollToBottom' | 'scrollToTop' | 'scrollToRight' | 'scrollToLeft'` - 滚动类型,默认值为 `singleAction`。 - `direction?: 'down' | 'up' | 'left' | 'right'` - 滚动方向,默认值为 `down`。仅在 `scrollType` 为 `singleAction` 时生效。不论是 Android 还是 Web,这里的滚动方向都是指页面哪个方向的内容会进入屏幕。比如当滚动方向是 `down` 时,页面下方被隐藏的内容会从屏幕底部开始逐渐向上露出。 - `distance?: number | null` - 滚动距离,单位为像素。设置为 `null` 表示由 Midscene 自动决定。 - `deepLocate?: boolean` - 是否开启[深度定位](#深度定位deeplocate)。该参数原来叫 `deepThink`,现已更名为 `deepLocate`。默认值为 false。 - `context?: string` - 本次调用的额外上下文。详见[单次调用上下文](#单次调用上下文)。 - `xpath?: string` - 目标元素的 xpath 路径,用于执行当前操作。如果提供了这个 xpath,Midscene 会优先使用该 xpath 来找到元素,然后依次使用缓存和 AI 模型。默认值为空 - `cacheable?: boolean` - 当启用 [缓存功能](../caching.mdx) 时,是否允许缓存当前 API 调用结果。默认值为 true **兼容用法**(已过时,但仍然支持): - `scrollParam: PlanningActionParamScroll` - 滚动参数(包含 scrollType、direction、distance)。 - `locate?: string | object` - 用自然语言描述的元素定位,或[使用图片作为提示词](#使用图片作为提示词)。 - `options?: object` - 可选的配置对象,类型与推荐用法中的 `opt` 类型相同。 - 返回值: - `Promise<void>` - 示例: ```typescript // 推荐用法 await agent.aiScroll('表单区域', { scrollType: 'singleAction', direction: 'up', distance: 100, }); // 兼容用法(不推荐) await agent.aiScroll( { scrollType: 'singleAction', direction: 'up', distance: 100 }, '表单区域', ); ``` :::note 关于签名变更 我们最近更新了 `aiScroll` 的 API 签名,将定位提示作为第一个参数,使得参数顺序更直观。旧的签名 `aiScroll(scrollParam, locate, options)` 仍然完全兼容,但建议新代码使用推荐的签名。 ::: #### `aiPinch()` {#agentaipinch} 执行双指缩放手势,用于放大或缩小。支持 Android、iOS 和 Web(基于 Chromium 的浏览器)。 - 类型 ```typescript function aiPinch( locate: string | object | undefined, opt: { direction: 'in' | 'out'; distance?: number; duration?: number; deepLocate?: boolean; xpath?: string; cacheable?: boolean; }, ): Promise<void>; ``` - 参数: - `locate: string | object | undefined` - 用自然语言描述的缩放目标元素,或[使用图片作为提示词](#使用图片作为提示词)。如果未传入,缩放将在屏幕中心执行。 - `opt: object` - 配置对象,包含: - `direction: 'in' | 'out'` - **必填。** `"in"` = 双指收拢(缩小),`"out"` = 双指张开(放大)。 - `distance?: number` - 每根手指移动的距离(像素)。默认值为屏幕较短边的四分之一。 - `duration?: number` - 缩放手势持续时间(毫秒),默认值为 `500`。 - `deepLocate?: boolean` - 是否开启[深度定位](#深度定位deeplocate)。默认值为 false。 - `context?: string` - 本次调用的额外上下文。详见[单次调用上下文](#单次调用上下文)。 - `xpath?: string` - 目标元素的 xpath 路径。默认值为空。 - `cacheable?: boolean` - 当启用[缓存功能](../caching.mdx)时,是否允许缓存。默认值为 true。 - 返回值: - `Promise<void>` - 示例: ```typescript // 在地图上放大(双指张开) await agent.aiPinch('地图区域', { direction: 'out', distance: 200 }); // 在屏幕中心缩小(双指收拢) await agent.aiPinch(undefined, { direction: 'in' }); // 自定义持续时间放大 await agent.aiPinch('图片', { direction: 'out', distance: 300, duration: 1000 }); ``` :::info 平台支持 - **Android**:通过 [yadb](https://github.com/ysbing/yadb) 的 `-pinch` 命令实现。 - **iOS**:通过 W3C Actions API 双触摸指针实现。 - **Web**:通过 CDP 触摸事件实现。Puppeteer/Playwright 需设置 `enableTouchEventsInActionSpace: true`。Playwright 仅支持 Chromium 内核浏览器。 - **HarmonyOS**:不支持。`uitest` 框架未提供多触点 API。 ::: #### `aiLongPress()` {#agentailongpress} 长按(按住不放)某个元素,常用于唤起右键/上下文菜单、触发选中模式或其他长按手势。 - 类型 ```typescript function aiLongPress( locate: string | object, opt?: { duration?: number; deepLocate?: boolean; xpath?: string; cacheable?: boolean; }, ): Promise<void>; ``` - 参数: - `locate: string | object` - 要长按的元素的自然语言描述,或[通过图像提示](#通过图像提示)。 - `opt?: object` - 可选配置对象: - `duration?: number` - 按住时长(毫秒)。Android 默认 `2000`,iOS 默认 `1000`,Web 默认 `500`。HarmonyOS 使用系统长按时长并忽略此选项。 - `deepLocate?: boolean` - 是否启用[深度定位](#深度定位deeplocate),默认 `false`。 - `context?: string` - 本次调用的额外上下文。详见[单次调用上下文](#单次调用上下文)。 - `xpath?: string` - 要操作元素的 xpath,默认空。 - `cacheable?: boolean` - 启用[缓存功能](../caching.mdx)时是否缓存,默认 `true`。 - 返回值: - 返回 `Promise<void>` - 示例: ```typescript // 长按首页的第一篇文章以唤起菜单 await agent.aiLongPress('首页的第一篇文章'); // 自定义长按时长 await agent.aiLongPress('消息气泡', { duration: 2000 }); ``` :::info 平台支持 - **Android**、**iOS**、**HarmonyOS**、**Web**(基于 Chromium 的浏览器,通过触摸事件实现)。HarmonyOS 会忽略 `duration` 选项,因为底层 `uitest` API 不支持自定义按住时长。 ::: #### `aiDoubleClick()` {#agentaidoubleclick} 双击某个元素。 - 类型 ```typescript function aiDoubleClick(locate: string | object, options?: object): Promise<void>; ``` - 参数: - `locate: string | object` - 用自然语言描述的元素定位,或[使用图片作为提示词](#使用图片作为提示词)。 - `options?: object` - 可选,一个配置对象,包含: - `deepLocate?: boolean` - 是否开启[深度定位](#深度定位deeplocate)。该参数原来叫 `deepThink`,现已更名为 `deepLocate`。默认值为 false。 - `context?: string` - 本次调用的额外上下文。详见[单次调用上下文](#单次调用上下文)。 - `xpath?: string` - 目标元素的 xpath 路径,用于执行当前操作。如果提供了这个 xpath,Midscene 会优先使用该 xpath 来找到元素,然后依次使用缓存和 AI 模型。默认值为空 - `cacheable?: boolean` - 当启用 [缓存功能](../caching.mdx) 时,是否允许缓存当前 API 调用结果。默认值为 true - 返回值: - `Promise<void>` - 示例: ```typescript await agent.aiDoubleClick('页面顶部的文件名称'); // 使用 deepLocate 功能精确定位元素 await agent.aiDoubleClick('页面顶部的文件名称', { deepLocate: true }); ``` #### `aiRightClick()` {#agentairightclick} > 可用于 web 页面和 PC 桌面端(`@midscene/computer`),不可用于移动设备(Android、iOS 或 HarmonyOS)。 右键点击某个元素。请注意,Midscene 在右键点击后无法与浏览器原生上下文菜单交互。这个接口通常用于已经监听了右键点击事件的元素。 - 类型 ```typescript function aiRightClick(locate: string | object, options?: object): Promise<void>; ``` - 参数: - `locate: string | object` - 用自然语言描述的元素定位,或[使用图片作为提示词](#使用图片作为提示词)。 - `options?: object` - 可选,一个配置对象,包含: - `deepLocate?: boolean` - 是否开启[深度定位](#深度定位deeplocate)。该参数原来叫 `deepThink`,现已更名为 `deepLocate`。默认值为 false。 - `context?: string` - 本次调用的额外上下文。详见[单次调用上下文](#单次调用上下文)。 - `xpath?: string` - 目标元素的 xpath 路径,用于执行当前操作。如果提供了这个 xpath,Midscene 会优先使用该 xpath 来找到元素,然后依次使用缓存和 AI 模型。默认值为空 - `cacheable?: boolean` - 当启用 [缓存功能](../caching.mdx) 时,是否允许缓存当前 API 调用结果。默认值为 true - 返回值: - `Promise<void>` - 示例: ```typescript await agent.aiRightClick('页面顶部的文件名称'); // 使用 deepLocate 功能精确定位元素 await agent.aiRightClick('页面顶部的文件名称', { deepLocate: true }); ``` ### 提取、定位与断言 {#extraction-location-assertion} #### `aiAsk()` {#agentaiask} 使用此方法,你可以针对当前页面,直接向 AI 模型发起提问,并获得字符串形式的回答。 `aiAsk()` 与 `aiString()` 的查询和返回行为相同,但仍可分别通过 `aiContexts.aiAsk` 和 `aiContexts.aiString` 配置 Agent 级 context。 - 类型 ```typescript function aiAsk(prompt: string | object, options?: object): Promise<string>; ``` - 参数: - `prompt: string | object` - 用自然语言描述的询问内容,或[使用图片作为提示词](#使用图片作为提示词)。 - `options?: object` - 可选,一个配置对象,包含: - `domIncluded?: boolean | 'visible-only'` - 是否向模型发送精简后的 DOM 信息,一般用于提取 UI 中不可见的属性,比如图片的链接。如果设置为 `'visible-only'`,则只发送可见的元素。默认值为 false。 - `screenshotIncluded?: boolean` - 是否向模型发送截图。默认值为 true。 - `context?: string` - 本次调用的额外上下文。详见[单次调用上下文](#单次调用上下文)。 - 返回值: - 返回一个 Promise。返回 AI 模型的回答。 - 示例: ```typescript const result = await agent.aiAsk('当前页面的应该怎么进行测试?'); console.log(result); // 输出 AI 模型的回答 ``` 除了 `aiAsk` 方法,你还可以使用 `aiQuery` 方法,直接从 UI 提取结构化的数据。 #### `aiQuery()` {#agentaiquery} 使用此方法,你可以直接从 UI 提取结构化的数据。只需在 `dataDemand` 中描述期望的数据格式(如字符串、数字、JSON、数组等),Midscene 即返回相应结果。 - 类型 ```typescript function aiQuery<T>(dataDemand: string | object, options?: object): Promise<T>; ``` - 参数: - `dataDemand: string | object`:描述预期的返回值和格式。 - `options?: object` - 可选,一个配置对象,包含: - `domIncluded?: boolean | 'visible-only'` - 是否向模型发送精简后的 DOM 信息,一般用于提取 UI 中不可见的属性,比如图片的链接。如果设置为 `'visible-only'`,则只发送可见的元素。默认值为 false。 - `screenshotIncluded?: boolean` - 是否向模型发送截图。默认值为 true。 - `context?: string` - 本次调用的额外上下文。详见[单次调用上下文](#单次调用上下文)。 - 返回值: - 返回值可以是任何合法的基本类型,比如字符串、数字、JSON、数组等。 - 你只需在 `dataDemand` 中描述它,Midscene 就会给你满足格式的返回。 - 示例: ```typescript const dataA = await agent.aiQuery({ time: '左上角展示的日期和时间,string', userInfo: '用户信息,{name: string}', tableFields: '表格的字段名,string[]', tableDataRecord: '表格中的数据记录,{id: string, [fieldName]: string}[]', }); // 你也可以用纯字符串描述预期的返回值格式: // dataB 将是一个字符串数组 const dataB = await agent.aiQuery('string[],列表中的任务名称'); // dataC 将是一个包含对象的数组 const dataC = await agent.aiQuery( '{name: string, age: string}[], 表格中的数据记录', ); // 使用 domIncluded 功能提取 UI 中不可见的属性 const dataD = await agent.aiQuery( '{name: string, age: string, avatarUrl: string}[], 表格中的数据记录', { domIncluded: true }, ); ``` 此外,我们还提供了 `aiBoolean()`, `aiNumber()`, `aiString()` 三个便捷方法,用于直接提取布尔值、数字和字符串。 #### `aiBoolean()` {#agentaiboolean} 从 UI 中提取一个布尔值。 - 类型 ```typescript function aiBoolean(prompt: string | object, options?: object): Promise<boolean>; ``` - 参数: - `prompt: string` - 用自然语言描述的期望值,或[使用图片作为提示词](#使用图片作为提示词)。 - `options?: object` - 可选,一个配置对象,包含: - `domIncluded?: boolean | 'visible-only'` - 是否向模型发送精简后的 DOM 信息,一般用于提取 UI 中不可见的属性,比如图片的链接。如果设置为 `'visible-only'`,则只发送可见的元素。默认值为 false。 - `screenshotIncluded?: boolean` - 是否向模型发送截图。默认值为 true。 - `context?: string` - 本次调用的额外上下文。详见[单次调用上下文](#单次调用上下文)。 - 返回值: - 返回一个 Promise。当 AI 返回结果时解析为布尔值。 - 示例: ```typescript const boolA = await agent.aiBoolean('是否存在登录对话框'); // 使用 domIncluded 功能提取 UI 中不可见的属性 const boolB = await agent.aiBoolean('忘记密码按钮是否存在链接', { domIncluded: true, }); ``` #### `aiNumber()` {#agentainumber} 从 UI 中提取一个数字。 - 类型 ```typescript function aiNumber(prompt: string | object, options?: object): Promise<number>; ``` - 参数: - `prompt: string | object` - 用自然语言描述的期望值,或[使用图片作为提示词](#使用图片作为提示词)。 - `options?: object` - 可选,一个配置对象,包含: - `domIncluded?: boolean | 'visible-only'` - 是否向模型发送精简后的 DOM 信息,一般用于提取 UI 中不可见的属性,比如图片的链接。如果设置为 `'visible-only'`,则只发送可见的元素。默认值为 false。 - `screenshotIncluded?: boolean` - 是否向模型发送截图。默认值为 true。 - `context?: string` - 本次调用的额外上下文。详见[单次调用上下文](#单次调用上下文)。 - 返回值: - 返回一个 Promise。当 AI 返回结果时解析为数字。 - 示例: ```typescript const numberA = await agent.aiNumber('账户剩余的积分'); // 使用 domIncluded 功能提取 UI 中不可见的属性 const numberB = await agent.aiNumber('账户剩余的积分元素的 value 值', { domIncluded: true, }); ``` #### `aiString()` {#agentaistring} 从 UI 中提取一个字符串。 `aiString()` 与 `aiAsk()` 的查询和返回行为相同,但仍可分别通过 `aiContexts.aiString` 和 `aiContexts.aiAsk` 配置 Agent 级 context。 - 类型 ```typescript function aiString(prompt: string | object, options?: object): Promise<string>; ``` - 参数: - `prompt: string | object` - 用自然语言描述的期望值,或[使用图片作为提示词](#使用图片作为提示词)。 - `options?: object` - 可选,一个配置对象,包含: - `domIncluded?: boolean | 'visible-only'` - 是否向模型发送精简后的 DOM 信息,一般用于提取 UI 中不可见的属性,比如图片的链接。如果设置为 `'visible-only'`,则只发送可见的元素。默认值为 false。 - `screenshotIncluded?: boolean` - 是否向模型发送截图。默认值为 true。 - `context?: string` - 本次调用的额外上下文。详见[单次调用上下文](#单次调用上下文)。 - 返回值: - 返回一个 Promise。当 AI 返回结果时解析为字符串。 - 示例: ```typescript const stringA = await agent.aiString('当前列表的第一条记录的名称'); // 使用 domIncluded 功能提取 UI 中不可见的属性 const stringB = await agent.aiString('当前列表的第一条记录的跳转链接', { domIncluded: true, }); ``` #### `aiLocate()` {#agentailocate} 通过自然语言描述一个元素的定位。 - 类型 ```typescript function aiLocate( locate: string | object, options?: object, ): Promise<{ rect: { left: number; top: number; width: number; height: number; }; center: [number, number]; dpr?: number; // 仅 Web:设备像素比 }>; ``` - 参数: - `locate: string | object` - 用自然语言描述的元素定位,或[使用图片作为提示词](#使用图片作为提示词)。 - `options?: object` - 可选,一个配置对象,包含: - `deepLocate?: boolean` - 是否开启[深度定位](#深度定位deeplocate)。该参数原来叫 `deepThink`,现已更名为 `deepLocate`。默认值为 false。 - `context?: string` - 本次调用的额外上下文。详见[单次调用上下文](#单次调用上下文)。 - `xpath?: string` - 目标元素的 xpath 路径,用于执行当前操作。如果提供了这个 xpath,Midscene 会优先使用该 xpath 来找到元素,然后依次使用缓存和 AI 模型。默认值为空 - `cacheable?: boolean` - 当启用 [缓存功能](../caching.mdx) 时,是否允许缓存当前 API 调用结果。默认值为 true - 返回值: - 返回一个 Promise。当元素定位成功时解析为元素定位信息。 - `rect` 在大多数定位链路里表示命中的目标元素边界。 - 有些模型只支持按点定位,不支持按元素边界定位。在这种情况下,例如 AutoGLM,这里的 `rect` 会退化成一个包含元素中心的 `8x8` 小方块,而不是真实的元素边界。 - 由于 `rect` 的表现会明显受底层模型能力影响,不建议对这个字段建立过强的边界语义依赖。 - 如果你想获得更稳定的点击位置,推荐优先使用 `center` 字段。 - `dpr` 是仅供 Web 使用的兼容字段,表示截图物理像素与 CSS 逻辑像素的比例。其他 Agent 类型不保证提供该字段。 - 示例: ```typescript const locateInfo = await agent.aiLocate('页面顶部的登录按钮'); console.log(locateInfo); ``` #### `aiAssert()` {#agentaiassert} 通过自然语言描述一个断言条件,让 AI 判断该条件是否为真。当条件不满足时,SDK 会抛出错误,并在错误信息中追加 AI 返回的详细原因。 - 类型 ```typescript function aiAssert( assertion: string | object, errorMsg?: string, options?: object, ): Promise<void>; ``` - 参数: - `assertion: string | object` - 用自然语言描述的断言条件,或[使用图片作为提示词](#使用图片作为提示词)。 - `errorMsg?: string` - 当断言失败时附加的可选错误提示信息。 - `options?: object` - 可选,一个配置对象,包含: - `domIncluded?: boolean | 'visible-only'` - 是否向模型发送精简后的 DOM 信息,一般用于提取 UI 中不可见的属性,比如图片的链接。如果设置为 `'visible-only'`,则只发送可见的元素。默认值为 false。 - `screenshotIncluded?: boolean` - 是否向模型发送截图。默认值为 true。 - `context?: string` - 本次调用的额外上下文。详见[单次调用上下文](#单次调用上下文)。 - 返回值: - 返回一个 Promise。当断言成功时解析为 void;若断言失败,则抛出一个错误,错误信息包含 `errorMsg` 以及 AI 生成的原因。 - 示例: ```typescript await agent.aiAssert('"Sauce Labs Onesie" 的价格是 7.99'); ``` :::info 断言在测试脚本中非常重要。为了降低因 AI 幻觉导致错误断言的风险(例如遗漏错误),你也可以使用 `.aiQuery` 加上常规的 JavaScript 断言来替代 `.aiAssert`。 例如,你可以这样替代上面的断言代码: ```typescript const items = await agent.aiQuery( '{name: string, price: number}[], 返回商品名称和价格列表', ); const onesieItem = items.find((item) => item.name === 'Sauce Labs Onesie'); expect(onesieItem).toBeTruthy(); expect(onesieItem.price).toBe(7.99); ``` ::: ### 观察与等待 {#observation-waiting} #### `startObserving()` {#agentstartobserving} `startObserving()` 会持续记录屏幕。它适合检查短暂出现的 UI,例如 toast、横幅和页面切换。 调用流程很简单:先开始录制,再执行页面操作,最后调用 `stop()`。`stop()` 返回 `UIObservation`。你可以查询或断言录制期间的画面。 ```typescript function startObserving(options?: { intervalMs?: number; // 采样间隔。默认 1,000 ms,最小 200 ms maxFrames?: number; // 最多保留的帧数。默认 30 watchdogMs?: number; // 最长录制时间。默认 300,000 ms;0 表示不限制 }): Promise<UIObserver>; ``` `UIObserver` 负责录制: - `observer.bufferedFrameCount: number` - 录制过程中当前缓冲的帧数。 - `observer.stop(): Promise<UIObservation>` - 停止录制,并返回 `UIObservation`。 `UIObservation` 保存已经录下的画面。调用 `stop()` 后,这些画面不再变化。 你可以调用 `aiQuery()`、`aiBoolean()`、`aiNumber()`、`aiString()`、`aiAsk()` 和 `aiAssert()`。这些方法只读取录制画面,不读取当前页面的 DOM。 因此,这些方法不支持 `domIncluded`。TypeScript 会检查这个错误。JavaScript 在运行时传入该参数,Midscene 也会抛出错误。 `UIObservation` 还包含 `frameCount`、`startedAt` 和 `endedAt`。使用完后,可以调用 `observation.dispose()` 清理临时图片。`agent.destroy()` 也会清理尚未释放的图片。 ```typescript const observer = await agent.startObserving(); await agent.aiAct('提交表单'); const observation = await observer.stop(); await observation.aiAssert('过程中弹出了成功提示 toast'); const toastCount = await observation.aiNumber('共出现了多少次成功 toast?'); await observation.dispose(); ``` 采样和资源占用: - Midscene 会优先使用连续帧源。Android 使用 scrcpy(`scrcpyConfig.enabled`),iOS 使用 WDA MJPEG(`wdaMjpegFrameSource.enabled`),Web 使用 CDP screencast。无法使用连续帧源时,Midscene 会定时截图。移动端此时的采样频率较低。 - data URL 形式的帧会在采样时写入文件。`UIObserver` 不会把所有图片长期保存在内存中。其他帧会在录制停止时分批解码并保存。查询或断言时,Midscene 才读取图片。 - 模型会接收最多 `maxFrames` 帧,以及最后一张截图。增大 `intervalMs` 可以减少采样帧,减小 `maxFrames` 可以限制帧数。观察帧会显示在报告时间线中,并带有 `Observed` 标签。 - iOS 的观察帧来自 MJPEG 流,分辨率和质量较低。最后一张截图仍使用完整质量。 #### `aiWaitFor()` {#agentaiwaitfor} 等待某个条件达成。为控制 AI 服务成本,相邻两次检查的开始时间至少间隔 `checkIntervalMs` 毫秒。 - 类型 ```typescript function aiWaitFor( assertion: string, options?: { timeoutMs?: number; checkIntervalMs?: number; context?: string; }, ): Promise<void>; ``` - 参数: - `assertion: string` - 用自然语言描述的断言条件 - `options?: object` - 可选的配置对象 - `timeoutMs?: number` - 超时时间(毫秒,默认为 15000)。每轮检查开始时都会记录时间,只要该时间点仍在超时窗口内,就会进入下一轮检查;否则视为超时 - `checkIntervalMs?: number` - 相邻两次检查开始时间的最小间隔(毫秒),默认值为 3000 - `context?: string` - 本次调用的额外上下文。详见[单次调用上下文](#单次调用上下文)。 - 返回值: - 返回一个 Promise。当断言成功时解析为 void;若超时,则抛出错误。 - 示例: ```typescript // 基本用法 await agent.aiWaitFor('界面上至少有一个耳机的信息'); // 使用自定义配置 await agent.aiWaitFor('购物车图标显示数量为 2', { timeoutMs: 30000, // 等待 30 秒 checkIntervalMs: 5000, // 每 5 秒检查一次 }); ``` :::info 考虑到 AI 服务的时间消耗,`.aiWaitFor` 并不是一个特别高效的方法。使用一个普通的 `sleep` 可能是替代 `waitFor` 的另一种方式。 ::: <a id="runyaml"></a> ### 工作流执行与上下文 {#workflow-context} #### `runYaml()` {#agentrunyaml} 执行一个 YAML 格式的自动化脚本。脚本中的 `tasks` 部分会被解析和执行,并返回所有 `.aiQuery` 调用的结果。 - 类型 ```typescript function runYaml(yamlScriptContent: string): Promise<{ result: any }>; ``` - 参数: - `yamlScriptContent: string` - YAML 格式的脚本内容 - 返回值: - 返回一个包含 `result` 属性的对象,其中包含所有 `aiQuery` 调用的结果 - 示例: ```typescript const { result } = await agent.runYaml(` tasks: - name: search weather flow: - ai: input 'weather today' in input box, click search button - sleep: 3000 - name: query weather flow: - aiQuery: "the result shows the weather info, {description: string}" `); console.log(result); ``` :::info 更多关于 YAML 脚本的信息,请参考 [Automate with Scripts in YAML](../automate-with-scripts-in-yaml)。 ::: #### `runGherkinScenario()` {#agentrungherkinscenario} 运行一个 Gherkin Scenario,并把其中的步骤映射为 Midscene Agent 调用。 :::caution Beta 此 API 从 Midscene 1.10 开始支持,目前仍处于 Beta 阶段。未来 API 可能发生变化。 ::: - 类型 ```typescript function runGherkinScenario( scenarioText: string, options?: { context?: string; abortSignal?: AbortSignal; deepThink?: 'unset' | true | false; deepLocate?: boolean; }, ): Promise<void>; ``` - 参数: - `scenarioText: string` - 一个 Gherkin scenario,或者一组不带 `Scenario:` 头部的 Gherkin 步骤 - `options?: object` - 可选的运行选项 - `context?: string` - 应用于本次运行每个步骤的 AI 补充信息。对于每个映射后的 API 调用,它会覆盖 Agent 的 API 级 context 和 `aiContexts.default` - `abortSignal?: AbortSignal` - 用于中止本次运行的可选信号 - `deepThink?: 'unset' | true | false` - 传给 `Given` 和 `When` 步骤对应的 `aiAct` - `deepLocate?: boolean` - 传给 `Given` 和 `When` 步骤对应的 `aiAct` - 返回值: - `Promise<void>` - 所有步骤执行完成后 resolve。如果某个步骤失败,错误文案会包含 Gherkin 行号、原始步骤,以及当时正在执行的 Midscene 语义动作。 - 示例: ```typescript await agent.runGherkinScenario(` Scenario: 添加待办事项 Given 待办事项页面已经打开 When 我添加一条名为“买牛奶”的待办事项 Then 待办事项列表中应该包含“买牛奶” `); ``` 关于支持规则、限制、缓存行为和 YAML 用法,请参考 [BDD 风格脚本(Gherkin)](../advanced/bdd-style-scripts-with-gherkin)。 #### `setAIContext()` {#agentsetaicontext} 设置 Agent 级共享回退 context,或某个 API 的 context。 - 类型 ```typescript function setAIContext( target: 'default' | AiApiName, context: string | undefined, ): void; ``` - 参数: - `target`:使用 `'default'` 表示所有 AI API 共享的缺省回退值,也可以传入 `'aiAct'`、`'aiTap'`、`'aiQuery'` 或 `'aiAssert'` 等 API 名称。 - `context`:提供给 AI 的补充信息,例如业务事实、规则、约束或输出要求。对 API target 传入 `''` 会屏蔽共享默认值;传入 `undefined` 会移除该 API 的覆盖值,并在已配置 `aiContexts.default` 时重新回退到该值。对 `default` target 传入 `undefined` 会移除共享回退值。 - 示例: ```typescript agent.setAIContext('default', '页面中的价格单位是美元。'); agent.setAIContext('aiQuery', '金额不返回货币符号。'); agent.setAIContext('aiQuery', ''); // aiQuery 不使用 aiContexts.default agent.setAIContext('aiQuery', undefined); // aiQuery 重新继承 aiContexts.default ``` #### `setAIActContext()`(已废弃) {#agentsetaiactcontext} 已废弃的兼容性 setter,用于设置后续 `agent.aiAct()` 或 `agent.ai()` 调用发送给 AI 模型的背景知识。新代码请使用 `agent.setAIContext('aiAct', context)`。该方法仍与后者等价,并覆盖原有的 `aiContexts.aiAct`。 对于即时操作类型的 API,比如 `aiTap()`,这个设置不会生效。 - 类型 ```typescript function setAIActContext(aiActContext: string): void; ``` - 参数: - `aiActContext: string` - 要发送给 AI 模型的背景知识。 - 示例: ```typescript await agent.setAIActContext('如果 “使用cookie” 对话框存在,先关闭它'); ``` :::info `agent.setAIActContext()` 和更早的 `agent.setAIActionContext()` 都仅为兼容而保留;新代码请使用 `agent.setAIContext('aiAct', context)`。 ::: #### `evaluateJavaScript()` {#agentevaluatejavascript} > 仅 Web Agent 可用。 这个方法允许你在 web 页面上下文中执行一段 JavaScript 代码,并返回执行结果。 - 类型 ```typescript function evaluateJavaScript(script: string): Promise<any>; ``` - 参数: - `script: string` - 要执行的 JavaScript 代码。 - 返回值: - 返回执行结果。 - 示例: ```typescript const result = await agent.evaluateJavaScript('document.title'); console.log(result); ``` #### `freezePageContext()` {#agentfreezepagecontext} 冻结当前页面上下文,使后续所有的操作都复用同一个页面快照,避免多次重复获取页面状态。在执行大量并发操作时,它可以显著提升性能。 一些注意点: * 通常情况下,你不需要使用这个方法,除非你确定“页面状态获取”是脚本性能瓶颈。 * 需要及时调用 `agent.unfreezePageContext()` 来恢复实时页面状态。 * 不要在交互类操作中使用这个方法,它会让 AI 模型无法感知到页面的最新状态,产生令人困惑的错误。 - 类型 ```typescript function freezePageContext(): Promise<void>; ``` - 返回值: - `Promise<void>` - 示例: ```typescript // 冻结页面上下文,确保多个操作看到相同的页面状态 await agent.freezePageContext(); // 执行一些操作... const results = await Promise.all([ agent.aiQuery('Username input box value'), agent.aiQuery('Password input box value'), agent.aiLocate('Login button'), ]); console.log(results); // 解冻页面上下文 await agent.unfreezePageContext(); ``` :::info 在报告中,使用冻结上下文的操作会在 Insight tab 中显示 🧊 图标。 ::: #### `unfreezePageContext()` {#agentunfreezepagecontext} 解冻页面上下文,恢复使用实时的页面状态。 - 类型 ```typescript function unfreezePageContext(): Promise<void>; ``` - 返回值: - `Promise<void>` <a id="log-screenshot"></a> <a id="agentlogscreenshot"></a> ### 报告、指标与生命周期 {#reporting-metrics-lifecycle} #### `recordToReport()` {#agentrecordtoreport} 默认在报告文件中记录当前截图并添加描述,也可以记录调用方传入的截图。 - 类型 ```typescript interface RecordToReportOptions { content?: string; /** @deprecated 请改用 screenshots。 */ screenshotBase64?: string; screenshots?: { /** * PNG/JPEG data URI,或裸 PNG base64 body。 */ base64: string; description?: string; }[]; } function recordToReport( title?: string, options?: RecordToReportOptions, ): Promise<void>; ``` - 参数: - `title?: string` - 可选,截图的标题,如果未提供,则标题为 'untitled'。 - `options?: RecordToReportOptions` - 可选,一个配置对象,包含: - `content?: string` - 截图的描述。 - `screenshots?: Array<{ base64: string; description?: string }>` - 在同一个报告条目下记录一张或多张传入的截图。设置该选项后,Midscene 不会再自动截图。`base64` 推荐使用 PNG/JPEG data URI,例如 `data:image/png;base64,...`;也可以传裸 base64 body,此时会按 PNG 处理。 - 兼容性: `screenshotBase64?: string` 仍然作为向后兼容的单截图覆盖参数被接受。新代码建议使用 `screenshots: [{ base64 }]`。`screenshots` 和 `screenshotBase64` 二者只能传入一个。 - 返回值: - `Promise<void>` - 示例: ```typescript await agent.recordToReport('登录页面', { content: '用户 A', }); const before = await page.screenshot({ encoding: 'base64' }); const after = await page.screenshot({ encoding: 'base64' }); await agent.recordToReport('结算页对比', { content: '对比提交前后的页面状态。', screenshots: [ { base64: `data:image/png;base64,${before}`, description: '提交前', }, { base64: `data:image/png;base64,${after}`, description: '提交后', }, ], }); ``` #### `_unstableLogContent()` {#agent_unstablelogcontent} 从报告文件中获取日志内容。日志内容的结构可能会在未来发生变化。 - 类型 ```typescript function _unstableLogContent(): object; ``` - 返回值: - 返回一个对象,包含日志内容。 - 示例: ```typescript const logContent = agent._unstableLogContent(); console.log(logContent); ``` **大模型用量指标** Midscene 会记录每一次大模型调用的 token 用量。你可以在运行时从 agent 读取聚合后的总量,这对于配合 Langfuse 等工具做成本可观测性非常有用。 #### `metrics` {#agentmetrics} 一个 getter,返回自 agent 创建以来累计的大模型用量快照。 - 类型 ```typescript interface UsageBucket { promptTokens: number; completionTokens: number; totalTokens: number; calls: number; } interface MidsceneUsageMetrics { totalPromptTokens: number; totalCompletionTokens: number; totalTokens: number; totalCachedInput: number; totalTimeCostMs: number; calls: number; // 按调用意图拆分:`planning`、`insight`、`default`。 byIntent: Record<string, UsageBucket>; // 按模型名称拆分。 byModel: Record<string, UsageBucket>; } ``` - 示例 ```typescript await agent.aiAct('搜索耳机'); const usage = agent.metrics; console.log(usage.totalTokens, usage.byIntent, usage.byModel); ``` #### `onLLMUsage` 选项 如需实时追踪,可在构造 agent 时传入 `onLLMUsage` 回调。每次大模型调用的用量一旦就绪即触发一次,回调参数为原始用量信息(token 数、模型名、意图、请求 id 等)。 ```typescript const agent = new PuppeteerAgent(page, { onLLMUsage: (usage) => { langfuse.event({ name: usage.intent, value: usage.total_tokens }); }, }); ``` #### `destroy()` {#agentdestroy} 完成 Agent 报告的收尾(finalization),并释放 Agent 持有的资源。 - 类型 ```typescript function destroy(): Promise<void>; ``` - 行为: - 停止正在运行的 observer。 - 调用底层 interface 的可选 `destroy()` 方法,并等待清理完成。 - 等待报告写入完成,再完成报告收尾。最后,将最终路径写入 `.reportFile`。如果没有生成报告,则写入 `undefined`。 - 首次调用完成后,再次调用不会重复清理。 - 如果 interface 清理失败,Midscene 仍会尝试完成报告收尾,然后抛出清理错误。 调用该方法后,请勿继续使用这个 Agent。 :::warning 资源清理范围 `Agent.destroy()` 会停止 Agent、完成报告收尾,并释放 Midscene 持有的控制资源。 它通常不会关闭目标页面或断开物理设备,但部分平台会结束相应的自动化会话或连接。 具体行为请参见各平台的 `destroy()` 说明。 ::: **属性** #### `.reportFile` {#agentreportfile} 当前报告文件的路径。它的类型是 `string | null | undefined`。 首次更新报告前,该属性没有可用值。以下情况也不会生成报告路径:关闭报告生成功能、Agent 在没有文件系统访问能力的浏览器环境中运行,或 Agent 没有产生 execution。 报告路径可用后,报告内容仍可能继续更新。使用最终报告前,请调用 [`await agent.destroy()`](#agentdestroy)。该方法会等待报告写入完成,完成报告的 收尾,并将最终路径写入 `.reportFile`。 ## 单次调用上下文 context 是提供给 AI 的补充信息,例如业务事实、判断或操作规则、约束以及输出要求。它用于补充 API 的主提示词,而不是替代主提示词。 使用 `aiContexts.default` 为所有 AI API 提供共享的缺省回退值,使用 `aiContexts[apiName]` 配置某个 API,再使用单次调用的 `options.context` 配置某一次请求。如果配置了 `aiContexts.default`,只有两个更具体的层级都没有提供值时才会选用它;它不会与其他层级自动合并。 ```typescript const defaultContext = '页面中划线价格表示原价,普通价格表示当前售价,所有价格单位均为美元。'; const agent = new PlaywrightAgent(page, { aiContexts: { default: defaultContext, aiAct: '', // 显式不使用默认 context aiQuery: '提取金额时只返回数字,不包含货币符号。', aiBoolean: `${defaultContext}\n\n仅当当前售价低于原价时,才判断商品正在促销。`, }, }); const isOnSale = await agent.aiBoolean('这个商品是否正在促销?'); ``` 最终用户 context 为 `options.context ?? aiContexts[apiName] ?? aiContexts.default`。这些层级互相覆盖,不会自动合并。`''` 是一个已定义值,会清空低优先级用户 context;`undefined` 或未配置字段则继续使用下一个已配置的回退值。如需组合多个 context 片段,请像上例一样显式拼接字符串。 支持的 API key 包括 `aiAct`、`aiTap`、`aiRightClick`、`aiDoubleClick`、`aiHover`、`aiInput`、`aiKeyboardPress`、`aiScroll`、`aiPinch`、`aiLongPress`、`aiClearInput`、`aiLocate`、`aiQuery`、`aiBoolean`、`aiNumber`、`aiString`、`aiAsk`、`aiAssert` 和 `aiWaitFor`。`ai` 与已废弃的 `aiAction` 别名使用 `aiAct` context。 <a id="深度定位deeplocate"></a> ### 共享类型 {#shared-types} **定位选项:深度定位(`deepLocate`)** `deepLocate` 是一个可选参数,适用于所有需要元素定位的 API(`aiAct`、`aiTap`、`aiHover`、`aiInput`、`aiKeyboardPress`、`aiScroll`、`aiDoubleClick`、`aiRightClick`、`aiLocate` 等)。 开启后,Midscene 会调用 AI 模型两次以精确定位元素,从而提升准确性。这在目标元素面积较小、难以和周围元素区分时非常有用。对于新一代模型(如 Qwen3.x / Doubao 2.0 / Gemini 3.5),大多数场景下带来的收益不明显,建议按需开启。 - **默认值**:`false` ```typescript // 开启 deepLocate 精确定位较难识别的元素 await agent.aiTap('右上角购物车图标', { deepLocate: true }); ``` :::note 历史上,`deepThink` 这个名字在不同 API 中承担过两种含义: - 在 `aiAct()` 中,`deepThink` 从一开始就表示**规划模式**,用于引导任务拆解和专注规划的思考过程。详情请参考 [`deepThink` 规划模式](#aiact-deepthink)。 - 在 `aiTap`、`aiHover` 等单步操作方法中,旧的 `deepThink` 表示**定位增强**,等同于现在的 `deepLocate`。 为了区分这两种语义,自 v1.5.1 起,`deepThink` 只用于表示规划模式,定位增强统一命名为 `deepLocate`。 - 对于 `aiAct()`,可以同时使用 `deepThink` 和 `deepLocate`:`deepThink` 控制规划模式,`deepLocate` 控制本节描述的深度定位。 - 对于 `aiTap`、`aiHover` 等单步操作方法,如果需要提升定位精确度,推荐使用语义更清晰的 `deepLocate` 参数;旧的 `deepThink` 参数仍然兼容,语义等同于 `deepLocate`。 ::: <a id="prompting-with-images"></a> <a id="使用图片作为提示词"></a> <a id="通过图像提示"></a> **使用图片的提示词输入** 你可以在提示词中使用图片作为补充,来描述无法通过自然语言表达的内容。 使用图片作为提示词时,提示词的参数格式如下: ```javascript { // 提示词文本,其中可提及需要使用的图片 prompt: string, // 提示词中提到的图片 images?: { // 图片名称,需要和提示词文本中提到的图片名称对应 name: string, // 图片 url,可以是本地图片路径、Base64 字符串,或者图片的 http 链接 url: string }[] // 开启该选项后,http 格式的图片链接会被转化为 Base64 编码发送给大模型,适用于图片链接不是公开可访问的情况。 convertHttpImage2Base64?: boolean } ``` - 示例一:使用图片描述点击位置 ```javascript await agent.aiTap({ prompt: '指定 logo', images: [ { name: '指定 logo', url: 'https://github.githubassets.com/assets/GitHub-Mark-ea2971cee799.png', }, ], }); ``` - 示例二:使用图片进行页面断言 ```javascript await agent.aiAssert({ prompt: '页面上是否存在指定 logo', images: [ { name: '指定 logo', url: 'https://github.githubassets.com/assets/GitHub-Mark-ea2971cee799.png', }, ], }); ``` - 示例三:使用图片引导操作(`aiAct`) ```javascript await agent.aiAct({ prompt: '点击与参考 logo 一致的图标', images: [ { name: '指定 logo', url: 'https://github.githubassets.com/assets/GitHub-Mark-ea2971cee799.png', }, ], }); ``` **图片尺寸的注意事项** 请遵守模型提供商对图片体积和尺寸的限制。过大或过小的图片都可能被拒绝,准确限制请以模型提供商的文档为准。 ### 报告工具 {#reporting-utilities} **`ReportMergingTool`** 每个自动化工作流都可以生成独立报告。`ReportMergingTool` 可以将这些报告合并,便于统一查看和管理。合并结果可能是独立的 HTML 文件,也可能是包含 `index.html` 和外部截图资源的目录。 #### `new ReportMergingTool()` 创建一个报告合并工具实例。 - 示例: ```typescript import { ReportMergingTool } from '@midscene/core/report'; const reportMergingTool = new ReportMergingTool(); ``` #### `.append()` 将自动化报告添加到待合并列表中。通常在每个自动化工作流结束后调用此方法。 - 类型 ```typescript type SkippedReportFileAttributes = Omit< ReportFileAttributes, 'testStatus' > & { testStatus: 'skipped'; }; type ReportFileWithAttributes = | { reportFilePath: string; reportAttributes: ReportFileAttributes; } | { reportFilePath?: undefined; reportAttributes: SkippedReportFileAttributes; }; function append(reportInfo: ReportFileWithAttributes): void; ``` - 参数: - `reportInfo: ReportFileWithAttributes` - 报告信息对象,包含: - `reportFilePath: string | undefined` - 报告文件的路径,通常是 `agent.reportFile`。只有当 `reportAttributes.testStatus` 为 `'skipped'` 时,才能省略该字段。其他状态缺少路径时,`append()` 会抛出错误 - `reportAttributes: object` - 报告属性 - `testId: string` - 自动化工作流的唯一标识符 - `testTitle: string` - 自动化工作流标题 - `testDescription: string` - 自动化工作流描述 - `testDuration: number` - 自动化工作流执行时长(毫秒) - `testStatus: 'passed' | 'failed' | 'timedOut' | 'skipped' | 'interrupted'` - 自动化状态 - 返回值: - `void` - 示例: ```typescript import type { TestStatus } from '@midscene/core'; // 在 afterEach 钩子中添加报告 afterEach(async (ctx) => { let workflowStatus: TestStatus = 'passed'; if (ctx.task.result?.state === 'skip') { workflowStatus = 'skipped'; } else if (ctx.task.result?.errors?.[0]?.message.includes('timed out')) { workflowStatus = 'timedOut'; } else if (ctx.task.result?.state === 'fail') { workflowStatus = 'failed'; } // 读取最终路径前,先完成报告的 finalization。 await agent.destroy(); const reportFilePath = agent.reportFile; const reportAttributes = { testId: ctx.task.name, testTitle: ctx.task.name, testDescription: '自动化工作流描述', testDuration: Date.now() - startTime, }; if (!reportFilePath) { if (workflowStatus !== 'skipped') { throw new Error('Midscene 报告未生成'); } reportMergingTool.append({ reportAttributes: { ...reportAttributes, testStatus: 'skipped', }, }); return; } reportMergingTool.append({ reportFilePath, reportAttributes: { ...reportAttributes, testStatus: workflowStatus, }, }); }); ``` 调用 `.mergeReports()` 前,应完成所有报告相关 Agent 的收尾。 #### `.mergeReports()` 执行报告合并操作,将所有添加的报告合并为一份报告。 - 类型 ```typescript function mergeReports( reportFileName?: 'AUTO' | string, opts?: { rmOriginalReports?: boolean; overwrite?: boolean; outputDir?: string; }, ): string | null; ``` - 参数: - `reportFileName?: 'AUTO' | string` - 合并后的报告文件名 - 默认为 `'AUTO'`,自动生成文件名 - 可以指定自定义文件名(不需要 `.html` 后缀) - `opts?: object` - 可选配置对象 - `rmOriginalReports?: boolean` - 是否删除原始报告文件,默认为 `false` - `overwrite?: boolean` - 如果目标文件已存在是否覆盖,默认为 `false` - `outputDir?: string` - 合并报告的输出目录。相对路径从当前工作目录开始解析。默认输出到 `midscene_run/report/` - 返回值: - 成功时返回合并报告的入口 HTML 路径 - 如果没有添加任何报告,返回 `null` - 所有源报告均使用 `single-html` 时,输出路径为 `<outputDir>/<reportFileName>.html` - 任一源报告使用 `html-and-external-assets` 时,输出路径为 `<outputDir>/<reportFileName>/index.html`,同时输出截图资源 - 示例: ```typescript // 基本用法 - 使用自动生成的文件名 afterAll(() => { reportMergingTool.mergeReports(); }); // 指定自定义文件名 afterAll(() => { reportMergingTool.mergeReports('my-automation-report'); }); // 合并后删除原始报告 afterAll(() => { reportMergingTool.mergeReports('my-automation-report', { rmOriginalReports: true, }); }); // 覆盖已存在的报告文件 afterAll(() => { reportMergingTool.mergeReports('my-automation-report', { overwrite: true, }); }); // 将合并报告写入自定义目录 afterAll(() => { reportMergingTool.mergeReports('my-automation-report', { outputDir: './test-results', }); }); ``` #### `.clear()` 清空待合并的报告列表。如果需要在同一个实例中进行多次合并操作,可以使用此方法清空之前的报告列表。 - 类型 ```typescript function clear(): void; ``` - 返回值: - `void` - 示例: ```typescript reportMergingTool.mergeReports('first-batch'); reportMergingTool.clear(); // 清空列表 // 继续添加新的报告... ``` ## Web 浏览器(`@midscene/web`) {#web} 当你需要自定义 Midscene 的浏览器自动化 Agent,或查阅 Web 专属构造参数时,请参考本篇。关于通用参数(报告、Hook、缓存等),请阅读[API 参考(通用)](#common)。 ### Web Action Space(动作空间) PuppeteerAgent、PlaywrightAgent 和 Chrome Bridge 共用一套 Action Space,Midscene Agent 在规划任务时可以使用这些操作: - `Tap` —— 左键点击元素。 - `RightClick` —— 右键点击元素。 - `DoubleClick` —— 双击元素。 - `Hover` —— 悬停目标元素。 - `Input` —— 输入文本,支持 `replace`/`typeOnly`/`clear` 模式(`append` 是 `typeOnly` 的已废弃别名)。 - `KeyboardPress` —— 按下指定键(可在按键前先聚焦目标)。 - `Scroll` —— 以元素为起点或从屏幕中央滚动,支持滚动到顶/底/左/右。 - `DragAndDrop` —— 从一个元素拖拽到另一个元素。 - `LongPress` —— 长按目标元素,可选自定义时长。 - `Swipe` —— 触摸式滑动(开启 `enableTouchEventsInActionSpace` 时可用)。 - `Pinch` —— 双指缩放手势,用于放大/缩小(开启 `enableTouchEventsInActionSpace` 时可用;Playwright 仅支持 Chromium 内核浏览器)。 - `ClearInput` —— 清空输入框内容。 - `Navigate` —— 在当前标签页打开指定 URL。 - `Reload` —— 刷新当前页面。 - `GoBack` —— 浏览器后退。 **生命周期与所有权** Puppeteer 和 Playwright Page/Browser Agent 继承通用的 [`destroy()`](#agentdestroy) 方法。调用该方法会完成 Midscene 报告收尾, 并清理 Agent 持有的资源。该方法不会关闭 Agent 使用的 Page、Browser 或 BrowserContext。 ### PuppeteerPageAgent / PuppeteerAgent {#puppeteer-agent} 当你需要在 Puppeteer 控制的浏览器里复用 Midscene 的 AI 操作能力时使用。 `PuppeteerPageAgent` 绑定单个 Puppeteer `Page`。`PuppeteerAgent` 仍作为兼容别名保留。 **导入** ```ts import { PuppeteerPageAgent } from '@midscene/web/puppeteer'; ``` **构造器** ```ts const agent = new PuppeteerPageAgent(page, { // 浏览器特有配置... }); ``` **浏览器特有选项** 除了通用 Agent 参数,Puppeteer 还提供: - `forceSameTabNavigation: boolean` —— 限制始终在当前标签页内导航,默认 `true`。 - `waitForNavigationTimeout: number` —— 当操作触发页面跳转时的最长等待时间,默认 `5000`(设为 `0` 表示不等待)。 - `waitForNetworkIdleTimeout: number` —— 每次操作后等待网络空闲的时间,默认 `2000`(设为 `0` 关闭)。 - `enableTouchEventsInActionSpace: boolean` —— 在动作空间里增加触摸手势(如滑动),用于需要触摸事件的页面,默认 `false`。 - `keyboardTypeDelay: number` —— 按键间延迟,单位为毫秒。取值必须是有限的非负数。在 `'legacy'` 模式下,Puppeteer 的 `page.keyboard.type` 负责处理正数延迟。默认值为 `undefined`。此时,Midscene 不传该选项,而是使用 Puppeteer 自身的默认值。通常无须配置。只有受控输入框在快速输入时丢字,才需要调大该值,例如 `80`。 - `inputStrategy: 'legacy' | 'sequential' | 'bulk'` —— `Input` 动作的默认文本输入策略。使用 `'bulk'` 可以一次插入完整字符串。使用 `'sequential'` 可以强制逐个 Unicode 码点输入。`'bulk'` 要求不设置 `keyboardTypeDelay` 或将其设为 `0`。如需延迟输入,请使用 `'sequential'`。默认值为 `'legacy'`。 - `forceChromeSelectRendering: boolean` —— 强制 `select` 元素使用 Chrome 的 base-select 样式,避免系统原生样式导致截图/元素提取不可见;需要 Puppeteer > `24.6.0`。默认值为 `true`;如需关闭(例如旧版 Chrome/Puppeteer)可设为 `false`。 - `customActions: DeviceAction[]` —— 添加额外的自定义动作,让 Agent 可以调用你定义的领域特定动作。 **使用说明** :::info - 每个页面一个 Agent:默认情况下(`forceSameTabNavigation: true`)Midscene 会拦截新标签并在当前页打开,便于调试;若想保留浏览器原生的新标签行为可设为 `false`,并自行给每个页面创建新的 `PuppeteerAgent`。如果需要同一个 Agent 管理浏览器级别的页面切换,请使用 `PuppeteerBrowserAgent`。 - `PuppeteerAgent` / `PuppeteerPageAgent` 为了兼容性仍保持 page-scoped 语义,不会暴露浏览器级别的页面切换能力;需要时请显式选择 `PuppeteerBrowserAgent`。 - 更多交互方法请参考 [API 参考(通用)](#interaction-methods)。 ::: ### PuppeteerBrowserAgent 当一个 Midscene Agent 需要管理 Puppeteer 浏览器内的页面切换时,使用 `PuppeteerBrowserAgent`。它绑定 browser 实例,维护一个 active page,并且可以选择自动跟随新打开的页面。 ```ts const agent = new PuppeteerBrowserAgent(browser, page, { autoFollowNewPage: true, }); ``` - 构造函数:`new PuppeteerBrowserAgent(browser, page, options?)` —— 你要显式指定初始 active page 时使用。 - 工厂方法:`PuppeteerBrowserAgent.create(browser, options?)` —— 你希望 Midscene 自动选择或创建初始 active page 时使用。它会优先使用 `initialPage`,否则复用浏览器里的第一个页面,或者创建一个新页面。 - `initialPage: Page` —— 工厂方法使用的初始 Puppeteer 页面。 - `autoFollowNewPage: boolean` —— 浏览器打开新页面时是否自动切换 active page,默认 `false`。 - `newPageTimeout: number` —— `waitForNewPage` 的超时时间,默认 `5000`。 - `activePage: Page` —— Browser Agent 当前控制的页面。 - `pages()` —— 列出绑定 browser 中的页面。 - `newPage()` —— 创建新页面并将其设为 active page。 - `setActivePage(page: Page)` —— 显式指定 Browser Agent 接下来控制哪个 Puppeteer 页面。 - `waitForNewPage(action?, options?)` —— 等待新打开的页面,但不会隐式切换 active page。 <a id="web-快速上手"></a> <a id="web-连接远程-puppeteer-浏览器"></a> **另请参阅** - [集成到 Puppeteer](../integrate-with-puppeteer) 获取安装、Fixture 与远程 CDP 配置。 ### PlaywrightPageAgent / PlaywrightAgent {#playwright-agent} 在 Playwright 浏览器中使用 Midscene 以支持带 AI 的测试或自动化流程。 `PlaywrightPageAgent` 绑定单个 Playwright `Page`。`PlaywrightAgent` 仍作为兼容别名保留。 **导入** ```ts import { PlaywrightPageAgent } from '@midscene/web/playwright'; ``` **构造器** ```ts const agent = new PlaywrightPageAgent(page, { // 浏览器特有配置... }); ``` **浏览器特有选项** - `forceSameTabNavigation: boolean` —— 强制在当前标签页内执行,默认 `true`。 - `waitForNavigationTimeout: number` —— 等待导航完成的时间,默认 `5000`(设为 `0` 关闭)。 - `waitForNetworkIdleTimeout: number` —— 每次操作后等待网络空闲的时间,默认 `2000`(设为 `0` 关闭)。 - `enableTouchEventsInActionSpace: boolean` —— 在动作空间里增加触摸手势(如滑动),用于需要触摸事件的页面,默认 `false`。 - `keyboardTypeDelay: number` —— 按键间延迟,单位为毫秒。取值必须是有限的非负数。在 `'legacy'` 模式下,Playwright 的 `page.keyboard.type` 负责处理正数延迟。默认值为 `undefined`。此时,Midscene 不传该选项,而是使用 Playwright 自身的默认值。通常无须配置。只有受控输入框在快速输入时丢字,才需要调大该值,例如 `80`。 - `inputStrategy: 'legacy' | 'sequential' | 'bulk'` —— `Input` 动作的默认文本输入策略。使用 `'bulk'` 可以一次插入完整字符串。使用 `'sequential'` 可以强制逐个 Unicode 码点输入。`'bulk'` 要求不设置 `keyboardTypeDelay` 或将其设为 `0`。如需延迟输入,请使用 `'sequential'`。默认值为 `'legacy'`。 - `forceChromeSelectRendering: boolean` —— 强制 `select` 元素使用 Chrome 的 base-select 样式,避免系统原生样式导致截图/元素提取不可见;需要 Playwright ≥ `1.52.0`。默认值为 `true`;如需关闭(例如旧版 Chrome/Playwright)可设为 `false`。 - `customActions: DeviceAction[]` —— 添加额外的自定义动作,让 Agent 可以调用你定义的领域特定动作。 **使用说明** :::info - 每个页面一个 Agent:默认 `forceSameTabNavigation` 为 `true`,Midscene 会拦截新标签确保稳定性;如需浏览器原生新标签行为请设为 `false`,并自行给每个页面创建新的 `PlaywrightAgent`。如果需要同一个 Agent 管理 browser context 级别的页面切换,请使用 `PlaywrightBrowserAgent`。 - `PlaywrightAgent` / `PlaywrightPageAgent` 为了兼容性仍保持 page-scoped 语义,不会暴露浏览器级别的页面切换能力;需要时请显式选择 `PlaywrightBrowserAgent`。 - 更多交互方法请参考 [API 参考(通用)](#interaction-methods)。 ::: ### PlaywrightBrowserAgent 当一个 Midscene Agent 需要管理 Playwright browser context 内的页面切换时,使用 `PlaywrightBrowserAgent`。它绑定 browser context,维护一个 active page,并且可以选择自动跟随新打开的页面。 ```ts const agent = new PlaywrightBrowserAgent(context, page, { autoFollowNewPage: true, }); ``` - 构造函数:`new PlaywrightBrowserAgent(context, page, options?)` —— 你要显式指定初始 active page 时使用。 - 工厂方法:`PlaywrightBrowserAgent.create(context, options?)` —— 你希望 Midscene 自动选择或创建初始 active page 时使用。它会优先使用 `initialPage`,否则复用 context 里的第一个页面,或者创建一个新页面。 - `initialPage: Page` —— 工厂方法使用的初始 Playwright 页面。 - `autoFollowNewPage: boolean` —— context 打开新页面时是否自动切换 active page,默认 `false`。 - `newPageTimeout: number` —— `waitForNewPage` 的超时时间,默认 `5000`。 - `activePage: Page` —— Browser Agent 当前控制的页面。 - `pages()` —— 列出绑定 browser context 中的页面。 - `newPage()` —— 创建新页面并将其设为 active page。 - `setActivePage(page: Page)` —— 显式指定 Browser Agent 接下来控制哪个 Playwright 页面。 - `waitForNewPage(action?, options?)` —— 等待新打开的页面,但不会隐式切换 active page。 <a id="web-playwright-快速上手"></a> <a id="web-使用-midscene-fixture-扩展-playwright-测试"></a> **另请参阅** - [集成到 Playwright](../integrate-with-playwright) 获取安装、Fixture 用法和更多配置。 ### Chrome Bridge Agent {#chrome-bridge-agent} Bridge mode 允许 Midscene 通过扩展控制当前桌面 Chrome 标签页,而无需再启动独立的自动化浏览器。 **导入** ```ts import { AgentOverChromeBridge } from '@midscene/web/bridge-mode'; ``` **构造器** ```ts const agent = new AgentOverChromeBridge({ allowRemoteAccess: false, // 其他桥接配置... }); ``` **桥接配置** - `closeNewTabsAfterDisconnect?: boolean` —— 是否在销毁时自动关闭桥接创建的新标签页,默认 `false`。 - `allowRemoteAccess?: boolean` —— 是否允许远程机器连接,默认 `false`(监听 `127.0.0.1`)。 - `host?: string` —— 自定义 Bridge Server 的监听地址,优先级高于 `allowRemoteAccess`。 - `port?: number` —— Bridge Server 端口,默认 `3766`。 - `enableWaterFlowAnimation?: boolean` —— Midscene 控制页面时是否显示蓝色动态边框和鼠标指示,默认 `true`;如需截取不含视觉覆盖层的截图,可设为 `false`。 - `keyboardTypeDelay?: number` —— 按键间延迟,单位为毫秒。取值必须是有限的非负数。`legacy` Bridge 输入沿用零延迟的 CDP 行为。如需在转发 Unicode 码点时应用延迟,请将 `inputStrategy` 设为 `'sequential'`。 - `inputStrategy?: 'legacy' | 'sequential' | 'bulk'` —— 默认文本输入策略。`'sequential'` 逐个转发 Unicode 码点。`'bulk'` 只转发一次 `insertText` 操作,并且要求不设置 `keyboardTypeDelay` 或将其设为 `0`。默认值为 `'legacy'`。 完整安装与能力说明,见 [Chrome 插件桥接模式](../bridge-mode#constructor)。 **使用说明** :::info 请先调用 `connectCurrentTab` 或 `connectNewTabWithUrl` 再执行其他操作。每个 `AgentOverChromeBridge` 只能连接一个标签页;`destroy` 之后需要重新创建实例。 ::: **方法** <a id="web-connectcurrenttab"></a> **`connectCurrentTab()`** ```ts function connectCurrentTab(options?: { forceSameTabNavigation?: boolean; }): Promise<void>; ``` - `options.forceSameTabNavigation`(默认 `true`)会拦截新标签并在当前页打开,方便调试;若想保留新标签行为可设为 `false`,但需要为每个新标签创建新的 Agent。 - 连接当前激活标签页,成功后返回 `Promise<void>`,如果扩展未允许连接会报错。 <a id="web-connectnewtabwithurl"></a> **`connectNewTabWithUrl()`** ```ts function connectNewTabWithUrl( url: string, options?: { forceSameTabNavigation?: boolean }, ): Promise<void>; ``` - `url` —— 新标签页要打开的地址。 - `options` —— 与 `connectCurrentTab` 相同。 - 打开新标签并连接成功后返回 `Promise<void>`。 <a id="web-destroy"></a> **`destroy()`** ```ts function destroy(closeNewTabsAfterDisconnect?: boolean): Promise<void>; ``` - `closeNewTabsAfterDisconnect` —— 运行时覆盖构造器配置,为 `true` 时销毁时关闭桥接创建的新标签页。 - 包含[通用 Agent 清理行为](#agentdestroy),其中包括报告收尾。 - 清理桥接连接、本地服务和 Agent 报告后,返回 `Promise<void>`。 <a id="web-打开新的桌面标签页"></a> <a id="web-附着到当前标签页"></a> **另请参阅** - [API 参考(通用)](#interaction-methods) 查看共享的 Agent 方法。 - [桥接模式](../bridge-mode) 了解扩展安装、执行顺序与 YAML 用法。 ## Android(`@midscene/android`) {#android} 当你需要自定义设备行为、把 Midscene 接入框架,或排查 adb 问题时,请查阅本节。关于通用构造函数(报告、Hook、缓存等)的参数说明,请参考平台无关的 [API 参考](#common)。 ### Android Action Space(动作空间) `AndroidDevice` 使用以下动作空间,Midscene Agent 在规划任务时可以使用这些操作: - `Tap` —— 点击元素。 - `DoubleClick` —— 双击元素。 - `Input` —— 输入文本,支持 `replace`/`typeOnly`/`clear` 模式(`append` 是 `typeOnly` 的已废弃别名)。支持可选参数 `autoDismissKeyboard`、`keyboardTypeDelay` 和 `inputStrategy`。 - `Scroll` —— 以元素为起点或从屏幕中央向上/下/左/右滚动,支持滚动到顶/底/左/右。 - `DragAndDrop` —— 从一个元素拖拽到另一个元素。 - `KeyboardPress` —— 按下指定键位。 - `LongPress` —— 长按目标元素,可选自定义时长。 - `PullGesture` —— 上拉或下拉(如下拉刷新),可选距离与持续时间。 - `Pinch` —— 双指缩放手势。`scale > 1` 放大,`scale < 1` 缩小。 - `ClearInput` —— 清空输入框内容。 - `Launch` —— 打开网页或 `package/.Activity`。 - `Terminate` —— 按包名强制停止应用。 - `RunAdbShell` —— 执行原始 `adb shell` 命令。此动作默认启用。将 `exposeRunAdbShellAction` 设为 `false`,可从动作空间中移除此动作。 - `AndroidBackButton` —— 触发系统返回。 - `AndroidHomeButton` —— 回到桌面。 - `AndroidRecentAppsButton` —— 打开多任务/最近应用。 ### AndroidDevice {#androiddevice} 创建一个可供 AndroidAgent 驱动的 adb 设备实例。 **导入** ```ts import { AndroidDevice, getConnectedDevices, getConnectedDevicesWithDetails, } from '@midscene/android'; ``` **构造函数** ```ts const device = new AndroidDevice(deviceId, { // 设备参数... }); ``` **设备选项** - `deviceId: string` —— 来自 `adb devices` 或 `getConnectedDevices()` 的值。 - `autoDismissKeyboard?: boolean` —— 输入完成后自动隐藏键盘,默认 `true`。 - `keyboardDismissStrategy?: 'esc-first' | 'back-first'` —— 关闭键盘的顺序,默认 `'esc-first'`。 - `keyboardTypeDelay?: number` —— 按键间延迟,单位为毫秒。取值必须是有限的非负数。在 `'legacy'` 模式下,正数延迟会让原生 `input text` 路径逐个 Unicode 码点输入。为兼容旧行为,yadb 仍会忽略该选项。如需拆分 yadb 输入,请将 `inputStrategy` 设为 `'sequential'`。 - `inputStrategy?: 'legacy' | 'sequential' | 'bulk'` —— `Input` 动作的默认文本输入策略。`'sequential'` 会针对每个 Unicode 码点调用一次 ADB 或 yadb。所选 IME 支持时,`'bulk'` 只调用一次底层文本接口。`'bulk'` 要求不设置 `keyboardTypeDelay` 或将其设为 `0`。如需延迟输入,请使用 `'sequential'`。默认值为 `'legacy'`。 - `androidAdbPath?: string` —— adb 可执行文件的自定义路径。 - `remoteAdbHost?: string` / `remoteAdbPort?: number` —— 指向远程 adb server。 - `imeStrategy?: 'always-yadb' | 'yadb-for-non-ascii'` —— 控制何时调用 [yadb](https://github.com/ysbing/yadb) 进行文本输入,默认 `'yadb-for-non-ascii'`。 - `'yadb-for-non-ascii'`(默认)—— 对 Unicode 字符(包括 Latin Unicode 如 ö、é、ñ)、中文、日文以及格式化符号(如 %s、%d)使用 yadb。纯 ASCII 文本使用更快的原生 `adb input text`。 - `'always-yadb'` —— 对所有文本输入始终使用 yadb,提供最大兼容性,但对纯 ASCII 文本稍慢。 - `screenshotStrategy?: 'auto' | 'always-yadb'` —— 控制截图方式,默认 `'auto'`。 - `'auto'`(默认)—— 先尝试 `adb.takeScreenshot`,失败后回退到 shell `screencap`;若 `screencap` 执行失败,则改用 yadb 工具。scrcpy 仅当 `scrcpyConfig.enabled` 开启时才会优先尝试。该策略不会分析截图内容:即使某个截图方法执行成功但返回全黑图片,也不会自动切换到 yadb;只有当前一种截图方法执行失败时,才会尝试下一种方法。 - `'always-yadb'` —— 绕过默认的 `auto` 截图流程(`adb.takeScreenshot`、`screencap`,以及开启时的 scrcpy),直接使用 [yadb](https://github.com/ysbing/yadb) 截图。适用于 `screencap` 对安全页面(`FLAG_SECURE`)截出黑屏、但 yadb 能正常截取的场景——能否截取取决于 Android 版本、ROM、root/hook 环境和设备配置,请以实际设备测试结果为准。yadb 只能截取默认显示器(`displayId=0`);如果同时设置该策略和非零 `displayId`,Midscene 会抛出错误。也可通过环境变量 `MIDSCENE_ANDROID_SCREENSHOT_STRATEGY=always-yadb` 设置。 - `displayId?: number` —— 在设备镜像多个屏幕时,选择特定虚拟屏幕。 - `exposeRunAdbShellAction?: boolean` —— 是否在动作空间中暴露内置的 `RunAdbShell` 动作。默认值:`true`。设为 `false` 后,AI 规划器、YAML 脚本和 `agent.runAdbShell()` 都无法执行 ADB shell 命令。 - `customActions?: DeviceAction[]` —— 添加额外的自定义动作,让 Agent 可以调用你定义的领域特定动作。 - `screenshotResizeScale?: number` —— **已废弃。** 此选项已移除,不再生效。如需控制发送给 AI 模型的截图尺寸,请使用 `AgentOpt` 中的 `screenshotShrinkFactor`。 - `minScreenshotBufferSize?: number` —— 截图 buffer 大小校验阈值,单位为字节;低于该值的 buffer 会被视为截图采集失败或已损坏。默认 `1024`(1KB)。设置为 `0` 仅跳过此大小校验;Midscene 仍会拒绝空 buffer 和无效图片格式。 - `alwaysRefreshScreenInfo?: boolean` —— 每一步都重新查询旋转角度与屏幕尺寸,默认 `false`。 <a id="scrcpy"></a> **scrcpy 配置和状态方法** - `scrcpyConfig?: object` —— scrcpy 截图配置,默认关闭。 - `enabled?: boolean` —— 是否启用 scrcpy 截图,默认 `false`。 - `maxSize?: number` —— 截图的最大宽度或高度。scrcpy 暂时不可用时,ADB/yadb 回退截图也会应用相同限制,避免规划和报告中的图片恢复为设备原始分辨率。必须使用非负整数;默认 `0`,表示不缩放。 - `videoBitRate?: number` —— scrcpy H.264 编码码率,单位为 bps,默认 `100000000`。调整该值是在编码带宽和截图细节之间取舍;只有独立的链路测量表明有需要时才应调优,并验证识别质量,不能仅凭 freshness 超时告警调整。 - `idleTimeoutMs?: number` —— 空闲连接的断开时间,单位为毫秒,默认 `30000`;设为 `0` 时禁用。 - `videoResetFrameTimeoutMs?: number` —— scrcpy 接受流内视频重置后,等待新鲜关键帧的时间,单位为毫秒。必须使用正整数,默认 `800`。仅当设备的 display capture 重启长尾更慢时再增大该值。 - `device.getScrcpyStatus()` —— 返回 `enabled`、`connected`、`lastError` 和 `retryAfter`。 - `device.retryScrcpy(): Promise<void>` —— 跳过冷却时间,立即重试 scrcpy 连接。 **使用说明** - 可以使用 `getConnectedDevices()` 发现设备,`udid` 与 `adb devices` 输出一致。 - 可以使用 `remoteAdbHost/remoteAdbPort` 连接远程 adb;如果 adb 不在 PATH 中,可设置 `androidAdbPath`。 <a id="android-device-destroy"></a> **`destroy()`** ```ts function destroy(): Promise<void>; ``` 释放 `AndroidDevice` 持有的资源,其中包括正在运行的 scrcpy 连接。同时,清除 ADB 状态。该方法可以重复调用。调用完成后,这个 Device 实例不能再执行 ADB 命令,但物理设备仍与 ADB server 保持连接。 调用 [`AndroidAgent.destroy()`](#agentdestroy) 时,会自动执行该方法。每个 `AndroidDevice` 实例只属于一个 `AndroidAgent`。 ### AndroidAgent {#androidagent} 将 Midscene 的 AI 规划能力绑定到 AndroidDevice,实现 UI 自动化。 **导入** ```ts import { AndroidAgent } from '@midscene/android'; ``` **构造函数** ```ts const agent = new AndroidAgent(device, { // 通用 Agent 参数... }); ``` **Android 特有选项** - `appNameMapping?: Record<string, string>` —— 将友好的应用名称映射到包名。当你在 `launch(target)` 里传入应用名称时,Agent 会在此映射中查找对应的包名;若未找到映射,则按原样尝试启动 `target`。 - 其余字段与[通用构造参数](#common-parameters)一致,包括 `generateReport`、`reportFileName`、`aiContexts`、`aiActContext`、`modelConfig`、`cache`、`createOpenAIClient` 和 `onTaskStartTip` 等。 **使用说明** :::info - 一个设备连接对应一个 Agent。 - `customActions` 用于向 `AndroidDevice` 添加额外的自定义动作。可以将它传给 Device 构造函数或 `agentFromAdbDevice()`。 - `launch`、`terminate`、`runAdbShell` 等 Android 专属辅助函数也可在 YAML 脚本中使用,语法见 [Android 平台特定动作](../automate-with-scripts-in-yaml#the-android-part)。 - 通用交互方法请查阅 [API 参考(通用)](#interaction-methods)。 ::: **Android 特有方法** <a id="android-agentlaunch"></a> **`agent.launch()`** 启动网页或原生 Android activity/package。 ```ts function launch(target: string): Promise<void>; ``` - `target: string` —— 可以是网页 URL,也可以是 `package/.Activity` 形式的字符串,例如 `com.android.settings/.Settings`,也可以是应用包名、URL 或应用名称。若传入应用名称且在 `appNameMapping` 中存在映射,将自动解析为对应包名;若未找到映射,则直接按 `target` 启动。 <a id="android-agentrunadbshell"></a> **`agent.runAdbShell()`** 通过连接的设备运行原始的 `adb shell` 命令。传入的内容只需要包含 shell 命令本身,不要包含 `adb shell` 前缀。 ```ts function runAdbShell(command: string, opt?: { timeout?: number }): Promise<string>; ``` - `command: string` —— 原样传递给 `adb shell` 的命令。例如要传 `input tap 100 200`,不要传 `adb shell input tap 100 200`。 - `opt.timeout?: number` —— 可选的命令执行超时时间,单位为毫秒。 此方法会调用 `RunAdbShell` 动作。创建 `AndroidDevice` 时,如果将 `exposeRunAdbShellAction` 设为 `false`,此方法将不可用。 ```ts const result = await agent.runAdbShell('dumpsys battery', { timeout: 60 * 1000 }); console.log(result); await agent.runAdbShell('input tap 100 200'); ``` <a id="android-agentterminate"></a> **`agent.terminate()`** 终止(强制停止)正在运行的 Android 应用。 ```ts function terminate(uri: string): Promise<void>; ``` - `uri: string` —— 应用包名、`appNameMapping` 中的应用名称,或 `package/.Activity`(仅使用包名部分)。 ```ts await agent.terminate('com.android.settings'); ``` <a id="android-导航辅助"></a> **导航辅助** - `agent.back(): Promise<void>` —— 触发 Android 系统的返回操作。 - `agent.home(): Promise<void>` —— 返回桌面。 - `agent.recentApps(): Promise<void>` —— 打开多任务/最近应用界面。 ### Android 工厂函数和工具 <a id="android-agentfromadbdevice"></a> **`agentFromAdbDevice()`** 从任意已连接的 adb 设备创建 `AndroidAgent`。 ```ts function agentFromAdbDevice( deviceId?: string, opts?: AndroidAgentOpt & AndroidDeviceOpt, ): Promise<AndroidAgent>; ``` - `deviceId?: string` —— 连接特定设备;留空表示使用“第一个可用设备”。 - `opts?: AndroidAgentOpt & AndroidDeviceOpt` —— Agent 选项与 [`AndroidDevice`](#androiddevice) 设置。未传 `deviceId` 时,Midscene 会通过 adb 自动发现设备。可在 `opts` 中设置 `androidAdbPath`、`remoteAdbHost` 和 `remoteAdbPort`,指定用于发现和连接设备的 adb。 <a id="android-getconnecteddevices"></a> **`getConnectedDevices()`** 列举 Midscene 可驱动的 adb 设备。 ```ts function getConnectedDevices( deviceOptions?: AndroidDeviceOpt, ): Promise<Array<{ udid: string; state: string; port?: number; }>>; ``` `deviceOptions` 是可选参数。省略时,Midscene 使用默认 adb 配置。通过 `androidAdbPath` 指定 adb 可执行文件,或通过 `remoteAdbHost` 和 `remoteAdbPort` 连接远程 adb server: ```ts const devices = await getConnectedDevices({ androidAdbPath: '/absolute/path/to/adb', remoteAdbHost: '192.168.1.10', remoteAdbPort: 5038, }); ``` <a id="android-getconnecteddeviceswithdetails"></a> **`getConnectedDevicesWithDetails()`** 与 `getConnectedDevices()` 功能类似,并额外返回设备品牌、型号、分辨率和屏幕密度。无法获取的字段为 `undefined`。 ```ts function getConnectedDevicesWithDetails( deviceOptions?: AndroidDeviceOpt, ): Promise<Array<{ udid: string; state: string; port?: number; model?: string; brand?: string; resolution?: string; density?: number; }>>; ``` <a id="android-快速开始"></a> <a id="android-启动原生-app"></a> **相关阅读** - [Android 快速开始](../platforms/android) 获取搭建与脚本示例。 ## iOS(`@midscene/ios`) {#ios} 当你需要自定义 iOS 设备行为、将 Midscene 接入依赖 WebDriverAgent 的工作流,或排查 WDA 请求问题时,请查阅本节。关于通用构造函数(报告、Hook、缓存等),请参考平台无关的 [API 参考](#common)。 ### iOS Action Space(动作空间) `IOSDevice` 使用以下动作空间,Midscene Agent 在规划任务时可以使用这些操作: - `Tap` —— 点击元素。 - `DoubleClick` —— 双击元素。 - `Input` —— 输入文本,支持 `replace`/`typeOnly`/`clear` 模式(`append` 是 `typeOnly` 的已废弃别名)。支持可选参数 `autoDismissKeyboard`、`keyboardTypeDelay` 和 `inputStrategy`。 - `Scroll` —— 以元素为起点或从屏幕中央向上/下/左/右滚动,支持滚动到顶/底/左/右。 - `DragAndDrop` —— 从一个元素拖拽到另一个元素。 - `KeyboardPress` —— 按下指定键位。 - `LongPress` —— 长按目标元素,可选自定义时长。 - `Pinch` —— 双指缩放手势。`scale > 1` 放大,`scale < 1` 缩小。 - `ClearInput` —— 清空输入框内容。 - `Launch` —— 打开网页、Bundle ID 或 URL Scheme。 - `Terminate` —— 通过 Bundle ID 关闭正在运行的 iOS 应用。 - `RunWdaRequest` —— 直接调用 WebDriverAgent REST 接口。 - `IOSHomeButton` —— 执行 iOS 系统 Home 操作。 - `IOSAppSwitcher` —— 打开 iOS 多任务视图。 ### IOSDevice {#iosdevice} 创建一个由 WebDriverAgent 支撑、供 IOSAgent 驱动的设备连接。 **导入** ```ts import { IOSDevice } from '@midscene/ios'; ``` **构造函数** ```ts const device = new IOSDevice({ // 设备参数... }); ``` **设备选项** - `wdaPort?: number` —— WebDriverAgent 端口,默认 `8100`。 - `wdaHost?: string` —— WebDriverAgent host,默认 `'localhost'`。 - `iOSDeviceClassOverride?: string` —— 使用 `agentFromWebDriverAgent()` 或 iOS Playground 时替换默认 `IOSDevice` 的 npm module path。目标模块必须导出 `IOSDevice` class 或 default class。 - `sessionId?: string` —— 复用已有的 WebDriverAgent session ID。传入后,Midscene 不再创建新的 WDA session;清理时只会从这个外部 WebDriver session 分离,不会删除它。 - `wdaMjpegPort?: number` —— WDA MJPEG 服务端口,用于实时画面流,默认 `9100`。 - `wdaMjpegFrameSource?: { enabled?: boolean }` —— 使用 WDA 的 MJPEG stream 作为 `agent.startObserving()` 的连续帧源。默认关闭;关闭时,观察逻辑会退回到连续调用 `screenshotBase64()`。 - `autoDismissKeyboard?: boolean` —— 文本输入后自动隐藏键盘,默认 `true`。 - `keyboardTypeDelay?: number` —— 按键间延迟,单位为毫秒。取值必须是有限的非负数。设为正数后,`legacy` 输入会通过 WDA 的 `/wda/keys` 接口逐个 Unicode 码点执行。适用于输入框在快速输入下丢字的场景。 - `inputStrategy?: 'legacy' | 'sequential' | 'bulk'` —— `Input` 动作的默认文本输入策略。`'sequential'` 会针对每个 Unicode 码点调用一次 WDA。`'bulk'` 只调用一次 WDA 文本接口,并且要求不设置 `keyboardTypeDelay` 或将其设为 `0`。如需延迟输入,请使用 `'sequential'`。默认值为 `'legacy'`。 - `customActions?: DeviceAction<any>[]` —— 添加额外的自定义动作,让 Agent 可以调用你定义的领域特定动作。 **使用说明** - 请确认已开启开发者模式且 WDA 能访问设备;真机转发端口时可借助 `iproxy`。 - 通过 `wdaHost`/`wdaPort` 可指向远程设备或自建的 WDA。 - 多设备并发时,请为每个设备设置不同的 `wdaPort` 和 `wdaMjpegPort`,避免 WDA 命令和 MJPEG stream 端口冲突。 - 通用交互方法请查阅 [API 参考(通用)](#interaction-methods)。 <a id="ios-device-destroy"></a> **`destroy()`** ```ts function destroy(): Promise<void>; ``` 尝试停止 MJPEG frame source、删除 WebDriverAgent session,并停止 WDA manager。 清理失败时会记录日志,但 Promise 不会因此被拒绝。该方法可以重复调用。调用完成 后,不能再使用这个 `IOSDevice` 实例。 调用 [`IOSAgent.destroy()`](#agentdestroy) 时,会自动执行该方法。每个 `IOSDevice` 实例只属于一个 `IOSAgent`。 ### IOSAgent {#iosagent} 将 Midscene 的 AI 规划能力绑定到 IOSDevice,通过 WebDriverAgent 实现 UI 自动化。 **导入** ```ts import { IOSAgent } from '@midscene/ios'; ``` **构造函数** ```ts const agent = new IOSAgent(device, { // 通用 Agent 参数... }); ``` **iOS 特有选项** - `appNameMapping?: Record<string, string>` —— 将友好的应用名称映射到 Bundle Identifier。当你在 `launch(target)` 或 `terminate(bundleId)` 里传入应用名称时,Agent 会在此映射中查找对应的 Bundle ID;若未找到映射,则按原样使用 `target`。用户提供的 appNameMapping 优先级高于默认映射。 - 其余字段与[通用构造参数](#common-parameters)一致,包括 `generateReport`、`reportFileName`、`aiContexts`、`aiActContext`、`modelConfig`、`cache`、`createOpenAIClient` 和 `onTaskStartTip` 等。 **使用说明** :::info - 一个设备连接对应一个 Agent。 - `customActions` 用于向 `IOSDevice` 添加额外的自定义动作。可以将它传给 Device 构造函数或 `agentFromWebDriverAgent()`。 - `launch`、`terminate`、`runWdaRequest` 等 iOS 专属辅助函数也可在 YAML 脚本中使用,语法见 [iOS 平台特定动作](../automate-with-scripts-in-yaml#the-ios-part)。 - 通用交互方法请查阅 [API 参考(通用)](#interaction-methods)。 ::: **iOS 特有方法** <a id="ios-agentlaunch"></a> **`agent.launch()`** 打开网页、原生应用或自定义 Scheme。 ```ts function launch(target: string): Promise<void>; ``` - `target: string` —— 目标地址(网页 URL、Bundle Identifier、URL scheme、tel/mailto 等)或应用名称。若传入应用名称且在 `appNameMapping` 中存在映射,将自动解析为对应 Bundle ID;若未找到映射,则直接按 `target` 启动。 ```ts await agent.launch('https://www.apple.com'); await agent.launch('com.apple.Preferences'); await agent.launch('myapp://profile/user/123'); await agent.launch('tel:+1234567890'); ``` <a id="ios-agentterminate"></a> **`agent.terminate()`** 通过 Bundle ID 终止(关闭)正在运行的 iOS 应用。 ```ts function terminate(bundleId: string): Promise<void>; ``` - `bundleId: string` —— 要终止的应用的 Bundle Identifier(如 `com.apple.Preferences`)。若传入应用名称且在 `appNameMapping` 中存在映射,将自动解析为对应 Bundle ID。 ```ts await agent.terminate('com.apple.Preferences'); await agent.terminate('com.apple.mobilesafari'); ``` <a id="ios-agentrunwdarequest"></a> **`agent.runWdaRequest()`** 当你需要更底层的控制能力时,执行原始的 WebDriverAgent REST 请求。 ```ts function runWdaRequest(params: { method: 'GET' | 'POST' | 'DELETE' | 'PUT'; endpoint: string; data?: Record<string, any>; }): Promise<any>; ``` - `params.method` —— HTTP 动词。支持值为 `GET`、`POST`、`DELETE` 和 `PUT`。 - `params.endpoint` —— WebDriverAgent 接口路径。 - `params.data` —— 可选的 JSON 请求体。 ```ts const screen = await agent.runWdaRequest({ method: 'GET', endpoint: '/wda/screen', }); await agent.runWdaRequest({ method: 'POST', endpoint: '/session/test/wda/pressButton', data: { name: 'home' }, }); ``` 如果直接调用设备实例上的 `IOSDevice.runWdaRequest()`,请使用位置参数签名 `runWdaRequest(method, endpoint, data?)`。 <a id="ios-应用间导航"></a> **应用间导航** - `agent.home(): Promise<void>` —— 回到主屏。 - `agent.appSwitcher(): Promise<void>` —— 打开多任务视图。 ### iOS 工厂函数和工具 <a id="agentfromwebdriveragent"></a> **`agentFromWebDriverAgent()`** 连接 WebDriverAgent 并返回可用的 IOSAgent。 ```ts function agentFromWebDriverAgent( opts?: IOSAgentOpt & IOSDeviceOpt, ): Promise<IOSAgent>; ``` - `opts?: IOSAgentOpt & IOSDeviceOpt` —— 在一个对象中同时传入 iOS Agent 选项与 [`IOSDevice`](#iosdevice) 的配置。 - 设置 `MIDSCENE_IOS_DEVICE_CLASS_OVERRIDE` 可通过环境变量应用同一个设备 class override。显式传入的选项优先级高于环境变量。 ```ts import { agentFromWebDriverAgent } from '@midscene/ios'; const agent = await agentFromWebDriverAgent({ wdaHost: 'localhost', wdaPort: 8100, iOSDeviceClassOverride: '@your-scope/ios-device', aiContexts: { aiAct: 'Accept permission dialogs automatically.' }, }); ``` <a id="ios-快速开始"></a> <a id="ios-自定义-host-与端口"></a> **相关阅读** - [iOS 快速开始](../platforms/ios) 获取搭建与脚本示例。 - [与任意界面集成](../integrate-with-any-interface) 查看自定义动作与 Schema 细节。 ## HarmonyOS(`@midscene/harmony`) {#harmonyos} 当你需要自定义设备行为、把 Midscene 接入框架,或排查 HDC 问题时,请查阅本节。关于通用构造函数(报告、Hook、缓存等)的参数说明,请参考平台无关的 [API 参考](#common)。 ### HarmonyOS Action Space(动作空间) `HarmonyDevice` 使用以下动作空间,Midscene Agent 在规划任务时可以使用这些操作: - `Tap` —— 点击元素。 - `DoubleClick` —— 双击元素。 - `Input` —— 输入文本,支持 `replace`/`typeOnly`/`clear` 模式。 - `Scroll` —— 以元素为起点或从屏幕中央向上/下/左/右滚动,支持滚动到顶/底/左/右。 - `DragAndDrop` —— 从一个元素拖拽到另一个元素。 - `KeyboardPress` —— 按下指定键位。 - `LongPress` —— 长按目标元素,可选自定义时长。 - `ClearInput` —— 清空输入框内容。 - ~~`Pinch`~~ —— 不支持。HarmonyOS `uitest` 框架未提供多触点输入 API。 - `Launch` —— 打开 HarmonyOS 应用(bundle name)。 - `Terminate` —— 按 bundle name 强制停止应用。 - `RunHdcShell` —— 执行原始 `hdc shell` 命令。 - `HarmonyBackButton` —— 触发系统返回。 - `HarmonyHomeButton` —— 回到桌面。 - `HarmonyRecentAppsButton` —— 打开多任务/最近应用。 ### HarmonyDevice {#harmonydevice} 创建一个可供 HarmonyAgent 驱动的 HDC 设备实例。 **导入** ```ts import { HarmonyDevice, getConnectedDevices } from '@midscene/harmony'; ``` **构造函数** ```ts const device = new HarmonyDevice(deviceId, { // 设备参数... }); ``` **设备选项** - `deviceId: string` —— 来自 `hdc list targets` 或 `getConnectedDevices()` 的值。 - `hdcPath?: string` —— HDC 可执行文件的自定义路径。若未设置,将依次从 `HDC_HOME` 环境变量和常见安装路径中查找。 - `autoDismissKeyboard?: boolean` —— 输入完成后自动隐藏键盘,默认 `true`。 - `keyboardDismissStrategy?: 'esc-first' | 'back-first'` —— 自动隐藏键盘时优先使用的按键,默认 `'esc-first'`。HarmonyOS 只发送该策略的首选按键:`'esc-first'` 发送 ESC,`'back-first'` 发送 Back。 - `keyboardTypeDelay?: number` —— 按键间延迟,单位为毫秒。取值必须是有限的非负数。设为正数后,`legacy` 输入会通过 `uitest uiInput inputText` 逐个 Unicode 码点执行。适用于输入框在快速输入下丢字的场景。 - `inputStrategy?: 'legacy' | 'sequential' | 'bulk'` —— `Input` 动作的默认文本输入策略。`'sequential'` 会针对每个 Unicode 码点调用一次 HDC。`'bulk'` 只调用一次 HDC 文本接口,并且要求不设置 `keyboardTypeDelay` 或将其设为 `0`。如需延迟输入,请使用 `'sequential'`。默认值为 `'legacy'`。 - `screenshotResizeScale?: number` —— **已废弃。** 此选项已移除,不再生效。如需控制发送给 AI 模型的截图尺寸,请使用 `AgentOpt` 中的 `screenshotShrinkFactor`。 - `customActions?: DeviceAction[]` —— 添加额外的自定义动作,让 Agent 可以调用你定义的领域特定动作。 **使用说明** - 可以使用 `getConnectedDevices()` 发现设备,返回的 `deviceId` 与 `hdc list targets` 输出一致。 - 如果 HDC 不在系统 PATH 中,可通过 `HDC_HOME` 环境变量或 `hdcPath` 选项指定路径。 <a id="harmony-device-destroy"></a> **`destroy()`** ```ts function destroy(): Promise<void>; ``` 释放 `HarmonyDevice` 持有的 HDC 状态和屏幕信息缓存。该方法可以重复调用。调用 完成后,这个 Device 实例不能再执行 HDC 命令,但物理设备仍与 HDC 保持连接。 调用 [`HarmonyAgent.destroy()`](#agentdestroy) 时,会自动执行该方法。每个 `HarmonyDevice` 实例只属于一个 `HarmonyAgent`。 ### HarmonyAgent {#harmonyagent} 将 Midscene 的 AI 规划能力绑定到 HarmonyDevice,实现 UI 自动化。 **导入** ```ts import { HarmonyAgent } from '@midscene/harmony'; ``` **构造函数** ```ts const agent = new HarmonyAgent(device, { appNameMapping: { 视频: 'com.example.video/PhoneAbility', }, }); ``` **HarmonyOS 特有选项** - `appNameMapping?: Record<string, string>` —— 将友好的应用名称映射到 bundle name 或显式 `bundle/Ability` 目标。仅填写 bundle name 时,Agent 会读取 bundle 元数据并解析其声明的启动 Ability;显式目标会跳过该查询。当你在 `launch(target)` 里传入应用名称时,Agent 会在此映射中查找对应目标;若未找到映射,则按原样尝试启动 `target`。 - 其余字段与[通用构造参数](#common-parameters)一致,包括 `generateReport`、`reportFileName`、`aiContexts`、`aiActContext`、`modelConfig`、`cache`、`createOpenAIClient` 和 `onTaskStartTip` 等。 **使用说明** :::info - 一个设备连接对应一个 Agent。 - `customActions` 用于向 `HarmonyDevice` 添加额外的自定义动作。可以将它传给 Device 构造函数或 `agentFromHdcDevice()`。 - `launch`、`terminate`、`runHdcShell` 等 HarmonyOS 专属辅助函数也可在 YAML 脚本中使用,语法见 [HarmonyOS 平台特定动作](../automate-with-scripts-in-yaml#harmony-部分)。 - 通用交互方法请查阅 [API 参考(通用)](#interaction-methods)。 ::: **HarmonyOS 特有方法** <a id="harmonyos-agentlaunch"></a> **`agent.launch()`** 启动 HarmonyOS 应用。 ```ts function launch(uri: string): Promise<void>; ``` - `uri: string` —— 可以是应用 bundle name(如 `com.huawei.hmos.settings`)、显式 `bundle/Ability` 目标,也可以是在 `appNameMapping` 中注册的应用名称。如果传入 `http://` 或 `https://` 开头的 URL,将通过浏览器打开。 ```ts await agent.launch('com.huawei.hmos.settings'); // 打开系统设置 await agent.launch('com.huawei.hmos.camera'); // 打开相机 await agent.launch('视频'); // 打开映射的指定 Ability ``` <a id="harmonyos-agentrunhdcshell"></a> **`agent.runHdcShell()`** 通过连接的设备运行原始的 `hdc shell` 命令。 ```ts function runHdcShell(command: string): Promise<string>; ``` - `command: string` —— 原样传递给 `hdc shell` 的命令。 ```ts const result = await agent.runHdcShell('hidumper -s RenderService -a screen'); console.log(result); ``` <a id="harmonyos-agentterminate"></a> **`agent.terminate()`** 终止(强制停止)正在运行的 HarmonyOS 应用。 ```ts function terminate(uri: string): Promise<void>; ``` - `uri: string` —— 应用 bundle name、`appNameMapping` 中的应用名称,或 `bundle/Ability`(仅使用 bundle name 部分)。 ```ts await agent.terminate('com.huawei.hmos.settings'); ``` <a id="harmonyos-导航辅助"></a> **导航辅助** - `agent.back(): Promise<void>` —— 触发 HarmonyOS 系统的返回操作。 - `agent.home(): Promise<void>` —— 返回桌面。 - `agent.recentApps(): Promise<void>` —— 打开多任务/最近应用界面。 ### HarmonyOS 工厂函数和工具 <a id="harmonyos-agentfromhdcdevice"></a> **`agentFromHdcDevice()`** 从任意已连接的 HDC 设备创建 `HarmonyAgent`。 ```ts function agentFromHdcDevice( deviceId?: string, opts?: HarmonyAgentOpt & HarmonyDeviceOpt, ): Promise<HarmonyAgent>; ``` - `deviceId?: string` —— 连接特定设备;留空表示使用"第一个可用设备"。 - `opts?: HarmonyAgentOpt & HarmonyDeviceOpt` —— 在一个对象中合并 Agent 选项与 [`HarmonyDevice`](#harmonydevice) 的设置。 ```ts import { agentFromHdcDevice } from '@midscene/harmony'; const agent = await agentFromHdcDevice('0123456789ABCDEF'); // 传入 deviceId // 或者使用第一个可用设备: // const agent = await agentFromHdcDevice(); ``` <a id="harmonyos-getconnecteddevices"></a> **`getConnectedDevices()`** 列举 Midscene 可驱动的 HDC 设备。 ```ts function getConnectedDevices( hdcPath?: string, ): Promise<Array<{ deviceId: string }>>; ``` ```ts import { getConnectedDevices } from '@midscene/harmony'; const devices = await getConnectedDevices(); console.log(devices); // [{ deviceId: '0123456789ABCDEF' }] ``` <a id="harmonyos-快速开始"></a> <a id="harmonyos-启动应用"></a> **相关阅读** - [HarmonyOS 快速开始](../platforms/harmonyos) 获取搭建与脚本示例。 ## 桌面端(`@midscene/computer`) {#desktop} 本页记录了 `@midscene/computer` 提供的 PC 桌面特定 API。 有关适用于所有平台的通用 API,请参阅 [通用 API 参考](#common)。 ### Agent 工厂函数 `agentForComputer(opts?): Promise<ComputerAgent>` 创建用于本机桌面自动化的 agent。 > 向后兼容:`agentFromComputer` 仍可作为别名使用。 `agentForRDPComputer(opts): Promise<ComputerAgent<RDPDevice>>` 创建用于通过 RDP 控制远程 Windows 桌面的 agent。 **参数:** ```typescript interface BaseComputerAgentOpt { // Agent 选项(继承自 AgentOpt) aiContexts?: AgentAIContexts; cache?: false | CacheConfig; // ... 其他 AgentOpt 属性 customActions?: DeviceAction<any>[]; keyboardTypeDelay?: number; inputStrategy?: 'legacy' | 'sequential' | 'bulk'; } interface LocalComputerAgentOpt extends BaseComputerAgentOpt { // 本机桌面选项 displayId?: string; keyboardDriver?: 'applescript' | 'libnut'; headless?: boolean; xvfbResolution?: string; } interface RDPComputerAgentOpt extends BaseComputerAgentOpt { host: string; port?: number; username?: string; password?: string; domain?: string; localAddress?: string; adminSession?: boolean; ignoreCertificate?: boolean; securityProtocol?: 'auto' | 'tls' | 'nla' | 'rdp'; desktopWidth?: number; desktopHeight?: number; } ``` **本机桌面选项** - `displayId`(可选):指定要控制的显示器。使用 `ComputerDevice.listDisplays()` 获取可用显示器。 - `customActions`(可选):添加额外的自定义动作,让 Agent 可以调用你定义的领域特定动作。 - `keyboardDriver?: 'applescript' | 'libnut'`:macOS 的键盘事件后端。`'applescript'` 是兼容性更好的默认选项;目标应用支持时,也可以使用输入速度更快的 `'libnut'`。 - `headless`(可选,仅 Linux):设为 `true` 时通过 [Xvfb](https://www.x.org/releases/X11R7.6/doc/man/man1/Xvfb.1.xhtml) 启动虚拟显示器,使桌面自动化能在无物理显示器的 Linux 服务器和 CI 环境中运行。也可通过环境变量 `MIDSCENE_COMPUTER_HEADLESS_LINUX=true` 设置。 - `xvfbResolution`(可选):Xvfb 虚拟显示器的分辨率,默认为 `'1920x1080x24'`。 **键盘输入选项** - `keyboardTypeDelay`(可选):按键间的最小延迟,单位为毫秒。取值必须是有限的非负数。在 `'legacy'` 模式下,正数延迟会让本机和 RDP Computer Agent 逐个 Unicode 码点发送文本。本机 Computer Agent 会发送真实按键事件。未设置该选项或将其设为 `0` 时,`legacy` 输入使用剪贴板粘贴,以免受到当前输入法的干扰。通过 `aiInput()` 传入的动作级 `keyboardTypeDelay` 优先于 Agent 级默认值。将它设为 `0`,可以只为当前动作恢复剪贴板输入。 - `inputStrategy?: 'legacy' | 'sequential' | 'bulk'`:默认文本输入策略。`'sequential'` 在本机使用真实按键事件,在 RDP 中针对每个 Unicode 码点调用一次后端。`'bulk'` 在本机执行一次剪贴板粘贴,在 RDP 中只调用一次后端。使用 `'bulk'` 时,必须省略 `keyboardTypeDelay`,或将它设为 `0`。如需延迟输入,请使用 `'sequential'`。默认值为 `'legacy'`。 Agent 级选项同样会作用于 `agent.ai()` 自动规划生成的 `Input` 动作,因此不要求用户单独调用 `aiInput()`: ```typescript const agent = await agentForComputer({ keyboardTypeDelay: 80 }); await agent.ai('把账号信息输入表单'); // 只覆盖这一次确定性输入,恢复整串粘贴。 await agent.aiInput('备注输入框', { value: '整串粘贴的内容', keyboardTypeDelay: 0, inputStrategy: 'bulk', }); ``` **RDP 选项** - `host`:远程 Windows 主机名或 IP。 - `port`:RDP 端口,默认是 `3389`。 - `username` / `password`:远程会话凭据。 - `domain`:可选的 Windows 域。 - `localAddress`:RDP TCP 连接使用的本地源 IP。运行 Midscene 的机器有多条出站路由时使用。 - `adminSession`:当服务端允许时,请求远程管理员会话。 - `ignoreCertificate`:用于跳过自签名证书校验。 - `securityProtocol`:可选 `'auto'`、`'tls'`、`'nla'` 或 `'rdp'`。 - `desktopWidth` / `desktopHeight`:请求指定远程桌面分辨率。 :::info 示例:在无头 Linux CI 中测试 Electron 应用 使用 `@midscene/computer` 在无头 Linux CI 中测试 [Obsidian](https://obsidian.md/)(Electron 应用)的完整示例:[https://github.com/web-infra-dev/midscene-example/tree/main/computer/electron-demo](https://github.com/web-infra-dev/midscene-example/tree/main/computer/electron-demo) ::: **示例:** ```typescript import { agentForComputer } from '@midscene/computer'; // 连接到主显示器 const agent = await agentForComputer({ aiContexts: { aiAct: '你正在自动化一个桌面应用。' }, }); // 连接到特定显示器 const displays = await ComputerDevice.listDisplays(); const agent2 = await agentForComputer({ displayId: displays[1].id, }); ``` **示例:通过 RDP 连接远程 Windows 桌面** ```typescript import { agentForRDPComputer } from '@midscene/computer'; const agent = await agentForRDPComputer({ aiContexts: { aiAct: 'You are controlling a remote Windows desktop over the RDP protocol.', }, host: '10.75.166.249', port: 3389, username: 'Admin', password: 'replace-with-your-password', // 可选:把 TCP 连接绑定到这个本地源 IP。 localAddress: '10.75.166.10', ignoreCertificate: true, }); await agent.aiWaitFor('The remote Windows desktop is visible'); await agent.aiAct('Click the Windows Start button'); await agent.aiAct('Open Settings'); ``` :::info 示例:通过 RDP 控制远程 Windows 桌面 一个可直接运行的 Demo:连接远程 Windows,打开「设置」并进入「Windows 更新」,最后输出一份结构化报告:[https://github.com/web-infra-dev/midscene-example/tree/main/computer/rdp-demo](https://github.com/web-infra-dev/midscene-example/tree/main/computer/rdp-demo) ::: 当运行 Midscene 的机器存在多条出站路由,且 RDP 服务器要求从指定本地源 IP 访问时,可以使用 `localAddress`。这里传入的是 IP 地址,不是网卡名。 ### ComputerDevice `ComputerDevice.listDisplays(): Promise<DisplayInfo[]>` 列出所有可用显示器。 **返回:** ```typescript interface DisplayInfo { id: string; name: string; primary?: boolean; } ``` **示例:** ```typescript import { ComputerDevice } from '@midscene/computer'; const displays = await ComputerDevice.listDisplays(); console.log('可用显示器:', displays); // [ // { id: '0', name: '内置显示器', primary: true }, // { id: '1', name: '外接显示器', primary: false } // ] ``` `checkComputerEnvironment(): Promise<EnvironmentCheck>` 检查计算机环境是否正确配置。 **返回:** ```typescript interface EnvironmentCheck { available: boolean; error?: string; platform: string; displays: number; } ``` **示例:** ```typescript import { checkComputerEnvironment } from '@midscene/computer'; const env = await checkComputerEnvironment(); console.log('环境检查:', env); if (!env.available) { console.error('环境错误:', env.error); } ``` <a id="computer-device-destroy"></a> **`ComputerDevice.destroy()`** ```typescript function destroy(): Promise<void>; ``` 释放本机输入驱动。如果存在 Agent 创建的 Xvfb 实例,也会将它停止。该方法可以 重复调用。调用完成后,不能再使用这个 `ComputerDevice` 实例。 <a id="rdp-device-destroy"></a> **`RDPDevice.destroy()`** ```typescript function destroy(): Promise<void>; ``` 断开 RDP backend,并清除连接状态。该方法可以重复调用。调用完成后,不能再使用 这个 `RDPDevice` 实例。 调用 [`ComputerAgent.destroy()`](#agentdestroy) 时,会自动执行对应的 Device 方法。`agentForComputer()` 和 `agentForRDPComputer()` 返回的 Agent 都遵循该行为。 ### ComputerAgent `ComputerAgent` 类继承自 `PageAgent<ComputerDevice>`,并继承所有通用 agent 方法: - `aiAct(action: string)`:使用 AI 执行操作 - `aiQuery(query: string)`:使用 AI 提取信息 - `aiAssert(assertion: string)`:使用 AI 断言条件 - `aiWaitFor(condition: string)`:等待条件 - `aiLocate(description: string)`:定位元素 - 更多... 定位元素后,还可以使用即时操作(Instant Action)进行直接、确定性的控制: - `aiTap()`、`aiDoubleClick()`、`aiRightClick()`、`aiHover()`:鼠标操作 - `aiInput()`、`aiClearInput()`、`aiKeyboardPress()`:键盘操作 - `aiScroll()`:滚动操作 详见 [通用 API 参考](#common)。 ### 桌面端操作与限制 `ComputerDevice` 支持以下操作: **鼠标操作** <a id="desktop-tap点击"></a> **Tap(点击)** 在目标位置单击。 ```typescript await agent.aiAct('点击文件菜单'); await agent.aiAct('点击屏幕中心'); ``` <a id="desktop-doubleclick双击"></a> **DoubleClick(双击)** 在目标位置双击。 ```typescript await agent.aiAct('双击桌面图标'); ``` <a id="desktop-rightclick右键"></a> **RightClick(右键)** 右键点击打开上下文菜单。 ```typescript await agent.aiAct('右键点击桌面'); await agent.aiAct('右键点击文件'); ``` <a id="desktop-mousemove移动鼠标-悬停"></a> **MouseMove(移动鼠标 / 悬停)** 将鼠标移动到目标元素上,也就是鼠标悬停(hover),例如用于触发悬停菜单或提示框。 ```typescript // 自然语言形式(移动鼠标 / 悬停) await agent.aiAct('移动鼠标到菜单项'); // 即时操作:一次调用完成定位并悬停 await agent.aiHover('菜单项「Products」'); ``` <a id="desktop-draganddrop拖放"></a> **DragAndDrop(拖放)** 从一个位置拖动并放到另一个位置。 ```typescript await agent.aiAct('将文件拖到文件夹'); ``` **键盘操作** <a id="desktop-keyboardpress按键"></a> **KeyboardPress(按键)** 按键盘按键,可选修饰键。 **支持的按键:** - 普通键:`a-z`、`0-9`、`Enter`、`Escape`、`Space`、`Tab` 等 - 方向键:`ArrowUp`、`ArrowDown`、`ArrowLeft`、`ArrowRight` - 功能键:`F1`-`F12` - 修饰键:`Command`/`Cmd`(macOS)、`Control`/`Ctrl`、`Alt`、`Shift`、`Win`(Windows) - 媒体键:`VolumeUp`、`VolumeDown`、`Mute` 等 **示例:** ```typescript // 简单按键 await agent.aiAct('按 Enter'); await agent.aiAct('按 Escape'); // 组合键(平台特定) if (process.platform === 'darwin') { // macOS await agent.aiAct('按 Cmd+Space'); // 打开 Spotlight await agent.aiAct('按 Cmd+Tab'); // 应用切换器 await agent.aiAct('按 Cmd+C'); // 复制 await agent.aiAct('按 Cmd+V'); // 粘贴 } else { // Windows/Linux await agent.aiAct('按 Windows 键'); // 开始菜单 await agent.aiAct('按 Alt+Tab'); // 应用切换器 await agent.aiAct('按 Ctrl+C'); // 复制 await agent.aiAct('按 Ctrl+V'); // 粘贴 } // 方向键 await agent.aiAct('按 ArrowDown'); await agent.aiAct('按 ArrowUp'); // 功能键 await agent.aiAct('按 F5'); // 刷新 ``` <a id="desktop-input输入"></a> **Input(输入)** 在输入框中输入文本。 ```typescript await agent.aiAct('在搜索框输入 "你好世界"'); await agent.aiAct('输入 "我的文档.txt"'); ``` <a id="desktop-clearinput清空输入"></a> **ClearInput(清空输入)** 清空输入框内容。 ```typescript await agent.aiAct('清空文本框'); ``` **滚动操作** 滚动屏幕或特定区域。 ```typescript // 滚动方向 await agent.aiAct('向下滚动'); await agent.aiAct('向上滚动'); await agent.aiAct('向左滚动'); await agent.aiAct('向右滚动'); // 滚动到位置 await agent.aiAct('滚动到顶部'); await agent.aiAct('滚动到底部'); ``` **显示器操作** <a id="desktop-listdisplays列出显示器"></a> **ListDisplays(列出显示器)** 获取所有已连接显示器的信息。 ```typescript const displays = await ComputerDevice.listDisplays(); ``` 使用 RDP 时,`ListDisplays` 会把当前远程会话视为单个显示器返回。 ## 运行时配置 {#runtime-configuration} 以下环境变量控制全局运行时行为,而不是模型请求。Agent 的 `modelConfig` 对象不支持这些参数。 | 名称 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | `MIDSCENE_RUN_DIR` | 字符串 | `midscene_run` | 报告、日志、模型调用记录和其他运行产物的保存目录。支持绝对路径,也支持相对于当前工作目录的路径。 | | `MIDSCENE_PREFERRED_LANGUAGE` | 字符串 | 根据时区自动判断:如果时区是 `Asia/Shanghai`,则为 `Chinese`,否则为 `English` | 相关模型响应的首选语言。该设置通过提示词引导模型,并非强制约束;实际输出可能因模型能力和上下文而不完全遵循。 | | `MIDSCENE_PLAYGROUND_HOST` | 字符串 | `127.0.0.1` | Playground 服务器监听的网络接口。远程设备、虚拟机、容器或其他计算机需要连接时,请设为对方可访问的接口地址。 | | `DEBUG` | 字符串 | 未设置 | 启用额外的 Debug 日志命名空间。支持的选择器请参考 [Debug 日志](#debug-logs)。 | 设置 `MIDSCENE_PLAYGROUND_HOST=0.0.0.0` 会监听所有网络接口。请仅在可信网络中使用该值。 ### Debug 日志 {#debug-logs} 将 `DEBUG` 设置为以下选择器之一: | 取值 | 描述 | | --- | --- | | `midscene:ai:profile:stats` | 使用逗号分隔的格式打印模型延迟和 Token 使用量。 | | `midscene:ai:profile:detail` | 打印详细的 Token 使用量日志。 | | `midscene:ai:call` | 打印 AI 响应详情。 | | `midscene:android:adb` | 打印 Android ADB 命令调用详情。 | | `midscene:*` | 打印全部 Midscene Debug 日志。 | 即使未设置 `DEBUG`,Midscene 也会将日志保存在 `<MIDSCENE_RUN_DIR>/log` 目录中。Debug 日志可能包含模型输入、输出或截图。分享前请先检查内容,也不要将日志提交到源码仓库。 模型连接检查、调用记录和 Tracing 集成请参考[模型调试与可观测性](../model-debugging-observability)。 **另请参阅** - [通用 API 参考](#common) - 适用于所有平台的 API - [模型配置](../model-config) - 配置 AI 模型 - [缓存](../caching) - 使用缓存提高性能 --- url: /zh/showcases-android.md --- **Prompt** : 打开懂车帝,搜索 su7 车型,查看参数配置 <video src="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/su72.mp4" poster="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/su7.png" height="300" controls /> 查看此次任务的完整报告:[report.html](https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/su7.html) **Prompt** : Open the Booking App, search for a hotel in Tokyo for four adults on Christmas, with a score of 8 or above. <video src="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/booking2.mp4" poster="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/booking.png" height="300" controls /> 查看此次任务的完整报告:[report.html](https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/booking.html) --- url: /zh/showcases-computer.md --- **macOS** **Prompt**:通过 Safari 发一条推文宣传 Midscene 支持 AutoGLM,要求如下: 1. 文案内容:Midscene now supports AutoGLM! 2. 媒体内容:使用下载文件夹里的 AutoGLM 视频! <video src="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/pc-twitter2.mp4" height="300" controls /> 查看此次任务的完整报告:[report.html](https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/pc-twitter2-midscene_report.html) **Prompt**:打开 Google 查询圣何塞明天天气温度 <video src="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/mac.mov" height="300" controls /> 查看此次任务的完整报告:[report.html](https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/weather-computer-2026-01-14_11-26-38-9592ecf5.html) **Windows** **Prompt**:打开 Sauce Demo 电商网站,登录并添加商品到购物车 <video src="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/windows.mov" height="300" controls /> 查看此次任务的完整报告:[report.html](https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/shop-computer-2026-01-14_11-57-36-f8411b8f.html) **Linux** **Prompt**:打开 TodoMVC,添加多个任务并筛选 <video src="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/linux.mov" height="300" controls /> 查看此次任务的完整报告:[report.html](https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/todo-computer-2026-01-13_15-40-37-6f45fb0f.html) --- url: /zh/showcases-harmony.md --- **Prompt** :打开设置,滚动查找「关于手机」,查看设备信息。 <video src="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/harmony.mp4" poster="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/harmony.png" height="300" controls /> 查看此次任务的完整报告:[report.html](https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/harmony.html) --- url: /zh/showcases-ios.md --- **Prompt** : 打开美团,帮我下单一杯 manner 超大杯冰美式咖啡,要加浓少冰喔,到结算页面让我确认 <video src="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/meituan2.mp4" poster="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/meituan.png" height="300" controls /> 查看此次任务的完整报告:[report.html](https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/meituan.html) **Prompt** : Open Twitter and auto-like the first tweet by @midscene\_ai <video src="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/x2.mp4" poster="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/x.png" height="300" controls /> 查看此次任务的完整报告:[report.html](https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/x.html) --- url: /zh/showcases-web.md --- **Prompt**:填写 GitHub 注册表单并通过表单校验,但不要提交。 <video src="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/github2.mp4" poster="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/github.png" height="300" controls /> Midscene 会为每次任务生成完整报告,供开发者回溯操作过程。上述 Demo 的报告请参考:[report.html](https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/github.html) --- url: /zh/showcases.md --- # 案例展示 本文介绍了跨平台 GUI Agent、E2E 测试和社区实践等案例。 ## 跨平台 GUI Agent 案例 ### Web **Prompt**:填写 GitHub 注册表单并通过表单校验,但不要提交。 <video src="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/github2.mp4" poster="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/github.png" height="300" controls /> Midscene 会为每次任务生成完整报告,供开发者回溯操作过程。上述 Demo 的报告请参考:[report.html](https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/github.html) ### iOS **Prompt** : 打开美团,帮我下单一杯 manner 超大杯冰美式咖啡,要加浓少冰喔,到结算页面让我确认 <video src="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/meituan2.mp4" poster="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/meituan.png" height="300" controls /> 查看此次任务的完整报告:[report.html](https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/meituan.html) **Prompt** : Open Twitter and auto-like the first tweet by @midscene\_ai <video src="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/x2.mp4" poster="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/x.png" height="300" controls /> 查看此次任务的完整报告:[report.html](https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/x.html) ### Android **Prompt** : 打开懂车帝,搜索 su7 车型,查看参数配置 <video src="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/su72.mp4" poster="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/su7.png" height="300" controls /> 查看此次任务的完整报告:[report.html](https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/su7.html) **Prompt** : Open the Booking App, search for a hotel in Tokyo for four adults on Christmas, with a score of 8 or above. <video src="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/booking2.mp4" poster="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/booking.png" height="300" controls /> 查看此次任务的完整报告:[report.html](https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/booking.html) ### HarmonyOS **Prompt** :打开设置,滚动查找「关于手机」,查看设备信息。 <video src="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/harmony.mp4" poster="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/harmony.png" height="300" controls /> 查看此次任务的完整报告:[report.html](https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/harmony.html) ### 桌面端 **macOS** **Prompt**:通过 Safari 发一条推文宣传 Midscene 支持 AutoGLM,要求如下: 1. 文案内容:Midscene now supports AutoGLM! 2. 媒体内容:使用下载文件夹里的 AutoGLM 视频! <video src="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/pc-twitter2.mp4" height="300" controls /> 查看此次任务的完整报告:[report.html](https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/1.0-showcases/pc-twitter2-midscene_report.html) **Prompt**:打开 Google 查询圣何塞明天天气温度 <video src="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/mac.mov" height="300" controls /> 查看此次任务的完整报告:[report.html](https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/weather-computer-2026-01-14_11-26-38-9592ecf5.html) **Windows** **Prompt**:打开 Sauce Demo 电商网站,登录并添加商品到购物车 <video src="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/windows.mov" height="300" controls /> 查看此次任务的完整报告:[report.html](https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/shop-computer-2026-01-14_11-57-36-f8411b8f.html) **Linux** **Prompt**:打开 TodoMVC,添加多个任务并筛选 <video src="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/linux.mov" height="300" controls /> 查看此次任务的完整报告:[report.html](https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/todo-computer-2026-01-13_15-40-37-6f45fb0f.html) ## E2E 测试场景 ### 懂车帝 App:Doubao Seed 2.1 Turbo 以下 5 个用例使用 `doubao-seed-2-1-turbo-260628`,测试 Android 端懂车帝 App 的排行榜筛选功能。测试设备分辨率为 720 × 1600。 #### 用例 1:销量榜功能测试 * **测试内容:** 验证销量榜在选择“轿车 + 上上个月 + 燃油车 + 18–25 万”时,组合筛选和结果展示是否正确 * **步骤数:** 8 / 16(脚本 / 模型调用) * **Token 量:** 输入 126,935(缓存 89,760)/ 输出 3,109 * **费用:** ¥0.212 * **测试报告:** [查看报告](https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/showcases/dongchedi/01-sales-sedan-fuel-preset-price.html) #### 用例 2:销量榜组合筛选测试 * **测试内容:** 验证销量榜在选择“SUV + 上个月 + 插电式混动”时,组合筛选和结果展示是否正确 * **步骤数:** 4 / 14(脚本 / 模型调用) * **Token 量:** 输入 137,310(缓存 100,000)/ 输出 2,472 * **费用:** ¥0.209 * **测试报告:** [查看报告](https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/showcases/dongchedi/02-sales-suv-hybrid.html) #### 用例 3:新能源榜功能测试 * **测试内容:** 验证新能源榜在选择“SUV + 近半年 + 纯电动 + 18–25 万”时,组合筛选和结果展示是否正确 * **步骤数:** 8 / 18(脚本 / 模型调用) * **Token 量:** 输入 149,996(缓存 103,896)/ 输出 6,279 * **费用:** ¥0.295 * **测试报告:** [查看报告](https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/showcases/dongchedi/03-new-energy-suv-pure-electric-preset-price.html) #### 用例 4:降价榜功能测试 * **测试内容:** 验证降价榜在选择“MPV + 近一年 + 新能源”并设置 15–30 万自定义价格时,组合筛选和结果展示是否正确 * **步骤数:** 13 / 29(脚本 / 模型调用) * **Token 量:** 输入 253,991(缓存 174,176)/ 输出 8,203 * **费用:** ¥0.467 * **测试报告:** [查看报告](https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/showcases/dongchedi/04-price-drop-mpv-new-energy-custom-price.html) #### 用例 5:榜单切换与筛选重置测试 * **测试内容:** 验证从销量榜切换至新能源榜时关联筛选条件是否正确重置,并验证后续筛选及恢复默认状态是否正确 * **步骤数:** 16 / 28(脚本 / 模型调用) * **Token 量:** 输入 217,283(缓存 151,848)/ 输出 5,688 * **费用:** ¥0.373 * **测试报告:** [查看报告](https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/showcases/dongchedi/05-switch-and-reset.html) :::info Doubao 计价口径 价格数据截至 2026 年 9 月 3 日,豆包费用使用[火山引擎官方价格](https://ark.volcengine.com/region:cn-beijing/model/detail?name=doubao-seed-2-1-turbo)计算。推理输入按 `¥3 / M Tokens`、缓存命中按 `¥0.6 / M Tokens`、推理输出按 `¥15 / M Tokens` 计算。5 个用例总费用约 ¥1.556,平均消耗约 182,253 Token,平均费用约 ¥0.311。以上数据仅代表本次测试,实际费用会随任务复杂度、缓存命中率和模型价格变化。 ::: ### Reddit App:Qwen 3.7 Plus 以下 2 个用例使用 [`qwen3.7-plus`](https://help.aliyun.com/zh/model-studio/model-pricing),测试 Android 端 Reddit App 的社区搜索、加入和帖子点赞功能。测试设备分辨率为 720 × 1600。 #### 用例 1:搜索并加入 Midscene 社区 * **测试内容:** 搜索 Midscene,打开准确的 `r/midscene` 社区,按需加入并验证账号已处于加入状态 * **步骤数:** 7 / 17(脚本 / 模型调用) * **Token 量:** 输入 147,127(缓存 34,816)/ 输出 3,418 * **费用:** ¥0.213 * **测试报告:** [查看报告](https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/showcases/reddit/01-search-and-join-midscene-community.html) #### 用例 2:点赞 Midscene 社区首篇帖子 * **测试内容:** 打开准确的 `r/midscene` 社区,按需点赞帖子列表中的首篇帖子,并验证其已处于点赞状态 * **步骤数:** 6 / 12(脚本 / 模型调用) * **Token 量:** 输入 103,231(缓存 8,704)/ 输出 3,142 * **费用:** ¥0.174 * **测试报告:** [查看报告](https://lf3-static.bytednsdoc.com/obj/eden-cn/luljzkpt/ljhwZthlaukjlkulzlp/showcases/reddit/02-upvote-first-post-in-midscene-community.html) :::info Qwen 计价口径 价格数据截至 2026 年 8 月 21 日,取自[阿里云百炼模型价格](https://help.aliyun.com/zh/model-studio/model-pricing)。输入单价按 `¥1.6 / M Tokens`、缓存读取单价按 `¥0.32 / M Tokens`、输出单价按 `¥6.4 / M Tokens` 计算。2 个用例平均消耗约 128,459 Token,平均费用约 ¥0.193。以上数据仅代表本次测试,实际费用会随任务复杂度、缓存命中率和模型价格变化。 ::: ## 社区案例 有社区开发者成功基于 Midscene 与[任意界面集成](/zh/integrate-with-any-interface.md)的特性,扩展了机械臂 + 视觉模型 + 语音模型等模块,运用于车机大屏测试场景中。 <video src="https://lf3-static.bytednsdoc.com/obj/eden-cn/vhaeh7vhabf/AI_Vision_Powered_Robotic_Arm.mp4" height="300" controls /> --- url: /zh/skills.md --- # 使用 Skills 控制任意平台 [Agent Skills](https://github.com/anthropics/skills) 是一种扩展 AI 编程助手能力的格式。Midscene 提供了 Agent Skills,让 AI 编程工具(如 Claude Code、Cline 等)可以通过 CLI 命令驱动 UI 自动化。 Skills 通过在终端中直接运行 CLI 命令来工作。AI 编程助手充当“大脑”:截图、分析 UI、决定下一步操作。 ## 支持的平台 | Skill | 包名 | CLI 命令 | 说明 | |-------|------|----------|------| | Browser Automation | `@midscene/web` | `npx @midscene/web` | 浏览器自动化,支持三种模式:默认 Puppeteer 无头模式、`--bridge` 桥接用户 Chrome、`--cdp <ws-endpoint>` 通过 CDP 直连已有浏览器 | | Desktop Computer Automation | `@midscene/computer` | `npx @midscene/computer` | macOS、Windows、Linux 桌面控制 | | Android Device Automation | `@midscene/android` | `npx @midscene/android` | 通过 ADB 控制 Android 设备 | | iOS Device Automation | `@midscene/ios` | `npx @midscene/ios` | 通过 WebDriverAgent 控制 iOS 设备 | | HarmonyOS Device Automation | `@midscene/harmony` | `npx @midscene/harmony` | 通过 HDC 控制鸿蒙设备 | 在默认 Puppeteer 模式下,可以通过 `--viewport-width <width>` 和 `--viewport-height <height>` 覆盖默认的 `1440x800` 视口尺寸。这两个参数仅支持默认 Puppeteer 模式,不支持 `--bridge` 或 `--cdp` 模式。 在 CDP 模式下,每个 HTTP header 使用一个独立的 `--extra-http-header 'Name:Value'` 参数。每条 CLI 命令都会创建新的 CDP session,因此所有可能发起请求的独立命令都必须重复携带这些参数: ```bash npx @midscene/web connect \ --cdp ws://127.0.0.1:9222/devtools/browser \ --extra-http-header 'x-use-ppe:1' \ --extra-http-header 'x-tt-env:ppe_example' \ --url https://example.com npx @midscene/web act \ --cdp ws://127.0.0.1:9222/devtools/browser \ --extra-http-header 'x-use-ppe:1' \ --extra-http-header 'x-tt-env:ppe_example' \ --prompt "click the button" ``` header 会在 `connect --url` 导航前生效,因此初始文档请求也会携带这些 header。不要把敏感鉴权值直接留在 shell 历史中。 如果 Chrome 安装在非标准路径,请将 `MIDSCENE_CHROME_PATH` 设置为 Chrome 可执行文件路径。`MIDSCENE_MCP_CHROME_PATH` 会暂时作为迁移别名继续生效。 ## 安装 确保已安装 [Node.js](https://nodejs.org),然后运行: ```bash # 通用安装 npx skills add web-infra-dev/midscene-skills # Claude Code npx skills add web-infra-dev/midscene-skills -a claude-code # OpenClaw npx skills add web-infra-dev/midscene-skills -a openclaw ``` Skills 仓库:[github.com/web-infra-dev/midscene-skills](https://github.com/web-infra-dev/midscene-skills) ## 模型配置 Midscene Skills 需要具备极强 UI 定位能力的多模态模型。请配置以下环境变量。你可以把它们设为系统环境变量,也可以写在当前工作目录的 `.env` 文件中(Midscene 会自动加载 `.env`)。 ```bash MIDSCENE_MODEL_API_KEY="your-api-key" MIDSCENE_MODEL_NAME="model-name" MIDSCENE_MODEL_BASE_URL="https://..." MIDSCENE_MODEL_FAMILY="family-identifier" ``` 支持的模型和配置详情请参考[支持的模型与配置](/zh/model-common-config.md)。 ## 使用 Skills 安装完成后,只需用自然语言把任务描述给你的 AI 编程助手即可。它会自动选择合适的 Skill、运行 CLI、读取截图并决定下一步。例如: > 打开相册应用,看看相册里的第一张照片是什么。 ## 案例:编码 Agent 写完代码后自行验证功能 以下示例中,我们让 Claude Code 开发一个 Electron Todo 应用。写完代码后,它会通过 `desktop-computer-automation` Skill 自行启动应用、操作 UI、截图验证功能是否符合预期。全程无需人工介入,也无需编写测试脚本。 **Prompt:** ``` 开发一个 Electron Todo 应用,包含添加、勾选、删除功能。 开发完成后,启动应用并用桌面自动化验证:添加 3 个 todo、勾选其中 1 个、删除 1 个,截图确认最终状态正确。 ``` 编码 Agent 会自主完成以下工作:编写 Todo 组件 → 启动 Electron 应用 → 连接桌面 → 截图识别 UI → 通过自然语言操作界面 → 截图验证结果。开发者只需描述意图,Skill 赋予 Agent “看屏幕、动鼠标”的能力,让它像人一样验证自己写的代码。 <video src="https://lf3-static.bytednsdoc.com/obj/eden-cn/nupipfups/Midscene/computer-skill.mp4" height="300" controls /> ## 更多应用场景 Skills 不仅限于本地桌面测试,通过组合不同 Skill 可以覆盖更多场景: * **桌面应用自动化测试**:验证 Electron、Qt、WPF 等桌面应用的功能流程 * **远程控制电脑**:通过远程桌面连接操控远程机器上的应用,实现远程运维和调试 * **移动端应用测试**:使用 `@midscene/android` 和 `@midscene/ios` Skill 在真机或模拟器上验证移动应用 * **跨应用工作流**:串联多个应用操作,如从浏览器取数据 → 粘贴到 Excel → 截图发送到 Slack * **CI/CD 集成**:在 Linux CI 中通过 Xvfb 无头模式运行,无需物理显示器 * **日常任务自动化**:批量填写表单、定时截图监控、自动整理文件等 ## 更多 请参考 [Skills 仓库](https://github.com/web-infra-dev/midscene-skills) 获取更多详情。 --- url: /zh/test-runner-overview.md --- # @midscene/test:下一代 AI 端到端测试框架 :::info 项目状态说明(Beta) 本文档介绍的 Test Runner 是 Midscene 全新打造的下一代通用测试运行器,采用声明式与可编程解耦的设计范式,用于替代原有的 YAML 自动化方案。 目前该方案正处于 **Beta** 体验阶段,测试协议与 API 正在持续演进。如果您有任何问题或建议,诚邀在 GitHub 上提交反馈。 (如果您仍在使用旧版方案,请查阅 [YAML 脚本运行器](/zh/yaml-script-runner.md)) ::: ## AI 变革时代下的测试工程挑战 即使 GUI Agent 已经能驱动起 E2E 测试流程,但离真正落地依然有很多实际的诉求,典型的有: 1. **需要结合确定性的脚本**:在实际测试场景中,仍然需要结合确定性的脚本(如 API 调用、数据准备、测试用例的生命周期控制等)来处理后台或前置任务,以确保测试工程的稳定、极速和高性价比。 2. **核心流程应为声明式语言**:希望核心流程是类似 YAML 的声明式语言,而不是在 `.ts` / `.js` 代码里揉着大量的自然语言,影响长期维护体验。 3. **留有共同维护的扩展空间**:需要留有扩展空间,让 Agent 和人类能共同维护测试脚本,使底层的确定性工程能与上层的自然语言意图具备清晰的边界和协作模式,避免代码和自然语言混杂。 为了应对这些落地挑战,Midscene 设计了全新的测试框架 `@midscene/test`。 ## 设计理念:声明式语义与确定性工程并重 为了顺应测试工程的演进,`@midscene/test` 回归端到端(E2E)测试的第一性原理,提倡\*\*“声明式语义与确定性工程并重”的设计理念\*\*。 为了应对上述问题,Midscene 的测试框架着重提供了以下能力: ### 基础原子能力与定制扩展能力 Midscene 提供了开箱即用的内置原子能力(不仅包含 `aiAct`、`aiAssert` 等 AI 交互,还包括环境初始化、设备与浏览器配置等),支持零配置快速上手。 为了应对复杂的业务场景,Midscene 支持通过 TypeScript 自定义原子节点(自定义 Node)。你可以将业务接口、数据准备或特定工具链进行封装,并在 YAML 用例中直接调用。这在保持 YAML 脚本简洁的同时,也使测试工程能够应对定制化的业务场景。 ### 用声明式语义的 YAML 文件来编写日常用例 聚焦“测什么” (What to test),承载业务测试意图。用例编写者可以直接用自然语言描述界面操作(如 `aiAct`)与校验(如 `aiAssert`),同时调用和装配由“确定性工程”所提供的各种业务节点。这有助于专注于业务测试流程,无需关注底层细节,提升了用例的编写效率与长期可维护性。 ### 自动生成 Markdown 说明书 提供 `describe-nodes` 描述工具,能够将注册的自定义 Node 及其入参校验规则(Zod Schema)一键自动编译导出为标准的 Markdown 说明文档。这免去了手动维护专属框架 API 手册的成本,方便用例编写者及 AI Agent 直接按需阅读与消费这些定制能力。 ### E2E 项目的工程化诉求 在实际企业级落地中,E2E 测试项目绝非一次性的、由 AI 驱动的即时验证,而是需要作为长期资产持续运行。因此,它必须面临稳定性、执行速度、可维护性等真实且严苛的工程化诉求。为此,Midscene 提供了配套的工程能力支持,保障测试在实际生产中的稳定高效: * **统一的可观测性与日志记录(Observability & Logging)**:无论是 AI 交互步骤还是自定义 Node 的运行,都将被统一记录在执行生命周期中。测试报告与运行日志会完整呈现每一个步骤的入参、出参、耗时、执行状态与界面截图。这方便了人类工程师进行问题排查与回放,同时也为 AI Agent 自主化地诊断问题、优化用例提供了必要的上下文数据。 * **标准的生命周期与并发控制**:提供生命周期钩子(Before/After)、环境并发机制,确保定制用例在持续集成(CI)等环境中并发、稳定地运行。 ## 实战案例:验证订单退款流程 假设电商团队需要验证已支付订单的退款流程: 1. **确定性工程(TypeScript 节点)**:用例开始前通过自定义 Node `order.prepare` 提前准备测试订单,结束后执行 `order.cleanup` 清理。 2. **声明式语义(AI 自然语言)**:Agent 根据 YAML 中的自然语言指令,在页面上提交退款申请并检查结果。 #### 项目文件结构 该实战项目的推荐文件目录结构如下: ```text ecommerce-tests/ ├── cases/ │ └── refund.yaml # 声明式用例:通过 YAML 文件描述具体测试用例和步骤 └── midscene.config.ts # 确定性工程:注册自定义 Node (如 order.prepare),定义执行环境 ``` * **`midscene.config.ts`**:由自动化/测试平台开发人员维护,用来实现底层“确定性工程”(编写自定义 Zod Schema 和 Node 的 execute 执行逻辑,配置浏览器/环境等)。 * **`cases/refund.yaml`**:由业务测试人员(或 AI Agent)维护,用来通过“声明式语义”(结合自然语言和自定义 Node)装配、表达高层业务测试意图。 #### 1. 自动导出的 Node 说明书示例 在此实战中,工程搭建者编写的 `order.prepare` 自定义节点,经过 `describe-nodes` 工具编译后,会自动生成如下 Markdown 说明书: ```markdown ## order.prepare * **标题**:准备订单数据 * **说明**:在数据库中提前创建一个处于指定状态的订单。 * **参数格式 (JSON Schema)**: { "type": "object", "properties": { "status": { "type": "string", "description": "订单状态,如 paid (已支付), refunded (已退款)。" } }, "required": ["status"] } ``` #### 2. 用例编写与运行(YAML) 用例编写者(或 AI Agent)阅读说明书后,可以直接在声明式 YAML 脚本中自由组装、调用该节点与 AI 动作: ```yaml # 1. 确定性工程:数据与环境准备 beforeEach: - order.prepare: status: paid - browser.openRefundPage: {} # 2. 声明式语义:表达业务测试意图 cases: - name: 已支付订单可以申请全额退款 steps: - aiAct: 点击申请退款,选择全额退款并提交 - aiAssert: prompt: 页面显示退款申请已提交,退款金额为订单的全部金额 message: 全额退款申请提交失败 - name: 已支付订单可以申请部分退款 steps: - aiAct: 点击申请退款,输入退款金额 10 元并提交 - aiAssert: prompt: 页面显示退款申请已提交,退款金额为 10 元 message: 部分退款申请提交失败 # 3. 确定性工程:资源清理 afterEach: - order.cleanup: {} ``` 框架维护者注册 `order.prepare`、`browser.openRefundPage` 和 `order.cleanup` 等自定义 Node 负责数据与页面管理,Midscene Node 负责界面操作与校验。测试意图与技术实现彼此分离,两类维护工作可以独立演进。 ## 接下来 * 阅读 [扩展和维护 Test Runner](/zh/extend-test-runner.md),了解如何注册 Node、管理运行资源和配置 Test Project。 * 阅读 [编写和运行测试用例](/zh/use-test-runner.md),了解 Case、Step、生命周期和运行结果。 --- url: /zh/use-test-runner.md --- # 编写和运行测试用例 框架维护者完成 Test Project 配置后,人类或 Agent 可以使用项目注册的 Node 编写测试用例。开始编写前,建议运行 `describe-nodes` 生成 Node 说明书,确认项目提供的能力和输入参数。 如果你尚不了解整体设计,请先阅读 [Test Runner 概览](/zh/test-runner-overview.md)。如果项目尚未配置,请阅读[扩展和维护 Test Runner](/zh/extend-test-runner.md)。 ## 核心概念 `@midscene/test` 使用以下概念描述测试项目。 ```text Test Project 配置 └── Execution Project(Web / Android / iOS / computer) └── Workflow Document ├── Lifecycle Step └── Case └── Step └── Node ``` * **Test Project(测试项目)**:由 `midscene.config.ts` 定义的顶层配置,负责注册共享 Node,并配置默认运行目标或 `projects` 数组。 * **Execution Project(执行项目)**:Test Project 内的一项子配置,描述一个运行目标及其平台、Project setup、文件和标签筛选、变量与 retry 策略;它不是独立于 Test Project 的同级实体。 * **Workflow Document(工作流文档)**:被 Execution Project 选中的 YAML 文件,包含生命周期和多个 Case。 * **Lifecycle Step(生命周期步骤)**:在 Workflow Document 的 `beforeAll`、`beforeEach`、`afterEach` 或 `afterAll` 钩子中声明并执行的步骤,用于进行数据准备、状态重置、资源清理等辅助性操作。 * **Case(用例)**:由多个 Step 组成的具名测试用例。 * **Step(步骤)**:在 YAML 中对 Node 的一次调用。 * **Node(节点)**:平台开发者注册的执行能力,例如 `aiAssert` 或 `order.create`。 Node 定义团队可用的执行能力,YAML 定义每个测试用例如何组合这些能力。 ## 编写 YAML 测试用例 每个 Workflow Document 必须包含非空的 `cases` 数组。每个 Case 必须包含 `name` 和非空的 `steps`。 ```yaml cases: - name: 创建订单 steps: - order.create: sku: midscene-mug quantity: 1 - aiAssert: 页面显示“下单成功” - name: 取消订单 steps: - order.cancel: orderId: example-order-id - aiAssert: 页面显示“订单已取消” ``` 运行器按照 YAML 中的声明顺序执行 Case。一个 Case 失败后,运行器会记录失败结果,并继续执行后续 Case。 ### 编写 Step 每个 Step 只能调用一个 Node。如果调用时只需传入 `prompt` 参数,可以使用字符串简写。 ```yaml steps: - aiAct: 点击提交订单按钮 ``` 上面的写法等价于: ```yaml steps: - aiAct: prompt: 点击提交订单按钮 ``` 业务 Node 可以接收自定义参数。 ```yaml steps: - order.create: sku: midscene-mug quantity: 2 ``` ### 设置超时和错误处理 `$` 用于设置由运行器控制的 Step 参数。运行器不会将这些参数传入 Node 的 `input`。 ```yaml steps: - order.create: sku: midscene-mug $: timeout: 30000 continue-on-error: true ``` 支持以下两个字段: * `timeout`:Step 的超时时间,单位为毫秒。 * `continue-on-error`:设为 `true` 后,即使 Step 失败,运行器也会继续执行当前阶段的后续 Step。默认值为 `false`。 `continue-on-error` 只控制运行器是否继续执行。只要有 Step 失败,Case 的最终状态就是 `failed`。 ### 使用 Project 变量与环境变量 运行器会在执行前递归解析 Node input: ```yaml steps: - launch: uri: ${appUri} - api.createOrder: baseURL: ${{TEST_API_BASE_URL}} payload: count: ${orderCount} ``` * `${name}` 读取当前 Execution Project 的 `variables`;独占整个标量时保留原始 JSON 类型。 * `${{ENV_NAME}}` 读取环境变量,结果始终是字符串。 * 对象或数组变量可以作为完整值使用,但不能嵌入更长的字符串。 * 未定义变量会在收集阶段失败。变量只解析 Node input,不解析 `$`。 Workflow YAML 不提供 `set`、`saveAs` 或 Step 输出表达式。每个 Step 独立运行,不会自动接收前序 Step 的结果。如需共享必要信息,请通过 Execution Project 的 `context` 显式提供。 ### 使用 tags 筛选 Case ```yaml cases: - name: Android 冒烟下单 tags: [smoke, android] steps: - aiAct: 完成下单 ``` 框架维护者在每个 Execution Project 中配置 `tags.include` 与 `tags.exclude`。exclude 始终优先;include 非空时,Case 命中任意一个 include tag 即会被选中。 ## 定义执行生命周期 Workflow Document 可以在 `cases` 前后声明生命周期 Step。 ```yaml beforeAll: - data.prepare: 创建本文件需要的测试数据 beforeEach: - browser.reset: 将页面恢复到初始状态 cases: - name: 创建订单 steps: - aiAct: 创建一个订单 - aiAssert: 页面显示“下单成功” - name: 取消订单 steps: - aiAct: 取消最新订单 - aiAssert: 页面显示“订单已取消” afterEach: - report.save: 保存当前用例的执行信息 afterAll: - data.cleanup: 删除本文件创建的测试数据 ``` 一个 Execution Project 的完整执行顺序如下。 ```text Project setup Workflow Document 1 beforeAll Case 1 attempt 1:beforeEach → steps → afterEach Case 1 retry: beforeEach → steps → afterEach Case 2 attempt 1:beforeEach → steps → afterEach afterAll Node 文档级清理 Workflow Document 2 ... Project teardown ``` 各阶段的职责如下: * `beforeAll` 和 `afterAll` 对每个 YAML 文件各运行一次,负责文档级业务准备与清理。 * `beforeEach` 和 `afterEach` 对每次 Case attempt 各运行一次。 * retry 会以新的 run ID、Agent scope 和缓存 scope 重跑整个 Case,但不会重跑 `beforeAll`。 * Node 可以注册 attempt 或 Document scope 的内部清理,例如释放 Agent 和生成报告;这些清理在相应 scope 结束时执行。 即使 Case 主体失败,`afterEach` 仍会执行。 如果 `beforeAll` 失败,运行器会将当前文件中的 Case 标记为 `not-run`,但仍会执行 `afterAll` 和已经注册的 Node 清理。 Project setup 在该 Project 的 Workflow Document 之前只执行一次,Project teardown 始终按 LIFO 顺序执行。收到中断信号时,当前 Step 会被取消,但 cleanup hook 与已注册 teardown 会使用可执行清理工作的 signal。 ## 运行测试 在项目根目录下,使用以下命令运行 YAML 测试用例: ```bash pnpm exec midscene-test ``` ### 指定用例目录或文件 默认情况下,运行器会递归查找并执行项目根目录下的所有 `.yaml` 和 `.yml` 文件(自动忽略 `node_modules` 和 `.git`)。 如果你只想执行特定目录或特定用例文件,可以将其作为参数传入: ```bash # 运行指定目录下的所有用例 pnpm exec midscene-test ./cases/smoke # 运行单个指定的用例文件 pnpm exec midscene-test ./cases/order.yaml ``` ### 过滤运行目标与配置文件 如果项目配置了多个运行平台或环境,你可以指定仅运行特定的 Execution Project,或者通过命令行指定自定义配置文件: ```bash # 只运行名为 android-smoke 和 ios-regression 的 Execution Project pnpm exec midscene-test --project android-smoke --project ios-regression # 使用指定的配置文件运行测试 pnpm exec midscene-test --config ./config/midscene.config.ts ``` 当发生用例运行失败、文档解析失败或收集阶段发生错误时,CLI 会返回退出码 `1`。 ## 查看测试结果 每次测试运行结束后,你可以在控制台直接看到用例的执行状态和简要汇总。同时,运行器会在本地生成可视化的测试报告。 ### Midscene 可视化报告 运行器会自动将详细的测试步骤、界面截图和 AI 决策过程记录到交互式 HTML 报告中。 默认情况下,报告保存在: ```text midscene_run/report/ ``` 你只需在浏览器中打开该目录下的 HTML 文件,即可直观地看到每一个 `aiAct` 和 `aiAssert` 的执行轨迹、元素定位结果以及完整的历史截图。 > **提示**:如果想修改可视化报告的默认保存目录,可以通过修改 `midscene.config.ts` 中的 `output.reportDir` 配置来实现。 ## 当前限制 目前版本的测试运行器存在以下局限性,在编写用例时需予以注意: * **无并行执行**:在单个 Execution Project 内部,所有的 Workflow Document、Case、attempt 和 Step 均为串行执行,暂不支持用例维度的并发。 * **无控制流**:不支持 DAG(有向无环图)、分支(If-Else)或循环(Loop)等控制流,用例按照声明顺序线性执行。 * **无用例间数据依赖**:不同的 Workflow Document 之间是完全隔离的,无法传递或共享执行中的数据。 * **仅限本地加载**:暂不支持直接从远程 URL、npm 包或 Git 仓库加载并执行 YAML 文件。 此外,运行器会在执行前对所有 YAML 文件进行静态收集和校验。若存在未知的顶层字段、未注册的 Node 或无效的 Step,运行器会立即抛出收集错误(Collection Error)并中断运行,不会等到执行该 Case 时才报错。 --- url: /zh/yaml-script-runner.md --- # YAML 脚本运行器 :::warning 下一代方案升级提示 本文档介绍的是老版 YAML 自动化运行方案。我们已推出了全新、面向未来的 **[Test Runner](/zh/test-runner-overview.md) (Beta)**。 新方案采用“自然语言驱动主线,可编程可定制 Node 作为辅助”的全新测试范式,支持完备的测试生命周期、多环境并发隔离以及标准化运行报告,是老版方案的**官方替代升级版**。 目前新方案正处于 **Beta 开放阶段**,我们强烈建议您阅读 **[Test Runner 概览](/zh/test-runner-overview.md)** 并基于新方案进行项目实践与迁移。 ::: Midscene 定义了一种 YAML 格式的脚本,方便开发者快速编写自动化脚本,并提供了对应的命令行工具来快速执行这些脚本。 举例来说,你可以编写如下 YAML 格式脚本示例: ```yaml page: url: https://www.bing.com tasks: - name: 搜索天气 flow: - ai: 搜索 "今日天气" - sleep: 3000 - aiAssert: 结果显示天气信息 ``` 并通过一条命令来执行它: ```bash midscene ./bing-search.yaml ``` 命令行会输出执行进度,并在完成后生成可视化报告。整个运行过程大幅简化了开发者做环境配置的复杂度。 本文将介绍如何使用 Midscene 的命令行工具。关于更多 YAML 格式脚本的内容,可以参考 [使用 YAML 格式的自动化脚本](/zh/automate-with-scripts-in-yaml.md)。 ## 使用 `.env` 配置环境变量 Midscene 命令行工具使用 [dotenv](https://www.npmjs.com/package/dotenv) 来加载 `.env` 文件。你可以在工具运行目录下创建一个 `.env` 文件,并添加以下配置: ```ini filename=.env MIDSCENE_MODEL_BASE_URL="https://替换为你的模型服务地址/v1" MIDSCENE_MODEL_API_KEY="替换为你的 API Key" MIDSCENE_MODEL_NAME="替换为你的模型名称" MIDSCENE_MODEL_FAMILY="替换为你的模型系列" ``` 支持的模型和完整配置示例请参考[支持的模型与配置](/zh/model-common-config.md)。 请注意: * 这个文件不是必须的,你也可以通过全局环境变量的形式来配置 * 请注意这里没有 `export` 前缀,这是 dotenv 库的约定 * `.env` 文件必须放置在**工具运行目录**下,而与 YAML 文件所在的目录无关。 * 这些变量默认是**不覆盖**全局环境变量中已经的同名变量的,如需修改这个策略,请参考后文“--dotenv-override” 参数 * 如需调试此部分环境变量的逻辑,可使用 `--dotenv-debug` 参数 ## 开始使用 ### 安装命令行工具 安装 CLI 前,请确认运行 `midscene` 的终端使用 Node.js `20.19+`、`22.12+` 或 `24+`。CLI 的部分执行路径会使用 Rstest/Rspack 工具链,这些依赖会拒绝 `20.17.0` 这类较旧的 Node 20 patch 版本。如果看到 Rspack 抛出的 `Unsupported Node.js version` 提示,请升级 Node.js 后重新安装全局 CLI 或项目依赖。 全局安装 `@midscene/cli` (推荐新手使用): ```bash npm i -g @midscene/cli ``` 或在项目中按需安装 ```bash npm i @midscene/cli --save-dev ``` ### 编写第一个脚本 编写一个名为 `bing-search.yaml` 的文件来驱动 Web 浏览器: ```yaml page: url: https://www.bing.com tasks: - name: 搜索天气 flow: - ai: 搜索 "今日天气" - sleep: 3000 - aiAssert: 结果显示天气信息 ``` 驱动已连接 adb 的 Android 设备: ```yaml android: deviceId: s4ey59 # device id 可以在 adb 命令行中通过 `adb devices` 命令获取 tasks: - name: 地图导航 flow: - ai: 打开地图应用 - ai: 在搜索栏输入 "杭州西湖",然后点击搜索按钮 - ai: 点击第一个搜索结果,进入详情页 - ai: 点击 "路线" 按钮,进入路线规划页面 - ai: 点击 "开始" 按钮开始导航 ``` 或者驱动配置好 WebDriverAgent 的 iOS 设备: ```yaml ios: wdaPort: 8100 tasks: - name: 修改系统设置 flow: - ai: 打开设置应用 - ai: 点击 "显示与亮度" - ai: 开启 "深色模式" - aiAssert: 深色模式已开启 ``` ### 运行脚本 ```bash midscene ./bing-search.yaml # 如果在项目中安装了 Midscene npx midscene ./bing-search.yaml ``` 命令行会输出执行进度,并在完成后生成可视化报告。 ## 命令行工具的高级用法 ### 在 `.yaml` 中使用环境变量来填入动态值 脚本中可以通过 `${variable-name}` 引用环境变量。环境变量会在 YAML 任务执行前完成替换,包括任务正文中的字符串。 ```ini filename=.env topic=weather today ``` ```yaml # ... - ai: 在输入框中输入 ${topic} # ... ``` ### 运行多个脚本 `@midscene/cli` 支持使用通配符匹配多个脚本来批量执行脚本,这相当于 `--files` 参数的简写。 ```bash # 运行单个脚本 midscene ./bing-search.yaml # 使用通配符模式运行所有匹配的脚本 midscene './scripts/**/*.yaml' ``` ### 分析命令行运行结果 执行完成后,输出目录会包含: * `--summary` 指定的 JSON 报告(默认 `index.json`),记录所有脚本的执行状态与统计数据。 * 每个 YAML 文件对应的独立执行结果(JSON 格式)。 * 每个脚本生成的可视化报告(HTML 格式)。 ### 运行在可视化(Headed)模式 > 仅适用于 Web page 场景 Headed 模式会打开浏览器窗口。默认情况下脚本在无头模式运行。 ```bash # 运行在 headed 模式 midscene /path/to/yaml --headed # 运行在 headed 模式并在结束后保留窗口 midscene /path/to/yaml --keep-window ``` ### 使用 CDP 连接模式 > 仅适用于 `web` 场景 CDP 模式可以让 YAML 脚本通过 Chrome DevTools Protocol 连接到已有的浏览器实例,无需启动新浏览器。适用于需要复用已有浏览器会话、连接远程浏览器或云端浏览器服务的场景。 在 `page` 配置中设置 `cdpEndpoint`: ```diff page: url: https://www.bing.com + cdpEndpoint: ws://localhost:9222/devtools/browser ``` :::info CDP 模式与桥接模式互斥,不可同时使用。CDP 模式下 Midscene 只会断开连接(disconnect),不会关闭浏览器。 ::: ### 使用桥接模式 > 仅适用于 Web page 场景 使用桥接模式可以让 YAML 脚本驱动现有的桌面浏览器,便于复用 Cookies、插件或已有状态。先安装 Chrome 扩展,然后在 `page` 配置中加入: ```diff page: url: https://www.bing.com + bridgeMode: newTabWithUrl ``` 更多细节请参阅 [通过 Chrome 插件桥接模式](/zh/bridge-mode.md)。 ### 使用 JavaScript 运行 YAML 脚本 调用 Agent 的 [`runYaml`](/zh/reference.md#runyaml) 方法同样可以在 JavaScript 中执行 YAML,注意该方法只会运行脚本中的 `tasks` 部分。 ## 命令行参数 命令行工具提供了多项参数,用于控制脚本的执行行为: * `--files <file1> <file2> ...`:指定脚本文件列表。默认按顺序执行(`--concurrent` 为 `1`),可通过 `--concurrent` 设置并发数量。支持 [glob](https://www.npmjs.com/package/glob) 通配符语法;当 glob 或目录匹配到多个文件时,匹配结果会按文件路径的字典序排序后加入执行列表。 * `--setup <file>`:在主 `--files` 之前执行的前置脚本,适用于所有受支持的 target。如果所有 setup attempt 都失败,整个批次会被中止,主脚本将被标记为未执行。Puppeteer Web setup 必须配合 `--share-browser-context` 使用;此时每次 retry 都从全新的 BrowserContext 和 Page 开始,成功 attempt 的上下文会共享给主脚本。每个 YAML 仍在独立 Page 中运行,因此 `sessionStorage` 等页面级状态不会在脚本间传递。桥接模式和非 Web target 必须省略 `--share-browser-context`;它们在 retry 时会创建新的 player 和 Agent,但不会重置底层浏览器、设备、桌面或外部 Interface 的状态。 * `--concurrent <number>`:设置并发执行的数量,默认 `1`。 * `--continue-on-error`:启用后,即使某个脚本失败也会继续执行后续脚本。默认关闭。 * `--retry <number>`:失败脚本的额外尝试次数。只有失败的脚本会被重试,可缓解网络波动或大模型输出不稳定导致的偶发失败。默认 `0`。Puppeteer Web setup retry 使用全新的 BrowserContext 和 Page,主脚本 retry 则保留成功的 setup 上下文。其他 target 每次 retry 会创建新的 player 和 Agent,但不会重置底层运行环境。 * `--share-browser-context`:在多个 Puppeteer Web 脚本之间共享同一个 BrowserContext(Cookies、同源 `localStorage` 等),同时每个 YAML 使用独立 Page。即使 `--concurrent 1`,`sessionStorage`、DOM、URL、`window.name` 等页面级状态也不会共享。同一批次中的 setup 和主脚本必须全部使用 Puppeteer Web target;不支持桥接模式和非 Web target。由于 Browser 只会创建或连接一次,浏览器级选项(`cdpEndpoint`、`chromeArgs`、`acceptInsecureCerts` 和 `downloadPath`)必须放在批次配置的全局 Web target 中,不能放在单个 setup 或主脚本中。默认关闭。 * `--summary <filename>`:指定生成的 JSON 总结报告路径。 * `--headed`:在带界面的浏览器中运行脚本,而非默认的无头模式。 * `--keep-window`:脚本执行完成后保持浏览器窗口,会自动开启 `--headed` 模式。 * `--config <filename>`:指定配置文件,文件中的参数会作为命令行参数的默认值。 * `--web.userAgent <ua>`:设置浏览器 UA,覆盖所有脚本中的 `web.userAgent`。 * `--web.viewportWidth <width>`:设置浏览器视口宽度,覆盖所有脚本中的 `web.viewportWidth`。 * `--web.viewportHeight <height>`:设置浏览器视口高度,覆盖所有脚本中的 `web.viewportHeight`。 * `--android.deviceId <device-id>`:设置安卓设备 ID,覆盖所有脚本中的 `android.deviceId`。 * `--harmony.deviceId <device-id>`:设置 HarmonyOS 设备 ID,覆盖所有脚本中的 `harmony.deviceId`。 * `--ios.wdaPort <port>`:设置 WebDriverAgent 端口,覆盖所有脚本中的 `ios.wdaPort`。 * `--ios.wdaHost <host>`:设置 WebDriverAgent 主机地址,覆盖所有脚本中的 `ios.wdaHost`。 * `--computer.displayId <display-id>`:设置显示器 ID,覆盖所有脚本中的 `computer.displayId`。 * `--dotenv-debug`:开启 dotenv 的调试日志,默认关闭。 * `--dotenv-override`:允许 dotenv 覆盖同名的全局环境变量,默认关闭。 示例: 使用 `--files` 指定执行顺序: ```bash midscene --files ./login.yaml ./buy/*.yaml ./checkout.yaml ``` 以 4 个并发执行多个互不依赖的搜索脚本,并在出错时继续运行: ```bash midscene --files './scripts/search-*.yaml' --concurrent 4 --continue-on-error ``` ### 通过文件编写命令行参数 可以把参数写到 YAML 配置文件中,并通过 `--config` 引用。命令行传入的参数优先级高于配置文件。 配置文件还可以包含一个全局 target 部分:`page`、`browser`、`web`、已弃用的 `target`、`android`、`ios`、`harmony`、`computer` 或 `interface`。其中的字段会深度合并到每个脚本对应的 target 部分,并覆盖脚本中的同名字段。完整优先级为:命令行 target 参数 > 配置文件中的 target 部分 > 单个脚本中的 target 部分;未被覆盖的字段会保留。合并后的每个脚本仍必须且只能解析出一种 target 类型。 ```yaml files: - './scripts/search-iphone.yaml' - './scripts/search-laptop.yaml' - './scripts/search-headphones.yaml' - './scripts/search-camera.yaml' concurrent: 4 continueOnError: true retry: 2 ``` 例如,下面的配置会为所有匹配的脚本统一指定 HarmonyOS 设备,同时保留单个脚本中未被覆盖的其他 HarmonyOS 设置: ```yaml files: - './scripts/harmony/*.yaml' harmony: deviceId: '127.0.0.1:5555' autoDismissKeyboard: true ``` 运行方式: ```bash midscene --config ./config.yaml ``` 当脚本必须严格按照 `files` 列表的顺序执行时,请设置 `concurrent: 1`(默认值)。 当该值大于 `1` 时,执行顺序不确定;脚本不得依赖其他脚本的启动或完成顺序。 #### 在并行脚本之前执行前置任务 当多个互不依赖的 Puppeteer Web 脚本都依赖同一个前置条件(例如登录)时,可以把前置任务写在 `setup` 下。前置脚本会在主 `files` 之前执行;成功后,主脚本再按配置的并发数运行。设置 `shareBrowserContext: true` 后,成功 setup attempt 的浏览器上下文(包括 Cookies 和同源 `localStorage`)会被主脚本复用。每个脚本使用独立 Page,因此 setup Page 的 `sessionStorage`、DOM、URL、`window.name` 和其他页面级状态不会复制到主 Page。同一共享批次中的所有脚本都必须使用 Puppeteer Web target,不支持桥接模式。 共享 Browser 只会根据批次配置创建或连接一次。请在批次配置的全局 Web target 中设置 `cdpEndpoint`、`chromeArgs`、`acceptInsecureCerts` 和 `downloadPath`。如果在单个 setup 或主脚本中定义这些浏览器级选项,CLI 会直接报错,因为它们无法应用到已经创建的共享 Browser。 ```yaml setup: ./scripts/login.yaml files: - ./scripts/search.yaml - ./scripts/report.yaml - ./scripts/settings.yaml shareBrowserContext: true concurrent: 3 retry: 2 ``` `retry` 表示额外尝试次数,因此 `retry: 2` 最多会执行三次。每次 setup retry 都会创建新的 BrowserContext 和 Page,避免失败 attempt 留下的 Cookies、`localStorage`、页面导航状态及其他浏览器副作用污染下一次尝试。setup 成功后,其上下文会与主脚本共享;主脚本失败时会在同一上下文中重试,从而保留成功 setup 建立的状态。如果所有 setup attempt 都失败,整个批次会被中止,主脚本将被标记为未执行。 :::warning 页面级状态不会共享 `shareBrowserContext` 遵循浏览器原生的存储边界,不会在 Page 之间复制或同步 `sessionStorage`。如果 setup 只把登录态写入 `sessionStorage`,主脚本将无法继承该登录态。需要让多个脚本看到前置状态时,请优先使用 Cookies、同源 `localStorage` 或服务端状态。脚本并发运行时,共享状态的写入可能发生竞争,需要显式协调。 ::: `setup` 也支持桥接模式、Android、iOS、HarmonyOS、Computer 和自定义 Interface target。这些 target 应省略 `shareBrowserContext`。retry 会创建新的脚本 player 和 Agent,但不会自动重置底层浏览器配置、设备、桌面会话或自定义 Interface;如果 retry 必须从干净环境开始,需要由 setup 脚本显式恢复环境。 ## 常见问题 **如何导出 JSON 格式的 Cookies?** 可以借助 [Chrome 扩展](https://chromewebstore.google.com/detail/get-cookiestxt-locally/cclelndahbckbenkjhflpdbgdldlbecc) 导出 Cookies。 **如何查看 dotenv 的调试日志?** 使用 `--dotenv-debug` 参数即可: ```bash midscene /path/to/yaml --dotenv-debug=true ```