1
0
mirror of synced 2026-08-04 14:36:55 +00:00

fix(api): delete Deferred interface, use waitFor* with callback (#218)

This commit is contained in:
Yury Semikhatsky
2021-01-20 16:33:26 -08:00
committed by GitHub
parent 6c71f15866
commit 58e97f1b36
45 changed files with 1442 additions and 904 deletions
@@ -54,6 +54,11 @@ public interface Browser {
void addListener(EventType type, Listener<EventType> listener);
void removeListener(EventType type, Listener<EventType> listener);
void onDisconnected(Runnable handler);
void offDisconnected(Runnable handler);
class NewContextOptions {
public class Proxy {
/**
@@ -94,19 +94,6 @@ public interface BrowserContext {
}
}
class FutureEventOptions {
public Double timeout;
public Predicate<Event<EventType>> predicate;
public FutureEventOptions withTimeout(double millis) {
timeout = millis;
return this;
}
public FutureEventOptions withPredicate(Predicate<Event<EventType>> predicate) {
this.predicate = predicate;
return this;
}
}
enum EventType {
CLOSE,
PAGE,
@@ -114,6 +101,24 @@ public interface BrowserContext {
void addListener(EventType type, Listener<EventType> listener);
void removeListener(EventType type, Listener<EventType> listener);
void onClose(Runnable handler);
void offClose(Runnable handler);
void onPage(Consumer<Page> handler);
void offPage(Consumer<Page> handler);
class WaitForPageOptions {
public Double timeout;
public WaitForPageOptions withTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
Page waitForPage(Runnable code, WaitForPageOptions options);
default Page waitForPage(Runnable code) { return waitForPage(code, null); }
class AddCookie {
/**
* **required**
@@ -465,21 +470,5 @@ public interface BrowserContext {
* @param handler Optional handler function used to register a routing with [{@code method: BrowserContext.route}].
*/
void unroute(Predicate<String> url, Consumer<Route> handler);
default Deferred<Event<EventType>> futureEvent(EventType event) {
return futureEvent(event, (FutureEventOptions) null);
}
default Deferred<Event<EventType>> futureEvent(EventType event, Predicate<Event<EventType>> predicate) {
FutureEventOptions options = new FutureEventOptions();
options.predicate = predicate;
return futureEvent(event, options);
}
/**
* Waits for event to fire and passes its value into the predicate function. Returns when the predicate returns truthy
* value. Will throw an error if the context closes before the event is fired. Returns the event data value.
*
*
* @param event Event name, same one would pass into {@code browserContext.on(event)}.
*/
Deferred<Event<EventType>> futureEvent(EventType event, FutureEventOptions options);
}
@@ -1,21 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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;
public interface Deferred<T> {
T get();
}
@@ -705,7 +705,7 @@ public interface Frame {
return this;
}
}
class FutureNavigationOptions {
class WaitForNavigationOptions {
/**
* Maximum operation time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be
* changed by using the [{@code method: BrowserContext.setDefaultNavigationTimeout}],
@@ -727,23 +727,23 @@ public interface Frame {
*/
public LoadState waitUntil;
public FutureNavigationOptions withTimeout(double timeout) {
public WaitForNavigationOptions withTimeout(double timeout) {
this.timeout = timeout;
return this;
}
public FutureNavigationOptions withUrl(String glob) {
public WaitForNavigationOptions withUrl(String glob) {
this.glob = glob;
return this;
}
public FutureNavigationOptions withUrl(Pattern pattern) {
public WaitForNavigationOptions withUrl(Pattern pattern) {
this.pattern = pattern;
return this;
}
public FutureNavigationOptions withUrl(Predicate<String> predicate) {
public WaitForNavigationOptions withUrl(Predicate<String> predicate) {
this.predicate = predicate;
return this;
}
public FutureNavigationOptions withWaitUntil(LoadState waitUntil) {
public WaitForNavigationOptions withWaitUntil(LoadState waitUntil) {
this.waitUntil = waitUntil;
return this;
}
@@ -1338,9 +1338,7 @@ public interface Frame {
* - {@code 'networkidle'} - wait until there are no network connections for at least {@code 500} ms.
*/
void waitForLoadState(LoadState state, WaitForLoadStateOptions options);
default Deferred<Response> futureNavigation() {
return futureNavigation(null);
}
default Response waitForNavigation(Runnable code) { return waitForNavigation(code, null); }
/**
* Returns the main resource response. In case of multiple redirects, the navigation will resolve with the response of the
* last redirect. In case of navigation to a different anchor or navigation due to History API usage, the navigation will
@@ -1352,7 +1350,7 @@ public interface Frame {
* <p> <strong>NOTE</strong> Usage of the [History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API) to change the URL is
* considered a navigation.
*/
Deferred<Response> futureNavigation(FutureNavigationOptions options);
Response waitForNavigation(Runnable code, WaitForNavigationOptions options);
default ElementHandle waitForSelector(String selector) {
return waitForSelector(selector, null);
}
@@ -74,19 +74,6 @@ public interface Page {
String stack();
}
class FutureEventOptions {
public Double timeout;
public Predicate<Event<EventType>> predicate;
public FutureEventOptions withTimeout(double millis) {
timeout = millis;
return this;
}
public FutureEventOptions withPredicate(Predicate<Event<EventType>> predicate) {
this.predicate = predicate;
return this;
}
}
enum EventType {
CLOSE,
CONSOLE,
@@ -111,6 +98,195 @@ public interface Page {
void addListener(EventType type, Listener<EventType> listener);
void removeListener(EventType type, Listener<EventType> listener);
void onClose(Runnable handler);
void offClose(Runnable handler);
void onConsole(Consumer<ConsoleMessage> handler);
void offConsole(Consumer<ConsoleMessage> handler);
void onCrash(Runnable handler);
void offCrash(Runnable handler);
void onDialog(Consumer<Dialog> handler);
void offDialog(Consumer<Dialog> handler);
void onDomContentLoaded(Runnable handler);
void offDomContentLoaded(Runnable handler);
void onDownload(Consumer<Download> handler);
void offDownload(Consumer<Download> handler);
void onFileChooser(Consumer<FileChooser> handler);
void offFileChooser(Consumer<FileChooser> handler);
void onFrameAttached(Consumer<Frame> handler);
void offFrameAttached(Consumer<Frame> handler);
void onFrameDetached(Consumer<Frame> handler);
void offFrameDetached(Consumer<Frame> handler);
void onFrameNavigated(Consumer<Frame> handler);
void offFrameNavigated(Consumer<Frame> handler);
void onLoad(Runnable handler);
void offLoad(Runnable handler);
void onPageError(Consumer<Error> handler);
void offPageError(Consumer<Error> handler);
void onPopup(Consumer<Page> handler);
void offPopup(Consumer<Page> handler);
void onRequest(Consumer<Request> handler);
void offRequest(Consumer<Request> handler);
void onRequestFailed(Consumer<Request> handler);
void offRequestFailed(Consumer<Request> handler);
void onRequestFinished(Consumer<Request> handler);
void offRequestFinished(Consumer<Request> handler);
void onResponse(Consumer<Response> handler);
void offResponse(Consumer<Response> handler);
void onWebSocket(Consumer<WebSocket> handler);
void offWebSocket(Consumer<WebSocket> handler);
void onWorker(Consumer<Worker> handler);
void offWorker(Consumer<Worker> handler);
class WaitForCloseOptions {
public Double timeout;
public WaitForCloseOptions withTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
Page waitForClose(Runnable code, WaitForCloseOptions options);
default Page waitForClose(Runnable code) { return waitForClose(code, null); }
class WaitForConsoleOptions {
public Double timeout;
public WaitForConsoleOptions withTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
ConsoleMessage waitForConsole(Runnable code, WaitForConsoleOptions options);
default ConsoleMessage waitForConsole(Runnable code) { return waitForConsole(code, null); }
class WaitForDownloadOptions {
public Double timeout;
public WaitForDownloadOptions withTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
Download waitForDownload(Runnable code, WaitForDownloadOptions options);
default Download waitForDownload(Runnable code) { return waitForDownload(code, null); }
class WaitForFileChooserOptions {
public Double timeout;
public WaitForFileChooserOptions withTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
FileChooser waitForFileChooser(Runnable code, WaitForFileChooserOptions options);
default FileChooser waitForFileChooser(Runnable code) { return waitForFileChooser(code, null); }
class WaitForFrameAttachedOptions {
public Double timeout;
public WaitForFrameAttachedOptions withTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
Frame waitForFrameAttached(Runnable code, WaitForFrameAttachedOptions options);
default Frame waitForFrameAttached(Runnable code) { return waitForFrameAttached(code, null); }
class WaitForFrameDetachedOptions {
public Double timeout;
public WaitForFrameDetachedOptions withTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
Frame waitForFrameDetached(Runnable code, WaitForFrameDetachedOptions options);
default Frame waitForFrameDetached(Runnable code) { return waitForFrameDetached(code, null); }
class WaitForFrameNavigatedOptions {
public Double timeout;
public WaitForFrameNavigatedOptions withTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
Frame waitForFrameNavigated(Runnable code, WaitForFrameNavigatedOptions options);
default Frame waitForFrameNavigated(Runnable code) { return waitForFrameNavigated(code, null); }
class WaitForPageErrorOptions {
public Double timeout;
public WaitForPageErrorOptions withTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
Error waitForPageError(Runnable code, WaitForPageErrorOptions options);
default Error waitForPageError(Runnable code) { return waitForPageError(code, null); }
class WaitForPopupOptions {
public Double timeout;
public WaitForPopupOptions withTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
Page waitForPopup(Runnable code, WaitForPopupOptions options);
default Page waitForPopup(Runnable code) { return waitForPopup(code, null); }
class WaitForRequestFailedOptions {
public Double timeout;
public WaitForRequestFailedOptions withTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
Request waitForRequestFailed(Runnable code, WaitForRequestFailedOptions options);
default Request waitForRequestFailed(Runnable code) { return waitForRequestFailed(code, null); }
class WaitForRequestFinishedOptions {
public Double timeout;
public WaitForRequestFinishedOptions withTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
Request waitForRequestFinished(Runnable code, WaitForRequestFinishedOptions options);
default Request waitForRequestFinished(Runnable code) { return waitForRequestFinished(code, null); }
class WaitForWebSocketOptions {
public Double timeout;
public WaitForWebSocketOptions withTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
WebSocket waitForWebSocket(Runnable code, WaitForWebSocketOptions options);
default WebSocket waitForWebSocket(Runnable code) { return waitForWebSocket(code, null); }
class WaitForWorkerOptions {
public Double timeout;
public WaitForWorkerOptions withTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
Worker waitForWorker(Runnable code, WaitForWorkerOptions options);
default Worker waitForWorker(Runnable code) { return waitForWorker(code, null); }
enum LoadState { LOAD, DOMCONTENTLOADED, NETWORKIDLE }
class AddScriptTagParams {
/**
@@ -1167,7 +1343,7 @@ public interface Page {
return this;
}
}
class FutureNavigationOptions {
class WaitForNavigationOptions {
/**
* Maximum operation time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be
* changed by using the [{@code method: BrowserContext.setDefaultNavigationTimeout}],
@@ -1189,47 +1365,47 @@ public interface Page {
*/
public Frame.LoadState waitUntil;
public FutureNavigationOptions withTimeout(double timeout) {
public WaitForNavigationOptions withTimeout(double timeout) {
this.timeout = timeout;
return this;
}
public FutureNavigationOptions withUrl(String glob) {
public WaitForNavigationOptions withUrl(String glob) {
this.glob = glob;
return this;
}
public FutureNavigationOptions withUrl(Pattern pattern) {
public WaitForNavigationOptions withUrl(Pattern pattern) {
this.pattern = pattern;
return this;
}
public FutureNavigationOptions withUrl(Predicate<String> predicate) {
public WaitForNavigationOptions withUrl(Predicate<String> predicate) {
this.predicate = predicate;
return this;
}
public FutureNavigationOptions withWaitUntil(Frame.LoadState waitUntil) {
public WaitForNavigationOptions withWaitUntil(Frame.LoadState waitUntil) {
this.waitUntil = waitUntil;
return this;
}
}
class FutureRequestOptions {
class WaitForRequestOptions {
/**
* Maximum wait time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable the timeout. The default value can be
* changed by using the [{@code method: Page.setDefaultTimeout}] method.
*/
public Double timeout;
public FutureRequestOptions withTimeout(double timeout) {
public WaitForRequestOptions withTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
class FutureResponseOptions {
class WaitForResponseOptions {
/**
* Maximum wait time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable the timeout. The default value can be
* changed by using the [{@code method: BrowserContext.setDefaultTimeout}] or [{@code method: Page.setDefaultTimeout}] methods.
*/
public Double timeout;
public FutureResponseOptions withTimeout(double timeout) {
public WaitForResponseOptions withTimeout(double timeout) {
this.timeout = timeout;
return this;
}
@@ -2044,23 +2220,6 @@ public interface Page {
*/
Video video();
Viewport viewportSize();
default Deferred<Event<EventType>> futureEvent(EventType event) {
return futureEvent(event, (FutureEventOptions) null);
}
default Deferred<Event<EventType>> futureEvent(EventType event, Predicate<Event<EventType>> predicate) {
FutureEventOptions options = new FutureEventOptions();
options.predicate = predicate;
return futureEvent(event, options);
}
/**
* Returns the event data value.
*
* <p> Waits for event to fire and passes its value into the predicate function. Returns when the predicate returns truthy
* value. Will throw an error if the page is closed before the event is fired.
*
* @param event Event name, same one would pass into {@code page.on(event)}.
*/
Deferred<Event<EventType>> futureEvent(EventType event, FutureEventOptions options);
default JSHandle waitForFunction(String pageFunction, Object arg) {
return waitForFunction(pageFunction, arg, null);
}
@@ -2101,9 +2260,7 @@ public interface Page {
* - {@code 'networkidle'} - wait until there are no network connections for at least {@code 500} ms.
*/
void waitForLoadState(LoadState state, WaitForLoadStateOptions options);
default Deferred<Response> futureNavigation() {
return futureNavigation(null);
}
default Response waitForNavigation(Runnable code) { return waitForNavigation(code, null); }
/**
* Returns the main resource response. In case of multiple redirects, the navigation will resolve with the response of the
* last redirect. In case of navigation to a different anchor or navigation due to History API usage, the navigation will
@@ -2118,19 +2275,21 @@ public interface Page {
*
* <p> Shortcut for main frame's [{@code method: Frame.waitForNavigation}].
*/
Deferred<Response> futureNavigation(FutureNavigationOptions options);
default Deferred<Request> futureRequest(String urlGlob) { return futureRequest(urlGlob, null); }
default Deferred<Request> futureRequest(Pattern urlPattern) { return futureRequest(urlPattern, null); }
default Deferred<Request> futureRequest(Predicate<String> urlPredicate) { return futureRequest(urlPredicate, null); }
Deferred<Request> futureRequest(String urlGlob, FutureRequestOptions options);
Deferred<Request> futureRequest(Pattern urlPattern, FutureRequestOptions options);
Deferred<Request> futureRequest(Predicate<String> urlPredicate, FutureRequestOptions options);
default Deferred<Response> futureResponse(String urlGlob) { return futureResponse(urlGlob, null); }
default Deferred<Response> futureResponse(Pattern urlPattern) { return futureResponse(urlPattern, null); }
default Deferred<Response> futureResponse(Predicate<String> urlPredicate) { return futureResponse(urlPredicate, null); }
Deferred<Response> futureResponse(String urlGlob, FutureResponseOptions options);
Deferred<Response> futureResponse(Pattern urlPattern, FutureResponseOptions options);
Deferred<Response> futureResponse(Predicate<String> urlPredicate, FutureResponseOptions options);
Response waitForNavigation(Runnable code, WaitForNavigationOptions options);
Request waitForRequest(Runnable code);
default Request waitForRequest(Runnable code, String urlGlob) { return waitForRequest(code, urlGlob, null); }
default Request waitForRequest(Runnable code, Pattern urlPattern) { return waitForRequest(code, urlPattern, null); }
default Request waitForRequest(Runnable code, Predicate<String> urlPredicate) { return waitForRequest(code, urlPredicate, null); }
Request waitForRequest(Runnable code, String urlGlob, WaitForRequestOptions options);
Request waitForRequest(Runnable code, Pattern urlPattern, WaitForRequestOptions options);
Request waitForRequest(Runnable code, Predicate<String> urlPredicate, WaitForRequestOptions options);
Response waitForResponse(Runnable code);
default Response waitForResponse(Runnable code, String urlGlob) { return waitForResponse(code, urlGlob, null); }
default Response waitForResponse(Runnable code, Pattern urlPattern) { return waitForResponse(code, urlPattern, null); }
default Response waitForResponse(Runnable code, Predicate<String> urlPredicate) { return waitForResponse(code, urlPredicate, null); }
Response waitForResponse(Runnable code, String urlGlob, WaitForResponseOptions options);
Response waitForResponse(Runnable code, Pattern urlPattern, WaitForResponseOptions options);
Response waitForResponse(Runnable code, Predicate<String> urlPredicate, WaitForResponseOptions options);
default ElementHandle waitForSelector(String selector) {
return waitForSelector(selector, null);
}
@@ -17,6 +17,7 @@
package com.microsoft.playwright;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
@@ -28,19 +29,6 @@ public interface WebSocket {
String text();
}
class FutureEventOptions {
public Double timeout;
public Predicate<Event<EventType>> predicate;
public FutureEventOptions withTimeout(double millis) {
timeout = millis;
return this;
}
public FutureEventOptions withPredicate(Predicate<Event<EventType>> predicate) {
this.predicate = predicate;
return this;
}
}
enum EventType {
CLOSE,
FRAMERECEIVED,
@@ -50,6 +38,50 @@ public interface WebSocket {
void addListener(EventType type, Listener<EventType> listener);
void removeListener(EventType type, Listener<EventType> listener);
void onClose(Runnable handler);
void offClose(Runnable handler);
void onFrameReceived(Consumer<FrameData> handler);
void offFrameReceived(Consumer<FrameData> handler);
void onFrameSent(Consumer<FrameData> handler);
void offFrameSent(Consumer<FrameData> handler);
void onSocketError(Consumer<String> handler);
void offSocketError(Consumer<String> handler);
class WaitForFrameReceivedOptions {
public Double timeout;
public WaitForFrameReceivedOptions withTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
FrameData waitForFrameReceived(Runnable code, WaitForFrameReceivedOptions options);
default FrameData waitForFrameReceived(Runnable code) { return waitForFrameReceived(code, null); }
class WaitForFrameSentOptions {
public Double timeout;
public WaitForFrameSentOptions withTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
FrameData waitForFrameSent(Runnable code, WaitForFrameSentOptions options);
default FrameData waitForFrameSent(Runnable code) { return waitForFrameSent(code, null); }
class WaitForSocketErrorOptions {
public Double timeout;
public WaitForSocketErrorOptions withTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
String waitForSocketError(Runnable code, WaitForSocketErrorOptions options);
default String waitForSocketError(Runnable code) { return waitForSocketError(code, null); }
/**
* Indicates that the web socket has been closed.
*/
@@ -58,22 +90,5 @@ public interface WebSocket {
* Contains the URL of the WebSocket.
*/
String url();
default Deferred<Event<EventType>> futureEvent(EventType event) {
return futureEvent(event, (FutureEventOptions) null);
}
default Deferred<Event<EventType>> futureEvent(EventType event, Predicate<Event<EventType>> predicate) {
FutureEventOptions options = new FutureEventOptions();
options.predicate = predicate;
return futureEvent(event, options);
}
/**
* Returns the event data value.
*
* <p> Waits for event to fire and passes its value into the predicate function. Returns when the predicate returns truthy
* value. Will throw an error if the webSocket is closed before the event is fired.
*
* @param event Event name, same one would pass into {@code webSocket.on(event)}.
*/
Deferred<Event<EventType>> futureEvent(EventType event, FutureEventOptions options);
}
@@ -17,6 +17,7 @@
package com.microsoft.playwright;
import java.util.*;
import java.util.function.Consumer;
/**
* The Worker class represents a [WebWorker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API). {@code worker}
@@ -30,6 +31,21 @@ public interface Worker {
void addListener(EventType type, Listener<EventType> listener);
void removeListener(EventType type, Listener<EventType> listener);
void onClose(Consumer<Worker> handler);
void offClose(Consumer<Worker> handler);
class WaitForCloseOptions {
public Double timeout;
public WaitForCloseOptions withTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
Worker waitForClose(Runnable code, WaitForCloseOptions options);
default Worker waitForClose(Runnable code) { return waitForClose(code, null); }
default Object evaluate(String pageFunction) {
return evaluate(pageFunction, null);
}
@@ -64,6 +80,5 @@ public interface Worker {
*/
JSHandle evaluateHandle(String pageFunction, Object arg);
String url();
Deferred<Event<EventType>> futureEvent(EventType event);
}
@@ -65,6 +65,43 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
listeners.remove(type, listener);
}
@Override
public void onClose(Runnable handler) {
listeners.add(EventType.CLOSE, handler);
}
@Override
public void offClose(Runnable handler) {
listeners.remove(EventType.CLOSE, handler);
}
@Override
public void onPage(Consumer<Page> handler) {
listeners.add(EventType.PAGE, handler);
}
@Override
public void offPage(Consumer<Page> handler) {
listeners.remove(EventType.PAGE, handler);
}
private <T> T waitForEventWithTimeout(EventType eventType, Runnable code, Double timeout) {
List<Waitable<T>> waitables = new ArrayList<>();
waitables.add(new WaitableEvent<>(listeners, eventType)
.apply(event -> (T) event.data()));
waitables.add(new WaitableContextClose<>());
waitables.add(timeoutSettings.createWaitable(timeout));
return runUntil(code, new WaitableRace<>(waitables));
}
@Override
public Page waitForPage(Runnable code, WaitForPageOptions options) {
if (options == null) {
options = new WaitForPageOptions();
}
return waitForEventWithTimeout(EventType.PAGE, code, options.timeout);
}
@Override
public void close() {
withLogging("BrowserContext.close", () -> closeImpl());
@@ -351,18 +388,6 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
}
@Override
public Deferred<Event<EventType>> futureEvent(EventType event, FutureEventOptions options) {
if (options == null) {
options = new FutureEventOptions();
}
List<Waitable<Event<EventType>>> waitables = new ArrayList<>();
waitables.add(new WaitableEvent<>(listeners, event, options.predicate));
waitables.add(new WaitableContextClose<>());
waitables.add(timeoutSettings.createWaitable(options.timeout));
return toDeferred(new WaitableRace<>(waitables));
}
private void unroute(UrlMatcher matcher, Consumer<Route> handler) {
withLogging("BrowserContext.unroute", () -> {
routes.remove(matcher, handler);
@@ -51,6 +51,16 @@ class BrowserImpl extends ChannelOwner implements Browser {
listeners.remove(type, listener);
}
@Override
public void onDisconnected(Runnable handler) {
listeners.add(EventType.DISCONNECTED, handler);
}
@Override
public void offDisconnected(Runnable handler) {
listeners.remove(EventType.DISCONNECTED, handler);
}
@Override
public void close() {
withLogging("Browser.close", () -> closeImpl());
@@ -18,7 +18,6 @@ package com.microsoft.playwright.impl;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Deferred;
import java.util.ArrayList;
import java.util.HashMap;
@@ -80,26 +79,16 @@ class ChannelOwner extends LoggingSupport {
return connection.sendMessage(guid, method, params);
}
@SuppressWarnings("unchecked")
<T> Deferred<T> toDeferred(Waitable<T> waitable) {
return new Deferred<T>() {
Exception constructionStackTrace = new Exception();
@Override
public T get() {
constructionStackTrace = null;
while (!waitable.isDone()) {
connection.processOneMessage();
}
return (T) waitable.get();
<T> T runUntil(Runnable code, Waitable<T> waitable) {
try {
code.run();
while (!waitable.isDone()) {
connection.processOneMessage();
}
@Override
protected void finalize() {
if (constructionStackTrace != null) {
connection.addUnusedDeferredObject(constructionStackTrace);
}
}
};
return waitable.get();
} finally {
waitable.dispose();
}
}
void handleEvent(String event, JsonObject parameters) {
@@ -54,7 +54,6 @@ public class Connection {
private final Root root;
private int lastId = 0;
private final Map<Integer, WaitableResult<JsonElement>> callbacks = new HashMap<>();
private final Deque<Exception> unusedDeferredObjects = new ArrayDeque<>();
class Root extends ChannelOwner {
Root(Connection connection) {
@@ -72,7 +71,7 @@ public class Connection {
}
public JsonElement sendMessage(String guid, String method, JsonObject params) {
return root.toDeferred(sendMessageAsync(guid, method, params)).get();
return root.runUntil(() -> {}, sendMessageAsync(guid, method, params));
}
public WaitableResult<JsonElement> sendMessageAsync(String guid, String method, JsonObject params) {
@@ -254,26 +253,4 @@ public class Connection {
return result;
}
void addUnusedDeferredObject(Exception exception) {
unusedDeferredObjects.add(exception);
if (unusedDeferredObjects.size() > 10) {
unusedDeferredObjects.removeLast();
}
}
void checkNoUnusedDeferredObjects() {
if (unusedDeferredObjects.isEmpty()) {
return;
}
List<String> chunks = new ArrayList<>();
chunks.add("Method get() has not been called on some Deferred<> objects. This indicates a " +
"bug in the client code. Here are some stack traces where such objects were constructed");
for (Exception e : unusedDeferredObjects) {
StringWriter writer = new StringWriter();
e.printStackTrace(new PrintWriter(writer));
chunks.add(writer.toString());
}
throw new PlaywrightException(String.join("\n", chunks));
}
}
@@ -16,16 +16,13 @@
package com.microsoft.playwright.impl;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Deferred;
import com.microsoft.playwright.ElementHandle;
import com.microsoft.playwright.FileChooser;
import com.microsoft.playwright.Frame;
import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Base64;
@@ -16,13 +16,11 @@
package com.microsoft.playwright.impl;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.*;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
@@ -674,7 +672,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
waitables.add(new WaitForLoadStateHelper(state));
waitables.add(page.createWaitForCloseHelper());
waitables.add(page.createWaitableTimeout(options.timeout));
toDeferred(new WaitableRace<>(waitables)).get();
runUntil(() -> {}, new WaitableRace<>(waitables));
}
private class WaitForLoadStateHelper implements Waitable<Void>, Listener<InternalEventType> {
@@ -712,7 +710,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
}
private class FutureNavigationHelper implements Waitable<Response>, Listener<InternalEventType> {
private class WaitForNavigationHelper implements Waitable<Response>, Listener<InternalEventType> {
private final UrlMatcher matcher;
private final LoadState expectedLoadState;
private WaitForLoadStateHelper loadStateHelper;
@@ -720,7 +718,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
private RequestImpl request;
private RuntimeException exception;
FutureNavigationHelper(UrlMatcher matcher, LoadState expectedLoadState) {
WaitForNavigationHelper(UrlMatcher matcher, LoadState expectedLoadState) {
this.matcher = matcher;
this.expectedLoadState = expectedLoadState;
internalListeners.add(InternalEventType.NAVIGATED, this);
@@ -780,13 +778,13 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
@Override
public Deferred<Response> futureNavigation(FutureNavigationOptions options) {
return withLoggingDeferred("Frame.futureNavigation", () -> futureNavigationImpl(options));
public Response waitForNavigation(Runnable code, WaitForNavigationOptions options) {
return withLogging("Frame.waitForNavigation", () -> waitForNavigationImpl(code, options));
}
Deferred<Response> futureNavigationImpl(FutureNavigationOptions options) {
Response waitForNavigationImpl(Runnable code, WaitForNavigationOptions options) {
if (options == null) {
options = new FutureNavigationOptions();
options = new WaitForNavigationOptions();
}
if (options.waitUntil == null) {
options.waitUntil = LOAD;
@@ -794,11 +792,11 @@ public class FrameImpl extends ChannelOwner implements Frame {
List<Waitable<Response>> waitables = new ArrayList<>();
UrlMatcher matcher = UrlMatcher.forOneOf(options.glob, options.pattern, options.predicate);
waitables.add(new FutureNavigationHelper(matcher, options.waitUntil));
waitables.add(new WaitForNavigationHelper(matcher, options.waitUntil));
waitables.add(page.createWaitForCloseHelper());
waitables.add(page.createWaitableFrameDetach(this));
waitables.add(page.createWaitableNavigationTimeout(options.timeout));
return toDeferred(new WaitableRace<>(waitables));
return runUntil(code, new WaitableRace<>(waitables));
}
private static String toProtocol(WaitForSelectorOptions.State state) {
@@ -834,13 +832,13 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
void waitForTimeoutImpl(double timeout) {
toDeferred(new WaitableTimeout<Void>(timeout) {
runUntil(() -> {}, new WaitableTimeout<Void>(timeout) {
@Override
public Void get() {
// Override to not throw.
return null;
}
}).get();
});
}
protected void handleEvent(String event, JsonObject params) {
@@ -16,14 +16,11 @@
package com.microsoft.playwright.impl;
import com.microsoft.playwright.Deferred;
import com.microsoft.playwright.Event;
import com.microsoft.playwright.Listener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.*;
import java.util.function.Consumer;
class ListenerCollection <EventType> {
private final HashMap<EventType, List<Listener<EventType>>> listeners = new HashMap<>();
@@ -51,6 +48,71 @@ class ListenerCollection <EventType> {
}
}
private static class RunnableWrapper<EventType> implements Listener<EventType> {
final Runnable callback;
private RunnableWrapper(Runnable callback) {
this.callback = callback;
}
@Override
public void handle(Event<EventType> event) {
callback.run();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RunnableWrapper<?> that = (RunnableWrapper<?>) o;
return Objects.equals(callback, that.callback);
}
@Override
public int hashCode() {
return Objects.hash(callback);
}
}
private static class ConsumerWrapper<EventType> implements Listener<EventType> {
final Consumer<?> callback;
private ConsumerWrapper(Consumer<?> callback) {
this.callback = callback;
}
@Override
public void handle(Event<EventType> event) {
((Consumer<Object>) callback).accept(event.data());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RunnableWrapper<?> that = (RunnableWrapper<?>) o;
return Objects.equals(callback, that.callback);
}
@Override
public int hashCode() {
return Objects.hash(callback);
}
}
void add(EventType type, Runnable listener) {
add(type, new RunnableWrapper<>(listener));
}
void remove(EventType type, Runnable listener) {
remove(type, new RunnableWrapper<>(listener));
}
void add(EventType type, Consumer listener) {
add(type, new ConsumerWrapper<>(listener));
}
void remove(EventType type, Consumer listener) {
remove(type, new ConsumerWrapper<>(listener));
}
void add(EventType type, Listener<EventType> listener) {
List<Listener<EventType>> list = listeners.get(type);
if (list == null) {
@@ -16,8 +16,6 @@
package com.microsoft.playwright.impl;
import com.microsoft.playwright.Deferred;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
@@ -60,25 +58,6 @@ class LoggingSupport {
}
}
<T> Deferred<T> withLoggingDeferred(String apiName, Supplier<Deferred<T>> code) {
if (isEnabled) {
logApi("=> " + apiName + " started");
}
Deferred<T> deferred = code.get();
return () -> {
boolean success = false;
try {
T result = deferred.get();
success = true;
return result;
} finally {
if (isEnabled) {
logApi("<= " + apiName + (success ? " succeeded" : " failed"));
}
}
};
}
private void logApi(String message) {
// This matches log format produced by the server.
String timestamp = timestampFormat.format(new Date());
@@ -44,7 +44,23 @@ public class PageImpl extends ChannelOwner implements Page {
private Viewport viewport;
private final Router routes = new Router();
private final Set<FrameImpl> frames = new LinkedHashSet<>();
private final ListenerCollection<EventType> listeners = new ListenerCollection<>();
private final ListenerCollection<EventType> listeners = new ListenerCollection<EventType>() {
@Override
void add(EventType eventType, Listener<EventType> listener) {
if (eventType == EventType.FILECHOOSER) {
willAddFileChooserListener();
}
super.add(eventType, listener);
}
@Override
void remove(EventType eventType, Listener<EventType> listener) {
super.remove(eventType, listener);
if (eventType == EventType.FILECHOOSER) {
didRemoveFileChooserListener();
}
}
};
final Map<String, Binding> bindings = new HashMap<>();
BrowserContextImpl ownedContext;
private boolean isClosed;
@@ -203,18 +219,316 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void addListener(EventType type, Listener<EventType> listener) {
if (type == EventType.FILECHOOSER) {
willAddFileChooserListener();
}
listeners.add(type, listener);
}
@Override
public void removeListener(EventType type, Listener<EventType> listener) {
listeners.remove(type, listener);
if (type == EventType.FILECHOOSER) {
didRemoveFileChooserListener();
}
@Override
public void onClose(Runnable handler) {
listeners.add(EventType.CLOSE, handler);
}
@Override
public void offClose(Runnable handler) {
listeners.remove(EventType.CLOSE, handler);
}
@Override
public void onConsole(Consumer<ConsoleMessage> handler) {
listeners.add(EventType.CONSOLE, handler);
}
@Override
public void offConsole(Consumer<ConsoleMessage> handler) {
listeners.remove(EventType.CONSOLE, handler);
}
@Override
public void onCrash(Runnable handler) {
listeners.add(EventType.CRASH, handler);
}
@Override
public void offCrash(Runnable handler) {
listeners.remove(EventType.CRASH, handler);
}
@Override
public void onDialog(Consumer<Dialog> handler) {
listeners.add(EventType.DIALOG, handler);
}
@Override
public void offDialog(Consumer<Dialog> handler) {
listeners.remove(EventType.DIALOG, handler);
}
@Override
public void onDomContentLoaded(Runnable handler) {
listeners.add(EventType.DOMCONTENTLOADED, handler);
}
@Override
public void offDomContentLoaded(Runnable handler) {
listeners.remove(EventType.DOMCONTENTLOADED, handler);
}
@Override
public void onDownload(Consumer<Download> handler) {
listeners.add(EventType.DOWNLOAD, handler);
}
@Override
public void offDownload(Consumer<Download> handler) {
listeners.remove(EventType.DOWNLOAD, handler);
}
@Override
public void onFileChooser(Consumer<FileChooser> handler) {
listeners.add(EventType.FILECHOOSER, handler);
}
@Override
public void offFileChooser(Consumer<FileChooser> handler) {
listeners.remove(EventType.FILECHOOSER, handler);
}
@Override
public void onFrameAttached(Consumer<Frame> handler) {
listeners.add(EventType.FRAMEATTACHED, handler);
}
@Override
public void offFrameAttached(Consumer<Frame> handler) {
listeners.remove(EventType.FRAMEATTACHED, handler);
}
@Override
public void onFrameDetached(Consumer<Frame> handler) {
listeners.add(EventType.FRAMEDETACHED, handler);
}
@Override
public void offFrameDetached(Consumer<Frame> handler) {
listeners.remove(EventType.FRAMEDETACHED, handler);
}
@Override
public void onFrameNavigated(Consumer<Frame> handler) {
listeners.add(EventType.FRAMENAVIGATED, handler);
}
@Override
public void offFrameNavigated(Consumer<Frame> handler) {
listeners.remove(EventType.FRAMENAVIGATED, handler);
}
@Override
public void onLoad(Runnable handler) {
listeners.add(EventType.LOAD, handler);
}
@Override
public void offLoad(Runnable handler) {
listeners.remove(EventType.LOAD, handler);
}
@Override
public void onPageError(Consumer<Error> handler) {
listeners.add(EventType.PAGEERROR, handler);
}
@Override
public void offPageError(Consumer<Error> handler) {
listeners.remove(EventType.PAGEERROR, handler);
}
@Override
public void onPopup(Consumer<Page> handler) {
listeners.add(EventType.POPUP, handler);
}
@Override
public void offPopup(Consumer<Page> handler) {
listeners.remove(EventType.POPUP, 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);
}
@Override
public void onWebSocket(Consumer<WebSocket> handler) {
listeners.add(EventType.WEBSOCKET, handler);
}
@Override
public void offWebSocket(Consumer<WebSocket> handler) {
listeners.remove(EventType.WEBSOCKET, handler);
}
@Override
public void onWorker(Consumer<Worker> handler) {
listeners.add(EventType.WORKER, handler);
}
@Override
public void offWorker(Consumer<Worker> handler) {
listeners.remove(EventType.WORKER, handler);
}
@Override
public Page waitForClose(Runnable code, WaitForCloseOptions options) {
if (options == null) {
options = new WaitForCloseOptions();
}
return waitForEventWithTimeout(EventType.CLOSE, code, options.timeout);
}
@Override
public ConsoleMessage waitForConsole(Runnable code, WaitForConsoleOptions options) {
if (options == null) {
options = new WaitForConsoleOptions();
}
return waitForEventWithTimeout(EventType.CONSOLE, code, options.timeout);
}
private <T> T waitForEventWithTimeout(EventType eventType, Runnable code, Double timeout) {
List<Waitable<T>> waitables = new ArrayList<>();
waitables.add(new WaitableEvent<>(listeners, eventType)
.apply(event -> (T) event.data()));
waitables.add(createWaitForCloseHelper());
waitables.add(createWaitableTimeout(timeout));
return runUntil(code, new WaitableRace<>(waitables));
}
@Override
public Download waitForDownload(Runnable code, WaitForDownloadOptions options) {
if (options == null) {
options = new WaitForDownloadOptions();
}
return waitForEventWithTimeout(EventType.DOWNLOAD, code, options.timeout);
}
@Override
public FileChooser waitForFileChooser(Runnable code, WaitForFileChooserOptions options) {
// TODO: enable/disable file chooser interception
if (options == null) {
options = new WaitForFileChooserOptions();
}
return waitForEventWithTimeout(EventType.FILECHOOSER, code, options.timeout);
}
@Override
public Frame waitForFrameAttached(Runnable code, WaitForFrameAttachedOptions options) {
if (options == null) {
options = new WaitForFrameAttachedOptions();
}
return waitForEventWithTimeout(EventType.FRAMEATTACHED, code, options.timeout);
}
@Override
public Frame waitForFrameDetached(Runnable code, WaitForFrameDetachedOptions options) {
if (options == null) {
options = new WaitForFrameDetachedOptions();
}
return waitForEventWithTimeout(EventType.FRAMEDETACHED, code, options.timeout);
}
@Override
public Frame waitForFrameNavigated(Runnable code, WaitForFrameNavigatedOptions options) {
if (options == null) {
options = new WaitForFrameNavigatedOptions();
}
return waitForEventWithTimeout(EventType.FRAMENAVIGATED, code, options.timeout);
}
@Override
public Error waitForPageError(Runnable code, WaitForPageErrorOptions options) {
if (options == null) {
options = new WaitForPageErrorOptions();
}
return waitForEventWithTimeout(EventType.PAGEERROR, code, options.timeout);
}
@Override
public Page waitForPopup(Runnable code, WaitForPopupOptions options) {
if (options == null) {
options = new WaitForPopupOptions();
}
return waitForEventWithTimeout(EventType.POPUP, code, options.timeout);
}
@Override
public Request waitForRequestFailed(Runnable code, WaitForRequestFailedOptions options) {
if (options == null) {
options = new WaitForRequestFailedOptions();
}
return waitForEventWithTimeout(EventType.REQUESTFAILED, code, options.timeout);
}
@Override
public Request waitForRequestFinished(Runnable code, WaitForRequestFinishedOptions options) {
if (options == null) {
options = new WaitForRequestFinishedOptions();
}
return waitForEventWithTimeout(EventType.REQUESTFINISHED, code, options.timeout);
}
@Override
public WebSocket waitForWebSocket(Runnable code, WaitForWebSocketOptions options) {
if (options == null) {
options = new WaitForWebSocketOptions();
}
return waitForEventWithTimeout(EventType.WEBSOCKET, code, options.timeout);
}
@Override
public Worker waitForWorker(Runnable code, WaitForWorkerOptions options) {
if (options == null) {
options = new WaitForWorkerOptions();
}
return waitForEventWithTimeout(EventType.WORKER, code, options.timeout);
}
@Override
@@ -804,32 +1118,6 @@ public class PageImpl extends ChannelOwner implements Page {
return timeoutSettings.createWaitable(timeout);
}
@Override
public Deferred<Event<EventType>> futureEvent(EventType event, FutureEventOptions options) {
return withLogging("Page.futureEvent", () -> futureEventImpl(event, options));
}
private Deferred<Event<EventType>> futureEventImpl(EventType event, FutureEventOptions options) {
if (options == null) {
options = new FutureEventOptions();
}
List<Waitable<Event<EventType>>> waitables = new ArrayList<>();
if (event == EventType.FILECHOOSER) {
willAddFileChooserListener();
waitables.add(new WaitableEvent<EventType>(listeners, event, options.predicate) {
@Override
public void dispose() {
super.dispose();
didRemoveFileChooserListener();
}
});
} else {
waitables.add(new WaitableEvent<>(listeners, event, options.predicate));
}
waitables.add(createWaitableTimeout(options.timeout));
return toDeferred(new WaitableRace<>(waitables));
}
@Override
public JSHandle waitForFunction(String pageFunction, Object arg, WaitForFunctionOptions options) {
return withLogging("Page.waitForFunction",
@@ -843,12 +1131,17 @@ public class PageImpl extends ChannelOwner implements Page {
}
@Override
public Deferred<Response> futureNavigation(FutureNavigationOptions options) {
return withLoggingDeferred("Page.futureNavigation", () -> futureNavigationImpl(options));
public Response waitForNavigation(Runnable code, WaitForNavigationOptions options) {
return withLogging("Page.waitForNavigation", () -> waitForNavigationImpl(code, options));
}
Deferred<Response> futureNavigationImpl(FutureNavigationOptions options) {
Frame.FutureNavigationOptions frameOptions = new Frame.FutureNavigationOptions();
@Override
public Request waitForRequest(Runnable code) {
return waitForRequest(code, UrlMatcher.any(), null);
}
Response waitForNavigationImpl(Runnable code, WaitForNavigationOptions options) {
Frame.WaitForNavigationOptions frameOptions = new Frame.WaitForNavigationOptions();
if (options != null) {
frameOptions.timeout = options.timeout;
frameOptions.waitUntil = options.waitUntil;
@@ -856,7 +1149,7 @@ public class PageImpl extends ChannelOwner implements Page {
frameOptions.pattern = options.pattern;
frameOptions.predicate = options.predicate;
}
return mainFrame.futureNavigationImpl(frameOptions);
return mainFrame.waitForNavigationImpl(code, frameOptions);
}
void frameNavigated(FrameImpl frame) {
@@ -887,8 +1180,8 @@ public class PageImpl extends ChannelOwner implements Page {
}
private class WaitableFrameDetach extends WaitableEvent<EventType> {
WaitableFrameDetach(Frame frame) {
super(PageImpl.this.listeners, EventType.FRAMEDETACHED, event -> frame.equals(event.data()));
WaitableFrameDetach(Frame frameArg) {
super(PageImpl.this.listeners, EventType.FRAMEDETACHED, event -> frameArg.equals(event.data()));
}
@Override
@@ -949,65 +1242,70 @@ public class PageImpl extends ChannelOwner implements Page {
}
@Override
public Deferred<Request> futureRequest(String urlGlob, FutureRequestOptions options) {
return futureRequest(new UrlMatcher(urlGlob), options);
public Request waitForRequest(Runnable code, String urlGlob, WaitForRequestOptions options) {
return waitForRequest(code, new UrlMatcher(urlGlob), options);
}
@Override
public Deferred<Request> futureRequest(Pattern urlPattern, FutureRequestOptions options) {
return futureRequest(new UrlMatcher(urlPattern), options);
public Request waitForRequest(Runnable code, Pattern urlPattern, WaitForRequestOptions options) {
return waitForRequest(code, new UrlMatcher(urlPattern), options);
}
@Override
public Deferred<Request> futureRequest(Predicate<String> urlPredicate, FutureRequestOptions options) {
return futureRequest(new UrlMatcher(urlPredicate), options);
public Request waitForRequest(Runnable code, Predicate<String> urlPredicate, WaitForRequestOptions options) {
return waitForRequest(code, new UrlMatcher(urlPredicate), options);
}
private Deferred<Request> futureRequest(UrlMatcher matcher, FutureRequestOptions options) {
return withLoggingDeferred("Page.futureRequest", () -> futureRequestImpl(matcher, options));
@Override
public Response waitForResponse(Runnable code) {
return waitForResponse(code, UrlMatcher.any(), null);
}
private Deferred<Request> futureRequestImpl(UrlMatcher matcher, FutureRequestOptions options) {
private Request waitForRequest(Runnable code, UrlMatcher matcher, WaitForRequestOptions options) {
return withLogging("Page.waitForRequest", () -> waitForRequestImpl(code, matcher, options));
}
private Request waitForRequestImpl(Runnable code, UrlMatcher matcher, WaitForRequestOptions options) {
if (options == null) {
options = new FutureRequestOptions();
options = new WaitForRequestOptions();
}
List<Waitable<Request>> waitables = new ArrayList<>();
waitables.add(new WaitableEvent<>(listeners, EventType.REQUEST, e -> matcher.test(((Request) e.data()).url()))
.apply(event -> (Request) event.data()));
waitables.add(createWaitForCloseHelper());
waitables.add(createWaitableTimeout(options.timeout));
return toDeferred(new WaitableRace<>(waitables));
return runUntil(code, new WaitableRace<>(waitables));
}
@Override
public Deferred<Response> futureResponse(String urlGlob, FutureResponseOptions options) {
return futureResponse(new UrlMatcher(urlGlob), options);
public Response waitForResponse(Runnable code, String urlGlob, WaitForResponseOptions options) {
return waitForResponse(code, new UrlMatcher(urlGlob), options);
}
@Override
public Deferred<Response> futureResponse(Pattern urlPattern, FutureResponseOptions options) {
return futureResponse(new UrlMatcher(urlPattern), options);
public Response waitForResponse(Runnable code, Pattern urlPattern, WaitForResponseOptions options) {
return waitForResponse(code, new UrlMatcher(urlPattern), options);
}
@Override
public Deferred<Response> futureResponse(Predicate<String> urlPredicate, FutureResponseOptions options) {
return futureResponse(new UrlMatcher(urlPredicate), options);
public Response waitForResponse(Runnable code, Predicate<String> urlPredicate, WaitForResponseOptions options) {
return waitForResponse(code, new UrlMatcher(urlPredicate), options);
}
private Deferred<Response> futureResponse(UrlMatcher matcher, FutureResponseOptions options) {
return withLoggingDeferred("Page.futureResponse", () -> futureResponseImpl(matcher, options));
private Response waitForResponse(Runnable code, UrlMatcher matcher, WaitForResponseOptions options) {
return withLogging("Page.waitForResponse", () -> waitForonseImpl(code, matcher, options));
}
private Deferred<Response> futureResponseImpl(UrlMatcher matcher, FutureResponseOptions options) {
private Response waitForonseImpl(Runnable code, UrlMatcher matcher, WaitForResponseOptions options) {
if (options == null) {
options = new FutureResponseOptions();
options = new WaitForResponseOptions();
}
List<Waitable<Response>> waitables = new ArrayList<>();
waitables.add(new WaitableEvent<>(listeners, EventType.RESPONSE, e -> matcher.test(((Response) e.data()).url()))
.apply(event -> (Response) event.data()));
waitables.add(createWaitForCloseHelper());
waitables.add(createWaitableTimeout(options.timeout));
return toDeferred(new WaitableRace<>(waitables));
return runUntil(code, new WaitableRace<>(waitables));
}
@Override
@@ -107,7 +107,5 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
if (!didClose) {
System.err.println("WARNING: Timed out while waiting for driver process to exit");
}
connection.checkNoUnusedDeferredObjects();
}
}
@@ -24,6 +24,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import java.util.function.Consumer;
class WebSocketImpl extends ChannelOwner implements WebSocket {
private final ListenerCollection<EventType> listeners = new ListenerCollection<>();
@@ -45,6 +46,70 @@ class WebSocketImpl extends ChannelOwner implements WebSocket {
listeners.remove(type, listener);
}
@Override
public void onClose(Runnable handler) {
listeners.add(EventType.CLOSE, handler);
}
@Override
public void offClose(Runnable handler) {
listeners.remove(EventType.CLOSE, handler);
}
@Override
public void onFrameReceived(Consumer<FrameData> handler) {
listeners.add(EventType.FRAMERECEIVED, handler);
}
@Override
public void offFrameReceived(Consumer<FrameData> handler) {
listeners.remove(EventType.FRAMERECEIVED, handler);
}
@Override
public void onFrameSent(Consumer<FrameData> handler) {
listeners.add(EventType.FRAMESENT, handler);
}
@Override
public void offFrameSent(Consumer<FrameData> handler) {
listeners.remove(EventType.FRAMESENT, handler);
}
@Override
public void onSocketError(Consumer<String> handler) {
listeners.add(EventType.SOCKETERROR, handler);
}
@Override
public void offSocketError(Consumer<String> handler) {
listeners.remove(EventType.SOCKETERROR, handler);
}
@Override
public FrameData waitForFrameReceived(Runnable code, WaitForFrameReceivedOptions options) {
if (options == null) {
options = new WaitForFrameReceivedOptions();
}
return waitForEventWithTimeout(EventType.FRAMERECEIVED, code, options.timeout);
}
@Override
public FrameData waitForFrameSent(Runnable code, WaitForFrameSentOptions options) {
if (options == null) {
options = new WaitForFrameSentOptions();
}
return waitForEventWithTimeout(EventType.FRAMESENT, code, options.timeout);
}
@Override
public String waitForSocketError(Runnable code, WaitForSocketErrorOptions options) {
if (options == null) {
options = new WaitForSocketErrorOptions();
}
return waitForEventWithTimeout(EventType.SOCKETERROR, code, options.timeout);
}
@Override
public boolean isClosed() {
return isClosed;
@@ -96,21 +161,14 @@ class WebSocketImpl extends ChannelOwner implements WebSocket {
}
}
@Override
public Deferred<Event<EventType>> futureEvent(EventType event, FutureEventOptions options) {
return withLoggingDeferred("WebSocket.futureEvent", () -> futureEventImpl(event, options));
}
private Deferred<Event<EventType>> futureEventImpl(EventType event, FutureEventOptions options) {
if (options == null) {
options = new FutureEventOptions();
}
List<Waitable<Event<EventType>>> waitables = new ArrayList<>();
waitables.add(new WaitableEvent<>(listeners, event, options.predicate));
private <T> T waitForEventWithTimeout(EventType eventType, Runnable code, Double timeout) {
List<Waitable<T>> waitables = new ArrayList<>();
waitables.add(new WaitableEvent<>(listeners, eventType)
.apply(event -> (T) event.data()));
waitables.add(new WaitableWebSocketError<>());
waitables.add(page.createWaitForCloseHelper());
waitables.add(page.createWaitableTimeout(options.timeout));
return toDeferred(new WaitableRace<>(waitables));
waitables.add(page.createWaitableTimeout(timeout));
return runUntil(code, new WaitableRace<>(waitables));
}
private static class FrameDataImpl implements FrameData {
@@ -21,6 +21,10 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.*;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import static com.microsoft.playwright.impl.Serialization.*;
import static com.microsoft.playwright.impl.Utils.isFunctionBody;
@@ -42,6 +46,33 @@ class WorkerImpl extends ChannelOwner implements Worker {
listeners.remove(type, listener);
}
@Override
public void onClose(Consumer<Worker> handler) {
listeners.add(EventType.CLOSE, handler);
}
@Override
public void offClose(Consumer<Worker> handler) {
listeners.remove(EventType.CLOSE, handler);
}
private <T> T waitForEventWithTimeout(EventType eventType, Runnable code, Double timeout) {
List<Waitable<T>> waitables = new ArrayList<>();
waitables.add(new WaitableEvent<>(listeners, eventType)
.apply(event -> (T) event.data()));
waitables.add(page.createWaitForCloseHelper());
waitables.add(page.createWaitableTimeout(timeout));
return runUntil(code, new WaitableRace<>(waitables));
}
@Override
public Worker waitForClose(Runnable code, WaitForCloseOptions options) {
if (options == null) {
options = new WaitForCloseOptions();
}
return waitForEventWithTimeout(EventType.CLOSE, code, options.timeout);
}
@Override
public Object evaluate(String pageFunction, Object arg) {
return withLogging("Worker.evaluate", () -> {
@@ -72,11 +103,6 @@ class WorkerImpl extends ChannelOwner implements Worker {
return initializer.get("url").getAsString();
}
@Override
public Deferred<Event<EventType>> futureEvent(EventType event) {
return toDeferred(new WaitableEvent<>(listeners, event));
}
@Override
protected void handleEvent(String event, JsonObject params) {
if ("close".equals(event)) {
@@ -44,9 +44,8 @@ public class TestBrowserContextBasic extends TestBase {
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.navigate(server.EMPTY_PAGE);
Deferred<Event<Page.EventType>> popupEvent = page.futureEvent(POPUP);
page.evaluate("url => window.open(url)", server.EMPTY_PAGE);
Page popup = (Page) popupEvent.get().data();
Page popup = page.waitForPopup(() ->
page.evaluate("url => window.open(url)", server.EMPTY_PAGE));
assertEquals(context, popup.context());
context.close();
}
@@ -155,10 +154,8 @@ public class TestBrowserContextBasic extends TestBase {
@Test
void closeShouldAbortFutureEvent() {
BrowserContext context = browser.newContext();
Deferred<Event<BrowserContext.EventType>> pageEvent = context.futureEvent(PAGE);
context.close();
try {
pageEvent.get();
context.waitForPage(() -> context.close());
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Context closed"));
@@ -23,6 +23,7 @@ import org.junit.jupiter.api.condition.EnabledIf;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import static com.microsoft.playwright.Keyboard.Modifier.SHIFT;
import static com.microsoft.playwright.Mouse.Button.RIGHT;
@@ -104,9 +105,7 @@ public class TestClick extends TestBase {
Page page = context.newPage();
page.navigate(server.PREFIX + "/wrappedlink.html");
Deferred<Response> navigationPromise = page.futureNavigation();
page.click("a");
navigationPromise.get();
page.waitForNavigation(() -> page.click("a"));
assertEquals(server.PREFIX + "/wrappedlink.html#clicked", page.url());
context.close();
@@ -1,56 +0,0 @@
/*
* 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.concurrent.atomic.AtomicBoolean;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
public class TestDeferred extends TestBase {
@Test
void throwIfGetNotCalled() {
page.futureNavigation();
context.futureEvent(BrowserContext.EventType.PAGE);
closeContext();
closeBrowser();
AtomicBoolean didFinalize = new AtomicBoolean();
new Object() {
@Override
protected void finalize() {
didFinalize.set(true);
}
};
while (!didFinalize.get()) {
System.gc();
}
Playwright p = playwright;
playwright = null;
try {
p.close();
fail("did not throw");
} catch (Exception e) {
assertTrue(e.getMessage().contains("com.microsoft.playwright.impl.PageImpl.futureNavigation"));
assertTrue(e.getMessage().contains("com.microsoft.playwright.impl.BrowserContextImpl.futureEvent"));
}
}
}
@@ -61,9 +61,7 @@ public class TestDownload extends TestBase {
@Test
void shouldReportDownloadsWithAcceptDownloadsFalse() {
page.setContent("<a href='" + server.PREFIX + "/downloadWithFilename'>download</a>");
Deferred<Event<Page.EventType>> downloadEvent = page.futureEvent(DOWNLOAD);
page.click("a");
Download download = (Download) downloadEvent.get().data();
Download download = page.waitForDownload(() -> page.click("a"));
assertEquals(server.PREFIX + "/downloadWithFilename", download.url());
assertEquals("file.txt", download.suggestedFilename());
try {
@@ -78,9 +76,7 @@ public class TestDownload extends TestBase {
void shouldReportDownloadsWithAcceptDownloadsTrue() throws IOException {
Page page = browser.newPage(new Browser.NewPageOptions().withAcceptDownloads(true));
page.setContent("<a href='" + server.PREFIX + "/download'>download</a>");
Deferred<Event<Page.EventType>> downloadEvent = page.futureEvent(DOWNLOAD);
page.click("a");
Download download = (Download) downloadEvent.get().data();
Download download = page.waitForDownload(() -> page.click("a"));
Path path = download.path();
assertTrue(Files.exists(path));
byte[] bytes = readAllBytes(path);
@@ -92,9 +88,7 @@ public class TestDownload extends TestBase {
void shouldSaveToUserSpecifiedPath() throws IOException {
Page page = browser.newPage(new Browser.NewPageOptions().withAcceptDownloads(true));
page.setContent("<a href='" + server.PREFIX + "/download'>download</a>");
Deferred<Event<Page.EventType>> downloadEvent = page.futureEvent(DOWNLOAD);
page.click("a");
Download download = (Download) downloadEvent.get().data();
Download download = page.waitForDownload(() -> page.click("a"));
Path userFile = Files.createTempFile("download-", ".txt");
download.saveAs(userFile);
@@ -108,9 +102,7 @@ public class TestDownload extends TestBase {
void shouldSaveToUserSpecifiedPathWithoutUpdatingOriginalPath() throws IOException {
Page page = browser.newPage(new Browser.NewPageOptions().withAcceptDownloads(true));
page.setContent("<a href='" + server.PREFIX + "/download'>download</a>");
Deferred<Event<Page.EventType>> downloadEvent = page.futureEvent(DOWNLOAD);
page.click("a");
Download download = (Download) downloadEvent.get().data();
Download download = page.waitForDownload(() -> page.click("a"));
Path userFile = Files.createTempFile("download-", ".txt");
download.saveAs(userFile);
@@ -133,9 +125,7 @@ public class TestDownload extends TestBase {
void shouldSaveToTwoDifferentPathsWithMultipleSaveAsCalls() throws IOException {
Page page = browser.newPage(new Browser.NewPageOptions().withAcceptDownloads(true));
page.setContent("<a href='" + server.PREFIX + "/download'>download</a>");
Deferred<Event<Page.EventType>> downloadEvent = page.futureEvent(DOWNLOAD);
page.click("a");
Download download = (Download) downloadEvent.get().data();
Download download = page.waitForDownload(() -> page.click("a"));
{
Path userFile = Files.createTempFile("download-", ".txt");
download.saveAs(userFile);
@@ -157,9 +147,7 @@ public class TestDownload extends TestBase {
void shouldSaveToOverwrittenFilepath() throws IOException {
Page page = browser.newPage(new Browser.NewPageOptions().withAcceptDownloads(true));
page.setContent("<a href='" + server.PREFIX + "/download'>download</a>");
Deferred<Event<Page.EventType>> downloadEvent = page.futureEvent(DOWNLOAD);
page.click("a");
Download download = (Download) downloadEvent.get().data();
Download download = page.waitForDownload(() -> page.click("a"));
Path userFile = Files.createTempFile("download-", ".txt");
{
download.saveAs(userFile);
@@ -180,9 +168,7 @@ public class TestDownload extends TestBase {
void shouldCreateSubdirectoriesWhenSavingToNonExistentUserSpecifiedPath() throws IOException {
Page page = browser.newPage(new Browser.NewPageOptions().withAcceptDownloads(true));
page.setContent("<a href='" + server.PREFIX + "/download'>download</a>");
Deferred<Event<Page.EventType>> downloadEvent = page.futureEvent(DOWNLOAD);
page.click("a");
Download download = (Download) downloadEvent.get().data();
Download download = page.waitForDownload(() -> page.click("a"));
Path downloads = Files.createTempDirectory("downloads");
Path nestedPath = downloads.resolve(Paths.get("these", "are", "directories", "download.txt"));
@@ -201,9 +187,7 @@ public class TestDownload extends TestBase {
void shouldErrorWhenSavingWithDownloadsDisabled() throws IOException {
Page page = browser.newPage(new Browser.NewPageOptions().withAcceptDownloads(false));
page.setContent("<a href='" + server.PREFIX + "/download'>download</a>");
Deferred<Event<Page.EventType>> downloadEvent = page.futureEvent(DOWNLOAD);
page.click("a");
Download download = (Download) downloadEvent.get().data();
Download download = page.waitForDownload(() -> page.click("a"));
Path userPath = Files.createTempFile("download-", ".txt");
try {
@@ -219,9 +203,7 @@ public class TestDownload extends TestBase {
void shouldErrorWhenSavingAfterDeletion() throws IOException {
Page page = browser.newPage(new Browser.NewPageOptions().withAcceptDownloads(true));
page.setContent("<a href='" + server.PREFIX + "/download'>download</a>");
Deferred<Event<Page.EventType>> downloadEvent = page.futureEvent(DOWNLOAD);
page.click("a");
Download download = (Download) downloadEvent.get().data();
Download download = page.waitForDownload(() -> page.click("a"));
Path userPath = Files.createTempFile("download-", ".txt");
download.delete();
@@ -252,9 +234,7 @@ public class TestDownload extends TestBase {
Page page = browser.newPage(new Browser.NewPageOptions().withAcceptDownloads(true));
page.navigate(server.EMPTY_PAGE);
page.setContent("<a download='file.txt' href='" + server.PREFIX + "/download'>download</a>");
Deferred<Event<Page.EventType>> downloadEvent = page.futureEvent(DOWNLOAD);
page.click("a");
Download download = (Download) downloadEvent.get().data();
Download download = page.waitForDownload(() -> page.click("a"));
assertEquals("file.txt", download.suggestedFilename());
Path path = download.path();
@@ -320,9 +300,7 @@ public class TestDownload extends TestBase {
});
Page page = browser.newPage(new Browser.NewPageOptions().withAcceptDownloads(true));
page.setContent("<a href='" + server.PREFIX + "/download'>download</a>");
Deferred<Event<Page.EventType>> downloadEvent = page.futureEvent(DOWNLOAD);
page.click("a", new Page.ClickOptions().withModifiers(ALT));
Download download = (Download) downloadEvent.get().data();
Download download = page.waitForDownload(() -> page.click("a", new Page.ClickOptions().withModifiers(ALT)));
Path path = download.path();
assertTrue(Files.exists(path));
byte[] bytes = readAllBytes(path);
@@ -343,9 +321,7 @@ public class TestDownload extends TestBase {
// - WebKit doesn't close the popup page
Page page = browser.newPage(new Browser.NewPageOptions().withAcceptDownloads(true));
page.setContent("<a target=_blank href='" + server.PREFIX + "/download'>download</a>");
Deferred<Event<Page.EventType>> downloadEvent = page.futureEvent(DOWNLOAD);
page.click("a");
Download download = (Download) downloadEvent.get().data();
Download download = page.waitForDownload(() -> page.click("a"));
Path path = download.path();
assertTrue(Files.exists(path));
byte[] bytes = readAllBytes(path);
@@ -357,9 +333,7 @@ public class TestDownload extends TestBase {
void shouldDeleteFile() {
Page page = browser.newPage(new Browser.NewPageOptions().withAcceptDownloads(true));
page.setContent("<a href='" + server.PREFIX + "/download'>download</a>");
Deferred<Event<Page.EventType>> downloadEvent = page.futureEvent(DOWNLOAD);
page.click("a");
Download download = (Download) downloadEvent.get().data();
Download download = page.waitForDownload(() -> page.click("a"));
Path path = download.path();
assertTrue(Files.exists(path));
download.delete();
@@ -371,9 +345,7 @@ public class TestDownload extends TestBase {
void shouldExposeStream() throws IOException {
Page page = browser.newPage(new Browser.NewPageOptions().withAcceptDownloads(true));
page.setContent("<a href='" + server.PREFIX + "/download'>download</a>");
Deferred<Event<Page.EventType>> downloadEvent = page.futureEvent(DOWNLOAD);
page.click("a");
Download download = (Download) downloadEvent.get().data();
Download download = page.waitForDownload(() -> page.click("a"));
InputStream stream = download.createReadStream();
ByteArrayOutputStream out = new ByteArrayOutputStream();
@@ -386,14 +358,8 @@ public class TestDownload extends TestBase {
void shouldDeleteDownloadsOnContextDestruction() {
Page page = browser.newPage(new Browser.NewPageOptions().withAcceptDownloads(true));
page.setContent("<a href='" + server.PREFIX + "/download'>download</a>");
Deferred<Event<Page.EventType>> downloadEvent1 = page.futureEvent(DOWNLOAD);
page.click("a");
Download download1 = (Download) downloadEvent1.get().data();
Deferred<Event<Page.EventType>> downloadEvent2 = page.futureEvent(DOWNLOAD);
page.click("a");
Download download2 = (Download) downloadEvent2.get().data();
Download download1 = page.waitForDownload(() -> page.click("a"));
Download download2 = page.waitForDownload(() -> page.click("a"));
Path path1 = download1.path();
Path path2 = download2.path();
assertTrue(Files.exists(path1));
@@ -408,14 +374,8 @@ public class TestDownload extends TestBase {
Browser browser = browserType.launch();
Page page = browser.newPage(new Browser.NewPageOptions().withAcceptDownloads(true));
page.setContent("<a href='" + server.PREFIX + "/download'>download</a>");
Deferred<Event<Page.EventType>> downloadEvent1 = page.futureEvent(DOWNLOAD);
page.click("a");
Download download1 = (Download) downloadEvent1.get().data();
Deferred<Event<Page.EventType>> downloadEvent2 = page.futureEvent(DOWNLOAD);
page.click("a");
Download download2 = (Download) downloadEvent2.get().data();
Download download1 = page.waitForDownload(() -> page.click("a"));
Download download2 = page.waitForDownload(() -> page.click("a"));
Path path1 = download1.path();
Path path2 = download2.path();
assertTrue(Files.exists(path1));
@@ -96,15 +96,14 @@ public class TestElementHandleOwnerFrame extends TestBase {
@Test
void shouldWorkForAdoptedElements() {
page.navigate(server.EMPTY_PAGE);
Deferred<Event<Page.EventType>> popupEvent = page.futureEvent(Page.EventType.POPUP);
page.evaluate("url => window['__popup'] = window.open(url)", server.EMPTY_PAGE);
Page popup = page.waitForPopup(() ->
page.evaluate("url => window['__popup'] = window.open(url)", server.EMPTY_PAGE));
JSHandle divHandle = page.evaluateHandle("() => {\n" +
" const div = document.createElement('div');\n" +
" document.body.appendChild(div);\n" +
" return div;\n" +
"}");
assertEquals(page.mainFrame(), divHandle.asElement().ownerFrame());
Page popup = (Page) popupEvent.get().data();
popup.waitForLoadState(Page.LoadState.DOMCONTENTLOADED);
page.evaluate("() => {\n" +
" const div = document.querySelector('div');\n" +
@@ -31,8 +31,8 @@ public class TestElementHandleQuerySelector extends TestBase {
@Test
void shouldWorkForAdoptedElements() {
page.navigate(server.EMPTY_PAGE);
Deferred<Event<Page.EventType>> popupEvent = page.futureEvent(Page.EventType.POPUP);
page.evaluate("url => window['__popup'] = window.open(url)", server.EMPTY_PAGE);
Page popup = page.waitForPopup(() -> page.evaluate(
"url => window['__popup'] = window.open(url)", server.EMPTY_PAGE));
// Test JSHandle
JSHandle divHandle = page.evaluateHandle("() => {\n" +
" const div = document.createElement('div');\n" +
@@ -45,7 +45,6 @@ public class TestElementHandleQuerySelector extends TestBase {
assertNotNull(divHandle.asElement().querySelector("span"));
assertEquals("hello", divHandle.asElement().querySelector("span").evaluate( "e => e.textContent"));
// Test Popup
Page popup = (Page) popupEvent.get().data();
popup.waitForLoadState(Page.LoadState.DOMCONTENTLOADED);
page.evaluate("() => {\n" +
" const div = document.querySelector('div');\n" +
@@ -120,19 +120,26 @@ public class TestGeolocation extends TestBase {
" }, err => {});\n" +
"}");
{
Deferred<Event<Page.EventType>> deferred = page.futureEvent(CONSOLE, event -> ((ConsoleMessage) event.data()).text().contains("lat=0 lng=10"));
context.setGeolocation(new Geolocation(0, 10));
deferred.get();
ConsoleMessage message = page.waitForConsole(() -> context.setGeolocation(new Geolocation(0, 10)));
// Location change events come several times so we loop until expected one is received.
while (!message.text().contains("lat=0 lng=10")) {
message = page.waitForConsole(() -> {});
}
assertTrue(message.text().contains("lat=0 lng=10"), message.text());
}
{
Deferred<Event<Page.EventType>> deferred = page.futureEvent(CONSOLE, event -> ((ConsoleMessage) event.data()).text().contains("lat=20 lng=30"));
context.setGeolocation(new Geolocation(20, 30));
deferred.get();
ConsoleMessage message = page.waitForConsole(() -> context.setGeolocation(new Geolocation(20, 30)));
while (!message.text().contains("lat=20 lng=30")) {
message = page.waitForConsole(() -> {});
}
assertTrue(message.text().contains("lat=20 lng=30"), message.text());
}
{
Deferred<Event<Page.EventType>> deferred = page.futureEvent(CONSOLE, event -> ((ConsoleMessage) event.data()).text().contains("lat=40 lng=50"));
context.setGeolocation(new Geolocation(40, 50));
deferred.get();
ConsoleMessage message = page.waitForConsole(() -> context.setGeolocation(new Geolocation(40, 50)));
while (!message.text().contains("lat=40 lng=50")) {
message = page.waitForConsole(() -> {});
}
assertTrue(message.text().contains("lat=40 lng=50"), message.text());
}
assertTrue(messages.contains("lat=0 lng=10"));
assertTrue(messages.contains("lat=20 lng=30"));
@@ -143,9 +150,8 @@ public class TestGeolocation extends TestBase {
void shouldUseContextOptionsForPopup() {
context.grantPermissions(asList("geolocation"));
context.setGeolocation(new Geolocation(10, 10));
Deferred<Event<Page.EventType>> popupEvent = page.futureEvent(POPUP);
page.evaluate("url => window['_popup'] = window.open(url)", server.PREFIX + "/geolocation.html");
Page popup = (Page) popupEvent.get().data();
Page popup = page.waitForPopup(() -> page.evaluate(
"url => window['_popup'] = window.open(url)", server.PREFIX + "/geolocation.html"));
popup.waitForLoadState();
Object geolocation = popup.evaluate("window['geolocationPromise']");
assertEquals(mapOf("longitude", 10, "latitude", 10), geolocation);
@@ -132,13 +132,13 @@ public class TestNetworkRequest extends TestBase {
writer.write("done");
}
});
Deferred<Event<Page.EventType>> responsePromise = page.futureEvent(RESPONSE);
Object text = page.evaluate("async url => {\n" +
" const data = await fetch(url);\n" +
" return data.text();\n" +
"}", server.CROSS_PROCESS_PREFIX + "/something");
assertEquals("done", text);
Response response = (Response) responsePromise.get().data();
Response response = page.waitForResponse(() -> {
Object text = page.evaluate("async url => {\n" +
" const data = await fetch(url);\n" +
" return data.text();\n" +
"}", server.CROSS_PROCESS_PREFIX + "/something");
assertEquals("done", text);
});
Map<String, String> expectedHeaders = serverRequest.get().headers.entrySet().stream().collect(
Collectors.toMap(e -> e.getKey().toLowerCase(), e -> e.getValue().get(0)));
assertEquals(expectedHeaders, response.request().headers());
@@ -26,6 +26,7 @@ import java.util.Arrays;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.Semaphore;
import java.util.regex.Pattern;
import static com.microsoft.playwright.Page.EventType.REQUESTFINISHED;
import static com.microsoft.playwright.Page.EventType.RESPONSE;
@@ -105,11 +106,9 @@ public class TestNetworkResponse extends TestBase {
requestFinished[0] |= ((Request) event.data()).url().contains("/get");
});
// send request and wait for server response
Deferred<Event<Page.EventType>> responseEvent = page.futureEvent(RESPONSE);
page.evaluate("() => fetch('./get', { method: 'GET'})");
assertNotNull(responseEvent.get());
Response pageResponse = page.waitForResponse(() -> page.evaluate("() => fetch('./get', { method: 'GET'})"));
assertNotNull(pageResponse);
responseWritten.acquire();
Response pageResponse = (Response) responseEvent.get().data();
assertEquals(200, pageResponse.status());
assertEquals(false, requestFinished[0]);
responseRead.release();
@@ -59,19 +59,28 @@ public class TestPageBasic extends TestBase {
// We have to interact with a page so that "beforeunload" handlers
// fire.
newPage.click("body");
Deferred<Event<Page.EventType>> event = newPage.futureEvent(DIALOG);
boolean[] didShowDialog = {false};
newPage.onDialog(dialog -> {
didShowDialog[0] = true;
assertEquals(Dialog.Type.BEFOREUNLOAD, dialog.type());
assertEquals("", dialog.defaultValue());
if (isChromium()) {
assertEquals("", dialog.message());
} else if (isWebKit()) {
assertEquals("Leave?", dialog.message());
} else {
assertEquals("This page is asking you to confirm that you want to leave - data you have entered may not be saved.", dialog.message());
}
dialog.accept();
});
newPage.close(new Page.CloseOptions().withRunBeforeUnload(true));
Dialog dialog = (Dialog) event.get().data();
assertEquals(Dialog.Type.BEFOREUNLOAD, dialog.type());
assertEquals("", dialog.defaultValue());
if (isChromium()) {
assertEquals("", dialog.message());
} else if (isWebKit()) {
assertEquals("Leave?", dialog.message());
} else {
assertEquals("This page is asking you to confirm that you want to leave - data you have entered may not be saved.", dialog.message());
for (int i = 0; i < 300; i++) {
if (didShowDialog[0]) {
break;
}
page.waitForTimeout(100);
}
dialog.accept();
assertTrue(didShowDialog[0]);
}
@Test
@@ -98,19 +107,17 @@ public class TestPageBasic extends TestBase {
@Test
void shouldTerminateNetworkWaiters() {
Page newPage = context.newPage();
Deferred<Request> request = newPage.futureRequest(server.EMPTY_PAGE);
Deferred<Response> response = newPage.futureResponse(server.EMPTY_PAGE);
newPage.close();
try {
request.get();
fail("get() should throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Page closed"));
assertFalse(e.getMessage().contains("Timeout"));
}
try {
response.get();
fail("get() should throw");
newPage.waitForResponse(() -> {
try {
newPage.waitForRequest(() -> newPage.close(), server.EMPTY_PAGE);
fail("waitForRequest() should throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Page closed"));
assertFalse(e.getMessage().contains("Timeout"));
}
});
fail("waitForResponse() should throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Page closed"));
assertFalse(e.getMessage().contains("Timeout"));
@@ -137,18 +144,14 @@ public class TestPageBasic extends TestBase {
@Test
void shouldProvideAccessToTheOpenerPage() {
Deferred<Event<Page.EventType>> popupEvent = page.futureEvent(POPUP);
page.evaluate("() => window.open('about:blank')");
Page popup = (Page) popupEvent.get().data();
Page popup = page.waitForPopup(() -> page.evaluate("() => window.open('about:blank')"));
Page opener = popup.opener();
assertEquals(page, opener);
}
@Test
void shouldReturnNullIfParentPageHasBeenClosed() {
Deferred<Event<Page.EventType>> popupEvent = page.futureEvent(POPUP);
page.evaluate("() => window.open('about:blank')");
Page popup = (Page) popupEvent.get().data();
Page popup = page.waitForPopup(() -> page.evaluate("() => window.open('about:blank')"));
page.close();
Page opener = popup.opener();
assertEquals(null, opener);
@@ -189,20 +192,15 @@ public class TestPageBasic extends TestBase {
@Test
void pageCloseShouldWorkWithWindowClose() {
Deferred<Event<Page.EventType>> newPagePromise = page.futureEvent(POPUP);
page.evaluate("() => window['newPage'] = window.open('about:blank')");
Page newPage = (Page) newPagePromise.get().data();
Deferred<Event<Page.EventType>> closedPromise = newPage.futureEvent(CLOSE);
page.evaluate("() => window['newPage'].close()");
closedPromise.get();
Page newPage = page.waitForPopup(() -> page.evaluate(
"() => window['newPage'] = window.open('about:blank')"));
newPage.waitForClose(() -> page.evaluate("() => window['newPage'].close()"));
}
@Test
void pageCloseShouldWorkWithPageClose() {
Page newPage = context.newPage();
Deferred<Event<Page.EventType>> closedPromise = newPage.futureEvent(CLOSE);
newPage.close();
closedPromise.get();
newPage.waitForClose(() -> newPage.close());
}
@Test
@@ -86,9 +86,8 @@ public class TestPageEmulateMedia extends TestBase {
BrowserContext context = browser.newContext(new Browser.NewContextOptions().withColorScheme(DARK));
Page page = context.newPage();
page.navigate(server.EMPTY_PAGE);
Deferred<Event<Page.EventType>> popupEvent = page.futureEvent(POPUP);
page.evaluate("url => { window.open(url); }", server.EMPTY_PAGE);
Page popup = (Page) popupEvent.get().data();
Page popup = page.waitForPopup(() ->
page.evaluate("url => { window.open(url); }", server.EMPTY_PAGE));
assertEquals(false, popup.evaluate("() => matchMedia('(prefers-color-scheme: light)').matches"));
assertEquals(true, popup.evaluate("() => matchMedia('(prefers-color-scheme: dark)').matches"));
context.close();
@@ -96,9 +95,8 @@ public class TestPageEmulateMedia extends TestBase {
{
Page page = browser.newPage(new Browser.NewPageOptions().withColorScheme(LIGHT));
page.navigate(server.EMPTY_PAGE);
Deferred<Event<Page.EventType>> popupEvent = page.futureEvent(POPUP);
page.evaluate("url => { window.open(url); }", server.EMPTY_PAGE);
Page popup = (Page) popupEvent.get().data();
Page popup = page.waitForPopup(() ->
page.evaluate("url => { window.open(url); }", server.EMPTY_PAGE));
assertEquals(true, popup.evaluate("() => matchMedia('(prefers-color-scheme: light)').matches"));
assertEquals(false, popup.evaluate("() => matchMedia('(prefers-color-scheme: dark)').matches"));
page.close();
@@ -435,19 +435,17 @@ public class TestPageEvaluate extends TestBase {
@Test
void shouldThrowANiceErrorAfterANavigation() {
Deferred<Response> navigation = page.futureNavigation();
try {
page.evaluate("() => {\n" +
" const promise = new Promise(f => window['__resolve'] = f);\n" +
" window.location.reload();\n" +
" setTimeout(() => window['__resolve'](42), 1000);\n" +
" return promise;\n" +
"}");
navigation.get();
page.waitForNavigation(() -> {
page.evaluate("() => {\n" +
" const promise = new Promise(f => window['__resolve'] = f);\n" +
" window.location.reload();\n" +
" setTimeout(() => window['__resolve'](42), 1000);\n" +
" return promise;\n" +
"}");
});
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("navigation"));
// The code may throw before navigation resolves, make sure .get() is called in that case too.
navigation.get();
}
}
@@ -79,10 +79,13 @@ public class TestPageEventNetwork extends TestBase {
@Test
void PageEventsRequestFinished() {
Deferred<Event<Page.EventType>> event = page.futureEvent(REQUESTFINISHED);
Response response = page.navigate(server.EMPTY_PAGE);
event.get();
Request request = response.request();
Response[] response = {null};
Request finishedRequest = page.waitForRequestFinished(() -> {
response[0] = page.navigate(server.EMPTY_PAGE);
});
assertNotNull(response[0]);
Request request = response[0].request();
assertEquals(request, finishedRequest);
assertEquals(server.EMPTY_PAGE, request.url());
assertNotNull(request.response());
assertEquals(page.mainFrame(), request.frame());
@@ -184,12 +184,12 @@ public class TestPageExposeFunction extends TestBase {
}, new Page.ExposeBindingOptions().withHandle(true));
page.navigate(server.EMPTY_PAGE);
Deferred<Response> navigation = page.futureNavigation(new Page.FutureNavigationOptions().withWaitUntil(LOAD));
page.evaluate("async url => {\n" +
" window['logme']({ foo: 42 });\n" +
" window.location.href = url;\n" +
"}", server.PREFIX + "/one-style.html");
navigation.get();
page.waitForNavigation(() -> {
page.evaluate("async url => {\n" +
" window['logme']({ foo: 42 });\n" +
" window.location.href = url;\n" +
"}", server.PREFIX + "/one-style.html");
}, new Page.WaitForNavigationOptions().withWaitUntil(LOAD));
}
@Test
@@ -101,9 +101,7 @@ public class TestPageRoute extends TestBase {
page.setContent("<form action='/rredirect' method='post'>\n" +
" <input type='hidden' id='foo' name='foo' value='FOOBAR'>\n" +
"</form>");
Deferred<Response> navigationResponse = page.futureNavigation();
page.evalOnSelector("form", "form => form.submit()");
navigationResponse.get();
page.waitForNavigation(() -> page.evalOnSelector("form", "form => form.submit()"));
}
// @see https://github.com/GoogleChrome/puppeteer/issues/3973
@@ -435,9 +433,8 @@ public class TestPageRoute extends TestBase {
Route[] route = {null};
page.route("**/*", r -> route[0] = r);
// Wait for request interception.
Deferred<Event<Page.EventType>> event = page.futureEvent(REQUEST);
page.evalOnSelector("iframe", "(frame, url) => frame.src = url", server.EMPTY_PAGE);
event.get();
page.waitForRequest(() ->
page.evalOnSelector("iframe", "(frame, url) => frame.src = url", server.EMPTY_PAGE));
// Delete frame to cause request to be canceled.
page.evalOnSelector("iframe", "frame => frame.remove()");
try {
@@ -94,9 +94,7 @@ public class TestPageSelectOption extends TestBase {
void shouldNotThrowWhenSelectCausesNavigation() {
page.navigate(server.PREFIX + "/input/select.html");
page.evalOnSelector("select", "select => select.addEventListener('input', () => window.location.href = '/empty.html')");
Deferred<Response> response = page.futureNavigation();
page.selectOption("select", "blue");
response.get();
page.waitForNavigation(() -> page.selectOption("select", "blue"));
assertTrue(page.url().contains("empty.html"));
}
@@ -67,9 +67,7 @@ public class TestPageSetInputFiles extends TestBase {
@Test
void shouldEmitEventOnce() {
page.setContent("<input type=file>");
Deferred<Event<Page.EventType>> event = page.futureEvent(Page.EventType.FILECHOOSER);
page.click("input");
FileChooser chooser = (FileChooser) event.get().data();
FileChooser chooser = page.waitForFileChooser(() -> page.click("input"));
assertNotNull(chooser);
}
@@ -99,20 +97,20 @@ public class TestPageSetInputFiles extends TestBase {
@Test
void shouldWorkWhenFileInputIsAttachedToDOM() {
page.setContent("<input type=file>");
Deferred<Event<Page.EventType>> chooser = page.futureEvent(Page.EventType.FILECHOOSER);
page.click("input");
assertNotNull(chooser.get());
FileChooser chooser = page.waitForFileChooser(() -> page.click("input"));
assertNotNull(chooser);
}
@Test
void shouldWorkWhenFileInputIsNotAttachedToDOM() {
Deferred<Event<Page.EventType>> chooser = page.futureEvent(Page.EventType.FILECHOOSER);
page.evaluate("() => {\n" +
" const el = document.createElement('input');\n" +
" el.type = 'file';\n" +
" el.click();\n" +
"}");
assertNotNull(chooser.get());
FileChooser chooser = page.waitForFileChooser(() -> {
page.evaluate("() => {\n" +
" const el = document.createElement('input');\n" +
" el.type = 'file';\n" +
" el.click();\n" +
"}");
});
assertNotNull(chooser);
}
@Test
@@ -128,8 +126,7 @@ public class TestPageSetInputFiles extends TestBase {
@Test
void shouldRespectTimeout() {
try {
Deferred<Event<Page.EventType>> event = page.futureEvent(Page.EventType.FILECHOOSER, new Page.FutureEventOptions().withTimeout(1));
event.get();
page.waitForFileChooser(() -> {}, new Page.WaitForFileChooserOptions().withTimeout(1));
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Timeout 1ms exceeded"));
@@ -140,8 +137,7 @@ public class TestPageSetInputFiles extends TestBase {
void shouldRespectDefaultTimeoutWhenThereIsNoCustomTimeout() {
page.setDefaultTimeout(1);
try {
Deferred<Event<Page.EventType>> event = page.futureEvent(Page.EventType.FILECHOOSER);
event.get();
page.waitForFileChooser(() -> {});
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Timeout 1ms exceeded"));
@@ -151,10 +147,8 @@ public class TestPageSetInputFiles extends TestBase {
@Test
void shouldPrioritizeExactTimeoutOverDefaultTimeout() {
page.setDefaultTimeout(0);
Deferred<Event<Page.EventType>> event = page.futureEvent(Page.EventType.FILECHOOSER,
new Page.FutureEventOptions().withTimeout(1));
try {
event.get();
page.waitForFileChooser(() -> {}, new Page.WaitForFileChooserOptions().withTimeout(1));
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Timeout 1ms exceeded"));
@@ -163,31 +157,32 @@ public class TestPageSetInputFiles extends TestBase {
@Test
void shouldWorkWithNoTimeout() {
Deferred<Event<Page.EventType>> event = page.futureEvent(Page.EventType.FILECHOOSER,
new Page.FutureEventOptions().withTimeout(0));
page.evaluate("() => setTimeout(() => {\n" +
" const el = document.createElement('input');\n" +
" el.type = 'file';\n" +
" el.click();\n" +
"}, 50)");
assertNotNull(event.get().data());
FileChooser fileChooser = page.waitForFileChooser(() -> {
page.evaluate("() => setTimeout(() => {\n" +
" const el = document.createElement('input');\n" +
" el.type = 'file';\n" +
" el.click();\n" +
"}, 50)");
}, new Page.WaitForFileChooserOptions().withTimeout(0));
assertNotNull(fileChooser);
}
@Test
void shouldReturnTheSameFileChooserWhenThereAreManyWatchdogsSimultaneously() {
page.setContent("<input type=file>");
Deferred<Event<Page.EventType>> fileChooser1 = page.futureEvent(Page.EventType.FILECHOOSER);
Deferred<Event<Page.EventType>> fileChooser2 = page.futureEvent(Page.EventType.FILECHOOSER);
page.evalOnSelector("input", "input => input.click()");
assertEquals(fileChooser1.get().data(), fileChooser2.get().data());
FileChooser[] fileChooser = {null};
FileChooser fileChooser1 = page.waitForFileChooser(() -> {
fileChooser[0] = page.waitForFileChooser(() -> {
page.evalOnSelector("input", "input => input.click()");
});
});
assertEquals(fileChooser[0], fileChooser1);
}
@Test
void shouldAcceptSingleFile() {
page.setContent("<input type=file oninput='javascript:console.timeStamp()'>");
Deferred<Event<Page.EventType>> event = page.futureEvent(Page.EventType.FILECHOOSER);
page.click("input");
FileChooser fileChooser = (FileChooser) event.get().data();
FileChooser fileChooser = page.waitForFileChooser(() -> page.click("input"));
assertEquals(page, fileChooser.page());
assertNotNull(fileChooser.element());
fileChooser.setFiles(FILE_TO_UPLOAD);
@@ -255,9 +250,7 @@ public class TestPageSetInputFiles extends TestBase {
@Test
void shouldNotAcceptMultipleFilesForSingleFileInput() {
page.setContent("<input type=file>");
Deferred<Event<Page.EventType>> event = page.futureEvent(Page.EventType.FILECHOOSER);
page.click("input");
FileChooser fileChooser = (FileChooser) event.get().data();
FileChooser fileChooser = page.waitForFileChooser(() -> page.click("input"));
try {
fileChooser.setFiles(new Path[]{FILE_TO_UPLOAD, Paths.get("src/test/resources/pptr.png")});
fail("did not throw");
@@ -281,27 +274,21 @@ public class TestPageSetInputFiles extends TestBase {
@Test
void shouldWorkForSingleFilePick() {
page.setContent("<input type=file>");
Deferred<Event<Page.EventType>> event = page.futureEvent(Page.EventType.FILECHOOSER);
page.click("input");
FileChooser fileChooser = (FileChooser) event.get().data();
FileChooser fileChooser = page.waitForFileChooser(() -> page.click("input"));
assertFalse(fileChooser.isMultiple());
}
@Test
void shouldWorkForMultiple() {
page.setContent("<input multiple type=file>");
Deferred<Event<Page.EventType>> event = page.futureEvent(Page.EventType.FILECHOOSER);
page.click("input");
FileChooser fileChooser = (FileChooser) event.get().data();
FileChooser fileChooser = page.waitForFileChooser(() -> page.click("input"));
assertTrue(fileChooser.isMultiple());
}
@Test
void shouldWorkForWebkitdirectory() {
page.setContent("<input multiple webkitdirectory type=file>");
Deferred<Event<Page.EventType>> event = page.futureEvent(Page.EventType.FILECHOOSER);
page.click("input");
FileChooser fileChooser = (FileChooser) event.get().data();
FileChooser fileChooser = page.waitForFileChooser(() -> page.click("input"));
assertTrue(fileChooser.isMultiple());
}
}
@@ -33,21 +33,20 @@ public class TestPageWaitForNavigation extends TestBase {
@Test
void shouldWork() {
page.navigate(server.EMPTY_PAGE);
Deferred<Response> response = page.futureNavigation();
page.evaluate("url => window.location.href = url", server.PREFIX + "/grid.html");
assertTrue(response.get().ok());
assertTrue(response.get().url().contains("grid.html"));
Response response = page.waitForNavigation(() -> page.evaluate(
"url => window.location.href = url", server.PREFIX + "/grid.html"));
assertTrue(response.ok());
assertTrue(response.url().contains("grid.html"));
}
@Test
void shouldRespectTimeout() {
Deferred<Response> promise = page.futureNavigation(new Page.FutureNavigationOptions().withUrl("**/frame.html").withTimeout(5000));
page.navigate(server.EMPTY_PAGE);
try {
promise.get();
page.waitForNavigation(
() -> page.navigate(server.EMPTY_PAGE),
new Page.WaitForNavigationOptions().withUrl("**/frame.html").withTimeout(5000));
fail("did not throw");
} catch (PlaywrightException e) {
// assertTrue(e.getMessage().contains("page.futureNavigation: Timeout 5000ms exceeded."));
assertTrue(e.getMessage().contains("Timeout 5000ms exceeded"));
// assertTrue(e.getMessage().contains("waiting for navigation to '**/frame.html' until 'load'"));
// assertTrue(e.getMessage().contains("navigated to '${server.EMPTY_PAGE}'"));
@@ -62,9 +61,8 @@ public class TestPageWaitForNavigation extends TestBase {
void shouldWorkWithClickingOnAnchorLinks() {
page.navigate(server.EMPTY_PAGE);
page.setContent("<a href='#foobar'>foobar</a>");
Deferred<Response> response = page.futureNavigation();
page.click("a");
assertNull(response.get());
Response response = page.waitForNavigation(() -> page.click("a"));
assertNull(response);
assertEquals(server.EMPTY_PAGE + "#foobar", page.url());
}
@@ -75,11 +73,9 @@ public class TestPageWaitForNavigation extends TestBase {
@Test
void shouldWorkWithClickingOnLinksWhichDoNotCommitNavigation() throws InterruptedException {
page.navigate(server.EMPTY_PAGE);
Deferred<Response> event = page.futureNavigation();
page.setContent("<a href='" + httpsServer.EMPTY_PAGE + "'>foobar</a>");
try {
page.click("a");
event.get();
page.waitForNavigation(() -> page.click("a"));
fail("did not throw");
} catch (PlaywrightException e) {
// TODO: figure out why it is inconsistent on Linux WebKit.
@@ -95,9 +91,8 @@ public class TestPageWaitForNavigation extends TestBase {
"<script>\n" +
" function pushState() { history.pushState({}, '', 'wow.html') }\n" +
"</script>");
Deferred<Response> response = page.futureNavigation();
page.click("a");
assertNull(response.get());
Response response = page.waitForNavigation(() -> page.click("a"));
assertNull(response);
assertEquals(server.PREFIX + "/wow.html", page.url());
}
@@ -108,9 +103,8 @@ public class TestPageWaitForNavigation extends TestBase {
"<script>\n" +
" function replaceState() { history.replaceState({}, '', '/replaced.html') }\n" +
"</script>");
Deferred<Response> response = page.futureNavigation();
page.click("a");
assertNull(response.get());
Response response = page.waitForNavigation(() -> page.click("a"));
assertNull(response);
assertEquals(server.PREFIX + "/replaced.html", page.url());
}
@@ -127,14 +121,13 @@ public class TestPageWaitForNavigation extends TestBase {
"</script>");
assertEquals(server.PREFIX + "/second.html", page.url());
Deferred<Response> backResponse = page.futureNavigation();
page.click("a#back");
assertNull(backResponse.get());
Response backResponse = page.waitForNavigation(() -> page.click("a#back"));
assertNull(backResponse);
assertEquals(server.PREFIX + "/first.html", page.url());
Deferred<Response> forwardResponse = page.futureNavigation();
Response forwardResponse = page.waitForNavigation(() -> page.click("a#forward"));
page.click("a#forward");
assertNull(forwardResponse.get());
assertNull(forwardResponse);
assertEquals(server.PREFIX + "/second.html", page.url());
}
@@ -159,51 +152,55 @@ public class TestPageWaitForNavigation extends TestBase {
void shouldWorkWithUrlMatch() {
page.navigate(server.EMPTY_PAGE);
Deferred<Response> response1 = page.futureNavigation(new Page.FutureNavigationOptions().withUrl("**/one-style.html"));
page.navigate(server.PREFIX + "/one-style.html");
assertNotNull(response1.get());
assertEquals(server.PREFIX + "/one-style.html", response1.get().url());
Response response1 = page.waitForNavigation(
() -> page.navigate(server.PREFIX + "/one-style.html"),
new Page.WaitForNavigationOptions().withUrl("**/one-style.html"));
assertNotNull(response1);
assertEquals(server.PREFIX + "/one-style.html", response1.url());
Deferred<Response> response2 = page.futureNavigation(new Page.FutureNavigationOptions().withUrl(Pattern.compile("frame.html$")));
page.navigate(server.PREFIX + "/frame.html");
assertNotNull(response2.get());
assertEquals(server.PREFIX + "/frame.html", response2.get().url());
Response response2 = page.waitForNavigation(
() -> page.navigate(server.PREFIX + "/frame.html"),
new Page.WaitForNavigationOptions().withUrl(Pattern.compile("frame.html$")));
assertNotNull(response2);
assertEquals(server.PREFIX + "/frame.html", response2.url());
Deferred<Response> response3 = page.futureNavigation(new Page.FutureNavigationOptions().withUrl(url -> {
try {
return new URL(url).getQuery().contains("foo=bar");
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}));
page.navigate(server.PREFIX + "/frame.html?foo=bar");
assertNotNull(response3.get());
assertEquals(server.PREFIX + "/frame.html?foo=bar", response3.get().url());
Response response3 = page.waitForNavigation(
() -> page.navigate(server.PREFIX + "/frame.html?foo=bar"),
new Page.WaitForNavigationOptions().withUrl(url -> {
try {
return new URL(url).getQuery().contains("foo=bar");
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}));
assertNotNull(response3);
assertEquals(server.PREFIX + "/frame.html?foo=bar", response3.url());
}
@Test
void shouldWorkWithUrlMatchForSameDocumentNavigations() {
page.navigate(server.EMPTY_PAGE);
Deferred<Response> waitPromise = page.futureNavigation(new Page.FutureNavigationOptions().withUrl("**/third.html"));
page.evaluate("() => {\n" +
" history.pushState({}, '', '/first.html');\n" +
"}");
page.evaluate("() => {\n" +
" history.pushState({}, '', '/second.html');\n" +
"}");
page.evaluate("() => {\n" +
" history.pushState({}, '', '/third.html');\n" +
"}");
assertNull(waitPromise.get());
Response response = page.waitForNavigation(() -> {
page.evaluate("() => {\n" +
" history.pushState({}, '', '/first.html');\n" +
"}");
page.evaluate("() => {\n" +
" history.pushState({}, '', '/second.html');\n" +
"}");
page.evaluate("() => {\n" +
" history.pushState({}, '', '/third.html');\n" +
"}");
}, new Page.WaitForNavigationOptions().withUrl("**/third.html"));
assertNull(response);
}
@Test
void shouldWorkForCrossProcessNavigations() {
page.navigate(server.EMPTY_PAGE);
Deferred<Response> waitPromise = page.futureNavigation(new Page.FutureNavigationOptions().withWaitUntil(Frame.LoadState.DOMCONTENTLOADED));
String url = server.CROSS_PROCESS_PREFIX + "/empty.html";
page.navigate(url);
Response response = waitPromise.get();
Response response = page.waitForNavigation(
() -> page.navigate(url),
new Page.WaitForNavigationOptions().withWaitUntil(Frame.LoadState.DOMCONTENTLOADED));
assertEquals(url, response.url());
assertEquals(url, page.url());
assertEquals(url, page.evaluate("document.location.href"));
@@ -213,11 +210,11 @@ public class TestPageWaitForNavigation extends TestBase {
void shouldWorkOnFrame() {
page.navigate(server.PREFIX + "/frames/one-frame.html");
Frame frame = page.frames().get(1);
Deferred<Response> response = frame.futureNavigation();
frame.evaluate("url => window.location.href = url", server.PREFIX + "/grid.html");
assertTrue(response.get().ok());
assertTrue(response.get().url().contains("grid.html"));
assertEquals(frame, response.get().frame());
Response response = frame.waitForNavigation(() ->
frame.evaluate("url => window.location.href = url", server.PREFIX + "/grid.html"));
assertTrue(response.ok());
assertTrue(response.url().contains("grid.html"));
assertEquals(frame, response.frame());
assertTrue(page.url().contains("/frames/one-frame.html"));
}
@@ -227,12 +224,12 @@ public class TestPageWaitForNavigation extends TestBase {
Frame frame = page.frames().get(1);
server.setRoute("/empty.html", exchange -> {});
try {
Deferred<Response> response = frame.futureNavigation();
page.evaluate("() => {\n" +
" frames[0].location.href = '/empty.html';\n" +
" setTimeout(() => document.querySelector('iframe').remove());\n" +
"}\n");
response.get();
frame.waitForNavigation(() -> {
page.evaluate("() => {\n" +
" frames[0].location.href = '/empty.html';\n" +
" setTimeout(() => document.querySelector('iframe').remove());\n" +
"}\n");
});
fail("did not throw");
} catch (PlaywrightException e) {
// assertTrue(e.getMessage().contains("waiting for navigation until \"load\""));
@@ -28,32 +28,33 @@ public class TestPageWaitForRequest extends TestBase {
@Test
void shouldWork() {
page.navigate(server.EMPTY_PAGE);
Deferred<Request> request = page.futureRequest(server.PREFIX + "/digits/2.png");
page.evaluate("() => {\n" +
" fetch('/digits/1.png');\n" +
" fetch('/digits/2.png');\n" +
" fetch('/digits/3.png');\n" +
"}");
assertEquals(server.PREFIX + "/digits/2.png", request.get().url());
Request request = page.waitForRequest(() -> {
page.evaluate("() => {\n" +
" fetch('/digits/1.png');\n" +
" fetch('/digits/2.png');\n" +
" fetch('/digits/3.png');\n" +
"}");
}, server.PREFIX + "/digits/2.png");
assertEquals(server.PREFIX + "/digits/2.png", request.url());
}
@Test
void shouldWorkWithPredicate() {
page.navigate(server.EMPTY_PAGE);
Deferred<Request> request = page.futureRequest(url -> url.equals(server.PREFIX + "/digits/2.png"));
page.evaluate("() => {\n" +
" fetch('/digits/1.png');\n" +
" fetch('/digits/2.png');\n" +
" fetch('/digits/3.png');\n" +
"}");
assertEquals(server.PREFIX + "/digits/2.png", request.get().url());
Request request = page.waitForRequest(() -> {
page.evaluate("() => {\n" +
" fetch('/digits/1.png');\n" +
" fetch('/digits/2.png');\n" +
" fetch('/digits/3.png');\n" +
"}");
}, url -> url.equals(server.PREFIX + "/digits/2.png"));
assertEquals(server.PREFIX + "/digits/2.png", request.url());
}
@Test
void shouldRespectTimeout() {
try {
page.futureEvent(REQUEST, new Page.FutureEventOptions()
.withPredicate(url -> false).withTimeout(1)).get();
page.waitForRequest(() -> {}, url -> false, new Page.WaitForRequestOptions().withTimeout(1));
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Timeout"), e.getMessage());
@@ -64,7 +65,7 @@ public class TestPageWaitForRequest extends TestBase {
void shouldRespectDefaultTimeout() {
page.setDefaultTimeout(1);
try {
page.futureEvent(REQUEST, url -> false).get();
page.waitForRequest(() -> {}, url -> false);
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Timeout"), e.getMessage());
@@ -74,24 +75,27 @@ public class TestPageWaitForRequest extends TestBase {
@Test
void shouldWorkWithNoTimeout() {
page.navigate(server.EMPTY_PAGE);
Deferred<Request> request = page.futureRequest(server.PREFIX + "/digits/2.png",
new Page.FutureRequestOptions().withTimeout(0));
page.evaluate("() => setTimeout(() => {\n" +
" fetch('/digits/1.png');\n" +
" fetch('/digits/2.png');\n" +
" fetch('/digits/3.png');\n" +
"}, 50)");
assertEquals(server.PREFIX + "/digits/2.png", request.get().url());
Request request = page.waitForRequest(() -> {
page.evaluate("() => setTimeout(() => {\n" +
" fetch('/digits/1.png');\n" +
" fetch('/digits/2.png');\n" +
" fetch('/digits/3.png');\n" +
"}, 50)");
},
server.PREFIX + "/digits/2.png",
new Page.WaitForRequestOptions().withTimeout(0));
assertEquals(server.PREFIX + "/digits/2.png", request.url());
}
@Test
void shouldWorkWithUrlMatch() {
page.navigate(server.EMPTY_PAGE);
Deferred<Request> request = page.futureRequest(Pattern.compile(".*digits/\\d\\.png"));
page.evaluate("() => {\n" +
" fetch('/digits/1.png');\n" +
"}");
assertEquals(server.PREFIX + "/digits/1.png", request.get().url());
Request request = page.waitForRequest(() -> {
page.evaluate("() => {\n" +
" fetch('/digits/1.png');\n" +
"}");
}, Pattern.compile(".*digits/\\d\\.png"));
assertEquals(server.PREFIX + "/digits/1.png", request.url());
}
void shouldWorkWithUrlMatchRegularExpressionFromADifferentContext() {
@@ -26,44 +26,47 @@ public class TestPageWaitForResponse extends TestBase {
@Test
void shouldWork() {
page.navigate(server.EMPTY_PAGE);
Deferred<Response> response = page.futureResponse(server.PREFIX + "/digits/2.png");
page.evaluate("() => {\n" +
" fetch('/digits/1.png');\n" +
" fetch('/digits/2.png');\n" +
" fetch('/digits/3.png');\n" +
"}");
assertEquals(server.PREFIX + "/digits/2.png", response.get().url());
Response response = page.waitForResponse(() -> {
page.evaluate("() => {\n" +
" fetch('/digits/1.png');\n" +
" fetch('/digits/2.png');\n" +
" fetch('/digits/3.png');\n" +
"}");
}, server.PREFIX + "/digits/2.png");
assertEquals(server.PREFIX + "/digits/2.png", response.url());
}
@Test
void shouldRespectTimeout() {
page.navigate(server.EMPTY_PAGE);
Deferred<Response> response = page.futureResponse(url -> url.equals(server.PREFIX + "/digits/2.png"));
page.evaluate("() => {\n" +
" fetch('/digits/1.png');\n" +
" fetch('/digits/2.png');\n" +
" fetch('/digits/3.png');\n" +
"}");
assertEquals(server.PREFIX + "/digits/2.png", response.get().url());
Response response = page.waitForResponse(() -> {
page.evaluate("() => {\n" +
" fetch('/digits/1.png');\n" +
" fetch('/digits/2.png');\n" +
" fetch('/digits/3.png');\n" +
"}");
}, url -> url.equals(server.PREFIX + "/digits/2.png"));
assertEquals(server.PREFIX + "/digits/2.png", response.url());
}
@Test
void shouldWorkWithPredicate() {
page.navigate(server.EMPTY_PAGE);
Deferred<Response> response = page.futureResponse(url -> url.equals(server.PREFIX + "/digits/2.png"));
page.evaluate("() => {\n" +
" fetch('/digits/1.png');\n" +
" fetch('/digits/2.png');\n" +
" fetch('/digits/3.png');\n" +
"}");
assertEquals(server.PREFIX + "/digits/2.png", response.get().url());
Response response = page.waitForResponse(() -> {
page.evaluate("() => {\n" +
" fetch('/digits/1.png');\n" +
" fetch('/digits/2.png');\n" +
" fetch('/digits/3.png');\n" +
"}");
}, url -> url.equals(server.PREFIX + "/digits/2.png"));
assertEquals(server.PREFIX + "/digits/2.png", response.url());
}
@Test
void shouldRespectDefaultTimeout() {
page.setDefaultTimeout(1);
try {
page.futureEvent(RESPONSE, url -> false).get();
page.waitForResponse(() -> {}, url -> false);
fail("did not throw");
} catch (PlaywrightException e) {
assertTrue(e.getMessage().contains("Timeout"), e.getMessage());
@@ -73,13 +76,16 @@ public class TestPageWaitForResponse extends TestBase {
@Test
void shouldWorkWithNoTimeout() {
page.navigate(server.EMPTY_PAGE);
Deferred<Response> response = page.futureResponse(server.PREFIX + "/digits/2.png",
new Page.FutureResponseOptions().withTimeout(0));
page.evaluate("() => setTimeout(() => {\n" +
" fetch('/digits/1.png');\n" +
" fetch('/digits/2.png');\n" +
" fetch('/digits/3.png');\n" +
"}, 50)");
assertEquals(server.PREFIX + "/digits/2.png", response.get().url());
Response response = page.waitForResponse(() -> {
page.evaluate("() => setTimeout(() => {\n" +
" fetch('/digits/1.png');\n" +
" fetch('/digits/2.png');\n" +
" fetch('/digits/3.png');\n" +
"}, 50)");
},
server.PREFIX + "/digits/2.png",
new Page.WaitForResponseOptions().withTimeout(0));
assertEquals(server.PREFIX + "/digits/2.png", response.url());
}
}
@@ -38,9 +38,7 @@ public class TestPopup extends TestBase {
page.navigate(server.EMPTY_PAGE);
page.setContent("<a target=_blank rel=noopener href='/popup/popup.html'>link</a>");
Future<Server.Request> requestPromise = server.futureRequest("/popup/popup.html");
Deferred<Event<BrowserContext.EventType>> popupEvent = context.futureEvent(BrowserContext.EventType.PAGE);
page.click("a");
Page popup = (Page) popupEvent.get().data();
Page popup = context.waitForPage(() -> page.click("a"));
popup.waitForLoadState(DOMCONTENTLOADED);
String userAgent = (String) popup.evaluate("() => window['initialUserAgent']");
Server.Request request = requestPromise.get();
@@ -60,9 +58,7 @@ public class TestPopup extends TestBase {
route.continue_();
intercepted[0] = true;
});
Deferred<Event<BrowserContext.EventType>> popup = context.futureEvent(BrowserContext.EventType.PAGE);
page.click("a");
popup.get();
context.waitForPage(() -> page.click("a"));
context.close();
assertTrue(intercepted[0]);
@@ -103,9 +99,8 @@ public class TestPopup extends TestBase {
.withHttpCredentials("user", "pass"));
Page page = context.newPage();
page.navigate(server.EMPTY_PAGE);
Deferred<Event<Page.EventType>> popupEvent = page.futureEvent(POPUP);
page.evaluate("url => window['_popup'] = window.open(url)", server.PREFIX + "/title.html");
Page popup = (Page) popupEvent.get().data();
Page popup = page.waitForPopup(() -> page.evaluate(
"url => window['_popup'] = window.open(url)", server.PREFIX + "/title.html"));
popup.waitForLoadState(DOMCONTENTLOADED);
assertEquals("Woof-Woof", popup.title());
context.close();
@@ -146,17 +141,18 @@ public class TestPopup extends TestBase {
.withViewport(700, 700));
Page page = context.newPage();
page.navigate(server.EMPTY_PAGE);
Deferred<Event<Page.EventType>> popupEvent = page.futureEvent(POPUP);
Object size = page.evaluate("() => {\n" +
" const win = window.open(window.location.href, 'Title', 'toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=600,height=300,top=0,left=0');\n" +
" return { width: win.innerWidth, height: win.innerHeight };\n" +
"}");
Page popup = (Page) popupEvent.get().data();
Object[] size = {null};
Page popup = page.waitForPopup(() -> {
size[0] = page.evaluate("() => {\n" +
" const win = window.open(window.location.href, 'Title', 'toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=600,height=300,top=0,left=0');\n" +
" return { width: win.innerWidth, height: win.innerHeight };\n" +
"}");
});
popup.setViewportSize(500, 400);
popup.waitForLoadState();
Object resized = popup.evaluate("() => ({ width: window.innerWidth, height: window.innerHeight })");
context.close();
assertEquals(mapOf("width", 600, "height", 300), size);
assertEquals(mapOf("width", 600, "height", 300), size[0]);
assertEquals(mapOf("width", 500, "height", 400), resized);
}
@@ -170,9 +166,9 @@ public class TestPopup extends TestBase {
route.continue_();
intercepted[0] = true;
});
Deferred<Event<Page.EventType>> popupEvent = page.futureEvent(POPUP);
page.evaluate("url => window['__popup'] = window.open(url)", server.EMPTY_PAGE);
popupEvent.get();
page.waitForPopup(() -> {
page.evaluate("url => window['__popup'] = window.open(url)", server.EMPTY_PAGE);
});
assertTrue(intercepted[0]);
context.close();
}
@@ -197,9 +193,9 @@ public class TestPopup extends TestBase {
context.addInitScript("() => window['injected'] = 123");
Page page = context.newPage();
page.navigate(server.EMPTY_PAGE);
Deferred<Event<Page.EventType>> popupEvent = page.futureEvent(POPUP);
page.evaluate("url => window.open(url)", server.CROSS_PROCESS_PREFIX + "/title.html");
Page popup = (Page) popupEvent.get().data();
Page popup = page.waitForPopup(() -> {
page.evaluate("url => window.open(url)", server.CROSS_PROCESS_PREFIX + "/title.html");
});
assertEquals(123, popup.evaluate("injected"));
popup.reload();
@@ -66,9 +66,7 @@ public class TestRequestContinue extends TestBase {
page.route("**/foo", route -> {
route.continue_(new Route.ContinueOverrides().withUrl(server.PREFIX + "/global-var.html"));
});
Deferred<Event<Page.EventType>> responseEvent = page.futureEvent(RESPONSE);
page.navigate(server.PREFIX + "/foo");
Response response = (Response) responseEvent.get().data();
Response response = page.navigate(server.PREFIX + "/foo");
assertEquals(server.PREFIX + "/foo", response.url());
assertEquals(123, page.evaluate("window['globalVar']"));
assertEquals("GET", serverRequest.get().method);
@@ -201,33 +201,28 @@ public class TestWebSocket extends TestBase {
@Test
void shouldNotHaveStrayErrorEvents() {
Deferred<Event<Page.EventType>> wsEvent = page.futureEvent(WEBSOCKET);
page.evaluate("port => {\n" +
" window.ws = new WebSocket('ws://localhost:' + port + '/ws');\n" +
"}", webSocketServer.getPort());
com.microsoft.playwright.WebSocket ws = (com.microsoft.playwright.WebSocket) wsEvent.get().data();
com.microsoft.playwright.WebSocket ws = page.waitForWebSocket(() -> {
page.evaluate("port => {\n" +
" window.ws = new WebSocket('ws://localhost:' + port + '/ws');\n" +
"}", webSocketServer.getPort());
});
boolean[] error = {false};
ws.addListener(SOCKETERROR, e -> error[0] = true);
Deferred<Event<com.microsoft.playwright.WebSocket.EventType>> frameReceivedEvent = ws.futureEvent(FRAMERECEIVED);
frameReceivedEvent.get();
ws.waitForFrameReceived(() -> {});
page.evaluate("window.ws.close()");
assertFalse(error[0]);
}
@Test
void shouldRejectFutureEventOnSocketClose() {
Deferred<Event<Page.EventType>> wsEvent = page.futureEvent(WEBSOCKET);
page.evaluate("port => {\n" +
" window.ws = new WebSocket('ws://localhost:' + port + '/ws');\n" +
"}", webSocketServer.getPort());
com.microsoft.playwright.WebSocket ws = (com.microsoft.playwright.WebSocket) wsEvent.get().data();
ws.futureEvent(FRAMERECEIVED).get();
Deferred<Event<com.microsoft.playwright.WebSocket.EventType>> frameSentEvent = ws.futureEvent(FRAMESENT);
page.evaluate("window.ws.close()");
com.microsoft.playwright.WebSocket ws = page.waitForWebSocket(() -> {
page.evaluate("port => {\n" +
" window.ws = new WebSocket('ws://localhost:' + port + '/ws');\n" +
"}", webSocketServer.getPort());
});
ws.waitForFrameReceived(() -> {});
try {
frameSentEvent.get();
ws.waitForFrameSent(() -> page.evaluate("window.ws.close()"));
fail("did not throw");
} catch (PlaywrightException exception) {
assertTrue(exception.getMessage().contains("Socket closed"));
@@ -236,17 +231,14 @@ public class TestWebSocket extends TestBase {
@Test
void shouldRejectFutureEventOnPageClose() {
Deferred<Event<Page.EventType>> wsEvent = page.futureEvent(WEBSOCKET);
page.evaluate("port => {\n" +
" window.ws = new WebSocket('ws://localhost:' + port + '/ws');\n" +
"}", webSocketServer.getPort());
com.microsoft.playwright.WebSocket ws = (com.microsoft.playwright.WebSocket) wsEvent.get().data();
ws.futureEvent(FRAMERECEIVED).get();
Deferred<Event<com.microsoft.playwright.WebSocket.EventType>> frameSentEvent = ws.futureEvent(FRAMESENT);
page.close();
com.microsoft.playwright.WebSocket ws = page.waitForWebSocket(() -> {
page.evaluate("port => {\n" +
" window.ws = new WebSocket('ws://localhost:' + port + '/ws');\n" +
"}", webSocketServer.getPort());
});
ws.waitForFrameReceived(() -> {});
try {
frameSentEvent.get();
ws.waitForFrameSent(() -> page.close());
fail("did not throw");
} catch (PlaywrightException exception) {
assertTrue(exception.getMessage().contains("Page closed"));
@@ -22,17 +22,14 @@ import org.junit.jupiter.api.condition.EnabledIf;
import static com.microsoft.playwright.Page.EventType.*;
import static com.microsoft.playwright.Utils.attachFrame;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.*;
public class TestWorkers extends TestBase {
@Test
void pageWorkers() {
Deferred<Event<Page.EventType>> workerEvent = page.futureEvent(WORKER);
page.navigate(server.PREFIX + "/worker/worker.html");
workerEvent.get();
Worker worker = page.workers().get(0);
Worker worker = page.waitForWorker(() ->
page.navigate(server.PREFIX + "/worker/worker.html"));
assertTrue(worker.url().contains("worker.js"));
assertEquals("worker function result", worker.evaluate("() => self['workerFunction']()"));
page.navigate(server.EMPTY_PAGE);
@@ -41,13 +38,15 @@ public class TestWorkers extends TestBase {
@Test
void shouldEmitCreatedAndDestroyedEvents() {
Deferred<Event<Page.EventType>> workerCreatedPromise = page.futureEvent(WORKER);
JSHandle workerObj = page.evaluateHandle("() => new Worker(URL.createObjectURL(new Blob(['1'], {type: 'application/javascript'})))");
Worker worker = (Worker) workerCreatedPromise.get().data();
JSHandle[] workerObj = {null};
Worker worker = page.waitForWorker(() -> {
workerObj[0] = page.evaluateHandle(
"() => new Worker(URL.createObjectURL(new Blob(['1'], {type: 'application/javascript'})))");
});
JSHandle workerThisObj = worker.evaluateHandle("() => this");
Deferred<Event<Worker.EventType>> workerDestroyedPromise = worker.futureEvent(Worker.EventType.CLOSE);
page.evaluate("workerObj => workerObj.terminate()", workerObj);
assertEquals(worker, workerDestroyedPromise.get().data());
Worker closedWorker = worker.waitForClose(() ->
page.evaluate("workerObj => workerObj.terminate()", workerObj[0]));
assertEquals(worker, closedWorker);
try {
workerThisObj.getProperty("self");
} catch (PlaywrightException e) {
@@ -57,16 +56,15 @@ public class TestWorkers extends TestBase {
@Test
void shouldReportConsoleLogs() {
Deferred<Event<Page.EventType>> consoleEvent = page.futureEvent(CONSOLE);
page.evaluate("() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'})))");
assertEquals("1", ((ConsoleMessage) consoleEvent.get().data()).text());
ConsoleMessage message = page.waitForConsole(() -> page.evaluate(
"() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'})))"));
assertEquals("1", message.text());
}
@Test
void shouldHaveJSHandlesForConsoleLogs() {
Deferred<Event<Page.EventType>> consoleEvent = page.futureEvent(CONSOLE);
page.evaluate("() => new Worker(URL.createObjectURL(new Blob(['console.log(1,2,3,this)'], {type: 'application/javascript'})))");
ConsoleMessage log = (ConsoleMessage) consoleEvent.get().data();
ConsoleMessage log = page.waitForConsole(() -> page.evaluate(
"() => new Worker(URL.createObjectURL(new Blob(['console.log(1,2,3,this)'], {type: 'application/javascript'})))"));
assertEquals("1 2 3 JSHandle@object", log.text());
assertEquals(4, log.args().size());
assertEquals("null", (log.args().get(3).getProperty("origin")).jsonValue());
@@ -74,23 +72,22 @@ public class TestWorkers extends TestBase {
@Test
void shouldEvaluate() {
Deferred<Event<Page.EventType>> workerCreatedPromise = page.futureEvent(WORKER);
page.evaluate("() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'})))");
Worker worker = (Worker) workerCreatedPromise.get().data();
Worker worker = page.waitForWorker(() -> page.evaluate(
"() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'})))"));
assertEquals(2, worker.evaluate("1+1"));
}
@Test
void shouldReportErrors() {
Deferred<Event<Page.EventType>> errorPromise = page.futureEvent(PAGEERROR);
page.evaluate("() => new Worker(URL.createObjectURL(new Blob([`\n" +
" setTimeout(() => {\n" +
" // Do a console.log just to check that we do not confuse it with an error.\n" +
" console.log('hey');\n" +
" throw new Error('this is my error');\n" +
" })\n" +
"`], {type: 'application/javascript'})))");
Page.Error errorLog = (Page.Error) errorPromise.get().data();
Page.Error errorLog = page.waitForPageError(() -> {
page.evaluate("() => new Worker(URL.createObjectURL(new Blob([`\n" +
" setTimeout(() => {\n" +
" // Do a console.log just to check that we do not confuse it with an error.\n" +
" console.log('hey');\n" +
" throw new Error('this is my error');\n" +
" })\n" +
"`], {type: 'application/javascript'})))");
});
assertTrue(errorLog.message().contains("this is my error"));
}
@@ -98,23 +95,19 @@ public class TestWorkers extends TestBase {
@DisabledIf(value="com.microsoft.playwright.TestBase#isFirefox", disabledReason="flaky upstream")
void shouldClearUponNavigation() {
page.navigate(server.EMPTY_PAGE);
Deferred<Event<Page.EventType>> workerCreatedPromise = page.futureEvent(WORKER);
page.evaluate("() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'})))");
Worker worker = (Worker) workerCreatedPromise.get().data();
Worker worker = page.waitForWorker(() -> page.evaluate(
"() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'})))"));
assertEquals(1, page.workers().size());
boolean[] destroyed = {false};
worker.addListener(Worker.EventType.CLOSE, event -> destroyed[0] = true);
page.navigate(server.PREFIX + "/one-style.html");
assertTrue(destroyed[0]);
Worker destroyed = worker.waitForClose(() -> page.navigate(server.PREFIX + "/one-style.html"));
assertEquals(worker, destroyed);
assertEquals(0, page.workers().size());
}
@Test
void shouldClearUponCrossProcessNavigation() {
page.navigate(server.EMPTY_PAGE);
Deferred<Event<Page.EventType>> workerCreatedPromise = page.futureEvent(WORKER);
page.evaluate("() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'})))");
Worker worker = (Worker) workerCreatedPromise.get().data();
Worker worker = page.waitForWorker(() -> page.evaluate(
"() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'})))"));
assertEquals(1, page.workers().size());
boolean[] destroyed = {false};
worker.addListener(Worker.EventType.CLOSE, event -> destroyed[0] = true);
@@ -127,31 +120,31 @@ public class TestWorkers extends TestBase {
@EnabledIf(value="com.microsoft.playwright.TestBase#isWebKit", disabledReason="fixme")
void shouldAttributeNetworkActivityForWorkerInsideIframeToTheIframe() {
page.navigate(server.PREFIX + "/empty.html");
Deferred<Event<Page.EventType>> workerEvent = page.futureEvent(WORKER);
Frame frame = attachFrame(page, "frame1", server.PREFIX + "/worker/worker.html");
Frame[] frame = {null};
Worker worker = page.waitForWorker(() -> {
frame[0] = attachFrame(page, "frame1", server.PREFIX + "/worker/worker.html");
});
assertNotNull(frame[0]);
String url = server.PREFIX + "/one-style.css";
Deferred<Request> request = page.futureRequest(url);
Worker worker = (Worker) workerEvent.get().data();
worker.evaluate("url => fetch(url).then(response => response.text()).then(console.log)", url);
assertEquals(url, request.get().url());
assertEquals(frame, request.get().frame());
Request request = page.waitForRequest(() -> {
worker.evaluate("url => fetch(url).then(response => response.text()).then(console.log)", url);
}, url);
assertEquals(url, request.url());
assertEquals(frame[0], request.frame());
}
@Test
void shouldReportNetworkActivity() {
Deferred<Event<Page.EventType>> workerEvent = page.futureEvent(WORKER);
page.navigate(server.PREFIX + "/worker/worker.html");
Worker worker = (Worker) workerEvent.get().data();
Worker worker = page.waitForWorker(() -> page.navigate(server.PREFIX + "/worker/worker.html"));
String url = server.PREFIX + "/one-style.css";
Deferred<Request> requestPromise = page.futureRequest(url);
Deferred<Response> responsePromise = page.futureResponse(url);
worker.evaluate("url => fetch(url).then(response => response.text()).then(console.log)", url);
Request request = requestPromise.get();
Response response = responsePromise.get();
assertEquals(url, request.url());
assertEquals(request, response.request());
Request[] request = {null};
Response response = page.waitForResponse(() -> {
request[0] = page.waitForRequest(() -> {
worker.evaluate("url => fetch(url).then(response => response.text()).then(console.log)", url);
}, url);
assertEquals(url, request[0].url());
}, url);
assertEquals(request[0], response.request());
assertTrue(response.ok());
}
@@ -160,15 +153,16 @@ public class TestWorkers extends TestBase {
// Chromium needs waitForDebugger enabled for this one.
page.navigate(server.EMPTY_PAGE);
String url = server.PREFIX + "/one-style.css";
Deferred<Request> requestPromise = page.futureRequest(url);
Deferred<Response> responsePromise = page.futureResponse(url);
page.evaluate("url => new Worker(URL.createObjectURL(new Blob([`\n" +
" fetch('${url}').then(response => response.text()).then(console.log);\n" +
"`], {type: 'application/javascript'})))", url);
Request request = requestPromise.get();
Response response = responsePromise.get();
assertEquals(url, request.url());
assertEquals(request, response.request());
Request[] request = {null};
Response response = page.waitForResponse(() -> {
request[0] = page.waitForRequest(() -> {
page.evaluate("url => new Worker(URL.createObjectURL(new Blob([`\n" +
" fetch('${url}').then(response => response.text()).then(console.log);\n" +
"`], {type: 'application/javascript'})))", url);
}, url);
assertEquals(url, request[0].url());
}, url);
assertEquals(request[0], response.request());
assertTrue(response.ok());
}
@@ -177,9 +171,8 @@ public class TestWorkers extends TestBase {
BrowserContext context = browser.newContext(new Browser.NewContextOptions().withLocale("ru-RU"));
Page page = context.newPage();
page.navigate(server.EMPTY_PAGE);
Deferred<Event<Page.EventType>> workerEvent = page.futureEvent(WORKER);
page.evaluate("() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'})))");
Worker worker = (Worker) workerEvent.get().data();
Worker worker = page.waitForWorker(() -> page.evaluate(
"() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'})))"));
assertEquals("10\u00A0000,2", worker.evaluate("() => (10000.20).toLocaleString()"));
context.close();
}
@@ -341,12 +341,108 @@ abstract class TypeDefinition extends Element {
}
class Event extends Element {
private static Map<String, String> eventNames = new HashMap<>();
static {
eventNames.put("Browser.disconnected", "Disconnected");
eventNames.put("BrowserContext.close", "Close");
eventNames.put("BrowserContext.page", "Page");
eventNames.put("Page.close", "Close");
eventNames.put("Page.console", "Console");
eventNames.put("Page.crash", "Crash");
eventNames.put("Page.dialog", "Dialog");
eventNames.put("Page.domcontentloaded", "DomContentLoaded");
eventNames.put("Page.download", "Download");
eventNames.put("Page.filechooser", "FileChooser");
eventNames.put("Page.frameattached", "FrameAttached");
eventNames.put("Page.framedetached", "FrameDetached");
eventNames.put("Page.framenavigated", "FrameNavigated");
eventNames.put("Page.load", "Load");
eventNames.put("Page.pageerror", "PageError");
eventNames.put("Page.popup", "Popup");
eventNames.put("Page.request", "Request");
eventNames.put("Page.requestfailed", "RequestFailed");
eventNames.put("Page.requestfinished", "RequestFinished");
eventNames.put("Page.response", "Response");
eventNames.put("Page.websocket", "WebSocket");
eventNames.put("Page.worker", "Worker");
eventNames.put("WebSocket.close", "Close");
eventNames.put("WebSocket.framereceived", "FrameReceived");
eventNames.put("WebSocket.framesent", "FrameSent");
eventNames.put("WebSocket.socketerror", "SocketError");
eventNames.put("Worker.close", "Close");
}
private static Set<String> waitForEvents = new HashSet<>();
static {
waitForEvents.add("BrowserContext.page");
waitForEvents.add("Page.close");
waitForEvents.add("Page.console");
waitForEvents.add("Page.download");
waitForEvents.add("Page.filechooser");
waitForEvents.add("Page.frameattached");
waitForEvents.add("Page.framedetached");
waitForEvents.add("Page.framenavigated");
waitForEvents.add("Page.pageerror");
waitForEvents.add("Page.popup");
waitForEvents.add("Page.request");
waitForEvents.add("Page.requestfailed");
waitForEvents.add("Page.requestfinished");
waitForEvents.add("Page.response");
waitForEvents.add("Page.websocket");
waitForEvents.add("Page.worker");
waitForEvents.add("WebSocket.framereceived");
waitForEvents.add("WebSocket.framesent");
waitForEvents.add("WebSocket.socketerror");
waitForEvents.add("Worker.close");
}
private final TypeRef type;
Event(Element parent, JsonObject jsonElement) {
super(parent, jsonElement);
type = new TypeRef(this, jsonElement.get("type"));
}
void writeListenerMethods(List<String> output, String offset) {
if (!eventNames.containsKey(jsonPath)) {
throw new RuntimeException("Unknown event: " + jsonPath);
}
String name = eventNames.get(jsonPath);
String listenerType = "void".equals(type.toJava()) ? "Runnable" : "Consumer<" + type.toJava() + ">";
output.add(offset + "void on" + name + "(" + listenerType + " handler);");
output.add(offset + "void off" + name + "(" + listenerType + " handler);");
}
void writeWaitForEventIfNeeded(List<String> output, String offset) {
if (!waitForEvents.contains(jsonPath)) {
return;
}
String name = eventNames.get(jsonPath);
String methodName = "waitFor" + name;
// Skip events for which there is waitFor* method in the upstream API, that method will generate the code.
if (Method.waitForMethods.contains(parent.jsonPath + "." + methodName)) {
return;
}
output.add("");
String optionsClass = toTitle(methodName) + "Options";
output.add(offset + "class " + optionsClass + " {");
output.add(offset + " public Double timeout;");
output.add(offset + " public " + optionsClass + " withTimeout(double timeout) {");
output.add(offset + " this.timeout = timeout;");
output.add(offset + " return this;");
output.add(offset + " }");
output.add(offset + "}");
String paramType = jsonPath.equals("Page.close") ? "Page" : type.toJava();
output.add(offset + paramType + " " + methodName + "(Runnable code, " + optionsClass + " options);");
output.add(offset + "default " + paramType + " " + methodName + "(Runnable code) { return " + methodName + "(code, null); }");
}
}
class Method extends Element {
@@ -362,7 +458,14 @@ class Method extends Element {
tsToJavaMethodName.put("$", "querySelector");
tsToJavaMethodName.put("$$", "querySelectorAll");
tsToJavaMethodName.put("goto", "navigate");
tsToJavaMethodName.put("waitForNavigation", "futureNavigation");
}
static Set<String> waitForMethods = new HashSet<>();
static {
waitForMethods.add("Page.waitForNavigation");
waitForMethods.add("Page.waitForRequest");
waitForMethods.add("Page.waitForResponse");
waitForMethods.add("Frame.waitForNavigation");
}
private static Map<String, String[]> customSignature = new HashMap<>();
@@ -451,38 +554,37 @@ class Method extends Element {
customSignature.put("Page.setInputFiles", setInputFilesWithSelector);
customSignature.put("Frame.setInputFiles", setInputFilesWithSelector);
String[] waitForEvent = {
"default Deferred<Event<EventType>> futureEvent(EventType event) {",
" return futureEvent(event, (FutureEventOptions) null);",
"}",
"default Deferred<Event<EventType>> futureEvent(EventType event, Predicate<Event<EventType>> predicate) {",
" FutureEventOptions options = new FutureEventOptions();",
" options.predicate = predicate;",
" return futureEvent(event, options);",
"}",
"Deferred<Event<EventType>> futureEvent(EventType event, FutureEventOptions options);",
};
customSignature.put("Page.waitForEvent", waitForEvent);
customSignature.put("BrowserContext.waitForEvent", waitForEvent);
customSignature.put("WebSocket.waitForEvent", waitForEvent);
// We only have typed onPage/onDownload/... event listeners in Java.
customSignature.put("Page.waitForEvent", new String[] {});
customSignature.put("BrowserContext.waitForEvent", new String[] {});
customSignature.put("WebSocket.waitForEvent", new String[] {});
customSignature.put("Page.waitForRequest", new String[] {
"default Deferred<Request> futureRequest(String urlGlob) { return futureRequest(urlGlob, null); }",
"default Deferred<Request> futureRequest(Pattern urlPattern) { return futureRequest(urlPattern, null); }",
"default Deferred<Request> futureRequest(Predicate<String> urlPredicate) { return futureRequest(urlPredicate, null); }",
"Deferred<Request> futureRequest(String urlGlob, FutureRequestOptions options);",
"Deferred<Request> futureRequest(Pattern urlPattern, FutureRequestOptions options);",
"Deferred<Request> futureRequest(Predicate<String> urlPredicate, FutureRequestOptions options);"
"Request waitForRequest(Runnable code);",
"default Request waitForRequest(Runnable code, String urlGlob) { return waitForRequest(code, urlGlob, null); }",
"default Request waitForRequest(Runnable code, Pattern urlPattern) { return waitForRequest(code, urlPattern, null); }",
"default Request waitForRequest(Runnable code, Predicate<String> urlPredicate) { return waitForRequest(code, urlPredicate, null); }",
"Request waitForRequest(Runnable code, String urlGlob, WaitForRequestOptions options);",
"Request waitForRequest(Runnable code, Pattern urlPattern, WaitForRequestOptions options);",
"Request waitForRequest(Runnable code, Predicate<String> urlPredicate, WaitForRequestOptions options);"
});
customSignature.put("Page.waitForResponse", new String[] {
"default Deferred<Response> futureResponse(String urlGlob) { return futureResponse(urlGlob, null); }",
"default Deferred<Response> futureResponse(Pattern urlPattern) { return futureResponse(urlPattern, null); }",
"default Deferred<Response> futureResponse(Predicate<String> urlPredicate) { return futureResponse(urlPredicate, null); }",
"Deferred<Response> futureResponse(String urlGlob, FutureResponseOptions options);",
"Deferred<Response> futureResponse(Pattern urlPattern, FutureResponseOptions options);",
"Deferred<Response> futureResponse(Predicate<String> urlPredicate, FutureResponseOptions options);"
"Response waitForResponse(Runnable code);",
"default Response waitForResponse(Runnable code, String urlGlob) { return waitForResponse(code, urlGlob, null); }",
"default Response waitForResponse(Runnable code, Pattern urlPattern) { return waitForResponse(code, urlPattern, null); }",
"default Response waitForResponse(Runnable code, Predicate<String> urlPredicate) { return waitForResponse(code, urlPredicate, null); }",
"Response waitForResponse(Runnable code, String urlGlob, WaitForResponseOptions options);",
"Response waitForResponse(Runnable code, Pattern urlPattern, WaitForResponseOptions options);",
"Response waitForResponse(Runnable code, Predicate<String> urlPredicate, WaitForResponseOptions options);"
});
String[] waitForNavigation = {
"default Response waitForNavigation(Runnable code) { return waitForNavigation(code, null); }",
"Response waitForNavigation(Runnable code, WaitForNavigationOptions options);"
};
customSignature.put("Frame.waitForNavigation", waitForNavigation);
customSignature.put("Page.waitForNavigation", waitForNavigation);
String[] selectOption = {
"default List<String> selectOption(String selector, String value) {",
" return selectOption(selector, value, null);",
@@ -742,15 +844,15 @@ class Field extends Element {
void writeBuilderMethod(List<String> output, String offset, String parentClass) {
if (asList("Frame.waitForNavigation.options.url",
"Page.waitForNavigation.options.url").contains(jsonPath)) {
output.add(offset + "public FutureNavigationOptions withUrl(String glob) {");
output.add(offset + "public WaitForNavigationOptions withUrl(String glob) {");
output.add(offset + " this.glob = glob;");
output.add(offset + " return this;");
output.add(offset + "}");
output.add(offset + "public FutureNavigationOptions withUrl(Pattern pattern) {");
output.add(offset + "public WaitForNavigationOptions withUrl(Pattern pattern) {");
output.add(offset + " this.pattern = pattern;");
output.add(offset + " return this;");
output.add(offset + "}");
output.add(offset + "public FutureNavigationOptions withUrl(Predicate<String> predicate) {");
output.add(offset + "public WaitForNavigationOptions withUrl(Predicate<String> predicate) {");
output.add(offset + " this.predicate = predicate;");
output.add(offset + " return this;");
output.add(offset + "}");
@@ -929,7 +1031,7 @@ class Interface extends TypeDefinition {
output.add("import java.nio.file.Path;");
}
output.add("import java.util.*;");
if (asList("Page", "BrowserContext").contains(jsonName)) {
if (asList("Page", "BrowserContext", "WebSocket", "Worker").contains(jsonName)) {
output.add("import java.util.function.Consumer;");
}
if (asList("Page", "Frame", "BrowserContext", "WebSocket").contains(jsonName)) {
@@ -957,9 +1059,6 @@ class Interface extends TypeDefinition {
for (Method m : methods) {
m.writeTo(output, offset);
}
if ("Worker".equals(jsonName)) {
output.add(offset + "Deferred<Event<EventType>> futureEvent(EventType event);");
}
if ("Playwright".equals(jsonName)) {
output.add("");
output.add(offset + "static Playwright create() {");
@@ -985,6 +1084,16 @@ class Interface extends TypeDefinition {
output.add("");
output.add(offset + "void addListener(EventType type, Listener<EventType> listener);");
output.add(offset + "void removeListener(EventType type, Listener<EventType> listener);");
for (Event e : events) {
output.add("");
e.writeListenerMethods(output, offset);
}
output.add("");
for (Event e : events) {
e.writeWaitForEventIfNeeded(output, offset);
}
output.add("");
}
private void writeSharedTypes(List<String> output, String offset) {
@@ -1184,22 +1293,6 @@ class Interface extends TypeDefinition {
break;
}
}
if (asList("Page", "BrowserContext", "WebSocket").contains(jsonName)){
output.add(offset + "class FutureEventOptions {");
output.add(offset + " public Double timeout;");
output.add(offset + " public Predicate<Event<EventType>> predicate;");
output.add(offset + " public FutureEventOptions withTimeout(double millis) {");
output.add(offset + " timeout = millis;");
output.add(offset + " return this;");
output.add(offset + " }");
output.add(offset + " public FutureEventOptions withPredicate(Predicate<Event<EventType>> predicate) {");
output.add(offset + " this.predicate = predicate;");
output.add(offset + " return this;");
output.add(offset + " }");
output.add(offset + "}");
output.add("");
}
}
}
@@ -261,9 +261,9 @@ class Types {
add("BrowserContext.waitForEvent.optionsOrPredicate", "Function|Object", "String");
add("BrowserContext.waitForEvent", "Promise<any>", "Deferred<Event<EventType>>", new Empty());
add("Page.waitForNavigation.options.url", "RegExp|function(URL):boolean|string", "Custom");
add("Page.waitForNavigation.options", "Object", "FutureNavigationOptions");
add("Page.waitForRequest.options", "Object", "FutureRequestOptions");
add("Page.waitForResponse.options", "Object", "FutureResponseOptions");
add("Page.waitForNavigation.options", "Object", "WaitForNavigationOptions");
add("Page.waitForRequest.options", "Object", "WaitForRequestOptions");
add("Page.waitForResponse.options", "Object", "WaitForResponseOptions");
add("Page.frame.options", "string|Object", "FrameOptions", new Empty());
add("Page.route.url", "RegExp|function(URL):boolean|string", "String");
add("Page.selectOption.values", "Array<ElementHandle>|Array<Object>|Array<string>|ElementHandle|Object|null|string", "String");
@@ -275,7 +275,7 @@ class Types {
add("Page.waitForRequest.urlOrPredicate", "RegExp|function(Request):boolean|string", "String");
add("Page.waitForResponse.urlOrPredicate", "RegExp|function(Response):boolean|string", "String");
add("Frame.waitForNavigation.options.url", "RegExp|function(URL):boolean|string", "Custom");
add("Frame.waitForNavigation.options", "Object", "FutureNavigationOptions");
add("Frame.waitForNavigation.options", "Object", "WaitForNavigationOptions");
add("Frame.selectOption.values", "Array<ElementHandle>|Array<Object>|Array<string>|ElementHandle|Object|null|string", "String");
add("Frame.setInputFiles.files", "Array<Object>|Array<path>|Object|path", "String");
add("ElementHandle.selectOption.values", "Array<ElementHandle>|Array<Object>|Array<string>|ElementHandle|Object|null|string", "String");