patrol
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePatrol E2E Test Skill
Patrol E2E测试技能
Run and manage Patrol integration tests for this Flutter project (macOS, iOS, Android, and Chrome).
为Flutter项目运行和管理Patrol集成测试(支持macOS、iOS、Android和Chrome平台)。
Running Tests
运行测试
CLI location:
patrolAlways use to avoid the interactive device selection prompt.
--deviceCLI位置:
patrol请始终使用参数,以避免出现交互式设备选择提示。
--devicemacOS (default)
macOS(默认)
bash
undefinedbash
undefinedRun a specific test file
运行指定测试文件
patrol test
--device macos
--target integration_test/$ARGUMENTS
--dart-define SOLIPLEX_BACKEND_URL=http://localhost:8000
--device macos
--target integration_test/$ARGUMENTS
--dart-define SOLIPLEX_BACKEND_URL=http://localhost:8000
patrol test
--device macos
--target integration_test/$ARGUMENTS
--dart-define SOLIPLEX_BACKEND_URL=http://localhost:8000
--device macos
--target integration_test/$ARGUMENTS
--dart-define SOLIPLEX_BACKEND_URL=http://localhost:8000
Run all integration tests
运行所有集成测试
patrol test
--device macos
--target integration_test/
--dart-define SOLIPLEX_BACKEND_URL=http://localhost:8000
--device macos
--target integration_test/
--dart-define SOLIPLEX_BACKEND_URL=http://localhost:8000
**First-time setup:** See [macOS Setup Guide](./setup/macos-setup.md) for entitlements and window constraints.patrol test
--device macos
--target integration_test/
--dart-define SOLIPLEX_BACKEND_URL=http://localhost:8000
--device macos
--target integration_test/
--dart-define SOLIPLEX_BACKEND_URL=http://localhost:8000
**首次设置:** 请查看[macOS设置指南](./setup/macos-setup.md)了解权限和窗口约束相关内容。iOS (simulator)
iOS(模拟器)
Use a booted simulator's device ID or name. The flag specifies the OS
version (defaults to — must match the simulator's OS).
--ioslatestbash
undefined使用已启动模拟器的设备ID或名称。参数用于指定操作系统版本(默认值为——必须与模拟器的系统版本匹配)。
--ioslatestbash
undefinedRun on a specific iOS simulator
在指定iOS模拟器上运行测试
patrol test
--device <DEVICE_ID>
--target integration_test/$ARGUMENTS
--dart-define SOLIPLEX_BACKEND_URL=http://localhost:8000
--device <DEVICE_ID>
--target integration_test/$ARGUMENTS
--dart-define SOLIPLEX_BACKEND_URL=http://localhost:8000
patrol test
--device <DEVICE_ID>
--target integration_test/$ARGUMENTS
--dart-define SOLIPLEX_BACKEND_URL=http://localhost:8000
--device <DEVICE_ID>
--target integration_test/$ARGUMENTS
--dart-define SOLIPLEX_BACKEND_URL=http://localhost:8000
If simulator OS != latest SDK, specify explicitly
如果模拟器系统版本与最新SDK不匹配,请显式指定
patrol test
--device <DEVICE_ID>
--ios=18.6
--target integration_test/$ARGUMENTS
--dart-define SOLIPLEX_BACKEND_URL=http://localhost:8000
--device <DEVICE_ID>
--ios=18.6
--target integration_test/$ARGUMENTS
--dart-define SOLIPLEX_BACKEND_URL=http://localhost:8000
Use `xcrun simctl list devices booted` to find the device ID and OS version.
**First-time setup:** See [iOS Setup Guide](./setup/ios-setup.md) for RunnerUITests target and simulator OS matching.patrol test
--device <DEVICE_ID>
--ios=18.6
--target integration_test/$ARGUMENTS
--dart-define SOLIPLEX_BACKEND_URL=http://localhost:8000
--device <DEVICE_ID>
--ios=18.6
--target integration_test/$ARGUMENTS
--dart-define SOLIPLEX_BACKEND_URL=http://localhost:8000
使用`xcrun simctl list devices booted`命令查找设备ID和系统版本。
**首次设置:** 请查看[iOS设置指南](./setup/ios-setup.md)了解RunnerUITests目标和模拟器系统版本匹配的相关要求。Android (emulator or device)
Android(模拟器或真机)
Requires a booted emulator or connected device. Use the name/serial from .
adb devicesbash
undefined需要已启动的模拟器或已连接的真机。使用命令返回的名称/序列号。
adb devicesbash
undefinedBoot emulator (if not running)
启动模拟器(如果未运行)
export ANDROID_HOME=~/Library/Android/sdk
export PATH="$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator:$PATH"
emulator -avd Patrol_Test_API_36 &
export ANDROID_HOME=~/Library/Android/sdk
export PATH="$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator:$PATH"
emulator -avd Patrol_Test_API_36 &
Run on Android emulator
在Android模拟器上运行测试
patrol test
--device emulator-5554
--target integration_test/$ARGUMENTS
--dart-define SOLIPLEX_BACKEND_URL=http://10.0.2.2:8000
--device emulator-5554
--target integration_test/$ARGUMENTS
--dart-define SOLIPLEX_BACKEND_URL=http://10.0.2.2:8000
**Note:** Android emulators use `10.0.2.2` to reach the host's `localhost`.
**First-time setup:** See [Android Setup Guide](./setup/android-setup.md) for AVD creation, JDK, and Google APIs image requirement.patrol test
--device emulator-5554
--target integration_test/$ARGUMENTS
--dart-define SOLIPLEX_BACKEND_URL=http://10.0.2.2:8000
--device emulator-5554
--target integration_test/$ARGUMENTS
--dart-define SOLIPLEX_BACKEND_URL=http://10.0.2.2:8000
**注意:** Android模拟器需使用`10.0.2.2`来访问主机的`localhost`。
**首次设置:** 请查看[Android设置指南](./setup/android-setup.md)了解AVD创建、JDK和Google APIs镜像的要求。Chrome (web)
Chrome(网页端)
Requires Node.js >= 18 (Playwright auto-installs on first run).
bash
undefined需要Node.js >= 18版本(Playwright会在首次运行时自动安装)。
bash
undefinedRun a specific test in Chrome
在Chrome中运行指定测试
patrol test
--device chrome
--target integration_test/$ARGUMENTS
--dart-define SOLIPLEX_BACKEND_URL=http://localhost:8000
--device chrome
--target integration_test/$ARGUMENTS
--dart-define SOLIPLEX_BACKEND_URL=http://localhost:8000
patrol test
--device chrome
--target integration_test/$ARGUMENTS
--dart-define SOLIPLEX_BACKEND_URL=http://localhost:8000
--device chrome
--target integration_test/$ARGUMENTS
--dart-define SOLIPLEX_BACKEND_URL=http://localhost:8000
Run headless (for CI or no-GUI environments)
无头模式运行(适用于CI或无GUI环境)
patrol test
--device chrome
--web-headless true
--target integration_test/$ARGUMENTS
--dart-define SOLIPLEX_BACKEND_URL=http://localhost:8000
--device chrome
--web-headless true
--target integration_test/$ARGUMENTS
--dart-define SOLIPLEX_BACKEND_URL=http://localhost:8000
patrol test
--device chrome
--web-headless true
--target integration_test/$ARGUMENTS
--dart-define SOLIPLEX_BACKEND_URL=http://localhost:8000
--device chrome
--web-headless true
--target integration_test/$ARGUMENTS
--dart-define SOLIPLEX_BACKEND_URL=http://localhost:8000
Custom viewport (default is browser-dependent)
自定义视口(默认值取决于浏览器)
patrol test
--device chrome
--web-viewport "1280x720"
--target integration_test/$ARGUMENTS
--dart-define SOLIPLEX_BACKEND_URL=http://localhost:8000
--device chrome
--web-viewport "1280x720"
--target integration_test/$ARGUMENTS
--dart-define SOLIPLEX_BACKEND_URL=http://localhost:8000
**First-time setup:** See [Web Setup Guide](./setup/web-setup.md) for Node.js, CORS, and viewport details.
If `$ARGUMENTS` is empty or "all", run against `integration_test/` (all tests).
If `$ARGUMENTS` is a filename like `smoke_test.dart`, run that specific file.patrol test
--device chrome
--web-viewport "1280x720"
--target integration_test/$ARGUMENTS
--dart-define SOLIPLEX_BACKEND_URL=http://localhost:8000
--device chrome
--web-viewport "1280x720"
--target integration_test/$ARGUMENTS
--dart-define SOLIPLEX_BACKEND_URL=http://localhost:8000
**首次设置:** 请查看[网页设置指南](./setup/web-setup.md)了解Node.js、CORS和视口的相关细节。
如果`$ARGUMENTS`为空或为"all",则运行`integration_test/`目录下的所有测试。
如果`$ARGUMENTS`是类似`smoke_test.dart`的文件名,则运行该指定文件。Pre-flight Checks
预检查
Before running tests, verify:
- Backend is running in at the URL above
--no-auth-mode - patrol CLI is installed:
patrol --version - Code compiles: Run first
dart analyze integration_test/ - test_bundle.dart is current: Patrol auto-generates this — if tests are missing from the bundle, delete it and let regenerate
patrol test
运行测试前,请验证以下内容:
- 后端服务正在运行:以模式在上述URL地址运行
--no-auth-mode - 已安装patrol CLI:执行验证
patrol --version - 代码可编译:先运行检查
dart analyze integration_test/ - test_bundle.dart为最新版本:Patrol会自动生成该文件——如果测试未包含在bundle中,请删除它并让重新生成
patrol test
Test Patterns
测试模式
Never use pumpAndSettle
禁止使用pumpAndSettle
SSE streams prevent settling. Use these instead:
- — polling-based, for UI states
waitForCondition - — stream-based, for async events (preferred)
harness.waitForLog - — fixed delays, only for brief rendering pauses
tester.pump(duration)
SSE流会阻止界面稳定。请改用以下方式:
- —— 基于轮询,适用于UI状态检查
waitForCondition - —— 基于流,适用于异步事件(推荐使用)
harness.waitForLog - —— 固定延迟,仅用于短暂的渲染停顿
tester.pump(duration)
Standard test structure
标准测试结构
dart
patrolTest('description', ($) async {
await verifyBackendOrFail(backendUrl);
ignoreKeyboardAssertions();
final harness = TestLogHarness();
await harness.initialize();
try {
await pumpTestApp($, harness);
// ... test body ...
} catch (e) {
harness.dumpLogs(last: 50);
rethrow;
} finally {
harness.dispose();
}
});dart
patrolTest('description', ($) async {
await verifyBackendOrFail(backendUrl);
ignoreKeyboardAssertions();
final harness = TestLogHarness();
await harness.initialize();
try {
await pumpTestApp($, harness);
// ... 测试主体代码 ...
} catch (e) {
harness.dumpLogs(last: 50);
rethrow;
} finally {
harness.dispose();
}
});Widget finders reference
Widget查找器参考
| Target | Finder |
|---|---|
| Room list items | |
| Chat input | |
| Send button | |
| Chat messages | |
| Settings icon | |
Avoid for text entry — it can resolve to the Semantics wrapper instead of the TextField, causing to fail.
find.bySemanticsLabelenterText| 目标 | 查找器 |
|---|---|
| 房间列表项 | |
| 聊天输入框 | |
| 发送按钮 | |
| 聊天消息 | |
| 设置图标 | |
避免 使用查找文本输入框——它可能匹配到Semantics包装器而非TextField,导致操作失败。
find.bySemanticsLabelenterTextLog patterns for assertions
断言用日志模式
| Logger | Pattern | Meaning |
|---|---|---|
| | App booted, router active |
| | Rooms API called |
| | Rooms parsed successfully |
| | AG-UI SSE stream opened |
| | First text chunk received |
| | SSE stream completed |
| 日志器 | 模式 | 含义 |
|---|---|---|
| | 应用已启动,路由激活 |
| | 调用了房间列表API |
| | 房间列表解析成功 |
| | AG-UI SSE流已开启 |
| | 收到首个文本块 |
| | SSE流已完成 |
Debugging: Two-Phase Workflow
调试:两阶段工作流
Phase 1: Discover (dart MCP — flutter run
)
flutter run阶段1:探索(dart MCP —— flutter run
)
flutter runUse to start the app, then inspect the live
widget tree and logs to find the right finders and log patterns for your test.
mcp__dart-tools__launch_apptext
mcp__dart-tools__launch_app → starts app, returns DTD URI
mcp__dart-tools__connect_dart_tooling_daemon → connect to running app
mcp__dart-tools__get_widget_tree(summaryOnly: true) → see real widget hierarchy
mcp__dart-tools__get_app_logs(pid, maxLines: 50) → see stdout log output
mcp__dart-tools__get_runtime_errors → check for exceptionsStdout logs appear as — use these
to identify the logger name and message pattern for .
[DEBUG] Router: redirect called for /harness.expectLog()The widget tree shows actual values — use these for
finders.
widgetRuntimeTypefind.byType()使用启动应用,然后检查实时Widget树和日志,为测试找到合适的查找器和日志模式。
mcp__dart-tools__launch_apptext
mcp__dart-tools__launch_app → 启动应用,返回DTD URI
mcp__dart-tools__connect_dart_tooling_daemon → 连接到运行中的应用
mcp__dart-tools__get_widget_tree(summaryOnly: true) → 查看实际Widget层级
mcp__dart-tools__get_app_logs(pid, maxLines: 50) → 查看标准输出日志
mcp__dart-tools__get_runtime_errors → 检查异常信息标准输出日志格式为——可借助这些日志识别所需的日志器名称和消息模式。
[DEBUG] Router: redirect called for /harness.expectLog()Widget树会显示实际的值——可将其用于查找器。
widgetRuntimeTypefind.byType()Phase 2: Assert (TestLogHarness — patrol run)
阶段2:断言(TestLogHarness —— patrol run)
Patrol launches the app through xcodebuild, which does not expose a
DTD URI. The dart MCP tools cannot connect during a patrol test run.
Instead, all test assertions use which captures logs
in-process via :
TestLogHarnessMemorySink- — synchronous check
harness.expectLog('Router', 'redirect called') - — stream-based wait
harness.waitForLog('ActiveRun', 'RUN_FINISHED') - — dump to console on failure
harness.dumpLogs(last: 50)
The harness sees the same logs that appear in ,
but accessed in-process rather than via stdout.
[DEBUG]get_app_logsPatrol通过xcodebuild启动应用,不会暴露DTD URI。在patrol测试运行期间,无法连接dart MCP工具。
取而代之的是,所有测试断言都使用****,它通过在进程内捕获日志:
TestLogHarnessMemorySink- —— 同步检查
harness.expectLog('Router', 'redirect called') - —— 基于流的等待
harness.waitForLog('ActiveRun', 'RUN_FINISHED') - —— 测试失败时将日志输出到控制台
harness.dumpLogs(last: 50)
该工具能看到与中相同的日志,但通过进程内访问而非标准输出获取。
get_app_logs[DEBUG]Git Worktree Caveat
Git工作树注意事项
This repo is a git worktree. Pre-commit hooks that invoke / must first, otherwise Flutter reports version . Wrapper scripts in handle this.
flutterdartunset GIT_DIR0.0.0-unknownscripts/本仓库是一个git工作树。调用/的预提交钩子必须先,否则Flutter会报告版本为。目录下的包装脚本已处理此问题。
flutterdartunset GIT_DIR0.0.0-unknownscripts/Troubleshooting
故障排查
| Symptom | Cause | Fix |
|---|---|---|
| "Multiple devices found" prompt | Missing | Add |
| | Add to PATH or use full path |
| Test hangs on "Waiting for app" | Entitlements missing/wrong | See macOS setup |
| GIT_DIR set in worktree | Use |
| "did not appear within 10s" | Widget in drawer, not body | See macOS setup |
| Finder matched Semantics, not TextField | Use |
| Accessibility permission denied | Entitlements changed | Re-approve in System Settings > Privacy > Accessibility |
| iOS: xcodebuild exit code 70 | | See iOS setup |
| iOS: "Device ... is not attached" | Wrong device ID format | Use UUID from |
| Android: "No connected devices" | Emulator not running or | See Android setup |
| Android: connection refused to localhost | Emulator can't reach host localhost | Use |
| Android: Gmail login prompt on boot | AVD uses Google Play image | See Android setup |
| Chrome: "Cannot find module playwright" | Node.js not installed | See Web setup |
| Chrome: CORS error in test | Backend missing CORS headers | See Web setup |
Chrome: "Failed to fetch" on | Backend offline or CORS block | Start backend; check browser console |
| 症状 | 原因 | 解决方法 |
|---|---|---|
| 出现"Multiple devices found"提示 | 缺少 | 添加 |
| | 将其添加到PATH或使用完整路径 |
| 测试在"Waiting for app"步骤挂起 | 权限缺失或错误 | 查看macOS设置指南 |
版本显示 | 工作树中设置了GIT_DIR | 使用 |
| 提示"did not appear within 10s" | Widget在抽屉中而非主内容区 | 查看macOS设置指南 |
| 查找器匹配到Semantics而非TextField | 改用 |
| 权限被拒绝 | 权限设置已更改 | 在系统设置>隐私与安全性>辅助功能中重新授权 |
| iOS:xcodebuild退出码70 | | 查看iOS设置指南 |
| iOS:提示"Device ... is not attached" | 设备ID格式错误 | 使用 |
| Android:提示"No connected devices" | 模拟器未运行或 | 查看Android设置指南 |
| Android:连接localhost被拒绝 | 模拟器无法访问主机的localhost | 使用 |
| Android:启动时出现Gmail登录提示 | AVD使用了Google Play镜像 | 查看Android设置指南 |
| Chrome:提示"Cannot find module playwright" | 未安装Node.js | 查看网页设置指南 |
| Chrome:测试中出现CORS错误 | 后端缺少CORS头 | 查看网页设置指南 |
Chrome: | 后端未启动或被CORS阻止 | 启动后端服务;检查浏览器控制台 |