flutter-integration-tests
Flutter Integration Tests
Setup
Add integration_test as a dev dependency:
flutter pub add 'dev:integration_test:{"sdk":"flutter"}'
Required pubspec.yaml entries:
dev_dependencies:
flutter_test:
sdk: flutter
integration_test:
sdk: flutter
Project Structure
my_app/
lib/
main.dart
integration_test/
app_test.dart
feature_a_test.dart
feature_b_test.dart
test_driver/ # Only for web testing and performance profiling
integration_test.dart
perf_driver.dart
- Place all integration tests in
integration_test/. - Only create
test_driver/when targeting web browsers or capturing performance timelines.
Writing Tests
Binding Initialization
Always initialize the binding before any tests:
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:my_app/main.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('description', (tester) async {
await tester.pumpWidget(const MyApp());
await tester.pumpAndSettle();
// assertions
});
}
Widget Identification
Add Key or ValueKey to widgets that tests need to find:
// In production code
FloatingActionButton(
key: const ValueKey('increment'),
onPressed: _incrementCounter,
child: const Icon(Icons.add),
)
// In test code
final fab = find.byKey(const ValueKey('increment'));
await tester.tap(fab);
Prefer find.byKey() over find.text() for stability across localization changes.
Core Test Pattern
testWidgets('tap increment, verify counter updates', (tester) async {
await tester.pumpWidget(const MyApp());
// 1. Verify initial state
expect(find.text('0'), findsOneWidget);
// 2. Perform action
await tester.tap(find.byKey(const ValueKey('increment')));
// 3. Wait for UI to settle
await tester.pumpAndSettle();
// 4. Verify result
expect(find.text('1'), findsOneWidget);
});
Grouping Tests
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('counter', () {
testWidgets('starts at zero', (tester) async { /* ... */ });
testWidgets('increments on tap', (tester) async { /* ... */ });
});
group('navigation', () {
testWidgets('opens settings page', (tester) async { /* ... */ });
});
}
Platform-Specific Patterns
Platform-Conditional Imports
When tests differ between web and native platforms:
// example_test.dart
import '_example_test_io.dart'
if (dart.library.js_interop) '_example_test_web.dart' as tests;
void main() => tests.main();
Create _example_test_io.dart and _example_test_web.dart with platform-specific implementations.
Screenshots
void main() {
final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('capture screenshot', (tester) async {
await tester.pumpWidget(const MyApp());
// Required on Android before taking screenshots
await binding.convertFlutterSurfaceToImage();
await tester.pumpAndSettle();
final bytes = await binding.takeScreenshot('home_screen');
expect(bytes.isNotEmpty, isTrue);
});
}
- Call
convertFlutterSurfaceToImage()before screenshots on Android/iOS. - Web screenshots return empty bytes; handle accordingly.
Golden File Testing
testWidgets('matches golden', (tester) async {
await tester.pumpWidget(const MyApp());
await tester.pumpAndSettle();
await expectLater(
find.byType(MaterialApp),
matchesGoldenFile('goldens/home_screen.png'),
);
});
Performance Profiling
For detailed guidance on performance profiling with traceAction(), timeline recording, and driver setup, see references/performance-profiling.md.
Running Tests
Mobile and Desktop
flutter test integration_test/app_test.dart
Web
- Create
test_driver/integration_test.dart:
import 'package:integration_test/integration_test_driver.dart';
Future<void> main() => integrationDriver();
- Launch ChromeDriver and run:
chromedriver --port=4444
flutter drive \
--driver=test_driver/integration_test.dart \
--target=integration_test/app_test.dart \
-d chrome
Use -d web-server for headless mode.
CI/CD (Linux)
Use xvfb-run for headless display:
xvfb-run flutter test integration_test -d linux
Best Practices
- Initialize binding once -- call
IntegrationTestWidgetsFlutterBinding.ensureInitialized()at the top ofmain(), before anytestWidgets. - Verify initial state -- always assert starting conditions before performing actions.
- Use
pumpAndSettle()after every action -- wait for animations and async operations to complete. - Prefer
find.byKey()overfind.text()-- keys are stable across locales and text changes. - Add
ValueKeyto testable widgets -- plan for testability in production code. - Group related tests -- use
group()for logical organization. - One concern per test -- each
testWidgetsshould verify a single user flow or behavior. - Use
--profilefor performance tests -- never measure performance in debug mode. - Use
--no-ddson mobile devices -- avoids Dart Development Service conflicts during profiling. - Separate platform implementations -- use conditional imports (
_io.dart/_web.dart) when behavior diverges. - Call
convertFlutterSurfaceToImage()on Android -- required before taking screenshots. - Clean up test apps on device -- leftover apps can cause subsequent test failures.
More from victor-teles/skills
vercel-workflow
Best practices for using Vercel Workflow DevKit. Use when creating, modifying, or debugging workflows, steps, hooks, webhooks, or any durable function using the Workflow DevKit. Ensures proper usage of directives, error handling, serialization, streaming, and workflow patterns.
13skill-creator
Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Claude's capabilities with specialized knowledge, workflows, or tool integrations.
2slop-remover
Remove AI-generated code slop and clean up code style
2check-build-errors
Run compile and type-check commands and report failures
2