Skip to content

Commit

Permalink
feat: support tracing API for chromium (microsoft#422)
Browse files Browse the repository at this point in the history
  • Loading branch information
yury-s committed May 4, 2021
1 parent 779d50c commit a95f8f3
Show file tree
Hide file tree
Showing 7 changed files with 261 additions and 16 deletions.
96 changes: 90 additions & 6 deletions playwright/src/main/java/com/microsoft/playwright/Browser.java
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,11 @@ class NewContextOptions {
*/
public List<String> permissions;
/**
* Network proxy settings to use with this context. Note that browser needs to be launched with the global proxy for this
* option to work. If all contexts override the proxy, global proxy will be never used and can be any string, for example
* {@code launch({ proxy: { server: 'per-context' } })}.
* Network proxy settings to use with this context.
*
* <p> <strong>NOTE:</strong> For Chromium on Windows the browser needs to be launched with the global proxy for this option to work. If all contexts
* override the proxy, global proxy will be never used and can be any string, for example {@code launch({ proxy: { server:
* 'http://per-context' } })}.
*/
public Proxy proxy;
/**
Expand Down Expand Up @@ -349,9 +351,11 @@ class NewPageOptions {
*/
public List<String> permissions;
/**
* Network proxy settings to use with this context. Note that browser needs to be launched with the global proxy for this
* option to work. If all contexts override the proxy, global proxy will be never used and can be any string, for example
* {@code launch({ proxy: { server: 'per-context' } })}.
* Network proxy settings to use with this context.
*
* <p> <strong>NOTE:</strong> For Chromium on Windows the browser needs to be launched with the global proxy for this option to work. If all contexts
* override the proxy, global proxy will be never used and can be any string, for example {@code launch({ proxy: { server:
* 'http://per-context' } })}.
*/
public Proxy proxy;
/**
Expand Down Expand Up @@ -525,6 +529,33 @@ public NewPageOptions setViewportSize(ViewportSize viewportSize) {
return this;
}
}
class StartTracingOptions {
/**
* specify custom categories to use instead of default.
*/
public List<String> categories;
/**
* A path to write the trace file to.
*/
public Path path;
/**
* captures screenshots in the trace.
*/
public Boolean screenshots;

public StartTracingOptions setCategories(List<String> categories) {
this.categories = categories;
return this;
}
public StartTracingOptions setPath(Path path) {
this.path = path;
return this;
}
public StartTracingOptions setScreenshots(boolean screenshots) {
this.screenshots = screenshots;
return this;
}
}
/**
* In case this browser is obtained using {@link BrowserType#launch BrowserType.launch()}, closes the browser and all of
* its pages (if any were opened).
Expand Down Expand Up @@ -593,6 +624,59 @@ default Page newPage() {
* BrowserContext#newPage BrowserContext.newPage()} to control their exact life times.
*/
Page newPage(NewPageOptions options);
/**
* <strong>NOTE:</strong> Tracing is only supported on Chromium-based browsers.
*
* <p> You can use {@link Browser#startTracing Browser.startTracing()} and {@link Browser#stopTracing Browser.stopTracing()} to
* create a trace file that can be opened in Chrome DevTools performance panel.
* <pre>{@code
* browser.startTracing(page, new Browser.StartTracingOptions()
* .setPath(Paths.get("trace.json")));
* page.goto('https://www.google.com');
* browser.stopTracing();
* }</pre>
*
* @param page Optional, if specified, tracing includes screenshots of the given page.
*/
default void startTracing(Page page) {
startTracing(page, null);
}
/**
* <strong>NOTE:</strong> Tracing is only supported on Chromium-based browsers.
*
* <p> You can use {@link Browser#startTracing Browser.startTracing()} and {@link Browser#stopTracing Browser.stopTracing()} to
* create a trace file that can be opened in Chrome DevTools performance panel.
* <pre>{@code
* browser.startTracing(page, new Browser.StartTracingOptions()
* .setPath(Paths.get("trace.json")));
* page.goto('https://www.google.com');
* browser.stopTracing();
* }</pre>
*/
default void startTracing() {
startTracing(null);
}
/**
* <strong>NOTE:</strong> Tracing is only supported on Chromium-based browsers.
*
* <p> You can use {@link Browser#startTracing Browser.startTracing()} and {@link Browser#stopTracing Browser.stopTracing()} to
* create a trace file that can be opened in Chrome DevTools performance panel.
* <pre>{@code
* browser.startTracing(page, new Browser.StartTracingOptions()
* .setPath(Paths.get("trace.json")));
* page.goto('https://www.google.com');
* browser.stopTracing();
* }</pre>
*
* @param page Optional, if specified, tracing includes screenshots of the given page.
*/
void startTracing(Page page, StartTracingOptions options);
/**
* <strong>NOTE:</strong> Tracing is only supported on Chromium-based browsers.
*
* <p> Returns the buffer with trace data.
*/
byte[] stopTracing();
/**
* Returns the browser version.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,8 @@ default void press(String key) {
*
* <p> <strong>NOTE:</strong> Modifier keys DO NOT effect {@code keyboard.type}. Holding down {@code Shift} will not type the text in upper case.
*
* <p> <strong>NOTE:</strong> For characters that are not on a US keyboard, only an {@code input} event will be sent.
*
* @param text A text to type into a focused element.
*/
default void type(String text) {
Expand All @@ -220,6 +222,8 @@ default void type(String text) {
*
* <p> <strong>NOTE:</strong> Modifier keys DO NOT effect {@code keyboard.type}. Holding down {@code Shift} will not type the text in upper case.
*
* <p> <strong>NOTE:</strong> For characters that are not on a US keyboard, only an {@code input} event will be sent.
*
* @param text A text to type into a focused element.
*/
void type(String text, TypeOptions options);
Expand Down
4 changes: 4 additions & 0 deletions playwright/src/main/java/com/microsoft/playwright/Page.java
Original file line number Diff line number Diff line change
Expand Up @@ -2077,6 +2077,8 @@ default void dispatchEvent(String selector, String type) {
*/
void dispatchEvent(String selector, String type, Object eventInit, DispatchEventOptions options);
/**
* This method changes the {@code CSS media type} through the {@code media} argument, and/or the {@code "prefers-colors-scheme"} media
* feature, using the {@code colorScheme} argument.
* <pre>{@code
* page.evaluate("() => matchMedia('screen').matches");
* // → true
Expand Down Expand Up @@ -2109,6 +2111,8 @@ default void emulateMedia() {
emulateMedia(null);
}
/**
* This method changes the {@code CSS media type} through the {@code media} argument, and/or the {@code "prefers-colors-scheme"} media
* feature, using the {@code colorScheme} argument.
* <pre>{@code
* page.evaluate("() => matchMedia('screen').matches");
* // → true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,7 @@
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.*;
import java.util.function.Consumer;

import static com.microsoft.playwright.impl.Serialization.gson;
Expand Down Expand Up @@ -183,6 +180,34 @@ public Page newPage(NewPageOptions options) {
return withLogging("Browser.newPage", () -> newPageImpl(options));
}

@Override
public void startTracing(Page page, StartTracingOptions options) {
withLogging("Browser.startTracing", () -> startTracingImpl(page, options));
}

private void startTracingImpl(Page page, StartTracingOptions options) {
if (options == null) {
options = new StartTracingOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
if (page != null) {
JsonObject jsonPage = new JsonObject();
jsonPage.addProperty("guid", ((PageImpl) page).guid);
params.add("page", jsonPage);
}
sendMessage("startTracing", params);
}

@Override
public byte[] stopTracing() {
return withLogging("Browser.stopTracing", () -> stopTracingImpl());
}

private byte[] stopTracingImpl() {
JsonObject json = sendMessage("stopTracing").getAsJsonObject();
return Base64.getDecoder().decode(json.get("binary").getAsString());
}

private Page newPageImpl(NewPageOptions options) {
BrowserContextImpl context = newContext(convertViaJson(options, NewContextOptions.class));
PageImpl page = context.newPage();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIf;
import org.junit.jupiter.api.condition.EnabledIf;

import java.io.OutputStreamWriter;
import java.util.Base64;
Expand All @@ -37,16 +38,18 @@ static void launchBrowser() {
launchBrowser(options);
}

static boolean isChromiumWindows() {
return isChromium() && isWindows;
}

@Test
void shouldThrowForMissingGlobalProxy() {
Browser browser = browserType.launch(createLaunchOptions());
try {
@EnabledIf(value="isChromiumWindows", disabledReason="Platform-specific")
void shouldThrowForMissingGlobalProxyOnChromiumWindows() {
try (Browser browser = browserType.launch(createLaunchOptions())) {
browser.newContext(new Browser.NewContextOptions().setProxy("localhost:" + server.PORT));
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Browser needs to be launched with the global proxy"));
} finally {
browser.close();
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* 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;

import com.google.gson.Gson;
import com.google.gson.JsonObject;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIf;
import org.junit.jupiter.api.io.TempDir;

import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.*;

@EnabledIf(value="com.microsoft.playwright.TestBase#isChromium", disabledReason="Chromium-only API")
public class TestChromiumTracing extends TestBase {
@Test
void shouldOutputATrace(@TempDir Path tempDir) {
Page page = browser.newPage();
Path outputTraceFile = tempDir.resolve("trace.json");
browser.startTracing(page, new Browser.StartTracingOptions()
.setScreenshots(true).setPath(outputTraceFile));
page.navigate(server.PREFIX + "/grid.html");
browser.stopTracing();
assertTrue(Files.exists(outputTraceFile));
page.close();
}

@Test
void shouldCreateDirectoriesAsNeeded(@TempDir Path tempDir) {
Page page = browser.newPage();
Path filePath = tempDir.resolve("these/are/directories/trace.json");
browser.startTracing(page, new Browser.StartTracingOptions()
.setScreenshots(true).setPath(filePath));
page.navigate(server.PREFIX + "/grid.html");
browser.stopTracing();
assertTrue(Files.exists(filePath));
page.close();
}

@Test
void shouldRunWithCustomCategoriesIfProvided(@TempDir Path tempDir) throws IOException {
Page page = browser.newPage();
Path outputTraceFile = tempDir.resolve("trace.json");
browser.startTracing(page, new Browser.StartTracingOptions()
.setPath(outputTraceFile)
.setCategories(asList("disabled-by-default-v8.cpu_profiler.hires")));
browser.stopTracing();
byte[] data = Files.readAllBytes(outputTraceFile);
JsonObject traceJson = new Gson().fromJson(new FileReader(outputTraceFile.toFile()), JsonObject.class);
assertTrue(traceJson.getAsJsonObject("metadata").get("trace-config")
.getAsString().contains("disabled-by-default-v8.cpu_profiler.hires"));
page.close();
}

@Test
void shouldThrowIfTracingOnTwoPages(@TempDir Path tempDir) {
Page page = browser.newPage();
Path outputTraceFile = tempDir.resolve("trace.json");
browser.startTracing(page, new Browser.StartTracingOptions()
.setPath(outputTraceFile));
Page newPage = browser.newPage();
try {
browser.startTracing(newPage, new Browser.StartTracingOptions()
.setPath(outputTraceFile));
fail("did not throw");
} catch (PlaywrightException e) {
}
newPage.close();
browser.stopTracing();
page.close();
}

@Test
void shouldReturnABuffer(@TempDir Path tempDir) throws IOException {
Page page = browser.newPage();
Path outputTraceFile = tempDir.resolve("trace.json");
browser.startTracing(page, new Browser.StartTracingOptions()
.setScreenshots(true).setPath(outputTraceFile));
page.navigate(server.PREFIX + "/grid.html");
byte[] trace = browser.stopTracing();
byte[] buf = Files.readAllBytes(outputTraceFile);
assertArrayEquals(buf, trace);
page.close();
}

@Test
void shouldWorkWithoutOptions() {
Page page = browser.newPage();
browser.startTracing(page);
page.navigate(server.PREFIX + "/grid.html");
byte[] trace = browser.stopTracing();
assertNotNull(trace);
page.close();
}

@Test
void shouldSupportABufferWithoutAPath() {
Page page = browser.newPage();
browser.startTracing(page, new Browser.StartTracingOptions().setScreenshots(true));
page.navigate(server.PREFIX + "/grid.html");
byte[] trace = browser.stopTracing();
assertTrue(new String(trace, StandardCharsets.UTF_8).contains("screenshot"));
page.close();
}
}
2 changes: 1 addition & 1 deletion scripts/CLI_VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.11.0-next-1619459952000
1.11.0-next-1620080583000

0 comments on commit a95f8f3

Please sign in to comment.