patrol

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Patrol 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:
patrol
Always use
--device
to avoid the interactive device selection prompt.
CLI位置:
patrol
请始终使用
--device
参数,以避免出现交互式设备选择提示。

macOS (default)

macOS(默认)

bash
undefined
bash
undefined

Run a specific test file

运行指定测试文件

patrol test
--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

Run all integration tests

运行所有集成测试

patrol test
--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

**首次设置:** 请查看[macOS设置指南](./setup/macos-setup.md)了解权限和窗口约束相关内容。

iOS (simulator)

iOS(模拟器)

Use a booted simulator's device ID or name. The
--ios
flag specifies the OS version (defaults to
latest
— must match the simulator's OS).
bash
undefined
使用已启动模拟器的设备ID或名称。
--ios
参数用于指定操作系统版本(默认值为
latest
——必须与模拟器的系统版本匹配)。
bash
undefined

Run on a specific iOS simulator

在指定iOS模拟器上运行测试

patrol test
--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

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

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

使用`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 devices
.
bash
undefined
需要已启动的模拟器或已连接的真机。使用
adb devices
命令返回的名称/序列号。
bash
undefined

Boot 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

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

**注意:** 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
undefined

Run a specific test in Chrome

在Chrome中运行指定测试

patrol test
--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

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
patrol test
--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

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

**首次设置:** 请查看[网页设置指南](./setup/web-setup.md)了解Node.js、CORS和视口的相关细节。

如果`$ARGUMENTS`为空或为"all",则运行`integration_test/`目录下的所有测试。
如果`$ARGUMENTS`是类似`smoke_test.dart`的文件名,则运行该指定文件。

Pre-flight Checks

预检查

Before running tests, verify:
  1. Backend is running in
    --no-auth-mode
    at the URL above
  2. patrol CLI is installed:
    patrol --version
  3. Code compiles: Run
    dart analyze integration_test/
    first
  4. test_bundle.dart is current: Patrol auto-generates this — if tests are missing from the bundle, delete it and let
    patrol test
    regenerate
运行测试前,请验证以下内容:
  1. 后端服务正在运行:以
    --no-auth-mode
    模式在上述URL地址运行
  2. 已安装patrol CLI:执行
    patrol --version
    验证
  3. 代码可编译:先运行
    dart analyze integration_test/
    检查
  4. test_bundle.dart为最新版本:Patrol会自动生成该文件——如果测试未包含在bundle中,请删除它并让
    patrol test
    重新生成

Test Patterns

测试模式

Never use pumpAndSettle

禁止使用pumpAndSettle

SSE streams prevent settling. Use these instead:
  1. waitForCondition
    — polling-based, for UI states
  2. harness.waitForLog
    — stream-based, for async events (preferred)
  3. tester.pump(duration)
    — fixed delays, only for brief rendering pauses
SSE流会阻止界面稳定。请改用以下方式:
  1. waitForCondition
    —— 基于轮询,适用于UI状态检查
  2. harness.waitForLog
    —— 基于流,适用于异步事件(推荐使用)
  3. 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查找器参考

TargetFinder
Room list items
find.byType(RoomListTile)
Chat input
find.byType(TextField)
Send button
find.byTooltip('Send message')
Chat messages
find.byType(ChatMessageWidget)
Settings icon
find.byIcon(Icons.settings)
Avoid
find.bySemanticsLabel
for text entry — it can resolve to the Semantics wrapper instead of the TextField, causing
enterText
to fail.
目标查找器
房间列表项
find.byType(RoomListTile)
聊天输入框
find.byType(TextField)
发送按钮
find.byTooltip('Send message')
聊天消息
find.byType(ChatMessageWidget)
设置图标
find.byIcon(Icons.settings)
避免 使用
find.bySemanticsLabel
查找文本输入框——它可能匹配到Semantics包装器而非TextField,导致
enterText
操作失败。

Log patterns for assertions

断言用日志模式

LoggerPatternMeaning
Router
redirect called
App booted, router active
HTTP
/api/v1/rooms
Rooms API called
Room
Rooms loaded:
Rooms parsed successfully
ActiveRun
RUN_STARTED
AG-UI SSE stream opened
ActiveRun
TEXT_START:
First text chunk received
ActiveRun
RUN_FINISHED
SSE stream completed
日志器模式含义
Router
redirect called
应用已启动,路由激活
HTTP
/api/v1/rooms
调用了房间列表API
Room
Rooms loaded:
房间列表解析成功
ActiveRun
RUN_STARTED
AG-UI SSE流已开启
ActiveRun
TEXT_START:
收到首个文本块
ActiveRun
RUN_FINISHED
SSE流已完成

Debugging: Two-Phase Workflow

调试:两阶段工作流

Phase 1: Discover (dart MCP —
flutter run
)

阶段1:探索(dart MCP ——
flutter run

Use
mcp__dart-tools__launch_app
to start the app, then inspect the live widget tree and logs to find the right finders and log patterns for your test.
text
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 exceptions
Stdout logs appear as
[DEBUG] Router: redirect called for /
— use these to identify the logger name and message pattern for
harness.expectLog()
.
The widget tree shows actual
widgetRuntimeType
values — use these for
find.byType()
finders.
使用
mcp__dart-tools__launch_app
启动应用,然后检查实时Widget树和日志,为测试找到合适的查找器和日志模式。
text
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树会显示实际的
widgetRuntimeType
值——可将其用于
find.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
TestLogHarness
which captures logs in-process via
MemorySink
:
  • harness.expectLog('Router', 'redirect called')
    — synchronous check
  • harness.waitForLog('ActiveRun', 'RUN_FINISHED')
    — stream-based wait
  • harness.dumpLogs(last: 50)
    — dump to console on failure
The harness sees the same
[DEBUG]
logs that appear in
get_app_logs
, but accessed in-process rather than via stdout.
Patrol通过xcodebuild启动应用,不会暴露DTD URI。在patrol测试运行期间,无法连接dart MCP工具。
取而代之的是,所有测试断言都使用**
TestLogHarness
**,它通过
MemorySink
在进程内捕获日志:
  • 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
flutter
/
dart
must
unset GIT_DIR
first, otherwise Flutter reports version
0.0.0-unknown
. Wrapper scripts in
scripts/
handle this.
本仓库是一个git工作树。调用
flutter
/
dart
的预提交钩子必须先
unset GIT_DIR
,否则Flutter会报告版本为
0.0.0-unknown
scripts/
目录下的包装脚本已处理此问题。

Troubleshooting

故障排查

SymptomCauseFix
"Multiple devices found" promptMissing
--device
flag
Add
--device macos
command not found: patrol
~/.pub-cache/bin
not on PATH
Add to PATH or use full path
Test hangs on "Waiting for app"Entitlements missing/wrongSee macOS setup
0.0.0-unknown
version
GIT_DIR set in worktreeUse
scripts/flutter-analyze.sh
wrapper
"did not appear within 10s"Widget in drawer, not bodySee macOS setup
Bad state: No element
on enterText
Finder matched Semantics, not TextFieldUse
find.byType(TextField)
instead
Accessibility permission deniedEntitlements changedRe-approve in System Settings > Privacy > Accessibility
iOS: xcodebuild exit code 70
OS=latest
doesn't match simulator
See iOS setup
iOS: "Device ... is not attached"Wrong device ID formatUse UUID from
xcrun simctl list devices booted
Android: "No connected devices"Emulator not running or
adb
not on PATH
See Android setup
Android: connection refused to localhostEmulator can't reach host localhostUse
10.0.2.2
instead of
localhost
Android: Gmail login prompt on bootAVD uses Google Play imageSee Android setup
Chrome: "Cannot find module playwright"Node.js not installedSee Web setup
Chrome: CORS error in testBackend missing CORS headersSee Web setup
Chrome: "Failed to fetch" on
verifyBackendOrFail
Backend offline or CORS blockStart backend; check browser console
症状原因解决方法
出现"Multiple devices found"提示缺少
--device
参数
添加
--device macos
参数
command not found: patrol
~/.pub-cache/bin
未加入PATH
将其添加到PATH或使用完整路径
测试在"Waiting for app"步骤挂起权限缺失或错误查看macOS设置指南
版本显示
0.0.0-unknown
工作树中设置了GIT_DIR使用
scripts/flutter-analyze.sh
包装脚本
提示"did not appear within 10s"Widget在抽屉中而非主内容区查看macOS设置指南
enterText
操作出现
Bad state: No element
错误
查找器匹配到Semantics而非TextField改用
find.byType(TextField)
权限被拒绝权限设置已更改在系统设置>隐私与安全性>辅助功能中重新授权
iOS:xcodebuild退出码70
OS=latest
与模拟器版本不匹配
查看iOS设置指南
iOS:提示"Device ... is not attached"设备ID格式错误使用
xcrun simctl list devices booted
返回的UUID
Android:提示"No connected devices"模拟器未运行或
adb
未加入PATH
查看Android设置指南
Android:连接localhost被拒绝模拟器无法访问主机的localhost使用
10.0.2.2
替代
localhost
Android:启动时出现Gmail登录提示AVD使用了Google Play镜像查看Android设置指南
Chrome:提示"Cannot find module playwright"未安装Node.js查看网页设置指南
Chrome:测试中出现CORS错误后端缺少CORS头查看网页设置指南
Chrome:
verifyBackendOrFail
出现"Failed to fetch"错误
后端未启动或被CORS阻止启动后端服务;检查浏览器控制台