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.
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.