Scan the dashboard once its charts are in the accessibility tree

An unrevealed chart cell is `invisible` and `aria-hidden`, so the chart row
is not in the accessibility tree at all until the reveal gate fires. Waiting
on the tile strip left it to chance whether the row was read: locally the
cells were still hidden when the strip appeared and revealed six seconds
later, while the runner that reported the broken heading order had already
revealed them. The row is the largest surface on the page, so a scan that
sometimes skips it is not a check of the page.

Gating on a revealed cell makes the coverage the same on every run. With the
heading level put back to its old value the case now fails 16 out of 16
repeats across both themes and both viewports, where the same defect
previously surfaced twice in several full runs.
This commit is contained in:
Marc-André Bombeck committed 2026-08-02 19:45:05 +02:00
1 parent ef9d66b7f9
commit 33fc238305
1 file changed
+15 -5
+15 -5
View File
@@ -778,13 +778,23 @@ const ADMIN_AND_BASELINE_ROUTES: readonly RouteCase[] = [
// is `app/loading.tsx` — a silhouette with no heading in it. The scan was
// therefore free to run against a document that legitimately had no `<h1>`
// yet and report the app as missing one. Measured under an 8× CPU
// throttle: `#main-content` at 759 ms, the tile strip at 4896 ms. The
// strip is the dashboard's own content and has a separate
// `dashboard-tile-strip-skeleton` marker for its loading phase, so the two
// states cannot be confused for one another.
// throttle: `#main-content` at 759 ms, the tile strip at 4896 ms.
//
// The gate is the revealed chart cell rather than the tile strip because
// an unrevealed cell is `invisible` AND `aria-hidden` — the chart cards
// are simply not in the accessibility tree, so axe walks past the whole
// chart row without reading it. Gating on the strip alone left it a
// coin flip whether the row was scanned at all: locally the cells were
// still hidden at strip time and revealed six seconds later, while the
// runner that reported the heading-order break had already revealed
// them. The row is the dashboard's largest surface; the scan has to see
// it every time or not claim to have checked the page.
name: "/ dashboard",
path: "/",
painted: (page) => page.locator('[data-slot="dashboard-tile-strip"]'),
painted: (page) =>
page
.locator('[data-slot="dashboard-chart-cell"][data-revealed="true"]')
.first(),
},
{
// The settings heading belongs to `<SettingsShell>`, i.e. the chrome —