fix: change Accessibility.snapshot type to String (#251)

This commit is contained in:
Yury Semikhatsky 2021-02-03 12:58:38 -08:00 committed by GitHub
parent b60bbc8b88
commit e31ea3cbe9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 72 additions and 457 deletions

View File

@ -53,7 +53,7 @@ public interface Accessibility {
return this;
}
}
default AccessibilityNode snapshot() {
default String snapshot() {
return snapshot(null);
}
/**
@ -63,6 +63,6 @@ public interface Accessibility {
* <p> <strong>NOTE:</strong> The Chromium accessibility tree contains nodes that go unused on most platforms and by most screen readers.
* Playwright will discard them as well for an easier to process tree, unless {@code interestingOnly} is set to {@code false}.
*/
AccessibilityNode snapshot(SnapshotOptions options);
String snapshot(SnapshotOptions options);
}

View File

@ -1,51 +0,0 @@
/*
* 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<AccessibilityNode> children();
}

View File

@ -20,6 +20,11 @@ import java.util.*;
/**
* {@code Dialog} objects are dispatched by page via the [{@code event: Page.dialog}] event.
*
* <p> <strong>NOTE:</strong> Dialogs are dismissed automatically, unless there is a [{@code event: Page.dialog}] listener. When listener is
* present, it **must** either [{@code method: Dialog.accept}] or [{@code method: Dialog.dismiss}] the dialog - otherwise the page will
* [freeze](https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#never_blocking) waiting for the dialog, and
* actions like click will never finish.
*/
public interface Dialog {
default void accept() {

View File

@ -16,9 +16,8 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
import com.google.gson.*;
import com.microsoft.playwright.Accessibility;
import com.microsoft.playwright.AccessibilityNode;
import static com.microsoft.playwright.impl.Serialization.gson;
@ -30,11 +29,11 @@ class AccessibilityImpl implements Accessibility {
}
@Override
public AccessibilityNode snapshot(SnapshotOptions options) {
public String snapshot(SnapshotOptions options) {
return page.withLogging("Accessibility.snapshot", () -> snapshotImpl(options));
}
private AccessibilityNode snapshotImpl(SnapshotOptions options) {
private String snapshotImpl(SnapshotOptions options) {
if (options == null) {
options = new SnapshotOptions();
}
@ -43,6 +42,6 @@ class AccessibilityImpl implements Accessibility {
if (!json.has("rootAXNode")) {
return null;
}
return new AccessibilityNodeImpl(json.getAsJsonObject("rootAXNode"));
return json.getAsJsonObject("rootAXNode").toString();
}
}

View File

@ -1,258 +0,0 @@
/*
* 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<AccessibilityNode> children() {
if (!json.has("children")) {
return null;
}
List<AccessibilityNode> result = new ArrayList<>();
for (JsonElement e : json.getAsJsonArray("children")) {
result.add(new AccessibilityNodeImpl(e.getAsJsonObject()));
}
return result;
}
}

View File

@ -16,115 +16,23 @@
package com.microsoft.playwright;
import com.google.gson.*;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIf;
import org.junit.jupiter.api.condition.EnabledIf;
import java.lang.reflect.Type;
import static org.junit.jupiter.api.Assertions.*;
public class TestAccessibility extends TestBase {
private static class AccessibilityNodeSerializer implements JsonSerializer<AccessibilityNode> {
@Override
public JsonElement serialize(AccessibilityNode src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject json = new JsonObject();
if (src.role() != null) {
json.addProperty("role", src.role());
}
if (src.name() != null) {
json.addProperty("name", src.name());
}
if (src.valueString() != null) {
json.addProperty("valueString", src.valueString());
}
if (src.valueNumber() != null) {
json.addProperty("valueNumber", src.valueNumber());
}
if (src.description() != null) {
json.addProperty("description", src.description());
}
if (src.keyshortcuts() != null) {
json.addProperty("keyshortcuts", src.keyshortcuts());
}
if (src.roledescription() != null) {
json.addProperty("roledescription", src.roledescription());
}
if (src.valuetext() != null) {
json.addProperty("valuetext", src.valuetext());
}
if (src.disabled() != null) {
json.addProperty("disabled", src.disabled());
}
if (src.expanded() != null) {
json.addProperty("expanded", src.expanded());
}
if (src.focused() != null) {
json.addProperty("focused", src.focused());
}
if (src.modal() != null) {
json.addProperty("modal", src.modal());
}
if (src.multiline() != null) {
json.addProperty("multiline", src.multiline());
}
if (src.multiselectable() != null) {
json.addProperty("multiselectable", src.multiselectable());
}
if (src.readonly() != null) {
json.addProperty("readonly", src.readonly());
}
if (src.required() != null) {
json.addProperty("required", src.required());
}
if (src.selected() != null) {
json.addProperty("selected", src.selected());
}
if (src.level() != null) {
json.addProperty("level", src.level());
}
if (src.valuemin() != null) {
json.addProperty("valuemin", src.valuemin());
}
if (src.valuemax() != null) {
json.addProperty("valuemax", src.valuemax());
}
if (src.autocomplete() != null) {
json.addProperty("autocomplete", src.autocomplete());
}
if (src.haspopup() != null) {
json.addProperty("haspopup", src.haspopup());
}
if (src.invalid() != null) {
json.addProperty("invalid", src.invalid());
}
if (src.orientation() != null) {
json.addProperty("orientation", src.orientation());
}
if (src.checked() != null) {
json.addProperty("checked", src.checked().toString().toLowerCase());
}
if (src.pressed() != null) {
json.addProperty("pressed", src.pressed().toString().toLowerCase());
}
if (src.children() != null) {
JsonArray children = new JsonArray();
for (AccessibilityNode child : src.children()) {
children.add(context.serialize(child));
}
json.add("children", children);
}
return json;
}
static void assertNodeEquals(String expected, String actual) {
JsonElement actualJson = new Gson().fromJson(actual, JsonElement.class);
assertNodeEquals(expected, actualJson);
}
private static Gson gson = new GsonBuilder()
.registerTypeHierarchyAdapter(AccessibilityNode.class, new AccessibilityNodeSerializer()).create();
static void assertNodeEquals(String expected, AccessibilityNode actual) {
JsonElement actualJson = gson.toJsonTree(actual);
static void assertNodeEquals(String expected, JsonElement actualJson) {
assertEquals(JsonParser.parseString(expected), actualJson);
}
@ -193,45 +101,55 @@ public class TestAccessibility extends TestBase {
@Test
void shouldWorkWithRegularText() {
page.setContent("<div>Hello World</div>");
AccessibilityNode snapshot = page.accessibility().snapshot();
AccessibilityNode node = snapshot.children().get(0);
assertEquals(isFirefox() ? "text leaf" : "text", node.role());
assertEquals("Hello World", node.name());
JsonObject snapshot = new Gson().fromJson(page.accessibility().snapshot(), JsonObject.class);
JsonObject node = snapshot.getAsJsonArray("children").get(0).getAsJsonObject();
assertEquals(isFirefox() ? "text leaf" : "text", node.get("role").getAsString());
assertEquals("Hello World", node.get("name").getAsString());
}
@Test
void roledescription() {
page.setContent("<div tabIndex=-1 aria-roledescription='foo'>Hi</div>");
AccessibilityNode snapshot = page.accessibility().snapshot();
assertEquals("foo", snapshot.children().get(0).roledescription());
JsonObject snapshot = new Gson().fromJson(page.accessibility().snapshot(), JsonObject.class);
assertEquals("foo", snapshot.getAsJsonArray("children")
.get(0).getAsJsonObject()
.get("roledescription").getAsString());
}
@Test
void orientation() {
page.setContent("<a href='' role='slider' aria-orientation='vertical'>11</a>");
AccessibilityNode snapshot = page.accessibility().snapshot();
assertEquals("vertical", snapshot.children().get(0).orientation());
JsonObject snapshot = new Gson().fromJson(page.accessibility().snapshot(), JsonObject.class);
assertEquals("vertical", snapshot.getAsJsonArray("children")
.get(0).getAsJsonObject()
.get("orientation").getAsString());
}
@Test
void autocomplete() {
page.setContent("<div role='textbox' aria-autocomplete='list'>hi</div>");
AccessibilityNode snapshot = page.accessibility().snapshot();
assertEquals("list", snapshot.children().get(0).autocomplete());
JsonObject snapshot = new Gson().fromJson(page.accessibility().snapshot(), JsonObject.class);
assertEquals("list", snapshot.getAsJsonArray("children")
.get(0).getAsJsonObject()
.get("autocomplete").getAsString());
}
@Test
void multiselectable() {
page.setContent("<div role='grid' tabIndex=-1 aria-multiselectable=true>hey</div>");
AccessibilityNode snapshot = page.accessibility().snapshot();
assertEquals(true, snapshot.children().get(0).multiselectable());
JsonObject snapshot = new Gson().fromJson(page.accessibility().snapshot(), JsonObject.class);
assertEquals(true, snapshot.getAsJsonArray("children")
.get(0).getAsJsonObject()
.get("multiselectable").getAsBoolean());
}
@Test
void keyshortcuts() {
page.setContent("<div role='grid' tabIndex=-1 aria-keyshortcuts='foo'>hey</div>");
AccessibilityNode snapshot = page.accessibility().snapshot();
assertEquals("foo", snapshot.children().get(0).keyshortcuts());
JsonObject snapshot = new Gson().fromJson(page.accessibility().snapshot(), JsonObject.class);
assertEquals("foo", snapshot.getAsJsonArray("children")
.get(0).getAsJsonObject()
.get("keyshortcuts").getAsString());
}
@Test
@ -283,8 +201,8 @@ public class TestAccessibility extends TestBase {
" name: 'my fake image'\n" +
" }]\n" +
"}";
AccessibilityNode snapshot = page.accessibility().snapshot();
assertNodeEquals(golden, snapshot.children().get(0));
JsonObject snapshot = new Gson().fromJson(page.accessibility().snapshot(), JsonObject.class);
assertNodeEquals(golden, snapshot.getAsJsonArray("children").get(0));
}
@Test
@ -313,42 +231,42 @@ public class TestAccessibility extends TestBase {
" name: 'my fake image'\n" +
" }]\n" +
"}";
AccessibilityNode snapshot = page.accessibility().snapshot();
assertNodeEquals(golden, snapshot.children().get(0));
JsonObject snapshot = new Gson().fromJson(page.accessibility().snapshot(), JsonObject.class);
assertNodeEquals(golden, snapshot.getAsJsonArray("children").get(0));
}
@Test
@EnabledIf(value="com.microsoft.playwright.TestBase#isChromium", disabledReason="skip")
void plainTextFieldWithRoleShouldNotHaveChildren() {
page.setContent("<div contenteditable='plaintext-only' role='textbox'>Edit this image:<img src='fakeimage.png' alt='my fake image'></div>");
AccessibilityNode snapshot = page.accessibility().snapshot();
JsonObject snapshot = new Gson().fromJson(page.accessibility().snapshot(), JsonObject.class);
assertNodeEquals("{\n" +
" role: 'textbox',\n" +
" name: '',\n" +
" valueString: 'Edit this image:'\n" +
"}", snapshot.children().get(0));
"}", snapshot.getAsJsonArray("children").get(0));
}
@Test
@EnabledIf(value="com.microsoft.playwright.TestBase#isChromium", disabledReason="skip")
void plainTextFieldWithoutRoleShouldNotHaveContent() {
page.setContent("<div contenteditable='plaintext-only'>Edit this image:<img src='fakeimage.png' alt='my fake image'></div>");
AccessibilityNode snapshot = page.accessibility().snapshot();
JsonObject snapshot = new Gson().fromJson(page.accessibility().snapshot(), JsonObject.class);
assertNodeEquals("{\n" +
" role: 'generic',\n" +
" name: ''\n" +
"}", snapshot.children().get(0));
"}", snapshot.getAsJsonArray("children").get(0));
}
@Test
@EnabledIf(value="com.microsoft.playwright.TestBase#isChromium", disabledReason="skip")
void plainTextFieldWithTabindexAndWithoutRoleShouldNotHaveContent() {
page.setContent("<div contenteditable='plaintext-only' tabIndex=0>Edit this image:<img src='fakeimage.png' alt='my fake image'></div>");
AccessibilityNode snapshot = page.accessibility().snapshot();
JsonObject snapshot = new Gson().fromJson(page.accessibility().snapshot(), JsonObject.class);
assertNodeEquals("{\n" +
" role: 'generic',\n" +
" name: ''\n" +
"}", snapshot.children().get(0));
"}", snapshot.getAsJsonArray("children").get(0));
}
@Test
@ -370,8 +288,8 @@ public class TestAccessibility extends TestBase {
" name: 'my favorite textbox',\n" +
" valueString: 'this is the inner content '\n" +
"}";
AccessibilityNode snapshot = page.accessibility().snapshot();
assertNodeEquals(golden, snapshot.children().get(0));
JsonObject snapshot = new Gson().fromJson(page.accessibility().snapshot(), JsonObject.class);
assertNodeEquals(golden, snapshot.getAsJsonArray("children").get(0));
}
@Test
@ -385,8 +303,8 @@ public class TestAccessibility extends TestBase {
" name: 'my favorite checkbox',\n" +
" checked: 'checked'\n" +
"}";
AccessibilityNode snapshot = page.accessibility().snapshot();
assertNodeEquals(golden, snapshot.children().get(0));
JsonObject snapshot = new Gson().fromJson(page.accessibility().snapshot(), JsonObject.class);
assertNodeEquals(golden, snapshot.getAsJsonArray("children").get(0));
}
@Test
@ -404,8 +322,8 @@ public class TestAccessibility extends TestBase {
" name: 'this is the inner content yo',\n" +
" checked: 'checked'\n" +
"}";
AccessibilityNode snapshot = page.accessibility().snapshot();
assertNodeEquals(golden, snapshot.children().get(0));
JsonObject snapshot = new Gson().fromJson(page.accessibility().snapshot(), JsonObject.class);
assertNodeEquals(golden, snapshot.getAsJsonArray("children").get(0));
}
@Test
@ -470,20 +388,23 @@ public class TestAccessibility extends TestBase {
" </div>\n" +
"</div>");
ElementHandle root = page.querySelector("#root");
AccessibilityNode snapshot = page.accessibility().snapshot(
new Accessibility.SnapshotOptions().withRoot(root).withInterestingOnly(false));
assertEquals("textbox", snapshot.role());
assertTrue(snapshot.valueString().contains("hello"));
assertTrue(snapshot.valueString().contains("world"));
assertNotNull(snapshot.children());
JsonObject snapshot = new Gson().fromJson(page.accessibility().snapshot(
new Accessibility.SnapshotOptions().withRoot(root).withInterestingOnly(false)),
JsonObject.class);
assertEquals("textbox", snapshot.get("role").getAsString());
assertTrue(snapshot.get("valueString").getAsString().contains("hello"));
assertTrue(snapshot.get("valueString").getAsString().contains("world"));
assertNotNull(snapshot.get("children"));
}
@Test
void shouldWorkWhenThereIsATitle() {
page.setContent("<title>This is the title</title>\n" +
"<div>This is the content</div>");
AccessibilityNode snapshot = page.accessibility().snapshot();
assertEquals("This is the title", snapshot.name());
assertEquals("This is the content", snapshot.children().get(0).name());
JsonObject snapshot = new Gson().fromJson(page.accessibility().snapshot(), JsonObject.class);
assertEquals("This is the title", snapshot.get("name").getAsString());
assertEquals("This is the content", snapshot.getAsJsonArray("children")
.get(0).getAsJsonObject()
.get("name").getAsString());
}
}

View File

@ -1 +1 @@
1.9.0-next-1612316912000
1.9.0-next-1612383782000

View File

@ -143,7 +143,6 @@ class Types {
// Return structures
add("ConsoleMessage.location", "Object", "Location");
add("ElementHandle.boundingBox", "Object|null", "BoundingBox", new Empty());
add("Accessibility.snapshot", "Object|null", "AccessibilityNode", new Empty());
add("WebSocket.framereceived", "Object", "FrameData", new Empty());
add("WebSocket.framesent", "Object", "FrameData", new Empty());