You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 17:36:03