package com.company.tests.base; import com.company.tests.config.ConfigReader; import com.microsoft.playwright.*; import com.microsoft.playwright.options.LoadState; import io.qameta.allure.Allure; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.TestInfo; import java.io.ByteArrayInputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; /** * Thread-safe BaseTest for parallel Playwright execution. * Handles Playwright/Browser/Context/Page lifecycle. * Captures traces and screenshots on test completion. */ public abstract class BaseTest { private static final ThreadLocal playwrightTL = new ThreadLocal<>(); private static final ThreadLocal browserTL = new ThreadLocal<>(); private static final ThreadLocal contextTL = new ThreadLocal<>(); private static final ThreadLocal pageTL = new ThreadLocal<>(); /** Access the Page in test/page-object classes */ protected Page page() { return pageTL.get(); } @BeforeEach void setUpPlaywright(TestInfo testInfo) throws Exception { // Ensure trace/video output directories exist Files.createDirectories(Paths.get("target/traces")); Files.createDirectories(Paths.get("target/videos")); Playwright playwright = Playwright.create(); playwrightTL.set(playwright); BrowserType.LaunchOptions launchOptions = new BrowserType.LaunchOptions() .setHeadless(ConfigReader.isHeadless()) .setSlowMo(Integer.parseInt(System.getProperty("slowMo", "0"))); Browser browser = resolveBrowser(playwright).launch(launchOptions); browserTL.set(browser); Browser.NewContextOptions contextOptions = new Browser.NewContextOptions() .setViewportSize(1920, 1080) .setLocale("en-US") .setTimezoneId("Asia/Kolkata") .setRecordVideoDir(Paths.get("target/videos/")); // Load saved auth state if available Path authState = Paths.get("target/auth/user-state.json"); if (Files.exists(authState)) { contextOptions.setStorageStatePath(authState); } BrowserContext context = browser.newContext(contextOptions); context.setDefaultTimeout(ConfigReader.getDefaultTimeout()); context.setDefaultNavigationTimeout(60_000); // Start tracing context.tracing().start(new Tracing.StartOptions() .setScreenshots(true) .setSnapshots(true) .setSources(true)); contextTL.set(context); Page page = context.newPage(); pageTL.set(page); } @AfterEach void tearDownPlaywright(TestInfo testInfo) { String safeName = testInfo.getDisplayName() .replaceAll("[^a-zA-Z0-9._-]", "_") .substring(0, Math.min(80, testInfo.getDisplayName().length())); try { // Capture screenshot for Allure if (pageTL.get() != null) { byte[] screenshot = pageTL.get().screenshot( new Page.ScreenshotOptions().setFullPage(true)); Allure.addAttachment("Final Screenshot", "image/png", new ByteArrayInputStream(screenshot), "png"); } } catch (Exception ignored) {} try { // Stop and save trace if (contextTL.get() != null) { contextTL.get().tracing().stop(new Tracing.StopOptions() .setPath(Paths.get("target/traces/" + safeName + ".zip"))); } } catch (Exception ignored) {} // Close resources in order closeQuietly(pageTL.get()); closeQuietly(contextTL.get()); closeQuietly(browserTL.get()); closeQuietly(playwrightTL.get()); pageTL.remove(); contextTL.remove(); browserTL.remove(); playwrightTL.remove(); } private BrowserType resolveBrowser(Playwright playwright) { return switch (System.getProperty("browser", "chromium").toLowerCase()) { case "firefox" -> playwright.firefox(); case "webkit" -> playwright.webkit(); default -> playwright.chromium(); }; } private void closeQuietly(AutoCloseable closeable) { if (closeable != null) { try { closeable.close(); } catch (Exception ignored) {} } } }