Files
ruvnet--RuView/dashboard/tests/a11y.spec.ts
T
rUv 7f5a692632 feat(nvsim): full simulator stack — Rust crate, dashboard, server, App Store, Ghost Murmur [ADR-089/090/091/092/093]
Squashed merge of feat/nvsim-pipeline-simulator (29 commits).

## Shipped

- ADR-089 nvsim crate (Accepted) — 50/50 tests, ~4.5 M samples/s, pinned witness cc8de9b01b0ff5bd…
- ADR-092 dashboard implementation (Implemented) — 8/12 §11 gates , 4/12 ⚠ (external infra)
- ADR-093 dashboard gap analysis (Implemented) — 21/21 catalogued gaps closed
- Plus ADR-090 (proposed conditional) and ADR-091 (proposed research-only)

## Live deploy
https://ruvnet.github.io/RuView/nvsim/

## Infra

- nvsim-server Dockerfile + GHCR publish workflow (.github/workflows/nvsim-server-docker.yml)
- axe-core + Playwright cross-browser CI (.github/workflows/dashboard-a11y.yml)
- gh-pages auto-deploy workflow already in place (preserves observatory + pose-fusion siblings)

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-04-27 12:41:01 -04:00

57 lines
2.3 KiB
TypeScript

/* axe-core accessibility smoke against the built dashboard.
* Closes ADR-092 §11.5 — formal axe scan.
*
* Runs against `npm run preview` (Vite preview server). Validates each
* primary view (home / scene / apps / inspector / witness / ghost-murmur)
* and asserts 0 critical/serious violations.
*/
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
const VIEWS = ['home', 'scene', 'apps', 'inspector', 'witness', 'ghost-murmur'] as const;
test.describe('axe-core a11y smoke', () => {
for (const view of VIEWS) {
test(`view: ${view}`, async ({ page }) => {
await page.goto('/');
// Dismiss the welcome modal if it auto-shows.
await page.evaluate(() => {
const sr = (document.querySelector('nv-app') as HTMLElement & { shadowRoot: ShadowRoot }).shadowRoot;
const ob = sr.querySelector('nv-onboarding') as HTMLElement | null;
if (ob?.hasAttribute('open')) {
(ob.shadowRoot?.querySelector('.skip') as HTMLElement | null)?.click();
}
});
// Navigate to the view via the rail button (except for home which is default).
if (view !== 'home') {
await page.evaluate((v) => {
const sr = (document.querySelector('nv-app') as HTMLElement & { shadowRoot: ShadowRoot }).shadowRoot;
const rail = sr.querySelector('nv-rail') as HTMLElement & { shadowRoot: ShadowRoot };
const btn = rail.shadowRoot.querySelector(`button[data-id=${v}-btn]`) as HTMLElement | null;
btn?.click();
}, view);
await page.waitForTimeout(300);
}
const results = await new AxeBuilder({ page })
.options({ runOnly: ['wcag2a', 'wcag2aa'] })
.analyze();
const critical = results.violations.filter((v) => v.impact === 'critical');
const serious = results.violations.filter((v) => v.impact === 'serious');
// Logging the violation summary makes CI failures readable.
if (critical.length || serious.length) {
for (const v of [...critical, ...serious]) {
console.error(`[${view}] ${v.impact} · ${v.id} · ${v.help}`);
for (const node of v.nodes) console.error(` ${node.target.join(' >> ')}`);
}
}
expect(critical.length, 'no critical violations').toBe(0);
expect(serious.length, 'no serious violations').toBe(0);
});
}
});