Patrol 是一個**專為 Flutter 實現的跨平台 E2E 測試開源框架**,主要由 LeanCode 開發與維護。它最大的作用就是補足 Flutter 官方 integration_test 的短板,特別是在原生互動方面,以及對 AI 友善。

開發者可以用 Flutter 風格來寫 Patrol 的測試用例,測試過程除了支援操作 Flutter Widget 之外,最重要的是可以深入控制 iOS/Android 的原生元件。
簡單來說,Patrol 透過在 Flutter 測試程序和原生自動化服務之間建立雙向 gRPC 通訊,來實現 Flutter 的全面自動化測試。
除了 Flutter 控件之外,它在原生自動化上還可以實現:


這對於真實 Flutter E2E 測試來說是非常不錯的補強,另外它對 Flutter 也很友善,例如:
相較於另一個知名測試框架 Maestro,Patrol 更貼近 Flutter,而且 LeanCode 本身也是專業的 Flutter 顧問公司,專案生態已在許多真實產品中驗證過,最重要的是開源且免費。配合 AI 可以對專案進行全流程腳本自動化測試,這就是我認為 Patrol 是目前最好的 Flutter 測試框架的原因。
接著看到原始碼,目前 Patrol 專案是透過 Melos 管理的 monorepo,核心主要包括:
| 包 | 作用 |
|---|---|
patrol |
Flutter 端核心函式庫(Dart) |
patrol_cli |
命令列工具,負責建構、打包、編排測試執行 |
patrol_finders |
可單獨使用的自訂查找器(finders) |
patrol_gen |
程式碼產生器,從 schema.dart 生成跨平台協定程式碼 |
patrol_log |
結構化日誌 |
adb |
ADB 封裝(用於 Android 裝置操作) |
patrol_devtools_extension |
DevTools 擴充功能 |
patrol_mcp |
MCP 服務支援 |

這裡面最重要的就是雙向 gRPC 通訊架構,這是 Patrol 的核心,整個系統存在兩個獨立的 gRPC 服務,分別在不同端運行以實現互動:
整個簡要流程如下所示:


而所有跨程序通訊的資料結構和服務介面都定義在根目錄的 schema.dart,然後 patrol_gen 會解析這份 schema,分別生成程式碼:

