Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions resources/testdriver.js
Original file line number Diff line number Diff line change
Expand Up @@ -1194,6 +1194,32 @@
return role;
},

/**
* Get accessibility properties for an element.
*
* @param {Element} element
* @returns {Promise} fulfilled after the accessibility properties are
* returned, or rejected in the cases the WebDriver
* command errors
*/
get_element_accessible_node: async function(element) {
let acc = await window.test_driver_internal.get_element_accessible_node(element);
return acc;
},

/**
* Get properties for an accessible node.
*
* @param {String} id
* @returns {Promise} fulfilled after the accessibility properties are
* returned, or rejected in the cases the WebDriver
* command errors
*/
get_accessible_node: async function(accId) {
let acc = await window.test_driver_internal.get_accessible_node(accId);
return acc;
},

/**
* Send keys to an element.
*
Expand Down Expand Up @@ -2370,6 +2396,14 @@
throw new Error("get_computed_name is a testdriver.js function which cannot be run in this context.");
},

async get_element_accessible_node(element) {
throw new Error("get_element_accessible_node is a testdriver.js function which cannot be run in this context.");
},

async get_accessible_node(accId) {
throw new Error("get_accessible_node is a testdriver.js function which cannot be run in this context.");
},

async send_keys(element, keys) {
if (this.in_automation) {
throw new Error("send_keys() is not implemented by testdriver-vendor.js");
Expand Down
29 changes: 29 additions & 0 deletions tools/wptrunner/wptrunner/executors/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,19 @@ def __call__(self, payload):
self.protocol.cookies.delete_all_cookies()


class GetAccessibleNodeAction:
name = "get_accessible_node"

def __init__(self, logger, protocol):
self.logger = logger
self.protocol = protocol

def __call__(self, payload):
id = payload["accId"]
self.logger.debug("Getting accessible node: %s" % id)
return self.protocol.accessibility.get_accessible_node(id)


class GetAllCookiesAction:
name = "get_all_cookies"

Expand Down Expand Up @@ -66,6 +79,20 @@ def __call__(self, payload):
return self.protocol.accessibility.get_computed_role(element)


class GetElementAccessibleNodeAction:
name = "get_element_accessible_node"

def __init__(self, logger, protocol):
self.logger = logger
self.protocol = protocol

def __call__(self, payload):
selector = payload["selector"]
element = self.protocol.select.element_by_selector(selector)
self.logger.debug("Getting accessible node for element: %s" % element)
return self.protocol.accessibility.get_element_accessible_node(element)


class GetNamedCookieAction:
name = "get_named_cookie"

Expand Down Expand Up @@ -600,6 +627,8 @@ def __call__(self, payload):
GetNamedCookieAction,
GetComputedLabelAction,
GetComputedRoleAction,
GetElementAccessibleNodeAction,
GetAccessibleNodeAction,
SendKeysAction,
MinimizeWindowAction,
SetWindowRectAction,
Expand Down
6 changes: 6 additions & 0 deletions tools/wptrunner/wptrunner/executors/executormarionette.py
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,12 @@ def get_computed_label(self, element):
def get_computed_role(self, element):
return element.computed_role

def get_element_accessible_node(self, element):
return element.accessible_node

def get_accessible_node(self, id):
return self.marionette.get_accessible_node(id)


class MarionetteVirtualSensorProtocolPart(VirtualSensorProtocolPart):
def setup(self):
Expand Down
12 changes: 12 additions & 0 deletions tools/wptrunner/wptrunner/executors/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,18 @@ def get_computed_role(self, element):
:param element: A protocol-specific handle to an element."""
pass

def get_element_accessible_node(self, element):
"""Return the accessibility properties for a specific element.

:param element: A protocol-specific handle to an element."""
pass

def get_accessible_node(self, id):
"""Return the properties for a specific accessible node.

:param id: The id of the accessible node."""
pass


class WebExtensionsProtocolPart(ProtocolPart):
"""Protocol part for managing WebExtensions"""
Expand Down
10 changes: 10 additions & 0 deletions tools/wptrunner/wptrunner/testdriver-extra.js
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,16 @@
return create_context_action("get_computed_role", context, {selector});
};

window.test_driver_internal.get_element_accessible_node = function(element) {
const selector = get_selector(element);
const context = get_context(element);
return create_context_action("get_element_accessible_node", context, {selector});
};

window.test_driver_internal.get_accessible_node = function(accId, context=null) {
return create_context_action("get_accessible_node", context, { accId });
};

window.test_driver_internal.get_named_cookie = function(name, context=null) {
return create_context_action("get_named_cookie", context, {name});
};
Expand Down
60 changes: 59 additions & 1 deletion wai-aria/scripts/aria-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,5 +192,63 @@ const AriaUtils = {
this.verifyLabelsBySelector(".ex-label-only", labelTestNamePrefix);
this.verifyRolesBySelector(".ex-role-only", roleTestNamePrefix);
},
};


/*
Asserts that the tree for a given accessible node matches the specified tree
structure.
This takes an accessible node and is not wrapped in a test. Most test files
will want to use verifyAccessibilityTree instead.
For example:
<div id="listbox" role="listbox" aria-label="listbox">
<div id="option1" role="option" aria-label="option1"></div>
<div id="option2" role="option" aria-label="option2"></div>
</div>
...
const listbox = await test_driver.get_element_accessible_node(document.getElementById("listbox"));
await AriaUtils.assertAccessibilityTree(listbox, {
role: "listbox",
label: "listbox",
children: [
{ role: "option", label: "option1", children: [] },
{ role: "option", label: "option2", children: [] },
],
}, "#listbox");
*/
assertAccessibilityTree: async function(acc, tree, position) {
for (const key in tree) {
if (key == "children") {
assert_equals(acc.children.length, tree.children.length, `${position} children.length`);
for (let c = 0; c < acc.children.length; ++c) {
const childId = acc.children[c];
const childAcc = await test_driver.get_accessible_node(childId);
await AriaUtils.assertAccessibilityTree(childAcc, tree.children[c], `${position}[${c}]`);
}
continue;
}
assert_equals(acc[key], tree[key], `${position} ${key}`);
}
},


