Before starting this tutorial, make sure you’ve completed the Quickstart guide to set up Patrol in your project.
Tutorial Project
We’ll use a starter project that’s already configured with Patrol. Clone it to follow along:Clone the Starter Project
To learn how to set up Patrol in your own project from scratch, refer back to the Quickstart guide.
Video Tutorial
Watch the video walkthrough for a visual guide:App Overview
Before writing tests, let’s understand what the app does:Sign In Screen
The first screen validates email and password:- Valid email: Must be in proper email format
- Valid password: At least 8 characters long
- Only valid credentials allow progression to the home screen
Home Screen
After signing in:- A notification permission dialog appears
- After granting permission, tap the notification icon to trigger a notification
- The notification appears after 3 seconds (works in foreground and background)
- Tapping the notification shows a snackbar: “Notification was tapped!”
Patrol can test real authentication providers that use WebView for sign-in using native automation.
Testing Philosophy: The Happy Path
UI tests should focus on the happy path - the main flow users take to accomplish their goal: ✅ Test: Successful sign-in, notification trigger, and notification tap❌ Don’t test: Validation error messages for invalid input
Validation errors exist to enable the happy path, not as features to exhaustively test in integration tests. Save detailed validation testing for unit tests.
Writing the Test
Let’s build the test step by step. We’ll create a single test that covers the entire flow.Step 1: Set Up the Test Structure
Createpatrol_test/app_test.dart:
patrol_test/app_test.dart
Step 2: Enable Hot Restart for Fast Development
Instead of running the full build each time you change the test, use hot restart:r to restart without rebuilding!
Step 3: Initialize and Pump the App
Start by initializing the app and pumping the root widget:patrol_test/app_test.dart
Step 4: Finding Widgets - First Attempt
Let’s try entering text in the email and password fields. We’ll start by finding them by type:.at() to specify the index:
Step 5: Better Practice - Using Keys
Finding by type is fragile. The recommended approach is using Keys. Add keys to your widgets insign_in_page.dart:
lib/pages/sign_in_page.dart
Step 6: Best Practice - Centralized Keys
Hardcoding key strings in both your app and tests creates duplication. Use a centralized keys file:lib/integration_test_keys.dart
lib/pages/sign_in_page.dart
patrol_test/app_test.dart
Benefits of centralized keys:
- Single source of truth
- Refactoring updates keys everywhere automatically
- Type-safe access
- Better IDE autocomplete
Step 7: Handle Permission Dialog
After signing in, the app requests notification permission. Use Patrol’s native automation:Native automation lets you interact with the OS your Flutter app runs on. Patrol supports Android, iOS, and macOS. Learn more
Step 8: Trigger Notification and Go to Home Screen
Add keys to the home page:lib/pages/home_page.dart
Step 9: Tap the Notification
Open the notification shade and tap the notification by finding its text:The notification has a 3-second delay, so we use a 5-second timeout to ensure it appears before trying to tap it.
Step 10: Verify the Result
Finally, verify that tapping the notification showed the success snackbar:Complete Test
Here’s the finished test:patrol_test/app_test.dart
Run the Complete Test
Run the test on a device or emulator:What You Learned
Congratulations! You’ve learned how to:Use Custom Finders
- Find widgets by type, text, and keys
- Use the
$()finder syntax - Use
.at()for multiple matches - Implement centralized keys
Native Automation
- Handle permission dialogs
- Press home button
- Open notification shade
- Tap on notifications
Test Best Practices
- Focus on happy path
- Use centralized keys
- Hot restart for fast development
- Handle timing with timeouts
Test Structure
- Create patrolTest
- Initialize and pump widgets
- Verify results with expectations
- Handle branching logic safely
Next Steps
Learn more finders
Explore advanced finder techniques like
.containing(), .which(), and custom matchers in the Finders guide.Master native automation
Discover all native automation capabilities in the Native Automation guide.
Run tests in CI/CD
Set up automated testing in your CI/CD pipeline with our CI/CD guides.
Test real features
Check out feature-specific guides for permissions, camera, WebViews, and more.
Additional Tips
Organizing tests for larger apps
Organizing tests for larger apps
For more complex apps:
- Create separate key files per feature:
lib/keys/auth_keys.dart,lib/keys/profile_keys.dart - Use page object models: Create classes that encapsulate page interactions
- Extract common flows into helper functions
- Group related tests in the same file
Handling branching logic in tests
Handling branching logic in tests
Generally, avoid
if statements in tests as they can hide flakiness. However, some cases are acceptable:✅ Good use cases:- Checking for permission dialogs (may or may not appear)
- Platform-specific behavior (
if (Platform.isAndroid)) - Feature flags that differ between environments
- Checking if elements exist before interacting (test should know the state)
- Working around flaky finders
- Conditional test logic based on previous step results
Debugging failing tests
Debugging failing tests
When tests fail:
- Check screenshots: Patrol saves screenshots on failure
- Use
patrol develop: See what’s happening in real-time - Add delays:
await Future.delayed(Duration(seconds: 1))to observe state - Print widget tree:
$.pumpAndSettle(); debugDumpApp(); - Check logs: Look at console output for errors
Testing on real devices
Testing on real devices
For iOS physical devices:
- Follow the Physical iOS Devices Setup guide
- Configure signing and provisioning profiles
- Use
patrol test --dart-define=PATROL_WAIT=10000for slower devices
- Enable Developer Options and USB Debugging
- Connect via USB or WiFi
- Run
adb devicesto verify connection
Resources
Explore Native Automation
Ready to dive deeper? Learn about all the native automation features Patrol offers.