> 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-zhong/e2e-testing.md).

# 端到端测试

## 概览

桌面端 E2E 测试使用 **WebDriverIO（WDIO）** 通过 Appium 驱动 Tauri 应用：

| 平台                          | 驱动              | 端口   | 应用格式     | 选择器       |
| --------------------------- | --------------- | ---- | -------- | --------- |
| **Linux / Appium Chromium** | Appium Chromium | 4723 | 调试二进制    | CSS / DOM |
| **macOS / Appium Chromium** | Appium Chromium | 4723 | `.app` 包 | CSS / DOM |

OpenHuman 的桌面应用目前使用 CEF 运行时（`tauri-runtime-cef`）。CI 使用 Appium 的 Chromium 驱动来驱动 Linux 调试二进制；手动的 macOS 和 Windows E2E 使用相同的 Chromium 驱动后端。

***

## 快速开始

### Linux / Appium Chromium

```bash
# 安装 Appium 和 Chromium 驱动（一次性）
npm install -g appium@3
appium driver install --source=npm appium-chromium-driver

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

# 运行所有流程
pnpm --filter openhuman-app test:e2e:all:flows

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

在无头 Linux 上，测试框架会在 **Xvfb** 下运行，以提供虚拟显示器。

### macOS / Appium Chromium

```bash
# 安装 Appium + Chromium 驱动（一次性，需要 Node 24+）
npm install -g appium@3
appium driver install --source=npm appium-chromium-driver

# 构建 .app bundle
pnpm --filter openhuman-app test:e2e:build

# 运行所有流程
pnpm --filter openhuman-app test:e2e:all:flows
```

### macOS 上的 Docker（本地 Linux 测试框架）

通过 Docker 在 macOS 上运行相同的基于 Linux 的测试框架。

```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。仓库会通过绑定挂载，因此构建结果会在多次运行之间保留。

***

## 架构

### 平台检测

`app/test/e2e/helpers/platform.ts` 导出：

* `isTauriDriver()`，旧的兼容封装，现在始终返回 `true` ，用于支持 DOM 的 Chromium 会话
* `isMac2()`，旧的兼容封装，现在始终返回 `false`
* `supportsExecuteScript()`, `true` 因为 Chromium 驱动支持 `browser.execute()` 在所有平台上

### 元素辅助函数

`app/test/e2e/helpers/element-helpers.ts` 提供统一 API：

| 辅助函数                      | Appium Chromium                              |
| ------------------------- | -------------------------------------------- |
| `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` 处理认证深度链接：

* **Appium Chromium**: `browser.execute(window.__simulateDeepLink(url))` 在所有平台上
* **macOS 备用方案**: `macos: deepLink` 扩展命令，然后 `open -a ...`

对于发布候选版本，在触及 CEF 预检、单实例或深度链接启动代码时，还要在 Linux 或 macOS 上手动运行一次二次实例冒烟测试：

1. 正常启动 OpenHuman 并保持其运行。
2. 触发 `openhuman://auth?token=e2e-token&key=auth` ，通过操作系统打开器。
3. 确认已经运行的窗口收到了回调，并且没有启动第二个完整的 CEF 实例。
4. 确认辅助进程能干净退出，没有 CEF 缓存锁错误。

这可以捕获一类回归：辅助进程在 CEF 缓存预检期间退出，而此时 Tauri 的深度链接转发路径尚未安装。

### 编写跨平台 spec