接著就是 PatrolBinding,作為整個框架執行時的核心,它主要負責 gRPC 綁定支援:
class PatrolBinding extends LiveTestWidgetsFlutterBinding {
// 1. 啟動時註冊 setUp/tearDown 鉤子,監聽原生端的測試執行請求
// 2. 透過 patrolAppService.testExecutionRequested 掛起等待
// 3. 測試完成後主動通知原生端結果(pass/fail + details)
// 4. 收集 FlutterError,序列化後傳回原生,讓 JUnit/XCTest 報告更準確
// 5. 註冊 DevTools Service Extension 支援 UI 樹檢查
}
這裡最重要的是,測試不是按順序全部執行,而是每個測試體都會先
await patrolAppService.waitForExecutionRequest(testName)掛起,然後等到原生 Runner 透過 gRPC 發出runDartTest指令時才真正執行。也就是說,原生端可以精確控制哪個測試在什麼時機執行,並在 JUnit/XCTest 報告中單獨呈現每個測試的結果。
然後來到原生端,Android 原生層主要有:
Automator.kt:封裝 UIAutomator2 API,實作所有系統級操作(點擊通知、處理權限彈窗、切換 Wi‑Fi 等)AutomatorServer.kt:基於 Ktor 啟動 HTTP 服務,將 gRPC 呼叫路由到 AutomatorPatrolJUnitRunner.java:
PatrolAppServiceClient 呼叫 Dart 端 listDartTests() 取得測試列表runDartTest() 觸發 Dart 執行也就是說,最終 Android CI 系統(Firebase Test Lab、BrowserStack、LambdaTest 等)可以直接識別每個測試用例的結果。
而對於 iOS 原生層,則是使用 XCTest + XCUIApplication 框架,原理類似,XCTest runner 和 Dart 程序之間透過 gRPC 完成握手和測試編排;對系統的操作(權限彈窗、通知、深色模式等)則透過 XCUIApplication 和 XCUIDevice API 完成,基本上都是官方原生支援的測試自動化介面。
最後不得不提 patrol_cli,它是整個執行機制的編排器,主要包括:
patrol.yaml / pubspec.yaml 設定test_bundler.dart 將多個測試檔案打包成單一入口(_bundled_test.dart),搭配 Dart group/test 機制建立完整測試樹xcodebuild / xcrun simctl(iOS)安裝、啟動測試
也就是說,原生 Runner 會動態將每個 Dart 測試註冊為獨立的 JUnit/XCTest 用例,而 CI 系統(Firebase Test Lab、Bitrise、BrowserStack 等)可以按單一測試維度展示結果,因此在結果呈現上也能有更好的測試粒度。整個能力核心主要包括:
| 類型 | 能力 |
|---|---|
| Flutter UI | 點擊、輸入文字、滑動、等待 widget 出現、查找 widget |
| 系統權限 | 檢測彈窗、授權(使用期間/僅此一次/拒絕)、精確/模糊位置 |
| 通知 | 打開通知欄、讀取通知列表、點擊通知 |
| 網路 | Wi‑Fi 開關、行動網路開關、飛航模式、藍牙開關 |
| 裝置狀態 | 回到主畫面、最近使用的 App、音量鍵、深色模式 |
| 跨 App | 打開指定 App(套件名稱/Bundle ID)、跳轉 URL |
| 相機/相簿 | 拍照、從相簿選擇單張/多張圖片 |
| 位置 | 設定 Mock GPS 座標 |
| 系統資訊 | 取得 OS 版本、判斷是否為虛擬裝置 |
更具體的 Flutter Method 有:
| Method | Purpose | Example |
|---|---|---|
$(pattern) |
查找並串接 widgets | $(Scaffold).$(TextField).at(1) |
.tap() |
點擊 widget | $(FloatingActionButton).tap() |
.enterText(text) |
在欄位中輸入文字 | $(#emailField).enterText('[email protected]') |
.scrollTo() |
滾動到 widget | $(#submitButton).scrollTo() |
.waitUntilVisible() |
等待可見 | $(Dialog).waitUntilVisible() |
.waitUntilExists() |
等待存在 | $(LoadingSpinner).waitUntilExists() |
.containing(pattern) |
依後代篩選 | $(ListTile).containing($('Premium')) |
.which(condition) |
自訂篩選 | $(TextField).which((w) => w.enabled) |
.visible |
檢查是否可見 | if ($(Dialog).visible) { ... } |
.exists |
檢查是否存在 | if ($(#errorMessage).exists) { ... } |

另外對應的 Finder 行為可以透過 PatrolTesterConfig 進行配置,你可以根據情境自訂腳本的事件設定,例如超過多久沒出現就代表是 UX 的 Bug:
| Property | Default | Purpose |
|---|---|---|
existsTimeout |
10s | waitUntilExists() 超時 |
visibleTimeout |
10s | waitUntilVisible() 超時 |
settleTimeout |
10s | pumpAndSettle() 超時 |
settlePolicy |
trySettle |
動作在什麼時候進行結算 |
dragDuration |
100ms | 拖曳手勢持續時間 |
settleBetweenScrollsTimeout |
5s | 滾動過程中的超時設定 |
同樣地,在原生層也可以直接透過 Dart 進行操作管理,整個實作都有相同的抽象適配:


基本上原生的大多數功能都支援,除了 iOS 的無障礙標籤還不支援之外,基本能力都已經對齊:
| Property | Android | iOS | Description |
|---|---|---|---|
text / label |
✓ | ✓ | 可見文字內容 |
resourceId / identifier |
✓ | ✓ | 唯一元素 ID |
className / elementType |
✓ | ✓ | View/element type |
contentDescription |
✓ | ✗ | 無障礙標籤 |
isClickable, isEnabled |
✓ | ✓ | 互動狀態 |
instance |
✓ | ✓ | 多個匹配時的索引 |

之後,目前 Patrol 除了 Android、iOS、Web 之外,也開始支援 macOS,不過還沒支援 Windows,確實是一大遺憾:

| Feature | Android | iOS | macOS |
|---|---|---|---|
| Test Runner | Gradle + JUnit | xcodebuild + XCTest | xcodebuild + XCTest |
| UI Automation | UiAutomator 2 | XCTest | XCTest |
| Min API/Version | API 21+ | iOS 13.0+ | macOS 12.0+ |
| Back Button | ✓ | ✗ | ✗ |
| Control Center | Quick Settings | ✓ | ✓ |
| Permission Dialogs | ✓ | ✓ | ✓ |
| Notifications | ✓ | ✓ | Limited |
| Camera/Gallery | ✓ | ✓ | Partial |
| Build Flavors | ✓ | ✓ Schemes | ✓ Schemes |
這裡 iOS 和 macOS 不支援 Back Button,是因為 iOS 和 macOS 從設計上就沒有全域 Back Button,XCTest 框架本身也沒有提供模擬「系統返回」的方法。在 Patrol 測試中,針對 iOS/macOS 需要明確操作 UI 元素來實現返回,也就是 Android 可以直接用
await $.native.pressBack();,但是 iOS 需要await $('Back').tap();,或者await $.tap(find.byTooltip('Back'));。
當然,也有一些限制,例如:
不過這些限制都不算大問題,所以如果你在做 Flutter,尤其是需要測試複雜使用者流程(權限、支付、推播、OAuth 等),我個人非常推薦直接用 Patrol,特別是透過 AI 完成自動化測試驗收時,真的比直接對接 Flutter MCP 效果更好。
因為 Patrol 同樣支援 MCP 服務,可以讓 AI 直接在 Flutter 專案中執行與管理 Patrol 測試,AI 能夠直接執行測試、看裝置截圖、讀取原生 UI 樹、查看日誌狀態、重跑測試並修復測試,然後 Hot Restart 後直接執行。

所以,如果你還沒試過,真的可以試試 Patrol,AI 時代,你值得擁有。