Getting started

If you want to use Patrol finders in your existing widget or golden tests, go to Using Patrol finders in widget tests.

In this tutorial, we are using example app, which has package name com.example.myapp on Android, bundle id com.example.MyApp on iOS and My App name on both Android and iOS. Replace any occurences of those names with proper values.

Add dependency on patrol

If you haven't already, add a dependency on the patrol package in the dev_dependencies section of pubspec.yaml.

flutter pub add patrol --dev

Configure Patrol in pubspec.yaml

Create patrol section 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

Install 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 test won't work! Here's why).

  1. Install patrol_cli executable:

    dart pub global activate patrol_cli
    
  2. Verify that installation was successful and your enviroment is set up properly:

    patrol doctor
    

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!

  1. Go to android/app/src/androidTest/java/com/example/myapp/ in your project directory. If there are no such folders, create them.

  2. Create a file named MainActivityTest.java and 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 your AndroidManifest is using: 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);
    }
}
  1. Go to the build.gradle file, located in android/app folder in your project directory.

  2. Add these 2 lines to the defaultConfig section:

  testInstrumentationRunner "pl.leancode.patrol.PatrolJUnitRunner"
  testInstrumentationRunnerArguments clearPackageData: "true"
  1. Add this section to the android section:
  testOptions {
    execution "ANDROIDX_TEST_ORCHESTRATOR"
  }
  1. Add this line to dependencies section:
  androidTestUtil "androidx.test:orchestrator:1.4.2"
  1. Open ios/Runner.xcworkspace in Xcode.

  2. Create a test target if you do not already have one via File > New > Target... and select UI Testing Bundle. Change the Product Name to RunnerUITests. Make sure Target to be Tested is set to Runner and language is set to Objective-C. Select Finish.

  3. 2 files are created: RunnerUITests.m and RunnerUITestsLaunchTests.m. Delete RunnerUITestsLaunchTests.m through Xcode.

  4. Make sure that the iOS Deployment Target of RunnerUITests within the Build Settings section is the same as Runner. The minimum supported iOS Deployment Target is 11.0.

Xcode iOS deployment target
Xcode iOS deployment target 2
  1. Replace contents of RunnerUITests.m file with the following:
ios/RunnerUITests/RunnerUITests.m
@import XCTest;
@import patrol;
@import ObjectiveC.runtime;

PATROL_INTEGRATION_TEST_IOS_RUNNER(RunnerUITests)

Add the newly created target to ios/Podfile by embedding in the existing Runner target.

ios/Podfile
target 'Runner' do
  # Do not change existing lines.
  ...

  target 'RunnerUITests' do
    inherit! :complete
  end
end
  1. Create an empty file integration_test/example_test.dart in the root of your Flutter project. From the command line, run:
$ flutter build ios --config-only integration_test/example_test.dart
  1. Go to your ios directory and run:
$ pod install --repo-update
  1. Open your Xcode project and make sure every target has correct base Build Configurations:
$ open Runner.xcworkspace
Xcode config setup
  1. Add 2 new "Run Script Phase" Build Phases to the RunnerUITests target:
Xcode config setup
  1. Paste this code into the first xcode_backend build Build Phase:
/bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build
  1. Paste this code into the second xcode_backend embed_and_thin Build Phase:
/bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed_and_thin
  1. Xcode by default also enables a "parallel execution" setting, which breaks Patrol. Disable it:

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 '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);
      await $.native.pressHome();
    },
  );
}

It does only 2 things:

  • first, it finds a text app
  • then, it then 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.dart on a local Android or iOS device (emulated or physical):

patrol test -t integration_test/example_test.dart

If the setup is successful, you should see a TEST PASSED message.

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

pubspec.yaml
patrol:
  app_name: My App
  flavor: development
  android:
    package_name: com.example.myapp
  ios:
    bundle_id: com.example.MyApp
    app_name: The Awesome App

To prevent issues during Patrol tests, please follow these guidelines:

  1. Do not call IntegrationTestWidgetsFlutterBinding.ensureInitialized. Patrol automatically initializes its own test binding.
  2. Do not modify the global FlutterError.onError callback. Patrol's internals depend on it.

Going from here

To learn how to write Patrol tests, see finders and native automation sections.