From ec49645c165b82b9fbb15fadbba3c3bccba98a42 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Tue, 27 Oct 2020 16:33:41 -0700 Subject: [PATCH] feat: implement remaining frame methods (#49) --- .../playwright/impl/ElementHandleImpl.java | 2 +- .../microsoft/playwright/impl/FrameImpl.java | 87 +++++++--- .../playwright/TestDispatchEvent.java | 155 ++++++++++++++++++ 3 files changed, 216 insertions(+), 28 deletions(-) create mode 100644 playwright/src/test/java/com/microsoft/playwright/TestDispatchEvent.java diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/ElementHandleImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/ElementHandleImpl.java index b917ac63..48363068 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/ElementHandleImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/ElementHandleImpl.java @@ -165,7 +165,7 @@ class ElementHandleImpl extends JSHandleImpl implements ElementHandle { JsonObject params = new JsonObject(); params.addProperty("type", type); params.add("eventInit", new Gson().toJsonTree(serializeArgument(eventInit))); - sendMessage("dispatchEvent", params).getAsJsonObject(); + sendMessage("dispatchEvent", params); } @Override diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/FrameImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/FrameImpl.java index 41390c9c..d2cc7e0b 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/FrameImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/FrameImpl.java @@ -38,7 +38,7 @@ public class FrameImpl extends ChannelOwner implements Frame { FrameImpl parentFrame; Set childFrames = new LinkedHashSet<>(); private final Set loadStates = new HashSet<>(); - enum InternalEventType { NAVIGATED, LOADSTATE }; + enum InternalEventType { NAVIGATED, LOADSTATE } private final ListenerCollection internalListeners = new ListenerCollection<>(); PageImpl page; boolean isDetached; @@ -66,20 +66,6 @@ public class FrameImpl extends ChannelOwner implements Frame { } } - private Object evaluate(String expression, Object arg, boolean forceExpression) { - JsonObject params = new JsonObject(); - params.addProperty("expression", expression); - params.addProperty("world", "main"); - if (!isFunctionBody(expression)) { - forceExpression = true; - } - params.addProperty("isFunction", !forceExpression); - params.add("arg", new Gson().toJsonTree(serializeArgument(arg))); - JsonElement json = sendMessage("evaluateExpression", params); - SerializedValue value = new Gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class); - return deserialize(value); - } - @Override public ElementHandle querySelector(String selector) { JsonObject params = new JsonObject(); @@ -140,7 +126,7 @@ public class FrameImpl extends ChannelOwner implements Frame { JsonObject params = new Gson().toJsonTree(options).getAsJsonObject(); if (options.path != null) { params.remove("path"); - byte[] encoded = new byte[0]; + byte[] encoded; try { encoded = Files.readAllBytes(options.path.toPath()); } catch (IOException e) { @@ -162,7 +148,7 @@ public class FrameImpl extends ChannelOwner implements Frame { JsonObject params = new Gson().toJsonTree(options).getAsJsonObject(); if (options.path != null) { params.remove("path"); - byte[] encoded = new byte[0]; + byte[] encoded; try { encoded = Files.readAllBytes(options.path.toPath()); } catch (IOException e) { @@ -240,12 +226,26 @@ public class FrameImpl extends ChannelOwner implements Frame { @Override public void dispatchEvent(String selector, String type, Object eventInit, DispatchEventOptions options) { - + if (options == null) { + options = new DispatchEventOptions(); + } + JsonObject params = new Gson().toJsonTree(options).getAsJsonObject(); + params.addProperty("selector", selector); + params.addProperty("type", type); + params.add("eventInit", new Gson().toJsonTree(serializeArgument(eventInit))); + sendMessage("dispatchEvent", params); } @Override - public Object evaluate(String pageFunction, Object arg) { - return evaluate(pageFunction, arg, false); + public Object evaluate(String expression, Object arg) { + JsonObject params = new JsonObject(); + params.addProperty("expression", expression); + params.addProperty("world", "main"); + params.addProperty("isFunction", isFunctionBody(expression)); + params.add("arg", new Gson().toJsonTree(serializeArgument(arg))); + JsonElement json = sendMessage("evaluateExpression", params); + SerializedValue value = new Gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class); + return deserialize(value); } @Override @@ -282,15 +282,25 @@ public class FrameImpl extends ChannelOwner implements Frame { @Override public ElementHandle frameElement() { - return null; + JsonObject json = sendMessage("frameElement").getAsJsonObject(); + return connection.getExistingObject(json.getAsJsonObject("element").get("guid").getAsString()); } @Override public String getAttribute(String selector, String name, GetAttributeOptions options) { + if (options == null) { + options = new GetAttributeOptions(); + } + JsonObject params = new Gson().toJsonTree(options).getAsJsonObject(); + params.addProperty("selector", selector); + params.addProperty("name", name); + JsonObject json = sendMessage("getAttribute", params).getAsJsonObject(); + if (json.has("value")) { + return json.get("value").getAsString(); + } return null; } - @Override public ResponseImpl navigate(String url, NavigateOptions options) { if (options == null) { @@ -312,17 +322,34 @@ public class FrameImpl extends ChannelOwner implements Frame { @Override public void hover(String selector, HoverOptions options) { - + if (options == null) { + options = new HoverOptions(); + } + JsonObject params = new Gson().toJsonTree(options).getAsJsonObject(); + params.addProperty("selector", selector); + sendMessage("hover", params); } @Override public String innerHTML(String selector, InnerHTMLOptions options) { - return null; + if (options == null) { + options = new InnerHTMLOptions(); + } + JsonObject params = new Gson().toJsonTree(options).getAsJsonObject(); + params.addProperty("selector", selector); + JsonObject json = sendMessage("innerHTML", params).getAsJsonObject(); + return json.get("value").getAsString(); } @Override public String innerText(String selector, InnerTextOptions options) { - return null; + if (options == null) { + options = new InnerTextOptions(); + } + JsonObject params = new Gson().toJsonTree(options).getAsJsonObject(); + params.addProperty("selector", selector); + JsonObject json = sendMessage("innerText", params).getAsJsonObject(); + return json.get("value").getAsString(); } @Override @@ -445,7 +472,13 @@ public class FrameImpl extends ChannelOwner implements Frame { @Override public void type(String selector, String text, TypeOptions options) { - + if (options == null) { + options = new TypeOptions(); + } + JsonObject params = new Gson().toJsonTree(options).getAsJsonObject(); + params.addProperty("selector", selector); + params.addProperty("text", text); + sendMessage("type", params); } @Override @@ -642,7 +675,7 @@ public class FrameImpl extends ChannelOwner implements Frame { @Override public Deferred waitForTimeout(int timeout) { - return toDeferred(new WaitableTimeout(timeout) { + return toDeferred(new WaitableTimeout(timeout) { @Override public Void get() { // Override to not throw. diff --git a/playwright/src/test/java/com/microsoft/playwright/TestDispatchEvent.java b/playwright/src/test/java/com/microsoft/playwright/TestDispatchEvent.java new file mode 100644 index 00000000..9ef4fe9b --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/TestDispatchEvent.java @@ -0,0 +1,155 @@ +/* + * 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.Test; + +import static com.microsoft.playwright.Utils.mapOf; +import static org.junit.jupiter.api.Assertions.*; + +public class TestDispatchEvent extends TestBase { + + @Test + void shouldDispatchClickEvent() { + page.navigate(server.PREFIX + "/input/button.html"); + page.dispatchEvent("button", "click"); + assertEquals("Clicked", page.evaluate("() => window['result']")); + } + + @Test + void shouldDispatchClickEventProperties() { + page.navigate(server.PREFIX + "/input/button.html"); + page.dispatchEvent("button", "click"); + assertNotNull(page.evaluate("bubbles")); + assertNotNull(page.evaluate("cancelable")); + assertNotNull(page.evaluate("composed")); + } + + @Test + void shouldDispatchClickSvg() { + page.setContent("\n" + + " \n" + + ""); + page.dispatchEvent("circle", "click"); + assertEquals(42, page.evaluate("() => window['__CLICKED']")); + } + + @Test + void shouldDispatchClickOnASpanWithAnInlineElementInside() { + page.setContent("\n" + + ""); + page.dispatchEvent("span", "click"); + assertEquals(42, page.evaluate("() => window['CLICKED']")); + } + + @Test + void shouldDispatchClickAfterNavigation() { + page.navigate(server.PREFIX + "/input/button.html"); + page.dispatchEvent("button", "click"); + page.navigate(server.PREFIX + "/input/button.html"); + page.dispatchEvent("button", "click"); + assertEquals("Clicked", page.evaluate("() => window['result']")); + } + + @Test + void shouldDispatchClickAfterACrossOriginNavigation() { + page.navigate(server.PREFIX + "/input/button.html"); + page.dispatchEvent("button", "click"); + page.navigate(server.CROSS_PROCESS_PREFIX + "/input/button.html"); + page.dispatchEvent("button", "click"); + assertEquals("Clicked", page.evaluate("() => window['result']")); + } + + @Test + void shouldNotFailWhenElementIsBlockedOnHover() { + page.setContent("\n" + + "\n" + + " \n" + + "
\n" + + "
"); + page.dispatchEvent("button", "click"); + assertNotNull(page.evaluate("() => window['clicked']")); + } + + @Test + void shouldDispatchClickWhenNodeIsAddedInShadowDom() { + page.navigate(server.EMPTY_PAGE); + page.evaluate("() => {\n" + + " const div = document.createElement('div');\n" + + " div.attachShadow({mode: 'open'});\n" + + " document.body.appendChild(div);\n" + + "}"); + page.evaluate("() => new Promise(f => setTimeout(f, 100))"); + page.evaluate("() => {\n" + + " const span = document.createElement('span');\n" + + " span.textContent = 'Hello from shadow';\n" + + " span.addEventListener('click', () => window['clicked'] = true);\n" + + " document.querySelector('div').shadowRoot.appendChild(span);\n" + + "}"); + // TODO: do it asynchronously before evals? + page.dispatchEvent("span", "click"); + assertEquals(true, page.evaluate("() => window['clicked']")); + } + +// @Test + void shouldBeAtomic() { + // TODO: playwright.selectors.register + } + + @Test + void shouldDispatchDragDropEvents() { + page.navigate(server.PREFIX + "/drag-n-drop.html"); + JSHandle dataTransfer = page.evaluateHandle("() => new DataTransfer()"); + page.dispatchEvent("#source", "dragstart", mapOf("dataTransfer", dataTransfer)); + page.dispatchEvent("#target", "drop", mapOf("dataTransfer", dataTransfer)); + ElementHandle source = page.querySelector("#source"); + ElementHandle target = page.querySelector("#target"); + assertEquals(true, page.evaluate("({source, target}) => {\n" + + " return source.parentElement === target;\n" + + "}", mapOf("source", source,"target", target))); + } + + @Test + void shouldDispatchDragDropEventsOnHandle() { + page.navigate(server.PREFIX + "/drag-n-drop.html"); + JSHandle dataTransfer = page.evaluateHandle("() => new DataTransfer()"); + ElementHandle source = page.querySelector("#source"); + source.dispatchEvent("dragstart", mapOf("dataTransfer", dataTransfer)); + ElementHandle target = page.querySelector("#target"); + target.dispatchEvent("drop", mapOf("dataTransfer", dataTransfer)); + assertEquals(true, page.evaluate("({source, target}) => {\n" + + " return source.parentElement === target;\n" + + "}", mapOf("source", source,"target", target))); + } + + @Test + void shouldDispatchClickEventOnHandle() { + page.navigate(server.PREFIX + "/input/button.html"); + ElementHandle button = page.querySelector("button"); + button.dispatchEvent("click"); + assertEquals("Clicked", page.evaluate("() => window['result']")); + } +}