feat: selectors (#63)

This commit is contained in:
Yury Semikhatsky 2020-10-30 16:19:08 -07:00 committed by GitHub
parent 602406b53b
commit d539159907
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 246 additions and 15 deletions

View File

@ -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.*;");

View File

@ -31,4 +31,6 @@ public interface Playwright {
BrowserType webkit(); BrowserType webkit();
Map<String, DeviceDescriptor> devices(); Map<String, DeviceDescriptor> devices();
Selectors selectors();
} }

View File

@ -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);
} }

View File

@ -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);

View File

@ -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;

View File

@ -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;
}
} }

View File

@ -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);
}
}

View File

@ -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()));
} }

View File

@ -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);

View File

@ -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"));
}
}
}