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: fluttertest/
integration_test/
app_test.dartflutter_driver (Legacy)
dev_dependencies:
flutter_driver:
sdk: flutter
test: ^1.25.0test_driver/
app.dart # app entry with enableFlutterDriverExtension()
app_test.dart # driver testWriting 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.dartCI 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 \
--headlessFor 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.dartflutter_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 viatester.widget<Text>(finder).data- No need for separate
app.dartentry 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.