Skip to content

Commit

Permalink
Extend testdriver to add accessibility API testing
Browse files Browse the repository at this point in the history
  • Loading branch information
spectranaut committed May 15, 2024
1 parent 6198cae commit 5fbd9d7
Show file tree
Hide file tree
Showing 14 changed files with 186 additions and 8 deletions.
18 changes: 18 additions & 0 deletions core-aam/acacia/test-testdriver.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!doctype html>
<meta charset=utf-8>
<title>core-aam: acacia test using testdriver</title>
<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>

<body>
<div id=testtest role="button"></div>
<script>
promise_test(async t => {
const node = await test_driver.get_accessibility_api_node('testtest');
assert_equals(node.role, 'push button');
}, 'An acacia test');
</script>
</body>
16 changes: 16 additions & 0 deletions resources/testdriver.js
Original file line number Diff line number Diff line change
Expand Up @@ -1066,6 +1066,18 @@
*/
clear_device_posture: function(context=null) {
return window.test_driver_internal.clear_device_posture(context);
},

/**
* Get a serialized object representing the accessibility API's accessibility node.
*
* @param {id} id of element
* @returns {Promise} Fullfilled with object representing accessibilty node,
* rejected in the cases of failures.
*/
get_accessibility_api_node: async function(dom_id) {
let jsonresult = await window.test_driver_internal.get_accessibility_api_node(dom_id);
return JSON.parse(jsonresult);
}
};

Expand Down Expand Up @@ -1254,6 +1266,10 @@

async clear_device_posture(context=null) {
throw new Error("clear_device_posture() is not implemented by testdriver-vendor.js");
},

async get_accessibility_api_node(dom_id) {
throw new Error("not implemented, whoops!");
}
};
})();
4 changes: 4 additions & 0 deletions tools/webdriver/webdriver/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -879,6 +879,10 @@ def get_computed_label(self):
def get_computed_role(self):
return self.send_element_command("GET", "computedrole")

@command
def get_accessibility_api_node(self):
return self.send_element_command("GET", "accessibilityapinode")

# This MUST come last because otherwise @property decorators above
# will be overridden by this.
@command
Expand Down
3 changes: 2 additions & 1 deletion tools/wpt/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,8 @@ def setup_kwargs(self, kwargs):
# We are on Taskcluster, where our Docker container does not have
# enough capabilities to run Chrome with sandboxing. (gh-20133)
kwargs["binary_args"].append("--no-sandbox")

if kwargs["force_renderer_accessibility"]:
kwargs["binary_args"].append("--force-renderer-accessibility")

class ContentShell(BrowserSetup):
name = "content_shell"
Expand Down
3 changes: 2 additions & 1 deletion tools/wptrunner/wptrunner/browsers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,8 @@ def executor_browser(self) -> Tuple[Type[ExecutorBrowser], Mapping[str, Any]]:
"host": self.host,
"port": self.port,
"pac": self.pac,
"env": self.env}
"env": self.env,
"pid": self.pid}

def settings(self, test: Test) -> BrowserSettings:
self._pac = test.environment.get("pac", None) if self._supports_pac else None
Expand Down
15 changes: 14 additions & 1 deletion tools/wptrunner/wptrunner/executors/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,18 @@ def __init__(self, logger, protocol):
def __call__(self, payload):
return self.protocol.device_posture.clear_device_posture()

class GetAccessibilityAPINodeAction:
name = "get_accessibility_api_node"

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

def __call__(self, payload):
dom_id = payload["dom_id"]
return self.protocol.platform_accessibility.get_accessibility_api_node(dom_id)


actions = [ClickAction,
DeleteAllCookiesAction,
GetAllCookiesAction,
Expand Down Expand Up @@ -499,4 +511,5 @@ def __call__(self, payload):
RemoveVirtualSensorAction,
GetVirtualSensorInformationAction,
SetDevicePostureAction,
ClearDevicePostureAction]
ClearDevicePostureAction,
GetAccessibilityAPINodeAction]
91 changes: 91 additions & 0 deletions tools/wptrunner/wptrunner/executors/executoracacia.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import acacia_atspi
import json
from .protocol import (PlatformAccessibilityProtocolPart)

# When running against chrome family browser:
# self.parent is WebDriverProtocol
# self.parent.webdriver is webdriver

