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("(selector, ", "(")
|
||||
.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) {
|
||||
@ -656,7 +663,7 @@ class Interface extends TypeDefinition {
|
||||
if ("Download".equals(jsonName)) {
|
||||
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.util.*;");
|
||||
|
@ -31,4 +31,6 @@ public interface Playwright {
|
||||
BrowserType webkit();
|
||||
|
||||
Map<String, DeviceDescriptor> devices();
|
||||
|
||||
Selectors selectors();
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
package com.microsoft.playwright;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
public interface Selectors {
|
||||
@ -27,9 +28,9 @@ public interface Selectors {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
default void register(String name, String script) {
|
||||
register(name, script, null);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -228,7 +228,7 @@ public class Connection {
|
||||
result = new Stream(parent, type, guid, initializer);
|
||||
break;
|
||||
case "Selectors":
|
||||
// result = new Playwright(parent, type, guid, initializer);
|
||||
result = new SelectorsImpl(parent, type, guid, initializer);
|
||||
break;
|
||||
case "Worker":
|
||||
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.Utils.isFunctionBody;
|
||||
|
||||
class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
|
||||
public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
|
||||
ElementHandleImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
}
|
||||
@ -384,6 +384,16 @@ class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
|
||||
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) {
|
||||
if (state == null) {
|
||||
state = WaitForSelectorOptions.State.VISIBLE;
|
||||
|
@ -20,10 +20,7 @@ import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.microsoft.playwright.BrowserType;
|
||||
import com.microsoft.playwright.DeviceDescriptor;
|
||||
import com.microsoft.playwright.Playwright;
|
||||
import com.microsoft.playwright.PlaywrightException;
|
||||
import com.microsoft.playwright.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@ -51,6 +48,7 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
|
||||
private final BrowserTypeImpl chromium;
|
||||
private final BrowserTypeImpl firefox;
|
||||
private final BrowserTypeImpl webkit;
|
||||
private final Selectors selectors;
|
||||
private final Map<String, DeviceDescriptor> devices = new HashMap<>();
|
||||
|
||||
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());
|
||||
firefox = parent.connection.getExistingObject(initializer.getAsJsonObject("firefox").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();
|
||||
for (JsonElement item : initializer.getAsJsonArray("deviceDescriptors")) {
|
||||
@ -87,4 +86,9 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
|
||||
public Map<String, DeviceDescriptor> 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 java.io.*;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
@ -111,7 +112,7 @@ public class TestNetworkResponse extends TestBase {
|
||||
@Test
|
||||
void shouldReturnBody() throws IOException {
|
||||
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()));
|
||||
}
|
||||
|
||||
@ -119,7 +120,7 @@ public class TestNetworkResponse extends TestBase {
|
||||
void shouldReturnBodyWithCompression() throws IOException {
|
||||
server.enableGzip("/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()));
|
||||
}
|
||||
|
||||
|
@ -61,7 +61,7 @@ public class TestRequestFulfill extends TestBase {
|
||||
page.route("**/*", route -> {
|
||||
byte[] imageBuffer;
|
||||
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) {
|
||||
e.printStackTrace();
|
||||
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