如何在Flutter中生成类似Selenium Extent Report的自动化测试报告
Flutter Integration Tests 类Extent Report测试报告实现方案
选型说明
- 已废弃的Flutter Driver无需考虑,直接使用官方当前主推的
integration_test包,和Flutter SDK原生集成,维护性更高 - 官方没有现成的Extent Report风格导出工具,可通过自定义测试监听、结合
test包的生命周期回调实现步骤记录、状态统计,最终生成可视化HTML/JSON报告
核心实现步骤
1. 引入依赖
在pubspec.yaml中添加必要依赖:
dev_dependencies: flutter_test: sdk: flutter integration_test: sdk: flutter test: ^1.24.0 # 用于访问测试生命周期API
2. 自定义测试报告记录器
实现单例类统一管理测试用例、步骤的状态数据:
class ExtentStyleReporter { static final ExtentStyleReporter _instance = ExtentStyleReporter._internal(); factory ExtentStyleReporter() => _instance; ExtentStyleReporter._internal(); // 存储全量测试用例数据 final List<Map<String, dynamic>> _testCases = []; // 当前执行的测试用例 Map<String, dynamic>? _currentTestCase; // 当前测试用例的步骤列表 List<Map<String, dynamic>> _currentSteps = []; // 启动新测试用例 void startTestCase(String testName) { _currentTestCase = { 'name': testName, 'status': 'running', 'startTime': DateTime.now().toIso8601String(), 'steps': [] }; _currentSteps = []; } // 记录测试步骤 void logStep(String stepDescription, {bool isPassed = true, String? errorMsg}) { _currentSteps.add({ 'description': stepDescription, 'status': isPassed ? 'pass' : 'fail', 'error': errorMsg, 'time': DateTime.now().toIso8601String() }); } // 结束测试用例 void endTestCase(bool isPassed, {String? failureReason}) { _currentTestCase?['status'] = isPassed ? 'pass' : 'fail'; _currentTestCase?['endTime'] = DateTime.now().toIso8601String(); _currentTestCase?['failureReason'] = failureReason; _currentTestCase?['steps'] = _currentSteps; _testCases.add(_currentTestCase!); _currentTestCase = null; _currentSteps = []; } // 生成Extent风格HTML报告,可自定义CSS样式匹配Extent Report的视觉效果 String generateHtmlReport() { final passCount = _testCases.where((tc) => tc['status'] == 'pass').length; final totalCount = _testCases.length; // 可自行扩展HTML结构,添加通过率饼图、用例耗时统计、失败截图预览等功能 return '''<html> <head> <title>Flutter Integration Test Report</title> <!-- 自定义Extent Report风格CSS --> <style> .pass {color: #00c851;} .fail {color: #ff4444;} .test-case {margin: 16px 0; padding: 12px; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);} .step {margin: 8px 0 0 24px;} </style> </head> <body> <h1>测试总览:通过率 ${(passCount/totalCount*100).toStringAsFixed(2)}% | 总用例数 $totalCount | 成功 $passCount | 失败 ${totalCount-passCount}</h1> ${_testCases.map((tc) => ''' <div class="test-case ${tc['status']}"> <h3>${tc['name']} | 状态:${tc['status'] == 'pass' ? '通过' : '失败'}</h3> <p>开始时间:${tc['startTime']} | 结束时间:${tc['endTime']}</p> ${tc['status'] == 'fail' ? '<p>失败原因:${tc['failureReason']}</p>' : ''} <div class="steps"> ${tc['steps'].map((step) => '<p class="step ${step['status']}">${step['time']} | ${step['description']} | ${step['status'] == 'pass' ? '通过' : '失败'}${step['error'] != null ? ' | 错误:${step['error']}' : ''}</p>').join('')} </div> </div> ''').join('')} </body></html>'''; } }
3. 集成到测试逻辑
在测试文件中对接测试生命周期,自动记录状态:
import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'dart:io'; // 用于保存报告到本地 void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); final reporter = ExtentStyleReporter(); tearDownAll(() async { // 所有测试完成后生成并保存报告 final htmlContent = reporter.generateHtmlReport(); // 可自定义保存路径,Android可存外部存储,iOS可存沙箱目录 final reportFile = File('/storage/emulated/0/test_report.html'); await reportFile.writeAsString(htmlContent); }); group('登录模块测试', () { testWidgets('正确账号密码登录成功', (tester) async { reporter.startTestCase('正确账号密码登录成功'); try { // 步骤1:启动应用进入登录页 reporter.logStep('启动应用,等待页面加载完成'); await tester.pumpAndSettle(); // 步骤2:输入账号 reporter.logStep('输入测试账号 test_user'); await tester.enterText(find.byKey(const Key('username_input')), 'test_user'); // 步骤3:输入密码 reporter.logStep('输入测试密码 123456'); await tester.enterText(find.byKey(const Key('password_input')), '123456'); // 步骤4:点击登录 reporter.logStep('点击登录按钮'); await tester.tap(find.byKey(const Key('login_btn'))); await tester.pumpAndSettle(); // 结果校验 expect(find.byKey(const Key('home_page')), findsOneWidget); reporter.logStep('校验首页元素存在,登录成功'); reporter.endTestCase(true); } catch (e) { // 失败时记录错误 reporter.logStep('测试执行异常', isPassed: false, errorMsg: e.toString()); // 可选:失败时截图保存到本地,将截图路径写入报告可实现点击预览 // final screenshot = await tester.takeScreenshot(); // 保存截图逻辑... reporter.endTestCase(false, failureReason: e.toString()); rethrow; } }); }); }
效果说明
- 最终生成的报告和Extent Report能力完全一致:支持展示每个测试用例的执行状态、耗时、失败原因,点击用例可展开查看每一步的执行状态、错误详情
- 可根据需求扩展支持失败截图预览、多环境标识、用例分类筛选等功能
内容的提问来源于stack exchange,提问作者Abhijit Biswas
相关产品推荐
相关产品推荐