def findActiveTab(root):
stack = [root]
while stack:
node = stack.pop()

if node.getRoleName() == 'frame':
relations = node.getRelations()
if 'ATSPI_RELATION_EMBEDS' in relations:
index = relations.index('ATSPI_RELATION_EMBEDS')
target = node.getTargetForRelationAtIndex(index)
print(target.getRoleName())
print(target.getName())
return target
continue

for i in range(node.getChildCount()):
child = node.getChildAtIndex(i)
stack.append(child)

return None

def serialize_node(node):
node_dictionary = {}
node_dictionary['role'] = node.getRoleName()
node_dictionary['name'] = node.getName()
node_dictionary['description'] = node.getDescription()
node_dictionary['states'] = sorted(node.getStates())
node_dictionary['interfaces'] = sorted(node.getInterfaces())
node_dictionary['attributes'] = sorted(node.getAttributes())

# TODO: serialize other attributes

return node_dictionary

def find_node(root, dom_id):
stack = [root]
while stack:
node = stack.pop()

attributes = node.getAttributes()
for attribute_pair in attributes:
[attribute, value] = attribute_pair.split(':', 1)
if attribute == 'id':
if value == dom_id:
return node

for i in range(node.getChildCount()):
child = node.getChildAtIndex(i)
stack.append(child)

return None

class AcaciaPlatformAccessibilityProtocolPart(PlatformAccessibilityProtocolPart):
def setup(self):
self.product_name = self.parent.product_name
self.root = None
self.errormsg = None

self.root = acacia_atspi.findRootAtspiNodeForName(self.product_name);
if self.root.isNull():
error = f"Cannot find root accessibility node for {self.product_name} - did you turn on accessibility?"
print(error)
self.errormsg = error


def get_accessibility_api_node(self, dom_id):
if self.root.isNull():
return json.dumps({"role": self.errormsg})

active_tab = findActiveTab(self.root)

# This will fail sometimes when accessibilty is off.
if not active_tab or active_tab.isNull():
return json.dumps({"role": "couldn't find active tab"})

# This fails sometimes for unknown reasons.
node = find_node(active_tab, dom_id)
if not node or node.isNull():
return json.dumps({"role": "couldn't find the node with that ID"})

return json.dumps(serialize_node(node))


6 changes: 5 additions & 1 deletion tools/wptrunner/wptrunner/executors/executormarionette.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
WdspecExecutor,
get_pages,
strip_server)

from .protocol import (AccessibilityProtocolPart,
ActionSequenceProtocolPart,
AssertsProtocolPart,
Expand All @@ -48,6 +49,7 @@
DevicePostureProtocolPart,
merge_dicts)

from .executoracacia import (AcaciaPlatformAccessibilityProtocolPart)

def do_delayed_imports():
global errors, marionette, Addons, WebAuthn
Expand Down Expand Up @@ -782,12 +784,14 @@ class MarionetteProtocol(Protocol):
MarionetteDebugProtocolPart,
MarionetteAccessibilityProtocolPart,
MarionetteVirtualSensorProtocolPart,
MarionetteDevicePostureProtocolPart]
MarionetteDevicePostureProtocolPart,
AcaciaPlatformAccessibilityProtocolPart]

def __init__(self, executor, browser, capabilities=None, timeout_multiplier=1, e10s=True, ccov=False):
do_delayed_imports()

super().__init__(executor, browser)
self.product_name = browser.product_name
self.marionette = None
self.marionette_port = browser.marionette_port
self.capabilities = capabilities
Expand Down
6 changes: 5 additions & 1 deletion tools/wptrunner/wptrunner/executors/executorwebdriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
DevicePostureProtocolPart,
merge_dicts)

from .executoracacia import (AcaciaPlatformAccessibilityProtocolPart)

from webdriver.client import Session
from webdriver import error

Expand Down Expand Up @@ -462,10 +464,12 @@ class WebDriverProtocol(Protocol):
WebDriverFedCMProtocolPart,
WebDriverDebugProtocolPart,
WebDriverVirtualSensorPart,
WebDriverDevicePostureProtocolPart]
WebDriverDevicePostureProtocolPart,
AcaciaPlatformAccessibilityProtocolPart]

