Skip to content

Commit 87b266f

Browse files
committed
llm docs: axe accessibility testing
Workflow documentation for exercising Quarto's axe: feature against real rendered output when triaging accessibility reports. Covers scan configuration, runtime signals (base64 options, completion flag), output modes, CSS variable bridge for themed reports, and CI patterns.
1 parent 10946d2 commit 87b266f

1 file changed

Lines changed: 88 additions & 0 deletions

File tree

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
---
2+
main_commit: 10946d28a
3+
analyzed_date: 2026-04-17
4+
key_files:
5+
- src/format/html/format-html-axe.ts
6+
- src/resources/formats/html/axe/axe-check.js
7+
- src/resources/schema/document-a11y.yml
8+
---
9+
10+
# Testing Axe Accessibility Checks
11+
12+
Workflow for exercising Quarto's `axe:` feature against real rendered output when triaging accessibility reports or validating fixes. For the internal architecture (dependency injection, reporters, sass bundles), see `axe-accessibility-architecture.md`.
13+
14+
## Quarto's Scan Configuration
15+
16+
`axe-check.js` calls `axe.run()` with only these options:
17+
18+
- `exclude: ["[data-tabster-dummy]"]` — works around microsoft/tabster#288
19+
- `preload: { assets: ["cssom"], timeout: 50000 }`
20+
21+
No `runOnly` filter — Quarto surfaces the full default rule set (WCAG 2.0 A/AA + WCAG 2.1 + best-practice). This matters when comparing Quarto's output against an external axe scan: they should agree rule-for-rule.
22+
23+
## Runtime Signals
24+
25+
Two DOM markers are useful for automated testing:
26+
27+
- **`<script id="quarto-axe-checker-options" type="text/plain">...`** — base64-encoded JSON of the `axe:` options, injected at build time by `format-html-axe.ts`. Patching this string switches output mode in a pre-rendered HTML file without re-rendering:
28+
29+
```bash
30+
# Switch output: document → json in place
31+
sed -i 's|eyJvdXRwdXQiOiJkb2N1bWVudCJ9|eyJvdXRwdXQiOiJqc29uIn0=|g' output/index.html
32+
# ({"output":"document"} ↔ {"output":"json"})
33+
```
34+
35+
- **`document.body.dataset.quartoAxeComplete === "true"`** — set by `axe-check.js` once the scan finishes. Use as a wait condition in headless-browser tests.
36+
37+
## Output Modes Recap
38+
39+
- `json` — one `console.log(JSON.stringify(result, null, 2))` call with the full axe result. Not stdout. Needs a browser to capture.
40+
- `console` — one `console.log` per violation with targets.
41+
- `document` — visual overlay (HTML), slide (RevealJS), or offcanvas (dashboard).
42+
43+
## Testing Pipeline
44+
45+
For a11y triage against a user's repro repo:
46+
47+
1. Clone the repro into a scratch dir (not inside `quarto-cli`).
48+
2. Render with the dev Quarto build: `package/dist/bin/quarto.cmd render`.
49+
3. Either run with `axe: output: json` in `_quarto.yml`, or patch the base64 config in the rendered HTML as above.
50+
4. Serve the output locally — `simple-http-server.exe --nocache -i -p PORT path/`.
51+
5. Scan with `agent-browser` (uses Chrome via CDP):
52+
53+
```bash
54+
agent-browser --session scan open "http://localhost:PORT/index.html"
55+
agent-browser --session scan wait --fn \
56+
'document.body.getAttribute("data-quarto-axe-complete") === "true"'
57+
agent-browser --session scan console > result.txt
58+
# extract the last [log] entry and feed to jq
59+
```
60+
61+
## Running Axe Manually (Full Control)
62+
63+
Quarto's built-in scan fires soon after `DOMContentLoaded`. For pages with late-arriving content (htmlwidgets, profvis, late-mounted callouts), the built-in scan can miss violations that a later scan would catch. Run axe directly via `agent-browser eval`:
64+
65+
```bash
66+
agent-browser --session scan eval --stdin <<'EOF'
67+
(async () => {
68+
const axe = await import("https://cdn.jsdelivr.net/npm/[email protected]/+esm");
69+
const results = await axe.default.run(document, { resultTypes: ["violations"] });
70+
return results.violations.map(v => ({
71+
id: v.id, impact: v.impact, nodes: v.nodes.length,
72+
targets: v.nodes.map(n => n.target), tags: v.tags
73+
}));
74+
})()
75+
EOF
76+
```
77+
78+
Use the same axe-core version (`4.10.3` at the time of writing) as bundled in `axe-check.js` to keep rule parity with Quarto's own scan.
79+
80+
## Comparing With / Without Fixes
81+
82+
To identify which violations a proposed patch resolves vs. which remain, render twice — once with the patch disabled, once with it enabled — and diff the rule IDs. `include-after-body:` is the right hook for injecting a JS fix without touching Quarto internals during triage.
83+
84+
## Tips
85+
86+
- The axe-core bundled version is loaded from `cdn.skypack.dev` at runtime. Offline environments will silently fail; note this when triaging reports that blame a "missing" scan.
87+
- If a scan only surfaces 1–2 violations where you expected many, the built-in scan likely ran before the page finished rendering. Run axe manually via eval to confirm.
88+
- Some violations change with viewport size (sidebar visibility, scrollable regions). Note the viewport when recording findings — `agent-browser` defaults to 1262×568 in headless mode.

0 commit comments

Comments
 (0)