> For the complete documentation index, see [llms.txt](https://tinyhumans.gitbook.io/openhuman/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://tinyhumans.gitbook.io/openhuman/zh/kai-fa/e2e-testing.md).

# 端到端测试

如何运行端到端测试套件：使用 Playwright 针对独立核心，使用 WDIO 通过原生 Wry 宿主，以及本地脚本、环境变量和 CI 流水线。

端到端覆盖使用 Rust mock 后端、真实浏览器中的 Playwright，以及通过桌面 Wry 宿主的 WebDriverIO（WDIO）。每个断言都有一个负责人：Playwright 负责浏览器渲染行为；WDIO 负责需要原生进程、窗口、操作系统或 Tauri IPC 的行为。CI 中只运行受支持的 Linux 桌面 harness。macOS 和 Windows 的桌面作业保持禁用，直到可用原生驱动为止。

## 概览

桌面 E2E 测试使用 WebDriverIO（WDIO）通过单个 `tauri-driver` （WebDriver）会话驱动其原生 Wry/WebKit webview：

| 平台    | 驱动                             | 端口   | 应用格式    | 选择器       |
| ----- | ------------------------------ | ---- | ------- | --------- |
| Linux | tauri-driver + WebKitWebDriver | 4444 | 调试二进制文件 | CSS / DOM |

Linux CI 通过 `tauri-driver`在 Xvfb 下驱动调试二进制文件。macOS 和 Windows 目前还没有自动化桌面 E2E 覆盖。它们曾使用的基于 Chromium 的驱动已经不再可用，必须由原生驱动（macOS 上的 Appium Mac2，Windows 上的 WinAppDriver）替代。 `pnpm --filter openhuman-app test:e2e:build` 仍然会在 macOS 上生成一个 `.app` 包，用于手动测试，但那里没有受支持的自动化会话。

***

## 快速开始

### Linux

```bash
# 构建 E2E 应用
pnpm --filter openhuman-app test:e2e:build

# 通过 tauri-driver 会话运行一个原生 spec
pnpm --filter openhuman-app test:e2e:session

# 在四个桌面 suite 分组中运行原生专用流程
pnpm --filter openhuman-app test:e2e:all:flows

# 在不启动 core 的情况下列出分配给 lane 1 的 Playwright 单元
bash app/scripts/e2e-web-shards.sh --lane=1 --lanes=4 --list

# 运行该 Playwright lane（每个单元都使用新的 core 和 workspace）
bash app/scripts/e2e-web-shards.sh --lane=1 --lanes=4

# 检查 spec 基名是否只有一个运行器所有者
pnpm e2e:ownership:check

# 运行单个 spec
bash app/scripts/e2e-run-spec.sh test/e2e/specs/smoke.spec.ts smoke
```

`app/scripts/e2e-run-session.sh` 启动 `tauri-driver` 于 `TAURI_DRIVER_PORT` （默认 `4444`）并使用 `WebKitWebDriver` 作为其原生驱动，等待其 `/status` 端点，然后针对 `app/test/wdio.conf.ts`运行 WDIO。在无头 Linux 上，应用本身在 Xvfb 下运行以提供虚拟显示；驱动进程不需要它。

### 在 macOS 上使用 Docker（本地使用 Linux harness）

要在 macOS 上运行同样的 Linux harness，请使用 Docker。

```bash
# 构建并运行所有 E2E 流程
docker compose -f e2e/docker-compose.yml run --rm e2e

# 先构建应用（如有需要）
docker compose -f e2e/docker-compose.yml run --rm e2e \
  pnpm --filter openhuman-app test:e2e:build

# 运行单个 spec
docker compose -f e2e/docker-compose.yml run --rm e2e \
  bash app/scripts/e2e-run-spec.sh test/e2e/specs/smoke.spec.ts smoke
```

需要 Docker Desktop 或 Colima。仓库通过 bind mount 挂载，因此构建结果会在运行之间保留。

***

## 架构

### 平台检测

`app/test/e2e/helpers/platform.ts` 导出两个函数，两者都硬编码为 `true`: `isTauriDriver()` 和 `supportsExecuteScript()`。每个会话都暴露 webview DOM 并支持 `browser.execute()`，因此仍然按这两个检查分支的 spec 总是走 DOM 路径。把它们当作兼容性 shim，而不是平台检测。

### 元素辅助工具

`app/test/e2e/helpers/element-helpers.ts` 提供一套针对 webview DOM 的 API：

| 辅助工具                      | 行为                                           |
| ------------------------- | -------------------------------------------- |
| `waitForText(text)`       | 对 DOM 文本内容使用 XPath                           |
| `waitForButton(text)`     | `按钮` / `[role="button"]` XPath               |
| `clickText(text)`         | 标准 `el.click()`                              |
| `clickNativeButton(text)` | 标准 `el.click()` 在按钮上                         |
| `clickToggle()`           | `[role="switch"]` / `input[type="checkbox"]` |
| `waitForWindowVisible()`  | 窗口句柄检查                                       |
| `waitForWebView()`        | `document.readyState` 检查                     |
| `hasAppChrome()`          | 窗口句柄检查                                       |
| `dumpAccessibilityTree()` | HTML 页面源代码                                   |

### 稳定的测试 ID

优先使用稳定的 `data-testid` 用于 E2E spec 点击或轮询的 UI 交互钩子。使用以下命名法： `<surface>-<element>-<id?>`，例如：

* `cron-jobs-panel`, `cron-refresh`
* `cron-job-row-<jobId>`, `cron-job-toggle-<jobId>`, `cron-job-run-<jobId>`, `cron-job-view-runs-<jobId>`, `cron-job-remove-<jobId>`
* `settings-nav-<routeId>`
* `skill-row-<skillId>`, `skill-install-<skillId>`, `skill-uninstall-<skillId>`
* `thread-row-<threadId>`, `new-thread-button`, `send-message-button`
* `onboarding-next-button`

使用 `waitForTestId(testId)` 和 `clickTestId(testId)` 来自 `element-helpers.ts` 当 spec 目标指向这些钩子之一时使用。将文本选择器保留给面向用户可见文案的断言，不要用于行/动作发现。

### 深层链接辅助工具

`app/test/e2e/helpers/deep-link-helpers.ts` 处理认证深层链接：

* 主要路径： `browser.execute(window.__simulateDeepLink(url))`。它在 tauri-driver 支持的每个平台上的 webview 中都可工作。
* 仅 macOS 的回退： `macos: deepLink` 扩展命令，然后 `open -a ...`。CI 不会执行这些，因为 macOS 没有自动化桌面会话。
* Linux 没有 shell 回退。 `xdg-open openhuman://...` 需要一个 `.desktop` 文件来注册 URL scheme，而 CI 容器没有这个文件。如果那里的 webview 模拟调用失败， `triggerDeepLink` 会立即抛出异常。

对于发布候选版本，当你改动单实例或深层链接启动代码时，也请在 Linux 或 macOS 上手动运行一次辅助实例 smoke test。它会测试 `tauri-plugin-single-instance`，OpenHuman 通过 `deep-link` 功能注册它。

1. 正常启动 OpenHuman 并让它保持运行。
2. 触发 `openhuman://auth?token=e2e-token&key=auth` ，通过操作系统打开器。
3. 确认已经运行的窗口收到回调，而不是启动第二个应用实例。
4. 确认辅助进程正常退出。

这可以捕获在 Tauri 的深层链接转发路径安装之前就启动，或因错误退出的第二个实例。

### 编写跨平台 spec

1. 使用 `element-helpers.ts`中的辅助工具。绝不要在 spec 中直接使用原始 `XCUIElementType*` 选择器。
2. 使用 `clickNativeButton(text)` 而不是内联按钮点击代码。
3. 使用 `hasAppChrome()` 而不是检查 `XCUIElementTypeMenuBar`.
4. 使用 `waitForWebView()` 而不是检查 `XCUIElementTypeWebView`.
5. 对于仅 macOS 的测试，使用 `process.platform` 守卫或单独的 spec 文件。
6. 使用 `navigateViaHash(route)` 用于 hash 路由。返回前它会等待 hash、 `document.readyState` 以及已挂载的 React 根。onboarding 之后， `walkOnboarding()` 还会等待 `#/home` 和一个主页标记，然后 spec 才会导航到其他位置。

***

## 环境变量

| 变量                          | 默认值     | 说明                                                       |
| --------------------------- | ------- | -------------------------------------------------------- |
| `TAURI_DRIVER_PORT`         | `4444`  | 端口 `tauri-driver` 监听于； `wdio.conf.ts` 连接到这里              |
| `E2E_MOCK_PORT`             | `18473` | Mock 后端服务器端口                                             |
| `E2E_PORT_BASE`             | 未设置     | Web lane 端口块：mock `base`、core `base+1`、web host `base+2` |
| `OPENHUMAN_WORKSPACE`       | （临时目录）  | 应用工作区目录                                                  |
| `OPENHUMAN_SERVICE_MOCK`    | `0`     | 启用服务 mock 模式                                             |
| `OPENHUMAN_E2E_MODE`        | 未设置     | 启用破坏性测试支持 RPC；E2E 运行器将其设为 `1`                            |
| `OPENHUMAN_E2E_AUTH_BYPASS` | 未设置     | 启用 JWT 绕过认证                                              |
| `DEBUG_E2E_DEEPLINK`        | （详细）    | 设置为 `0` 以关闭深层链接日志                                        |
| `E2E_FORCE_CARGO_CLEAN`     | 未设置     | 在 E2E 构建前强制 cargo clean                                  |

同一台机器上的两个 web E2E 会话需要独立端口。只要它的三个端口中有任何一个已经在监听，该 lane 就会拒绝启动。它的就绪探测只是普通的 HTTP GET，而其他会话的 mock、core 和 web host 也都能同样正确地响应，而 `openhuman-core run` 会回退到相邻端口而不是退出，因此它可能会在一个没有任何探测器访问的端口上继续存活。将 `E2E_PORT_BASE` 同时用于构建和运行。mock 和 core 端口会编译进包中，之后无法更改。

```bash
E2E_PORT_BASE=31000 pnpm --filter openhuman-app test:e2e:web
```

***

## CI 工作流

### Push / PR 检查

默认的拉取请求门禁是 `.github/workflows/ci-fast.yml`：质量检查，加上每个变更区域的完整单元测试套件。针对 main 的拉取请求不会运行任何 E2E 套件。 `main`.

`.github/workflows/ci-full.yml` 运行在目标为 `release` 的拉取请求上，以及每次 push 到该分支时。其最终门禁要求通过 Playwright 矩阵、原生桌面 suite 和 Rust E2E：

| lane           | 作业                                  | 平台                                        |
| -------------- | ----------------------------------- | ----------------------------------------- |
| Rust mock 后端   | `rust-e2e`                          | Linux                                     |
| Playwright web | `playwright-e2e`                    | 四个必需的 Linux 作业，每个作业 16 个普通 shard 加上轮询特殊情况 |
| 桌面             | `e2e-desktop`，通过 `e2e-reusable.yml` | 四个 Linux suite 分组                         |

Playwright 先构建一次 web bundle 和独立 core，然后将同一个制品上传到所有四个必需 lane。必需选择会运行已发现的 110 个 spec 中的 109 个：整合清单中的全部 48 个活跃浏览器所有者及其断言接收者仍然是必需的，其他所有健康的浏览器 spec 也是如此。它会调度 64 个普通 shard 和 9 个独立案例（Connections 别名、Connections 深层链接，以及 7 个 Skills 搜索/安装案例），每个都有自己的 core 和 workspace：总计 73 个必需单元。一个单独的 `playwright-legacy-diagnostic` 作业运行未更改的 `aui-tool-result-elements.spec.ts` ，作为一个文件原子单元。其组件兼容性阻塞项记录在 [issue #236](https://github.com/senamakel/openhuman/issues/236#issuecomment-6284472501)中。只有这个诊断作业是建议性的；它被排除在最终门禁之外，而全部四个必需 lane 仍然严格执行。调度器会继续处理已分配单元以收集失败，并在任何必需单元失败时以非零退出。因此，CI Full 会在必需和诊断作业中运行 74 个新的 Playwright 单元，但不会声称旧诊断通过。手动 Playwright 会严格选择全部 110 个 spec，使用相同的 64 个 shard 和 9 个独立案例（73 个单元）；它没有建议性选择。

桌面 lane 调用可复用工作流，并设置 `run_linux: true`, `run_macos: false` 和 `run_windows: false`，因此只运行 Linux harness。Linux 使用四个新的 WDIO 会话： `基础` (`auth,navigation,system`), `聊天` (`chat,skills,journeys`), `connections` (`providers,provider-web,connectors`），以及 `设置` (`settings,notifications`）。支付和 webhooks 在它们的浏览器所有者迁移到 Playwright 之后，不再有原生流程，因此不在原生 shard 分配中。整合过程审查了 51 对同 basename 的原生/Playwright 配对；它只保留了 5 个狭义拆分的混合案例，并退役了 1 对过时的离线横幅配对。原生 suite 还保留了重复清单之外的 24 个既有原生专用文件，另外还有 `tauri-commands.spec.ts`，其浏览器启动检查已移到 Playwright smoke 所有者，而其原生 IPC 检查仍然保留。这 24 个原生专用文件并未评估是否需要桌面宿主。可复用工作流也会将相同的分组定义应用于其 macOS 和 Windows 全套作业，但这些作业目前已禁用。

因此，macOS 和 Windows 桌面 E2E 在任何分支上都没有排程覆盖。 `.github/workflows/e2e.yml` 是一个手动触发，其 `run_macos` 和 `run_windows` 输入也默认为 `false`。旧的基于 Chromium 的驱动依赖 CEF/CDP，并且在 Wry 下不可用；不要把这些作业描述为有效覆盖。

### 运行器所有权与混合宿主案例

每个 spec 文件都属于一个运行器。将浏览器渲染 UI、浏览器路由、表单、聊天渲染和设置持久化放在 `app/test/playwright/specs/`中。将 `app/test/e2e/specs/` 保留给需要实时桌面进程、真实 Tauri 命令、OS 通知权限、原生深层链接启动、窗口行为或 core 重启的检查。仅仅一次 JSON-RPC 调用并不会让测试变成桌面测试。将共享的元素动作保留在 `app/test/e2e/helpers/element-helpers.ts`.

有 5 个文件故意同时存在于两个运行器目录中，因为两侧证明的是不同的契约：

| 文件                                 | 原生 WDIO 断言                     | Playwright 断言                           |
| ---------------------------------- | ------------------------------ | --------------------------------------- |
| `smoke.spec.ts`                    | 实时 Wry 窗口、应用外壳以及正常的嵌入式 core 启动 | 浏览器根/外壳、独立 core 启动 URL/令牌和 RPC          |
| `login-flow.spec.ts`               | 桌面窗口和深层链接令牌处理，包括无效、过期或绕过情况     | 浏览器回调入口、令牌/配置文件结果、浏览器绕过和 onboarding     |
| `auth-access-control.spec.ts`      | 桌面重新认证和原生认证过期事件投递              | 账户/账单/注销/撤销 UI 以及权威认证状态                 |
| `notifications.spec.ts`            | OS 权限状态和真实通知插件调用               | 渲染后的通知标题/正文以及 System Events/空 Alerts UI |
| `settings-data-management.spec.ts` | 重置 IPC、用户数据擦除和嵌入式 core 重启      | 清除数据确认和取消 UI                            |

可执行文件守卫 `pnpm e2e:ownership:check` 拒绝任何其他重复、过期例外或缺失文件。在添加 spec 之前，先决定哪个运行时拥有该行为，在两个 spec 目录中搜索等价流程（包括已重命名文件），并且只将其添加到拥有该行为的 suite 中。如果宿主和浏览器案例覆盖的是不同的行为，请明确命名并记录两者的职责；切勿保留第二份完整副本作为额外保险。

当前清单有 30 个原生 spec 文件和 110 个 Playwright spec 文件，只有上面这 5 个混合文件按基名共享。那 24 个既有原生专用文件不在重复清单中，也未评估是否需要桌面宿主； `tauri-commands.spec.ts` 保留了上文所述的原生 IPC 检查。离线横幅 E2E 已被退役，因为在这个基线之前，唯一的 UI 消费者已被移除；专门的连接性选择器和监听器单元测试保留了设备/后端区分，但并未声明有 E2E 横幅覆盖。

***

## 故障排查

### Linux：“WebView 未就绪”超时

这通常意味着 `tauri-driver` 从未到达其 `/status` 端点，或者应用在挂载 webview 之前崩溃。使用 `app/scripts/e2e-run-session.sh`，它会启动 `tauri-driver` 并在调用 WDIO 之前等待该端点，而不是手动驱动应用。

请确保 `DISPLAY` 已设置且 Xvfb 正在运行：

```bash
export DISPLAY=:99
Xvfb :99 -screen 0 1280x1024x24 &
```

还要确保 dbus 正在运行（webkit2gtk 需要它）：

```bash
eval $(dbus-launch --sh-syntax)
```

### Linux： `tauri-driver` 或 `WebKitWebDriver` 未找到

安装 `tauri-driver` 使用 `cargo install tauri-driver`，并确保 `WebKitWebDriver` 位于 `PATH` （在 Debian 和 Ubuntu 上，它随 `webkit2gtk-driver`）或者将 `WEBKIT_WEBDRIVER` 指向其位置。 `e2e-run-session.sh` 默认为 `/usr/bin/WebKitWebDriver`.

### macOS：深度链接在 `tauri dev`

深度链接需要一个 `.app` bundle。请使用 `pnpm tauri build --debug --bundles app` 代替。

### Docker：首次运行时构建较慢

第一次 Docker 构建会编译 Rust 并安装 E2E 测试框架依赖。后续运行会使用缓存层。Docker 卷会缓存 Cargo 注册表和 git 源。

## 规范：通知

混合通知用例有不同的负责人。Playwright 验证可见通知的标题/正文以及 System Events/空 Alerts UI。WDIO 只检查本机权限状态值并调用操作系统通知插件。请通过 lane 调度器运行浏览器负责人，或使用以下命令运行本机负责人：

```bash
bash app/scripts/e2e-run-spec.sh test/e2e/specs/notifications.spec.ts notifications
```

本机测试套件使用一个实时 `tauri-driver` 会话；它不负责普通的通知列表 RPC 或页面渲染断言。之前的连接横幅 E2E 已退役，因为在基线之前，源代码历史已经移除了其唯一的 UI 消费者；有针对性的选择器和监听器单元测试仍然覆盖设备/后端连接性的区别。不声明任何浏览器横幅覆盖。

***

## Agent 可观测的工件流

### 开始一项 composer 测试

聊天草稿会按线程保留。在某个规范输入其初始提示之前，请调用 `replaceChatComposerText(input, prompt)` 来自 `app/test/playwright/helpers/chat-composer.ts`。它会选中并删除恢复的草稿，等待其变为空值，通过键盘事件输入，并检查确切的提示词。请在以下情况下使用 `clearChatComposer(input)` 当某个用例需要空的 composer 时。这些辅助函数同时支持 textarea 和 Lexical 界面。

演练草稿恢复或追加后续内容的用例应保留其草稿，并显式驱动该行为。该辅助函数的浏览器回归测试位于 `app/test/playwright/specs/chat-composer-helper.spec.ts`.

若要进行一次规范、可检查的运行，并将截图、页面源代码转储以及 mock 请求日志保存到磁盘：

```bash
bash app/scripts/e2e-agent-review.sh
```

工件默认保存在 `app/test/e2e/artifacts/<timestamp>-agent-review/` ；如需覆盖路径，请设置 `E2E_ARTIFACT_DIR` 以覆盖该路径。脚本会将该目录作为输出路径传递给 Playwright，测试结果和附件会保存在那里。审查用例会通过以下方式附加检查点截图、页面 HTML，以及经过脱敏的 mock 请求日志： `testInfo.attach`。 [`agent-observability.md`](/openhuman/zh/kai-fa/agent-observability.md).

***

## Rust 推理提供方 E2E

这些测试（`tests/inference_provider_e2e.rs`）使用 wiremock 来模拟 HTTP 上游，并且不需要真实的 LLM API 调用。它们覆盖 OpenAI 兼容聊天、Anthropic 认证方式、按模型禁用 temperature、Ollama 本地提供方，以及 `/v1` HTTP 端点认证层。

```bash
# 本地：
bash scripts/test-rust-inference-e2e.sh

# 通过 Docker（Linux，与 CI 使用相同的镜像）：
docker compose -f e2e/docker-compose.yml run --rm inference-e2e
```

***

## 另请参阅

* [测试策略](/openhuman/zh/kai-fa/testing-strategy.md)：某个测试属于哪一层。
* [Agent 可观测性](/openhuman/zh/kai-fa/agent-observability.md)：审查运行写入的工件流。
* [Tauri shell](/openhuman/zh/kai-fa/architecture/tauri-shell.md)：Wry webview `tauri-driver` 所附加到的。
* [开始设置](/openhuman/zh/kai-fa/getting-set-up.md)：本地 E2E 构建所需的工具链和子模块。
