Skip to content

Commit

Permalink
feat: roll 1.34 beta driver, implement context events (microsoft#1283)
Browse files Browse the repository at this point in the history
  • Loading branch information
yury-s authored May 18, 2023
1 parent 30a6961 commit 8547c70
Show file tree
Hide file tree
Showing 17 changed files with 438 additions and 33 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom

| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->113.0.5672.53<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Chromium <!-- GEN:chromium-version -->114.0.5735.35<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->16.4<!-- GEN:stop --> ||||
| Firefox <!-- GEN:firefox-version -->112.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Firefox <!-- GEN:firefox-version -->113.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |

Headless execution is supported for all the browsers on all platforms. Check out [system requirements](https://playwright.dev/java/docs/next/intro/#system-requirements) for details.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,51 @@ public interface BrowserContext extends AutoCloseable {
*/
void offClose(Consumer<BrowserContext> handler);

/**
* Emitted when JavaScript within the page calls one of console API methods, e.g. {@code console.log} or {@code
* console.dir}. Also emitted if the page throws an error or a warning.
*
* <p> The arguments passed into {@code console.log} and the page are available on the {@code ConsoleMessage} event handler
* argument.
*
* <p> **Usage**
* <pre>{@code
* context.onConsoleMessage(msg -> {
* for (int i = 0; i < msg.args().size(); ++i)
* System.out.println(i + ": " + msg.args().get(i).jsonValue());
* });
* page.evaluate("() => console.log('hello', 5, { foo: 'bar' })");
* }</pre>
*/
void onConsoleMessage(Consumer<ConsoleMessage> handler);
/**
* Removes handler that was previously added with {@link #onConsoleMessage onConsoleMessage(handler)}.
*/
void offConsoleMessage(Consumer<ConsoleMessage> handler);

/**
* Emitted when a JavaScript dialog appears, such as {@code alert}, {@code prompt}, {@code confirm} or {@code
* beforeunload}. Listener **must** either {@link Dialog#accept Dialog.accept()} or {@link Dialog#dismiss Dialog.dismiss()}
* the dialog - otherwise the page will <a
* href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#never_blocking">freeze</a> waiting for the
* dialog, and actions like click will never finish.
*
* <p> **Usage**
* <pre>{@code
* context.onDialog(dialog -> {
* dialog.accept();
* });
* }</pre>
*
* <p> <strong>NOTE:</strong> When no {@link Page#onDialog Page.onDialog()} or {@link BrowserContext#onDialog BrowserContext.onDialog()} listeners are
* present, all dialogs are automatically dismissed.
*/
void onDialog(Consumer<Dialog> handler);
/**
* Removes handler that was previously added with {@link #onDialog onDialog(handler)}.
*/
void offDialog(Consumer<Dialog> handler);

/**
* The event is emitted when a new Page is created in the BrowserContext. The page may still be loading. The event will
* also fire for popup pages. See also {@link Page#onPopup Page.onPopup()} to receive events about popups relevant to a
Expand Down Expand Up @@ -295,6 +340,33 @@ public WaitForConditionOptions setTimeout(double timeout) {
return this;
}
}
class WaitForConsoleMessageOptions {
/**
* Receives the {@code ConsoleMessage} object and resolves to truthy value when the waiting should resolve.
*/
public Predicate<ConsoleMessage> predicate;
/**
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The
* default value can be changed by using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()}.
*/
public Double timeout;

/**
* Receives the {@code ConsoleMessage} object and resolves to truthy value when the waiting should resolve.
*/
public WaitForConsoleMessageOptions setPredicate(Predicate<ConsoleMessage> predicate) {
this.predicate = predicate;
return this;
}
/**
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The
* default value can be changed by using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()}.
*/
public WaitForConsoleMessageOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
class WaitForPageOptions {
/**
* Receives the {@code Page} object and resolves to truthy value when the waiting should resolve.
Expand Down Expand Up @@ -331,6 +403,9 @@ public WaitForPageOptions setTimeout(double timeout) {
* browserContext.addCookies(Arrays.asList(cookieObject1, cookieObject2));
* }</pre>
*
* @param cookies Adds cookies to the browser context.
*
* <p> For the cookie to apply to all subdomains as well, prefix domain with a dot, like this: ".example.com".
* @since v1.8
*/
void addCookies(List<Cookie> cookies);
Expand Down Expand Up @@ -1242,6 +1317,28 @@ default void waitForCondition(BooleanSupplier condition) {
* @since v1.32
*/
void waitForCondition(BooleanSupplier condition, WaitForConditionOptions options);
/**
* Performs action and waits for a {@code ConsoleMessage} to be logged by in the pages in the context. If predicate is
* provided, it passes {@code ConsoleMessage} value into the {@code predicate} function and waits for {@code
* predicate(message)} to return a truthy value. Will throw an error if the page is closed before the {@link
* BrowserContext#onConsoleMessage BrowserContext.onConsoleMessage()} event is fired.
*
* @param callback Callback that performs the action triggering the event.
* @since v1.34
*/
default ConsoleMessage waitForConsoleMessage(Runnable callback) {
return waitForConsoleMessage(null, callback);
}
/**
* Performs action and waits for a {@code ConsoleMessage} to be logged by in the pages in the context. If predicate is
* provided, it passes {@code ConsoleMessage} value into the {@code predicate} function and waits for {@code
* predicate(message)} to return a truthy value. Will throw an error if the page is closed before the {@link
* BrowserContext#onConsoleMessage BrowserContext.onConsoleMessage()} event is fired.
*
* @param callback Callback that performs the action triggering the event.
* @since v1.34
*/
ConsoleMessage waitForConsoleMessage(WaitForConsoleMessageOptions options, Runnable callback);
/**
* Performs action and waits for a new {@code Page} to be created in the context. If predicate is provided, it passes
* {@code Page} value into the {@code predicate} function and waits for {@code predicate(event)} to return a truthy value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ public interface ConsoleMessage {
* @since v1.8
*/
String location();
/**
* The page that produced this console message, if any.
*
* @since v1.33
*/
Page page();
/**
* The text of the console message.
*
Expand Down
6 changes: 6 additions & 0 deletions playwright/src/main/java/com/microsoft/playwright/Dialog.java
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ default void accept() {
* @since v1.8
*/
String message();
/**
* The page that initiated this dialog, if available.
*
* @since v1.33
*/
Page page();
/**
* Returns dialog's type, can be one of {@code alert}, {@code beforeunload}, {@code confirm} or {@code prompt}.
*
Expand Down
14 changes: 14 additions & 0 deletions playwright/src/main/java/com/microsoft/playwright/Locator.java
Original file line number Diff line number Diff line change
Expand Up @@ -2056,6 +2056,20 @@ public WaitForOptions setTimeout(double timeout) {
* @since v1.14
*/
List<String> allTextContents();
/**
* Creates a locator that matches both this locator and the argument locator.
*
* <p> **Usage**
*
* <p> The following example finds a button with a specific title.
* <pre>{@code
* Locator button = page.getByRole(AriaRole.BUTTON).and(page.getByTitle("Subscribe"));
* }</pre>
*
* @param locator Additional locator to match.
* @since v1.33
*/
Locator and(Locator locator);
/**
* Calls <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/blur">blur</a> on the element.
*
Expand Down
17 changes: 10 additions & 7 deletions playwright/src/main/java/com/microsoft/playwright/Page.java
Original file line number Diff line number Diff line change
Expand Up @@ -82,15 +82,15 @@ public interface Page extends AutoCloseable {
* Emitted when JavaScript within the page calls one of console API methods, e.g. {@code console.log} or {@code
* console.dir}. Also emitted if the page throws an error or a warning.
*
* <p> The arguments passed into {@code console.log} appear as arguments on the event handler.
* <p> The arguments passed into {@code console.log} are available on the {@code ConsoleMessage} event handler argument.
*
* <p> An example of handling {@code console} event:
* <p> **Usage**
* <pre>{@code
* page.onConsoleMessage(msg -> {
* for (int i = 0; i < msg.args().size(); ++i)
* System.out.println(i + ": " + msg.args().get(i).jsonValue());
* });
* page.evaluate("() => console.log('hello', 5, {foo: 'bar'})");
* page.evaluate("() => console.log('hello', 5, { foo: 'bar' })");
* }</pre>
*/
void onConsoleMessage(Consumer<ConsoleMessage> handler);
Expand Down Expand Up @@ -127,13 +127,16 @@ public interface Page extends AutoCloseable {
* the dialog - otherwise the page will <a
* href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#never_blocking">freeze</a> waiting for the
* dialog, and actions like click will never finish.
*
* <p> **Usage**
* <pre>{@code
* page.onDialog(dialog -> {
* dialog.accept();
* });
* }</pre>
*
* <p> <strong>NOTE:</strong> When no {@link Page#onDialog Page.onDialog()} listeners are present, all dialogs are automatically dismissed.
* <p> <strong>NOTE:</strong> When no {@link Page#onDialog Page.onDialog()} or {@link BrowserContext#onDialog BrowserContext.onDialog()} listeners are
* present, all dialogs are automatically dismissed.
*/
void onDialog(Consumer<Dialog> handler);
/**
Expand Down Expand Up @@ -2394,7 +2397,7 @@ class ScreenshotOptions {
*/
public ScreenshotCaret caret;
/**
* An object which specifies clipping of the resulting image. Should have the following fields:
* An object which specifies clipping of the resulting image.
*/
public Clip clip;
/**
Expand Down Expand Up @@ -2464,13 +2467,13 @@ public ScreenshotOptions setCaret(ScreenshotCaret caret) {
return this;
}
/**
* An object which specifies clipping of the resulting image. Should have the following fields:
* An object which specifies clipping of the resulting image.
*/
public ScreenshotOptions setClip(double x, double y, double width, double height) {
return setClip(new Clip(x, y, width, height));
}
/**
* An object which specifies clipping of the resulting image. Should have the following fields:
* An object which specifies clipping of the resulting image.
*/
public ScreenshotOptions setClip(Clip clip) {
this.clip = clip;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
PageImpl ownerPage;
private static final Map<EventType, String> eventSubscriptions() {
Map<EventType, String> result = new HashMap<>();
result.put(EventType.CONSOLE, "console");
result.put(EventType.DIALOG, "dialog");
result.put(EventType.REQUEST, "request");
result.put(EventType.RESPONSE, "response");
result.put(EventType.REQUESTFINISHED, "requestFinished");
Expand All @@ -77,6 +79,8 @@ static class HarRecorder {

enum EventType {
CLOSE,
CONSOLE,
DIALOG,
PAGE,
REQUEST,
REQUESTFAILED,
Expand Down Expand Up @@ -120,6 +124,26 @@ public void offClose(Consumer<BrowserContext> handler) {
listeners.remove(EventType.CLOSE, handler);
}

@Override
public void onConsoleMessage(Consumer<ConsoleMessage> handler) {
listeners.add(EventType.CONSOLE, handler);
}

@Override
public void offConsoleMessage(Consumer<ConsoleMessage> handler) {
listeners.remove(EventType.CONSOLE, 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 onPage(Consumer<Page> handler) {
listeners.add(EventType.PAGE, handler);
Expand Down Expand Up @@ -516,6 +540,18 @@ public void waitForCondition(BooleanSupplier predicate, WaitForConditionOptions
runUntil(() -> {}, new WaitableRace<>(waitables));
}

@Override
public ConsoleMessage waitForConsoleMessage(WaitForConsoleMessageOptions options, Runnable code) {
return withWaitLogging("BrowserContext.waitForConsoleMessage", logger -> waitForConsoleMessageImpl(options, code));
}

private ConsoleMessage waitForConsoleMessageImpl(WaitForConsoleMessageOptions options, Runnable code) {
if (options == null) {
options = new WaitForConsoleMessageOptions();
}
return waitForEventWithTimeout(EventType.CONSOLE, code, options.predicate, options.timeout);
}

private class WaitableContextClose<R> extends WaitableEvent<EventType, R> {
WaitableContextClose() {
super(BrowserContextImpl.this.listeners, EventType.CLOSE);
Expand Down Expand Up @@ -554,7 +590,36 @@ WaitableResult<JsonElement> pause() {

@Override
protected void handleEvent(String event, JsonObject params) {
if ("route".equals(event)) {
if ("dialog".equals(event)) {
String guid = params.getAsJsonObject("dialog").get("guid").getAsString();
DialogImpl dialog = connection.getExistingObject(guid);
boolean hasListeners = false;
if (listeners.hasListeners(EventType.DIALOG)) {
hasListeners = true;
listeners.notify(EventType.DIALOG, dialog);
}
PageImpl page = dialog.page();
if (page != null) {
if (page.listeners.hasListeners(PageImpl.EventType.DIALOG)) {
hasListeners = true;
page.listeners.notify(PageImpl.EventType.DIALOG, dialog);
}
}
// Although we do similar handling on the server side, we still need this logic
// on the client side due to a possible race condition between two async calls:
// a) removing "dialog" listener subscription (client->server)
// b) actual "dialog" event (server->client)
if (!hasListeners) {
if ("beforeunload".equals(dialog.type())) {
try {
dialog.accept();
} catch (PlaywrightException e) {
}
} else {
dialog.dismiss();
}
}
} else if ("route".equals(event)) {
RouteImpl route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
handleRoute(route);
} else if ("page".equals(event)) {
Expand All @@ -570,6 +635,14 @@ protected void handleEvent(String event, JsonObject params) {
if (binding != null) {
bindingCall.call(binding);
}
} else if ("console".equals(event)) {
String guid = params.getAsJsonObject("message").get("guid").getAsString();
ConsoleMessageImpl message = connection.getExistingObject(guid);
listeners.notify(BrowserContextImpl.EventType.CONSOLE, message);
PageImpl page = message.page();
if (page != null) {
page.listeners.notify(PageImpl.EventType.CONSOLE, message);
}
} else if ("request".equals(event)) {
String guid = params.getAsJsonObject("request").get("guid").getAsString();
RequestImpl request = connection.getExistingObject(guid);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,24 @@
import com.google.gson.JsonObject;
import com.microsoft.playwright.ConsoleMessage;
import com.microsoft.playwright.JSHandle;
import com.microsoft.playwright.Page;

import java.util.ArrayList;
import java.util.List;

import static com.microsoft.playwright.impl.Serialization.gson;

public class ConsoleMessageImpl extends ChannelOwner implements ConsoleMessage {
private PageImpl page;

public ConsoleMessageImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
// Note: currently, we only report console messages for pages and they always have a page.
// However, in the future we might report console messages for service workers or something else,
// where page() would be null.
if (initializer.has("page")) {
page = connection.getExistingObject(initializer.getAsJsonObject("page").get("guid").getAsString());
}
}

public String type() {
Expand All @@ -55,4 +64,9 @@ public String location() {
location.get("lineNumber").getAsNumber() + ":" +
location.get("columnNumber").getAsNumber();
}

@Override
public PageImpl page() {
return page;
}
}
Loading

0 comments on commit 8547c70

Please sign in to comment.