40 lines
1.3 KiB
TypeScript
40 lines
1.3 KiB
TypeScript
import { expect, type Page } from "@playwright/test";
|
|
import AxeBuilder from "@axe-core/playwright";
|
|
|
|
interface Waiver {
|
|
page: string;
|
|
rule: string;
|
|
reason: string;
|
|
}
|
|
|
|
/**
|
|
* critical/serious 违规的逐条豁免清单(spec S3):
|
|
* 仅允许「属于将被 ② 阅读器改版重写的旧 chrome」的问题进这里,reason 必须写明归属。
|
|
* 例:{ page: "reader-chrome", rule: "color-contrast", reason: "旧 rd-* chrome 配色,② 迁移 shadcn token 后移除本豁免" }
|
|
*/
|
|
const WAIVERS: Waiver[] = [];
|
|
|
|
export async function scanA11y(page: Page, name: string): Promise<void> {
|
|
const results = await new AxeBuilder({ page }).withTags(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"]).analyze();
|
|
|
|
if (results.violations.length > 0) {
|
|
console.log(
|
|
`[axe:${name}]`,
|
|
JSON.stringify(
|
|
results.violations.map((v) => ({ id: v.id, impact: v.impact, nodes: v.nodes.length })),
|
|
null,
|
|
2,
|
|
),
|
|
);
|
|
}
|
|
|
|
const blocking = results.violations.filter(
|
|
(v) =>
|
|
(v.impact === "critical" || v.impact === "serious") && !WAIVERS.some((w) => w.page === name && w.rule === v.id),
|
|
);
|
|
expect(
|
|
blocking.map((v) => ({ id: v.id, impact: v.impact })),
|
|
`${name} 存在未豁免的 critical/serious a11y 违规`,
|
|
).toEqual([]);
|
|
}
|