main
md 163 lines 7.17 KB
Rendered Raw
1 ---
2 name: flutter-add-integration-test
3 description: Configures Flutter Driver for app interaction and converts MCP actions into permanent integration tests. Use when adding integration testing to a project, exploring UI components via MCP, or automating user flows with the integration_test package.
4 metadata:
5 model: models/gemini-3.1-pro-preview
6 last_modified: Tue, 21 Apr 2026 18:29:20 GMT
7 ---
8 # Implementing Flutter Integration Tests
9
10 ## Contents
11 - [Project Setup and Dependencies](#project-setup-and-dependencies)
12 - [Interactive Exploration via MCP](#interactive-exploration-via-mcp)
13 - [Test Authoring Guidelines](#test-authoring-guidelines)
14 - [Execution and Profiling](#execution-and-profiling)
15 - [Workflow: End-to-End Integration Testing](#workflow-end-to-end-integration-testing)
16 - [Examples](#examples)
17
18 ## Project Setup and Dependencies
19
20 Configure the project to support integration testing and Flutter Driver extensions.
21
22 1. Add required development dependencies to `pubspec.yaml`:
23 ```bash
24 flutter pub add 'dev:integration_test:{"sdk":"flutter"}'
25 flutter pub add 'dev:flutter_test:{"sdk":"flutter"}'
26 ```
27 2. Enable the Flutter Driver extension in your application entry point (typically `lib/main.dart` or a dedicated `lib/main_test.dart`):
28 - Import `package:flutter_driver/driver_extension.dart`.
29 - Call `enableFlutterDriverExtension();` before `runApp()`.
30 3. Add `Key` parameters (e.g., `ValueKey('login_button')`) to critical widgets in the application code to ensure reliable targeting during tests.
31
32 ## Interactive Exploration via MCP
33
34 Use the Dart/Flutter MCP server tools to interactively explore and manipulate the application state before writing static tests.
35
36 - **Launch**: Execute `launch_app` with `target: "lib/main_test.dart"` to start the application and acquire the DTD URI.
37 - **Inspect**: Execute `get_widget_tree` to discover available `Key`s, `Text` nodes, and widget `Type`s.
38 - **Interact**: Execute `tap`, `enter_text`, and `scroll` to simulate user flows.
39 - **Wait**: Always execute `waitFor` or verify state with `get_health` when navigating or triggering animations.
40 - **Troubleshoot Unmounted Widgets**: If a widget is not found in the tree, it may be lazily loaded in a `SliverList` or `ListView`. Execute `scroll` or `scrollIntoView` to force the widget to mount before interacting with it.
41
42 ## Test Authoring Guidelines
43
44 Structure integration tests using the `flutter_test` API paradigm.
45
46 - Create a dedicated `integration_test/` directory at the project root.
47 - Name all test files using the `<name>_test.dart` convention.
48 - Initialize the binding by calling `IntegrationTestWidgetsFlutterBinding.ensureInitialized();` at the start of `main()`.
49 - Load the application UI using `await tester.pumpWidget(MyApp());`.
50 - Trigger frames and wait for animations to complete using `await tester.pumpAndSettle();` after interactions like `tester.tap()`.
51 - Assert widget visibility using `expect(find.byKey(ValueKey('foo')), findsOneWidget);` or `findsNothing`.
52 - Scroll to specific off-screen widgets using `await tester.scrollUntilVisible(itemFinder, 500.0, scrollable: listFinder);`.
53
54 **Conditional Logic for Legacy `flutter_driver`:**
55 - If maintaining or migrating legacy `flutter_driver` tests, use `driver.waitFor()`, `driver.waitForAbsent()`, `driver.tap()`, and `driver.scroll()` instead of the `WidgetTester` APIs.
56
57 ## Execution and Profiling
58
59 Execute tests using the `flutter drive` command. Require a host driver script located in `test_driver/integration_test.dart` that calls `integrationDriver()`.
60
61 **Conditional Execution Targets:**
62 - **If testing on Chrome:** Launch `chromedriver --port=4444` in a separate terminal, then run:
63 `flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart -d chrome`
64 - **If testing headless web:** Run with `-d web-server`.
65 - **If testing on Android (Local):** Run `flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart`.
66 - **If testing on Firebase Test Lab (Android):**
67 1. Build debug APK: `flutter build apk --debug`
68 2. Build test APK: `./gradlew app:assembleAndroidTest`
69 3. Upload both APKs to the Firebase Test Lab console.
70
71 ## Workflow: End-to-End Integration Testing
72
73 Copy and follow this checklist to implement and verify integration tests.
74
75 - [ ] **Task Progress: Setup**
76 - [ ] Add `integration_test` and `flutter_test` to `pubspec.yaml`.
77 - [ ] Inject `enableFlutterDriverExtension()` into the app entry point.
78 - [ ] Assign `ValueKey`s to target widgets.
79 - [ ] **Task Progress: Exploration**
80 - [ ] Run `launch_app` via MCP.
81 - [ ] Map the widget tree using `get_widget_tree`.
82 - [ ] Validate interaction paths using MCP tools (`tap`, `enter_text`).
83 - [ ] **Task Progress: Authoring**
84 - [ ] Create `integration_test/app_test.dart`.
85 - [ ] Write test cases using `WidgetTester` APIs.
86 - [ ] Create `test_driver/integration_test.dart` with `integrationDriver()`.
87 - [ ] **Task Progress: Execution & Feedback Loop**
88 - [ ] Run `flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart`.
89 - [ ] **Feedback Loop**: Review test output -> If `PumpAndSettleTimedOutException` occurs, check for infinite animations -> If widget not found, add `scrollUntilVisible` -> Re-run test until passing.
90
91 ## Examples
92
93 ### Standard Integration Test (`integration_test/app_test.dart`)
94
95 ```dart
96 import 'package:flutter/material.dart';
97 import 'package:flutter_test/flutter_test.dart';
98 import 'package:integration_test/integration_test.dart';
99 import 'package:my_app/main.dart';
100
101 void main() {
102 IntegrationTestWidgetsFlutterBinding.ensureInitialized();
103
104 group('End-to-end test', () {
105 testWidgets('tap on the floating action button, verify counter', (tester) async {
106 // Load app widget.
107 await tester.pumpWidget(const MyApp());
108
109 // Verify the counter starts at 0.
110 expect(find.text('0'), findsOneWidget);
111
112 // Find the floating action button to tap on.
113 final fab = find.byKey(const ValueKey('increment'));
114
115 // Emulate a tap on the floating action button.
116 await tester.tap(fab);
117
118 // Trigger a frame and wait for animations.
119 await tester.pumpAndSettle();
120
121 // Verify the counter increments by 1.
122 expect(find.text('1'), findsOneWidget);
123 });
124 });
125 }
126 ```
127
128 ### Host Driver Script (`test_driver/integration_test.dart`)
129
130 ```dart
131 import 'package:integration_test/integration_test_driver.dart';
132
133 Future<void> main() => integrationDriver();
134 ```
135
136 ### Performance Profiling Driver Script (`test_driver/perf_driver.dart`)
137
138 Use this driver script if you wrap your test actions in `binding.traceAction()` to capture performance metrics.
139
140 ```dart
141 import 'package:flutter_driver/flutter_driver.dart' as driver;
142 import 'package:integration_test/integration_test_driver.dart';
143
144 Future<void> main() {
145 return integrationDriver(
146 responseDataCallback: (data) async {
147 if (data != null) {
148 final timeline = driver.Timeline.fromJson(
149 data['scrolling_timeline'] as Map<String, dynamic>,
150 );
151
152 final summary = driver.TimelineSummary.summarize(timeline);
153
154 await summary.writeTimelineToFile(
155 'scrolling_timeline',
156 pretty: true,
157 includeSummary: true,
158 );
159 }
160 },
161 );
162 }
163 ```