# 端到端测试

## 概览

桌面端端到端测试使用 **WebDriverIO（WDIO）** 通过两个自动化后端来驱动 Tauri 应用：

| 平台                 | 驱动程序           | 端口   | 应用格式     | 选择器           |
| ------------------ | -------------- | ---- | -------- | ------------- |
| **Linux / CEF 状态** | `tauri-driver` | 4444 | 调试二进制文件  | CSS / DOM     |
| **macOS / Appium** | Appium Mac2    | 4723 | `.app` 包 | XPath / 无障碍功能 |

OpenHuman 的桌面应用当前使用 CEF 运行时（`tauri-runtime-cef`）。Linux `tauri-driver` 与 WebKitWebDriver / webkit2gtk 通信，且无法驱动由 CEF 支持的 WebView，因此在存在兼容 CEF 的驱动或替代测试框架之前，CI 中禁用了 Linux CEF E2E。当前受支持的路径是用于本地运行的 macOS/Appium，以及在该工作流启用时手动运行的 macOS/Appium 工作流。

***

## 快速开始

### Linux / CEF 状态

```bash
# 安装 tauri-driver（一次性）
cargo install tauri-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** 之下以提供虚拟显示。此路径目前仅对非 CEF / 与 WebKit 兼容的调试有用；默认的 CEF 应用无法被 WebKitWebDriver 自动化。

### macOS / Appium

```bash
# 安装 Appium + Mac2 驱动（一次性，需要 Node 24+）
npm install -g appium
appium driver install mac2

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

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

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

使用 Docker 从 macOS 运行相同的基于 Linux 的测试框架。同样适用 CEF 限制：在存在兼容 CEF 的驱动之前，这不是默认 CEF 运行时的受支持路径。

```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` 在 Linux 上（tauri-driver 会话）
* `isMac2()`, `true` 在 macOS 上（Appium Mac2 会话）
* `supportsExecuteScript()`, `true` 当 `browser.execute()` 可用时（仅 tauri-driver）

### 元素辅助函数

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

| 辅助函数                      | Mac2（macOS）                                     | tauri-driver（Linux）                          |
| ------------------------- | ----------------------------------------------- | -------------------------------------------- |
| `waitForText(text)`       | 基于 @label/@value/@title 的 XPath                 | 基于 DOM 文本内容的 XPath                           |
| `waitForButton(text)`     | XCUIElementTypeButton XPath                     | `button` / `[role="button"]` XPath           |
| `clickText(text)`         | W3C 指针操作                                        | 标准 `el.click()`                              |
| `clickNativeButton(text)` | 在 XCUIElementTypeButton 上的 W3C 指针操作             | 标准 `el.click()` 在 button 上                   |
| `clickToggle()`           | XCUIElementTypeSwitch / XCUIElementTypeCheckBox | `[role="switch"]` / `input[type="checkbox"]` |
| `waitForWindowVisible()`  | XCUIElementTypeWindow                           | 窗口句柄检查                                       |
| `waitForWebView()`        | XCUIElementTypeWebView                          | `document.readyState` 检查                     |
| `hasAppChrome()`          | XCUIElementTypeMenuBar                          | 窗口句柄检查                                       |
| `dumpAccessibilityTree()` | 无障碍 XML                                         | HTML 页面源代码                                   |

### 稳定的测试 ID

优先使用稳定的 `data-testid` 挂钩，用于 E2E 规格点击或轮询的 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` 处理认证深链接：

* **tauri-driver**: `browser.execute(window.__simulateDeepLink(url))` （主要方式）， `xdg-open` （回退方式）
* **Appium Mac2**: `macos: deepLink` 扩展命令（主要方式）， `open -a ...` （回退方式）

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

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

这可以捕获这样一类回归问题：次级进程在安装 Tauri 深链接转发路径之前，于 CEF 缓存预检期间退出。

### 编写跨平台 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 根节点后再返回。完成引导后， `walkOnboarding()` 还会等待 `#/home` 以及一个 Home 页面标记，然后 spec 才导航到其他位置。

***

## 环境变量

| 变量                          | 默认值     | 说明                                 |
| --------------------------- | ------- | ---------------------------------- |
| `TAURI_DRIVER_PORT`         | `4444`  | tauri-driver WebDriver 端口          |
| `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 检查

默认的 `test.yml` 工作流运行前端单元测试和 Rust 检查。其 Linux `tauri-driver` E2E 作业被注释掉了，因为 WebKitWebDriver 无法驱动由 CEF 支持的 WebView。

已禁用的 Linux E2E 作业过去会：

1. 安装系统依赖（webkit2gtk、Xvfb、dbus）
2. 安装 `tauri-driver` 通过 cargo
3. 构建嵌入了模拟服务器 URL 的应用
4. 在 Xvfb 下运行所有 E2E 流程

### macOS / Appium

macOS/Appium 是当前 CEF 桌面应用受支持的自动化后端。可在本地运行，或在该工作流启用时通过手动触发的 macOS 工作流运行：

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

***

## 故障排除

### Linux：“WebView not ready” 超时

对于默认的 CEF 运行时，这通常意味着不受支持的 Linux `tauri-driver` 路径正尝试通过 WebKitWebDriver 驱动由 CEF 支持的 WebView。请使用 macOS/Appium，或等待兼容 CEF 的 Linux 驱动。

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

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

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

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

### Linux：找不到 tauri-driver

```bash
cargo install tauri-driver
```

### macOS：深链接在以下环境中无法工作 `tauri dev`

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

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

首次 Docker 构建会从源码编译 Rust + tauri-driver。后续运行会使用缓存层。Cargo registry 和 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 页面显示 System Events 部分（`[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`）同时为 Linux/tauri-driver 和 macOS/Appium Mac2 编写，但对于默认 CEF 运行时，在存在兼容 CEF 的驱动之前，Linux 执行被禁用。UI 断言（Notifications 页面各部分）需要 `browser.execute()` 支持，因此在 Mac2 上当 `supportsExecuteScript()` 返回时会自动跳过 `false`.

***

## 代理可观察的工件流程

对于一次规范、可检查的运行，它会将截图、页面源代码转储和模拟请求日志写入磁盘：

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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://tinyhumans.gitbook.io/openhuman/zh/kai-fa/e2e-testing.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
