feat: ElementHandle basic functionality (#29)

This commit is contained in:
Yury Semikhatsky 2020-10-21 11:02:40 -07:00 committed by GitHub
parent 3690079b4f
commit 5e02d0ae49
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 262 additions and 30 deletions

View File

@ -183,6 +183,7 @@ class Types {
add("Page.waitForTimeout", "Promise", "Deferred<Void>", new Empty());
add("Frame.waitForFunction", "Promise<JSHandle>", "Deferred<JSHandle>", new Empty());
add("Page.waitForFunction", "Promise<JSHandle>", "Deferred<JSHandle>", new Empty());
add("ElementHandle.waitForElementState", "Promise", "Deferred<Void>", new Empty());
// Custom options
add("Page.pdf.options.margin.top", "string|number", "String");

View File

@ -434,10 +434,10 @@ public interface ElementHandle extends JSHandle {
uncheck(null);
}
void uncheck(UncheckOptions options);
default void waitForElementState(ElementState state) {
waitForElementState(state, null);
default Deferred<Void> waitForElementState(ElementState state) {
return waitForElementState(state, null);
}
void waitForElementState(ElementState state, WaitForElementStateOptions options);
Deferred<Void> waitForElementState(ElementState state, WaitForElementStateOptions options);
default Deferred<ElementHandle> waitForSelector(String selector) {
return waitForSelector(selector, null);
}

View File

@ -20,17 +20,16 @@ import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Deferred;
import com.microsoft.playwright.ElementHandle;
import com.microsoft.playwright.Frame;
import com.microsoft.playwright.*;
import java.util.ArrayList;
import java.util.List;
import static com.microsoft.playwright.impl.Serialization.toProtocol;
import static com.microsoft.playwright.impl.Serialization.*;
import static com.microsoft.playwright.impl.Utils.isFunctionBody;
public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
public ElementHandleImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
ElementHandleImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
@ -69,12 +68,26 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
@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);
SerializedValue value = new Gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
return deserialize(value);
}
@Override
public Object evalOnSelectorAll(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("evalOnSelectorAll", params);
SerializedValue value = new Gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
return deserialize(value);
}
@Override
@ -99,12 +112,12 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
JsonObject params = new Gson().toJsonTree(options).getAsJsonObject();
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);
@ -124,12 +137,12 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
JsonObject params = new Gson().toJsonTree(options).getAsJsonObject();
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);
@ -137,47 +150,71 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
@Override
public void dispatchEvent(String type, Object eventInit) {
JsonObject params = new JsonObject();
params.addProperty("type", type);
params.add("eventInit", new Gson().toJsonTree(serializeArgument(eventInit)));
sendMessage("dispatchEvent", params).getAsJsonObject();
}
@Override
public void fill(String value, FillOptions options) {
if (options == null) {
options = new FillOptions();
}
JsonObject params = new Gson().toJsonTree(options).getAsJsonObject();
params.addProperty("value", value);
sendMessage("fill", params);
}
@Override
public void focus() {
sendMessage("focus", new JsonObject());
}
@Override
public String getAttribute(String name) {
return null;
JsonObject params = new JsonObject();
params.addProperty("name", name);
JsonObject json = sendMessage("getAttribute", params).getAsJsonObject();
return json.has("value") ? json.get("value").getAsString() : null;
}
@Override
public void hover(HoverOptions options) {
JsonObject params = new Gson().toJsonTree(options).getAsJsonObject();
params.remove("modifiers");
if (options.modifiers != null) {
params.add("modifiers", Serialization.toProtocol(options.modifiers));
}
sendMessage("hover", params);
}
@Override
public String innerHTML() {
return null;
JsonObject json = sendMessage("innerHTML", new JsonObject()).getAsJsonObject();
return json.get("value").getAsString();
}
@Override
public String innerText() {
return null;
JsonObject json = sendMessage("innerText", new JsonObject()).getAsJsonObject();
return json.get("value").getAsString();
}
@Override
public Frame ownerFrame() {
return null;
JsonObject json = sendMessage("ownerFrame", new JsonObject()).getAsJsonObject();
return connection.getExistingObject(json.getAsJsonObject("frame").get("guid").getAsString());
}
@Override
public void press(String key, PressOptions options) {
if (options == null) {
options = new PressOptions();
}
JsonObject params = new Gson().toJsonTree(options).getAsJsonObject();
params.addProperty("key", key);
sendMessage("press", params);
}
@Override
@ -187,17 +224,26 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
@Override
public void scrollIntoViewIfNeeded(ScrollIntoViewIfNeededOptions options) {
if (options == null) {
options = new ScrollIntoViewIfNeededOptions();
}
JsonObject params = new Gson().toJsonTree(options).getAsJsonObject();
sendMessage("scrollIntoViewIfNeeded", params);
}
@Override
public List<String> selectOption(String values, SelectOptionOptions options) {
// TODO:
return null;
}
@Override
public void selectText(SelectTextOptions options) {
if (options == null) {
options = new SelectTextOptions();
}
JsonObject params = new Gson().toJsonTree(options).getAsJsonObject();
sendMessage("selectText", params);
}
@Override
@ -207,12 +253,18 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
@Override
public String textContent() {
return null;
JsonObject json = sendMessage("textContent", new JsonObject()).getAsJsonObject();
return json.has("value") ? json.get("value").getAsString() : null;
}
@Override
public void type(String text, TypeOptions options) {
if (options == null) {
options = new TypeOptions();
}
JsonObject params = new Gson().toJsonTree(options).getAsJsonObject();
params.addProperty("text", text);
sendMessage("type", params);
}
@Override
@ -225,12 +277,38 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
}
@Override
public void waitForElementState(ElementState state, WaitForElementStateOptions options) {
public Deferred<Void> waitForElementState(ElementState state, WaitForElementStateOptions options) {
if (options == null) {
options = new WaitForElementStateOptions();
}
JsonObject params = new Gson().toJsonTree(options).getAsJsonObject();
params.addProperty("state", toProtocol(state));
return toDeferred(sendMessageAsync("waitForElementState", params).apply(json -> null));
}
private static String toProtocol(ElementState state) {
if (state == null) {
throw new IllegalArgumentException("State cannot by null");
}
return state.toString().toLowerCase();
}
@Override
public Deferred<ElementHandle> waitForSelector(String selector, WaitForSelectorOptions options) {
return null;
if (options == null) {
options = new WaitForSelectorOptions();
}
JsonObject params = new Gson().toJsonTree(options).getAsJsonObject();
params.remove("state");
params.addProperty("state", toProtocol(options.state));
params.addProperty("selector", selector);
return toDeferred(sendMessageAsync("waitForElementState", params).apply(json -> null));
}
private static String toProtocol(WaitForSelectorOptions.State state) {
if (state == null) {
state = WaitForSelectorOptions.State.VISIBLE;
}
return state.toString().toLowerCase();
}
}

