flutter-add-integration-test
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseImplementing Flutter Integration Tests
实现Flutter集成测试
Contents
目录
Project Setup and Dependencies
项目设置与依赖
Configure the project to support integration testing and Flutter Driver extensions.
- Add required development dependencies to :
pubspec.yamlbashflutter pub add 'dev:integration_test:{"sdk":"flutter"}' flutter pub add 'dev:flutter_test:{"sdk":"flutter"}' - Enable the Flutter Driver extension in your application entry point (typically or a dedicated
lib/main.dart):lib/main_test.dart- Import .
package:flutter_driver/driver_extension.dart - Call before
enableFlutterDriverExtension();.runApp()
- Import
- Add parameters (e.g.,
Key) to critical widgets in the application code to ensure reliable targeting during tests.ValueKey('login_button')
配置项目以支持集成测试和Flutter Driver扩展。
- 向添加必要的开发依赖:
pubspec.yamlbashflutter pub add 'dev:integration_test:{"sdk":"flutter"}' flutter pub add 'dev:flutter_test:{"sdk":"flutter"}' - 在应用入口(通常是或专用的
lib/main.dart)中启用Flutter Driver扩展:lib/main_test.dart- 导入。
package:flutter_driver/driver_extension.dart - 在之前调用
runApp()。enableFlutterDriverExtension();
- 导入
- 在应用代码中的关键组件上添加参数(例如
Key),以确保测试期间能可靠定位组件。ValueKey('login_button')
Interactive Exploration via MCP
通过MCP进行交互式探索
Use the Dart/Flutter MCP server tools to interactively explore and manipulate the application state before writing static tests.
- Launch: Execute with
launch_appto start the application and acquire the DTD URI.target: "lib/main_test.dart" - Inspect: Execute to discover available
get_widget_trees,Keynodes, and widgetTexts.Type - Interact: Execute ,
tap, andenter_textto simulate user flows.scroll - Wait: Always execute or verify state with
waitForwhen navigating or triggering animations.get_health - Troubleshoot Unmounted Widgets: If a widget is not found in the tree, it may be lazily loaded in a or
SliverList. ExecuteListVieworscrollto force the widget to mount before interacting with it.scrollIntoView
使用Dart/Flutter MCP服务器工具,在编写静态测试前交互式探索和操作应用状态。
- 启动:执行并设置
launch_app以启动应用并获取DTD URI。target: "lib/main_test.dart" - 检查:执行以发现可用的
get_widget_tree、Key节点和组件Text。Type - 交互:执行、
tap和enter_text来模拟用户流程。scroll - 等待:在导航或触发动画时,务必执行或通过
waitFor验证状态。get_health - 排查未挂载组件问题:如果组件未在树中找到,它可能是在或
SliverList中懒加载的。在交互前执行ListView或scroll以强制组件挂载。scrollIntoView
Test Authoring Guidelines
测试编写指南
Structure integration tests using the API paradigm.
flutter_test- Create a dedicated directory at the project root.
integration_test/ - Name all test files using the convention.
<name>_test.dart - Initialize the binding by calling at the start of
IntegrationTestWidgetsFlutterBinding.ensureInitialized();.main() - Load the application UI using .
await tester.pumpWidget(MyApp()); - Trigger frames and wait for animations to complete using after interactions like
await tester.pumpAndSettle();.tester.tap() - Assert widget visibility using or
expect(find.byKey(ValueKey('foo')), findsOneWidget);.findsNothing - Scroll to specific off-screen widgets using .
await tester.scrollUntilVisible(itemFinder, 500.0, scrollable: listFinder);
Conditional Logic for Legacy :
flutter_driver- If maintaining or migrating legacy tests, use
flutter_driver,driver.waitFor(),driver.waitForAbsent(), anddriver.tap()instead of thedriver.scroll()APIs.WidgetTester
遵循 API范式构建集成测试。
flutter_test- 在项目根目录创建专用的目录。
integration_test/ - 所有测试文件使用命名规范。
<name>_test.dart - 在开头调用
main()初始化绑定。IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - 使用加载应用UI。
await tester.pumpWidget(MyApp()); - 在等交互后,使用
tester.tap()触发帧并等待动画完成。await tester.pumpAndSettle(); - 使用或
expect(find.byKey(ValueKey('foo')), findsOneWidget);断言组件可见性。findsNothing - 使用滚动到特定的屏幕外组件。
await tester.scrollUntilVisible(itemFinder, 500.0, scrollable: listFinder);
针对旧版的条件逻辑:
flutter_driver- 如果维护或迁移旧版测试,请使用
flutter_driver、driver.waitFor()、driver.waitForAbsent()和driver.tap()替代driver.scroll()API。WidgetTester
Execution and Profiling
执行与性能分析
Execute tests using the command. Require a host driver script located in that calls .
flutter drivetest_driver/integration_test.dartintegrationDriver()Conditional Execution Targets:
- If testing on Chrome: Launch in a separate terminal, then run:
chromedriver --port=4444flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart -d chrome - If testing headless web: Run with .
-d web-server - If testing on Android (Local): Run .
flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart - If testing on Firebase Test Lab (Android):
- Build debug APK:
flutter build apk --debug - Build test APK:
./gradlew app:assembleAndroidTest - Upload both APKs to the Firebase Test Lab console.
- Build debug APK:
使用命令执行测试。需要位于中的宿主驱动脚本,该脚本调用。
flutter drivetest_driver/integration_test.dartintegrationDriver()条件执行目标:
- 如果在Chrome上测试:在单独终端启动,然后运行:
chromedriver --port=4444flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart -d chrome - 如果测试无头Web:使用运行。
-d web-server - 如果在本地Android上测试:运行。
flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart - 如果在Firebase Test Lab(Android)上测试:
- 构建调试APK:
flutter build apk --debug - 构建测试APK:
./gradlew app:assembleAndroidTest - 将两个APK上传到Firebase Test Lab控制台。
- 构建调试APK:
Workflow: End-to-End Integration Testing
工作流:端到端集成测试
Copy and follow this checklist to implement and verify integration tests.
- Task Progress: Setup
- Add and
integration_testtoflutter_test.pubspec.yaml - Inject into the app entry point.
enableFlutterDriverExtension() - Assign s to target widgets.
ValueKey
- Add
- Task Progress: Exploration
- Run via MCP.
launch_app - Map the widget tree using .
get_widget_tree - Validate interaction paths using MCP tools (,
tap).enter_text
- Run
- Task Progress: Authoring
- Create .
integration_test/app_test.dart - Write test cases using APIs.
WidgetTester - Create with
test_driver/integration_test.dart.integrationDriver()
- Create
- Task Progress: Execution & Feedback Loop
- Run .
flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart - Feedback Loop: Review test output -> If occurs, check for infinite animations -> If widget not found, add
PumpAndSettleTimedOutException-> Re-run test until passing.scrollUntilVisible
- Run
复制并遵循此检查清单以实现和验证集成测试。
- 任务进度:设置
- 向添加
pubspec.yaml和integration_test。flutter_test - 在应用入口注入。
enableFlutterDriverExtension() - 为目标组件分配。
ValueKey
- 向
- 任务进度:探索
- 通过MCP运行。
launch_app - 使用映射组件树。
get_widget_tree - 使用MCP工具(、
tap)验证交互路径。enter_text
- 通过MCP运行
- 任务进度:编写
- 创建。
integration_test/app_test.dart - 使用API编写测试用例。
WidgetTester - 创建包含的
integrationDriver()。test_driver/integration_test.dart
- 创建
- 任务进度:执行与反馈循环
- 运行。
flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart - 反馈循环:查看测试输出 -> 如果出现,检查是否存在无限动画 -> 如果未找到组件,添加
PumpAndSettleTimedOutException-> 重新运行测试直到通过。scrollUntilVisible
- 运行
Examples
示例
Standard Integration Test (integration_test/app_test.dart
)
integration_test/app_test.dart标准集成测试(integration_test/app_test.dart
)
integration_test/app_test.dartdart
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('tap on the floating action button, verify counter', (tester) async {
// Load app widget.
await tester.pumpWidget(const MyApp());
// Verify the counter starts at 0.
expect(find.text('0'), findsOneWidget);
// Find the floating action button to tap on.
final fab = find.byKey(const ValueKey('increment'));
// Emulate a tap on the floating action button.
await tester.tap(fab);
// Trigger a frame and wait for animations.
await tester.pumpAndSettle();
// Verify the counter increments by 1.
expect(find.text('1'), findsOneWidget);
});
});
}dart
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('tap on the floating action button, verify counter', (tester) async {
// Load app widget.
await tester.pumpWidget(const MyApp());
// Verify the counter starts at 0.
expect(find.text('0'), findsOneWidget);
// Find the floating action button to tap on.
final fab = find.byKey(const ValueKey('increment'));
// Emulate a tap on the floating action button.
await tester.tap(fab);
// Trigger a frame and wait for animations.
await tester.pumpAndSettle();
// Verify the counter increments by 1.
expect(find.text('1'), findsOneWidget);
});
});
}Host Driver Script (test_driver/integration_test.dart
)
test_driver/integration_test.dart宿主驱动脚本(test_driver/integration_test.dart
)
test_driver/integration_test.dartdart
import 'package:integration_test/integration_test_driver.dart';
Future<void> main() => integrationDriver();dart
import 'package:integration_test/integration_test_driver.dart';
Future<void> main() => integrationDriver();Performance Profiling Driver Script (test_driver/perf_driver.dart
)
test_driver/perf_driver.dart性能分析驱动脚本(test_driver/perf_driver.dart
)
test_driver/perf_driver.dartUse this driver script if you wrap your test actions in to capture performance metrics.
binding.traceAction()dart
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,
);
}
},
);
}如果您将测试操作包装在中以捕获性能指标,请使用此驱动脚本。
binding.traceAction()dart
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,
);
}
},
);
}