1
0
mirror of synced 2026-08-07 16:06:55 +00:00

feat: add console listener, generate some events

This commit is contained in:
Yury Semikhatsky
2020-09-30 18:25:24 -07:00
parent 694950d036
commit 06132ebfb2
11 changed files with 229 additions and 78 deletions
@@ -205,6 +205,84 @@ abstract class TypeDefinition extends Element {
}
}
class Event extends Element {
private final TypeRef type;
private enum ApiType {HANDLER, LISTENER, WAIT_FOR}
private static class Info {
final String typePrefix;
final ApiType apiType;
Info(String typePrefix, ApiType apiType) {
this.typePrefix = typePrefix;
this.apiType = apiType;
}
}
private static Map<String, Info> events = new HashMap<>();
private static void add(String jsonPath, String typePrefix, ApiType apiType) {
events.put(jsonPath, new Info(typePrefix, apiType));
}
static {
add("Browser.disconnected", "Disconnected", ApiType.WAIT_FOR);
add("BrowserContext.page", "Page", ApiType.WAIT_FOR);
add("Page.console", "Console", ApiType.LISTENER);
add("Page.crash", "Crash", ApiType.WAIT_FOR);
add("Page.dialog", "Dialog", ApiType.HANDLER);
add("Page.domcontentloaded", "DomContentLoaded", ApiType.WAIT_FOR);
add("Page.download", "Download", ApiType.WAIT_FOR);
add("Page.filechooser", "FileChooser", ApiType.HANDLER);
add("Page.frameattached", "FrameAttached", ApiType.WAIT_FOR);
add("Page.framedetached", "FrameDetached", ApiType.WAIT_FOR);
add("Page.framenavigated", "FrameNavigated", ApiType.WAIT_FOR);
add("Page.load", "Load", ApiType.WAIT_FOR);
add("Page.pageerror", "Error", ApiType.LISTENER);
add("Page.popup", "Popup", ApiType.WAIT_FOR);
add("Page.request", "Request", ApiType.WAIT_FOR);
add("Page.requestfailed", "RequestFailed", ApiType.WAIT_FOR);
add("Page.requestfinished", "RequestFinished", ApiType.WAIT_FOR);
add("Page.response", "Response", ApiType.WAIT_FOR);
add("Page.worker", "Worker", ApiType.WAIT_FOR);
add("Worker.close", "Close", ApiType.WAIT_FOR);
add("ChromiumBrowser.disconnected", "Disconnected", ApiType.WAIT_FOR);
add("ChromiumBrowserContext.backgroundpage", "BackgroundPage", ApiType.WAIT_FOR);
add("ChromiumBrowserContext.serviceworker", "ServiceWorker", ApiType.WAIT_FOR);
add("ChromiumBrowserContext.page", "Page", ApiType.WAIT_FOR);
add("FirefoxBrowser.disconnected", "Disconnected", ApiType.WAIT_FOR);
add("WebKitBrowser.disconnected", "Disconnected", ApiType.WAIT_FOR);
}
Event(Element parent, JsonObject jsonElement) {
super(parent, jsonElement);
type = new TypeRef(this, jsonElement.get("type"));
if (!events.containsKey(jsonPath)) {
throw new RuntimeException("Type mapping is missing for event: " + jsonPath);
}
}
void writeTo(List<String> output, String offset) {
// TODO: only whitelisted events are generated for now as the API may change.
if (!"Page.console".equals(jsonPath) &&
!"Page.popup".equals(jsonPath)) {
return;
}
Info info = events.get(jsonPath);
String templateArg = type.toJava().replace("void", "Void");
if (info.apiType == ApiType.WAIT_FOR) {
output.add(offset + "Deferred<" + templateArg + "> waitFor" + info.typePrefix + "();");
return;
}
if (info.apiType == ApiType.LISTENER || info.apiType == ApiType.HANDLER) {
String listenerType = info.typePrefix;
output.add(offset + "void add" + listenerType + "Listener(Listener<" + templateArg + "> listener);");
output.add(offset + "void remove" + listenerType + "Listener(Listener<" + templateArg + "> listener);");
return;
}
throw new RuntimeException("Unexpected apiType " + info.apiType + " for: " + jsonPath);
}
}
class Method extends Element {
final TypeRef returnType;
final List<Param> params = new ArrayList<>();
@@ -307,13 +385,20 @@ class Field extends Element {
return type.toJava() + " " + name;
}
void writeTo(List<String> output, String offset) {
output.add(offset + "public " + toJava() + ";");
void writeTo(List<String> output, String offset, String access) {
output.add(offset + access + toJava() + ";");
}
void writeGetter(List<String> output, String offset) {
output.add(offset + "public " + type.toJava() + " " + name + "() {");
output.add(offset + " return this." + name + ";");
output.add(offset + "}");
}
}
class Interface extends TypeDefinition {
private final List<Method> methods = new ArrayList<>();
private final List<Event> events = new ArrayList<>();
private static String header = "/**\n" +
" * Copyright (c) Microsoft Corporation.\n" +
" *\n" +
@@ -336,14 +421,15 @@ class Interface extends TypeDefinition {
super(null, jsonElement);
JsonObject members = jsonElement.get("members").getAsJsonObject();
for (Map.Entry<String, JsonElement> m : members.entrySet())
createMember(m.getValue().getAsJsonObject());
}
private void createMember(JsonObject json) {
String kind = json.get("kind").getAsString();
if ("method".equals(kind)) {
methods.add(new Method(this, json));
for (Map.Entry<String, JsonElement> m : members.entrySet()) {
JsonObject json = m.getValue().getAsJsonObject();
String kind = json.get("kind").getAsString();
if ("method".equals(kind)) {
methods.add(new Method(this, json));
}
if ("event".equals(kind)) {
events.add(new Event(this, json));
}
}
}
@@ -354,6 +440,9 @@ class Interface extends TypeDefinition {
output.add("");
output.add("public interface " + jsonName + " {");
super.writeTo(output, offset + " ");
for (Event e : events) {
e.writeTo(output, offset + " ");
}
for (Method m : methods) {
m.writeTo(output, offset + " ");
}
@@ -381,11 +470,20 @@ class NestedClass extends TypeDefinition {
output.add(offset + access + "class " + name + " {");
String bodyOffset = offset + " ";
super.writeTo(output, bodyOffset);
boolean isReturnType = parent.parent instanceof Method;
String fieldAccess = isReturnType ? "private " : "public ";
for (Field f : fields) {
f.writeTo(output, bodyOffset);
f.writeTo(output, bodyOffset, fieldAccess);
}
output.add("");
writeBuilderMethods(output, bodyOffset);
if (isReturnType) {
for (Field f : fields) {
f.writeGetter(output, bodyOffset);
}
} else {
writeBuilderMethods(output, bodyOffset);
}
output.add(offset + "}");
}
@@ -21,21 +21,18 @@ import java.util.function.BiConsumer;
public interface ConsoleMessage {
class Location {
public String url;
public int lineNumber;
public int columnNumber;
private String url;
private int lineNumber;
private int columnNumber;
public Location withUrl(String url) {
this.url = url;
return this;
public String url() {
return this.url;
}
public Location withLineNumber(int lineNumber) {
this.lineNumber = lineNumber;
return this;
public int lineNumber() {
return this.lineNumber;
}
public Location withColumnNumber(int columnNumber) {
this.columnNumber = columnNumber;
return this;
public int columnNumber() {
return this.columnNumber;
}
}
List<JSHandle> args();
@@ -664,16 +664,14 @@ public interface Page {
}
}
class PageViewportSize {
public int width;
public int height;
private int width;
private int height;
public PageViewportSize withWidth(int width) {
this.width = width;
return this;
public int width() {
return this.width;
}
public PageViewportSize withHeight(int height) {
this.height = height;
return this;
public int height() {
return this.height;
}
}
class WaitForFunctionOptions {
@@ -746,6 +744,9 @@ public interface Page {
return this;
}
}
void addConsoleListener(Listener<ConsoleMessage> listener);
void removeConsoleListener(Listener<ConsoleMessage> listener);
Deferred<Page> waitForPopup();
default void close() {
close(null);
}
@@ -21,11 +21,10 @@ import java.util.function.BiConsumer;
public interface Request {
class RequestFailure {
public String errorText;
private String errorText;
public RequestFailure withErrorText(String errorText) {
this.errorText = errorText;
return this;
public String errorText() {
return this.errorText;
}
}
class RequestPostDataJSON {
@@ -21,6 +21,7 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Browser;
import com.microsoft.playwright.BrowserContext;
import com.microsoft.playwright.Deferred;
import com.microsoft.playwright.Page;
import java.util.List;
@@ -66,7 +66,7 @@ class ChannelOwner {
return connection.sendMessage(guid, method, params);
}
void sendMessageNoWait(String method, JsonObject params) {
protected void sendMessageNoWait(String method, JsonObject params) {
connection.sendMessageNoWait(guid, method, params);
}
@@ -137,7 +137,6 @@ public class Connection {
if (message.error == null) {
callback.complete(message);
} else {
System.out.println(message.error);
callback.completeExceptionally(new RuntimeException(message.error.toString()));
}
return;
@@ -18,8 +18,12 @@ package com.microsoft.playwright.impl;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.microsoft.playwright.ConsoleMessage;
import com.microsoft.playwright.JSHandle;
public class ConsoleMessageImpl extends ChannelOwner {
import java.util.List;
public class ConsoleMessageImpl extends ChannelOwner implements ConsoleMessage {
public ConsoleMessageImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
@@ -32,21 +36,9 @@ public class ConsoleMessageImpl extends ChannelOwner {
return initializer.get("text").getAsString();
}
// args(): JSHandle[] {
// return this._initializer.args.map(JSHandle.from);
// }
public static class Location {
String url;
int lineNumber;
int columnNumber;
@Override
public String toString() {
return url +
":" + lineNumber +
":" + columnNumber;
}
@Override
public List<JSHandle> args() {
return null;
}
public Location location() {
@@ -133,8 +133,9 @@ public class FrameImpl extends ChannelOwner implements Frame {
JsonObject params = new JsonObject();
params.addProperty("expression", expression);
params.addProperty("world", "main");
if (!isFunctionBody(expression))
if (!isFunctionBody(expression)) {
forceExpression = true;
}
params.addProperty("isFunction", !forceExpression);
params.add("arg", new Gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evaluateExpression", params);
@@ -155,7 +156,15 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public Object evalOnSelector(String selector, String pageFunction, Object arg) {
return null;
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
params.addProperty("expression", pageFunction);
params.addProperty("isFunction", isFunctionBody(pageFunction));
params.add("arg", new Gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evalOnSelector", params);
// System.out.println("json = " + new Gson().toJson(json));
SerializedValue value = new Gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
return deserialize(value);
}
@Override
@@ -24,6 +24,7 @@ import com.microsoft.playwright.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
@@ -31,7 +32,7 @@ import java.util.function.Supplier;
public class PageImpl extends ChannelOwner implements Page {
private final FrameImpl mainFrame;
private final List<DialogHandler> dialogHandlers = new ArrayList<>();
private final List<ConsoleListener> consoleListeners = new ArrayList<>();
private final List<Listener<ConsoleMessage>> consoleListeners = new ArrayList<>();
PageImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
@@ -39,7 +40,18 @@ public class PageImpl extends ChannelOwner implements Page {
mainFrame.page = this;
}
public Supplier<PageImpl> waitForPopup() {
@Override
public void addConsoleListener(Listener<ConsoleMessage> listener) {
consoleListeners.add(listener);
}
@Override
public void removeConsoleListener(Listener<ConsoleMessage> listener) {
consoleListeners.remove(listener);
}
@Override
public Deferred<Page> waitForPopup() {
Supplier<JsonObject> popupSupplier = waitForProtocolEvent("popup");
return () -> {
JsonObject params = popupSupplier.get();
@@ -64,31 +76,21 @@ public class PageImpl extends ChannelOwner implements Page {
if ("dialog".equals(event)) {
String guid = params.getAsJsonObject("dialog").get("guid").getAsString();
DialogImpl dialog = connection.getExistingObject(guid);
for (DialogHandler handler: new ArrayList<>(dialogHandlers))
for (DialogHandler handler: new ArrayList<>(dialogHandlers)) {
handler.handle(dialog);
}
// If no action taken dismiss dialog to not hang.
if (!dialog.isHandled())
dialog.dismiss();
} else if ("console".equals(event)) {
String guid = params.getAsJsonObject("message").get("guid").getAsString();
ConsoleMessageImpl message = connection.getExistingObject(guid);
for (ConsoleListener listener: new ArrayList<>(consoleListeners))
for (Listener<ConsoleMessage> listener: new ArrayList<>(consoleListeners)) {
listener.handle(message);
}
}
}
public interface ConsoleListener {
void handle(ConsoleMessageImpl m);
}
public void addConsoleListener(ConsoleListener listener) {
consoleListeners.add(listener);
}
public void removeConsoleListener(ConsoleListener listener) {
consoleListeners.remove(listener);
}
public <T> T evalTyped(String expression) {
return mainFrame.evalTyped(expression);
}
@@ -100,22 +102,22 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public ElementHandle querySelector(String selector) {
return null;
return mainFrame.querySelector(selector);
}
@Override
public List<ElementHandle> querySelectorAll(String selector) {
return null;
return mainFrame.querySelectorAll(selector);
}
@Override
public Object evalOnSelector(String selector, String pageFunction, Object arg) {
return null;
return mainFrame.evalOnSelector(selector, pageFunction, arg);
}
@Override
public Object evalOnSelectorAll(String selector, String pageFunction, Object arg) {
return null;
return mainFrame.evalOnSelectorAll(selector, pageFunction, arg);
}
@Override
@@ -19,9 +19,11 @@ package com.microsoft.playwright;
import org.junit.jupiter.api.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
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 TestClick {
private static Playwright playwright;
@@ -155,4 +157,55 @@ public class TestClick {
" return textarea.value.substring(textarea.selectionStart, textarea.selectionEnd);\n" +
"}"));
};
@Test
void should_click_offscreen_buttons() {
page.navigate(server.PREFIX + "/offscreenbuttons.html");
List<String> messages = new ArrayList<>();
page.addConsoleListener(msg -> messages.add(msg.text()));
for (int i = 0; i < 11; ++i) {
// We might've scrolled to click a button - reset to (0, 0).
page.evaluate("() => window.scrollTo(0, 0)");
page.click("#btn" + i);
}
assertEquals(Arrays.asList(
"button #0 clicked",
"button #1 clicked",
"button #2 clicked",
"button #3 clicked",
"button #4 clicked",
"button #5 clicked",
"button #6 clicked",
"button #7 clicked",
"button #8 clicked",
"button #9 clicked",
"button #10 clicked"
), messages);
}
@Test
void should_waitFor_visible_when_already_visible() {
page.navigate(server.PREFIX + "/input/button.html");
page.click("button");
assertEquals("Clicked", page.evaluate("result"));
}
@Test
void should_not_wait_with_force() {
page.navigate(server.PREFIX + "/input/button.html");
page.evalOnSelector("button", "b => b.style.display = 'none'");
Exception exception = null;
try {
page.click("button", new Page.ClickOptions().withForce(true));
} catch (RuntimeException e) {
exception = e;
}
assertNotNull(exception);
assertTrue(exception.getMessage().contains("Element is not visible"));
assertEquals("Was not clicked", page.evaluate("result"));
}
// TODO: not supported in sync api
void should_waitFor_display_none_to_be_gone() {
}
}