View File

@ -0,0 +1,153 @@
/**
* 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.ElementHandle.ElementState.*;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
public class TestElementHandleWaitForElementState extends TestBase {
static void giveItAChanceToResolve(Page page) {
for (int i = 0; i < 5; i++) {
page.evaluate("() => new Promise(f => requestAnimationFrame(() => requestAnimationFrame(f)))");
}
}
@Test
void shouldWaitForVisible() {
page.setContent("<div style='display:none'>content</div>");
ElementHandle div = page.querySelector("div");
Deferred<Void> promise = div.waitForElementState(VISIBLE);
giveItAChanceToResolve(page);
div.evaluate("div => div.style.display = 'block'");
promise.get();
}
@Test
void shouldWaitForAlreadyVisible() {
page.setContent("<div>content</div>");
ElementHandle div = page.querySelector("div");
div.waitForElementState(VISIBLE);
}
@Test
void shouldTimeoutWaitingForVisible() {
page.setContent("<div style='display:none'>content</div>");
ElementHandle div = page.querySelector("div");
Deferred<Void> result = div.waitForElementState(VISIBLE, new ElementHandle.WaitForElementStateOptions().withTimeout(1000));
try {
result.get();
fail("did not throw");
} catch (RuntimeException e) {
assertTrue(e.getMessage().contains("Timeout 1000ms exceeded"));
}
}
@Test
void shouldThrowWaitingForVisibleWhenDetached() {
page.setContent("<div style='display:none'>content</div>");
ElementHandle div = page.querySelector("div");
Deferred<Void> promise = div.waitForElementState(VISIBLE);
div.evaluate("div => div.remove()");
try {
promise.get();
fail("did not throw");
} catch (RuntimeException e) {
assertTrue(e.getMessage().contains("Element is not attached to the DOM"));
}
}
@Test
void shouldWaitForHidden() {
page.setContent("<div>content</div>");
ElementHandle div = page.querySelector("div");
Deferred<Void> promise = div.waitForElementState(HIDDEN);
giveItAChanceToResolve(page);
div.evaluate("div => div.style.display = 'none'");
promise.get();
}
@Test
void shouldWaitForAlreadyHidden() {
page.setContent("<div></div>");
ElementHandle div = page.querySelector("div");
Deferred<Void> result = div.waitForElementState(HIDDEN);
result.get();
}
@Test
void shouldWaitForHiddenWhenDetached() {
page.setContent("<div>content</div>");
ElementHandle div = page.querySelector("div");
Deferred<Void> promise = div.waitForElementState(HIDDEN);
giveItAChanceToResolve(page);
div.evaluate("div => div.remove()");
promise.get();
}
@Test
void shouldWaitForEnabledButton() {
page.setContent("<button disabled><span>Target</span></button>");
ElementHandle span = page.querySelector("text=Target");
Deferred<Void> promise = span.waitForElementState(ENABLED);
giveItAChanceToResolve(page);
span.evaluate("span => span.parentElement.disabled = false");
promise.get();
}
@Test
void shouldThrowWaitingForEnabledWhenDetached() {
page.setContent("<button disabled>Target</button>");
ElementHandle button = page.querySelector("button");
Deferred<Void> promise = button.waitForElementState(ENABLED);
button.evaluate("button => button.remove()");
try {
promise.get();
fail("did not throw");
} catch (RuntimeException e) {
assertTrue(e.getMessage().contains("Element is not attached to the DOM"));
}
}
@Test
void shouldWaitForDisabledButton() {
page.setContent("<button><span>Target</span></button>");
ElementHandle span = page.querySelector("text=Target");
Deferred<Void> promise = span.waitForElementState(DISABLED);
giveItAChanceToResolve(page);
span.evaluate("span => span.parentElement.disabled = true");
promise.get();
}
@Test
void shouldWaitForStablePosition() {
// TODO: test.fixme(browserName === "firefox" && platform === "linux");
page.navigate(server.PREFIX + "/input/button.html");
ElementHandle button = page.querySelector("button");
page.evalOnSelector("button", "button => {\n" +
" button.style.transition = 'margin 10000ms linear 0s';\n" +
" button.style.marginLeft = '20000px';\n" +
"}");
Deferred<Void> promise = button.waitForElementState(STABLE);
giveItAChanceToResolve(page);
button.evaluate("button => button.style.transition = ''");
promise.get();
}
}