diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 18a843b2..384d24d2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -81,7 +81,7 @@ jobs: - name: Install MS Edge if: matrix.browser-channel == 'msedge' && matrix.os == 'macos-latest' shell: bash - run: mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install msedge" -f playwright/pom.xml + run: mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install msedge" -f playwright/pom.xml - name: Run tests run: mvn test --no-transfer-progress --fail-at-end -D org.slf4j.simpleLogger.showDateTime=true -D org.slf4j.simpleLogger.dateTimeFormat=HH:mm:ss env: diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/junit/BrowserContextExtension.java b/playwright/src/main/java/com/microsoft/playwright/impl/junit/BrowserContextExtension.java index 312af7c7..4a58f364 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/junit/BrowserContextExtension.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/junit/BrowserContextExtension.java @@ -16,28 +16,22 @@ package com.microsoft.playwright.impl.junit; -import com.microsoft.playwright.Browser; -import com.microsoft.playwright.BrowserContext; -import com.microsoft.playwright.Playwright; -import com.microsoft.playwright.PlaywrightException; +import com.microsoft.playwright.*; import com.microsoft.playwright.impl.Utils; import com.microsoft.playwright.junit.Options; import org.junit.jupiter.api.extension.*; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + import static com.microsoft.playwright.impl.junit.ExtensionUtils.*; +import static com.microsoft.playwright.impl.junit.PageExtension.cleanUpPage; -public class BrowserContextExtension implements ParameterResolver, AfterEachCallback { +public class BrowserContextExtension implements ParameterResolver, TestWatcher { private static final ThreadLocal threadLocalBrowserContext = new ThreadLocal<>(); - @Override - public void afterEach(ExtensionContext extensionContext) { - BrowserContext browserContext = threadLocalBrowserContext.get(); - threadLocalBrowserContext.remove(); - if (browserContext != null) { - browserContext.close(); - } - } - @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { return !isClassHook(extensionContext) && isParameterSupported(parameterContext, extensionContext, BrowserContext.class); @@ -51,6 +45,7 @@ public class BrowserContextExtension implements ParameterResolver, AfterEachCall /** * Returns the BrowserContext that belongs to the current test. Will be created if it doesn't already exist. * NOTE: this method is subject to change. + * * @param extensionContext the context in which the current test or container is being executed. * @return The BrowserContext that belongs to the current test. */ @@ -66,10 +61,97 @@ public class BrowserContextExtension implements ParameterResolver, AfterEachCall Browser browser = BrowserExtension.getOrCreateBrowser(extensionContext); Browser.NewContextOptions contextOptions = getContextOptions(playwright, options); browserContext = browser.newContext(contextOptions); + if (shouldRecordTrace(options)) { + Tracing.StartOptions startOptions = new Tracing.StartOptions().setSnapshots(true).setScreenshots(true).setTitle(extensionContext.getDisplayName()); + if (System.getenv("PLAYWRIGHT_JAVA_SRC") != null) { + startOptions.setSources(true); + } + browserContext.tracing().start(startOptions); + } threadLocalBrowserContext.set(browserContext); return browserContext; } + @Override + public void testSuccessful(ExtensionContext extensionContext) { + saveTraceWhenOn(extensionContext); + closeBrowserContext(); + } + + @Override + public void testAborted(ExtensionContext extensionContext, Throwable cause) { + saveTraceWhenOn(extensionContext); + closeBrowserContext(); + } + + @Override + public void testFailed(ExtensionContext extensionContext, Throwable cause) { + Options options = OptionsExtension.getOptions(extensionContext); + if (shouldRecordTrace(options)) { + saveTrace(extensionContext); + } + closeBrowserContext(); + } + + private static void saveTraceWhenOn(ExtensionContext extensionContext) { + Options options = OptionsExtension.getOptions(extensionContext); + if (options.trace.equals(Options.Trace.ON)) { + saveTrace(extensionContext); + } + } + + private static void saveTrace(ExtensionContext extensionContext) { + BrowserContext browserContext = threadLocalBrowserContext.get(); + if (browserContext == null) { + return; + } + Path outputPath = getOutputPath(extensionContext); + createOutputPath(outputPath); + Tracing.StopOptions stopOptions = new Tracing.StopOptions().setPath(outputPath.resolve("trace.zip")); + browserContext.tracing().stop(stopOptions); + } + + private static void createOutputPath(Path outputPath) { + if (!Files.exists(outputPath)) { + try { + Files.createDirectories(outputPath); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + private static Path getOutputPath(ExtensionContext extensionContext) { + BrowserType browserType = BrowserExtension.getBrowser().browserType(); + Path defaultOutputPath = getDefaultOutputPath(extensionContext); + String outputDirName = extensionContext.getRequiredTestClass().getName() + "." + + extensionContext.getRequiredTestMethod().getName() + "-" + + browserType.name(); + return defaultOutputPath.resolve(outputDirName); + } + + private static Path getDefaultOutputPath(ExtensionContext extensionContext) { + Options options = OptionsExtension.getOptions(extensionContext); + Path outputPath = options.outputDir; + if (outputPath == null) { + outputPath = Paths.get(System.getProperty("user.dir")).resolve("test-results"); + } + return outputPath; + } + + private void closeBrowserContext() { + cleanUpPage(); + BrowserContext browserContext = threadLocalBrowserContext.get(); + threadLocalBrowserContext.remove(); + if (browserContext != null) { + browserContext.close(); + } + } + + private static boolean shouldRecordTrace(Options options) { + return options.trace.equals(Options.Trace.ON) || options.trace.equals(Options.Trace.RETAIN_ON_FAILURE); + } + private static Browser.NewContextOptions getContextOptions(Playwright playwright, Options options) { Browser.NewContextOptions contextOptions = Utils.clone(options.contextOptions); if (contextOptions == null) { @@ -94,7 +176,7 @@ public class BrowserContextExtension implements ParameterResolver, AfterEachCall contextOptions.hasTouch = deviceDescriptor.hasTouch; } - if(options.ignoreHTTPSErrors != null) { + if (options.ignoreHTTPSErrors != null) { contextOptions.setIgnoreHTTPSErrors(options.ignoreHTTPSErrors); } diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/junit/BrowserExtension.java b/playwright/src/main/java/com/microsoft/playwright/impl/junit/BrowserExtension.java index c3576447..3203a089 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/junit/BrowserExtension.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/junit/BrowserExtension.java @@ -86,6 +86,10 @@ public class BrowserExtension implements ParameterResolver, AfterAllCallback { return browser; } + static Browser getBrowser() { + return threadLocalBrowser.get(); + } + private static BrowserType.ConnectOptions getConnectOptions(Options options) { BrowserType.ConnectOptions connectOptions = options.connectOptions; if(connectOptions == null) { diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/junit/PageExtension.java b/playwright/src/main/java/com/microsoft/playwright/impl/junit/PageExtension.java index 2522d5c5..848bd933 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/junit/PageExtension.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/junit/PageExtension.java @@ -22,17 +22,8 @@ import org.junit.jupiter.api.extension.*; import static com.microsoft.playwright.impl.junit.ExtensionUtils.*; -public class PageExtension implements ParameterResolver, AfterEachCallback { - private static final ThreadLocal threadLocalPage = new ThreadLocal<>(); - - @Override - public void afterEach(ExtensionContext extensionContext) { - Page page = threadLocalPage.get(); - threadLocalPage.remove(); - if (page != null) { - page.close(); - } - } +public class PageExtension implements ParameterResolver { + private static final ThreadLocal threadLocalPage = new ThreadLocal<>(); @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { @@ -47,6 +38,7 @@ public class PageExtension implements ParameterResolver, AfterEachCallback { /** * Returns the Page that belongs to the current test. Will be created if it doesn't already exist. * NOTE: this method is subject to change. + * * @param extensionContext the context in which the current test or container is being executed. * @return The Page that belongs to the current test. */ @@ -61,4 +53,8 @@ public class PageExtension implements ParameterResolver, AfterEachCallback { threadLocalPage.set(page); return page; } + + static void cleanUpPage() { + threadLocalPage.remove(); + } } diff --git a/playwright/src/main/java/com/microsoft/playwright/junit/Options.java b/playwright/src/main/java/com/microsoft/playwright/junit/Options.java index 8de7dba9..7d4a38f4 100644 --- a/playwright/src/main/java/com/microsoft/playwright/junit/Options.java +++ b/playwright/src/main/java/com/microsoft/playwright/junit/Options.java @@ -16,10 +16,9 @@ package com.microsoft.playwright.junit; -import com.microsoft.playwright.APIRequest; -import com.microsoft.playwright.Browser; -import com.microsoft.playwright.BrowserType; -import com.microsoft.playwright.Playwright; +import com.microsoft.playwright.*; + +import java.nio.file.Path; /** * NOTE: this API is experimental and is subject to changes. @@ -47,6 +46,26 @@ public class Options { // If this is set, BrowserType.connect will be used. Otherwise, BrowserType.launch will be used. public String wsEndpoint; public BrowserType.ConnectOptions connectOptions; + // The dir where test artifacts will be stored + public Path outputDir; + // When to record traces. Default is OFF. + public Trace trace = Trace.OFF; + + public enum Trace { + OFF, + ON, + RETAIN_ON_FAILURE; + } + + public Options setTrace(Trace trace) { + this.trace = trace; + return this; + } + + public Options setOutputDir(Path outputDir) { + this.outputDir = outputDir; + return this; + } public Options setWsEndpoint(String wsEndpoint) { this.wsEndpoint = wsEndpoint; diff --git a/playwright/src/test/java/com/microsoft/playwright/TestOptionsFactories.java b/playwright/src/test/java/com/microsoft/playwright/TestOptionsFactories.java index 6f07285c..ee19f209 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestOptionsFactories.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestOptionsFactories.java @@ -44,7 +44,7 @@ public class TestOptionsFactories { return headfulEnv != null && !"0".equals(headfulEnv) && !"false".equals(headfulEnv); } - private static String getBrowserName() { + public static String getBrowserName() { String browserName = System.getenv("BROWSER"); if (browserName == null) { browserName = "chromium";