Skip to main content
This guide will help you install Patrol and run your first test. By the end, you’ll have Patrol configured and a working test running on an emulator or device.
If you want to use Patrol finders in your existing widget or golden tests, see Using Patrol finders in widget tests.

Prerequisites

Before you begin, make sure you have:
  • Flutter SDK 3.32.0 or higher
  • Dart SDK 3.8.0 or higher
  • An Android emulator or iOS simulator (or physical device)
  • Xcode (for iOS/macOS testing) or Android Studio (for Android testing)

Installation

1

Install patrol_cli

The Patrol CLI is required to run integration tests. Install it globally:
flutter pub global activate patrol_cli
Make sure to add the Dart pub cache to your PATH environment variable:macOS/Linux:
export PATH="$PATH":"$HOME/.pub-cache/bin"
Windows:
$env:PATH += ";$env:LOCALAPPDATA\Pub\Cache\bin"
Verify the installation:
patrol --version
2

Check your environment

Run patrol doctor to verify your environment is set up correctly:
patrol doctor
Example output:
Patrol CLI version: 4.1.0
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
Make sure all checks are green for the platform you want to test.
Using Flutter version managers?If you use FVM, puro, or another version manager, set the PATROL_FLUTTER_COMMAND environment variable:
export PATROL_FLUTTER_COMMAND="fvm flutter"
# or
export PATROL_FLUTTER_COMMAND="puro flutter"
Or pass it as an argument: patrol test --flutter-command "fvm flutter"
3

Add Patrol to your project

Add patrol as a dev dependency:
flutter pub add patrol --dev
This adds the following to your pubspec.yaml:
pubspec.yaml
dev_dependencies:
  patrol: ^4.1.1
  flutter_test:
    sdk: flutter
4

Configure patrol in pubspec.yaml

Add a patrol section to your pubspec.yaml with your app details:
pubspec.yaml
name: my_app
description: My awesome Flutter app

dependencies:
  flutter:
    sdk: flutter

dev_dependencies:
  patrol: ^4.1.1
  flutter_test:
    sdk: flutter

patrol:
  app_name: My App
  android:
    package_name: com.example.myapp
  ios:
    bundle_id: com.example.MyApp
Android package name:
  • Open android/app/build.gradle
  • Look for applicationId in the defaultConfig section
iOS bundle ID:
  • Open ios/Runner.xcodeproj/project.pbxproj
  • Search for PRODUCT_BUNDLE_IDENTIFIER
  • Or open your project in Xcode and check the General tab
By default, Patrol looks for tests in patrol_test/. To use a different directory, add:
pubspec.yaml
patrol:
  app_name: My App
  test_directory: integration_test  # Custom directory
  android:
    package_name: com.example.myapp
  ios:
    bundle_id: com.example.MyApp
5

Set up native integration

Patrol needs to integrate with your app’s native code to enable full testing capabilities.
1
Create the directory structure:
mkdir -p android/app/src/androidTest/java/com/example/myapp
Replace com/example/myapp with your app’s package name (use / instead of .)
2
Create MainActivityTest.java in that directory:
android/app/src/androidTest/java/com/example/myapp/MainActivityTest.java
package com.example.myapp; // replace with your package name

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();
        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);
    }
}
3
Update android/app/build.gradle.kts (or build.gradle if using Groovy):
android/app/build.gradle.kts
android {
    defaultConfig {
        // Add these lines:
        testInstrumentationRunner = "pl.leancode.patrol.PatrolJUnitRunner"
        testInstrumentationRunnerArguments["clearPackageData"] = "true"
    }

    // Add this section:
    testOptions {
        execution = "ANDROIDX_TEST_ORCHESTRATOR"
    }
}

dependencies {
    // Add this line:
    androidTestUtil("androidx.test:orchestrator:1.5.1")
}
6

Create your first test

Create a test file at patrol_test/example_test.dart:
patrol_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 increments smoke test',
    ($) async {
      // Start the app
      await $.pumpWidgetAndSettle(
        MaterialApp(
          home: Scaffold(
            appBar: AppBar(title: const Text('Patrol Test')),
            body: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  const Text('You have pushed the button this many times:'),
                  const Text('0', key: Key('counter')),
                  ElevatedButton(
                    key: const Key('increment'),
                    onPressed: () {},
                    child: const Text('Increment'),
                  ),
                ],
              ),
            ),
          ),
        ),
      );

      // Verify the app started
      expect($('Patrol Test'), findsOneWidget);
      
      // Test native interaction (except on macOS)
      if (!Platform.isMacOS) {
        await $.platform.mobile.pressHome();
      }
    },
  );
}
This is a simple test to verify Patrol is working. In real tests, you’d pump your actual app widget.
7

