skills/flutter/flutter-add-integration-test/SKILL.md
Configure and run integration tests using the integration_test package with Flutter Driver. Use when testing complete user flows, verifying navigation, or running end-to-end tests on devices or CI.
npx skillsauth add dhruvanbhalara/skills flutter-add-integration-testInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Add required development dependencies to pubspec.yaml:
flutter pub add 'dev:integration_test:{"sdk":"flutter"}'
flutter pub add 'dev:flutter_test:{"sdk":"flutter"}'
Create directory structure:
project_root/
├── integration_test/
│ └── app_test.dart # Test cases
└── test_driver/
└── integration_test.dart # Host driver script
Create the host driver script at test_driver/integration_test.dart:
import 'package:integration_test/integration_test_driver.dart';
Future<void> main() => integrationDriver();
Add ValueKeys to critical widgets in production code for reliable targeting:
FloatingActionButton(
key: const ValueKey('increment_fab'),
onPressed: _increment,
child: const Icon(Icons.add),
)
main() — this replaces the default test binding.tester.pumpWidget(const MyApp()).tester.pumpAndSettle() after every interaction to wait for animations and async operations.expect(find.byKey(ValueKey('foo')), findsOneWidget).tester.scrollUntilVisible(finder, 500.0).import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:my_app/main.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('End-to-end test', () {
testWidgets('complete user flow', (tester) async {
// Load full app
await tester.pumpWidget(const MyApp());
// Interact with widgets
await tester.tap(find.byKey(const ValueKey('login_button')));
await tester.pumpAndSettle();
// Assert navigation happened
expect(find.byType(HomePage), findsOneWidget);
});
});
}
Choose the execution method based on target platform:
flutter test integration_test/
# Terminal 1: Start ChromeDriver
chromedriver --port=4444
# Terminal 2: Run tests
flutter drive \
--driver=test_driver/integration_test.dart \
--target=integration_test/app_test.dart \
-d chrome
flutter drive \
--driver=test_driver/integration_test.dart \
--target=integration_test/app_test.dart \
-d web-server
# 1. Build debug APK
flutter build apk --debug
# 2. Build instrumentation test APK
pushd android && ./gradlew app:assembleAndroidTest && popd
# 3. Upload both APKs to Firebase Test Lab via console or gcloud:
gcloud firebase test android run \
--type instrumentation \
--app build/app/outputs/flutter-apk/app-debug.apk \
--test build/app/outputs/apk/androidTest/debug/app-debug-androidTest.apk
Wrap test actions in binding.traceAction() to capture performance timelines:
void main() {
final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('scrolling performance', (tester) async {
await tester.pumpWidget(const MyApp());
await binding.traceAction(() async {
final listFinder = find.byType(Scrollable);
await tester.fling(listFinder, const Offset(0, -500), 10000);
await tester.pumpAndSettle();
}, reportKey: 'scrolling_timeline');
});
}
Use this driver to capture and write timeline data:
import 'package:flutter_driver/flutter_driver.dart' as driver;
import 'package:integration_test/integration_test_driver.dart';
Future<void> main() {
return integrationDriver(
responseDataCallback: (data) async {
if (data != null) {
final timeline = driver.Timeline.fromJson(
data['scrolling_timeline'] as Map<String, dynamic>,
);
final summary = driver.TimelineSummary.summarize(timeline);
await summary.writeTimelineToFile(
'scrolling_timeline',
pretty: true,
includeSummary: true,
);
}
},
);
}
- name: Run integration tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 33
script: flutter test integration_test/ --flavor dev
reactivecircus/android-emulator-runner for Android emulator.actions/upload-artifact.chromedriver as a service and test with -d chrome.| Error | Cause | Fix |
|---|---|---|
| PumpAndSettleTimedOutException | Infinite animation (e.g., CircularProgressIndicator) | Use pump() instead, or dismiss the loading state |
| Widget not found | Lazy-loaded in SliverList or ListView | Call scrollUntilVisible() before interacting |
| Test hangs | Network call in production code | Mock HTTP client or use --dart-define to bypass |
| No host driver specified | Missing test_driver/integration_test.dart | Create the host driver file |
integration_test and flutter_test to dev_dependencies.ValueKeys to target widgets in production code.integration_test/app_test.dart with binding initialization.test_driver/integration_test.dart with integrationDriver().flutter test integration_test/flutter drive ... -d chromePumpAndSettleTimedOutException → check for infinite animations.scrollUntilVisible.import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:my_app/main.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('Counter app', () {
testWidgets('tap FAB, verify counter increments', (tester) async {
await tester.pumpWidget(const MyApp());
// Verify initial state
expect(find.text('0'), findsOneWidget);
// Tap the increment button
final fab = find.byKey(const ValueKey('increment_fab'));
await tester.tap(fab);
await tester.pumpAndSettle();
// Verify counter incremented
expect(find.text('1'), findsOneWidget);
});
});
}
testWidgets('login and navigate to home', (tester) async {
await tester.pumpWidget(const MyApp());
// Enter credentials
await tester.enterText(find.byKey(const ValueKey('email_field')), '[email protected]');
await tester.enterText(find.byKey(const ValueKey('password_field')), 'password123');
// Submit login
await tester.tap(find.byKey(const ValueKey('login_button')));
await tester.pumpAndSettle();
// Verify navigation to home
expect(find.byType(HomePage), findsOneWidget);
expect(find.byType(LoginPage), findsNothing);
});
development
Perform REST API networking operations (GET, POST, PUT, DELETE) using the lightweight and robust standard `http` package, including platform configurations and background parsing models.
development
Configure internationalization and localization support using Flutter's built-in l10n system, App Resource Bundle (ARB) files, and ICU formatting syntax.
development
Create model classes with fromJson/toJson using dart:convert and Dart 3 pattern matching. Use when manually mapping JSON to classes, parsing HTTP responses, or choosing between manual and code-generated serialization.
data-ai
Diagnose and fix Flutter layout constraint violations (RenderFlex overflow, unbounded height/width, ParentData misuse). Use when encountering layout exceptions, yellow-black overflow stripes, or red error screens.