an enhanced html magic with the ability to test for violations¤
i want to ship some tools that make it easier for interactive accessibility testing and analysis.
typically, accessibility is a challenge to integrate with python because of the need for multiple run times.
to run axe we need javascript and to run the vnu validator we need java.
after we've run our accessibility tests we need to reason with them,
and working in a python environment is ideal for data analysis.
in this post we'll explore standard testing with explicit and implicit accessibility testing invocations.
in IPython we must use the async api because the sync api raises an error.
>>> playwright.sync_api.sync_playwright().__enter__()
Traceback (most recent call last):
...
playwright._impl._errors.Error: It looks like you are using Playwright Sync API inside the asyncio loop.
Please use the Async API instead.
therefore we need to write a lot more code to manage the async state.
%%## testing axe and vnu with nbnbconvert_a11yasyncwithplaywright.async_api.async_playwright()aspw:1.enteranasynchronousplaywrightcontextbrowser=awaitpw.chromium.launch()2.navigationtoanhtmldocumentpage=awaitbrowser.new_page()weneedtousetheuriformatofthefileawaitpage.goto(file:=files.iloc[0].absolute().as_uri())3.`nbconvert_a11y`nowprovidesconveniencefunctionsfortestingthesepagesaxe,vnu,aom=awaitasyncio.gather(nbconvert_a11y.axe.async_axe.pw_axe(page),nbconvert_a11y.axe.async_axe.pw_validate_html(page),nbconvert_a11y.axe.async_axe.pw_accessibility_tree(page))theexampleaboverepresentstheanalysisofsinglefile's accessibility, html formatting, and accessibility tree.therearetimeswherewemighthavetomeasuremanypagesorperhapsonlypartofapage.thenextsectionswilllookatscalingtestingtomultiplefilesthenpartsofahtmldocument.
testing axe and vnu with nbnbconvert_a11y
async with playwright.async_api.async_playwright() as pw:
the example above represents the analysis of single file's accessibility, html formatting, and accessibility tree.
there are times where we might have to measure many pages or perhaps only part of a page.
the next sections will look at scaling testing to multiple files then parts of a html document.
aggregating accessibility violations across many files has been a challenge.
this reasoning is often required when venturing to retrofit accessibility of exists sites.
we'll want to figure out how to reason with common and uncommon violations in documents.
the dataframe approach that follows brings accessibility violations nearer to data analytics.
%%asyncdefgoto(browser,file):`goto`isacoveniencefunctionforcreatinganewbrowserpageandpopulatingitwiththecontentsofafile.page=awaitbrowser.new_page()awaitpage.goto(file.absolute().as_uri())returnpageasyncwithplaywright.async_api.async_playwright()aspw:wecanscaletheanalysisbyapplyingourperfileanalysistomanyfiles.theoutcomeofthisarethreedifferentdataframesthatareripefordataanalysis.browser=awaitpw.chromium.launch()pages=awaitfiles.apply(partial(goto,browser)).gather()axe,vnu,aom=awaitasyncio.gather(pages.apply(nbconvert_a11y.axe.async_axe.pw_axe).gather(),pages.apply(nbconvert_a11y.axe.async_axe.pw_validate_html).gather(),pages.apply(nbconvert_a11y.axe.async_axe.pw_accessibility_tree).gather(),)atthispoint,we've generated a larger dataframe that aggregates the `results` of each analysisthatwecanreasonwith.(results:=pandas.concat(dict(axe=axe,vnu=vnu,aom=aom),axis=1))
goto is a covenience function for creating a new browser page and populating it with the contents of a file.
page = await browser.new_page()
await page.goto(file.absolute().as_uri())
return page
async with playwright.async_api.async_playwright() as pw:
we can scale the analysis by applying our per file analysis to many files.
the outcome of this are three different dataframes that are ripe for data analysis.
when developing components we tend to not want a firehose like we get from the files approach.
rather we'll report one at a time.
%reload_extnbconvert_a11y.axe
the magic exposes a persistent browser that we can use to generate pages from from.
%%html<sectionrole="list"></section>
accessibility violations for about:blank (1 sub-exception)
5 html violations (5 sub-exceptions)
| nbconvert_a11y.axe.base_axe_exceptions.AxeExceptions: accessibility violations for about:blank (1 sub-exception)
+-+---------------- 1 ----------------
| nbconvert_a11y.axe.base_axe_exceptions.aria_allowed_role_minor: {'description': 'Ensures role attribute has an appropriate value for the '
| 'element',
| 'help': 'ARIA role should be appropriate for the element',
| 'helpUrl': 'https://dequeuniversity.com/rules/axe/4.8/aria-allowed-role?application=axeAPI',
| 'id': 'aria-allowed-role',
| 'impact': 'minor',
| 'nodes': [{'all': [],
| 'any': [{'data': ['list'],
| 'id': 'aria-allowed-role',
| 'impact': 'minor',
| 'message': 'ARIA role list is not allowed for given '
| 'element',
| 'relatedNodes': []}],
| 'failureSummary': 'Fix any of the following:\n'
| ' ARIA role list is not allowed for given '
| 'element',
| 'html': '<section role="list"></section>',
| 'impact': 'minor',
| 'none': [],
| 'target': ['section']}],
| 'tags': ['cat.aria', 'best-practice']}
+------------------------------------
| ExceptionGroup: 5 html violations (5 sub-exceptions)
+-+---------------- 1 ----------------
| nbconvert_a11y.pytest_w3c.error-Start tag seen without seeing a doctype first. Expected “<!DOCTYPE html>”.
+---------------- 2 ----------------
| nbconvert_a11y.pytest_w3c.error-Element “head” is missing a required instance of child element “title”.
+---------------- 3 ----------------
| nbconvert_a11y.pytest_w3c.error-Bad value “list” for attribute “role” on element “section”.
+---------------- 4 ----------------
| nbconvert_a11y.pytest_w3c.info-Section lacks heading. Consider using “h2”-“h6” elements to add identifying headings to all sections, or else use a “div” element instead for any cases where no heading is needed.
+---------------- 5 ----------------
| nbconvert_a11y.pytest_w3c.info-Consider adding a “lang” attribute to the “html” start tag to declare the language of this document.
+------------------------------------
accessibility violations for about:blank (1 sub-exception)
3 html violations (3 sub-exceptions)
| nbconvert_a11y.axe.base_axe_exceptions.AxeExceptions: accessibility violations for about:blank (1 sub-exception)
+-+---------------- 1 ----------------
| nbconvert_a11y.axe.base_axe_exceptions.region_moderate: {'description': 'Ensures all page content is contained by landmarks',
| 'help': 'All page content should be contained by landmarks',
| 'helpUrl': 'https://dequeuniversity.com/rules/axe/4.8/region?application=axeAPI',
| 'id': 'region',
| 'impact': 'moderate',
| 'nodes': [{'all': [],
| 'any': [{'data': {'isIframe': False},
| 'id': 'region',
| 'impact': 'moderate',
| 'message': 'Some page content is not contained by '
| 'landmarks',
| 'relatedNodes': []}],
| 'failureSummary': 'Fix any of the following:\n'
| ' Some page content is not contained by '
| 'landmarks',
| 'html': '<form aria-label="">\n'
| ' <input type="number" aria-label="numbers/">\n'
| '</form>',
| 'impact': 'moderate',
| 'none': [],
| 'target': ['form']}],
| 'tags': ['cat.keyboard', 'best-practice']}
+------------------------------------
| ExceptionGroup: 3 html violations (3 sub-exceptions)
+-+---------------- 1 ----------------
| nbconvert_a11y.pytest_w3c.error-Start tag seen without seeing a doctype first. Expected “<!DOCTYPE html>”.
+---------------- 2 ----------------
| nbconvert_a11y.pytest_w3c.error-Element “head” is missing a required instance of child element “title”.
+---------------- 3 ----------------
| nbconvert_a11y.pytest_w3c.info-Consider adding a “lang” attribute to the “html” start tag to declare the language of this document.
+------------------------------------