feat: add console listener, generate some events
This commit is contained in:
@@ -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() {
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user