diff --git a/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java b/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java index d36d9c35..60f12432 100644 --- a/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java +++ b/playwright/src/main/java/com/microsoft/playwright/BrowserContext.java @@ -81,6 +81,55 @@ public interface BrowserContext extends AutoCloseable { */ void offPage(Consumer 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()}. + * + *

In order to intercept and mutate requests, see {@link BrowserContext#route BrowserContext.route()} or {@link Page#route + * Page.route()}. + */ + void onRequest(Consumer handler); + /** + * Removes handler that was previously added with {@link #onRequest onRequest(handler)}. + */ + void offRequest(Consumer 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()}. + * + *

NOTE: 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 handler); + /** + * Removes handler that was previously added with {@link #onRequestFailed onRequestFailed(handler)}. + */ + void offRequestFailed(Consumer 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 handler); + /** + * Removes handler that was previously added with {@link #onRequestFinished onRequestFinished(handler)}. + */ + void offRequestFinished(Consumer 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 handler); + /** + * Removes handler that was previously added with {@link #onResponse onResponse(handler)}. + */ + void offResponse(Consumer 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}. diff --git a/playwright/src/main/java/com/microsoft/playwright/BrowserType.java b/playwright/src/main/java/com/microsoft/playwright/BrowserType.java index 235b7ea3..b91cab45 100644 --- a/playwright/src/main/java/com/microsoft/playwright/BrowserType.java +++ b/playwright/src/main/java/com/microsoft/playwright/BrowserType.java @@ -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 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 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 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; diff --git a/playwright/src/main/java/com/microsoft/playwright/Download.java b/playwright/src/main/java/com/microsoft/playwright/Download.java index a00307bc..9b4be980 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Download.java +++ b/playwright/src/main/java/com/microsoft/playwright/Download.java @@ -23,8 +23,8 @@ import java.util.*; /** * {@code Download} objects are dispatched by page via the {@link Page#onDownload Page.onDownload()} event. * - *

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. + *

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. * *

Download event is emitted once the download starts. Download path becomes available once download completes: *

{@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);
   /**
diff --git a/playwright/src/main/java/com/microsoft/playwright/Page.java b/playwright/src/main/java/com/microsoft/playwright/Page.java
index 88f5572b..06a416ea 100644
--- a/playwright/src/main/java/com/microsoft/playwright/Page.java
+++ b/playwright/src/main/java/com/microsoft/playwright/Page.java
@@ -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()}.
    *
    * 

Shortcut for main frame's {@link Frame#querySelector Frame.querySelector()}. * diff --git a/playwright/src/main/java/com/microsoft/playwright/Tracing.java b/playwright/src/main/java/com/microsoft/playwright/Tracing.java new file mode 100644 index 00000000..fd0fe7e8 --- /dev/null +++ b/playwright/src/main/java/com/microsoft/playwright/Tracing.java @@ -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. + * + *

Start with specifying the folder traces will be stored in: + *

{@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")))
+ * }
+ */ +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. + *
{@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")))
+   * }
+ */ + default void start() { + start(null); + } + /** + * Start tracing. + *
{@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")))
+   * }
+ */ + void start(StartOptions options); + /** + * Stop tracing. + */ + void stop(); +} + diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java index 5669df00..f6b045e1 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java @@ -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 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 handler) { + listeners.add(EventType.REQUEST, handler); + } + + @Override + public void offRequest(Consumer handler) { + listeners.remove(EventType.REQUEST, handler); + } + + @Override + public void onRequestFailed(Consumer handler) { + listeners.add(EventType.REQUESTFAILED, handler); + } + + @Override + public void offRequestFailed(Consumer handler) { + listeners.remove(EventType.REQUESTFAILED, handler); + } + + @Override + public void onRequestFinished(Consumer handler) { + listeners.add(EventType.REQUESTFINISHED, handler); + } + + @Override + public void offRequestFinished(Consumer handler) { + listeners.remove(EventType.REQUESTFINISHED, handler); + } + + @Override + public void onResponse(Consumer handler) { + listeners.add(EventType.RESPONSE, handler); + } + + @Override + public void offResponse(Consumer handler) { + listeners.remove(EventType.RESPONSE, handler); + } + private T waitForEventWithTimeout(EventType eventType, Runnable code, Double timeout) { List> 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 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(); } diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/DownloadImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/DownloadImpl.java index e2bc1e31..69a9c568 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/DownloadImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/DownloadImpl.java @@ -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()); diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java index 0d9a1196..e8fc6392 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java @@ -48,7 +48,7 @@ public class PageImpl extends ChannelOwner implements Page { private ViewportSize viewport; private final Router routes = new Router(); private final Set frames = new LinkedHashSet<>(); - private final ListenerCollection listeners = new ListenerCollection() { + final ListenerCollection listeners = new ListenerCollection() { @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); diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/TracingImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/TracingImpl.java new file mode 100644 index 00000000..c55709c6 --- /dev/null +++ b/playwright/src/main/java/com/microsoft/playwright/impl/TracingImpl.java @@ -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")); + } +} diff --git a/playwright/src/main/java/com/microsoft/playwright/options/BrowserChannel.java b/playwright/src/main/java/com/microsoft/playwright/options/BrowserChannel.java index 63621d1e..5b104618 100644 --- a/playwright/src/main/java/com/microsoft/playwright/options/BrowserChannel.java +++ b/playwright/src/main/java/com/microsoft/playwright/options/BrowserChannel.java @@ -25,5 +25,5 @@ public enum BrowserChannel { MSEDGE_BETA, MSEDGE_DEV, MSEDGE_CANARY, - FIREFOX_STABLE + @Deprecated FIREFOX_STABLE } \ No newline at end of file diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextNetworkEvents.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextNetworkEvents.java new file mode 100644 index 00000000..6086eb70 --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextNetworkEvents.java @@ -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 requests = new ArrayList<>(); + context.onRequest(request -> requests.add(request.url())); + page.navigate(getServer().EMPTY_PAGE); + page.setContent("yo"); + 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 responses = new ArrayList<>(); + context.onResponse(response -> responses.add(response.url())); + page.navigate(getServer().EMPTY_PAGE); + page.setContent("yo"); + 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 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 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); + } +} diff --git a/playwright/src/test/java/com/microsoft/playwright/TestPageEventNetwork.java b/playwright/src/test/java/com/microsoft/playwright/TestPageEventNetwork.java index c02500a6..961055a9 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestPageEventNetwork.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestPageEventNetwork.java @@ -97,9 +97,9 @@ public class TestPageEventNetwork extends TestBase { List 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); } diff --git a/playwright/src/test/java/com/microsoft/playwright/TestTracing.java b/playwright/src/test/java/com/microsoft/playwright/TestTracing.java new file mode 100644 index 00000000..f528cb72 --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/TestTracing.java @@ -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(""); + 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(""); + 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)); + } + +} diff --git a/scripts/CLI_VERSION b/scripts/CLI_VERSION index 18e2b25b..fb648f03 100644 --- a/scripts/CLI_VERSION +++ b/scripts/CLI_VERSION @@ -1 +1 @@ -1.11.0-1620331022000 +1.12.0-next-1621019018000 diff --git a/tools/api-generator/src/main/java/com/microsoft/playwright/tools/ApiGenerator.java b/tools/api-generator/src/main/java/com/microsoft/playwright/tools/ApiGenerator.java index d78de0bb..8a109649 100644 --- a/tools/api-generator/src/main/java/com/microsoft/playwright/tools/ApiGenerator.java +++ b/tools/api-generator/src/main/java/com/microsoft/playwright/tools/ApiGenerator.java @@ -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