Newsletter
TechAnV Blog
Get updates on security engineering, Rust, eBPF, and DevSecOps. No spam, unsubscribe anytime.
Check your inbox and click the confirmation link to complete your subscription.
axe-core and shot-scraper for accessibility audits#
I just watched a talk by Pamela Fox at North Bay Python on Automated accessibility audits. The video should be up within 24 hours.
One of the tools Pamela introduced us to was axe-core, which is a JavaScript library at the heart of a whole ecosystem of accessibility auditing tools.
I figured out how to use it to run an accessibility audit using my shot-scraper CLI tool:
1shot-scraper javascript https://datasette.io "2async () => {3 const axeCore = await import('https://cdn.jsdelivr.net/npm/axe-core@4.7.2/+esm');4 return axeCore.default.run();5}6"The first line loads an ESM build of axe-core from the jsdelivr CDN. I figured out the URL for this by searching jsdelivr and finding their axe-core page.
The second line calls the .run() method, which defaults to returning an enormous JSON object containing the results of the audit.
shot-scraper dumps the return value of tha async() function to standard output in my terminal.
The output started like this:
1{2 "testEngine": {3 "name": "axe-core",4 "version": "4.7.2"5 },6 "testRunner": {7 "name": "axe"8 },9 "testEnvironment": {10 "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/115.0.5790.75 Safari/537.36",11 "windowWidth": 1280,12 "windowHeight": 720,13 "orientationAngle": 0,14 "orientationType": "landscape-primary"15 },16 "timestamp": "2023-07-30T18:32:39.591Z",17 "url": "https://datasette.io/",18 "toolOptions": {19 "reporter": "v1"20 },21 "inapplicable": [22 {23 "id": "accesskeys",24 "impact": null,25 "tags": [26 "cat.keyboard",27 "best-practice"28 ],That inapplicable section goes on for a long time, but it’s not actually interesting - it shows all of the audit checks that the page passed.
The most interesting section is called violations. We can filter to just that using jq:
1shot-scraper javascript https://datasette.io "2async () => {3 const axeCore = await import('https://cdn.jsdelivr.net/npm/axe-core@4.7.2/+esm');4 return axeCore.default.run();5}6" | jq .violationsWhich produced (for my page) an array of four objects, starting like this:
1[2 {3 "id": "color-contrast",4 "impact": "serious",5 "tags": [6 "cat.color",7 "wcag2aa",8 "wcag143",9 "ACT",10 "TTv5",11 "TT13.c"12 ],13 "description": "Ensures the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds",14 "help": "Elements must meet minimum color contrast ratio thresholds",15 "helpUrl": "https://dequeuniversity.com/rules/axe/4.7/color-contrast?application=axeAPI",16 "nodes": [17 {18 "any": [19 {20 "id": "color-contrast",21 "data": {22 "fgColor": "#ffffff",23 "bgColor": "#8484f4",24 "contrastRatio": 3.18,25 "fontSize": "10.8pt (14.4px)",26 "fontWeight": "normal",27 "messageKey": null,28 "expectedContrastRatio": "4.5:1",29 "shadowColor": null30 },31 "relatedNodes": [32 {33 "html": "<input type=\"submit\" value=\"Search\">",34 "target": [35 "input[type=\"submit\"]"36 ]37 }38 ],39 "impact": "serious",40 "message": "Element has insufficient color contrast of 3.18 (foreground color: #ffffff, background color: #8484f4, font size: 10.8pt (14.4px), font weight: normal). Expected contrast ratio of 4.5:1"41 }42 ],I loaded these into a SQLite database using sqlite-utils:
1shot-scraper javascript https://datasette.io "2async () => {3 const axeCore = await import('https://cdn.jsdelivr.net/npm/axe-core@4.7.2/+esm');4 return axeCore.default.run();5}6" | jq .violations \7 | sqlite-utils insert /tmp/v.db violations -Then I ran open /tmp/v.db to open that database in Datasette Desktop.