Skip to content

Commit 2e32eb7

Browse files
authored
feat(junit): Implement automatic saving of traces and screenshots via fixtures (#1560)
1 parent b81b144 commit 2e32eb7

File tree

6 files changed

+133
-32
lines changed

6 files changed

+133
-32
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ jobs:
8181
- name: Install MS Edge
8282
if: matrix.browser-channel == 'msedge' && matrix.os == 'macos-latest'
8383
shell: bash
84-
run: mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install msedge" -f playwright/pom.xml
84+
run: mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install msedge" -f playwright/pom.xml
8585
- name: Run tests
8686
run: mvn test --no-transfer-progress --fail-at-end -D org.slf4j.simpleLogger.showDateTime=true -D org.slf4j.simpleLogger.dateTimeFormat=HH:mm:ss
8787
env:

playwright/src/main/java/com/microsoft/playwright/impl/junit/BrowserContextExtension.java

Lines changed: 97 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,28 +16,22 @@
1616

1717
package com.microsoft.playwright.impl.junit;
1818

19-
import com.microsoft.playwright.Browser;
20-
import com.microsoft.playwright.BrowserContext;
21-
import com.microsoft.playwright.Playwright;
22-
import com.microsoft.playwright.PlaywrightException;
19+
import com.microsoft.playwright.*;
2320
import com.microsoft.playwright.impl.Utils;
2421
import com.microsoft.playwright.junit.Options;
2522
import org.junit.jupiter.api.extension.*;
2623

24+
import java.io.IOException;
25+
import java.nio.file.Files;
26+
import java.nio.file.Path;
27+
import java.nio.file.Paths;
28+
2729
import static com.microsoft.playwright.impl.junit.ExtensionUtils.*;
30+
import static com.microsoft.playwright.impl.junit.PageExtension.cleanUpPage;
2831

29-
public class BrowserContextExtension implements ParameterResolver, AfterEachCallback {
32+
public class BrowserContextExtension implements ParameterResolver, TestWatcher {
3033
private static final ThreadLocal<BrowserContext> threadLocalBrowserContext = new ThreadLocal<>();
3134

32-
@Override
33-
public void afterEach(ExtensionContext extensionContext) {
34-
BrowserContext browserContext = threadLocalBrowserContext.get();
35-
threadLocalBrowserContext.remove();
36-
if (browserContext != null) {
37-
browserContext.close();
38-
}
39-
}
40-
4135
@Override
4236
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
4337
return !isClassHook(extensionContext) && isParameterSupported(parameterContext, extensionContext, BrowserContext.class);
@@ -51,6 +45,7 @@ public Object resolveParameter(ParameterContext parameterContext, ExtensionConte
5145
/**
5246
* Returns the BrowserContext that belongs to the current test. Will be created if it doesn't already exist.
5347
* <strong>NOTE:</strong> this method is subject to change.
48+
*
5449
* @param extensionContext the context in which the current test or container is being executed.
5550
* @return The BrowserContext that belongs to the current test.
5651
*/
@@ -66,10 +61,97 @@ public static BrowserContext getOrCreateBrowserContext(ExtensionContext extensio
6661
Browser browser = BrowserExtension.getOrCreateBrowser(extensionContext);
6762
Browser.NewContextOptions contextOptions = getContextOptions(playwright, options);
6863
browserContext = browser.newContext(contextOptions);
64+
if (shouldRecordTrace(options)) {
65+
Tracing.StartOptions startOptions = new Tracing.StartOptions().setSnapshots(true).setScreenshots(true).setTitle(extensionContext.getDisplayName());
66+
if (System.getenv("PLAYWRIGHT_JAVA_SRC") != null) {
67+
startOptions.setSources(true);
68+
}
69+
browserContext.tracing().start(startOptions);
70+
}
6971
threadLocalBrowserContext.set(browserContext);
7072
return browserContext;
7173
}
7274

75+
@Override
76+
public void testSuccessful(ExtensionContext extensionContext) {
77+
saveTraceWhenOn(extensionContext);
78+
closeBrowserContext();
79+
}
80+
81+
@Override
82+
public void testAborted(ExtensionContext extensionContext, Throwable cause) {
83+
saveTraceWhenOn(extensionContext);
84+
closeBrowserContext();
85+
}
86+
87+
@Override
88+
public void testFailed(ExtensionContext extensionContext, Throwable cause) {
89+
Options options = OptionsExtension.getOptions(extensionContext);
90+
if (shouldRecordTrace(options)) {
91+
saveTrace(extensionContext);
92+
}
93+
closeBrowserContext();
94+
}
95+
96+
private static void saveTraceWhenOn(ExtensionContext extensionContext) {
97+
Options options = OptionsExtension.getOptions(extensionContext);
98+
if (options.trace.equals(Options.Trace.ON)) {
99+
saveTrace(extensionContext);
100+
}
101+
}
102+
103+
private static void saveTrace(ExtensionContext extensionContext) {
104+
BrowserContext browserContext = threadLocalBrowserContext.get();
105+
if (browserContext == null) {
106+
return;
107+
}
108+
Path outputPath = getOutputPath(extensionContext);
109+
createOutputPath(outputPath);
110+
Tracing.StopOptions stopOptions = new Tracing.StopOptions().setPath(outputPath.resolve("trace.zip"));
111+
browserContext.tracing().stop(stopOptions);
112+
}
113+
114+
private static void createOutputPath(Path outputPath) {
115+
if (!Files.exists(outputPath)) {
116+
try {
117+
Files.createDirectories(outputPath);
118+
} catch (IOException e) {
119+
throw new RuntimeException(e);
120+
}
121+
}
122+
}
123+
124+
private static Path getOutputPath(ExtensionContext extensionContext) {
125+
BrowserType browserType = BrowserExtension.getBrowser().browserType();
126+
Path defaultOutputPath = getDefaultOutputPath(extensionContext);
127+
String outputDirName = extensionContext.getRequiredTestClass().getName() + "." +
128+
extensionContext.getRequiredTestMethod().getName() + "-" +
129+
browserType.name();
130+
return defaultOutputPath.resolve(outputDirName);
131+
}
132+
133+
private static Path getDefaultOutputPath(ExtensionContext extensionContext) {
134+
Options options = OptionsExtension.getOptions(extensionContext);
135+
Path outputPath = options.outputDir;
136+
if (outputPath == null) {
137+
outputPath = Paths.get(System.getProperty("user.dir")).resolve("test-results");
138+
}
139+
return outputPath;
140+
}
141+
142+
private void closeBrowserContext() {
143+
cleanUpPage();
144+
BrowserContext browserContext = threadLocalBrowserContext.get();
145+
threadLocalBrowserContext.remove();
146+
if (browserContext != null) {
147+
browserContext.close();
148+
}
149+
}
150+
151+
private static boolean shouldRecordTrace(Options options) {
152+
return options.trace.equals(Options.Trace.ON) || options.trace.equals(Options.Trace.RETAIN_ON_FAILURE);
153+
}
154+
73155
private static Browser.NewContextOptions getContextOptions(Playwright playwright, Options options) {
74156
Browser.NewContextOptions contextOptions = Utils.clone(options.contextOptions);
75157
if (contextOptions == null) {
@@ -94,7 +176,7 @@ private static Browser.NewContextOptions getContextOptions(Playwright playwright
94176
contextOptions.hasTouch = deviceDescriptor.hasTouch;
95177
}
96178

97-
if(options.ignoreHTTPSErrors != null) {
179+
if (options.ignoreHTTPSErrors != null) {
98180
contextOptions.setIgnoreHTTPSErrors(options.ignoreHTTPSErrors);
99181
}
100182

playwright/src/main/java/com/microsoft/playwright/impl/junit/BrowserExtension.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ public static Browser getOrCreateBrowser(ExtensionContext extensionContext) {
8686
return browser;
8787
}
8888

89+
static Browser getBrowser() {
90+
return threadLocalBrowser.get();
91+
}
92+
8993
private static BrowserType.ConnectOptions getConnectOptions(Options options) {
9094
BrowserType.ConnectOptions connectOptions = options.connectOptions;
9195
if(connectOptions == null) {

playwright/src/main/java/com/microsoft/playwright/impl/junit/PageExtension.java

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,8 @@
2222

2323
import static com.microsoft.playwright.impl.junit.ExtensionUtils.*;
2424

25-
public class PageExtension implements ParameterResolver, AfterEachCallback {
26-
private static final ThreadLocal<Page> threadLocalPage = new ThreadLocal<>();
27-
28-
@Override
29-
public void afterEach(ExtensionContext extensionContext) {
30-
Page page = threadLocalPage.get();
31-
threadLocalPage.remove();
32-
if (page != null) {
33-
page.close();
34-
}
35-
}
25+
public class PageExtension implements ParameterResolver {
26+
private static final ThreadLocal<Page> threadLocalPage = new ThreadLocal<>();
3627

3728
@Override
3829
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
@@ -47,6 +38,7 @@ public Object resolveParameter(ParameterContext parameterContext, ExtensionConte
4738
/**
4839
* Returns the Page that belongs to the current test. Will be created if it doesn't already exist.
4940
* <strong>NOTE:</strong> this method is subject to change.
41+
*
5042
* @param extensionContext the context in which the current test or container is being executed.
5143
* @return The Page that belongs to the current test.
5244
*/
@@ -61,4 +53,8 @@ public static Page getOrCreatePage(ExtensionContext extensionContext) {
6153
threadLocalPage.set(page);
6254
return page;
6355
}
56+
57+
static void cleanUpPage() {
58+
threadLocalPage.remove();
59+
}
6460
}

playwright/src/main/java/com/microsoft/playwright/junit/Options.java

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,9 @@
1616

1717
package com.microsoft.playwright.junit;
1818

19-
import com.microsoft.playwright.APIRequest;
20-
import com.microsoft.playwright.Browser;
21-
import com.microsoft.playwright.BrowserType;
22-
import com.microsoft.playwright.Playwright;
19+
import com.microsoft.playwright.*;
20+
21+
import java.nio.file.Path;
2322

2423
/**
2524
* <strong>NOTE:</strong> this API is experimental and is subject to changes.
@@ -47,6 +46,26 @@ public class Options {
4746
// If this is set, BrowserType.connect will be used. Otherwise, BrowserType.launch will be used.
4847
public String wsEndpoint;
4948
public BrowserType.ConnectOptions connectOptions;
49+
// The dir where test artifacts will be stored
50+
public Path outputDir;
51+
// When to record traces. Default is OFF.
52+
public Trace trace = Trace.OFF;
53+
54+
public enum Trace {
55+
OFF,
56+
ON,
57+
RETAIN_ON_FAILURE;
58+
}
59+
60+
public Options setTrace(Trace trace) {
61+
this.trace = trace;
62+
return this;
63+
}
64+
65+
public Options setOutputDir(Path outputDir) {
66+
this.outputDir = outputDir;
67+
return this;
68+
}
5069

5170
public Options setWsEndpoint(String wsEndpoint) {
5271
this.wsEndpoint = wsEndpoint;

playwright/src/test/java/com/microsoft/playwright/TestOptionsFactories.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ private static boolean getHeadful() {
4444
return headfulEnv != null && !"0".equals(headfulEnv) && !"false".equals(headfulEnv);
4545
}
4646

47-
private static String getBrowserName() {
47+
public static String getBrowserName() {
4848
String browserName = System.getenv("BROWSER");
4949
if (browserName == null) {
5050
browserName = "chromium";

0 commit comments

Comments
 (0)