Skip to content

Commit 8547c70

Browse files
authored
feat: roll 1.34 beta driver, implement context events (#1283)
1 parent 30a6961 commit 8547c70

File tree

17 files changed

+438
-33
lines changed

17 files changed

+438
-33
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom
1111

1212
| | Linux | macOS | Windows |
1313
| :--- | :---: | :---: | :---: |
14-
| Chromium <!-- GEN:chromium-version -->113.0.5672.53<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
14+
| Chromium <!-- GEN:chromium-version -->114.0.5735.35<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
1515
| WebKit <!-- GEN:webkit-version -->16.4<!-- GEN:stop --> ||||
16-
| Firefox <!-- GEN:firefox-version -->112.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
16+
| Firefox <!-- GEN:firefox-version -->113.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
1717

1818
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.
1919

playwright/src/main/java/com/microsoft/playwright/BrowserContext.java

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,51 @@ public interface BrowserContext extends AutoCloseable {
5858
*/
5959
void offClose(Consumer<BrowserContext> handler);
6060

61+
/**
62+
* Emitted when JavaScript within the page calls one of console API methods, e.g. {@code console.log} or {@code
63+
* console.dir}. Also emitted if the page throws an error or a warning.
64+
*
65+
* <p> The arguments passed into {@code console.log} and the page are available on the {@code ConsoleMessage} event handler
66+
* argument.
67+
*
68+
* <p> **Usage**
69+
* <pre>{@code
70+
* context.onConsoleMessage(msg -> {
71+
* for (int i = 0; i < msg.args().size(); ++i)
72+
* System.out.println(i + ": " + msg.args().get(i).jsonValue());
73+
* });
74+
* page.evaluate("() => console.log('hello', 5, { foo: 'bar' })");
75+
* }</pre>
76+
*/
77+
void onConsoleMessage(Consumer<ConsoleMessage> handler);
78+
/**
79+
* Removes handler that was previously added with {@link #onConsoleMessage onConsoleMessage(handler)}.
80+
*/
81+
void offConsoleMessage(Consumer<ConsoleMessage> handler);
82+
83+
/**
84+
* Emitted when a JavaScript dialog appears, such as {@code alert}, {@code prompt}, {@code confirm} or {@code
85+
* beforeunload}. Listener **must** either {@link Dialog#accept Dialog.accept()} or {@link Dialog#dismiss Dialog.dismiss()}
86+
* the dialog - otherwise the page will <a
87+
* href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#never_blocking">freeze</a> waiting for the
88+
* dialog, and actions like click will never finish.
89+
*
90+
* <p> **Usage**
91+
* <pre>{@code
92+
* context.onDialog(dialog -> {
93+
* dialog.accept();
94+
* });
95+
* }</pre>
96+
*
97+
* <p> <strong>NOTE:</strong> When no {@link Page#onDialog Page.onDialog()} or {@link BrowserContext#onDialog BrowserContext.onDialog()} listeners are
98+
* present, all dialogs are automatically dismissed.
99+
*/
100+
void onDialog(Consumer<Dialog> handler);
101+
/**
102+
* Removes handler that was previously added with {@link #onDialog onDialog(handler)}.
103+
*/
104+
void offDialog(Consumer<Dialog> handler);
105+
61106
/**
62107
* The event is emitted when a new Page is created in the BrowserContext. The page may still be loading. The event will
63108
* also fire for popup pages. See also {@link Page#onPopup Page.onPopup()} to receive events about popups relevant to a
@@ -295,6 +340,33 @@ public WaitForConditionOptions setTimeout(double timeout) {
295340
return this;
296341
}
297342
}
343+
class WaitForConsoleMessageOptions {
344+
/**
345+
* Receives the {@code ConsoleMessage} object and resolves to truthy value when the waiting should resolve.
346+
*/
347+
public Predicate<ConsoleMessage> predicate;
348+
/**
349+
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The
350+
* default value can be changed by using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()}.
351+
*/
352+
public Double timeout;
353+
354+
/**
355+
* Receives the {@code ConsoleMessage} object and resolves to truthy value when the waiting should resolve.
356+
*/
357+
public WaitForConsoleMessageOptions setPredicate(Predicate<ConsoleMessage> predicate) {
358+
this.predicate = predicate;
359+
return this;
360+
}
361+
/**
362+
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The
363+
* default value can be changed by using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()}.
364+
*/
365+
public WaitForConsoleMessageOptions setTimeout(double timeout) {
366+
this.timeout = timeout;
367+
return this;
368+
}
369+
}
298370
class WaitForPageOptions {
299371
/**
300372
* Receives the {@code Page} object and resolves to truthy value when the waiting should resolve.
@@ -331,6 +403,9 @@ public WaitForPageOptions setTimeout(double timeout) {
331403
* browserContext.addCookies(Arrays.asList(cookieObject1, cookieObject2));
332404
* }</pre>
333405
*
406+
* @param cookies Adds cookies to the browser context.
407+
*
408+
* <p> For the cookie to apply to all subdomains as well, prefix domain with a dot, like this: ".example.com".
334409
* @since v1.8
335410
*/
336411
void addCookies(List<Cookie> cookies);
@@ -1242,6 +1317,28 @@ default void waitForCondition(BooleanSupplier condition) {
12421317
* @since v1.32
12431318
*/
12441319
void waitForCondition(BooleanSupplier condition, WaitForConditionOptions options);
1320+
/**
1321+
* Performs action and waits for a {@code ConsoleMessage} to be logged by in the pages in the context. If predicate is
1322+
* provided, it passes {@code ConsoleMessage} value into the {@code predicate} function and waits for {@code
1323+
* predicate(message)} to return a truthy value. Will throw an error if the page is closed before the {@link
1324+
* BrowserContext#onConsoleMessage BrowserContext.onConsoleMessage()} event is fired.
1325+
*
1326+
* @param callback Callback that performs the action triggering the event.
1327+
* @since v1.34
1328+
*/
1329+
default ConsoleMessage waitForConsoleMessage(Runnable callback) {
1330+
return waitForConsoleMessage(null, callback);
1331+
}
1332+
/**
1333+
* Performs action and waits for a {@code ConsoleMessage} to be logged by in the pages in the context. If predicate is
1334+
* provided, it passes {@code ConsoleMessage} value into the {@code predicate} function and waits for {@code
1335+
* predicate(message)} to return a truthy value. Will throw an error if the page is closed before the {@link
1336+
* BrowserContext#onConsoleMessage BrowserContext.onConsoleMessage()} event is fired.
1337+
*
1338+
* @param callback Callback that performs the action triggering the event.
1339+
* @since v1.34
1340+
*/
1341+
ConsoleMessage waitForConsoleMessage(WaitForConsoleMessageOptions options, Runnable callback);
12451342
/**
12461343
* Performs action and waits for a new {@code Page} to be created in the context. If predicate is provided, it passes
12471344
* {@code Page} value into the {@code predicate} function and waits for {@code predicate(event)} to return a truthy value.

playwright/src/main/java/com/microsoft/playwright/ConsoleMessage.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ public interface ConsoleMessage {
5656
* @since v1.8
5757
*/
5858
String location();
59+
/**
60+
* The page that produced this console message, if any.
61+
*
62+
* @since v1.33
63+
*/
64+
Page page();
5965
/**
6066
* The text of the console message.
6167
*

playwright/src/main/java/com/microsoft/playwright/Dialog.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@ default void accept() {
8181
* @since v1.8
8282
*/
8383
String message();
84+
/**
85+
* The page that initiated this dialog, if available.
86+
*
87+
* @since v1.33
88+
*/
89+
Page page();
8490
/**
8591
* Returns dialog's type, can be one of {@code alert}, {@code beforeunload}, {@code confirm} or {@code prompt}.
8692
*

playwright/src/main/java/com/microsoft/playwright/Locator.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2056,6 +2056,20 @@ public WaitForOptions setTimeout(double timeout) {
20562056
* @since v1.14
20572057
*/
20582058
List<String> allTextContents();
2059+
/**
2060+
* Creates a locator that matches both this locator and the argument locator.
2061+
*
2062+
* <p> **Usage**
2063+
*
2064+
* <p> The following example finds a button with a specific title.
2065+
* <pre>{@code
2066+
* Locator button = page.getByRole(AriaRole.BUTTON).and(page.getByTitle("Subscribe"));
2067+
* }</pre>
2068+
*
2069+
* @param locator Additional locator to match.
2070+
* @since v1.33
2071+
*/
2072+
Locator and(Locator locator);
20592073
/**
20602074
* Calls <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/blur">blur</a> on the element.
20612075
*

playwright/src/main/java/com/microsoft/playwright/Page.java

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,15 +82,15 @@ public interface Page extends AutoCloseable {
8282
* Emitted when JavaScript within the page calls one of console API methods, e.g. {@code console.log} or {@code
8383
* console.dir}. Also emitted if the page throws an error or a warning.
8484
*
85-
* <p> The arguments passed into {@code console.log} appear as arguments on the event handler.
85+
* <p> The arguments passed into {@code console.log} are available on the {@code ConsoleMessage} event handler argument.
8686
*
87-
* <p> An example of handling {@code console} event:
87+
* <p> **Usage**
8888
* <pre>{@code
8989
* page.onConsoleMessage(msg -> {
9090
* for (int i = 0; i < msg.args().size(); ++i)
9191
* System.out.println(i + ": " + msg.args().get(i).jsonValue());
9292
* });
93-
* page.evaluate("() => console.log('hello', 5, {foo: 'bar'})");
93+
* page.evaluate("() => console.log('hello', 5, { foo: 'bar' })");
9494
* }</pre>
9595
*/
9696
void onConsoleMessage(Consumer<ConsoleMessage> handler);
@@ -127,13 +127,16 @@ public interface Page extends AutoCloseable {
127127
* the dialog - otherwise the page will <a
128128
* href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#never_blocking">freeze</a> waiting for the
129129
* dialog, and actions like click will never finish.
130+
*
131+
* <p> **Usage**
130132
* <pre>{@code
131133
* page.onDialog(dialog -> {
132134
* dialog.accept();
133135
* });
134136
* }</pre>
135137
*
136-
* <p> <strong>NOTE:</strong> When no {@link Page#onDialog Page.onDialog()} listeners are present, all dialogs are automatically dismissed.
138+
* <p> <strong>NOTE:</strong> When no {@link Page#onDialog Page.onDialog()} or {@link BrowserContext#onDialog BrowserContext.onDialog()} listeners are
139+
* present, all dialogs are automatically dismissed.
137140
*/
138141
void onDialog(Consumer<Dialog> handler);
139142
/**
@@ -2394,7 +2397,7 @@ class ScreenshotOptions {
23942397
*/
23952398
public ScreenshotCaret caret;
23962399
/**
2397-
* An object which specifies clipping of the resulting image. Should have the following fields:
2400+
* An object which specifies clipping of the resulting image.
23982401
*/
23992402
public Clip clip;
24002403
/**
@@ -2464,13 +2467,13 @@ public ScreenshotOptions setCaret(ScreenshotCaret caret) {
24642467
return this;
24652468
}
24662469
/**
2467-
* An object which specifies clipping of the resulting image. Should have the following fields:
2470+
* An object which specifies clipping of the resulting image.
24682471
*/
24692472
public ScreenshotOptions setClip(double x, double y, double width, double height) {
24702473
return setClip(new Clip(x, y, width, height));
24712474
}
24722475
/**
2473-
* An object which specifies clipping of the resulting image. Should have the following fields:
2476+
* An object which specifies clipping of the resulting image.
24742477
*/
24752478
public ScreenshotOptions setClip(Clip clip) {
24762479
this.clip = clip;

playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
5353
PageImpl ownerPage;
5454
private static final Map<EventType, String> eventSubscriptions() {
5555
Map<EventType, String> result = new HashMap<>();
56+
result.put(EventType.CONSOLE, "console");
57+
result.put(EventType.DIALOG, "dialog");
5658
result.put(EventType.REQUEST, "request");
5759
result.put(EventType.RESPONSE, "response");
5860
result.put(EventType.REQUESTFINISHED, "requestFinished");
@@ -77,6 +79,8 @@ static class HarRecorder {
7779

7880
enum EventType {
7981
CLOSE,
82+
CONSOLE,
83+
DIALOG,
8084
PAGE,
8185
REQUEST,
8286
REQUESTFAILED,
@@ -120,6 +124,26 @@ public void offClose(Consumer<BrowserContext> handler) {
120124
listeners.remove(EventType.CLOSE, handler);
121125
}
122126

127+
@Override
128+
public void onConsoleMessage(Consumer<ConsoleMessage> handler) {
129+
listeners.add(EventType.CONSOLE, handler);
130+
}
131+
132+
@Override
133+
public void offConsoleMessage(Consumer<ConsoleMessage> handler) {
134+
listeners.remove(EventType.CONSOLE, handler);
135+
}
136+
137+
@Override
138+
public void onDialog(Consumer<Dialog> handler) {
139+
listeners.add(EventType.DIALOG, handler);
140+
}
141+
142+
@Override
143+
public void offDialog(Consumer<Dialog> handler) {
144+
listeners.remove(EventType.DIALOG, handler);
145+
}
146+
123147
@Override
124148
public void onPage(Consumer<Page> handler) {
125149
listeners.add(EventType.PAGE, handler);
@@ -516,6 +540,18 @@ public void waitForCondition(BooleanSupplier predicate, WaitForConditionOptions
516540
runUntil(() -> {}, new WaitableRace<>(waitables));
517541
}
518542

543+
@Override
544+
public ConsoleMessage waitForConsoleMessage(WaitForConsoleMessageOptions options, Runnable code) {
545+
return withWaitLogging("BrowserContext.waitForConsoleMessage", logger -> waitForConsoleMessageImpl(options, code));
546+
}
547+
548+
private ConsoleMessage waitForConsoleMessageImpl(WaitForConsoleMessageOptions options, Runnable code) {
549+
if (options == null) {
550+
options = new WaitForConsoleMessageOptions();
551+
}
552+
return waitForEventWithTimeout(EventType.CONSOLE, code, options.predicate, options.timeout);
553+
}
554+
519555
private class WaitableContextClose<R> extends WaitableEvent<EventType, R> {
520556
WaitableContextClose() {
521557
super(BrowserContextImpl.this.listeners, EventType.CLOSE);
@@ -554,7 +590,36 @@ WaitableResult<JsonElement> pause() {
554590

555591
@Override
556592
protected void handleEvent(String event, JsonObject params) {
557-
if ("route".equals(event)) {
593+
if ("dialog".equals(event)) {
594+
String guid = params.getAsJsonObject("dialog").get("guid").getAsString();
595+
DialogImpl dialog = connection.getExistingObject(guid);
596+
boolean hasListeners = false;
597+
if (listeners.hasListeners(EventType.DIALOG)) {
598+
hasListeners = true;
599+
listeners.notify(EventType.DIALOG, dialog);
600+
}
601+
PageImpl page = dialog.page();
602+
if (page != null) {
603+
if (page.listeners.hasListeners(PageImpl.EventType.DIALOG)) {
604+
hasListeners = true;
605+
page.listeners.notify(PageImpl.EventType.DIALOG, dialog);
606+
}
607+
}
608+
// Although we do similar handling on the server side, we still need this logic
609+
// on the client side due to a possible race condition between two async calls:
610+
// a) removing "dialog" listener subscription (client->server)
611+
// b) actual "dialog" event (server->client)
612+
if (!hasListeners) {
613+
if ("beforeunload".equals(dialog.type())) {
614+
try {
615+
dialog.accept();
616+
} catch (PlaywrightException e) {
617+
}
618+
} else {
619+
dialog.dismiss();
620+
}
621+
}
622+
} else if ("route".equals(event)) {
558623
RouteImpl route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
559624
handleRoute(route);
560625
} else if ("page".equals(event)) {
@@ -570,6 +635,14 @@ protected void handleEvent(String event, JsonObject params) {
570635
if (binding != null) {
571636
bindingCall.call(binding);
572637
}
638+
} else if ("console".equals(event)) {
639+
String guid = params.getAsJsonObject("message").get("guid").getAsString();
640+
ConsoleMessageImpl message = connection.getExistingObject(guid);
641+
listeners.notify(BrowserContextImpl.EventType.CONSOLE, message);
642+
PageImpl page = message.page();
643+
if (page != null) {
644+
page.listeners.notify(PageImpl.EventType.CONSOLE, message);
645+
}
573646
} else if ("request".equals(event)) {
574647
String guid = params.getAsJsonObject("request").get("guid").getAsString();
575648
RequestImpl request = connection.getExistingObject(guid);

playwright/src/main/java/com/microsoft/playwright/impl/ConsoleMessageImpl.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,24 @@
2020
import com.google.gson.JsonObject;
2121
import com.microsoft.playwright.ConsoleMessage;
2222
import com.microsoft.playwright.JSHandle;
23+
import com.microsoft.playwright.Page;
2324

2425
import java.util.ArrayList;
2526
import java.util.List;
2627

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

2930
public class ConsoleMessageImpl extends ChannelOwner implements ConsoleMessage {
31+
private PageImpl page;
32+
3033
public ConsoleMessageImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
3134
super(parent, type, guid, initializer);
35+
// Note: currently, we only report console messages for pages and they always have a page.
36+
// However, in the future we might report console messages for service workers or something else,
37+
// where page() would be null.
38+
if (initializer.has("page")) {
39+
page = connection.getExistingObject(initializer.getAsJsonObject("page").get("guid").getAsString());
40+
}
3241
}
3342

3443
public String type() {
@@ -55,4 +64,9 @@ public String location() {
5564
location.get("lineNumber").getAsNumber() + ":" +
5665
location.get("columnNumber").getAsNumber();
5766
}
67+
68+
@Override
69+
public PageImpl page() {
70+
return page;
71+
}
5872
}

0 commit comments

Comments
 (0)