feat: roll driver, implement context tracing and network APIs (#444)

This commit is contained in:
Yury Semikhatsky 2021-05-17 23:32:44 +00:00 committed by GitHub
parent bcf879b8f0
commit 02bd360319
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 536 additions and 39 deletions

View File

@ -81,6 +81,55 @@ public interface BrowserContext extends AutoCloseable {
*/
void offPage(Consumer<Page> handler);
/**
* Emitted when a request is issued from any pages created through this context. The [request] object is read-only. To only
* listen for requests from a particular page, use {@link Page#onRequest Page.onRequest()}.
*
* <p> In order to intercept and mutate requests, see {@link BrowserContext#route BrowserContext.route()} or {@link Page#route
* Page.route()}.
*/
void onRequest(Consumer<Request> handler);
/**
* Removes handler that was previously added with {@link #onRequest onRequest(handler)}.
*/
void offRequest(Consumer<Request> handler);
/**
* Emitted when a request fails, for example by timing out. To only listen for failed requests from a particular page, use
* {@link Page#onRequestFailed Page.onRequestFailed()}.
*
* <p> <strong>NOTE:</strong> HTTP Error responses, such as 404 or 503, are still successful responses from HTTP standpoint, so request will complete
* with {@link BrowserContext#onRequestFinished BrowserContext.onRequestFinished()} event and not with {@link
* BrowserContext#onRequestFailed BrowserContext.onRequestFailed()}.
*/
void onRequestFailed(Consumer<Request> handler);
/**
* Removes handler that was previously added with {@link #onRequestFailed onRequestFailed(handler)}.
*/
void offRequestFailed(Consumer<Request> handler);
/**
* Emitted when a request finishes successfully after downloading the response body. For a successful response, the
* sequence of events is {@code request}, {@code response} and {@code requestfinished}. To listen for successful requests from a particular
* page, use {@link Page#onRequestFinished Page.onRequestFinished()}.
*/
void onRequestFinished(Consumer<Request> handler);
/**
* Removes handler that was previously added with {@link #onRequestFinished onRequestFinished(handler)}.
*/
void offRequestFinished(Consumer<Request> handler);
/**
* Emitted when [response] status and headers are received for a request. For a successful response, the sequence of events
* is {@code request}, {@code response} and {@code requestfinished}. To listen for response events from a particular page, use {@link
* Page#onResponse Page.onResponse()}.
*/
void onResponse(Consumer<Response> handler);
/**
* Removes handler that was previously added with {@link #onResponse onResponse(handler)}.
*/
void offResponse(Consumer<Response> handler);
class ExposeBindingOptions {
/**
* Whether to pass the argument as a handle, instead of passing by value. When passing a handle, only one argument is
@ -664,6 +713,7 @@ public interface BrowserContext extends AutoCloseable {
* Returns storage state for this browser context, contains current cookies and local storage snapshot.
*/
String storageState(StorageStateOptions options);
Tracing tracing();
/**
* Removes a route created with {@link BrowserContext#route BrowserContext.route()}. When {@code handler} is not specified,
* removes all routes for the {@code url}.

View File

@ -111,7 +111,7 @@ public interface BrowserType {
*/
public BrowserChannel channel;
/**
* Enable Chromium sandboxing. Defaults to {@code false}.
* Enable Chromium sandboxing. Defaults to {@code true}.
*/
public Boolean chromiumSandbox;
/**
@ -181,6 +181,10 @@ public interface BrowserType {
* disable timeout.
*/
public Double timeout;
/**
* If specified, traces are saved into this directory.
*/
public Path traceDir;
public LaunchOptions setArgs(List<String> args) {
this.args = args;
@ -253,6 +257,10 @@ public interface BrowserType {
this.timeout = timeout;
return this;
}
public LaunchOptions setTraceDir(Path traceDir) {
this.traceDir = traceDir;
return this;
}
}
class LaunchPersistentContextOptions {
/**
@ -302,8 +310,8 @@ public interface BrowserType {
public Map<String, String> env;
/**
* Path to a browser executable to run instead of the bundled one. If {@code executablePath} is a relative path, then it is
* resolved relative to the current working directory. **BEWARE**: Playwright is only guaranteed to work with the bundled
* Chromium, Firefox or WebKit, use at your own risk.
* resolved relative to the current working directory. Note that Playwright only works with the bundled Chromium, Firefox
* or WebKit, use at your own risk.
*/
public Path executablePath;
/**
@ -407,7 +415,6 @@ public interface BrowserType {
public ScreenSize screenSize;
/**
* Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
* Defaults to 0.
*/
public Double slowMo;
/**
@ -421,6 +428,10 @@ public interface BrowserType {
* metaZones.txt</a> for a list of supported timezone IDs.
*/
public String timezoneId;
/**
* If specified, traces are saved into this directory.
*/
public Path traceDir;
/**
* Specific user agent to use in this context.
*/
@ -589,6 +600,10 @@ public interface BrowserType {
this.timezoneId = timezoneId;
return this;
}
public LaunchPersistentContextOptions setTraceDir(Path traceDir) {
this.traceDir = traceDir;
return this;
}
public LaunchPersistentContextOptions setUserAgent(String userAgent) {
this.userAgent = userAgent;
return this;

View File

@ -23,8 +23,8 @@ import java.util.*;
/**
* {@code Download} objects are dispatched by page via the {@link Page#onDownload Page.onDownload()} event.
*
* <p> All the downloaded files belonging to the browser context are deleted when the browser context is closed. All downloaded
* files are deleted when the browser closes.
* <p> If {@code downloadsPath} isn't specified, all the downloaded files belonging to the browser context are deleted when the
* browser context is closed. And all downloaded files are deleted when the browser closes.
*
* <p> Download event is emitted once the download starts. Download path becomes available once download completes:
* <pre>{@code
@ -59,15 +59,20 @@ public interface Download {
* Returns download error if any. Will wait for the download to finish if necessary.
*/
String failure();
/**
* Get the page that the download belongs to.
*/
Page page();
/**
* Returns path to the downloaded file in case of successful download. The method will wait for the download to finish if
* necessary. The method throws when connected remotely.
*/
Path path();
/**
* Saves the download to a user-specified path. It is safe to call this method while the download is still in progress.
* Copy the download to a user-specified path. It is safe to call this method while the download is still in progress. Will
* wait for the download to finish if necessary.
*
* @param path Path where the download should be saved.
* @param path Path where the download should be copied.
*/
void saveAs(Path path);
/**

View File

@ -3142,7 +3142,8 @@ public interface Page extends AutoCloseable {
void press(String selector, String key, PressOptions options);
/**
* The method finds an element matching the specified selector within the page. If no elements match the selector, the
* return value resolves to {@code null}.
* return value resolves to {@code null}. To wait for an element on the page, use {@link Page#waitForSelector
* Page.waitForSelector()}.
*
* <p> Shortcut for main frame's {@link Frame#querySelector Frame.querySelector()}.
*

View File

@ -0,0 +1,110 @@
/*
* 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 java.nio.file.Path;
import java.util.*;
/**
* API for collecting and saving Playwright traces. Playwright traces can be opened using the Playwright CLI after
* Playwright script runs.
*
* <p> Start with specifying the folder traces will be stored in:
* <pre>{@code
* Browser browser = chromium.launch(new BrowserType.LaunchOptions().setTraceDir("trace"));
* BrowserContext context = browser.newContext();
* context.tracing.start(page, new Tracing.StartOptions()
* .setName("trace")
* .setScreenshots(true)
* .setSnapshots(true);
* Page page = context.newPage();
* page.goto("https://playwright.dev");
* context.tracing.stop();
* context.tracing.export(Paths.get("trace.zip")))
* }</pre>
*/
public interface Tracing {
class StartOptions {
/**
* If specified, the trace is going to be saved into the file with the given name inside the {@code traceDir} folder specified in
* {@link BrowserType#launch BrowserType.launch()}.
*/
public String name;
/**
* Whether to capture screenshots during tracing. Screenshots are used to build a timeline preview.
*/
public Boolean screenshots;
/**
* Whether to capture DOM snapshot on every action.
*/
public Boolean snapshots;
public StartOptions setName(String name) {
this.name = name;
return this;
}
public StartOptions setScreenshots(boolean screenshots) {
this.screenshots = screenshots;
return this;
}
public StartOptions setSnapshots(boolean snapshots) {
this.snapshots = snapshots;
return this;
}
}
/**
* Export trace into the file with the given name. Should be called after the tracing has stopped.
*
* @param path File to save the trace into.
*/
void export(Path path);
/**
* Start tracing.
* <pre>{@code
* context.tracing.start(page, new Tracing.StartOptions()
* .setName("trace")
* .setScreenshots(true)
* .setSnapshots(true);
* Page page = context.newPage();
* page.goto('https://playwright.dev');
* context.tracing.stop();
* context.tracing.export(Paths.get("trace.zip")))
* }</pre>
*/
default void start() {
start(null);
}
/**
* Start tracing.
* <pre>{@code
* context.tracing.start(page, new Tracing.StartOptions()
* .setName("trace")
* .setScreenshots(true)
* .setSnapshots(true);
* Page page = context.newPage();
* page.goto('https://playwright.dev');
* context.tracing.stop();
* context.tracing.export(Paths.get("trace.zip")))
* }</pre>
*/
void start(StartOptions options);
/**
* Stop tracing.
*/
void stop();
}

View File

@ -19,10 +19,7 @@ package com.microsoft.playwright.impl;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.BrowserContext;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.Route;
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.BindingCallback;
import com.microsoft.playwright.options.Cookie;
import com.microsoft.playwright.options.FunctionCallback;
@ -47,6 +44,7 @@ import static java.util.Arrays.asList;
class BrowserContextImpl extends ChannelOwner implements BrowserContext {
private final BrowserImpl browser;
private final TracingImpl tracing;
final List<PageImpl> pages = new ArrayList<>();
final Router routes = new Router();
private boolean isClosedOrClosing;
@ -59,6 +57,10 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
enum EventType {
CLOSE,
PAGE,
REQUEST,
REQUESTFAILED,
REQUESTFINISHED,
RESPONSE,
}
BrowserContextImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
@ -68,6 +70,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
} else {
browser = null;
}
this.tracing = new TracingImpl(this);
}
@Override
@ -90,6 +93,46 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
listeners.remove(EventType.PAGE, handler);
}
@Override
public void onRequest(Consumer<Request> handler) {
listeners.add(EventType.REQUEST, handler);
}
@Override
public void offRequest(Consumer<Request> handler) {
listeners.remove(EventType.REQUEST, handler);
}
@Override
public void onRequestFailed(Consumer<Request> handler) {
listeners.add(EventType.REQUESTFAILED, handler);
}
@Override
public void offRequestFailed(Consumer<Request> handler) {
listeners.remove(EventType.REQUESTFAILED, handler);
}
@Override
public void onRequestFinished(Consumer<Request> handler) {
listeners.add(EventType.REQUESTFINISHED, handler);
}
@Override
public void offRequestFinished(Consumer<Request> handler) {
listeners.remove(EventType.REQUESTFINISHED, handler);
}
@Override
public void onResponse(Consumer<Response> handler) {
listeners.add(EventType.RESPONSE, handler);
}
@Override
public void offResponse(Consumer<Response> handler) {
listeners.remove(EventType.RESPONSE, handler);
}
private <T> T waitForEventWithTimeout(EventType eventType, Runnable code, Double timeout) {
List<Waitable<T>> waitables = new ArrayList<>();
waitables.add(new WaitableEvent<>(listeners, eventType));
@ -356,6 +399,11 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
});
}
@Override
public Tracing tracing() {
return tracing;
}
@Override
public void unroute(String url, Consumer<Route> handler) {
unroute(new UrlMatcher(url), handler);
@ -418,6 +466,47 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
if (binding != null) {
bindingCall.call(binding);
}
} else if ("request".equals(event)) {
String guid = params.getAsJsonObject("request").get("guid").getAsString();
RequestImpl request = connection.getExistingObject(guid);
listeners.notify(EventType.REQUEST, request);
if (params.has("page")) {
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
page.listeners.notify(PageImpl.EventType.REQUEST, request);
}
} else if ("requestFailed".equals(event)) {
String guid = params.getAsJsonObject("request").get("guid").getAsString();
RequestImpl request = connection.getExistingObject(guid);
if (params.has("failureText")) {
request.failure = params.get("failureText").getAsString();
}
if (request.timing != null) {
request.timing.responseEnd = params.get("responseEndTiming").getAsDouble();
}
listeners.notify(EventType.REQUESTFAILED, request);
if (params.has("page")) {
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
page.listeners.notify(PageImpl.EventType.REQUESTFAILED, request);
}
} else if ("requestFinished".equals(event)) {
String guid = params.getAsJsonObject("request").get("guid").getAsString();
RequestImpl request = connection.getExistingObject(guid);
if (request.timing != null) {
request.timing.responseEnd = params.get("responseEndTiming").getAsDouble();
}
listeners.notify(EventType.REQUESTFINISHED, request);
if (params.has("page")) {
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
page.listeners.notify(PageImpl.EventType.REQUESTFINISHED, request);
}
} else if ("response".equals(event)) {
String guid = params.getAsJsonObject("response").get("guid").getAsString();
Response response = connection.getExistingObject(guid);
listeners.notify(EventType.RESPONSE, response);
if (params.has("page")) {
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
page.listeners.notify(PageImpl.EventType.RESPONSE, response);
}
} else if ("close".equals(event)) {
didClose();
}

View File

@ -18,15 +18,18 @@ package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Download;
import com.microsoft.playwright.Page;
import java.io.InputStream;
import java.nio.file.Path;
class DownloadImpl extends LoggingSupport implements Download {
private final PageImpl page;
private final ArtifactImpl artifact;
private final JsonObject initializer;
DownloadImpl(ArtifactImpl artifact, JsonObject initializer) {
DownloadImpl(PageImpl page, ArtifactImpl artifact, JsonObject initializer) {
this.page = page;
this.artifact = artifact;
this.initializer = initializer;
}
@ -56,6 +59,11 @@ class DownloadImpl extends LoggingSupport implements Download {
return withLogging("Download.failure", () -> artifact.failure());
}
@Override
public Page page() {
return page;
}
@Override
public Path path() {
return withLogging("Download.path", () -> artifact.pathAfterFinished());

View File

@ -48,7 +48,7 @@ public class PageImpl extends ChannelOwner implements Page {
private ViewportSize viewport;
private final Router routes = new Router();
private final Set<FrameImpl> frames = new LinkedHashSet<>();
private final ListenerCollection<EventType> listeners = new ListenerCollection<EventType>() {
final ListenerCollection<EventType> listeners = new ListenerCollection<EventType>() {
@Override
void add(EventType eventType, Consumer<?> listener) {
if (eventType == EventType.FILECHOOSER) {
@ -145,7 +145,7 @@ public class PageImpl extends ChannelOwner implements Page {
String artifactGuid = params.getAsJsonObject("artifact").get("guid").getAsString();
ArtifactImpl artifact = connection.getExistingObject(artifactGuid);
artifact.isRemote = browserContext.browser() != null && browserContext.browser().isRemote;
DownloadImpl download = new DownloadImpl(artifact, params);
DownloadImpl download = new DownloadImpl(this, artifact, params);
listeners.notify(EventType.DOWNLOAD, download);
} else if ("fileChooser".equals(event)) {
String guid = params.getAsJsonObject("element").get("guid").getAsString();
@ -170,25 +170,6 @@ public class PageImpl extends ChannelOwner implements Page {
listeners.notify(EventType.LOAD, this);
} else if ("domcontentloaded".equals(event)) {
listeners.notify(EventType.DOMCONTENTLOADED, this);
} else if ("request".equals(event)) {
String guid = params.getAsJsonObject("request").get("guid").getAsString();
Request request = connection.getExistingObject(guid);
listeners.notify(EventType.REQUEST, request);
} else if ("requestFailed".equals(event)) {
String guid = params.getAsJsonObject("request").get("guid").getAsString();
RequestImpl request = connection.getExistingObject(guid);
if (params.has("failureText")) {
request.failure = params.get("failureText").getAsString();
}
listeners.notify(EventType.REQUESTFAILED, request);
} else if ("requestFinished".equals(event)) {
String guid = params.getAsJsonObject("request").get("guid").getAsString();
Request request = connection.getExistingObject(guid);
listeners.notify(EventType.REQUESTFINISHED, request);
} else if ("response".equals(event)) {
String guid = params.getAsJsonObject("response").get("guid").getAsString();
Response response = connection.getExistingObject(guid);
listeners.notify(EventType.RESPONSE, response);
} else if ("frameAttached".equals(event)) {
String guid = params.getAsJsonObject("frame").get("guid").getAsString();
FrameImpl frame = connection.getExistingObject(guid);

View File

@ -0,0 +1,66 @@
/*
* 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.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Tracing;
import java.nio.file.Path;
import static com.microsoft.playwright.impl.Serialization.gson;
class TracingImpl implements Tracing {
private final BrowserContextImpl context;
TracingImpl(BrowserContextImpl context) {
this.context = context;
}
@Override
public void export(Path path) {
context.withLogging("Tracing.export", () -> exportImpl(path));
}
private void exportImpl(Path path) {
JsonObject json = context.sendMessage("tracingExport").getAsJsonObject();
ArtifactImpl artifact = context.connection.getExistingObject(json.getAsJsonObject("artifact").get("guid").getAsString());
if (context.browser().isRemote) {
artifact.isRemote = true;
}
artifact.saveAs(path);
artifact.delete();
}
@Override
public void start(StartOptions options) {
context.withLogging("Tracing.start", () -> startImpl(options));
}
private void startImpl(StartOptions options) {
if (options == null) {
options = new StartOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
context.sendMessage("tracingStart", params);
}
@Override
public void stop() {
context.withLogging("Tracing.stop", () -> context.sendMessage("tracingStop"));
}
}

View File

@ -25,5 +25,5 @@ public enum BrowserChannel {
MSEDGE_BETA,
MSEDGE_DEV,
MSEDGE_CANARY,
FIREFOX_STABLE
@Deprecated FIREFOX_STABLE
}

View File

@ -0,0 +1,93 @@
/*
* 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 org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.*;
public class TestBrowserContextNetworkEvents extends TestBase {
@Test
void BrowserContextEventsRequest() {
List<String> requests = new ArrayList<>();
context.onRequest(request -> requests.add(request.url()));
page.navigate(getServer().EMPTY_PAGE);
page.setContent("<a target=_blank rel=noopener href='/one-style.html'>yo</a>");
Page page1 = context.waitForPage(() -> page.click("a"));
page1.waitForLoadState();
assertEquals(asList(
getServer().EMPTY_PAGE,
getServer().PREFIX + "/one-style.html",
getServer().PREFIX + "/one-style.css"), requests);
}
@Test
void BrowserContextEventsResponse() {
List<String> responses = new ArrayList<>();
context.onResponse(response -> responses.add(response.url()));
page.navigate(getServer().EMPTY_PAGE);
page.setContent("<a target=_blank rel=noopener href='/one-style.html'>yo</a>");
Page page1 = context.waitForPage(() -> page.click("a"));
page1.waitForLoadState();
assertEquals(asList(
getServer().EMPTY_PAGE,
getServer().PREFIX + "/one-style.html",
getServer().PREFIX + "/one-style.css"), responses);
}
@Test
void BrowserContextEventsRequestFailed() {
getServer().setRoute("/one-style.css", exchange -> exchange.getResponseBody().close());
List<Request> failedRequests = new ArrayList<>();
context.onRequestFailed(request -> failedRequests.add(request));
page.navigate(getServer().PREFIX + "/one-style.html");
assertEquals(1, failedRequests.size());
assertTrue(failedRequests.get(0).url().contains("one-style.css"));
assertNull(failedRequests.get(0).response());
assertEquals("stylesheet", failedRequests.get(0).resourceType());
assertNotNull(failedRequests.get(0).frame());
}
@Test
void BrowserContextEventsRequestFinished() {
Request[] requestRef = {null};
context.onRequestFinished(r -> requestRef[0] = r);
Response response = page.navigate(getServer().EMPTY_PAGE);
Request request = response.request();
assertEquals(getServer().EMPTY_PAGE, request.url());
assertNotNull(request.response());
assertEquals(request.frame(), page.mainFrame());
assertEquals(getServer().EMPTY_PAGE, request.frame().url());
assertNull(request.failure());
}
@Test
void shouldFireEventsInProperOrder() {
List<String> events = new ArrayList<>();
context.onRequest(r -> events.add("request"));
context.onResponse(r -> events.add("response"));
context.onRequestFinished(r -> events.add("requestfinished"));
Response response = page.navigate(getServer().EMPTY_PAGE);
assertNull(response.finished());
assertEquals(asList("request", "response", "requestfinished"), events);
}
}

View File

@ -97,9 +97,9 @@ public class TestPageEventNetwork extends TestBase {
List<String> events = new ArrayList<>();
page.onRequest(request -> events.add("request"));
page.onResponse(response -> events.add("response"));
page.onRequestFinished(r -> events.add("requestfinished"));
Response response = page.navigate(getServer().EMPTY_PAGE);
assertNull(response.finished());
events.add("requestfinished");
assertEquals(asList("request", "response", "requestfinished"), events);
}

View File

@ -0,0 +1,75 @@
/*
* 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 org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class TestTracing extends TestBase {
@BeforeAll
static void launchBrowser(@TempDir Path tempDir) {
BrowserType.LaunchOptions options = createLaunchOptions();
options.setTraceDir(tempDir.resolve("trace-dir"));
launchBrowser(options);
}
@Test
void shouldCollectTrace1(@TempDir Path tempDir) {
context.tracing().start(new Tracing.StartOptions().setName("test")
.setScreenshots(true).setSnapshots(true));
page.navigate(getServer().EMPTY_PAGE);
page.setContent("<button>Click</button>");
page.click("'Click'");
page.close();
context.tracing().stop();
Path traceFile = tempDir.resolve("trace.zip");
context.tracing().export(traceFile);
assertTrue(Files.exists(traceFile));
}
@Test
void shouldCollectTwoTraces(@TempDir Path tempDir) {
context.tracing().start(new Tracing.StartOptions().setName("test1")
.setScreenshots(true).setSnapshots(true));
page.navigate(getServer().EMPTY_PAGE);
page.setContent("<button>Click</button>");
page.click("'Click'");
context.tracing().stop();
Path traceFile1 = tempDir.resolve("trace1.zip");
context.tracing().export(traceFile1);
context.tracing().start(new Tracing.StartOptions().setName("test2")
.setScreenshots(true).setSnapshots(true));
page.dblclick("'Click'");
page.close();
context.tracing().stop();
Path traceFile2 = tempDir.resolve("trace2.zip");
context.tracing().export(traceFile2);
assertTrue(Files.exists(traceFile1));
assertTrue(Files.exists(traceFile2));
}
}

View File

@ -1 +1 @@
1.11.0-1620331022000
1.12.0-next-1621019018000

View File

@ -867,7 +867,7 @@ class Interface extends TypeDefinition {
if ("Download".equals(jsonName)) {
output.add("import java.io.InputStream;");
}
if (asList("Page", "Frame", "ElementHandle", "FileChooser", "Browser", "BrowserContext", "BrowserType", "Download", "Route", "Selectors", "Video").contains(jsonName)) {
if (asList("Page", "Frame", "ElementHandle", "FileChooser", "Browser", "BrowserContext", "BrowserType", "Download", "Route", "Selectors", "Tracing", "Video").contains(jsonName)) {
output.add("import java.nio.file.Path;");
}
output.add("import java.util.*;");
@ -1016,6 +1016,10 @@ class Enum extends TypeDefinition {
}
enumValues.add(value.substring(1, value.length() - 1).replace("-", "_").toUpperCase());
}
if ("BrowserChannel".equals(jsonName)) {
// Firefox stable 'channel' was removed in 1.12.0
enumValues.add("@Deprecated FIREFOX_STABLE");
}
}
@Override