Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
23c31dd
Create new test type for accessibility API testing
spectranaut May 1, 2024
c791be1
Fix mac tests
spectranaut Feb 17, 2026
1397c27
Fix windows
spectranaut Feb 18, 2026
ead41ea
Refactor atspi_wrapper and fix flake
spectranaut Feb 19, 2026
cd87468
Refactor wrappers, formatting
spectranaut Feb 19, 2026
60e2296
Enable firefox caching and don't poll for id
spectranaut Feb 20, 2026
29ba134
Add back polling for firefox
spectranaut Feb 20, 2026
9794151
Use test result 'PRECONDITION_FAILED' for tests on wrong platform
spectranaut Feb 26, 2026
275d1e9
Update Atspi role inspection
spectranaut Mar 2, 2026
2584f20
Revert "Add back polling for firefox"
spectranaut Mar 2, 2026
83d86ee
Correct mac blockquote assertion
spectranaut Mar 2, 2026
a3e79db
Use proper pytest parameterizing norms
spectranaut Mar 3, 2026
e3cbcb9
In AXAPI: use the PID if it exists
spectranaut Mar 4, 2026
3a39f17
Code review comments
spectranaut Mar 4, 2026
f6d57f4
Move tests to core-aam/aamtests
spectranaut Mar 16, 2026
df4425c
Remove old wai-aria-aam test dir
spectranaut Mar 16, 2026
d09af1d
Put aamtest specific env vars in browser_kwargs
spectranaut Mar 17, 2026
56d8a8a
Code review from ms2ger
spectranaut Mar 19, 2026
e4d051d
Remove tests, adding in seperate PR
spectranaut Mar 20, 2026
10d1788
Remove unnecessary __init__.py
spectranaut Mar 20, 2026
2919686
Add initial role and attribute tests
spectranaut Mar 20, 2026
5bdfd24
More IA2 directory to third_party_modified
spectranaut Mar 30, 2026
f802530
Fix import names
spectranaut Mar 30, 2026
2870ea6
Apply suggestion from @spectranaut
spectranaut Mar 31, 2026
e2ebe8a
Add location to classic session fixture
spectranaut Mar 31, 2026
401342e
Delete unnecessary file
spectranaut Apr 7, 2026
175f1f9
Instead of PRECONDITION_FAILED, skip not applicable tests
spectranaut Apr 7, 2026
6226edd
Move iaccessible2 folder our of third_party_modified
spectranaut Apr 16, 2026
261aa30
Apply suggestions from code review
spectranaut Apr 16, 2026
d03dec5
Code review comments from Jonathan Lee
spectranaut Apr 16, 2026
5da0d0f
More fixes
spectranaut Apr 16, 2026
b86acbd
Rename shared Wdspec* classes to Pytest*
spectranaut Apr 16, 2026
f89b949
Add back polling for firefox
spectranaut Apr 27, 2026
fe32b5d
Minor comments/requests from cookiecrook
spectranaut Apr 27, 2026
a2d6005
Add documentation for aamtest test type (#58632)
spectranaut Apr 27, 2026
b23715c
Revert "Add back polling for firefox"
spectranaut Apr 27, 2026
04e9829
Update docs with suggestion from cookiecrook
spectranaut Apr 28, 2026
d4fca40
Fix tables and links
spectranaut Apr 28, 2026
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
2 changes: 1 addition & 1 deletion .taskcluster.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ tasks:
owner: ${owner}
source: ${event.repository.clone_url}
payload:
image: ghcr.io/web-platform-tests/wpt:2
image: ghcr.io/web-platform-tests/wpt:3
maxRunTime: 7200
artifacts:
public/results:
Expand Down
Empty file added core-aam/aamtests/__init__.py
Empty file.
33 changes: 33 additions & 0 deletions core-aam/aamtests/attribute/aria_autocomplete_inline_list_both.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import pytest

# Testing: https://w3c.github.io/core-aam/#ariaAutocompleteInlineListBoth

TEST_HTML = {
"both": "<input role='combobox' id='test' aria-autocomplete='both'>",
"inline": "<input role='combobox' id='test' aria-autocomplete='inline'>",
"list": "<input role='combobox' id='test' aria-autocomplete='list'>",
}

@pytest.mark.parametrize("test_value,test_html", TEST_HTML.items(), ids=TEST_HTML.keys())
def test_atspi(atspi, session, inline, test_value, test_html):
session.url = inline(test_html)

# Spec:
# Object Attribute: autocomplete:<value>
# State: STATE_SUPPORTS_AUTOCOMPLETION

node = atspi.find_node("test", session.url)
assert f"autocomplete:{test_value}" in atspi.Accessible.get_attributes_as_array(node)
assert "STATE_SUPPORTS_AUTOCOMPLETION" in atspi.get_state_list_helper(node)

# Intentionally no AX API test. AX API does not surface this attribute.

# @pytest.mark.parametrize("test_value,test_html", TEST_HTML.items(), ids=TEST_HTML.keys())
# def test_ia2(ia2, session, inline, test_value, test_html):
# session.url = inline(test_html)
#
# # Spec:
# # Object Attribute: autocomplete:<value>
# # State: IA2_STATE_SUPPORTS_AUTOCOMPLETION

# Intentionally no UIA test. UIA does not surface this attribute.
30 changes: 30 additions & 0 deletions core-aam/aamtests/attribute/aria_braillelabel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Testing: https://w3c.github.io/core-aam/#ariaBraillelabel

TEST_HTML = "<button id='test' aria-braillelabel='foobar'> </button>"

def test_atspi(atspi, session, inline):
session.url = inline(TEST_HTML)

# Spec:
# Object Attribute: braillelabel:<value>

node = atspi.find_node("test", session.url)
assert "braillelabel:foobar" in atspi.Accessible.get_attributes_as_array(node)

# def test_axapi(axapi, session, inline):
# session.url = inline(TEST_HTML)
#
# # Spec:
# # AXBrailleLabel: <value>

# def test_ia2(ia2, session, inline):
# session.url = inline(TEST_HTML)
#
# # Spec:
# # Object Attribute: braillelabel:<value>

# def test_uia(uia, session, inline):
# session.url = inline(TEST_HTML)
#
# # Spec:
# # Property: AriaProperties.braillelabel: <value>
39 changes: 39 additions & 0 deletions core-aam/aamtests/attribute/aria_error_message.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Testing: https://w3c.github.io/core-aam/#ariaErrorMessage

TEST_HTML = "<div role='checkbox' id='test' aria-errormessage='error' aria-invalid='true'>content</div> <div id='error'>hello world</div>"

def test_atspi(atspi, session, inline):
session.url = inline(TEST_HTML)

# Spec:
# Relation: RELATION_ERROR_MESSAGE: points to accessible nodes matching IDREFs, if the referenced objects are in the accessibility tree
# Reverse Relation: RELATION_ERROR_FOR: points to element

node = atspi.find_node("test", session.url)
relations = atspi.get_relations_dictionary_helper(node)
assert 'RELATION_ERROR_MESSAGE' in relations
assert 'error' in relations['RELATION_ERROR_MESSAGE']
reverse_node = atspi.find_node('error', session.url)
reverse_relations = atspi.get_relations_dictionary_helper(reverse_node)
assert 'RELATION_ERROR_FOR' in reverse_relations
assert 'test' in reverse_relations['RELATION_ERROR_FOR']

# def test_axapi(axapi, session, inline):
# session.url = inline(TEST_HTML)
#
# # Spec:
# # Property: AXErrorMessageElements: pointers to accessible nodes matching IDREFs

# def test_ia2(ia2, session, inline):
# session.url = inline(TEST_HTML)
#
# # Spec:
# # Relation: IA2_RELATION_ERROR: points to accessible nodes matching IDREFs, if the referenced objects are in the accessibility tree
# # Reverse Relation: IA2_RELATION_ERROR_FOR: points to element
# # See also: Mapping Additional Relations

# def test_uia(uia, session, inline):
# session.url = inline(TEST_HTML)
#
# # Spec:
# # Property: ControllerFor: pointer to the target accessible object
12 changes: 12 additions & 0 deletions core-aam/aamtests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import sys
from pathlib import Path

# Add webdriver to the Python path so we can import the fixtures
webdriver_tests_path = Path(__file__).parent.parent.parent / "webdriver"
sys.path.insert(0, str(webdriver_tests_path))

pytest_plugins = (
"tests.support.fixtures",
"tests.support.classic.fixtures",
"aamtests.support.fixtures_a11y_api",
)
45 changes: 45 additions & 0 deletions core-aam/aamtests/role/blockquote.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Testing: https://w3c.github.io/core-aam/#role-map-blockquote

TEST_HTML = "<div role='blockquote' id='test'>content</div>"

def test_atspi(atspi, session, inline):
session.url = inline(TEST_HTML)

# Spec:
# Role: ROLE_BLOCK_QUOTE

node = atspi.find_node("test", session.url)
assert atspi.Accessible.get_role(node) == atspi.Role.BLOCK_QUOTE

def test_axapi(axapi, session, inline):
session.url = inline(TEST_HTML)

# Spec:
# AXRole: AXGroup
# AXSubrole: <nil>

node = axapi.find_node("test", session.url)
role = axapi.AXUIElementCopyAttributeValue(node, "AXRole", None)[1]
assert role == "AXGroup"
role = axapi.AXUIElementCopyAttributeValue(node, "AXSubrole", None)[1]
assert role == "AXUnknown"

def test_ia2(ia2, session, inline):
session.url = inline(TEST_HTML)

# Spec:
# Role: ROLE_SYSTEM_GROUPING
# Role: IA2_ROLE_BLOCK_QUOTE

node = ia2.find_node("test", session.url)
assert ia2.get_role(node) == "IA2_ROLE_BLOCK_QUOTE"
assert ia2.get_msaa_role(node) == "ROLE_SYSTEM_GROUPING"


# def test_uia(uia, session, inline):
# session.url = inline(TEST_HTML)
# node = uia.find_node("test", session.url)
#
# # Spec:
# # Control Type: Group
# # Localized Control Type: blockquote
42 changes: 42 additions & 0 deletions core-aam/aamtests/role/button.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import pytest

# Testing: https://w3c.github.io/core-aam/#role-map-button

TEST_HTML = {
"no-attributes": "<div id=test role=button>click me</div>",
"aria-pressed-undefined": "<div id=test role=button aria-pressed>click me</div>",
"aria-haspopup-undefined": "<div id=test role=button aria-haspopup>click me</div>",
"aria-haspopup-false": "<div id=test role=button aria-haspopup=false>click me</div>",
}

@pytest.mark.parametrize("test_html", TEST_HTML.values(), ids=TEST_HTML.keys())
def test_atspi(atspi, session, inline, test_html):
Comment thread
spectranaut marked this conversation as resolved.
session.url = inline(test_html)

# Spec:
# Role: ROLE_PUSH_BUTTON

node = atspi.find_node("test", session.url)
assert atspi.Accessible.get_role(node) == atspi.Role.PUSH_BUTTON

# @pytest.mark.parametrize("test_html", TEST_HTML.values(), ids=TEST_HTML.keys())
# def test_axapi(axapi, session, inline):
# session.url = inline(test_html)
#
# # Spec:
# # AXRole: AXButton
# # AXSubrole: <nil>

# @pytest.mark.parametrize("test_html", TEST_HTML.values(), ids=TEST_HTML.keys())
# def test_ia2(ia2, session, inline):
# session.url = inline(test_html)
#
# # Spec:
# # Role: ROLE_SYSTEM_PUSHBUTTON

# @pytest.mark.parametrize("test_html", TEST_HTML.values(), ids=TEST_HTML.keys())
# def test_uia(uia, session, inline):
# session.url = inline(test_html)
#
# # Spec:
# # Control Type: Button
44 changes: 44 additions & 0 deletions core-aam/aamtests/role/button_haspopup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import pytest

# Testing: https://w3c.github.io/core-aam/#role-map-button-haspopup

TEST_HTML = {
"true": "<div id=test role=button aria-haspopup=true>click me</div>",
"menu": "<div id=test role=button aria-haspopup=menu>click me</div>",
"listbox": "<div id=test role=button aria-haspopup=listbox>click me</div>",
"tree": "<div id=test role=button aria-haspopup=tree>click me</div>",
"grid": "<div id=test role=button aria-haspopup=grid>click me</div>",
"dialog": "<div id=test role=button aria-haspopup=dialog>click me</div>"
}

@pytest.mark.parametrize("test_html", TEST_HTML.values(), ids=TEST_HTML.keys())
def test_atspi(atspi, session, inline, test_html):
session.url = inline(test_html)

# Spec:
# Role: ROLE_PUSH_BUTTON

node = atspi.find_node("test", session.url)
assert atspi.Accessible.get_role(node) == atspi.Role.PUSH_BUTTON

# @pytest.mark.parametrize("test_html", TEST_HTML.values(), ids=TEST_HTML.keys())
# def test_axapi(axapi, session, inline, test_html):
# session.url = inline(test_html)
#
# # Spec:
# # AXRole: AXPopUpButton
# # AXSubrole: <nil>

# @pytest.mark.parametrize("test_html", TEST_HTML.values(), ids=TEST_HTML.keys())
# def test_ia2(ia2, session, inline, test_html):
# session.url = inline(test_html)
#
# # Spec:
# # Role: ROLE_SYSTEM_BUTTONMENU

# @pytest.mark.parametrize("test_html", TEST_HTML.values(), ids=TEST_HTML.keys())
# def test_uia(uia, session, inline, test_html):
# session.url = inline(test_html)
#
# # Spec:
# # Control Type: Button
41 changes: 41 additions & 0 deletions core-aam/aamtests/role/button_pressed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import pytest

# Testing: https://w3c.github.io/core-aam/#role-map-button-pressed

TEST_HTML = {
"true": "<div id=test role=button aria-pressed=true>press me</div>",
"false": "<div id=test role=button aria-pressed=false>press me</div>"
}

@pytest.mark.parametrize("test_html", TEST_HTML.values(), ids=TEST_HTML.keys())
def test_atspi(atspi, session, inline, test_html):
session.url = inline(test_html)

# Spec:
# Role: ROLE_TOGGLE_BUTTON

node = atspi.find_node("test", session.url)
assert atspi.Accessible.get_role(node) == atspi.Role.TOGGLE_BUTTON

# @pytest.mark.parametrize("test_html", TEST_HTML.values(), ids=TEST_HTML.keys())
# def test_axapi(axapi, session, inline, test_html):
# session.url = inline(test_html)
#
# # Spec:
# # AXRole: AXCheckBox
# # AXSubrole: AXToggle

# @pytest.mark.parametrize("test_html", TEST_HTML.values(), ids=TEST_HTML.keys())
# def test_ia2(ia2, session, inline, test_html):
# session.url = inline(test_html)
#
# # Spec:
# # Role: ROLE_SYSTEM_PUSHBUTTON
# # Role: IA2_ROLE_TOGGLE_BUTTON

# @pytest.mark.parametrize("test_html", TEST_HTML.values(), ids=TEST_HTML.keys())
# def test_uia(uia, session, inline, test_html):
# session.url = inline(test_html)
#
# # Spec:
# # Control Type: Button
Empty file.
53 changes: 53 additions & 0 deletions core-aam/aamtests/support/api_wrapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import abc
import time
from typing import Any, Callable, Generic, Optional, TypeVar

ApiNode = TypeVar('ApiNode')
PollResult = TypeVar('PollResult')

class ApiWrapper(Generic[ApiNode], abc.ABC):
def __init__(self, pid: int, product_name: str, timeout: float) -> None:
"""Setup for accessibility API testing.

:pid: The PID of the process which exposes the accessibility API.
:product_name: The name of the browser, used to find the browser in the accessibility API.
:timeout: The timeout the test harness has set for this test, local timeouts can be set based on it.
"""
self.product_name: str = product_name
self.pid: int = pid
self.root: Optional[Any] = None
self.document: Optional[ApiNode] = None
self.test_url: Optional[str] = None
self.timeout: float = timeout

self.root = self._find_browser()

if not self.root:
raise Exception(
f"Couldn't find browser {self.product_name} in accessibility API {self.api_name}."
)

@property
@abc.abstractmethod
def api_name(self) -> str:
pass

@abc.abstractmethod
def _find_browser(self) -> Optional[ApiNode]:
pass

def _poll_for(self, find: Callable[[], Optional[PollResult]], error: str) -> PollResult:
"""Poll until the `find` function returns something.

:param url: The url of the test.
:return: Whatever find returns.
"""
found = find()
stop = time.time() + self.timeout
while not found:
if time.time() > stop:
raise TimeoutError(error)
time.sleep(0.01)
found = find()

return found
Loading
Loading