From a592c12649af725aefb569954992a65ae737b876 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Sun, 1 Nov 2020 14:29:24 -0800 Subject: [PATCH] feat: accessibility (#64) --- .../com/microsoft/playwright/tools/Types.java | 1 + .../microsoft/playwright/Accessibility.java | 4 +- .../playwright/AccessibilityNode.java | 51 ++++ .../playwright/impl/AccessibilityImpl.java | 44 +++ .../impl/AccessibilityNodeImpl.java | 258 ++++++++++++++++++ .../microsoft/playwright/impl/PageImpl.java | 4 +- .../playwright/TestAccessibility.java | 68 +++++ 7 files changed, 427 insertions(+), 3 deletions(-) create mode 100644 playwright/src/main/java/com/microsoft/playwright/AccessibilityNode.java create mode 100644 playwright/src/main/java/com/microsoft/playwright/impl/AccessibilityImpl.java create mode 100644 playwright/src/main/java/com/microsoft/playwright/impl/AccessibilityNodeImpl.java create mode 100644 playwright/src/test/java/com/microsoft/playwright/TestAccessibility.java diff --git a/api-generator/src/main/java/com/microsoft/playwright/tools/Types.java b/api-generator/src/main/java/com/microsoft/playwright/tools/Types.java index 184f7157..e70b963c 100644 --- a/api-generator/src/main/java/com/microsoft/playwright/tools/Types.java +++ b/api-generator/src/main/java/com/microsoft/playwright/tools/Types.java @@ -182,6 +182,7 @@ class Types { add("Dialog.type", "string", "Type", new Empty()); add("ConsoleMessage.location", "Object", "Location"); add("ElementHandle.boundingBox", "Promise", "BoundingBox", new Empty()); + add("Accessibility.snapshot", "Promise", "AccessibilityNode", new Empty()); add("Page.waitForRequest", "Promise", "Deferred"); add("Page.waitForResponse", "Promise", "Deferred"); diff --git a/playwright/src/main/java/com/microsoft/playwright/Accessibility.java b/playwright/src/main/java/com/microsoft/playwright/Accessibility.java index 1ad409bf..b8e43064 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Accessibility.java +++ b/playwright/src/main/java/com/microsoft/playwright/Accessibility.java @@ -32,9 +32,9 @@ public interface Accessibility { return this; } } - default Object snapshot() { + default AccessibilityNode snapshot() { return snapshot(null); } - Object snapshot(SnapshotOptions options); + AccessibilityNode snapshot(SnapshotOptions options); } diff --git a/playwright/src/main/java/com/microsoft/playwright/AccessibilityNode.java b/playwright/src/main/java/com/microsoft/playwright/AccessibilityNode.java new file mode 100644 index 00000000..bc24dd27 --- /dev/null +++ b/playwright/src/main/java/com/microsoft/playwright/AccessibilityNode.java @@ -0,0 +1,51 @@ +/* + * 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 java.util.List; + +public interface AccessibilityNode { + String role(); + String name(); + String valueString(); + Double valueNumber(); + String description(); + String keyshortcuts(); + String roledescription(); + String valuetext(); + Boolean disabled(); + Boolean expanded(); + Boolean focused(); + Boolean modal(); + Boolean multiline(); + Boolean multiselectable(); + Boolean readonly(); + Boolean required(); + Boolean selected(); + enum CheckedState { CHECKED, UNCHECKED, MIXED } + CheckedState checked(); + enum PressedState { PRESSED, RELEASED, MIXED } + PressedState pressed(); + Integer level(); + Double valuemin(); + Double valuemax(); + String autocomplete(); + String haspopup(); + String invalid(); + String orientation(); + List children(); +} diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/AccessibilityImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/AccessibilityImpl.java new file mode 100644 index 00000000..cc344f01 --- /dev/null +++ b/playwright/src/main/java/com/microsoft/playwright/impl/AccessibilityImpl.java @@ -0,0 +1,44 @@ +/* + * 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.Accessibility; +import com.microsoft.playwright.AccessibilityNode; + +import static com.microsoft.playwright.impl.Serialization.gson; + +class AccessibilityImpl implements Accessibility { + private final PageImpl page; + + AccessibilityImpl(PageImpl page) { + this.page = page; + } + + @Override + public AccessibilityNode snapshot(SnapshotOptions options) { + if (options == null) { + options = new SnapshotOptions(); + } + JsonObject params = gson().toJsonTree(options).getAsJsonObject(); + JsonObject json = page.sendMessage("accessibilitySnapshot", params).getAsJsonObject(); + if (!json.has("rootAXNode")) { + return null; + } + return new AccessibilityNodeImpl(json.getAsJsonObject("rootAXNode")); + } +} diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/AccessibilityNodeImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/AccessibilityNodeImpl.java new file mode 100644 index 00000000..36a8a05a --- /dev/null +++ b/playwright/src/main/java/com/microsoft/playwright/impl/AccessibilityNodeImpl.java @@ -0,0 +1,258 @@ +/* + * 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.JsonElement; +import com.google.gson.JsonObject; +import com.microsoft.playwright.AccessibilityNode; + +import java.util.ArrayList; +import java.util.List; + +class AccessibilityNodeImpl implements AccessibilityNode { + private final JsonObject json; + + AccessibilityNodeImpl(JsonObject json) { + this.json = json; + } + + @Override + public String role() { + return json.get("role").getAsString(); + } + + @Override + public String name() { + return json.get("name").getAsString(); + } + + @Override + public String valueString() { + if (!json.has("valueString")) { + return null; + } + return json.get("valueString").getAsString(); + } + + @Override + public Double valueNumber() { + if (!json.has("valueNumber")) { + return null; + } + return json.get("valueNumber").getAsDouble(); + } + + @Override + public String description() { + if (!json.has("description")) { + return null; + } + return json.get("description").getAsString(); + } + + @Override + public String keyshortcuts() { + if (!json.has("keyshortcuts")) { + return null; + } + return json.get("keyshortcuts").getAsString(); + } + + @Override + public String roledescription() { + if (!json.has("roledescription")) { + return null; + } + return json.get("roledescription").getAsString(); + } + + @Override + public String valuetext() { + if (!json.has("valuetext")) { + return null; + } + return json.get("valuetext").getAsString(); + } + + @Override + public Boolean disabled() { + if (!json.has("disabled")) { + return null; + } + return json.get("disabled").getAsBoolean(); + } + + @Override + public Boolean expanded() { + if (!json.has("expanded")) { + return null; + } + return json.get("expanded").getAsBoolean(); + } + + @Override + public Boolean focused() { + if (!json.has("focused")) { + return null; + } + return json.get("focused").getAsBoolean(); + } + + @Override + public Boolean modal() { + if (!json.has("modal")) { + return null; + } + return json.get("modal").getAsBoolean(); + } + + @Override + public Boolean multiline() { + if (!json.has("multiline")) { + return null; + } + return json.get("multiline").getAsBoolean(); + } + + @Override + public Boolean multiselectable() { + if (!json.has("multiselectable")) { + return null; + } + return json.get("multiselectable").getAsBoolean(); + } + + @Override + public Boolean readonly() { + if (!json.has("readonly")) { + return null; + } + return json.get("readonly").getAsBoolean(); + } + + @Override + public Boolean required() { + if (!json.has("required")) { + return null; + } + return json.get("required").getAsBoolean(); + } + + @Override + public Boolean selected() { + if (!json.has("selected")) { + return null; + } + return json.get("selected").getAsBoolean(); + } + + @Override + public CheckedState checked() { + if (!json.has("checked")) { + return null; + } + String value = json.get("checked").getAsString(); + switch (value) { + case "checked": return CheckedState.CHECKED; + case "unchecked": return CheckedState.UNCHECKED; + case "mixed": return CheckedState.MIXED; + default: throw new IllegalStateException("Unexpected value: " + value); + } + } + + @Override + public PressedState pressed() { + if (!json.has("pressed")) { + return null; + } + String value = json.get("pressed").getAsString(); + switch (value) { + case "pressed": return PressedState.PRESSED; + case "released": return PressedState.RELEASED; + case "mixed": return PressedState.MIXED; + default: throw new IllegalStateException("Unexpected value: " + value); + } + } + + @Override + public Integer level() { + if (!json.has("level")) { + return null; + } + return json.get("level").getAsInt(); + } + + @Override + public Double valuemin() { + if (!json.has("valuemin")) { + return null; + } + return json.get("valuemin").getAsDouble(); + } + + @Override + public Double valuemax() { + if (!json.has("valuemax")) { + return null; + } + return json.get("valuemax").getAsDouble(); + } + + @Override + public String autocomplete() { + if (!json.has("autocomplete")) { + return null; + } + return json.get("autocomplete").getAsString(); + } + + @Override + public String haspopup() { + if (!json.has("haspopup")) { + return null; + } + return json.get("haspopup").getAsString(); + } + + @Override + public String invalid() { + if (!json.has("invalid")) { + return null; + } + return json.get("invalid").getAsString(); + } + + @Override + public String orientation() { + if (!json.has("orientation")) { + return null; + } + return json.get("orientation").getAsString(); + } + + @Override + public List children() { + if (!json.has("children")) { + return null; + } + List result = new ArrayList<>(); + for (JsonElement e : json.getAsJsonArray("children")) { + result.add(new AccessibilityNodeImpl(e.getAsJsonObject())); + } + return result; + } +} 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 25fefe70..b357f819 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java @@ -37,6 +37,7 @@ public class PageImpl extends ChannelOwner implements Page { private final FrameImpl mainFrame; private final KeyboardImpl keyboard; private final MouseImpl mouse; + private final AccessibilityImpl accessibility; private Viewport viewport; private final Router routes = new Router(); private final Set frames = new LinkedHashSet<>(); @@ -54,6 +55,7 @@ public class PageImpl extends ChannelOwner implements Page { mainFrame.page = this; keyboard = new KeyboardImpl(this); mouse = new MouseImpl(this); + accessibility = new AccessibilityImpl(this); frames.add(mainFrame); timeoutSettings = new TimeoutSettings(browserContext.timeoutSettings); } @@ -232,7 +234,7 @@ public class PageImpl extends ChannelOwner implements Page { @Override public Accessibility accessibility() { - return null; + return accessibility; } @Override diff --git a/playwright/src/test/java/com/microsoft/playwright/TestAccessibility.java b/playwright/src/test/java/com/microsoft/playwright/TestAccessibility.java new file mode 100644 index 00000000..1a2873a2 --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/TestAccessibility.java @@ -0,0 +1,68 @@ +/* + * 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 static org.junit.jupiter.api.Assertions.assertEquals; + +public class TestAccessibility extends TestBase { + + @Test + void shouldWorkWithRegularText() { + page.setContent("
Hello World
"); + AccessibilityNode snapshot = page.accessibility().snapshot(); + AccessibilityNode node = snapshot.children().get(0); + assertEquals(isFirefox ? "text leaf" : "text", node.role()); + assertEquals("Hello World", node.name()); + } + + @Test + void roledescription() { + page.setContent("
Hi
"); + AccessibilityNode snapshot = page.accessibility().snapshot(); + assertEquals("foo", snapshot.children().get(0).roledescription()); + } + + @Test + void orientation() { + page.setContent("11"); + AccessibilityNode snapshot = page.accessibility().snapshot(); + assertEquals("vertical", snapshot.children().get(0).orientation()); + } + + @Test + void autocomplete() { + page.setContent("
hi
"); + AccessibilityNode snapshot = page.accessibility().snapshot(); + assertEquals("list", snapshot.children().get(0).autocomplete()); + } + + @Test + void multiselectable() { + page.setContent("
hey
"); + AccessibilityNode snapshot = page.accessibility().snapshot(); + assertEquals(true, snapshot.children().get(0).multiselectable()); + } + + @Test + void keyshortcuts() { + page.setContent("
hey
"); + AccessibilityNode snapshot = page.accessibility().snapshot(); + assertEquals("foo", snapshot.children().get(0).keyshortcuts()); + } +}