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

为何Extent Report仅显示最后一个测试套件结果?多套件报告问题

解决Extent Report仅显示最后一个TestNG测试套件结果的问题

这个问题的核心原因很明确:你每个测试套件运行时都会重新初始化ExtentReports和ExtentHtmlReporter实例,并且每个套件结束都执行flush(),导致前一个套件的报告被覆盖,后续套件也无法写入已经关闭的报告流。

下面是具体的修复方案,分步骤来:

1. 用单例模式管理ExtentReports实例

我们需要让所有测试套件共享同一个ExtentReports和ExtentHtmlReporter实例,避免重复初始化覆盖报告。创建一个工具类来管理单例:

public class ExtentManager {
    private static ExtentReports extent;
    private static ExtentHtmlReporter htmlReporter;

    // 线程安全的单例获取方法
    public static synchronized ExtentReports getExtent() {
        return extent;
    }

    // 初始化报告,只执行一次
    public static void initExtent() {
        if (extent == null) {
            htmlReporter = new ExtentHtmlReporter(Utils.getReportDir() + "/report.html");
            htmlReporter.loadXMLConfig(String.valueOf(new File("src/test/java/extentreports/extent-config.xml")));
            
            extent = new ExtentReports();
            extent.attachReporter(htmlReporter);
            
            // 设置系统信息
            extent.setSystemInfo("OS Name", System.getProperty("os.name"));
            extent.setSystemInfo("OS Version", System.getProperty("os.version"));
            extent.setSystemInfo("Java Version", System.getProperty("java.version"));
            extent.setSystemInfo("User Name", System.getProperty("user.name"));
            
            // 配置报告样式
            htmlReporter.config().setChartVisibilityOnOpen(true);
            htmlReporter.config().setDocumentTitle("Automation");
            htmlReporter.config().setReportName("Report");
            htmlReporter.config().setTestViewChartLocation(ChartLocation.TOP);
            htmlReporter.config().setTheme(Theme.DARK);
        }
    }

    // 创建测试用例节点
    public static ExtentTest createTest(String testName) {
        return extent.createTest(testName);
    }
}

2. 用TestNG套件监听器控制报告生命周期

TestNG的@BeforeSuite和@AfterSuite会在每个套件执行时触发,所以我们需要用ISuiteListener来监听所有套件的开始和结束,确保只在第一个套件启动时初始化报告,最后一个套件结束时才flush:

import org.testng.ISuite;
import org.testng.ISuiteListener;
import java.io.File;
import java.util.List;
import java.util.stream.Collectors;

public class ExtentSuiteListener implements ISuiteListener {

    @Override
    public void onStart(ISuite suite) {
        // 仅在首次启动时初始化报告
        ExtentManager.initExtent();
    }

    @Override
    public void onFinish(ISuite suite) {
        // 判断当前是否是最后一个执行的套件
        List<String> suiteFileNames = suite.getXmlSuite().getSuiteFiles().stream()
                .map(filePath -> new File(filePath).getName())
                .collect(Collectors.toList());
        
        String currentSuiteName = suite.getName();
        if (suiteFileNames.indexOf(currentSuiteName) == suiteFileNames.size() - 1) {
            // 最后一个套件执行完毕,flush报告
            ExtentManager.getExtent().flush();
            loginAndroid.quitTest(); // 全局驱动在这里关闭
        }
    }
}

3. 修改测试类代码

去掉原来的@BeforeSuite和@AfterSuite方法,修改测试用例中创建test的方式,同时调整@AfterMethod使用静态的test变量:

import com.aventstack.extentreports.Status;
import com.aventstack.extentreports.markuputils.ExtentColor;
import com.aventstack.extentreports.markuputils.MarkupHelper;
import org.testng.ITestResult;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.Test;
import java.io.IOException;

public class YourTestClass {
    // 测试类中定义静态的ExtentTest变量
    public static ExtentTest test;

    @AfterMethod
    public void getResult(ITestResult result) {
        if (result.getStatus() == ITestResult.FAILURE) {
            test.log(Status.FAIL, MarkupHelper.createLabel(result.getName() + " Test case FAILED due to below issues:", ExtentColor.RED));
            test.fail(result.getThrowable());
            String screenShotPath = MobileActions.Screenshot("Failed");
            try {
                test.addScreenCaptureFromPath(screenShotPath);
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else if (result.getStatus() == ITestResult.SUCCESS) {
            test.log(Status.PASS, MarkupHelper.createLabel(result.getName() + " Test Case PASSED", ExtentColor.GREEN));
        } else {
            test.log(Status.SKIP, MarkupHelper.createLabel(result.getName() + " Test Case SKIPPED", ExtentColor.ORANGE));
            test.skip(result.getThrowable());
        }
    }

    // 测试用例中创建test的方式
    @Test
    public void testAddImageOnCanvas() {
        test = ExtentManager.createTest("Add Image on Canvas");
        // 你的测试逻辑...
    }
}

4. 配置TestNG XML

在主测试套件的XML中添加我们的监听器,确保所有套件都使用这个监听器:

<suite name="All_Test_Suites">
    <listeners>
        <listener class-name="com.your.package.ExtentSuiteListener"/> <!-- 替换成你的监听器类路径 -->
    </listeners>
    <suite-files>
        <suite-file path="Font_Module.xml"></suite-file>
        <suite-file path="Image_Module.xml"></suite-file>
    </suite-files>
</suite>

为什么原来的代码不生效?

  • 重复初始化覆盖报告:每个套件运行时,@BeforeSuite都会重新创建ExtentHtmlReporter,这会清空之前的report.html文件,只保留当前套件的结果。
  • 提前关闭报告流:每个套件结束时的@AfterSuite执行extent.flush(),会关闭报告的写入流,后续套件无法再向报告中添加内容。

通过上面的改造,所有测试套件都会共享同一个报告实例,只有在最后一个套件执行完成后才会flush报告,这样所有套件的结果都会被保留下来。

内容的提问来源于stack exchange,提问作者T.Che

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 09:09:34