Run your first test

Make sure you have an emulator/simulator running or a device connected, then run:
patrol test -t patrol_test/example_test.dart
If everything is set up correctly, you should see output like:
Test summary:
📝 Total: 1
✅ Successful: 1
❌ Failed: 0
⏩ Skipped: 0
📊 Report: build/patrol_test/reports/example_test.json
⏱️  Duration: 5s
Developing tests faster with Hot Restart:Use patrol develop instead of patrol test to enable hot restart:
patrol develop -t patrol_test/example_test.dart
Press r in the terminal to restart the test without rebuilding the app!

What’s Next?

Congratulations! You’ve successfully set up Patrol and run your first test. Here’s what to explore next:

Write Your First Real Test

Follow a comprehensive tutorial to write a full integration test with native interactions

Learn Custom Finders

Master Patrol’s intuitive finder syntax to write cleaner tests

Explore Native Automation

Learn how to interact with permissions, notifications, and system UI

CLI Commands

Discover all the patrol CLI commands and their options

Troubleshooting

This usually means your patrol and patrol_cli versions are incompatible.Check the compatibility table and ensure you’re using compatible versions.
# Check versions
patrol --version
flutter pub deps | grep patrol

# Update to latest
flutter pub global activate patrol_cli
flutter pub upgrade patrol
This is caused by using an incompatible JDK version. Patrol officially supports JDK 17.Check your JDK version:
javac -version
In Android Studio/IntelliJ, go to Settings → Build, Execution, Deployment → Build Tools → Gradle → Gradle JDK and select JDK 17.
This happens when parallel execution is enabled in Xcode.To fix:
  1. Open your project in Xcode
  2. Go to Product → Scheme → Edit Scheme
  3. Select Test from the left sidebar
  4. Uncheck Execute in parallel
  5. Repeat for all schemes if you have multiple
This is usually caused by leftover FLUTTER_TARGET configuration.Search for FLUTTER_TARGET in your *.xcconfig and *.pbxproj files and remove both the key and value.Then regenerate the configuration:
flutter build ios --config-only patrol_test/example_test.dart
Make sure you’re NOT calling:
  • WidgetsFlutterBinding.ensureInitialized() - Patrol handles this
  • runApp() - Use $.pumpWidgetAndSettle() instead
  • Modifying FlutterError.onError - This breaks test error handling
See Initializing app inside a test below.

Initializing Your App in Tests

To test your real app, you need to initialize it properly inside the test. Here are the key rules:
DO NOT in your test initialization:
  1. Call WidgetsFlutterBinding.ensureInitialized() - Patrol already does this
  2. Use runApp() - Use $.pumpWidgetAndSettle() instead
  3. Modify FlutterError.onError - This breaks test error handling (e.g., Firebase Crashlytics)
Good example:
patrol_test/app_test.dart
import 'package:my_app/main.dart';
import 'package:patrol/patrol.dart';

void main() {
  patrolTest('app test', ($) async {
    // Do your initialization
    await initializeServices();
    await loadConfig();
    
    // Pump your app (NOT runApp!)
    await $.pumpWidgetAndSettle(const MyApp());
    
    // Start testing
    await $('Login').tap();
  });
}

Future<void> initializeServices() async {
  // Initialize services, DI, etc.
  // But DON'T call WidgetsFlutterBinding.ensureInitialized()
}
Create a helper function for test initialization to avoid duplication:
patrol_test/common.dart
import 'package:my_app/main.dart';
import 'package:patrol/patrol.dart';

Future<void> createApp(PatrolIntegrationTester $) async {
  await initializeServices();
  await $.pumpWidgetAndSettle(const MyApp());
}
Then use it in tests:
patrolTest('test', ($) async {
  await createApp($);
  // Continue testing...
});

Using Flavors

If your app uses flavors, specify them in pubspec.yaml:
pubspec.yaml
patrol:
  app_name: My App
  flavor: development
  android:
    package_name: com.example.myapp.dev
  ios:
    bundle_id: com.example.MyApp.dev
Or pass as an argument:
patrol test --flavor development

Add to .gitignore

Patrol generates a test bundle file that should not be committed:
.gitignore
# Patrol generated file
patrol_test/test_bundle.dart

Get Help

If you’re stuck:

Ready for more?

Continue to the comprehensive tutorial to write a real-world test with native interactions

Build docs developers (and LLMs) love