1. **使用辅助函数** 来自 `element-helpers.ts`，绝不要使用原始的 `XCUIElementType*` 选择器在 spec 中
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` 以及一个 Home 页面标记，然后 spec 才会跳转到别处。

***

## 环境变量

| 变量                          | 默认值     | 描述                               |
| --------------------------- | ------- | -------------------------------- |
| `APPIUM_PORT`               | `4723`  | Appium 服务端口                      |
| `E2E_MOCK_PORT`             | `18473` | 模拟后端服务端口                         |
| `OPENHUMAN_WORKSPACE`       | （临时目录）  | 应用工作区目录                          |
| `OPENHUMAN_SERVICE_MOCK`    | `0`     | 启用服务模拟模式                         |
| `OPENHUMAN_E2E_MODE`        | 未设置     | 启用破坏性的测试支持 RPC；E2E 运行器会将其设置为 `1` |
| `OPENHUMAN_E2E_AUTH_BYPASS` | 未设置     | 启用 JWT 绕过认证                      |
| `DEBUG_E2E_DEEPLINK`        | （详细）    | 设置为 `0` 以关闭深度链接日志                |
| `E2E_FORCE_CARGO_CLEAN`     | 未设置     | 在 E2E 构建前强制执行 cargo clean        |

***

## CI 工作流

### Push / PR 检查

默认的拉取请求门禁是 `.github/workflows/ci-lite.yml` （快速通道：质量检查 + 仅针对变更文件范围的单元测试）。E2E 套件不会在发往 `main` 的 PR 上运行——完整的 E2E 矩阵（Rust mock 后端、Playwright Web、Linux/macOS/Windows 桌面端）会在 `.github/workflows/ci-full.yml` 运行于目标为 `release` 分支的 PR 上，以及每次推送到该分支时。

macOS 和 Windows 桌面端 E2E 不会在每个 PR 上运行。需要跨平台桌面信号时，请使用手动触发的 E2E 工作流（`.github/workflows/e2e.yml`）在晋级前运行。

### macOS / Appium Chromium

macOS/Appium Chromium 可用于本地运行，也可通过手动触发的 E2E 工作流使用：

1. 安装 Appium + Chromium 驱动
2. 构建 `.app` 包
3. 运行所有 E2E 流程

***

## 故障排查

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

对于默认的 CEF 运行时，这通常意味着某个过时的本地运行器正试图通过 WebKitWebDriver 驱动一个基于 CEF 的 WebView。当前 CI 在 Linux 上使用 Appium Chromium 驱动；请使用 `app/scripts/e2e-run-session.sh` 或 PR CI 工作流来走受支持的 Linux 路径。

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

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

还要确保 dbus 已启动（webkit2gtk 需要它）：

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

### Linux：未找到 Appium Chromium 驱动

```bash
npm install -g appium@3
appium driver install --source=npm appium-chromium-driver
```

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

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

### Docker：首次运行构建很慢

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

## Spec：通知

**文件**: `app/test/e2e/specs/notifications.spec.ts`

通过实时 core sidecar 和 Notifications UI 页面测试通知 RPC 方法：

* `notification_ingest`，通过 core RPC 创建一条新通知
* `notification_list`，验证返回了已摄取的通知
* `notification_mark_read`，将通知标记为已读
* `notification_stats`，检查聚合统计的形状
* UI：Notifications 页面渲染集成通知部分（`[data-testid="integration-notifications-section"]`)
* UI：Notifications 页面显示系统事件部分（`[data-testid="system-events-section"]`)

**运行**:

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

**平台说明**：RPC 测试（`notification_ingest`, `notification_list`, `notification_mark_read`, `notification_stats`）通过统一的 Appium Chromium 后端运行。UI 断言需要 `browser.execute()` 支持，而当前后端在所有平台上都提供该支持。

***

## 面向 Agent 可观测的产物流程

要进行一次可作为基准、可检查的运行，并将截图、页面源代码转储和模拟请求日志写入磁盘：

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

产物会落在 `app/test/e2e/artifacts/<timestamp>-agent-review/`。完整详情 + 辅助 API： [`AGENT-OBSERVABILITY.md`](https://github.com/tinyhumansai/openhuman/blob/main/gitbooks/developing/AGENT-OBSERVABILITY.md)。任何失败的测试都会触发 `wdio.conf.ts`的 `afterTest` 钩子，它会写入 `failure-*.png` + `failure-*.source.xml` 到同一个运行目录中。

***

## 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
```
