diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a6987f91..13383842 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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: diff --git a/README.md b/README.md index 567cb306..e462fdbd 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom | :--- | :---: | :---: | :---: | | Chromium 99.0.4763.0 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | WebKit 15.4 | ✅ | ✅ | ✅ | -| Firefox 94.0.1 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Firefox 95.0 | :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. diff --git a/assertions/src/main/java/com/microsoft/playwright/assertions/PageAssertions.java b/assertions/src/main/java/com/microsoft/playwright/assertions/PageAssertions.java index 363a71ff..9b52daf0 100644 --- a/assertions/src/main/java/com/microsoft/playwright/assertions/PageAssertions.java +++ b/assertions/src/main/java/com/microsoft/playwright/assertions/PageAssertions.java @@ -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()}: *
{@code
* ...
diff --git a/playwright/src/main/java/com/microsoft/playwright/Tracing.java b/playwright/src/main/java/com/microsoft/playwright/Tracing.java
index 2418e057..f57d6865 100644
--- a/playwright/src/main/java/com/microsoft/playwright/Tracing.java
+++ b/playwright/src/main/java/com/microsoft/playwright/Tracing.java
@@ -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.
*/
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/Connection.java b/playwright/src/main/java/com/microsoft/playwright/impl/Connection.java
index 4f5f6ba4..fc81d344 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/Connection.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/Connection.java
@@ -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 objects = new HashMap<>();
private final Root root;
private int lastId = 0;
- private final Path srcDir;
+ private final StackTraceCollector stackTraceCollector;
private final Map> 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 internalSendMessage(String guid, String method, JsonObject params) {
int id = ++lastId;
WaitableResult 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);
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/StackTraceCollector.java b/playwright/src/main/java/com/microsoft/playwright/impl/StackTraceCollector.java
new file mode 100644
index 00000000..93c67eaf
--- /dev/null
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/StackTraceCollector.java
@@ -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;
+ }
+
+
+}
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/TracingImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/TracingImpl.java
index 26597996..c476ff4a 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/TracingImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/TracingImpl.java
@@ -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");
}
diff --git a/playwright/src/test/java/com/microsoft/playwright/TestClick.java b/playwright/src/test/java/com/microsoft/playwright/TestClick.java
index 8be6c150..50ae49f5 100644
--- a/playwright/src/test/java/com/microsoft/playwright/TestClick.java
+++ b/playwright/src/test/java/com/microsoft/playwright/TestClick.java
@@ -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;
diff --git a/playwright/src/test/java/com/microsoft/playwright/TestElementHandleBoundingBox.java b/playwright/src/test/java/com/microsoft/playwright/TestElementHandleBoundingBox.java
index fa4acd92..9965e4ba 100644
--- a/playwright/src/test/java/com/microsoft/playwright/TestElementHandleBoundingBox.java
+++ b/playwright/src/test/java/com/microsoft/playwright/TestElementHandleBoundingBox.java
@@ -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);
diff --git a/playwright/src/test/java/com/microsoft/playwright/TestTracing.java b/playwright/src/test/java/com/microsoft/playwright/TestTracing.java
index c6f4e6a9..85cc6725 100644
--- a/playwright/src/test/java/com/microsoft/playwright/TestTracing.java
+++ b/playwright/src/test/java/com/microsoft/playwright/TestTracing.java
@@ -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("");
+ page.click("'Click'");
+ Path trace = tmpDir.resolve("trace1.zip");
+ context.tracing().stop(new Tracing.StopOptions().setPath(trace));
+
+ Map entries = parseTrace(trace);
+ Map 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 parseTrace(Path trace) throws IOException {
+ Map 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;
+ }
}
diff --git a/scripts/CLI_VERSION b/scripts/CLI_VERSION
index fada3a67..7ecc7214 100644
--- a/scripts/CLI_VERSION
+++ b/scripts/CLI_VERSION
@@ -1 +1 @@
-1.18.0-alpha-dec-16-2021
+1.18.0-alpha-1639770748000