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

Java Selenium+Cucumber下Extent Report生成后发送邮件解决方案

问题背景

当前技术栈为Java Selenium + Cucumber测试框架,基于extent properties配置与Spark模板生成Extent Report测试报告。
存在的时序问题:Extent Report完整写入磁盘需要约1分30秒,但配置的邮件发送逻辑在@After钩子中会提前触发,此时报告尚未生成完成,发送的附件是残缺文件。之前通过多处添加Thread.sleep()强制等待的方案无法适配生成时间波动,仍然存在时序竞争问题。
原有实现代码如下:

@After
public void endTest(Scenario scenario) throws IOException, InterruptedException, AddressException, InvalidFormatException,
ParseException, MessagingException {
    if (scenario.isFailed()) {

        try {
            
            //screenshot
            File sourcePath=    ((TakesScreenshot)base.driver).getScreenshotAs(OutputType.FILE);
            byte[] fileContent = FileUtils.readFileToByteArray(sourcePath);
            scenario.attach(fileContent, "image/png", "image"); // ... and embed it in
        } catch (WebDriverException e) {
            e.printStackTrace();
        }

    } else {
        try {
            File sourcePath=    ((TakesScreenshot)base.driver).getScreenshotAs(OutputType.FILE);
            byte[] fileContent = FileUtils.readFileToByteArray(sourcePath);
            scenario.attach(fileContent, "image/png", "image"); 
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    Thread.sleep(30000);

    
    String zip = Zip.zip(new File("./Report"));

    BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream("C:\\Project\\Contract\\Report.zip"), 10000);
    byte[] decode = Base64.getDecoder().decode(zip);
    stream.write(decode);
    stream.close();

    Thread.sleep(10000);

    Automail automail = new Automail();
    automail.Gen_Reports();
    
    Thread.sleep(30000);

    if(base.driver!=null)
    {
    base.driver.quit();
    }
}
问题根因
  • 硬编码Thread.sleep()属于不可靠的同步方案,测试用例数量变化、机器IO性能波动、磁盘负载高低都会改变Extent Report的实际生成时间,固定等待时长要么等待不足拿到残缺文件,要么等待过久拖慢测试执行效率
  • 核心逻辑放错了钩子位置:Cucumber的@After是每个测试场景执行完成后立即触发,而Extent Report的Spark模板是在所有测试场景执行完毕、调用extent.flush()后才会开始全量写入磁盘,在单场景后置钩子中等待,永远无法对齐整个测试套件的报告生成时机。
可靠实现方案

1. 拆分钩子职责

将不同生命周期的逻辑拆分到对应钩子中,不要全部堆在单场景@After里:

  • 单场景@After:仅负责场景截图挂载、WebDriver资源释放
  • 全局套件后置钩子:Cucumber搭配JUnit5时用@AfterAll、搭配JUnit4时用@AfterClass、搭配TestNG时用@AfterSuite,在这个钩子里处理Extent Report flush、报告生成状态检测、压缩、邮件发送逻辑,这个钩子会在所有测试场景全部执行完成后才触发,时机完全匹配报告生成流程。

2. 用文件状态轮询替代硬编码等待

实现轮询检测逻辑,只有同时满足三个条件才判定报告写入完成:

  • Spark模板生成的报告入口文件(一般为./Report/index.html)存在
  • 文件大小连续3次轮询无变化,排除IO写入过程中的波动
  • 文件可被正常读取,确认没有被写入进程加锁占用
    同时设置最大超时时间避免无限等待,超时直接抛出异常告警。

3. 修正后完整代码

首先实现文件写入完成检测的工具方法:

/**
 * 等待目标文件写入完成
 * @param reportFile 要检测的报告入口文件
 * @param timeoutMs 最大等待超时时间,单位毫秒
 * @param pollIntervalMs 轮询检测间隔,单位毫秒
 * @return 写入完成返回true,超时返回false
 */
private static boolean waitForFileWriteComplete(File reportFile, long timeoutMs, long pollIntervalMs) throws InterruptedException {
    long startTime = System.currentTimeMillis();
    long lastFileSize = -1;
    int stableCheckCount = 0;
    // 连续3次检测文件大小无变化,才判定写入稳定
    int requiredStableTimes = 3;

    while (System.currentTimeMillis() - startTime < timeoutMs) {
        if (reportFile.exists() && reportFile.isFile()) {
            long currentSize = reportFile.length();
            if (currentSize == lastFileSize) {
                stableCheckCount++;
                if (stableCheckCount >= requiredStableTimes) {
                    // 验证文件可正常读取,无写入锁占用
                    try (FileInputStream ignored = new FileInputStream(reportFile)) {
                        return true;
                    } catch (IOException e) {
                        // 文件被锁定,重置稳定计数重新检测
                        stableCheckCount = 0;
                    }
                }
            } else {
                lastFileSize = currentSize;
                stableCheckCount = 0;
            }
        }
        Thread.sleep(pollIntervalMs);
    }
    return false;
}

精简后的单场景@After钩子,移除所有硬编码等待和报告处理逻辑:

@After
public void endTest(Scenario scenario) {
    // 统一处理截图,无需拆分失败/成功分支
    try {
        if (base.driver != null) {
            File sourcePath = ((TakesScreenshot)base.driver).getScreenshotAs(OutputType.FILE);
            byte[] fileContent = FileUtils.readFileToByteArray(sourcePath);
            scenario.attach(fileContent, "image/png", "test_screenshot");
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        // 及时释放WebDriver资源
        if(base.driver != null) {
            base.driver.quit();
        }
    }
}

全局后置钩子处理报告、压缩、发邮件逻辑:

@AfterAll
public static void afterAllTests() throws Exception {
    // 1. 手动触发Extent Report全量写入,替换为你项目中实际的ExtentReports实例
    extent.flush();

    // 2. 等待报告完全写入,最大等待3分钟,每2秒轮询一次
    File sparkIndexFile = new File("./Report/index.html");
    boolean reportReady = waitForFileWriteComplete(sparkIndexFile, 180000, 2000);
    if (!reportReady) {
        throw new RuntimeException("测试报告生成超时,超过3分钟未完成写入");
    }

    // 3. 报告就绪后执行压缩
    String zipBase64 = Zip.zip(new File("./Report"));
    try (BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream("C:\\Project\\Contract\\Report.zip"), 10000)) {
        byte[] decode = Base64.getDecoder().decode(zipBase64);
        stream.write(decode);
    }

    // 4. 压缩完成后触发邮件发送
    Automail automail = new Automail();
    automail.Gen_Reports();
}
注意事项
  • 不要在单场景@After钩子中调用extent.flush(),反复flush会大幅拖慢报告生成速度,仅在全局后置钩子中调用一次即可
  • Windows环境下文件写入时默认加独占锁,必须保留文件可读取校验逻辑,否则可能出现文件大小稳定但仍被占用,压缩包损坏的问题
  • 最大超时时间建议设置为日常报告生成耗时的2倍,比如日常1分30秒生成就设3分钟,足够覆盖偶发的IO波动场景

内容的提问来源于stack exchange,提问作者gaurav kumar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 05:06:21