Overview of Patrol MCP
masterpatrol develop command and exposing its capabilities as a set of MCP tools that an AI agent can invoke.repository·master·Indexed 23 days ago
https://github.com/leancodepl/patrolA multiplatform E2E UI testing framework for Flutter that extends the standard integration_test package with native automation support for handling permissions, notifications, and system settings. It includes the Patrol CLI for building and running tests, patrol_finders for a streamlined widget selection API, and Patrol MCP for AI agent integration.
patrol develop command and exposing its capabilities as a set of MCP tools that an AI agent can invoke.Patrol is a multiplatform E2E (End-to-End) UI testing framework for Flutter applications. It is designed to overcome the limitations of the standard integration_test plugin by providing native platform interaction capabilities.
Key features include:
adb package is a simple Dart wrapper around the Android Debug Bridge (ADB) command-line tool. It allows developers to interact with Android devices and emulators programmatically through Dart code.Patrol MCP is a Model Context Protocol (MCP) server designed to enable AI assistants (such as Claude, Cursor, Copilot, and Gemini) to interact directly with your Flutter projects via Patrol.
When integrated, an AI assistant can perform the following tasks:
Patrol supports the following platforms:
Note on devices: On mobile platforms (Android and iOS), Patrol works on both physical devices and virtual devices (emulators/simulators).
Unsupported platforms:
Patrol MCP (Model Context Protocol) is an optional extension that allows an AI agent to interact with your testing environment. When set up, an AI agent can:
For setup instructions, refer to the package on pub.dev: https://pub.dev/packages/patrol_mcp
Patrol uses a logical expression syntax for filtering tests via the CLI.
| Operator | Description |
|---|---|
|| | OR: Matches if the test has at least one of the specified tags. |
&& | AND: Matches only if the test has all specified tags. |
! | NOT: Matches if the test does NOT have the specified tag. |
Constraints:
() to control operator precedence.The native2 API allows you to perform native automation using platform-specific selectors within a single method call. This solves the issue where Android and iOS require different identification arguments (e.g., Android's resourceName vs iOS's label or identifier).
Instead of using flaky text-based selectors or writing manual if (Platform.isAndroid) checks, you use a NativeSelector that contains both android and ios configurations.
// Single method call with platform-specific selectors
await $.native2.tap(
NativeSelector(
android: AndroidSelector(
resourceName: 'com.android.camera2:id/shutter_button',
),
ios: IOSSelector(label: 'Take Picture'),
),
);Patrol tests should be organized using a Module pattern to encapsulate feature-specific interactions. A Module is a class that extends Module and receives the PatrolIntegrationTester (represented by $ in examples) via its constructor. This allows you to group related actions (like navigation or searching) into reusable methods that use keys to find elements.
To manage multiple modules, use a Modules aggregator class that holds instances of your feature modules, initialized with the tester instance.
import 'package:patrol/patrol.dart';
// Feature module implementation
final class Home extends Module {
Home(super.$);
Future<void> navigateToSettings() async {
await $(keys.home.settingsButton).scrollTo().tap();
}
}
// Modules aggregator
final class Modules {
Modules(this._$);
final PatrolIntegrationTester _$;
late final home = Home(_$);
}To prevent test breakage when UI changes, avoid hardcoding string keys in tests. Instead, create a single source of truth for all Key objects in a file like integration_test_keys.dart.
Pattern:
Keys class.final keys = Keys(); instance.Example implementation:
class SignInPageKeys {
final emailTextField = const Key('emailTextField');
final signInButton = const Key('signInButton');
}
class Keys {
final signInPage = SignInPageKeys();
}
final keys = Keys();Usage in test:
await $(keys.signInPage.emailTextField).enterText('test@email.com');class SignInPageKeys {
final emailTextField = const Key('emailTextField');
final passwordTextField = const Key('passwordTextField');
final signInButton = const Key('signInButton');
}
class HomePageKeys {
final notificationIcon = const Key('notificationIcon');
final successSnackbar = const Key('successSnackbar');
}
class Keys {
final signInPage = SignInPageKeys();
final homePage = HomePageKeys();
}
final keys = Keys();Proper key management is critical for stable E2E testing. Follow these constraints:
key parameter to existing widgets as the first parameter in the constructor. NEVER change widget signatures, refactor existing code structure, or create new widgets in the app.| Key Type | When to Use |
|---|---|
| Individual Keys | When widgets are hardcoded, known at compile time, or have distinct, meaningful names. |
| Parameterized Keys | When widgets are generated from dynamic data, DTOs, enums, loops, or lists. |
Rules for Parameterized Keys: