invoking axe-core from python playwright¤
javascript is not my jam. id rather write python. this post is a result of these inconsistencies.
in my accessibility, i often need to test the accessibility of a web page. the javascript way to this is to run using playwright and playwright-axe together.
i couldn't find an evident way to do that. instead i peaked into the playwright-axe integration and realizes we could invoke axe through python by copying a little bit.
auditting an html file¤
- create a headless playwright browser
- load the page
- inject axe-core
- audit the page
- close the browser
from pathlib import Path
async def audit(file, **config):
import playwright.async_api
async with playwright.async_api.async_playwright() as play:
browser, page = await get_browser_page(play)
await page.goto(Path(file).absolute().as_uri())
await __import__("asyncio").sleep(3)
await injectReadability(page)
# await injectAxe(page)
# data = await get_audit_data(page, **config)
await __import__("asyncio").sleep(30)
await browser.close()
return data
async def injectReadability(page):
await page.evaluate(requests.get("https://raw.githubusercontent.com/mozilla/readability/master/Readability.js").text)
status = await page.evaluate("""async () => {
const readability = await import('https://cdn.skypack.dev/@mozilla/readability');
return (new readability.Readability(document)).parse();
}""")
await audit("better.html")
async def get_browser_page(play, **options):
from shlex import split
browser = await play.chromium.launch(
args=split('--enable-blink-features="AccessibilityObjectModel"'),
headless=False, channel="chrome-beta")
return browser, await browser.new_page()
injectAxe
mimics how playwright-axe
loads the package.
we used a cached requests object vendored from unpkg
async def injectAxe(page):
await page.evaluate(requests.get("https://unpkg.com/axe-core").text)
async def injectReadability(page):
await page.evaluate(requests.get("https://raw.githubusercontent.com/mozilla/readability/master/Readability.js").text)
status = await page.evaluate("""var article = new Readability(document).parse();""")
get_audit_data
extracts the axe test results and the accesssbility tree from the page.
async def get_audit_data(page, **config):
from json import dumps
return await __import__("asyncio").gather(
page.evaluate(F"window.axe.run(window.document, {dumps(config)})"),
page.accessibility.snapshot())
testing a page¤
we use this notebook as the test artifact by generating an html version of it with nbconvert
import requests_cache, requests, pandas; requests_cache.install_cache("a11y")
from pathlib import Path
THIS = Path("2022-10-27-axe-core-playwright-python.ipynb")
if __name__ == "__main__":
!jupyter nbconvert --to html $THIS
getting the accessibility audit objects¤
axe
contains the axe test data and tree
contains the accessibility tree.
axe, tree = map(pandas.Series, await audit(THIS.with_suffix(".html"), runOnly="best-practice".split()))
axe violations¤
pandas.DataFrame(axe.violations)
accessibility tree¤
pandas.DataFrame(tree.children)
conclusion¤
we can run axe in python playwright and analyze results in pandas.