feat(tracing): collect sources for remote tracing (#755)
This commit is contained in:
@@ -55,6 +55,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
|
||||
Path videosDir;
|
||||
URL baseUrl;
|
||||
Path recordHarPath;
|
||||
LocalUtils localUtils;
|
||||
|
||||
enum EventType {
|
||||
CLOSE,
|
||||
|
||||
@@ -40,6 +40,7 @@ class BrowserImpl extends ChannelOwner implements Browser {
|
||||
boolean isRemote;
|
||||
boolean isConnectedOverWebSocket;
|
||||
private boolean isConnected = true;
|
||||
LocalUtils localUtils;
|
||||
|
||||
enum EventType {
|
||||
DISCONNECTED,
|
||||
@@ -171,6 +172,7 @@ class BrowserImpl extends ChannelOwner implements Browser {
|
||||
context.setBaseUrl(options.baseURL);
|
||||
}
|
||||
context.recordHarPath = options.recordHarPath;
|
||||
context.localUtils = localUtils;
|
||||
contexts.add(context);
|
||||
return context;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ import java.util.function.Consumer;
|
||||
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||
|
||||
class BrowserTypeImpl extends ChannelOwner implements BrowserType {
|
||||
LocalUtils localUtils;
|
||||
|
||||
BrowserTypeImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
}
|
||||
@@ -45,7 +47,9 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
|
||||
}
|
||||
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||
JsonElement result = sendMessage("launch", params);
|
||||
return connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("browser").get("guid").getAsString());
|
||||
BrowserImpl browser = connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("browser").get("guid").getAsString());
|
||||
browser.localUtils = localUtils;
|
||||
return browser;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -76,6 +80,7 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
|
||||
BrowserImpl browser = connection.getExistingObject(playwright.initializer.getAsJsonObject("preLaunchedBrowser").get("guid").getAsString());
|
||||
browser.isRemote = true;
|
||||
browser.isConnectedOverWebSocket = true;
|
||||
browser.localUtils = localUtils;
|
||||
Consumer<JsonPipe> connectionCloseListener = t -> browser.notifyRemoteClosed();
|
||||
pipe.onClose(connectionCloseListener);
|
||||
browser.onDisconnected(b -> {
|
||||
@@ -109,6 +114,7 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
|
||||
|
||||
BrowserImpl browser = connection.getExistingObject(json.getAsJsonObject("browser").get("guid").getAsString());
|
||||
browser.isRemote = true;
|
||||
browser.localUtils = localUtils;
|
||||
if (json.has("defaultContext")) {
|
||||
String contextId = json.getAsJsonObject("defaultContext").get("guid").getAsString();
|
||||
BrowserContextImpl defaultContext = connection.getExistingObject(contextId);
|
||||
@@ -174,6 +180,7 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
|
||||
context.setBaseUrl(options.baseURL);
|
||||
}
|
||||
context.recordHarPath = options.recordHarPath;
|
||||
context.localUtils = localUtils;
|
||||
return context;
|
||||
}
|
||||
|
||||
|
||||
@@ -268,6 +268,7 @@ public class Connection {
|
||||
result = new JsonPipe(parent, type, guid, initializer);
|
||||
break;
|
||||
case "LocalUtils":
|
||||
result = new LocalUtils(parent, type, guid, initializer);
|
||||
break;
|
||||
case "Page":
|
||||
result = new PageImpl(parent, type, guid, initializer);
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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 java.nio.file.Path;
|
||||
|
||||
class LocalUtils extends ChannelOwner {
|
||||
LocalUtils(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
}
|
||||
|
||||
void zip(Path zipFile, JsonArray entries) {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("zipFile", zipFile.toString());
|
||||
params.add("entries", entries);
|
||||
sendMessage("zip", params);
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,7 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
|
||||
private final BrowserTypeImpl webkit;
|
||||
private final SelectorsImpl selectors;
|
||||
private final APIRequestImpl apiRequest;
|
||||
private final LocalUtils localUtils;
|
||||
private SharedSelectors sharedSelectors;
|
||||
|
||||
PlaywrightImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||
@@ -67,6 +68,10 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
|
||||
|
||||
selectors = connection.getExistingObject(initializer.getAsJsonObject("selectors").get("guid").getAsString());
|
||||
apiRequest = new APIRequestImpl(this);
|
||||
localUtils = connection.getExistingObject(initializer.getAsJsonObject("utils").get("guid").getAsString());
|
||||
chromium.localUtils = localUtils;
|
||||
firefox.localUtils = localUtils;
|
||||
webkit.localUtils = localUtils;
|
||||
}
|
||||
|
||||
void initSharedSelectors(PlaywrightImpl parent) {
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.microsoft.playwright.impl;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import com.microsoft.playwright.Tracing;
|
||||
@@ -40,7 +41,6 @@ class TracingImpl implements Tracing {
|
||||
if (isRemote) {
|
||||
mode = "compressTrace";
|
||||
} else {
|
||||
// TODO: support source zips and do compression on the client.
|
||||
mode = "compressTraceAndSources";
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,12 @@ class TracingImpl implements Tracing {
|
||||
}
|
||||
artifact.saveAs(path);
|
||||
artifact.delete();
|
||||
|
||||
// Add local sources to the remote trace if necessary.
|
||||
if (isRemote && json.has("sourceEntries")) {
|
||||
JsonArray entries = json.getAsJsonArray("sourceEntries");
|
||||
context.localUtils.zip(path, entries);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -19,21 +19,22 @@ package com.microsoft.playwright;
|
||||
import com.microsoft.playwright.impl.Driver;
|
||||
import com.microsoft.playwright.options.WaitForSelectorState;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
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.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
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 static com.microsoft.playwright.Utils.mapOf;
|
||||
import static com.microsoft.playwright.Utils.parseTrace;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@@ -485,4 +486,25 @@ public class TestBrowserTypeConnect extends TestBase {
|
||||
assertTrue(Files.exists(traceFile));
|
||||
assertTrue(Files.size(traceFile) > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRecordTraceWithSources(@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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ 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;
|
||||
|
||||
|
||||
@@ -21,26 +21,19 @@ 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.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.Map;
|
||||
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() {
|
||||
@@ -121,7 +114,7 @@ public class TestTracing extends TestBase {
|
||||
Path trace = tmpDir.resolve("trace1.zip");
|
||||
context.tracing().stop(new Tracing.StopOptions().setPath(trace));
|
||||
|
||||
Map<String, byte[]> entries = parseTrace(trace);
|
||||
Map<String, byte[]> entries = Utils.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());
|
||||
|
||||
@@ -131,18 +124,4 @@ public class TestTracing extends TestBase {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,16 +20,17 @@ import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.*;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@@ -88,6 +89,21 @@ class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
enum OS { WINDOWS, MAC, LINUX, UNKNOWN }
|
||||
static OS getOS() {
|
||||
String name = System.getProperty("os.name").toLowerCase();
|
||||
|
||||
Reference in New Issue
Block a user