Patrol

repository·master·Indexed 23 days ago

https://github.com/leancodepl/patrol

A 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.

Tokens
61.8K
Snippets
163
Records
309
Agent score
72%

What's inside Patrol

  1. What is Patrol?

    master

    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:

    • Native Access: Interact with permission dialogs, notifications, WebViews, and device settings (like Wi-Fi) using plain Dart code.
    • Improved Test Writing: Uses a custom finder system for more readable code and supports Hot Restart for faster development cycles.
    • Production-Ready Features: Provides full test isolation between tests, sharding support, and real-time console logs during execution.
    • Device Farm Compatibility: Works with major providers such as Firebase Test Lab, BrowserStack, LambdaTest, Marathon, emulator.wtf, and AWS Device Farm.
  2. Use Patrol MCP with AI assistants

    master

    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:

    • Run and re-run specific Patrol tests.
    • Capture screenshots using auto-detected platforms.
    • Read the native UI tree during active test sessions to understand the state of the application.
  3. Understand Patrol's Open Source vs. Paid Services

    master
    Patrol is an open-source framework maintained by LeanCode under the Apache 2.0 License and is free to use. LeanCode offers optional value-added services for companies that need to accelerate adoption, scale quickly, or require professional implementation support. These services include custom project setup, CI/CD integration, automated test creation, consultation, and professional training programs.
  4. Check supported platforms for Patrol

    master

    Patrol supports the following platforms:

    • Android: version 5.0 (API 21) and newer.
    • iOS: version 13 and newer.
    • macOS: version 10.14 and newer (currently in alpha support).

    Note on devices: On mobile platforms (Android and iOS), Patrol works on both physical devices and virtual devices (emulators/simulators).

    Unsupported platforms:

    • Windows
    • Linux
  5. Tag expression syntax and operators

    master

    Patrol uses a logical expression syntax for filtering tests via the CLI.

    OperatorDescription
    ||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:

    • Tags must be valid Dart identifiers (though hyphens are permitted).
    • Expressions can be grouped using parentheses () to control operator precedence.
  6. Use `native2` for cross-platform native automation

    master

    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'),
      ),
    );
  7. Organize tests using Modules

    master

    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(_$);
    }
  8. Manage widget keys for maintainable tests

    master

    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:

    1. Define classes for page-specific keys.
    2. Aggregate them into a global Keys class.
    3. Export a global final keys = Keys(); instance.
    4. Use this instance in both your application code and your test code.

    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();
  9. Conventions for Test Keys

    master

    Proper key management is critical for stable E2E testing. Follow these constraints:

    General Key Rules

    • Scope: Assign keys ONLY to widgets involved in testing.
    • Implementation: Add the 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.
    • Source of Truth: Do not hardcode keys in the app. Use a shared keys file between the app and tests.
    • Uniqueness & Order: Ensure every key value is unique and always sort keys alphabetically.

    Individual vs. Parameterized Keys

    Key TypeWhen to Use
    Individual KeysWhen widgets are hardcoded, known at compile time, or have distinct, meaningful names.
    Parameterized KeysWhen widgets are generated from dynamic data, DTOs, enums, loops, or lists.

    Rules for Parameterized Keys:

    • Always prefer using existing enums or DTOs as the parameter.
    • Use existing widget properties for the parameters.
    • Consistency: Never assign a parameterized key in the app and then use fixed values for it in the keys file (or vice versa).
    • No Helpers: Do not create helper methods; use parameterized keys directly.