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

如何在JUnit5 Playwright测试中仅失败时保留.har文件?

JUnit5 + Playwright 仅在测试失败时保留HAR文件

问题背景

我在用JUnit5写Playwright测试,需求是仅当测试因断言失败、超时等情况失败时保存.har文件,测试通过则自动丢弃。现在的实现是在@BeforeEach里启动HAR录制,但不管测试结果如何都会保留文件。试过在断言后用File.delete()删除,但这种方法不够优雅,也没找到停止HAR生成的相关文档。

当前代码:

@Test
public void myTest() {
    try {
        // .har文件已在录制
        navigateToSomePages();
        assertThat(page.locator(cssSelector)).hasText("allGood");
    } catch (AssertionFailedError | TimeoutError error) {
        // 不管测试结果如何,.har文件都会保留,只想在失败/断言抛出时保留
        doScreenShot();
        throw error;
    }
}

@BeforeEach
// 每个测试前启动HAR录制
protected void beforeEachAbstractMethod() {
    context = browser.newContext(new Browser.NewContextOptions()
            .setRecordHarPath(Paths.get("file.har"))
            .setRecordHarMode(HarMode.MINIMAL));
}

Playwright HAR录制核心规则(翻译自官方文档)

  • HAR录制与浏览器上下文绑定,只有当上下文关闭时,才会将录制的内容写入指定路径的文件;
  • 可通过setRecordHarPath指定HAR文件路径,setRecordHarMode控制录制内容的详细程度(MINIMAL模式仅记录请求URL、状态码和响应头等关键信息);
  • 若未指定recordHarPath,HAR内容会暂存于内存,可通过context.har()获取,但不会写入本地文件。

优化方案

方案一:利用JUnit5 TestWatcher扩展(推荐)

通过JUnit5的测试生命周期监听接口,统一控制所有测试用例的HAR文件保留逻辑,无需在每个测试中重复编写代码。

完整实现代码

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.TestWatcher;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.assertj.core.api.Assertions;
import com.microsoft.playwright.Browser;
import com.microsoft.playwright.BrowserContext;
import com.microsoft.playwright.HarMode;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;

@ExtendWith(HARRetentionExtension.class)
public class PlaywrightHARTest {
    private Browser browser;
    private BrowserContext context;
    private Path tempHarPath;
    // 失败测试HAR文件保存目录
    private static final Path FAILED_HAR_DIR = Paths.get("failed-tests-har");

    static {
        // 初始化保存目录,不存在则创建
        FAILED_HAR_DIR.toFile().mkdirs();
    }

    @BeforeEach
    void setupTest() {
        // 生成唯一临时HAR路径,避免并行测试文件冲突
        tempHarPath = Paths.get(System.getProperty("java.io.tmpdir"), 
                                "test-har-" + System.currentTimeMillis() + ".har");
        // 启动HAR录制到临时文件
        context = browser.newContext(new Browser.NewContextOptions()
                .setRecordHarPath(tempHarPath)
                .setRecordHarMode(HarMode.MINIMAL));
    }

    @AfterEach
    void teardownTest() {
        // 关闭上下文,触发HAR内容写入临时文件
        if (context != null) {
            context.close();
        }
    }

    @Test
    void testPageContent() {
        navigateToTargetPage();
        Assertions.assertThat(page.locator("#content")).hasText("Expected Content");
    }

    // 自定义JUnit5扩展,监听测试结果处理HAR文件
    public static class HARRetentionExtension implements TestWatcher {
        @Override
        public void testFailed(ExtensionContext context, Throwable cause) {
            PlaywrightHARTest testInstance = context.getTestInstance(PlaywrightHARTest.class).get();
            // 测试失败:将临时HAR文件移动到保存目录,用测试类+方法名命名
            Path targetPath = FAILED_HAR_DIR.resolve(
                context.getTestClass().get().getSimpleName() + 
                "-" + context.getTestMethod().get().getName() + ".har"
            );
            try {
                Files.move(testInstance.tempHarPath, targetPath);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void testSuccessful(ExtensionContext context) {
            PlaywrightHARTest testInstance = context.getTestInstance(PlaywrightHARTest.class).get();
            // 测试成功:删除临时HAR文件
            try {
                Files.deleteIfExists(testInstance.tempHarPath);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    // 页面导航辅助方法
    private void navigateToTargetPage() {
        // 实现页面跳转逻辑
    }
}

核心优势

  • 统一管理:所有测试的HAR文件逻辑集中在扩展中,无需重复编写;
  • 避免冲突:用时间戳生成临时文件,解决并行测试时的文件覆盖问题;
  • 优雅可控:通过官方生命周期接口监听测试状态,逻辑清晰。

方案二:测试内finally块处理(轻量版)

如果不想编写扩展,可在单个测试方法中通过finally块处理,适合少量测试的场景:

@Test
public void myTest() {
    boolean testSuccess = true;
    try {
        navigateToSomePages();
        assertThat(page.locator(cssSelector)).hasText("allGood");
    } catch (AssertionFailedError | TimeoutError error) {
        doScreenShot();
        testSuccess = false;
        throw error;
    } finally {
        // 关闭上下文,确保HAR文件写入
        context.close();
        if (testSuccess) {
            // 测试通过,删除HAR文件
            try {
                Files.deleteIfExists(Paths.get("file.har"));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 03:29:54