feat: ElementHandle.boundingBox (#31)

This commit is contained in:
Yury Semikhatsky 2020-10-21 14:34:02 -07:00 committed by GitHub
parent fcfe9245cf
commit 3f81284a7a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 170 additions and 3 deletions

View File

@ -623,6 +623,15 @@ class Interface extends TypeDefinition {
output.add(offset + "}"); output.add(offset + "}");
break; break;
} }
case "ElementHandle": {
output.add(offset + "class BoundingBox {");
output.add(offset + " public double x;");
output.add(offset + " public double y;");
output.add(offset + " public double width;");
output.add(offset + " public double height;");
output.add(offset + "}");
break;
}
default: return; default: return;
} }
output.add(""); output.add("");

View File

@ -168,6 +168,7 @@ class Types {
// Return structures // Return structures
add("ConsoleMessage.location", "Object", "Location"); add("ConsoleMessage.location", "Object", "Location");
add("ElementHandle.boundingBox", "Promise<null|Object>", "BoundingBox", new Empty());
add("Page.waitForRequest", "Promise<Request>", "Deferred<Request>"); add("Page.waitForRequest", "Promise<Request>", "Deferred<Request>");
add("Page.waitForResponse", "Promise<Response>", "Deferred<Response>"); add("Page.waitForResponse", "Promise<Response>", "Deferred<Response>");

View File

@ -19,6 +19,13 @@ package com.microsoft.playwright;
import java.util.*; import java.util.*;
public interface ElementHandle extends JSHandle { public interface ElementHandle extends JSHandle {
class BoundingBox {
public double x;
public double y;
public double width;
public double height;
}
enum ElementState { DISABLED, ENABLED, HIDDEN, STABLE, VISIBLE } enum ElementState { DISABLED, ENABLED, HIDDEN, STABLE, VISIBLE }
class CheckOptions { class CheckOptions {
public Boolean force; public Boolean force;
@ -369,7 +376,7 @@ public interface ElementHandle extends JSHandle {
return evalOnSelectorAll(selector, pageFunction, null); return evalOnSelectorAll(selector, pageFunction, null);
} }
Object evalOnSelectorAll(String selector, String pageFunction, Object arg); Object evalOnSelectorAll(String selector, String pageFunction, Object arg);
Object boundingBox(); BoundingBox boundingBox();
default void check() { default void check() {
check(null); check(null);
} }

View File

@ -91,9 +91,13 @@ class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
} }
@Override @Override
public Object boundingBox() { public BoundingBox boundingBox() {
JsonObject json = sendMessage("boundingBox", new JsonObject()).getAsJsonObject();
if (!json.has("value")) {
return null; return null;
} }
return new Gson().fromJson(json.get("value"), BoundingBox.class);
}
@Override @Override
public void check(CheckOptions options) { public void check(CheckOptions options) {

View File

@ -0,0 +1,146 @@
/**
* 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 java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
public class TestElementHandleBoundingBox extends TestBase {
@Test
void shouldWork() {
// TODO: test.fail(browserName === "firefox" && headful);
page.setViewportSize(500, 500);
page.navigate(server.PREFIX + "/grid.html");
ElementHandle elementHandle = page.querySelector(".box:nth-of-type(13)");
ElementHandle.BoundingBox box = elementHandle.boundingBox();
assertEquals(100, box.x);
assertEquals(50, box.y);
assertEquals(50, box.width);
assertEquals(50, box.height);
}
@Test
void shouldHandleNestedFrames() {
page.setViewportSize(500, 500);
page.navigate(server.PREFIX + "/frames/nested-frames.html");
Frame nestedFrame = page.frameByName("dos");
assertNotNull(nestedFrame);
ElementHandle elementHandle = nestedFrame.querySelector("div");
ElementHandle.BoundingBox box = elementHandle.boundingBox();
assertEquals(24, box.x);
assertEquals(224, box.y);
assertEquals(268, box.width);
assertEquals(18, box.height);
}
@Test
void shouldReturnNullForInvisibleElements() {
page.setContent("<div style='display:none'>hi</div>");
ElementHandle element = page.querySelector("div");
assertNull(element.boundingBox());
}
@Test
void shouldForceALayout() {
page.setViewportSize(500, 500);
page.setContent("<div style='width: 100px; height: 100px'>hello</div>");
ElementHandle elementHandle = page.querySelector("div");
page.evaluate("element => element.style.height = '200px'", elementHandle);
ElementHandle.BoundingBox box = elementHandle.boundingBox();
assertEquals(box.x, 8);
assertEquals(box.y, 8);
assertEquals(box.width, 100);
assertEquals(box.height, 200);
}
@Test
void shouldWorkWithSVGNodes() {
page.setContent("<svg xmlns='http://www.w3.org/2000/svg' width='500' height='500'>\n" +
"<rect id='theRect' x='30' y='50' width='200' height='300'></rect>\n" +
"</svg>");
ElementHandle element = page.querySelector("#therect");
ElementHandle.BoundingBox pwBoundingBox = element.boundingBox();
Map<String, Integer> webBoundingBox = (Map<String, Integer>) page.evaluate("e => {\n" +
" const rect = e.getBoundingClientRect();\n" +
" return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };\n" +
"}", element);
assertEquals(webBoundingBox.get("x").doubleValue(), pwBoundingBox.x);
assertEquals(webBoundingBox.get("y").doubleValue(), pwBoundingBox.y);
assertEquals(webBoundingBox.get("width").doubleValue(), pwBoundingBox.width);
assertEquals(webBoundingBox.get("height").doubleValue(), pwBoundingBox.height);
}
@Test
void shouldWorkWithPageScale() {
// TODO: test.skip(browserName === "firefox");
BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.withViewport(400, 400).withIsMobile(true));
Page page = context.newPage();
page.navigate(server.PREFIX + "/input/button.html");
ElementHandle button = page.querySelector("button");
button.evaluate("button => {\n" +
" document.body.style.margin = '0';\n" +
" button.style.borderWidth = '0';\n" +
" button.style.width = '200px';\n" +
" button.style.height = '20px';\n" +
" button.style.marginLeft = '17px';\n" +
" button.style.marginTop = '23px';\n" +
"}");
ElementHandle.BoundingBox box = button.boundingBox();
assertEquals(17 * 100, round(box.x * 100));
assertEquals(23 * 100, round(box.y * 100));
assertEquals(200 * 100, round(box.width * 100));
assertEquals(20 * 100, round(box.height * 100));
context.close();
}
static long round(Object o) {
if (o instanceof Integer) {
return ((Integer) o).longValue();
}
return Math.round((Double) o);
}
@Test
void shouldWorkWhenInlineBoxChildIsOutsideOfViewport() {
page.setContent("<style>\n" +
"i {\n" +
" position: absolute;\n" +
" top: -1000px;\n" +
"}\n" +
"body {\n" +
" margin: 0;\n" +
" font-size: 12px;\n" +
"}\n" +
"</style>\n" +
"<span><i>woof</i><b>doggo</b></span>");
ElementHandle handle = page.querySelector("span");
ElementHandle.BoundingBox pwBoundingBox = handle.boundingBox();
Map<String, Object> webBoundingBox = (Map<String, Object>) handle.evaluate("e => {\n" +
" const rect = e.getBoundingClientRect();\n" +
" return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };\n" +
"}");
assertEquals(round(webBoundingBox.get("x")), round(pwBoundingBox.x));
assertEquals(round(webBoundingBox.get("y")), round(pwBoundingBox.y));
assertEquals(round(webBoundingBox.get("width")), round(pwBoundingBox.width));
assertEquals(round(webBoundingBox.get("height")), round(pwBoundingBox.height));
}
}