Skip to content

Flutter E2E Testing: A Complete Guide

End-to-end (E2E) testing verifies that your Flutter app works correctly from the user’s perspective. Instead of testing a single widget or function in isolation, an E2E test launches the full application on a real device or emulator, walks through a user flow, and asserts that the expected outcome appears on screen.

This guide covers what E2E testing means for Flutter, the main approaches available today, and how to choose the right one for your project.

Unit tests and widget tests catch logic errors early, but they cannot verify that navigation, platform channels, network calls, and animations work together on a physical device. E2E tests fill that gap. They help you:

  • Catch regressions in login flows, checkout screens, and other critical paths before users do.
  • Validate platform-specific behavior on Android and iOS.
  • Confirm that third-party plugins (camera, GPS, push notifications) integrate correctly.
  • Gate CI/CD pipelines so broken builds never reach production.

Flutter’s cross-platform promise means you ship to multiple targets from one codebase. E2E tests prove that promise holds on each target.

integration_test is the official package bundled with the Flutter SDK. Tests are written in Dart using the WidgetTester API.

integration_test/login_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:my_app/main.dart' as app;
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('user can log in', (tester) async {
app.main();
await tester.pumpAndSettle();
await tester.tap(find.text('Sign In'));
await tester.pumpAndSettle();
await tester.enterText(find.byKey(Key('email')), 'user@example.com');
await tester.enterText(find.byKey(Key('password')), 'secret123');
await tester.tap(find.text('Continue'));
await tester.pumpAndSettle();
expect(find.text('Welcome'), findsOneWidget);
});
}

Strengths: Ships with Flutter, zero extra dependencies, direct access to the widget tree.

Limitations: Verbose Dart boilerplate, pumpAndSettle timing issues, no built-in reporting or CI dashboard, cannot interact with native system dialogs (permission prompts, keyboards).

Patrol extends integration_test with native automation capabilities. It adds a NativeAutomator that can tap native OS dialogs, handle permissions, and interact with the notification shade.

patrolTest('user grants location permission', ($) async {
await $.pumpWidgetAndSettle(MyApp());
await $('Allow location').tap();
await $.native.grantPermission();
expect($('Map loaded'), findsOneWidget);
});

Strengths: Native dialog support, custom finder syntax ($()), built on top of the familiar Flutter test API.

Limitations: Still Dart-only, requires test code to live inside the app project, limited cloud-farm support, no plain-English syntax.

FlutterProbe takes a different approach. Tests are written in ProbeScript, a plain-English language stored in .probe files. A Go CLI parses the scripts and sends commands to a lightweight Dart agent embedded in the app, communicating over WebSocket with sub-50ms round-trips.

test "user can log in"
open the app
wait until "Sign In" appears
tap "Sign In"
type "user@example.com" into "Email"
type "secret123" into "Password"
tap "Continue"
see "Welcome"

Strengths: No Dart test code to maintain, human-readable scripts that non-developers can review, direct widget-tree access without a WebDriver layer, built-in visual regression, self-healing selectors, support for five cloud farms, and parallel execution via --parallel and --shard flags.

Limitations: Requires installing a separate CLI and embedding the Dart agent. ProbeScript is purpose-built for E2E flows, so complex programmatic logic may need hooks or recipes.

Aspectintegration_testPatrolFlutterProbe
LanguageDartDartProbeScript (plain English)
Native dialog supportNoYesYes (permissions, GPS, clipboard)
Cloud farm supportManual setupLimited5 farms built-in
CI reportingDIYDIYBuilt-in HTML/JUnit reports
Visual regressionNoNoYes
Self-healing selectorsNoNoYes
Migration toolingN/AN/AImports from 7 formats

Choose integration_test when you need a quick smoke test on a single platform with no extra dependencies. It is the lowest-friction starting point and good enough for small projects with a single developer.

Choose Patrol when your tests must interact with native OS dialogs (permission prompts, system alerts) and your team is comfortable writing Dart test code. Patrol is a natural upgrade from integration_test.

Choose FlutterProbe when you want tests that scale across devices, run on cloud farms, and stay readable by QA engineers and product managers who do not write Dart. The migration tool can convert existing Maestro, Gherkin, or Detox suites into ProbeScript automatically.

  1. Install the CLI — a single binary for macOS, Linux, and Windows.
  2. Add the Dart agent to your app (pubspec.yaml one-liner).
  3. Write your first .probe file following the Quick Start guide.
  4. Run it: probe run tests/smoke/login.probe --device emulator-5554.
  5. View the HTML report or pipe JUnit XML into your CI pipeline. See CI/CD with GitHub Actions for a working workflow.

The VS Code extension adds syntax highlighting, CodeLens run buttons, and a test explorer so you can author and debug tests without leaving your editor.