mirror of
https://github.com/microsoft/playwright-java.git
synced 2025-09-08 21:01:00 +00:00
feat: selectors (#63)
This commit is contained in:
parent
602406b53b
commit
d539159907
@ -376,6 +376,13 @@ class Method extends Element {
|
|||||||
.replace("String selector, ", "")
|
.replace("String selector, ", "")
|
||||||
.replace("(selector, ", "(")
|
.replace("(selector, ", "(")
|
||||||
.replace("ElementHandle.", "")).toArray(String[]::new));
|
.replace("ElementHandle.", "")).toArray(String[]::new));
|
||||||
|
|
||||||
|
customSignature.put("Selectors.register", new String[] {
|
||||||
|
"default void register(String name, String script) { register(name, script, null); }",
|
||||||
|
"void register(String name, String script, RegisterOptions options);",
|
||||||
|
"default void register(String name, Path path) { register(name, path, null); }",
|
||||||
|
"void register(String name, Path path, RegisterOptions options);"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Method(TypeDefinition parent, JsonObject jsonElement) {
|
Method(TypeDefinition parent, JsonObject jsonElement) {
|
||||||
@ -656,7 +663,7 @@ class Interface extends TypeDefinition {
|
|||||||
if ("Download".equals(jsonName)) {
|
if ("Download".equals(jsonName)) {
|
||||||
output.add("import java.io.InputStream;");
|
output.add("import java.io.InputStream;");
|
||||||
}
|
}
|
||||||
if (asList("Page", "Frame", "ElementHandle", "FileChooser", "ChromiumBrowser", "Download", "Route").contains(jsonName)) {
|
if (asList("Page", "Frame", "ElementHandle", "FileChooser", "ChromiumBrowser", "Download", "Route", "Selectors").contains(jsonName)) {
|
||||||
output.add("import java.nio.file.Path;");
|
output.add("import java.nio.file.Path;");
|
||||||
}
|
}
|
||||||
output.add("import java.util.*;");
|
output.add("import java.util.*;");
|
||||||
|
@ -31,4 +31,6 @@ public interface Playwright {
|
|||||||
BrowserType webkit();
|
BrowserType webkit();
|
||||||
|
|
||||||
Map<String, DeviceDescriptor> devices();
|
Map<String, DeviceDescriptor> devices();
|
||||||
|
|
||||||
|
Selectors selectors();
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package com.microsoft.playwright;
|
package com.microsoft.playwright;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public interface Selectors {
|
public interface Selectors {
|
||||||
@ -27,9 +28,9 @@ public interface Selectors {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default void register(String name, String script) {
|
default void register(String name, String script) { register(name, script, null); }
|
||||||
register(name, script, null);
|
|
||||||
}
|
|
||||||
void register(String name, String script, RegisterOptions options);
|
void register(String name, String script, RegisterOptions options);
|
||||||
|
default void register(String name, Path path) { register(name, path, null); }
|
||||||
|
void register(String name, Path path, RegisterOptions options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -228,7 +228,7 @@ public class Connection {
|
|||||||
result = new Stream(parent, type, guid, initializer);
|
result = new Stream(parent, type, guid, initializer);
|
||||||
break;
|
break;
|
||||||
case "Selectors":
|
case "Selectors":
|
||||||
// result = new Playwright(parent, type, guid, initializer);
|
result = new SelectorsImpl(parent, type, guid, initializer);
|
||||||
break;
|
break;
|
||||||
case "Worker":
|
case "Worker":
|
||||||
result = new WorkerImpl(parent, type, guid, initializer);
|
result = new WorkerImpl(parent, type, guid, initializer);
|
||||||
|
@ -34,7 +34,7 @@ import java.util.List;
|
|||||||
import static com.microsoft.playwright.impl.Serialization.*;
|
import static com.microsoft.playwright.impl.Serialization.*;
|
||||||
import static com.microsoft.playwright.impl.Utils.isFunctionBody;
|
import static com.microsoft.playwright.impl.Utils.isFunctionBody;
|
||||||
|
|
||||||
class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
|
public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
|
||||||
ElementHandleImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
ElementHandleImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||||
super(parent, type, guid, initializer);
|
super(parent, type, guid, initializer);
|
||||||
}
|
}
|
||||||
@ -384,6 +384,16 @@ class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
|
|||||||
return toDeferred(sendMessageAsync("waitForElementState", params).apply(json -> null));
|
return toDeferred(sendMessageAsync("waitForElementState", params).apply(json -> null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String createSelectorForTest(String name) {
|
||||||
|
JsonObject params = new JsonObject();
|
||||||
|
params.addProperty("name", name);
|
||||||
|
JsonObject json = sendMessage("createSelectorForTest", params).getAsJsonObject();
|
||||||
|
if (json.has("value")) {
|
||||||
|
return json.get("value").getAsString();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private static String toProtocol(WaitForSelectorOptions.State state) {
|
private static String toProtocol(WaitForSelectorOptions.State state) {
|
||||||
if (state == null) {
|
if (state == null) {
|
||||||
state = WaitForSelectorOptions.State.VISIBLE;
|
state = WaitForSelectorOptions.State.VISIBLE;
|
||||||
|
@ -20,10 +20,7 @@ import com.google.gson.Gson;
|
|||||||
import com.google.gson.JsonArray;
|
import com.google.gson.JsonArray;
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import com.microsoft.playwright.BrowserType;
|
import com.microsoft.playwright.*;
|
||||||
import com.microsoft.playwright.DeviceDescriptor;
|
|
||||||
import com.microsoft.playwright.Playwright;
|
|
||||||
import com.microsoft.playwright.PlaywrightException;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -51,6 +48,7 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
|
|||||||
private final BrowserTypeImpl chromium;
|
private final BrowserTypeImpl chromium;
|
||||||
private final BrowserTypeImpl firefox;
|
private final BrowserTypeImpl firefox;
|
||||||
private final BrowserTypeImpl webkit;
|
private final BrowserTypeImpl webkit;
|
||||||
|
private final Selectors selectors;
|
||||||
private final Map<String, DeviceDescriptor> devices = new HashMap<>();
|
private final Map<String, DeviceDescriptor> devices = new HashMap<>();
|
||||||
|
|
||||||
public PlaywrightImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
public PlaywrightImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||||
@ -58,6 +56,7 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
|
|||||||
chromium = parent.connection.getExistingObject(initializer.getAsJsonObject("chromium").get("guid").getAsString());
|
chromium = parent.connection.getExistingObject(initializer.getAsJsonObject("chromium").get("guid").getAsString());
|
||||||
firefox = parent.connection.getExistingObject(initializer.getAsJsonObject("firefox").get("guid").getAsString());
|
firefox = parent.connection.getExistingObject(initializer.getAsJsonObject("firefox").get("guid").getAsString());
|
||||||
webkit = parent.connection.getExistingObject(initializer.getAsJsonObject("webkit").get("guid").getAsString());
|
webkit = parent.connection.getExistingObject(initializer.getAsJsonObject("webkit").get("guid").getAsString());
|
||||||
|
selectors = parent.connection.getExistingObject(initializer.getAsJsonObject("selectors").get("guid").getAsString());
|
||||||
|
|
||||||
Gson gson = Serialization.gson();
|
Gson gson = Serialization.gson();
|
||||||
for (JsonElement item : initializer.getAsJsonArray("deviceDescriptors")) {
|
for (JsonElement item : initializer.getAsJsonArray("deviceDescriptors")) {
|
||||||
@ -87,4 +86,9 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
|
|||||||
public Map<String, DeviceDescriptor> devices() {
|
public Map<String, DeviceDescriptor> devices() {
|
||||||
return devices;
|
return devices;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Selectors selectors() {
|
||||||
|
return selectors;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* 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.PlaywrightException;
|
||||||
|
import com.microsoft.playwright.Selectors;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import static com.microsoft.playwright.impl.Serialization.gson;
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
|
class SelectorsImpl extends ChannelOwner implements Selectors {
|
||||||
|
SelectorsImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||||
|
super(parent, type, guid, initializer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void register(String name, String script, RegisterOptions options) {
|
||||||
|
if (options == null) {
|
||||||
|
options = new RegisterOptions();
|
||||||
|
}
|
||||||
|
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
|
||||||
|
params.addProperty("name", name);
|
||||||
|
params.addProperty("source", script);
|
||||||
|
sendMessage("register", params);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void register(String name, Path path, RegisterOptions options) {
|
||||||
|
byte[] buffer;
|
||||||
|
try {
|
||||||
|
buffer = Files.readAllBytes(path);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new PlaywrightException("Failed to read selector from file: " + path, e);
|
||||||
|
}
|
||||||
|
register(name, new String(buffer, UTF_8), options);
|
||||||
|
}
|
||||||
|
}
|
@ -18,9 +18,10 @@ package com.microsoft.playwright;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Paths;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
@ -111,7 +112,7 @@ public class TestNetworkResponse extends TestBase {
|
|||||||
@Test
|
@Test
|
||||||
void shouldReturnBody() throws IOException {
|
void shouldReturnBody() throws IOException {
|
||||||
Response response = page.navigate(server.PREFIX + "/pptr.png");
|
Response response = page.navigate(server.PREFIX + "/pptr.png");
|
||||||
byte[] expected = Files.readAllBytes(new File("src/test/resources/pptr.png").toPath());
|
byte[] expected = Files.readAllBytes(Paths.get("src/test/resources/pptr.png"));
|
||||||
assertTrue(Arrays.equals(expected, response.body()));
|
assertTrue(Arrays.equals(expected, response.body()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,7 +120,7 @@ public class TestNetworkResponse extends TestBase {
|
|||||||
void shouldReturnBodyWithCompression() throws IOException {
|
void shouldReturnBodyWithCompression() throws IOException {
|
||||||
server.enableGzip("/pptr.png");
|
server.enableGzip("/pptr.png");
|
||||||
Response response = page.navigate(server.PREFIX + "/pptr.png");
|
Response response = page.navigate(server.PREFIX + "/pptr.png");
|
||||||
byte[] expected = Files.readAllBytes(new File("src/test/resources/pptr.png").toPath());
|
byte[] expected = Files.readAllBytes(Paths.get("src/test/resources/pptr.png"));
|
||||||
assertTrue(Arrays.equals(expected, response.body()));
|
assertTrue(Arrays.equals(expected, response.body()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ public class TestRequestFulfill extends TestBase {
|
|||||||
page.route("**/*", route -> {
|
page.route("**/*", route -> {
|
||||||
byte[] imageBuffer;
|
byte[] imageBuffer;
|
||||||
try {
|
try {
|
||||||
imageBuffer = Files.readAllBytes(new File("src/test/resources/pptr.png").toPath());
|
imageBuffer = Files.readAllBytes(Paths.get("src/test/resources/pptr.png"));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
|
@ -0,0 +1,150 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.microsoft.playwright.impl.ElementHandleImpl;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
public class TestSelectorsRegister extends TestBase {
|
||||||
|
@Test
|
||||||
|
void shouldWork() {
|
||||||
|
String selectorScript = "{\n" +
|
||||||
|
" create(root, target) {\n" +
|
||||||
|
" return target.nodeName;\n" +
|
||||||
|
" },\n" +
|
||||||
|
" query(root, selector) {\n" +
|
||||||
|
" return root.querySelector(selector);\n" +
|
||||||
|
" },\n" +
|
||||||
|
" queryAll(root, selector) {\n" +
|
||||||
|
" return Array.from(root.querySelectorAll(selector));\n" +
|
||||||
|
" }\n" +
|
||||||
|
"}";
|
||||||
|
// Register one engine before creating context.
|
||||||
|
playwright.selectors().register("tag", selectorScript);
|
||||||
|
|
||||||
|
BrowserContext context = browser.newContext();
|
||||||
|
// Register another engine after creating context.
|
||||||
|
playwright.selectors().register("tag2", selectorScript);
|
||||||
|
|
||||||
|
Page page = context.newPage();
|
||||||
|
page.setContent("<div><span></span></div><div></div>");
|
||||||
|
|
||||||
|
assertEquals("DIV", ((ElementHandleImpl) page.querySelector("div")).createSelectorForTest("tag"));
|
||||||
|
assertEquals("DIV", page.evalOnSelector("tag=DIV", "e => e.nodeName"));
|
||||||
|
assertEquals("SPAN", page.evalOnSelector("tag=SPAN", "e => e.nodeName"));
|
||||||
|
assertEquals(2, page.evalOnSelectorAll("tag=DIV", "es => es.length"));
|
||||||
|
|
||||||
|
assertEquals("DIV", ((ElementHandleImpl) page.querySelector("div")).createSelectorForTest("tag2"));
|
||||||
|
assertEquals("DIV", page.evalOnSelector("tag2=DIV", "e => e.nodeName"));
|
||||||
|
assertEquals("SPAN", page.evalOnSelector("tag2=SPAN", "e => e.nodeName"));
|
||||||
|
assertEquals(2, page.evalOnSelectorAll("tag2=DIV", "es => es.length"));
|
||||||
|
try {
|
||||||
|
// Selector names are case-sensitive.
|
||||||
|
page.querySelector("tAG=DIV");
|
||||||
|
fail("did not throw");
|
||||||
|
} catch (PlaywrightException e) {
|
||||||
|
assertTrue(e.getMessage().contains("Unknown engine \"tAG\" while parsing selector tAG=DIV"));
|
||||||
|
}
|
||||||
|
context.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldWorkWithPath() {
|
||||||
|
playwright.selectors().register("foo", Paths.get("src/test/resources/sectionselectorengine.js"));
|
||||||
|
page.setContent("<section></section>");
|
||||||
|
assertEquals("SECTION", page.evalOnSelector("foo=whatever", "e => e.nodeName"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldWorkInMainAndIsolatedWorld() {
|
||||||
|
String createDummySelector = "{\n" +
|
||||||
|
" create(root, target) { },\n" +
|
||||||
|
" query(root, selector) {\n" +
|
||||||
|
" return window['__answer'];\n" +
|
||||||
|
" },\n" +
|
||||||
|
" queryAll(root, selector) {\n" +
|
||||||
|
" return [document.body, document.documentElement, window['__answer']];\n" +
|
||||||
|
" }\n" +
|
||||||
|
"}";
|
||||||
|
playwright.selectors().register("main", createDummySelector);
|
||||||
|
playwright.selectors().register("isolated", createDummySelector, new Selectors.RegisterOptions().withContentScript(true));
|
||||||
|
page.setContent("<div><span><section></section></span></div>");
|
||||||
|
page.evaluate("() => window['__answer'] = document.querySelector('span')");
|
||||||
|
// Works in main if asked.
|
||||||
|
assertEquals("SPAN", page.evalOnSelector("main=ignored", "e => e.nodeName"));
|
||||||
|
assertEquals("SPAN", page.evalOnSelector("css=div >> main=ignored", "e => e.nodeName"));
|
||||||
|
assertEquals(true, page.evalOnSelectorAll("main=ignored", "es => window['__answer'] !== undefined"));
|
||||||
|
assertEquals(3, page.evalOnSelectorAll("main=ignored", "es => es.filter(e => e).length"));
|
||||||
|
// Works in isolated by default.
|
||||||
|
assertNull(page.querySelector("isolated=ignored"));
|
||||||
|
assertNull(page.querySelector("css=div >> isolated=ignored"));
|
||||||
|
// $$eval always works in main, to avoid adopting nodes one by one.
|
||||||
|
assertEquals(true, page.evalOnSelectorAll("isolated=ignored", "es => window['__answer'] !== undefined"));
|
||||||
|
assertEquals(3, page.evalOnSelectorAll("isolated=ignored", "es => es.filter(e => e).length"));
|
||||||
|
// At least one engine in main forces all to be in main.
|
||||||
|
assertEquals("SPAN", page.evalOnSelector("main=ignored >> isolated=ignored", "e => e.nodeName"));
|
||||||
|
assertEquals("SPAN", page.evalOnSelector("isolated=ignored >> main=ignored", "e => e.nodeName"));
|
||||||
|
// Can be chained to css.
|
||||||
|
assertEquals("SECTION", page.evalOnSelector("main=ignored >> css=section", "e => e.nodeName"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldHandleErrors() {
|
||||||
|
try {
|
||||||
|
page.querySelector("neverregister=ignored");
|
||||||
|
fail("did not throw");
|
||||||
|
} catch (PlaywrightException e) {
|
||||||
|
assertTrue(e.getMessage().contains("Unknown engine \"neverregister\" while parsing selector neverregister=ignored"));
|
||||||
|
}
|
||||||
|
String createDummySelector = "{\n" +
|
||||||
|
" create(root, target) {\n" +
|
||||||
|
" return target.nodeName;\n" +
|
||||||
|
" },\n" +
|
||||||
|
" query(root, selector) {\n" +
|
||||||
|
" return root.querySelector(\"dummy\");\n" +
|
||||||
|
" },\n" +
|
||||||
|
" queryAll(root, selector) {\n" +
|
||||||
|
" return Array.from(root.querySelectorAll(\"dummy\"));\n" +
|
||||||
|
" }\n" +
|
||||||
|
"}";
|
||||||
|
try {
|
||||||
|
playwright.selectors().register("$", createDummySelector);
|
||||||
|
fail("did not throw");
|
||||||
|
} catch (PlaywrightException e) {
|
||||||
|
assertTrue(e.getMessage().contains("Selector engine name may only contain [a-zA-Z0-9_] characters"));
|
||||||
|
}
|
||||||
|
// Selector names are case-sensitive.
|
||||||
|
playwright.selectors().register("dummy", createDummySelector);
|
||||||
|
playwright.selectors().register("duMMy", createDummySelector);
|
||||||
|
try {
|
||||||
|
playwright.selectors().register("dummy", createDummySelector);
|
||||||
|
fail("did not throw");
|
||||||
|
} catch (PlaywrightException e) {
|
||||||
|
assertTrue(e.getMessage().contains("\"dummy\" selector engine has been already registered"));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
playwright.selectors().register("css", createDummySelector);
|
||||||
|
fail("did not throw");
|
||||||
|
} catch (PlaywrightException e) {
|
||||||
|
assertTrue(e.getMessage().contains("\"css\" is a predefined selector engine"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user