Skip to content

python interface for handling axe accessibility testsยค

reasoning with accessibility tests requires interacting with the contents of the report. i desire a really smooth interactive experience that lets me test many things at once and interact with the data.

from nbconvert_a11y.axe import Browser, AxeExceptions, aria_input_field_name, image_alt, serious, aria_allowed_role
async for page in Browser.pages(Path("../../../nbconvert-a11y/tests/exports/html/lorenz-executed-section.html")):
    violations = await AxeExceptions.from_page(page)

it is hard to have zero accessibility violations. we can use tests to quantify inaccessibility rather than conveniently ignore things. measuring inaccessibility is a good baseline improving accessibility. since zero accessibility violations is a huge accomplishment we use our xfail to indicate violations we are aware of.

assert violations.xfail(aria_input_field_name, image_alt)

assert is not required; it is used to state that there is indeed a test at this place in the code. if there is a failure the assertion never reached, otherwise the methods returns true.

the statement above is shorthand for the following representations using modern python syntax for exception groups.

try:
    assert violations.xfail(), "there are user content accessibility violations"
except* image_alt: pass
except* aria_input_field_name: pass
assert violations.xfail()
  + Exception Group Traceback (most recent call last):
  |   File "/home/tbone/mambaforge/envs/p311/lib/python3.11/site-packages/IPython/core/interactiveshell.py", line 3548, in run_code
  |     exec(code_obj, self.user_global_ns, self.user_ns)
  |   File "/tmp/ipykernel_74808/1746942673.py", line 1, in <module>
  |     assert violations.xfail()
  |            ^^^^^^^^^^^^^^^^^^
  |   File "/home/tbone/Documents/nbconvert-a11y/nbconvert_a11y/base_axe_exceptions.py", line 12, in xfail
  |     raise self
  |   File "/home/tbone/Documents/nbconvert-a11y/nbconvert_a11y/base_axe_exceptions.py", line 12, in xfail
  |     raise self
  |   File "/home/tbone/Documents/nbconvert-a11y/nbconvert_a11y/base_axe_exceptions.py", line 12, in xfail
  |     raise self
  | ExceptionGroup: 1 serious, 1 critical accessibility violations (2 sub-exceptions)
  | ARIA input fields must have an accessible name
  | https://dequeuniversity.com/rules/axe/4.8/aria-input-field-name?application=axeAPI
  | Images must have alternate text
  | https://dequeuniversity.com/rules/axe/4.8/image-alt?application=axeAPI
  +-+---------------- 1 ----------------
    | nbconvert_a11y.base_axe_exceptions.serious_aria_input_field_name: ('Fix any of the following:\n  aria-label attribute does not exist or is empty\n  aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n  Element has no title attribute', {'id': 'aria-input-field-name', 'impact': 'serious', 'tags': ['cat.aria', 'wcag2a', 'wcag412', 'TTv5', 'TT5.c', 'EN-301-549', 'EN-9.4.1.2', 'ACT'], 'description': 'Ensures every ARIA input field has an accessible name', 'help': 'ARIA input fields must have an accessible name', 'helpUrl': 'https://dequeuniversity.com/rules/axe/4.8/aria-input-field-name?application=axeAPI', 'nodes': [{'any': [{'id': 'aria-label', 'data': None, 'relatedNodes': [], 'impact': 'serious', 'message': 'aria-label attribute does not exist or is empty'}, {'id': 'aria-labelledby', 'data': None, 'relatedNodes': [], 'impact': 'serious', 'message': 'aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty'}, {'id': 'non-empty-title', 'data': {'messageKey': 'noAttr'}, 'relatedNodes': [], 'impact': 'serious', 'message': 'Element has no title attribute'}], 'all': [], 'none': [], 'impact': 'serious', 'html': '<div class="noUi-handle noUi-handle-lower" data-handle="0" tabindex="0" role="slider" aria-orientati', 'target': ['div[aria-valuetext="10"]'], 'failureSummary': 'Fix any of the following:\n  aria-label attribute does not exist or is empty\n  aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n  Element has no title attribute'}, {'any': [{'id': 'aria-label', 'data': None, 'relatedNodes': [], 'impact': 'serious', 'message': 'aria-label attribute does not exist or is empty'}, {'id': 'aria-labelledby', 'data': None, 'relatedNodes': [], 'impact': 'serious', 'message': 'aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty'}, {'id': 'non-empty-title', 'data': {'messageKey': 'noAttr'}, 'relatedNodes': [], 'impact': 'serious', 'message': 'Element has no title attribute'}], 'all': [], 'none': [], 'impact': 'serious', 'html': '<div class="noUi-handle noUi-handle-lower" data-handle="0" tabindex="0" role="slider" aria-orientati', 'target': ['div[aria-valuemin="-2.7"]'], 'failureSummary': 'Fix any of the following:\n  aria-label attribute does not exist or is empty\n  aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n  Element has no title attribute'}, {'any': [{'id': 'aria-label', 'data': None, 'relatedNodes': [], 'impact': 'serious', 'message': 'aria-label attribute does not exist or is empty'}, {'id': 'aria-labelledby', 'data': None, 'relatedNodes': [], 'impact': 'serious', 'message': 'aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty'}, {'id': 'non-empty-title', 'data': {'messageKey': 'noAttr'}, 'relatedNodes': [], 'impact': 'serious', 'message': 'Element has no title attribute'}], 'all': [], 'none': [], 'impact': 'serious', 'html': '<div class="noUi-handle noUi-handle-lower" data-handle="0" tabindex="0" role="slider" aria-orientati', 'target': ['div[aria-valuetext="28"]'], 'failureSummary': 'Fix any of the following:\n  aria-label attribute does not exist or is empty\n  aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n  Element has no title attribute'}]})
    +---------------- 2 ----------------
    | nbconvert_a11y.base_axe_exceptions.critical_image_alt: ('Fix any of the following:\n  Element does not have an alt attribute\n  aria-label attribute does not exist or is empty\n  aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n  Element has no title attribute\n  Element\'s default semantics were not overridden with role="none" or role="presentation"', {'id': 'image-alt', 'impact': 'critical', 'tags': ['cat.text-alternatives', 'wcag2a', 'wcag111', 'section508', 'section508.22.a', 'TTv5', 'TT7.a', 'TT7.b', 'EN-301-549', 'EN-9.1.1.1', 'ACT'], 'description': 'Ensures <img> elements have alternate text or a role of none or presentation', 'help': 'Images must have alternate text', 'helpUrl': 'https://dequeuniversity.com/rules/axe/4.8/image-alt?application=axeAPI', 'nodes': [{'any': [{'id': 'has-alt', 'data': None, 'relatedNodes': [], 'impact': 'critical', 'message': 'Element does not have an alt attribute'}, {'id': 'aria-label', 'data': None, 'relatedNodes': [], 'impact': 'critical', 'message': 'aria-label attribute does not exist or is empty'}, {'id': 'aria-labelledby', 'data': None, 'relatedNodes': [], 'impact': 'critical', 'message': 'aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty'}, {'id': 'non-empty-title', 'data': {'messageKey': 'noAttr'}, 'relatedNodes': [], 'impact': 'critical', 'message': 'Element has no title attribute'}, {'id': 'presentational-role', 'data': None, 'relatedNodes': [], 'impact': 'critical', 'message': 'Element\'s default semantics were not overridden with role="none" or role="presentation"'}], 'all': [], 'none': [], 'impact': 'critical', 'html': '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAfQAAAHzCAYAAADW0+8yAAAAOXRFWHRTb2Z0d2FyZQBN', 'target': ['.jp-RenderedImage > img'], 'failureSummary': 'Fix any of the following:\n  Element does not have an alt attribute\n  aria-label attribute does not exist or is empty\n  aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty\n  Element has no title attribute\n  Element\'s default semantics were not overridden with role="none" or role="presentation"'}]})
    +------------------------------------