Skip to content

Explainer: WebDriver Extension for Accessible Nodes, etc. (potential solution for #197) #203

@cookiecrook

Description

@cookiecrook

Rather than muddy the problem issue #197 with a specific proposed solution, I'm posting this as a standalone issue. Ideally we could turn this Issue into an Explainer and eventually a Spec, but the goal is to get wider approval of the idea first, during a few meetings at TPAC 2023 Sept 11–15 in Spain.

Note: I will be editing this problem description, so expect changes.

Note on WebDriver-BiDi

This explainer does not use BiDi examples, but we don't anticipate problems converting to the other format and welcome accessibility additions to Classic and/or BiDi. It's been suggested that this be added to the BiDi roadmap.

Current State of Cross-Browser Web Accessibility Testing

Existing WebDriver accessibility testing methods go through DOM Element, to AX Element, then to its label or role.

get a string value from the backing accessibility object (if it exists) of a given DOM element

session/{session_id}/element/{element_id}/computedrole
session/{session_id}/element/{element_id}/computedlabel

In 2023, we added over 1000 automated accessibility tests to the WPT Interop 2023 Accessibility Investigation using the above two WebDriver methods, but there is so much more to test, and no way available to test it in WPT/WebDriver.

Potential Changes

See also: #197
…a new WebDriver accessibility extension might look something like this:


1. Way to access the backing "accessible node" of a DOM element (if one exists).

Note

Only one of the following two accessors 👇 are needed, not both

get accessible node from its mainstream DOM element (if one exists)

EITHER a new method in a new accessibility-specific webdriver extension.

session/{session_id}/accessibility/element/{elId}/accessiblenode
#                    ^^^^^^^^^^^^^

OR a new method on the existing webdriver element interface.

session/{session_id}/element/{elId}/accessiblenode

Note

Only one of the preceding two accessors 👆 are needed, not both. Currently prototyping option 2.


2. Way to access an "accessible node" by its WebDriver ID directly (e.g. you may receive this ID from a parent/child cross-reference).

Regardless if an accessible node is associated with a DOM element (some are not), once you already have the accessible node id:

get accessible node by its WebDriver ID

session/{session_id}/accessibility/node/{axId}

3. Way to Trigger an Accessibility Event/Notification.

We also need a way to trigger a notification on the accessibility object, too.

session/{session_id}/accessibility/node/{axId}/synthesizeevent

Note

synthesizeevent is just a draft name. Very open to change on every aspect of this.

Common Events

Click/Press

where the minimum payload is the notification type (e.g. a screen reader “click” would fire):

{ "type": "press" } 

Explanation: “AX Press” almost always results in a DOM “click” but the event object on a “press on AX object” event can end up very different from a “click on DOM element.” For example:

  • Event target can be different with leaf nodes inside the interactive (e.g. a span in a button that intercepted the mouse event, versus the interactive itself, that the AT "cursor" is on... there are a number of known and unknown implementation differences here.)
  • Event timing can be different (e.g. mouseup and mousedown possibly in the same or adjacent event loops, which is unlikely for mechanical mouse or trackpad users.)
  • Other event properties can be different... Often these also allow some heuristic detection surface between AT users and mainstream others. See Several core architectural features of the Web Platform may allow heuristic detectability of assistive technology w3ctag/design-principles#293
  • See also: WebDriver’s element click intercepted error code; relevant because an accessibility click can bypass pointer hit point obscuration.
AT Focus (pulls keyboard focus if the element is focusable)

AT Focus should be verifiable, b/c it will pull standard keyboard focus along with it, if the AT focused elements is keyboard focusable.

{ "type": "focus" } 

Other Events/Notifications

Trigger “Action” (lower priority for v1/MVP)

It could also be used for non-default “actions” (e.g. trigger the associated “reply” action):

{
  "type": "action",
  "label": "reply" /* possible this should use something other than the translatable user string label */
} 

This one 👆 has native precedent, but the proposed Web API hasn’t yet shipped, so it may be lower priority.

Scroll into view a.k.a. “scroll to visible” (lower priority for v1/MVP)

This might not be needed as it’s usually called downstream from focus, rather than directly from AT.

{ "type": "scrollToVisible" } 
Show Menu (lower priority for v1/MVP)

Show menu (VO and other AT’s equivalent to show the “right-click” menu). This sometimes results in a different AT-vs-mainstream behavior when web site has overridden the “right-click” mouse behavior.

{ "type": "showMenu" } 

I don’t know how if “showMenu” would be interoperable on other systems, but it’s in WebKit because Mac VO and other AT support it. I assume Windows has something similar.


4. Test-Only (WebDriver-only for now?) Interface for accessible node.

Return value for the accessibleNode would be a static snap shot of the element at the time of the request:

  • including attributes like
    • checked
    • selected
    • label (equiv to el.computedlabel)
    • role (equiv to el.computedrole)
    • etc.
  • including ID references to parent and children in the accessibility tree, as well as the element ID of the mainstream DOM element (if there is one)
  • probably including relationships to other elements (label/for, aria-controls, etc.)
  • See more perf and implementation discussion points below.

Example return object for accessible node getter.

{ 
  "domnode": "<webdriver_dom_id>", /* optional, as not all axnodes will have domnodes, and vice versa */
  "label": "First Name", /* equivalent to /session/{sId}/element/{eId}/computedlabel */
  "role": "textbox", /* equivalent to /session/{sId}/element/{eId}/computedrole */
  "parent": "<parent_id>", /* WebDriver accessibleNode IDs not DOM IDs, */
  "children": ["<child_1_id>", "<child_2_id>", "<child_n_id>" ], /* ditto */
  "checked": undefined, /* checked n/a on text fields, perhaps ommitted in this returned object? */
  "required": "true", /* from `required` or `aria-required` attrs */
  "…": "…" /* dozens more accessibility relevant properties… */
}

Discussion Points

Getter Interface for ~“accessibleNode”

There’s a balance between whether to return a limited scope of known things to query" or to return "over-expose” as much as possible about the backing accessible object… Some relationships or properties are costly or slow to return, so we’ll probably need to start with a subset of the things that all implementations can return reasonably quickly.

Perhaps multiple getters: a default set of the easy ones (role, label, required, checked, yadda, yadda) and then we don’t include the ones with a significant perf cost or other complications unless requested specifically.

  1. /session/{sID}/accessibility/~ax_element/{axID} for defaults
  2. /session/{sID}/accessibility/~ax_element/{axID}/~full for everything
  3. /session/{sID}/accessibility/~ax_element/{axID}/~partial for a specific set, with an array of keys in the post payload

Note

Note that ~ above indicates TBD draft name proposals... Open to changes, of course.

Object/Node Persistance

API should be clear that Accessibility Objects/Nodes are not expected to persist once removed from the accessibility tree. Though this may be possible in some implementations, it is unlikely to be readily achievable in all implementations, so:

  • there is no expectation that a hidden or ignored element, for example, will return a backing accessibility object
  • when a DOM element is hidden and then re-displayed, there is no expectation that the current accessibility object bears any relationship to the backing accessibility object that existed before the DOM element was hidden. IOW, the WebDriver ID may be different, and there may be no way to reconcile the accessible object other than the earlier (now destroyed) object and the current object both reference the same DOM element WebDriver ID.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions