Use when writing E2E/integration tests, testing native interactions like permissions or system dialogs, capturing UI regressions, or validating cross-platform behavior (Patrol 4.x).
Scanned 8/31/2026
Install to Claude Code
npx -y skills add evanca/flutter-ai-rules --skill patrol-e2e-testing --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Patrol E2e Testing?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/evanca-patrol-e2e-testing)More formats (shields.io, HTML) on the badges page.
---
name: patrol-e2e-testing
description: "Use when writing E2E/integration tests, testing native interactions like permissions or system dialogs, capturing UI regressions, or validating cross-platform behavior (Patrol 4.x)."
license: MIT
---
# Patrol E2E Testing Skill
Design, implement, and run end-to-end (E2E) tests using Patrol 4.x in Flutter projects.
## When to Use
Use this skill when:
* A new screen or user flow needs E2E test coverage.
* A feature interacts with native components (permissions, notifications, system dialogs, deep links).
* A UI bug should be captured as a regression test.
* Cross-platform behavior (Android / iOS / Web) must be validated.
* Setting up Patrol in a new or existing Flutter project.
## Setup
Follow the official Patrol documentation for installation and project initialization:
[https://patrol.leancode.co/documentation#setup](https://patrol.leancode.co/documentation#setup)
Key Patrol conventions:
* Add `patrol` as a dev dependency.
* Place tests in `patrol_test/`.
* Name test files with a `_test.dart` suffix.
* Execute tests with `patrol test`.
## Workflow
Follow these steps when implementing or updating Patrol tests.
### 1. Identify the user journey
Break the feature into:
* **Actions**: taps, scrolls, input, navigation, deep links.
* **Observable outcomes**: visible text, screen changes, enabled buttons, dialogs.
Rules:
* One test per primary (happy-path) journey.
* Separate tests for critical edge cases.
* Avoid combining unrelated flows in a single test.
### 2. Structure the Patrol test
Basic Patrol structure:
```dart
import 'package:flutter_test/flutter_test.dart';
import 'package:patrol/patrol.dart';
void main() {
patrolTest(
'user can log in successfully',
($) async {
await $.pumpWidgetAndSettle(const MyApp());
const email = String.fromEnvironment('E2E_EMAIL');
const password = String.fromEnvironment('E2E_PASSWORD');
await $(#emailField).enterText(email);
await $(#passwordField).enterText(password);
await $(#loginButton).tap();
await $.waitUntilVisible($(#homeScreenTitle));
expect($(#homeScreenTitle).text, equals('Welcome'));
},
);
}
```
Key concepts:
* Use `patrolTest()` instead of `testWidgets()`.
* `$` is the Patrol tester.
* Use `$(#keyName)` to find widgets by `Key`.
* Use explicit wait conditions (e.g., `waitUntilVisible`).
### 3. Handle native dialogs
For OS-level permission dialogs:
```dart
patrolTest('grants camera permission', ($) async {
await $.pumpWidgetAndSettle(const MyApp());
await $(#openCameraButton).tap();
if (await $.native.isPermissionDialogVisible()) {
await $.native.grantPermission();
}
await $.waitUntilVisible($(#cameraPreview));
});
```
Use native automation only when required by the feature.
### 4. Selector & interaction quick reference
**Finding widgets:**
```dart
$('some text') // by text
$(TextField) // by type
$(Icons.arrow_back) // by icon
```
**Tapping:**
```dart
// Tap a widget containing a specific text label
await $(Container).$('click').tap();
// Tap a container that contains an ElevatedButton
await $(Container).containing(ElevatedButton).tap();
// Tap only the enabled ElevatedButton
await $(ElevatedButton)
.which<ElevatedButton>(
(b) => b.enabled,
)
.tap();
```
**Entering text:**
```dart
// Enter text into the second TextField on screen
await $(TextField).at(1).enterText('your input');
```
**Scrolling:**
```dart
await $(widget_you_want_to_scroll_to).scrollTo();
```
**Native interactions:**
```dart
// Grant permission while app is in use
await $.native.grantPermissionWhenInUse();
// Open notification shade and tap a notification by text
await $.native.openNotifications();
await $.native.tapOnNotificationBySelector(
Selector(textContains: 'text'),
);
```
### 5. Running Patrol tests
Run all tests:
```bash
patrol test
```
Run a specific file with live reload (development mode):
```bash
patrol develop -t integration_test/my_test.dart
```
Run a specific file:
```bash
patrol test --target patrol_test/login_test.dart
```
Run on web:
```bash
patrol test --device chrome
```
Headless web (CI):
```bash
patrol test --device chrome --web-headless true
```
Filter by tags:
```bash
patrol test --tags android
```
### 6. Stabilization patterns
Flaky tests undermine confidence. Apply these patterns:
```dart
// AVOID — arbitrary delay
await Future.delayed(Duration(seconds: 3));
// PREFER — explicit wait condition
await $.waitUntilVisible($(#targetWidget));
// For animations, pump until settled
await $.pumpAndSettle();
```
* Never use `Future.delayed` as a synchronization mechanism.
* Use `waitUntilVisible` or `waitUntilExists` to wait for UI state.
* Set `settleTimeout` in `PatrolTesterConfig` for slow CI environments.
### Output requirements
When applied, this skill produces:
1. Patrol test(s) covering the specified feature.
2. Any required widget `Key` additions to production code.
3. Exact `patrol test` command(s) to execute locally.
4. Notes explaining stabilization or timing decisions.
**Checkpoint:** Run `patrol test --target <file>` locally to confirm the test passes before committing.
### Quality bar
A valid Patrol test must be:
* **Deterministic** — no arbitrary delays; uses explicit wait conditions.
* **Readable** — clear test name describing the user journey.
* **Minimal but complete** — one assertion chain per journey.
* **Secret-safe** — credentials loaded from `String.fromEnvironment`, never hardcoded.
* **CI-ready** — passes headless with `--web-headless true` or on emulator.No comments yet. Be the first to comment!