mirror of
https://github.com/microsoft/playwright-java.git
synced 2025-12-28 10:20:45 +00:00
feat: include source files in trace (#754)
This commit is contained in:
parent
963afac983
commit
86e91590cb
5
.github/workflows/test.yml
vendored
5
.github/workflows/test.yml
vendored
@ -40,6 +40,11 @@ jobs:
|
||||
run: mvn test --no-transfer-progress --fail-at-end
|
||||
env:
|
||||
BROWSER: ${{ matrix.browser }}
|
||||
- name: Run tracing tests w/ sources
|
||||
run: mvn test --no-transfer-progress --fail-at-end -D test=*TestTracing*
|
||||
env:
|
||||
BROWSER: ${{ matrix.browser }}
|
||||
PLAYWRIGHT_JAVA_SRC: src/test/java
|
||||
- name: Test Spring Boot Starter
|
||||
shell: bash
|
||||
env:
|
||||
|
||||
@ -13,7 +13,7 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom
|
||||
| :--- | :---: | :---: | :---: |
|
||||
| Chromium <!-- GEN:chromium-version -->99.0.4763.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| WebKit <!-- GEN:webkit-version -->15.4<!-- GEN:stop --> | ✅ | ✅ | ✅ |
|
||||
| Firefox <!-- GEN:firefox-version -->94.0.1<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| Firefox <!-- GEN:firefox-version -->95.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
|
||||
Headless execution is supported for all the browsers on all platforms. Check out [system requirements](https://playwright.dev/java/docs/next/intro/#system-requirements) for details.
|
||||
|
||||
|
||||
@ -22,7 +22,7 @@ import com.microsoft.playwright.Page;
|
||||
|
||||
/**
|
||||
* The {@code PageAssertions} class provides assertion methods that can be used to make assertions about the {@code Page} state in the
|
||||
* tests. A new instance of {@code LocatorAssertions} is created by calling {@link PlaywrightAssertions#assertThat
|
||||
* tests. A new instance of {@code PageAssertions} is created by calling {@link PlaywrightAssertions#assertThat
|
||||
* PlaywrightAssertions.assertThat()}:
|
||||
* <pre>{@code
|
||||
* ...
|
||||
|
||||
@ -51,6 +51,11 @@ public interface Tracing {
|
||||
* Whether to capture DOM snapshot on every action.
|
||||
*/
|
||||
public Boolean snapshots;
|
||||
/**
|
||||
* Whether to include source files for trace actions. List of the directories with source code for the application must be
|
||||
* provided via {@code PLAYWRIGHT_JAVA_SRC} environment variable.
|
||||
*/
|
||||
public Boolean sources;
|
||||
/**
|
||||
* Trace name to be shown in the Trace Viewer.
|
||||
*/
|
||||
@ -78,6 +83,14 @@ public interface Tracing {
|
||||
this.snapshots = snapshots;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Whether to include source files for trace actions. List of the directories with source code for the application must be
|
||||
* provided via {@code PLAYWRIGHT_JAVA_SRC} environment variable.
|
||||
*/
|
||||
public StartOptions setSources(boolean sources) {
|
||||
this.sources = sources;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Trace name to be shown in the Trace Viewer.
|
||||
*/
|
||||
|
||||
@ -16,23 +16,18 @@
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.Playwright;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import com.microsoft.playwright.TimeoutError;
|
||||
|
||||
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.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.microsoft.playwright.impl.LoggingSupport.logWithTimestamp;
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
|
||||
class Message {
|
||||
@ -62,7 +57,7 @@ public class Connection {
|
||||
private final Map<String, ChannelOwner> objects = new HashMap<>();
|
||||
private final Root root;
|
||||
private int lastId = 0;
|
||||
private final Path srcDir;
|
||||
private final StackTraceCollector stackTraceCollector;
|
||||
private final Map<Integer, WaitableResult<JsonElement>> callbacks = new HashMap<>();
|
||||
private String apiName;
|
||||
private static final boolean isLogging;
|
||||
@ -91,14 +86,11 @@ public class Connection {
|
||||
this.transport = transport;
|
||||
root = new Root(this);
|
||||
String srcRoot = System.getenv("PLAYWRIGHT_JAVA_SRC");
|
||||
if (srcRoot == null) {
|
||||
srcDir = null;
|
||||
} else {
|
||||
srcDir = Paths.get(srcRoot);
|
||||
if (!Files.exists(srcDir)) {
|
||||
throw new PlaywrightException("PLAYWRIGHT_JAVA_SRC environment variable points to non-existing location: '" + srcRoot + "'");
|
||||
}
|
||||
}
|
||||
stackTraceCollector = (srcRoot == null) ? null: new StackTraceCollector(Paths.get(srcRoot));
|
||||
}
|
||||
|
||||
boolean isCollectingStacks() {
|
||||
return stackTraceCollector != null;
|
||||
}
|
||||
|
||||
String setApiName(String name) {
|
||||
@ -119,45 +111,6 @@ public class Connection {
|
||||
return internalSendMessage(guid, method, params);
|
||||
}
|
||||
|
||||
private String sourceFile(StackTraceElement frame) {
|
||||
String pkg = frame.getClassName();
|
||||
int lastDot = pkg.lastIndexOf('.');
|
||||
if (lastDot == -1) {
|
||||
pkg = "";
|
||||
} else {
|
||||
pkg = frame.getClassName().substring(0, lastDot + 1);
|
||||
}
|
||||
pkg = pkg.replace('.', File.separatorChar);
|
||||
return srcDir.resolve(pkg).resolve(frame.getFileName()).toString();
|
||||
}
|
||||
|
||||
private JsonArray currentStackTrace() {
|
||||
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
|
||||
|
||||
int index = 0;
|
||||
while (index < stack.length && !stack[index].getClassName().equals(getClass().getName())) {
|
||||
index++;
|
||||
};
|
||||
// Find Playwright API call
|
||||
while (index < stack.length && stack[index].getClassName().startsWith("com.microsoft.playwright.")) {
|
||||
// hack for tests
|
||||
if (stack[index].getClassName().startsWith("com.microsoft.playwright.Test")) {
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
JsonArray jsonStack = new JsonArray();
|
||||
for (; index < stack.length; index++) {
|
||||
StackTraceElement frame = stack[index];
|
||||
JsonObject jsonFrame = new JsonObject();
|
||||
jsonFrame.addProperty("file", sourceFile(frame));
|
||||
jsonFrame.addProperty("line", frame.getLineNumber());
|
||||
jsonFrame.addProperty("function", frame.getClassName() + "." + frame.getMethodName());
|
||||
jsonStack.add(jsonFrame);
|
||||
}
|
||||
return jsonStack;
|
||||
}
|
||||
|
||||
private WaitableResult<JsonElement> internalSendMessage(String guid, String method, JsonObject params) {
|
||||
int id = ++lastId;
|
||||
WaitableResult<JsonElement> result = new WaitableResult<>();
|
||||
@ -168,8 +121,8 @@ public class Connection {
|
||||
message.addProperty("method", method);
|
||||
message.add("params", params);
|
||||
JsonObject metadata = new JsonObject();
|
||||
if (srcDir != null) {
|
||||
metadata.add("stack", currentStackTrace());
|
||||
if (stackTraceCollector != null) {
|
||||
metadata.add("stack", stackTraceCollector.currentStackTrace());
|
||||
}
|
||||
if (apiName != null) {
|
||||
metadata.addProperty("apiName", apiName);
|
||||
|
||||
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
class StackTraceCollector {
|
||||
private final Path srcDir;
|
||||
|
||||
StackTraceCollector(Path srcDir) {
|
||||
if (!Files.exists(srcDir.toAbsolutePath())) {
|
||||
throw new PlaywrightException("Source location doesn't exist: '" + srcDir.toAbsolutePath() + "'");
|
||||
}
|
||||
this.srcDir = srcDir;
|
||||
}
|
||||
|
||||
private String sourceFile(StackTraceElement frame) {
|
||||
String pkg = frame.getClassName();
|
||||
int lastDot = pkg.lastIndexOf('.');
|
||||
if (lastDot == -1) {
|
||||
pkg = "";
|
||||
} else {
|
||||
pkg = frame.getClassName().substring(0, lastDot + 1);
|
||||
}
|
||||
pkg = pkg.replace('.', File.separatorChar);
|
||||
String file = frame.getFileName();
|
||||
if (file == null) {
|
||||
return "";
|
||||
}
|
||||
return srcDir.resolve(pkg).resolve(file).toString();
|
||||
}
|
||||
|
||||
JsonArray currentStackTrace() {
|
||||
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
|
||||
|
||||
int index = 0;
|
||||
while (index < stack.length && !stack[index].getClassName().equals(getClass().getName())) {
|
||||
index++;
|
||||
};
|
||||
// Find Playwright API call
|
||||
while (index < stack.length && stack[index].getClassName().startsWith("com.microsoft.playwright.")) {
|
||||
// hack for tests
|
||||
if (stack[index].getClassName().startsWith("com.microsoft.playwright.Test")) {
|
||||
break;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
JsonArray jsonStack = new JsonArray();
|
||||
for (; index < stack.length; index++) {
|
||||
StackTraceElement frame = stack[index];
|
||||
JsonObject jsonFrame = new JsonObject();
|
||||
jsonFrame.addProperty("file", sourceFile(frame));
|
||||
jsonFrame.addProperty("line", frame.getLineNumber());
|
||||
jsonFrame.addProperty("function", frame.getClassName() + "." + frame.getMethodName());
|
||||
jsonStack.add(jsonFrame);
|
||||
}
|
||||
return jsonStack;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -16,8 +16,8 @@
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import com.microsoft.playwright.Tracing;
|
||||
|
||||
import java.nio.file.Path;
|
||||
@ -26,16 +26,23 @@ import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
|
||||
class TracingImpl implements Tracing {
|
||||
private final BrowserContextImpl context;
|
||||
private boolean includeSources;
|
||||
|
||||
TracingImpl(BrowserContextImpl context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
private void stopChunkImpl(Path path) {
|
||||
boolean isRemote = context.browser() != null && context.browser().isRemote;
|
||||
JsonObject params = new JsonObject();
|
||||
String mode = "doNotSave";
|
||||
if (path != null) {
|
||||
mode = "compressTrace";
|
||||
if (isRemote) {
|
||||
mode = "compressTrace";
|
||||
} else {
|
||||
// TODO: support source zips and do compression on the client.
|
||||
mode = "compressTraceAndSources";
|
||||
}
|
||||
}
|
||||
params.addProperty("mode", mode);
|
||||
JsonObject json = context.sendMessage("tracingStopChunk", params).getAsJsonObject();
|
||||
@ -45,7 +52,7 @@ class TracingImpl implements Tracing {
|
||||
ArtifactImpl artifact = context.connection.getExistingObject(json.getAsJsonObject("artifact").get("guid").getAsString());
|
||||
// In case of CDP connection browser is null but since the connection is established by
|
||||
// the driver it is safe to consider the artifact local.
|
||||
if (context.browser() != null && context.browser().isRemote) {
|
||||
if (isRemote) {
|
||||
artifact.isRemote = true;
|
||||
}
|
||||
artifact.saveAs(path);
|
||||
@ -77,6 +84,13 @@ class TracingImpl implements Tracing {
|
||||
options = new StartOptions();
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
includeSources = options.sources != null;
|
||||
if (includeSources) {
|
||||
if (!context.connection.isCollectingStacks()) {
|
||||
throw new PlaywrightException("Source root directories must be provided to enable source collection");
|
||||
}
|
||||
params.addProperty("sources", true);
|
||||
}
|
||||
context.sendMessage("tracingStart", params);
|
||||
context.sendMessage("tracingStartChunk");
|
||||
}
|
||||
|
||||
@ -20,6 +20,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledIf;
|
||||
import org.junit.jupiter.api.condition.EnabledIf;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@ -45,7 +45,7 @@ public class TestElementHandleBoundingBox extends TestBase {
|
||||
|
||||
@Test
|
||||
void shouldHandleNestedFrames() {
|
||||
page.setViewportSize(500, 500);
|
||||
page.setViewportSize(616, 500);
|
||||
page.navigate(server.PREFIX + "/frames/nested-frames.html");
|
||||
Frame nestedFrame = page.frame("dos");
|
||||
assertNotNull(nestedFrame);
|
||||
|
||||
@ -16,17 +16,31 @@
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import org.junit.jupiter.api.Assumptions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import static com.microsoft.playwright.Utils.copy;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class TestTracing extends TestBase {
|
||||
|
||||
private static final String _PW_JAVA_TEST_SRC = "_PW_JAVA_TEST_SRC";
|
||||
|
||||
@Override
|
||||
@BeforeAll
|
||||
void launchBrowser() {
|
||||
@ -96,4 +110,39 @@ public class TestTracing extends TestBase {
|
||||
assertTrue(Files.exists(traceFile1));
|
||||
assertTrue(Files.exists(traceFile2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCollectSources(@TempDir Path tmpDir) throws IOException {
|
||||
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'");
|
||||
Path trace = tmpDir.resolve("trace1.zip");
|
||||
context.tracing().stop(new Tracing.StopOptions().setPath(trace));
|
||||
|
||||
Map<String, byte[]> entries = parseTrace(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());
|
||||
|
||||
String path = getClass().getName().replace('.', File.separatorChar);
|
||||
Path sourceFile = Paths.get(System.getenv("PLAYWRIGHT_JAVA_SRC"), path + ".java");
|
||||
byte[] thisFile = Files.readAllBytes(sourceFile);
|
||||
assertEquals(new String(thisFile, UTF_8), new String(sources.values().iterator().next(), UTF_8));
|
||||
}
|
||||
|
||||
private static Map<String, byte[]> parseTrace(Path trace) throws IOException {
|
||||
Map<String, byte[]> entries = new HashMap<>();
|
||||
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(trace.toFile()))) {
|
||||
for (ZipEntry zipEntry = zis.getNextEntry(); zipEntry != null; zipEntry = zis.getNextEntry()) {
|
||||
ByteArrayOutputStream content = new ByteArrayOutputStream();
|
||||
try (OutputStream output = content) {
|
||||
copy(zis, output);
|
||||
}
|
||||
entries.put(zipEntry.getName(), content.toByteArray());
|
||||
}
|
||||
zis.closeEntry();
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1 +1 @@
|
||||
1.18.0-alpha-dec-16-2021
|
||||
1.18.0-alpha-1639770748000
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user