Skip to content

Commit ffc5cfb

Browse files
committed
test(e2e): axe sweep over every Ladle story (CL-9156)
test:e2e builds and serves the stories and fails on any serious or critical axe violation. CI runs it with the rest of the e2e suite.
1 parent 1c9ee71 commit ffc5cfb

5 files changed

Lines changed: 51 additions & 9 deletions

File tree

‎CONTRIBUTING.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ bun run test:e2e # pack, install into tests/e2e/host, run Playwright
2727

2828
`test:e2e` needs Chromium once: `bunx playwright install chromium`. Each scenario is a
2929
route in `tests/e2e/host/src/scenarios.tsx`, rendered against the packed tarball.
30+
The same run builds the Ladle stories and fails on any serious or critical axe
31+
violation in any story.
3032

3133
All five must be clean before a change lands; CI runs them on every pull request.
3234
`typecheck` is not negotiable and `any` is not a way past it: the escapes that exist each

‎bun.lock‎

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
"sonner": "^2.0.7"
6161
},
6262
"devDependencies": {
63+
"@axe-core/playwright": "^4.13.0",
6364
"@eslint/js": "^9.39.1",
6465
"@happy-dom/global-registrator": "^20.11.2",
6566
"@ladle/react": "^5.1.1",

‎tests/e2e/playwright.config.ts‎

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { defineConfig, devices } from "@playwright/test";
22

3-
// Serves `host/`, which installs the packed tarball `bun run test:e2e` writes.
3+
// Serves `host/`, which installs the packed tarball `bun run test:e2e` writes,
4+
// and the Ladle stories build for the accessibility sweep.
45
export default defineConfig({
56
testDir: ".",
67
testMatch: "*.spec.ts",
@@ -9,12 +10,22 @@ export default defineConfig({
910
snapshotPathTemplate: "{testDir}/__screenshots__/{arg}{ext}",
1011
use: { baseURL: "http://localhost:4173" },
1112
projects: [{ name: "chromium", use: devices["Desktop Chrome"] }],
12-
webServer: {
13-
// bun keeps a stale copy of a rewritten local tarball unless both go, so
14-
// the host pins exact versions instead of committing a lockfile.
15-
command: "rm -rf bun.lock node_modules/@corbits && bun install && bun run typecheck && bun run dev",
16-
cwd: "host",
17-
url: "http://localhost:4173/smoke",
18-
reuseExistingServer: false,
19-
},
13+
webServer: [
14+
{
15+
// bun keeps a stale copy of a rewritten local tarball unless both go, so
16+
// the host pins exact versions instead of committing a lockfile.
17+
command: "rm -rf bun.lock node_modules/@corbits && bun install && bun run typecheck && bun run dev",
18+
cwd: "host",
19+
url: "http://localhost:4173/smoke",
20+
reuseExistingServer: false,
21+
timeout: 180_000,
22+
},
23+
{
24+
command: "bun run stories:build && bunx ladle preview --port 61000",
25+
cwd: "../..",
26+
url: "http://localhost:61000/meta.json",
27+
reuseExistingServer: false,
28+
timeout: 180_000,
29+
},
30+
],
2031
});

‎tests/e2e/stories-a11y.spec.ts‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import AxeBuilder from "@axe-core/playwright";
2+
import { expect, test } from "@playwright/test";
3+
4+
const STORIES = "http://localhost:61000";
5+
6+
test("every Ladle story is free of serious and critical axe violations", async ({ page, request }) => {
7+
test.setTimeout(600_000);
8+
const meta = (await (await request.get(`${STORIES}/meta.json`)).json()) as { stories: Record<string, unknown> };
9+
const failures: string[] = [];
10+
11+
for (const theme of ["light", "dark"]) {
12+
for (const story of Object.keys(meta.stories)) {
13+
await page.goto(`${STORIES}/?story=${story}&mode=preview&theme=${theme}`);
14+
await page.locator("[data-storyloaded]").waitFor();
15+
const { violations } = await new AxeBuilder({ page }).analyze();
16+
for (const violation of violations) {
17+
if (violation.impact === "serious" || violation.impact === "critical") {
18+
failures.push(`${story} (${theme}): ${violation.id} (${violation.nodes.length})`);
19+
}
20+
}
21+
}
22+
}
23+
24+
expect(failures).toEqual([]);
25+
});

0 commit comments

Comments
 (0)