1
0
mirror of synced 2026-08-05 15:06:54 +00:00

feat(tracing): collect sources for remote tracing (#755)

This commit is contained in:
Yury Semikhatsky
2021-12-20 12:20:57 -08:00
committed by GitHub
parent 86e91590cb
commit e926c1ae82
11 changed files with 108 additions and 35 deletions
@@ -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();