flutter_driver vs integration_test: When to Use Each in 2026

flutter_driver vs integration_test: When to Use Each in 2026

Flutter has two end-to-end testing frameworks: the legacy flutter_driver and the modern integration_test package. For new projects, use integration_test — it runs in the same process as your app, works with flutter test, and supports Web and Desktop. flutter_driver remains useful for specific scenarios like performance tracing.

Key Takeaways

integration_test is the current recommendation. It runs tests in the same Dart isolate as the app, making it faster and easier to debug than flutter_driver.

flutter_driver uses a separate process. Tests talk to the app over a socket — useful for performance profiling but adds complexity.

integration_test runs with flutter test. No separate driver process or flutter drive command needed for most use cases.

Web and Desktop work with integration_test. flutter_driver has limited platform support. integration_test runs everywhere Flutter runs.

flutter_driver is still needed for timeline/performance tests. If you need Timeline profiling, you still need flutter_driver with enableFlutterDriverExtension().

Architecture Comparison

flutter_driver

┌─────────────────┐      socket      ┌─────────────────┐
│   Test Process  │ ←──────────────→ │   App Process   │
│  (flutter_driver│                  │  (running on    │
│   test code)    │                  │  device/emulator│
└─────────────────┘                  └─────────────────┘

Two separate processes. The test sends commands, the app executes them and returns results.

integration_test

┌─────────────────────────────────┐
│         Single Process          │
│  ┌───────────┐  ┌────────────┐  │
│  │ Test Code │  │  App Code  │  │
│  │           │  │            │  │
│  └───────────┘  └────────────┘  │
└─────────────────────────────────┘

Same process. Tests can access app state directly.

Package Setup

integration_test (Modern)

# pubspec.yaml
dev_dependencies:
  integration_test:
    sdk: flutter
  flutter_test:
    sdk: flutter
test/
  integration_test/
    app_test.dart

flutter_driver (Legacy)

dev_dependencies:
  flutter_driver:
    sdk: flutter
  test: ^1.25.0
test_driver/
  app.dart          # app entry with enableFlutterDriverExtension()
  app_test.dart     # driver test

Writing Tests: Side-by-Side

integration_test

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('login flow', (tester) async {
    app.main();
    await tester.pumpAndSettle();

    await tester.tap(find.byKey(const Key('email_field')));
    await tester.enterText(find.byKey(const Key('email_field')), 'user@example.com');
    await tester.enterText(find.byKey(const Key('password_field')), 'secret');
    await tester.tap(find.byKey(const Key('login_button')));
    await tester.pumpAndSettle();

    expect(find.text('Welcome, user@example.com'), findsOneWidget);
  });
}

flutter_driver

// test_driver/app.dart
import 'package:flutter_driver/driver_extension.dart';
import 'package:my_app/main.dart' as app;

void main() {
  enableFlutterDriverExtension();
  app.main();
}

// test_driver/app_test.dart
import 'package:flutter_driver/flutter_driver.dart';
import 'package:test/test.dart';

void main() {
  late FlutterDriver driver;

  setUpAll(() async {
    driver = await FlutterDriver.connect();
  });

  tearDownAll(() => driver.close());

  test('login flow', () async {
    await driver.tap(find.byValueKey('email_field'));
    await driver.enterText('user@example.com');
    await driver.tap(find.byValueKey('password_field'));
    await driver.enterText('secret');
    await driver.tap(find.byValueKey('login_button'));
    await driver.waitFor(find.text('Welcome, user@example.com'));
  });
}

Finder Differences

Action integration_test flutter_driver
Find by key find.byKey(Key('k')) find.byValueKey('k')
Find by text find.text('Hello') find.text('Hello')
Find by type find.byType(ElevatedButton) find.byType('ElevatedButton')
Find by tooltip find.byTooltip('Submit') find.byTooltip('Submit')
Find descendant find.descendant(of: ..., matching: ...) find.descendant(of: ..., matching: ...)

integration_test uses the same find.* API as flutter_test — no separate finder syntax to learn.

