Install Patrol
Check out our video version of this tutorial on YouTube!
If you want to use Patrol finders in your existing widget or golden tests, go to Using Patrol finders in widget tests.
Setup
- Install - patrol_cli:- flutter pub global activate patrol_cli- Patrol CLI (command-line interface) is a small program that enables running Patrol UI tests. It is necessary to run UI tests ( - flutter testwon't work! Here's why).- Make sure to add - patrolto your- PATHenvironment variable. It's explained in the README.
- Verify that installation was successful and your environment is set up properly: - patrol doctor- Example output: - Patrol CLI version: 2.3.1+1 Android: • Program adb found in /Users/username/Library/Android/sdk/platform-tools/adb • Env var $ANDROID_HOME set to /Users/username/Library/Android/sdk iOS / macOS: • Program xcodebuild found in /usr/bin/xcodebuild • Program ideviceinstaller found in /opt/homebrew/bin/ideviceinstaller- Be sure that for the platform you want to run the test on, all the checks are green. - Patrol CLI invokes the Flutter CLI for certain commands. To override the command used, pass the - --flutter-commandargument or set the- PATROL_FLUTTER_COMMANDenvironment variable. This supports FVM (by setting the value to- fvm flutter), puro (- puro flutter) and potentially other version managers.
- Add a dependency on the - patrolpackage in the- dev_dependenciessection of- pubspec.yaml.- patrolpackage requires Android SDK version 21 or higher.- flutter pub add patrol --dev
- Create - patrolsection in your- pubspec.yaml:pubspec.yaml- dependencies: # ... dev_dependencies: # ... patrol: app_name: My App android: package_name: com.example.myapp ios: bundle_id: com.example.MyApp macos: bundle_id: com.example.macos.MyApp- In this tutorial, we are using example app, which has package name - com.example.myappon Android, bundle id- com.example.MyAppon iOS,- com.example.macos.MyAppon macOS and- My Appname on all platforms. Replace any occurences of those names with proper values.- If you don't know where to get - package_nameand- bundle_idfrom, see the FAQ section.
- Integrate with native side - The 3 first steps were common across platforms. The rest is platform-specific. - Psst... Android is a bit easier to set up, so we recommend starting with it! - Go to android/app/src/androidTest/java/com/example/myapp/ in your project directory. If there are no such folders, create them. Remember to replace - /com/example/myapp/with the path created by your app's package name.
- Create a file named - MainActivityTest.javaand copy there the code below.MainActivityTest.java- package com.example.myapp; // replace "com.example.myapp" with your app's package import androidx.test.platform.app.InstrumentationRegistry; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; import pl.leancode.patrol.PatrolJUnitRunner; @RunWith(Parameterized.class) public class MainActivityTest { @Parameters(name = "{0}") public static Object[] testCases() { PatrolJUnitRunner instrumentation = (PatrolJUnitRunner) InstrumentationRegistry.getInstrumentation(); // replace "MainActivity.class" with "io.flutter.embedding.android.FlutterActivity.class" // if in AndroidManifest.xml in manifest/application/activity you have // android:name="io.flutter.embedding.android.FlutterActivity" instrumentation.setUp(MainActivity.class); instrumentation.waitForPatrolAppService(); return instrumentation.listDartTests(); } public MainActivityTest(String dartTestName) { this.dartTestName = dartTestName; } private final String dartTestName; @Test public void runDartTest() { PatrolJUnitRunner instrumentation = (PatrolJUnitRunner) InstrumentationRegistry.getInstrumentation(); instrumentation.runDartTest(dartTestName); } }
- Go to the build.gradle.kts file, located in android/app folder in your project directory. 
- Add these 2 lines to the - defaultConfigsection:android/app/build.gradle.kts- testInstrumentationRunner = "pl.leancode.patrol.PatrolJUnitRunner" testInstrumentationRunnerArguments["clearPackageData"] = "true"
- Add this section to the - androidsection:android/app/build.gradle.kts- testOptions { execution = "ANDROIDX_TEST_ORCHESTRATOR" }
- Add this line to - dependenciessection:android/app/build.gradle.kts- androidTestUtil("androidx.test:orchestrator:1.5.1")
 - Bear in mind that ProGuard can lead to some problems if not well configured, potentially causing issues such as - ClassNotFoundExceptions. Keep all the Patrol packages or disable ProGuard in- android/app/build.gradle.kts:android/app/build.gradle.kts- ... buildTypes { getByName("release") { ... } getByName("debug") { isMinifyEnabled = false isShrinkResources = false } }
- Create a simple integration test - Let's create a dummy Flutter integration test that you'll use to verify that Patrol is correctly set up. - Paste the following code into - integration_test/example_test.dart:integration_test/example_test.dart- import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:patrol/patrol.dart'; void main() { patrolTest( 'counter state is the same after going to home and switching apps', ($) async { // Replace later with your app's main widget await $.pumpWidgetAndSettle( MaterialApp( home: Scaffold( appBar: AppBar(title: const Text('app')), backgroundColor: Colors.blue, ), ), ); expect($('app'), findsOneWidget); if (!Platform.isMacOS) { await $.native.pressHome(); } }, ); }- It does only 2 things: - first, it finds a text app
- then (on mobile platforms), it exits to home screen
 - It's a very simple test, but it's enough to verify that Patrol is correctly set up. To run - integration_test/example_test.darton a connected Android, iOS or macOS device:- patrol test -t integration_test/example_test.dart- If the setup is successful, you should see a summary like one below. - Test summary: 📝 Total: 1 ✅ Successful: 1 ❌ Failed: 0 ⏩ Skipped: 0 📊 Report: <some path> ⏱️ Duration: 4s- If something went wrong, please proceed to the FAQ section which might contain an answer to your issue. 
- first, it finds a text 
To prevent issues during Patrol tests, please follow these guidelines:
- Do not call IntegrationTestWidgetsFlutterBinding.ensureInitialized. Patrol automatically initializes its own test binding.
- Do not modify the global FlutterError.onErrorcallback. Patrol's internals depend on it. Keep in mind that this callback can also be modified by popular packages such as Sentry or Crashlytics. In such cases, you can disable them for Patrol tests.
If you are looking for a working example of a Flutter app with Patrol tests, check out the example app in the patrol repository.
Flavors
If your app is using flavors, then you can pass them like so:
patrol test --target integration_test/example_test.dart --flavor development
or you can specify them in pubspec.yaml (recommended):
patrol:
  app_name: My App
  flavor: development
  android:
    package_name: com.example.myapp
  ios:
    bundle_id: com.example.MyApp
    app_name: The Awesome App
  macos:
    bundle_id: com.example.macos.MyApp
iOS
If you couldn't find an answer to your question/problem, feel free to ask on Patrol Discord Server.
Going from here
To learn how to write Patrol tests, see finders and native automation sections.

