You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(ui-scripts): show a11y violations on the screenshot itself
The a11y section of the visual-diff report was a nested list of axe rule ids, CSS
selectors, and raw failureSummary prose, repeated per theme. Nothing was anchored to
the image, so the artifact designers actually read - the screenshot - carried no
accessibility information at all.
Capture: while the page is still rendered, record each violating element's bounding
box, a human-readable name, and the contrast numbers axe already computed. a11y.json
gains a per-screenshot page size so boxes can be positioned as percentages and hold at
any rendered scale.
Report: numbered boxes over each offending element on the "Actual" image, colored and
ordered by impact; a card per finding leading with plain language, the element's name,
a suggested fix, and - for contrast - swatches with the measured and required ratios;
a run-level overview grouped by rule that collapses the three themes into one line;
impact and rule filters that narrow boxes and cards together; and a lightbox A11y mode
pairing the full-size screenshot with its findings. Raw axe output moves behind a
Technical details disclosure.
The PR comment now names the failing rules in a table instead of pointing at the run
logs.
Reports generated from a11y.json captured before this change still render: the boxes
are omitted, and contrast is recovered by parsing axe's prose.
📊 **[View full report](https://instructure.design/visual-regression/pr-${{ github.event.pull_request.number }}/index.html)** — every violation is boxed and numbered on its screenshot, with the offending element named and contrast failures shown as color swatches.
176
190
177
191
${{ steps.summary.outputs.diff_images }}
178
192
179
-
<sub>Baselines come from the `visual-baselines` branch. They refresh on every merge to `master`. The <b>Cypress assertions</b> line above covers a11y (axe) and console-error checks — a ❌ there means the suite found real issues even if the visual diff is clean.</sub>
193
+
<sub>Baselines come from the `visual-baselines` branch. They refresh on every merge to `master`. The <b>Cypress suite</b> line covers the a11y and console-error assertions — a ❌ there means the suite found real issues even if the visual diff is clean.</sub>
|`--baseline-dir <dir>`|`.baselines`| Baselines to compare against. |
66
-
|`--output-dir <dir>`|`visual-report`| Where the HTML report + diff PNGs are written. |
67
-
|`--threshold <0..1>`|`0.1`| Per-pixel YIQ color threshold. Lower is stricter. |
68
-
|`--fail-on-missing-baseline` / `--no-fail-on-missing-baseline`| on | Whether to exit 1 when actual screenshots have no baseline. |
69
-
|`--pr-number <n>`| — | Rendered in the report header as a link to the PR. |
70
-
|`--pr-url <url>`| — | Target of the PR link. |
71
-
|`--meta <file>`| — | JSON mapping screenshot slug → visited URL path. Enables per-row source-file links in the report. Produced automatically by `spec.cy.ts` via `cy.task('recordMeta', ...)`. |
72
-
|`--source-base-url <url>`| — | GitHub blob URL of `regression-test/src/app` on the branch being reviewed. Combined with `--meta` to build links like `treebrowser/page.tsx`. |
73
-
|`--facets <list>`| — | Comma-separated facet suffixes (e.g. `canvas,light,dark`) rendered as one-click filter chips that match screenshots named `<name>-<facet>`. |
74
-
|`--app-path <path>`| — | Path, relative to the report root, where a static export of the app is published (CI uses `app`). Enables the lightbox **HTML** view, which iframes the live rendered page. |
|`--baseline-dir <dir>`|`.baselines`| Baselines to compare against. |
66
+
|`--output-dir <dir>`|`visual-report`| Where the HTML report + diff PNGs are written. |
67
+
|`--threshold <0..1>`|`0.1`| Per-pixel YIQ color threshold. Lower is stricter. |
68
+
|`--fail-on-missing-baseline` / `--no-fail-on-missing-baseline`| on | Whether to exit 1 when actual screenshots have no baseline. |
69
+
|`--pr-number <n>`| — | Rendered in the report header as a link to the PR. |
70
+
|`--pr-url <url>`| — | Target of the PR link. |
71
+
|`--meta <file>`| — | JSON mapping screenshot slug → visited URL path. Enables per-row source-file links in the report. Produced automatically by `spec.cy.ts` via `cy.task('recordMeta', ...)`. |
72
+
|`--source-base-url <url>`| — | GitHub blob URL of `regression-test/src/app` on the branch being reviewed. Combined with `--meta` to build links like `treebrowser/page.tsx`. |
73
+
|`--facets <list>`| — | Comma-separated facet suffixes (e.g. `canvas,light,dark`) rendered as one-click filter chips that match screenshots named `<name>-<facet>`. |
74
+
|`--app-path <path>`| — | Path, relative to the report root, where a static export of the app is published (CI uses `app`). Enables the lightbox **HTML** view, which iframes the live rendered page. |
75
+
|`--a11y <file>`| — | JSON of axe violations keyed by screenshot slug. Enables everything in [Accessibility findings](#accessibility-findings) below. Produced automatically by `spec.cy.ts` via `cy.task('recordA11y', ...)`. |
75
76
76
77
### Interpreting the report
77
78
78
79
The HTML report is a single self-contained page showing one section per screenshot. Each row has a status badge (`ok`, `changed`, `new`, `removed`), a three-up grid of baseline / actual / diff images, and — when metadata is available — a link to the source `page.tsx`.
79
80
80
81
Top-bar controls:
81
82
82
-
-**Filter buttons** (`All`, `Changed`, `New`, `Removed`, `Unchanged`) — the active one is highlighted.
83
+
-**Filter buttons** (`All`, `Changed`, `New`, `Removed`, `Unchanged`, `⚠ A11y`) — the active one is highlighted.
83
84
-**Search input** — live, debounced substring filter on the screenshot name.
85
+
-**Theme chips** — narrow to one theme's screenshots.
86
+
-**Impact chips** and **Show markers on screenshots** — see below.
84
87
-**Lightbox** — click any thumbnail to open a fullscreen viewer. Inside:
85
88
-`Baseline`, `Actual`, `Diff` buttons switch between the three images.
86
89
-`Slider` mode overlays baseline and actual with a drag handle so you can scrub the boundary back and forth.
90
+
-`⚠ N a11y` mode shows the screenshot at full size with every violating element boxed, and the findings docked alongside. Shown only for screenshots with findings.
87
91
-`HTML` mode iframes the live rendered page (a static export of the app published next to the report) so you can inspect the real DOM, computed styles, and text alongside the pixels. Shown only when the page still exists and the app was published (`--app-path`).
88
92
-`1:1` / `Fit` toggles between native pixel size (scrollable) and fit-to-window.
89
93
-`‹` / `›` step through visible rows (respects the current filter).
90
94
95
+
### Accessibility findings
96
+
97
+
axe runs against every page and theme, and the report shows what it found **on the picture**, so a designer can act on it without reading a selector.
98
+
99
+
While the page is still on screen, `spec.cy.ts` records each violating element's bounding box, a human-readable name for it, and — for contrast failures — the foreground/background colors and ratios axe computed (see `cypress/support/a11y-capture.ts`). The report then renders:
100
+
101
+
-**A run-level overview** at the top of the page: one line per rule with its impact, how many elements it affects, and how many screenshots it appears on. The same rule tripping in `canvas`, `light`, and `dark` collapses to a single line. Click a rule to narrow the list to just the screenshots it affects.
102
+
-**Numbered boxes on the “Actual” image**, colored by impact. The numbers match the cards below, so “#3” is the same finding everywhere it appears. Uncheck **Show markers on screenshots** when they get in the way of comparing pixels.
103
+
-**One card per offending element**, leading with a plain-language headline ("Text contrast is too low") rather than the rule id, naming the element (`button “Archive this item”`), and suggesting a fix. Contrast failures show both colors as swatches with the measured and required ratios. The raw axe output — rule id, selector, and `failureSummary` — sits behind **Technical details**.
104
+
-**Impact chips** (`critical` / `serious` / `moderate` / `minor`) that narrow both the list and the boxes on each image.
105
+
106
+
Clicking a card's number badge opens the lightbox in **A11y** mode, scrolled to that finding. In that view, clicking a card highlights its box and vice versa.
107
+
108
+
Findings are ordered worst-impact-first, so `#1` on a screenshot is always the most serious thing on it.
109
+
110
+
Plain-language copy comes from a rule dictionary in `packages/ui-scripts/lib/commands/visual-diff.ts` (`RULE_COPY`). A rule that isn't in it still renders correctly — it just falls back to axe's own `help` text. Add an entry when a new rule starts showing up.
111
+
112
+
A page with a known, tracked a11y issue can opt out of the axe check entirely with `a11y: false` and an `a11ySkipReason` in the `PAGES` array in `spec.cy.ts`.
113
+
91
114
## Adding a new component page
92
115
93
116
1. Create `regression-test/src/app/<component-name>/page.tsx`. The page component must start with `'use client'` and render meaningful variations of the component you're covering.
0 commit comments