- Raise use-swipe-gesture mutation score to 66.13% (target 65%+) - Maintain use-reduced-motion mutation score at 76.32% (target 50%+) - Fix use-reduced-motion.ts ESLint set-state-in-effect warning - Add UJ-10 deep searcher journey (category browse → article read → content discovery) - Add 40 new test files (analytics, detail, sections, ui, lib components) - Update test-strategy-plan.md to v2.0 (sync test count to 1591) - Sync README.md with final release metrics Quality gates: TS 0 errors, ESLint 0 errors, 121 suites / 1591 tests passed
73 lines
2.0 KiB
TypeScript
73 lines
2.0 KiB
TypeScript
// @ts-nocheck
|
|
import { Page } from '@playwright/test';
|
|
import AxeBuilder from '@axe-core/playwright';
|
|
import { AccessibilityResult, Violation } from '../../types';
|
|
|
|
export class AccessibilityTester {
|
|
constructor(private page: Page) {}
|
|
|
|
async runAxeScan(pageName: string, url: string): Promise<AccessibilityResult> {
|
|
const accessibilityScanResults = await new AxeBuilder({ page: this.page })
|
|
.withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
|
|
.analyze();
|
|
|
|
const violations: Violation[] = accessibilityScanResults.violations.map(v => ({
|
|
id: v.id,
|
|
impact: v.impact || 'unknown',
|
|
description: v.description,
|
|
help: v.help,
|
|
helpUrl: v.helpUrl,
|
|
nodes: v.nodes.length
|
|
}));
|
|
|
|
const passes = accessibilityScanResults.passes.length;
|
|
const incomplete = accessibilityScanResults.incomplete.length;
|
|
const score = this.calculateScore(violations, passes, incomplete);
|
|
|
|
return {
|
|
score,
|
|
violations,
|
|
passes,
|
|
incomplete,
|
|
page: pageName,
|
|
url
|
|
};
|
|
}
|
|
|
|
private calculateScore(violations: Violation[], passes: number, incomplete: number): number {
|
|
const total = violations.length + passes + incomplete;
|
|
if (total === 0) return 100;
|
|
return parseFloat(((passes / total) * 100).toFixed(1));
|
|
}
|
|
|
|
async checkColorContrast(): Promise<boolean> {
|
|
const results = await new AxeBuilder({ page: this.page })
|
|
.withTags(['wcag2aa'])
|
|
.include('#content')
|
|
.analyze();
|
|
|
|
return results.violations.filter(v => v.id === 'color-contrast').length === 0;
|
|
}
|
|
|
|
async checkAltText(): Promise<{ total: number; withAlt: number; withoutAlt: number }> {
|
|
const images = await this.page.locator('img').all();
|
|
let withAlt = 0;
|
|
let withoutAlt = 0;
|
|
|
|
for (const image of images) {
|
|
const alt = await image.getAttribute('alt');
|
|
if (alt && alt.trim() !== '') {
|
|
withAlt++;
|
|
} else {
|
|
withoutAlt++;
|
|
}
|
|
}
|
|
|
|
return {
|
|
total: images.length,
|
|
withAlt,
|
|
withoutAlt
|
|
};
|
|
}
|
|
}
|