Running Tests

integration_test

# Run on connected device/emulator
flutter test integration_test/app_test.dart

# Run on web
flutter test integration_test/app_test.dart -d chrome

# Run on desktop
flutter test integration_test/app_test.dart -d macos

# All integration tests
flutter test integration_test/

flutter_driver

# Requires running app or emulator
flutter drive --target=test_driver/app.dart

CI Integration

integration_test in GitHub Actions

name: Integration Tests

on: [push, pull_request]

jobs:
  integration-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: subosito/flutter-action@v2

      - name: Install dependencies
        run: flutter pub get

      - name: Run integration tests (headless Chrome)
        run: |
          flutter test integration_test/ \
            -d chrome \
            --headless

For mobile integration tests in CI, use Firebase Test Lab or BrowserStack.

flutter_driver in GitHub Actions

- name: Start emulator and run driver tests
  uses: reactivecircus/android-emulator-runner@v2
  with:
    api-level: 33
    script: flutter drive --target=test_driver/app.dart

flutter_driver on CI requires a running device or emulator — more complex to set up.

Accessing App State in integration_test

Since integration_test runs in the same process:

testWidgets('cart has 3 items after adding', (tester) async {
  app.main();
  await tester.pumpAndSettle();

  // Add items
  for (int i = 0; i < 3; i++) {
    await tester.tap(find.byKey(const Key('add_to_cart_button')));
    await tester.pumpAndSettle();
  }

  // Access app state directly (not possible in flutter_driver)
  final cart = CartService.instance;
  expect(cart.items.length, 3);
});

With flutter_driver, you'd have to assert through the UI only — no direct state access.

Screenshots

integration_test

import 'package:integration_test/integration_test.dart';

void main() {
  final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();

  testWidgets('take screenshot', (tester) async {
    app.main();
    await tester.pumpAndSettle();

    await binding.takeScreenshot('home_screen');
  });
}

flutter_driver

final screenshot = await driver.screenshot();
final file = File('screenshots/home.png');
await file.writeAsBytes(screenshot);

Performance Testing (Where flutter_driver Shines)

flutter_driver is still the go-to for performance timeline testing:

test('scrolling performance', () async {
  final timeline = await driver.traceAction(() async {
    await driver.scroll(
      find.byValueKey('product_list'),
      0, -500,
      const Duration(milliseconds: 300),
    );
  });

  final summary = TimelineSummary.summarize(timeline);
  expect(summary.computeAverageFrameBuildTimeMillis(), lessThan(16.0));
  expect(summary.countJankyFrames(), lessThan(5));
});

integration_test doesn't have built-in timeline tracing — if you need frame budget analysis, flutter_driver is still necessary.

Migration: flutter_driver → integration_test

// flutter_driver (before)
final listFinder = find.byValueKey('product_list');
await driver.tap(find.byValueKey('search_button'));
await driver.enterText('Flutter book');
await driver.waitFor(find.byValueKey('search_results'));

// integration_test (after)
await tester.tap(find.byKey(const Key('search_button')));
await tester.enterText(find.byKey(const Key('search_field')), 'Flutter book');
await tester.pumpAndSettle(); // replaces waitFor
expect(find.byKey(const Key('search_results')), findsOneWidget);

Key changes:

  • find.byValueKey('k') → find.byKey(const Key('k'))
  • driver.waitFor(finder) → tester.pumpAndSettle()
  • driver.getText(finder) → get text via tester.widget<Text>(finder).data
  • No need for separate app.dart entry point file

Decision Guide

Need Use
New project integration_test
Web or Desktop integration_test
Direct state access in tests integration_test
Frame timeline / performance profiling flutter_driver
Existing driver test suite Keep flutter_driver until migration is justified
CI on emulators Either (both work, integration_test simpler)

For 2026 projects, default to integration_test. It's faster to run, simpler to set up, and works across all Flutter platforms. Migrate flutter_driver tests when you have time, and keep driver-based tests only where you need Timeline data.

Read more

Start now free