Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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