diff --git a/api-generator/src/main/java/com/microsoft/playwright/tools/ApiGenerator.java b/api-generator/src/main/java/com/microsoft/playwright/tools/ApiGenerator.java index d0724ba6..5d27f259 100644 --- a/api-generator/src/main/java/com/microsoft/playwright/tools/ApiGenerator.java +++ b/api-generator/src/main/java/com/microsoft/playwright/tools/ApiGenerator.java @@ -312,6 +312,54 @@ class Method extends Element { }; customSignature.put("Page.waitForEvent", waitForEvent); customSignature.put("BrowserContext.waitForEvent", waitForEvent); + + String[] selectOption = { + "default List selectOption(String selector, String value) {", + " return selectOption(selector, value, null);", + "}", + "default List selectOption(String selector, String value, SelectOptionOptions options) {", + " String[] values = value == null ? null : new String[]{ value };", + " return selectOption(selector, values, options);", + "}", + "default List selectOption(String selector, String[] values) {", + " return selectOption(selector, values, null);", + "}", + "default List selectOption(String selector, String[] values, SelectOptionOptions options) {", + " if (values == null) {", + " return selectOption(selector, new ElementHandle.SelectOption[0], options);", + " }", + " return selectOption(selector, Arrays.asList(values).stream().map(", + " v -> new ElementHandle.SelectOption().withValue(v)).toArray(ElementHandle.SelectOption[]::new), options);", + "}", + "default List selectOption(String selector, ElementHandle.SelectOption value) {", + " return selectOption(selector, value, null);", + "}", + "default List selectOption(String selector, ElementHandle.SelectOption value, SelectOptionOptions options) {", + " ElementHandle.SelectOption[] values = value == null ? null : new ElementHandle.SelectOption[]{value};", + " return selectOption(selector, values, options);", + "}", + "default List selectOption(String selector, ElementHandle.SelectOption[] values) {", + " return selectOption(selector, values, null);", + "}", + "List selectOption(String selector, ElementHandle.SelectOption[] values, SelectOptionOptions options);", + "default List selectOption(String selector, ElementHandle value) {", + " return selectOption(selector, value, null);", + "}", + "default List selectOption(String selector, ElementHandle value, SelectOptionOptions options) {", + " ElementHandle[] values = value == null ? null : new ElementHandle[]{value};", + " return selectOption(selector, values, options);", + "}", + "default List selectOption(String selector, ElementHandle[] values) {", + " return selectOption(selector, values, null);", + "}", + "List selectOption(String selector, ElementHandle[] values, SelectOptionOptions options);", + }; + customSignature.put("Page.selectOption", selectOption); + customSignature.put("Frame.selectOption", selectOption); + customSignature.put("ElementHandle.selectOption", Arrays.stream(selectOption).map(s -> s + .replace("String selector, ", "") + .replace("(selector, ", "(") + .replace("ElementHandle.", "")).toArray(String[]::new)); } Method(TypeDefinition parent, JsonObject jsonElement) { @@ -502,7 +550,7 @@ class Field extends Element { class Interface extends TypeDefinition { private final List methods = new ArrayList<>(); private final List events = new ArrayList<>(); - private static String header = "/**\n" + + private static String header = "/*\n" + " * Copyright (c) Microsoft Corporation.\n" + " *\n" + " * Licensed under the Apache License, Version 2.0 (the \"License\");\n" + @@ -686,6 +734,25 @@ class Interface extends TypeDefinition { output.add(offset + " public double height;"); output.add(offset + "}"); output.add(""); + output.add(offset + "class SelectOption {"); + output.add(offset + " public String value;"); + output.add(offset + " public String label;"); + output.add(offset + " public Integer index;"); + output.add(""); + output.add(offset + " public SelectOption withValue(String value) {"); + output.add(offset + " this.value = value;"); + output.add(offset + " return this;"); + output.add(offset + " }"); + output.add(offset + " public SelectOption withLabel(String label) {"); + output.add(offset + " this.label = label;"); + output.add(offset + " return this;"); + output.add(offset + " }"); + output.add(offset + " public SelectOption withIndex(int index) {"); + output.add(offset + " this.index = index;"); + output.add(offset + " return this;"); + output.add(offset + " }"); + output.add(offset + "}"); + output.add(""); break; } case "FileChooser": { diff --git a/playwright/src/main/java/com/microsoft/playwright/ElementHandle.java b/playwright/src/main/java/com/microsoft/playwright/ElementHandle.java index 904e8397..44133454 100644 --- a/playwright/src/main/java/com/microsoft/playwright/ElementHandle.java +++ b/playwright/src/main/java/com/microsoft/playwright/ElementHandle.java @@ -27,6 +27,25 @@ public interface ElementHandle extends JSHandle { public double height; } + class SelectOption { + public String value; + public String label; + public Integer index; + + public SelectOption withValue(String value) { + this.value = value; + return this; + } + public SelectOption withLabel(String label) { + this.label = label; + return this; + } + public SelectOption withIndex(int index) { + this.index = index; + return this; + } + } + enum ElementState { DISABLED, ENABLED, HIDDEN, STABLE, VISIBLE } class CheckOptions { public Boolean force; @@ -420,10 +439,45 @@ public interface ElementHandle extends JSHandle { scrollIntoViewIfNeeded(null); } void scrollIntoViewIfNeeded(ScrollIntoViewIfNeededOptions options); - default List selectOption(String values) { + default List selectOption(String value) { + return selectOption(value, null); + } + default List selectOption(String value, SelectOptionOptions options) { + String[] values = value == null ? null : new String[]{ value }; + return selectOption(values, options); + } + default List selectOption(String[] values) { return selectOption(values, null); } - List selectOption(String values, SelectOptionOptions options); + default List selectOption(String[] values, SelectOptionOptions options) { + if (values == null) { + return selectOption(new SelectOption[0], options); + } + return selectOption(Arrays.asList(values).stream().map( + v -> new SelectOption().withValue(v)).toArray(SelectOption[]::new), options); + } + default List selectOption(SelectOption value) { + return selectOption(value, null); + } + default List selectOption(SelectOption value, SelectOptionOptions options) { + SelectOption[] values = value == null ? null : new SelectOption[]{value}; + return selectOption(values, options); + } + default List selectOption(SelectOption[] values) { + return selectOption(values, null); + } + List selectOption(SelectOption[] values, SelectOptionOptions options); + default List selectOption(ElementHandle value) { + return selectOption(value, null); + } + default List selectOption(ElementHandle value, SelectOptionOptions options) { + ElementHandle[] values = value == null ? null : new ElementHandle[]{value}; + return selectOption(values, options); + } + default List selectOption(ElementHandle[] values) { + return selectOption(values, null); + } + List selectOption(ElementHandle[] values, SelectOptionOptions options); default void selectText() { selectText(null); } diff --git a/playwright/src/main/java/com/microsoft/playwright/Frame.java b/playwright/src/main/java/com/microsoft/playwright/Frame.java index 857830de..bf5a5ba0 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Frame.java +++ b/playwright/src/main/java/com/microsoft/playwright/Frame.java @@ -560,10 +560,45 @@ public interface Frame { press(selector, key, null); } void press(String selector, String key, PressOptions options); - default List selectOption(String selector, String values) { + default List selectOption(String selector, String value) { + return selectOption(selector, value, null); + } + default List selectOption(String selector, String value, SelectOptionOptions options) { + String[] values = value == null ? null : new String[]{ value }; + return selectOption(selector, values, options); + } + default List selectOption(String selector, String[] values) { return selectOption(selector, values, null); } - List selectOption(String selector, String values, SelectOptionOptions options); + default List selectOption(String selector, String[] values, SelectOptionOptions options) { + if (values == null) { + return selectOption(selector, new ElementHandle.SelectOption[0], options); + } + return selectOption(selector, Arrays.asList(values).stream().map( + v -> new ElementHandle.SelectOption().withValue(v)).toArray(ElementHandle.SelectOption[]::new), options); + } + default List selectOption(String selector, ElementHandle.SelectOption value) { + return selectOption(selector, value, null); + } + default List selectOption(String selector, ElementHandle.SelectOption value, SelectOptionOptions options) { + ElementHandle.SelectOption[] values = value == null ? null : new ElementHandle.SelectOption[]{value}; + return selectOption(selector, values, options); + } + default List selectOption(String selector, ElementHandle.SelectOption[] values) { + return selectOption(selector, values, null); + } + List selectOption(String selector, ElementHandle.SelectOption[] values, SelectOptionOptions options); + default List selectOption(String selector, ElementHandle value) { + return selectOption(selector, value, null); + } + default List selectOption(String selector, ElementHandle value, SelectOptionOptions options) { + ElementHandle[] values = value == null ? null : new ElementHandle[]{value}; + return selectOption(selector, values, options); + } + default List selectOption(String selector, ElementHandle[] values) { + return selectOption(selector, values, null); + } + List selectOption(String selector, ElementHandle[] values, SelectOptionOptions options); default void setContent(String html) { setContent(html, null); } diff --git a/playwright/src/main/java/com/microsoft/playwright/Page.java b/playwright/src/main/java/com/microsoft/playwright/Page.java index 7b755344..22fc8daf 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Page.java +++ b/playwright/src/main/java/com/microsoft/playwright/Page.java @@ -931,10 +931,45 @@ public interface Page { return screenshot(null); } byte[] screenshot(ScreenshotOptions options); - default List selectOption(String selector, String values) { + default List selectOption(String selector, String value) { + return selectOption(selector, value, null); + } + default List selectOption(String selector, String value, SelectOptionOptions options) { + String[] values = value == null ? null : new String[]{ value }; + return selectOption(selector, values, options); + } + default List selectOption(String selector, String[] values) { return selectOption(selector, values, null); } - List selectOption(String selector, String values, SelectOptionOptions options); + default List selectOption(String selector, String[] values, SelectOptionOptions options) { + if (values == null) { + return selectOption(selector, new ElementHandle.SelectOption[0], options); + } + return selectOption(selector, Arrays.asList(values).stream().map( + v -> new ElementHandle.SelectOption().withValue(v)).toArray(ElementHandle.SelectOption[]::new), options); + } + default List selectOption(String selector, ElementHandle.SelectOption value) { + return selectOption(selector, value, null); + } + default List selectOption(String selector, ElementHandle.SelectOption value, SelectOptionOptions options) { + ElementHandle.SelectOption[] values = value == null ? null : new ElementHandle.SelectOption[]{value}; + return selectOption(selector, values, options); + } + default List selectOption(String selector, ElementHandle.SelectOption[] values) { + return selectOption(selector, values, null); + } + List selectOption(String selector, ElementHandle.SelectOption[] values, SelectOptionOptions options); + default List selectOption(String selector, ElementHandle value) { + return selectOption(selector, value, null); + } + default List selectOption(String selector, ElementHandle value, SelectOptionOptions options) { + ElementHandle[] values = value == null ? null : new ElementHandle[]{value}; + return selectOption(selector, values, options); + } + default List selectOption(String selector, ElementHandle[] values) { + return selectOption(selector, values, null); + } + List selectOption(String selector, ElementHandle[] values, SelectOptionOptions options); default void setContent(String html) { setContent(html, null); } diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/ElementHandleImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/ElementHandleImpl.java index 3492bd6f..b917ac63 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/ElementHandleImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/ElementHandleImpl.java @@ -20,18 +20,17 @@ import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import com.microsoft.playwright.*; +import com.microsoft.playwright.Deferred; +import com.microsoft.playwright.ElementHandle; +import com.microsoft.playwright.FileChooser; +import com.microsoft.playwright.Frame; -import java.io.DataOutputStream; import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; import java.util.ArrayList; import java.util.Base64; import java.util.List; -import static com.microsoft.playwright.impl.Serialization.deserialize; -import static com.microsoft.playwright.impl.Serialization.serializeArgument; +import static com.microsoft.playwright.impl.Serialization.*; import static com.microsoft.playwright.impl.Utils.isFunctionBody; class ElementHandleImpl extends JSHandleImpl implements ElementHandle { @@ -277,9 +276,32 @@ class ElementHandleImpl extends JSHandleImpl implements ElementHandle { } @Override - public List selectOption(String values, SelectOptionOptions options) { - // TODO: - return null; + public List selectOption(SelectOption[] values, SelectOptionOptions options) { + if (options == null) { + options = new SelectOptionOptions(); + } + JsonObject params = new Gson().toJsonTree(options).getAsJsonObject(); + if (values != null) { + params.add("options", new Gson().toJsonTree(values)); + } + return selectOption(params); + } + + @Override + public List selectOption(ElementHandle[] values, SelectOptionOptions options) { + if (options == null) { + options = new SelectOptionOptions(); + } + JsonObject params = new Gson().toJsonTree(options).getAsJsonObject(); + if (values != null) { + params.add("elements", Serialization.toProtocol(values)); + } + return selectOption(params); + } + + private List selectOption(JsonObject params) { + JsonObject json = sendMessage("selectOption", params).getAsJsonObject(); + return parseStringList(json.getAsJsonArray("values")); } @Override diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/FrameImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/FrameImpl.java index bdf9d4a5..2832b5ad 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/FrameImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/FrameImpl.java @@ -26,7 +26,6 @@ import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; -import java.nio.file.Paths; import java.util.*; import static com.microsoft.playwright.Frame.LoadState.*; @@ -358,10 +357,35 @@ public class FrameImpl extends ChannelOwner implements Frame { } @Override - public List selectOption(String selector, String values, SelectOptionOptions options) { - return null; + public List selectOption(String selector, ElementHandle.SelectOption[] values, SelectOptionOptions options) { + if (options == null) { + options = new SelectOptionOptions(); + } + JsonObject params = new Gson().toJsonTree(options).getAsJsonObject(); + params.addProperty("selector", selector); + if (values != null) { + params.add("options", new Gson().toJsonTree(values)); + } + return selectOption(params); } + @Override + public List selectOption(String selector, ElementHandle[] values, SelectOptionOptions options) { + if (options == null) { + options = new SelectOptionOptions(); + } + JsonObject params = new Gson().toJsonTree(options).getAsJsonObject(); + params.addProperty("selector", selector); + if (values != null) { + params.add("elements", Serialization.toProtocol(values)); + } + return selectOption(params); + } + + private List selectOption(JsonObject params) { + JsonObject json = sendMessage("selectOption", params).getAsJsonObject(); + return parseStringList(json.getAsJsonArray("values")); + } static String toProtocol(LoadState waitUntil) { if (waitUntil == null) { diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java index 2accec1f..6ab20f5c 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java @@ -586,7 +586,12 @@ public class PageImpl extends ChannelOwner implements Page { } @Override - public List selectOption(String selector, String values, SelectOptionOptions options) { + public List selectOption(String selector, ElementHandle.SelectOption[] values, SelectOptionOptions options) { + return mainFrame.selectOption(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class)); + } + + @Override + public List selectOption(String selector, ElementHandle[] values, SelectOptionOptions options) { return mainFrame.selectOption(selector, values, convertViaJson(options, Frame.SelectOptionOptions.class)); } diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/Serialization.java b/playwright/src/main/java/com/microsoft/playwright/impl/Serialization.java index ba3704fe..be3d7563 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/Serialization.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/Serialization.java @@ -18,16 +18,16 @@ package com.microsoft.playwright.impl; import com.google.gson.Gson; import com.google.gson.JsonArray; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import com.microsoft.playwright.FileChooser; -import com.microsoft.playwright.JSHandle; -import com.microsoft.playwright.Keyboard; -import com.microsoft.playwright.Mouse; +import com.microsoft.playwright.*; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.lang.reflect.Array; import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; class Serialization { static SerializedError serializeError(Throwable e) { @@ -199,4 +199,22 @@ class Serialization { } return jsonFiles; } + + static JsonArray toProtocol(ElementHandle[] handles) { + JsonArray jsonElements = new JsonArray(); + for (ElementHandle handle : handles) { + JsonObject jsonHandle = new JsonObject(); + jsonHandle.addProperty("guid", ((ElementHandleImpl) handle).guid); + jsonElements.add(jsonHandle); + } + return jsonElements; + } + + static List parseStringList(JsonArray array) { + List result = new ArrayList<>(); + for (JsonElement e : array) { + result.add(e.getAsString()); + } + return result; + } } diff --git a/playwright/src/test/java/com/microsoft/playwright/TestPageSelectOption.java b/playwright/src/test/java/com/microsoft/playwright/TestPageSelectOption.java new file mode 100644 index 00000000..31381621 --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/TestPageSelectOption.java @@ -0,0 +1,236 @@ +/* + * 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.Arrays; +import java.util.Collections; +import java.util.List; + +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static org.junit.jupiter.api.Assertions.*; + +public class TestPageSelectOption extends TestBase { + @Test + void shouldSelectSingleOption() { + page.navigate(server.PREFIX + "/input/select.html"); + page.selectOption("select", "blue"); + assertEquals(asList("blue"), page.evaluate("() => window['result'].onInput")); + assertEquals(asList("blue"), page.evaluate("() => window['result'].onChange")); + } + + @Test + void shouldSelectSingleOptionByValue() { + page.navigate(server.PREFIX + "/input/select.html"); + page.selectOption("select", new ElementHandle.SelectOption().withValue("blue")); + assertEquals(asList("blue"), page.evaluate("() => window['result'].onInput")); + assertEquals(asList("blue"), page.evaluate("() => window['result'].onChange")); + } + + @Test + void shouldSelectSingleOptionByLabel() { + page.navigate(server.PREFIX + "/input/select.html"); + page.selectOption("select", new ElementHandle.SelectOption().withLabel("Indigo")); + assertEquals(asList("indigo"), page.evaluate("() => window['result'].onInput")); + assertEquals(asList("indigo"), page.evaluate("() => window['result'].onChange")); + } + + @Test + void shouldSelectSingleOptionByHandle() { + page.navigate(server.PREFIX + "/input/select.html"); + page.selectOption("select", page.querySelector("[id=whiteOption]")); + assertEquals(asList("white"), page.evaluate("() => window['result'].onInput")); + assertEquals(asList("white"), page.evaluate("() => window['result'].onChange")); + } + + @Test + void shouldSelectSingleOptionByIndex() { + page.navigate(server.PREFIX + "/input/select.html"); + page.selectOption("select", new ElementHandle.SelectOption().withIndex(2)); + assertEquals(asList("brown"), page.evaluate("() => window['result'].onInput")); + assertEquals(asList("brown"), page.evaluate("() => window['result'].onChange")); + } + + @Test + void shouldSelectSingleOptionByMultipleAttributes() { + page.navigate(server.PREFIX + "/input/select.html"); + page.selectOption("select", new ElementHandle.SelectOption().withValue("green").withLabel("Green")); + assertEquals(asList("green"), page.evaluate("() => window['result'].onInput")); + assertEquals(asList("green"), page.evaluate("() => window['result'].onChange")); + } + + @Test + void shouldNotSelectSingleOptionWhenSomeAttributesDoNotMatch() { + page.navigate(server.PREFIX + "/input/select.html"); + page.selectOption("select", new ElementHandle.SelectOption().withValue("green").withLabel("Brown")); + assertEquals("", page.evaluate("() => document.querySelector('select').value")); + } + + @Test + void shouldSelectOnlyFirstOption() { + page.navigate(server.PREFIX + "/input/select.html"); + page.selectOption("select", new String[]{"blue", "green", "red"}); + assertEquals(asList("blue"), page.evaluate("() => window['result'].onInput")); + assertEquals(asList("blue"), page.evaluate("() => window['result'].onChange")); + } + + @Test + void shouldNotThrowWhenSelectCausesNavigation() { + page.navigate(server.PREFIX + "/input/select.html"); + page.evalOnSelector("select", "select => select.addEventListener('input', () => window.location.href = '/empty.html')"); + Deferred response = page.waitForNavigation(); + page.selectOption("select", "blue"); + response.get(); + assertTrue(page.url().contains("empty.html")); + } + + @Test + void shouldSelectMultipleOptions() { + page.navigate(server.PREFIX + "/input/select.html"); + page.evaluate("() => window['makeMultiple']()"); + page.selectOption("select", new String[]{"blue", "green", "red"}); + assertEquals(asList("blue", "green", "red"), page.evaluate("() => window['result'].onInput")); + assertEquals(asList("blue", "green", "red"), page.evaluate("() => window['result'].onChange")); + } + + @Test + void shouldSelectMultipleOptionsWithAttributes() { + page.navigate(server.PREFIX + "/input/select.html"); + page.evaluate("() => window['makeMultiple']()"); + page.selectOption("select", new ElementHandle.SelectOption[] { + new ElementHandle.SelectOption().withValue("blue"), + new ElementHandle.SelectOption().withLabel("Green"), + new ElementHandle.SelectOption().withIndex(4), + }); + assertEquals(asList("blue", "gray", "green"), page.evaluate("() => window['result'].onInput")); + assertEquals(asList("blue", "gray", "green"), page.evaluate("() => window['result'].onChange")); + } + + @Test + void shouldRespectEventBubbling() { + page.navigate(server.PREFIX + "/input/select.html"); + page.selectOption("select", "blue"); + assertEquals(asList("blue"), page.evaluate("() => window['result'].onBubblingInput")); + assertEquals(asList("blue"), page.evaluate("() => window['result'].onBubblingChange")); + } + + @Test + void shouldThrowWhenElementIsNotASelect() { + page.navigate(server.PREFIX + "/input/select.html"); + try { + page.selectOption("body", ""); + fail("did not throw"); + } catch (RuntimeException e) { + assertTrue(e.getMessage().contains("Element is not a