def __init__(self, executor, browser, capabilities, **kwargs):
super().__init__(executor, browser)
self.product_name = browser.product_name
self.capabilities = capabilities
if hasattr(browser, "capabilities"):
if self.capabilities is None:
Expand Down
14 changes: 14 additions & 0 deletions tools/wptrunner/wptrunner/executors/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,20 @@ def get_computed_role(self, element):
pass


class PlatformAccessibilityProtocolPart(ProtocolPart):
"""Protocol part for platform accessibility introspection"""
__metaclass__ = ABCMeta

name = "platform_accessibility"

@abstractmethod
def get_accessibility_api_node(self, dom_id):
"""Return the the platform accessibilty object.
:param id: DOM ID."""
pass


class CookiesProtocolPart(ProtocolPart):
"""Protocol part for managing cookies"""
__metaclass__ = ABCMeta
Expand Down
4 changes: 4 additions & 0 deletions tools/wptrunner/wptrunner/testdriver-extra.js
Original file line number Diff line number Diff line change
Expand Up @@ -335,4 +335,8 @@
window.test_driver_internal.clear_device_posture = function(context=null) {
return create_action("clear_device_posture", {context});
};

window.test_driver_internal.get_accessibility_api_node = function(dom_id) {
return create_action("get_accessibility_api_node", {dom_id});
};
})();
11 changes: 8 additions & 3 deletions tools/wptrunner/wptrunner/testrunner.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ def __init__(self, suite_name, index, test_queue,
test_implementations, stop_flag, retry_index=0, rerun=1,
pause_after_test=False, pause_on_unexpected=False,
restart_on_unexpected=True, debug_info=None,
capture_stdio=True, restart_on_new_group=True, recording=None, max_restarts=5):
capture_stdio=True, restart_on_new_group=True, recording=None, max_restarts=5, product_name=None):
"""Thread that owns a single TestRunner process and any processes required
by the TestRunner (e.g. the Firefox binary).
Expand All @@ -332,6 +332,7 @@ def __init__(self, suite_name, index, test_queue,
self.suite_name = suite_name
self.manager_number = index
self.test_implementation_key = None
self.product_name = product_name

self.test_implementations = {}
for key, test_implementation in test_implementations.items():
Expand Down Expand Up @@ -594,6 +595,7 @@ def start_test_runner(self):
self.executor_kwargs["group_metadata"] = self.state.group_metadata
self.executor_kwargs["browser_settings"] = self.browser.browser_settings
executor_browser_cls, executor_browser_kwargs = self.browser.browser.executor_browser()
executor_browser_kwargs["product_name"] = self.product_name

args = (self.remote_queue,
self.command_queue,
Expand Down Expand Up @@ -984,8 +986,10 @@ def __init__(self, suite_name, test_queue_builder, test_implementations,
capture_stdio=True,
restart_on_new_group=True,
recording=None,
max_restarts=5):
max_restarts=5,
product_name=None):
self.suite_name = suite_name
self.product_name = product_name
self.test_queue_builder = test_queue_builder
self.test_implementations = test_implementations
self.pause_after_test = pause_after_test
Expand Down Expand Up @@ -1031,7 +1035,8 @@ def run(self, tests):
self.capture_stdio,
self.restart_on_new_group,
recording=self.recording,
max_restarts=self.max_restarts)
max_restarts=self.max_restarts,
product_name=self.product_name)
manager.start()
self.pool.add(manager)
self.wait()
Expand Down
2 changes: 2 additions & 0 deletions tools/wptrunner/wptrunner/wptcommandline.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,8 @@ def create_parser(product_choices=None):
chrome_group.add_argument("--no-enable-experimental", action="store_false", dest="enable_experimental",
help="Do not enable --enable-experimental-web-platform-features flag "
"on experimental channels")
chrome_group.add_argument( "--force-renderer-accessibility", action="store_true",
dest="force_renderer_accessibility",help="Turn on accessibility.")
chrome_group.add_argument(
"--enable-sanitizer",
action="store_true",
Expand Down
1 change: 1 addition & 0 deletions tools/wptrunner/wptrunner/wptrunner.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ def run_test_iteration(test_status, test_loader, test_queue_builder,
kwargs["restart_on_new_group"],
recording=recording,
max_restarts=kwargs["max_restarts"],
product_name=product.name
) as manager_group:
try:
handle_interrupt_signals()
Expand Down

0 comments on commit 5fbd9d7

Please sign in to comment.