Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions playwright/src/test/java/com/microsoft/playwright/Server.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.function.Function;
import java.util.zip.GZIPOutputStream;

import static com.microsoft.playwright.Utils.copy;
Expand All @@ -40,6 +41,7 @@ public class Server implements HttpHandler {
private final Map<String, String> csp = Collections.synchronizedMap(new HashMap<>());
private final Map<String, HttpHandler> routes = Collections.synchronizedMap(new HashMap<>());
private final Set<String> gzipRoutes = Collections.synchronizedSet(new HashSet<>());
private Function<String, InputStream> resourceProvider;

private static class Auth {
public final String user;
Expand Down Expand Up @@ -75,6 +77,8 @@ private Server(int port, boolean https) throws IOException {
server.createContext("/", this);
server.setExecutor(null); // creates a default executor
server.start();
// Resources from "src/test/resources/" are copied to "resources/" directory in the jar.
resourceProvider = path -> Server.class.getClassLoader().getResourceAsStream("resources" + path);
}

public void stop() {
Expand All @@ -93,6 +97,10 @@ void enableGzip(String path) {
gzipRoutes.add(path);
}

void setResourceProvider(Function<String, InputStream> resourceProvider) {
this.resourceProvider = resourceProvider;
}

static class Request {
public final String url;
public final String method;
Expand Down Expand Up @@ -187,18 +195,16 @@ public void handle(HttpExchange exchange) throws IOException {
path = "/index.html";
}

// Resources from "src/test/resources/" are copied to "resources/" directory in the jar.
String resourcePath = "resources" + path;
InputStream resource = getClass().getClassLoader().getResourceAsStream(resourcePath);
InputStream resource = resourceProvider.apply(path);
if (resource == null) {
exchange.getResponseHeaders().add("Content-Type", "text/plain");
exchange.sendResponseHeaders(404, 0);
try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("File not found: " + resourcePath);
writer.write("File not found: " + path);
}
return;
}
exchange.getResponseHeaders().add("Content-Type", mimeType(new File(resourcePath)));
exchange.getResponseHeaders().add("Content-Type", mimeType(new File(path)));
ByteArrayOutputStream body = new ByteArrayOutputStream();
OutputStream output = body;
if (gzipRoutes.contains(path)) {
Expand Down
239 changes: 149 additions & 90 deletions playwright/src/test/java/com/microsoft/playwright/TestTracing.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@

package com.microsoft.playwright;

import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName;
import com.microsoft.playwright.options.AriaRole;
import com.microsoft.playwright.options.Location;
import com.microsoft.playwright.options.MouseButton;
Expand All @@ -27,18 +25,12 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Arrays.asList;
import java.util.regex.Pattern;

import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;

public class TestTracing extends TestBase {
Expand All @@ -57,7 +49,7 @@ void launchBrowser(@TempDir Path tempDir) {
}

@Test
void shouldCollectTrace1(@TempDir Path tempDir) {
void shouldCollectTrace1(@TempDir Path tempDir) throws Exception {
context.tracing().start(new Tracing.StartOptions().setName("test")
.setScreenshots(true).setSnapshots(true));
page.navigate(server.EMPTY_PAGE);
Expand All @@ -68,10 +60,18 @@ void shouldCollectTrace1(@TempDir Path tempDir) {
context.tracing().stop(new Tracing.StopOptions().setPath(traceFile));

assertTrue(Files.exists(traceFile));
TraceViewerPage.showTraceViewer(this.browserType, traceFile, traceViewer -> {
assertThat(traceViewer.actionTitles()).hasText(new Pattern[] {
Pattern.compile("Navigate to \"/empty.html\""),
Pattern.compile("Set content"),
Pattern.compile("Click"),
Pattern.compile("Close")
});
});
}

@Test
void shouldCollectTwoTraces(@TempDir Path tempDir) {
void shouldCollectTwoTraces(@TempDir Path tempDir) throws Exception {
context.tracing().start(new Tracing.StartOptions().setName("test1")
.setScreenshots(true).setSnapshots(true));
page.navigate(server.EMPTY_PAGE);
Expand All @@ -89,10 +89,25 @@ void shouldCollectTwoTraces(@TempDir Path tempDir) {

assertTrue(Files.exists(traceFile1));
assertTrue(Files.exists(traceFile2));

TraceViewerPage.showTraceViewer(this.browserType, traceFile1, traceViewer -> {
assertThat(traceViewer.actionTitles()).hasText(new Pattern[] {
Pattern.compile("Navigate to \"/empty.html\""),
Pattern.compile("Set content"),
Pattern.compile("Click")
});
});

TraceViewerPage.showTraceViewer(this.browserType, traceFile2, traceViewer -> {
assertThat(traceViewer.actionTitles()).hasText(new Pattern[] {
Pattern.compile("Double click"),
Pattern.compile("Close")
});
});
}

@Test
void shouldWorkWithMultipleChunks(@TempDir Path tempDir) {
void shouldWorkWithMultipleChunks(@TempDir Path tempDir) throws Exception {
context.tracing().start(new Tracing.StartOptions().setScreenshots(true).setSnapshots(true));
page.navigate(server.PREFIX + "/frames/frame.html");

Expand All @@ -109,28 +124,60 @@ void shouldWorkWithMultipleChunks(@TempDir Path tempDir) {

assertTrue(Files.exists(traceFile1));
assertTrue(Files.exists(traceFile2));

TraceViewerPage.showTraceViewer(this.browserType, traceFile1, traceViewer -> {
assertThat(traceViewer.actionTitles()).hasText(new Pattern[] {
Pattern.compile("Set content"),
Pattern.compile("Click")
});
traceViewer.selectSnapshot("After");
FrameLocator frame = traceViewer.snapshotFrame("Set content", 0, false);
assertThat(frame.locator("button")).hasText("Click");
});

TraceViewerPage.showTraceViewer(this.browserType, traceFile2, traceViewer -> {
assertThat(traceViewer.actionTitles()).containsText(new String[] {"Hover"});
FrameLocator frame = traceViewer.snapshotFrame("Hover", 0, false);
assertThat(frame.locator("button")).hasText("Click");
});
}

@Test
void shouldCollectSources(@TempDir Path tmpDir) throws IOException {
void shouldCollectSources(@TempDir Path tmpDir) throws Exception {
Assumptions.assumeTrue(System.getenv("PLAYWRIGHT_JAVA_SRC") != null, "PLAYWRIGHT_JAVA_SRC must point to the directory containing this test source.");
context.tracing().start(new Tracing.StartOptions().setSources(true));
page.navigate(server.EMPTY_PAGE);
page.setContent("<button>Click</button>");
page.click("'Click'");
myMethodOuter();
Path trace = tmpDir.resolve("trace1.zip");
context.tracing().stop(new Tracing.StopOptions().setPath(trace));

Map<String, byte[]> entries = Utils.parseZip(trace);
Map<String, byte[]> sources = entries.entrySet().stream().filter(e -> e.getKey().endsWith(".txt")).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
assertEquals(1, sources.size());
TraceViewerPage.showTraceViewer(this.browserType, trace, traceViewer -> {
assertThat(traceViewer.actionTitles()).hasText(new Pattern[] {
Pattern.compile("Navigate to \"/empty.html\""),
Pattern.compile("Set content"),
Pattern.compile("Click")
});
traceViewer.showSourceTab();
assertThat(traceViewer.stackFrames()).containsText(new Pattern[] {
Pattern.compile("myMethodInner"),
Pattern.compile("myMethodOuter"),
Pattern.compile("shouldCollectSources")
});
traceViewer.selectAction("Set content");
assertThat(traceViewer.page().locator(".source-tab-file-name"))
.hasAttribute("title", Pattern.compile(".*TestTracing\\.java"));
assertThat(traceViewer.page().locator(".source-line-running"))
.containsText("page.setContent(\"<button>Click</button>\");");
});
}

private void myMethodOuter() {
myMethodInner();
}

String path = getClass().getName().replace('.', File.separatorChar);
String[] srcRoots = System.getenv("PLAYWRIGHT_JAVA_SRC").split(File.pathSeparator);
// Resolve in the last specified source dir.
Path sourceFile = Paths.get(srcRoots[srcRoots.length - 1], path + ".java");
byte[] thisFile = Files.readAllBytes(sourceFile);
assertEquals(new String(thisFile, UTF_8), new String(sources.values().iterator().next(), UTF_8));
private void myMethodInner() {
page.getByText("Click").click();
}

@Test
Expand All @@ -140,7 +187,7 @@ void shouldNotFailWhenSourcesSetExplicitlyToFalse() throws IOException {
}

@Test
void shouldRespectTracesDirAndName(@TempDir Path tempDir) {
void shouldRespectTracesDirAndName(@TempDir Path tempDir) throws Exception {
Path tracesDir = tempDir.resolve("trace-dir");
BrowserType.LaunchOptions options = createLaunchOptions();
options.setTracesDir(tracesDir);
Expand All @@ -159,6 +206,24 @@ void shouldRespectTracesDirAndName(@TempDir Path tempDir) {
context.tracing().stop(new Tracing.StopOptions().setPath(tempDir.resolve("trace2.zip")));
assertTrue(Files.exists(tracesDir.resolve("name2.trace")));
assertTrue(Files.exists(tracesDir.resolve("name2.network")));

TraceViewerPage.showTraceViewer(this.browserType, tempDir.resolve("trace1.zip"), traceViewer -> {
assertThat(traceViewer.actionTitles()).hasText(new Pattern[] {
Pattern.compile("Navigate to \"/one-style.html\"")
});
FrameLocator frame = traceViewer.snapshotFrame("Navigate", 0, false);
assertThat(frame.locator("body")).hasCSS("background-color", "rgb(255, 192, 203)");
assertThat(frame.locator("body")).hasText("hello, world!");
});

TraceViewerPage.showTraceViewer(this.browserType, tempDir.resolve("trace2.zip"), traceViewer -> {
assertThat(traceViewer.actionTitles()).hasText(new Pattern[] {
Pattern.compile("Navigate to \"/har.html\"")
});
FrameLocator frame = traceViewer.snapshotFrame("Navigate", 0, false);
assertThat(frame.locator("body")).hasCSS("background-color", "rgb(255, 192, 203)");
assertThat(frame.locator("body")).hasText("hello, world!");
});
}
}

Expand All @@ -179,11 +244,9 @@ void canCallTracingGroupGroupEndAtAnyTimeAndAutoClose(@TempDir Path tempDir) thr
context.tracing().groupEnd();
context.tracing().groupEnd();

List<TraceEvent> events = parseTraceEvents(traceFile1);
List<TraceEvent> groups = events.stream().filter(e -> "tracingGroup".equals(e.method)).collect(Collectors.toList());
assertEquals(1, groups.size());
assertEquals("actual", groups.get(0).title);

TraceViewerPage.showTraceViewer(this.browserType, traceFile1, traceViewer -> {
assertThat(traceViewer.actionTitles()).containsText(new String[] {"actual", "Navigate to \"/empty.html\""});
});
}

@Test
Expand All @@ -202,9 +265,16 @@ void traceGroupGroupEnd(@TempDir Path tempDir) throws Exception {
Path traceFile1 = tempDir.resolve("trace1.zip");
context.tracing().stop(new Tracing.StopOptions().setPath(traceFile1));

List<TraceEvent> events = parseTraceEvents(traceFile1);
List<String> calls = events.stream().filter(e -> e.renderedTitle() != null).map(e -> e.renderedTitle()).collect(Collectors.toList());
assertEquals(asList("outer group", "Frame.goto", "inner group 1", "Frame.click", "inner group 2", "Frame.isVisible"), calls);
TraceViewerPage.showTraceViewer(this.browserType, traceFile1, traceViewer -> {
traceViewer.expandAction("inner group 1");
assertThat(traceViewer.actionTitles()).hasText(new Pattern[] {
Pattern.compile("outer group"),
Pattern.compile("Navigate to \"data:"),
Pattern.compile("inner group 1"),
Pattern.compile("Click"),
Pattern.compile("inner group 2"),
});
});
}

@Test
Expand Down Expand Up @@ -240,64 +310,53 @@ void shouldTraceVariousAPIs(@TempDir Path tempDir) throws Exception {
Path traceFile1 = tempDir.resolve("trace1.zip");
context.tracing().stop(new Tracing.StopOptions().setPath(traceFile1));

List<TraceEvent> events = parseTraceEvents(traceFile1);
List<String> calls = events.stream().filter(e -> e.renderedTitle() != null).map(e -> e.renderedTitle())
.collect(Collectors.toList());
assertEquals(asList(
"BrowserContext.clockInstall",
"Frame.setContent",
"Frame.click",
"Frame.click",
"Page.keyboardType",
"Page.keyboardPress",
"Page.keyboardDown",
"Page.keyboardInsertText",
"Page.keyboardUp",
"Page.mouseMove",
"Page.mouseDown",
"Page.mouseMove",
"Page.mouseWheel",
"Page.mouseUp",
"BrowserContext.clockFastForward",
"BrowserContext.clockFastForward",
"BrowserContext.clockPauseAt",
"BrowserContext.clockRunFor",
"BrowserContext.clockSetFixedTime",
"BrowserContext.clockSetSystemTime",
"BrowserContext.clockResume",
"Frame.click"),
calls);
TraceViewerPage.showTraceViewer(this.browserType, traceFile1, traceViewer -> {
assertThat(traceViewer.actionTitles()).hasText(new Pattern[] {
Pattern.compile("Install clock"),
Pattern.compile("Set content"),
Pattern.compile("Click"),
Pattern.compile("Click"),
Pattern.compile("Type"),
Pattern.compile("Press"),
Pattern.compile("Key down"),
Pattern.compile("Insert"),
Pattern.compile("Key up"),
Pattern.compile("Mouse move"),
Pattern.compile("Mouse down"),
Pattern.compile("Mouse move"),
Pattern.compile("Mouse wheel"),
Pattern.compile("Mouse up"),
Pattern.compile("Fast forward clock"),
Pattern.compile("Fast forward clock"),
Pattern.compile("Pause clock"),
Pattern.compile("Run clock"),
Pattern.compile("Set fixed time"),
Pattern.compile("Set system time"),
Pattern.compile("Resume clock"),
Pattern.compile("Click")
});
});
}

private static class TraceEvent {
String type;
String name;
String title;
@SerializedName("class")
String clazz;
String method;
Double startTime;
Double endTime;
String callId;

String renderedTitle() {
if (title != null) {
return title;
}
if (clazz != null && method != null) {
return clazz + "." + method;
}
return null;
}
}
@Test
public void shouldNotRecordNetworkActions(@TempDir Path tempDir) throws Exception {
context.tracing().start(new Tracing.StartOptions());

page.onRequest(request -> {
request.allHeaders();
});
page.onResponse(response -> {
response.text();
});
page.navigate(server.EMPTY_PAGE);

Path traceFile1 = tempDir.resolve("trace1.zip");
context.tracing().stop(new Tracing.StopOptions().setPath(traceFile1));

private static List<TraceEvent> parseTraceEvents(Path traceFile) throws IOException {
Map<String, byte[]> files = Utils.parseZip(traceFile);
Map<String, byte[]> traces = files.entrySet().stream().filter(e -> e.getKey().endsWith(".trace")).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
assertNotNull(traces.get("trace.trace"));
return Arrays.stream(new String(traces.get("trace.trace"), UTF_8)
.split("\n"))
.map(s -> new Gson().fromJson(s, TraceEvent.class))
.collect(Collectors.toList());
TraceViewerPage.showTraceViewer(this.browserType, traceFile1, traceViewer -> {
assertThat(traceViewer.actionTitles()).hasText(new Pattern[] {
Pattern.compile("Navigate to \"/empty.html\"")
});
});
}
}
Loading
Loading