diff --git a/api-generator/src/main/java/com/microsoft/playwright/tools/ApiGenerator.java b/api-generator/src/main/java/com/microsoft/playwright/tools/ApiGenerator.java index 4eb1e829..5d9413d0 100644 --- a/api-generator/src/main/java/com/microsoft/playwright/tools/ApiGenerator.java +++ b/api-generator/src/main/java/com/microsoft/playwright/tools/ApiGenerator.java @@ -464,6 +464,8 @@ class Interface extends TypeDefinition { "\n" + "package com.microsoft.playwright;\n"; + private static Set allowedBaseInterfaces = new HashSet<>(Arrays.asList("Browser", "JSHandle")); + Interface(JsonObject jsonElement) { super(null, jsonElement); @@ -489,7 +491,16 @@ class Interface extends TypeDefinition { output.add("import java.util.*;"); output.add("import java.util.function.BiConsumer;"); output.add(""); - output.add("public interface " + jsonName + " {"); + + String implementsClause = ""; + if (jsonElement.getAsJsonObject().has("extends")) { + String base = jsonElement.getAsJsonObject().get("extends").getAsString(); + if (allowedBaseInterfaces.contains(base)) { + implementsClause = " extends " + base; + } + } + + output.add("public interface " + jsonName + implementsClause + " {"); offset = " "; writeSharedTypes(output, offset); super.writeTo(output, offset); diff --git a/lib/src/main/java/com/microsoft/playwright/ChromiumBrowser.java b/lib/src/main/java/com/microsoft/playwright/ChromiumBrowser.java index 58e8eef3..3dab1963 100644 --- a/lib/src/main/java/com/microsoft/playwright/ChromiumBrowser.java +++ b/lib/src/main/java/com/microsoft/playwright/ChromiumBrowser.java @@ -19,7 +19,7 @@ package com.microsoft.playwright; import java.util.*; import java.util.function.BiConsumer; -public interface ChromiumBrowser { +public interface ChromiumBrowser extends Browser { class StartTracingOptions { public String path; public Boolean screenshots; diff --git a/lib/src/main/java/com/microsoft/playwright/ElementHandle.java b/lib/src/main/java/com/microsoft/playwright/ElementHandle.java index d30335a9..c82cae68 100644 --- a/lib/src/main/java/com/microsoft/playwright/ElementHandle.java +++ b/lib/src/main/java/com/microsoft/playwright/ElementHandle.java @@ -19,7 +19,7 @@ package com.microsoft.playwright; import java.util.*; import java.util.function.BiConsumer; -public interface ElementHandle { +public interface ElementHandle extends JSHandle { enum ElementState { DISABLED, ENABLED, HIDDEN, STABLE, VISIBLE } class CheckOptions { public Boolean force; diff --git a/lib/src/main/java/com/microsoft/playwright/FirefoxBrowser.java b/lib/src/main/java/com/microsoft/playwright/FirefoxBrowser.java index a521fac3..4dc53eaa 100644 --- a/lib/src/main/java/com/microsoft/playwright/FirefoxBrowser.java +++ b/lib/src/main/java/com/microsoft/playwright/FirefoxBrowser.java @@ -19,6 +19,6 @@ package com.microsoft.playwright; import java.util.*; import java.util.function.BiConsumer; -public interface FirefoxBrowser { +public interface FirefoxBrowser extends Browser { } diff --git a/lib/src/main/java/com/microsoft/playwright/WebKitBrowser.java b/lib/src/main/java/com/microsoft/playwright/WebKitBrowser.java index 8897a166..eefd641b 100644 --- a/lib/src/main/java/com/microsoft/playwright/WebKitBrowser.java +++ b/lib/src/main/java/com/microsoft/playwright/WebKitBrowser.java @@ -19,6 +19,6 @@ package com.microsoft.playwright; import java.util.*; import java.util.function.BiConsumer; -public interface WebKitBrowser { +public interface WebKitBrowser extends Browser { } diff --git a/lib/src/main/java/com/microsoft/playwright/impl/Connection.java b/lib/src/main/java/com/microsoft/playwright/impl/Connection.java index 4ac0892e..ffe1bf50 100644 --- a/lib/src/main/java/com/microsoft/playwright/impl/Connection.java +++ b/lib/src/main/java/com/microsoft/playwright/impl/Connection.java @@ -198,11 +198,14 @@ public class Connection { case "Electron": // result = new Playwright(parent, type, guid, initializer); break; + case "ElementHandle": + result = new ElementHandleImpl(parent, type, guid, initializer); + break; case "Frame": result = new FrameImpl(parent, type, guid, initializer); break; case "JSHandle": -// result = new JSHandle(parent, type, guid, initializer); + result = new JSHandleImpl(parent, type, guid, initializer); break; case "Page": result = new PageImpl(parent, type, guid, initializer); diff --git a/lib/src/main/java/com/microsoft/playwright/impl/ElementHandleImpl.java b/lib/src/main/java/com/microsoft/playwright/impl/ElementHandleImpl.java new file mode 100644 index 00000000..1621af6d --- /dev/null +++ b/lib/src/main/java/com/microsoft/playwright/impl/ElementHandleImpl.java @@ -0,0 +1,214 @@ +/** + * Copyright (c) Microsoft Corporation. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.microsoft.playwright.impl; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.microsoft.playwright.ElementHandle; +import com.microsoft.playwright.Frame; +import com.microsoft.playwright.JSHandle; + +import java.util.List; + +import static com.microsoft.playwright.impl.Serialization.toProtocol; + +public class ElementHandleImpl extends JSHandleImpl implements ElementHandle { + public ElementHandleImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) { + super(parent, type, guid, initializer); + } + + @Override + public ElementHandle asElement() { + return this; + } + + @Override + public ElementHandle querySelector(String selector) { + JsonObject params = new JsonObject(); + params.addProperty("selector", selector); + JsonElement json = sendMessage("querySelector", params); + JsonObject element = json.getAsJsonObject().getAsJsonObject("element"); + if (element == null) { + return null; + } + return connection.getExistingObject(element.get("guid").getAsString()); + } + + @Override + public List querySelectorAll(String selector) { + return null; + } + + @Override + public Object evalOnSelector(String selector, String pageFunction, Object arg) { + return null; + } + + @Override + public Object evalOnSelectorAll(String selector, String pageFunction, Object arg) { + return null; + } + + @Override + public Object boundingBox() { + return null; + } + + @Override + public void check(CheckOptions options) { + + } + + @Override + public void click(ClickOptions options) { + if (options == null) { + options = new ClickOptions(); + } + JsonObject params = new Gson().toJsonTree(options).getAsJsonObject(); + params.remove("button"); + if (options.button != null) { + params.addProperty("button", toProtocol(options.button)); + } + + params.remove("modifiers"); + if (options.modifiers != null) { + params.add("modifiers", toProtocol(options.modifiers)); + } + + sendMessage("click", params); + } + + @Override + public Frame contentFrame() { + return null; + } + + @Override + public void dblclick(DblclickOptions options) { + if (options == null) { + options = new DblclickOptions(); + } + JsonObject params = new Gson().toJsonTree(options).getAsJsonObject(); + params.remove("button"); + if (options.button != null) { + params.addProperty("button", toProtocol(options.button)); + } + + params.remove("modifiers"); + if (options.modifiers != null) { + params.add("modifiers", toProtocol(options.modifiers)); + } + + sendMessage("dblclick", params); + } + + @Override + public void dispatchEvent(String type, Object eventInit) { + + } + + @Override + public void fill(String value, FillOptions options) { + + } + + @Override + public void focus() { + + } + + @Override + public String getAttribute(String name) { + return null; + } + + @Override + public void hover(HoverOptions options) { + + } + + @Override + public String innerHTML() { + return null; + } + + @Override + public String innerText() { + return null; + } + + @Override + public Frame ownerFrame() { + return null; + } + + @Override + public void press(String key, PressOptions options) { + + } + + @Override + public byte[] screenshot(ScreenshotOptions options) { + return new byte[0]; + } + + @Override + public void scrollIntoViewIfNeeded(ScrollIntoViewIfNeededOptions options) { + + } + + @Override + public List selectOption(String values, SelectOptionOptions options) { + return null; + } + + @Override + public void selectText(SelectTextOptions options) { + + } + + @Override + public void setInputFiles(String files, SetInputFilesOptions options) { + + } + + @Override + public String textContent() { + return null; + } + + @Override + public void type(String text, TypeOptions options) { + + } + + @Override + public void uncheck(UncheckOptions options) { + + } + + @Override + public void waitForElementState(ElementState state, WaitForElementStateOptions options) { + + } + + @Override + public ElementHandle waitForSelector(String selector, WaitForSelectorOptions options) { + return null; + } +} diff --git a/lib/src/main/java/com/microsoft/playwright/impl/FrameImpl.java b/lib/src/main/java/com/microsoft/playwright/impl/FrameImpl.java index 6be7c0fd..95cc97ea 100644 --- a/lib/src/main/java/com/microsoft/playwright/impl/FrameImpl.java +++ b/lib/src/main/java/com/microsoft/playwright/impl/FrameImpl.java @@ -17,7 +17,6 @@ package com.microsoft.playwright.impl; import com.google.gson.Gson; -import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.microsoft.playwright.*; @@ -70,7 +69,14 @@ public class FrameImpl extends ChannelOwner implements Frame { @Override public ElementHandle querySelector(String selector) { - return null; + JsonObject params = new JsonObject(); + params.addProperty("selector", selector); + JsonElement json = sendMessage("querySelector", params); + JsonObject element = json.getAsJsonObject().getAsJsonObject("element"); + if (element == null) { + return null; + } + return connection.getExistingObject(element.get("guid").getAsString()); } @Override @@ -116,32 +122,6 @@ public class FrameImpl extends ChannelOwner implements Frame { } - private static String toProtocol(Mouse.Button button) { - switch (button) { - case LEFT: return "left"; - case RIGHT: return "right"; - case MIDDLE: return "middle"; - default: throw new RuntimeException("Unexpected value: " + button); - } - } - - private static JsonArray toProtocol(Set modifiers) { - JsonArray result = new JsonArray(); - if (modifiers.contains(Keyboard.Modifier.ALT)) { - result.add("Alt"); - } - if (modifiers.contains(Keyboard.Modifier.CONTROL)) { - result.add("Control"); - } - if (modifiers.contains(Keyboard.Modifier.META)) { - result.add("Meta"); - } - if (modifiers.contains(Keyboard.Modifier.SHIFT)) { - result.add("Shift"); - } - return result; - } - @Override public void click(String selector, ClickOptions options) { if (options == null) { @@ -152,12 +132,12 @@ public class FrameImpl extends ChannelOwner implements Frame { params.remove("button"); if (options.button != null) { - params.addProperty("button", toProtocol(options.button)); + params.addProperty("button", Serialization.toProtocol(options.button)); } params.remove("modifiers"); if (options.modifiers != null) { - params.add("modifiers", toProtocol(options.modifiers)); + params.add("modifiers", Serialization.toProtocol(options.modifiers)); } sendMessage("click", params); @@ -178,12 +158,12 @@ public class FrameImpl extends ChannelOwner implements Frame { params.remove("button"); if (options.button != null) { - params.addProperty("button", toProtocol(options.button)); + params.addProperty("button", Serialization.toProtocol(options.button)); } params.remove("modifiers"); if (options.modifiers != null) { - params.add("modifiers", toProtocol(options.modifiers)); + params.add("modifiers", Serialization.toProtocol(options.modifiers)); } sendMessage("dblclick", params); @@ -201,7 +181,13 @@ public class FrameImpl extends ChannelOwner implements Frame { @Override public JSHandle evaluateHandle(String pageFunction, Object arg) { - return null; + JsonObject params = new JsonObject(); + params.addProperty("expression", pageFunction); + params.addProperty("world", "main"); + params.addProperty("isFunction", isFunctionBody(pageFunction)); + params.add("arg", new Gson().toJsonTree(serializeArgument(arg))); + JsonElement json = sendMessage("evaluateExpressionHandle", params); + return connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("handle").get("guid").getAsString()); } @Override diff --git a/lib/src/main/java/com/microsoft/playwright/impl/JSHandleImpl.java b/lib/src/main/java/com/microsoft/playwright/impl/JSHandleImpl.java new file mode 100644 index 00000000..2c1f0644 --- /dev/null +++ b/lib/src/main/java/com/microsoft/playwright/impl/JSHandleImpl.java @@ -0,0 +1,59 @@ +/** + * Copyright (c) Microsoft Corporation. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.microsoft.playwright.impl; + +import com.google.gson.JsonObject; +import com.microsoft.playwright.ElementHandle; +import com.microsoft.playwright.JSHandle; + +import java.util.Map; + +public class JSHandleImpl extends ChannelOwner implements JSHandle { + public JSHandleImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) { + super(parent, type, guid, initializer); + } + + @Override + public ElementHandle asElement() { + return null; + } + + @Override + public Object evaluate(String pageFunction, Object arg) { + return null; + } + + @Override + public JSHandle evaluateHandle(String pageFunction, Object arg) { + return null; + } + + @Override + public Map getProperties() { + return null; + } + + @Override + public JSHandle getProperty(String propertyName) { + return null; + } + + @Override + public Object jsonValue() { + return null; + } +} diff --git a/lib/src/main/java/com/microsoft/playwright/impl/MouseImpl.java b/lib/src/main/java/com/microsoft/playwright/impl/MouseImpl.java index 7d4885c8..8e574917 100644 --- a/lib/src/main/java/com/microsoft/playwright/impl/MouseImpl.java +++ b/lib/src/main/java/com/microsoft/playwright/impl/MouseImpl.java @@ -20,6 +20,7 @@ import com.google.gson.Gson; import com.google.gson.JsonObject; import com.microsoft.playwright.Mouse; +import static com.microsoft.playwright.impl.Serialization.toProtocol; import static com.microsoft.playwright.impl.Utils.convertViaJson; class MouseImpl implements Mouse { @@ -29,15 +30,6 @@ class MouseImpl implements Mouse { this.page = page; } - private static String toProtocol(Mouse.Button button) { - switch (button) { - case LEFT: return "left"; - case RIGHT: return "right"; - case MIDDLE: return "middle"; - default: throw new RuntimeException("Unexpected value: " + button); - } - } - @Override public void click(int x, int y, ClickOptions options) { if (options == null) { diff --git a/lib/src/main/java/com/microsoft/playwright/impl/PageImpl.java b/lib/src/main/java/com/microsoft/playwright/impl/PageImpl.java index 3221ddd7..5209d118 100644 --- a/lib/src/main/java/com/microsoft/playwright/impl/PageImpl.java +++ b/lib/src/main/java/com/microsoft/playwright/impl/PageImpl.java @@ -106,7 +106,7 @@ public class PageImpl extends ChannelOwner implements Page { @Override public void close(CloseOptions options) { - JsonObject params = options == null ? new JsonObject() : (JsonObject) new Gson().toJsonTree(options); + JsonObject params = options == null ? new JsonObject() : new Gson().toJsonTree(options).getAsJsonObject(); sendMessage("close", params); if (ownedContext != null) { ownedContext.close(); @@ -205,7 +205,7 @@ public class PageImpl extends ChannelOwner implements Page { @Override public JSHandle evaluateHandle(String pageFunction, Object arg) { - return null; + return mainFrame.evaluateHandle(pageFunction, arg); } @Override diff --git a/lib/src/main/java/com/microsoft/playwright/impl/Protocol.java b/lib/src/main/java/com/microsoft/playwright/impl/Protocol.java index 734a95dc..88046676 100644 --- a/lib/src/main/java/com/microsoft/playwright/impl/Protocol.java +++ b/lib/src/main/java/com/microsoft/playwright/impl/Protocol.java @@ -22,6 +22,7 @@ class Binary { } class Channel { + String guid; } class Metadata{ diff --git a/lib/src/main/java/com/microsoft/playwright/impl/Serialization.java b/lib/src/main/java/com/microsoft/playwright/impl/Serialization.java index 9f806e30..ed8f09db 100644 --- a/lib/src/main/java/com/microsoft/playwright/impl/Serialization.java +++ b/lib/src/main/java/com/microsoft/playwright/impl/Serialization.java @@ -17,13 +17,14 @@ package com.microsoft.playwright.impl; import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.microsoft.playwright.JSHandle; +import com.microsoft.playwright.Keyboard; +import com.microsoft.playwright.Mouse; import java.io.ByteArrayOutputStream; import java.io.PrintStream; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.util.*; class Serialization { static SerializedError serializeError(Throwable e) { @@ -38,8 +39,16 @@ class Serialization { return result; } - private static SerializedValue serializeValue(Object value) { + private static SerializedValue serializeValue(Object value, List handles, int depth) { + if (depth > 100) { + throw new RuntimeException("Maximum argument depth exceeded"); + } SerializedValue result = new SerializedValue(); + if (value instanceof JSHandleImpl) { + result.h = handles.size(); + handles.add((JSHandleImpl) value); + return result; + } if (value == null) result.v = "undefined"; else if (value instanceof Double) { @@ -64,7 +73,7 @@ class Serialization { else if (value instanceof List) { List list = new ArrayList<>(); for (Object o : (List) value) - list.add(serializeValue(o)); + list.add(serializeValue(o, handles, depth + 1)); result.a = list.toArray(new SerializedValue[0]); } else if (value instanceof Map) { List list = new ArrayList<>(); @@ -72,7 +81,7 @@ class Serialization { for (Map.Entry e : map.entrySet()) { SerializedValue.O o = new SerializedValue.O(); o.k = e.getKey(); - o.v = serializeValue(e.getValue()); + o.v = serializeValue(e.getValue(), handles, depth + 1); list.add(o); } result.o = list.toArray(new SerializedValue.O[0]); @@ -83,8 +92,15 @@ class Serialization { static SerializedArgument serializeArgument(Object arg) { SerializedArgument result = new SerializedArgument(); - result.value = serializeValue(arg); - result.handles = new Channel[0]; + List handles = new ArrayList<>(); + result.value = serializeValue(arg, handles, 0); + result.handles = new Channel[handles.size()]; + int i = 0; + for (JSHandleImpl handle : handles) { + result.handles[i] = new Channel(); + result.handles[i].guid = handle.guid; + ++i; + } return result; } @@ -129,4 +145,30 @@ class Serialization { } throw new RuntimeException("Unexpected result: " + new Gson().toJson(value)); } + + static String toProtocol(Mouse.Button button) { + switch (button) { + case LEFT: return "left"; + case RIGHT: return "right"; + case MIDDLE: return "middle"; + default: throw new RuntimeException("Unexpected value: " + button); + } + } + + static JsonArray toProtocol(Set modifiers) { + JsonArray result = new JsonArray(); + if (modifiers.contains(Keyboard.Modifier.ALT)) { + result.add("Alt"); + } + if (modifiers.contains(Keyboard.Modifier.CONTROL)) { + result.add("Control"); + } + if (modifiers.contains(Keyboard.Modifier.META)) { + result.add("Meta"); + } + if (modifiers.contains(Keyboard.Modifier.SHIFT)) { + result.add("Shift"); + } + return result; + } } diff --git a/lib/src/test/java/com/microsoft/playwright/TestClick.java b/lib/src/test/java/com/microsoft/playwright/TestClick.java index d011086f..aa261fbf 100644 --- a/lib/src/test/java/com/microsoft/playwright/TestClick.java +++ b/lib/src/test/java/com/microsoft/playwright/TestClick.java @@ -31,16 +31,21 @@ import static org.junit.jupiter.api.Assertions.*; public class TestClick { private static Playwright playwright; private static Server server; - private Browser browser; - private boolean isChromium; - private boolean isWebKit; - private boolean headful; + private static Browser browser; + private static boolean isChromium; + private static boolean isWebKit; + private static boolean headful; private BrowserContext context; private Page page; @BeforeAll - static void createPlaywright() { + static void launchBrowser() { playwright = Playwright.create(); + BrowserType.LaunchOptions options = new BrowserType.LaunchOptions(); + browser = playwright.chromium().launch(options); + isChromium = true; + isWebKit = false; + headful = false; } @BeforeAll @@ -50,25 +55,22 @@ public class TestClick { @AfterAll static void stopServer() throws IOException { + browser.close(); server.stop(); server = null; } @BeforeEach void setUp() { -// BrowserType.LaunchOptions options = new BrowserType.LaunchOptions().withHeadless(false).withSlowMo(1000); - BrowserType.LaunchOptions options = new BrowserType.LaunchOptions(); - browser = playwright.chromium().launch(options); - isChromium = true; - isWebKit = false; - headful = false; context = browser.newContext(); page = context.newPage(); } @AfterEach void tearDown() { - browser.close(); + context.close(); + context = null; + page = null; } @Test diff --git a/lib/src/test/java/com/microsoft/playwright/TestElementHandleClick.java b/lib/src/test/java/com/microsoft/playwright/TestElementHandleClick.java new file mode 100644 index 00000000..d7e045da --- /dev/null +++ b/lib/src/test/java/com/microsoft/playwright/TestElementHandleClick.java @@ -0,0 +1,169 @@ +/** + * Copyright (c) Microsoft Corporation. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.microsoft.playwright; + +import org.junit.jupiter.api.*; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.*; + +public class TestElementHandleClick { + private static Playwright playwright; + private static Server server; + private static Browser browser; + private static boolean isChromium; + private static boolean isWebKit; + private static boolean headful; + private BrowserContext context; + private Page page; + + @BeforeAll + static void launchBrowser() { + playwright = Playwright.create(); + BrowserType.LaunchOptions options = new BrowserType.LaunchOptions(); + browser = playwright.chromium().launch(options); + isChromium = true; + isWebKit = false; + headful = false; + } + + @BeforeAll + static void startServer() throws IOException { + server = new Server(8907); + } + + @AfterAll + static void stopServer() throws IOException { + browser.close(); + server.stop(); + server = null; + } + + @BeforeEach + void setUp() { + context = browser.newContext(); + page = context.newPage(); + } + + @AfterEach + void tearDown() { + context.close(); + context = null; + page = null; + } + + @Test + void should_work() { + page.navigate(server.PREFIX + "/input/button.html"); + ElementHandle button = page.querySelector("button"); + button.click(); + assertEquals("Clicked", page.evaluate("() => window['result']")); + } + + @Test + void should_work_with_Node_removed() { + page.navigate(server.PREFIX + "/input/button.html"); + page.evaluate("() => delete window['Node']"); + ElementHandle button = page.querySelector("button"); + button.click(); + assertEquals("Clicked", page.evaluate("() => window['result']")); + } + + @Test + void should_work_for_Shadow_DOM_v1() { + page.navigate(server.PREFIX + "/shadow.html"); + ElementHandle buttonHandle = page.evaluateHandle("() => window['button']").asElement(); + buttonHandle.click(); + assertEquals(true, page.evaluate("clicked")); + } + + @Test + void should_work_for_TextNodes() { + page.navigate(server.PREFIX + "/input/button.html"); + ElementHandle buttonTextNode = page.evaluateHandle("() => document.querySelector('button').firstChild").asElement(); + buttonTextNode.click(); + assertEquals("Clicked", page.evaluate("() => window['result']")); + } + + @Test + void should_throw_for_detached_nodes() { + page.navigate(server.PREFIX + "/input/button.html"); + ElementHandle button = page.querySelector("button"); + page.evaluate("button => button.remove()", button); + try { + button.click(); + fail("click should throw"); + } catch (RuntimeException e) { + assertTrue(e.getMessage().contains("Element is not attached to the DOM")); + } + } + + @Test + void should_throw_for_hidden_nodes_with_force() { + page.navigate(server.PREFIX + "/input/button.html"); + ElementHandle button = page.querySelector("button"); + page.evaluate("button => button.style.display = 'none'", button); + try { + button.click(new ElementHandle.ClickOptions().withForce(true)); + fail("click should throw"); + } catch (RuntimeException e) { + assertTrue(e.getMessage().contains("Element is not visible")); + } + } + + @Test + void should_throw_for_recursively_hidden_nodes_with_force() { + page.navigate(server.PREFIX + "/input/button.html"); + ElementHandle button = page.querySelector("button"); + page.evaluate("button => button.parentElement.style.display = 'none'", button); + try { + button.click(new ElementHandle.ClickOptions().withForce(true)); + fail("click should throw"); + } catch (RuntimeException e) { + assertTrue(e.getMessage().contains("Element is not visible")); + } + } + + @Test + void should_throw_for__br__elements_with_force() { + page.setContent("hello
goodbye"); + ElementHandle br = page.querySelector("br"); + try { + br.click(new ElementHandle.ClickOptions().withForce(true)); + fail("click should throw"); + } catch (RuntimeException e) { + assertTrue(e.getMessage().contains("Element is outside of the viewport")); + } + } + + @Test + void should_double_click_the_button() { + page.navigate(server.PREFIX + "/input/button.html"); + page.evaluate("() => {\n" + + " window['double'] = false;\n" + + " const button = document.querySelector('button');\n" + + " button.addEventListener('dblclick', event => {\n" + + " window['double'] = true;\n" + + " });\n" + + "}"); + ElementHandle button = page.querySelector("button"); + button.dblclick(); + assertEquals(true, page.evaluate("double")); + assertEquals("Clicked", page.evaluate("result")); + } +}