/*
Verifies that the tree for a given accessible node matches the specified tree
structure.
This calls assertAccessibilityTree, but it accepts a CSS selector and wraps
the call in promise_test.
For example:
<div id="listbox"> ... </div>
...
AriaUtils.verifyAccessibilityTree("#listbox", { ... });
*/
verifyAccessibilityTree: function(selector, tree) {
const el = document.querySelector(selector);
if (!el) {
throw `selector passed to verifyAccessibilityTree("${selector}") doesn't match an element`;
}
promise_test(async t => {
const acc = await test_driver.get_element_accessible_node(el);
await AriaUtils.assertAccessibilityTree(acc, tree, selector);
}, `accessibility tree for ${selector}`);
},
};
59 changes: 59 additions & 0 deletions wai-aria/state/basic.tentative.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<!doctype html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="/resources/testdriver-actions.js"></script>

<div id="checkboxCheckedMissing" role="checkbox"></div>
<div id="checkboxCheckedTrue" role="checkbox" aria-checked="true"></div>
<div id="checkboxCheckedFalse" role="checkbox" aria-checked="false"></div>
<div id="checkboxCheckedMixed" role="checkbox" aria-checked="mixed"></div>
<div id="buttonPressedMissing" role="button"></div>
<div id="buttonPressedTrue" role="button" aria-pressed="true"></div>
<div id="buttonPressedFalse" role="button" aria-pressed="false"></div>
<div id="buttonPressedMixed" role="button" aria-pressed="mixed"></div>

<script>

promise_test(async t => {
const acc = await test_driver.get_element_accessible_node(document.getElementById("checkboxCheckedMissing"));
assert_equals(acc.checked, "false");
}, "checkbox with missing aria-checked");

promise_test(async t => {
const acc = await test_driver.get_element_accessible_node(document.getElementById("checkboxCheckedTrue"));
assert_equals(acc.checked, "true");
}, "checkbox with aria-checked true");

promise_test(async t => {
const acc = await test_driver.get_element_accessible_node(document.getElementById("checkboxCheckedFalse"));
assert_equals(acc.checked, "false");
}, "checkbox with aria-checked false");

promise_test(async t => {
const acc = await test_driver.get_element_accessible_node(document.getElementById("checkboxCheckedMixed"));
assert_equals(acc.checked, "mixed");
}, "checkbox with aria-checked mixed");

promise_test(async t => {
const acc = await test_driver.get_element_accessible_node(document.getElementById("buttonPressedMissing"));
assert_equals(acc.pressed, "undefined");
}, "button with missing aria-pressed");

promise_test(async t => {
const acc = await test_driver.get_element_accessible_node(document.getElementById("buttonPressedTrue"));
assert_equals(acc.pressed, "true");
}, "button with aria-pressed true");

promise_test(async t => {
const acc = await test_driver.get_element_accessible_node(document.getElementById("buttonPressedFalse"));
assert_equals(acc.pressed, "false");
}, "button with aria-pressed false");

promise_test(async t => {
const acc = await test_driver.get_element_accessible_node(document.getElementById("buttonPressedMixed"));
assert_equals(acc.pressed, "mixed");
}, "button with aria-pressed mixed");

</script>
39 changes: 39 additions & 0 deletions wai-aria/tree/basic.tentative.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<!doctype html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="/resources/testdriver-actions.js"></script>
<script src="/wai-aria/scripts/aria-utils.js"></script>

<div id="listbox" role="listbox" aria-label="listbox">
<div id="option1" role="option" aria-label="option1"></div>
<div id="option2" role="option" aria-label="option2"></div>
</div>

<script>

promise_test(async t => {
const listbox = await test_driver.get_element_accessible_node(document.getElementById("listbox"));
assert_equals(listbox.children.length, 2);
const option1 = await test_driver.get_accessible_node(listbox.children[0]);
assert_equals(option1.id, listbox.children[0]);
assert_equals(option1.parent, listbox.id);
assert_equals(option1.role, "option");
assert_equals(option1.label, "option1");
const option2 = await test_driver.get_accessible_node(listbox.children[1]);
assert_equals(option2.id, listbox.children[1]);
assert_equals(option2.parent, listbox.id);
assert_equals(option2.label, "option2");
}, "listbox explicit tree");

AriaUtils.verifyAccessibilityTree("#listbox", {
role: "listbox",
label: "listbox",
children: [
{ role: "option", label: "option1", children: [] },
{ role: "option", label: "option2", children: [] },
],
});

</script>
Loading