Add comprehensive accessibility testing agent with: - Full WCAG 2.2 Level AA compliance scanning via axe-core - EN 301 549 EU compliance mapping - ARIA Authoring Practices Guide (APG) patterns - EU Accessibility Act legal framework support - AccName (Accessible Name) computation Enhanced remediation code generation: - Framework detection (Slick, Swiper, Bootstrap, Owl Carousel, etc.) - Context-aware HTML/CSS/JS fix templates - WebVTT caption file generation with multi-language support - Playwright + axe-core test generation for violations - CSS accessibility utilities (skip links, focus indicators, etc.) Agent registration: - Added ACCESSIBILITY_ALLY to QEAgentType enum - Created AccessibilityAllyAgent with full task implementation - Registered in AgentRegistry with 'qe-a11y-ally' and 'a11y-ally' aliases - Added to QEAgentFactory with 10 capabilities 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
34 KiB
A11y-Ally Agent - Technical Specification
Document Information
- Version: 1.0
- Date: 2025-12-12
- Status: Draft
- Related: Implementation Plan
1. System Architecture
1.1 Component Diagram
┌─────────────────────────────────────────────────────────────────┐
│ qe-a11y-ally Agent │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────┐ │
│ │ Scan Engine │ │ Context Engine │ │ Remediation │ │
│ │ │ │ │ │ Engine │ │
│ │ • axe-core │ │ • DOM analysis │ │ • ARIA gen │ │
│ │ • Playwright │ │ • Semantic │ │ • Contrast │ │
│ │ • Custom rules │ │ inference │ │ • Keyboard │ │
│ └────────┬─────────┘ └────────┬─────────┘ └──────┬───────┘ │
│ │ │ │ │
│ └─────────────────────┴────────────────────┘ │
│ │ │
├──────────────────────────────┼─────────────────────────────────┤
│ │ │
│ ┌───────────────────────────▼──────────────────────────────┐ │
│ │ MCP Tool Layer │ │
│ │ │ │
│ │ • scan-comprehensive • analyze-context │ │
│ │ • generate-remediation • test-keyboard-navigation │ │
│ │ • simulate-screen-reader • analyze-color-contrast │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │ │
└──────────────────────────────┼─────────────────────────────────┘
│
┌──────────────────────────────▼─────────────────────────────────┐
│ Integration Layer │
├────────────────────────────────────────────────────────────────┤
│ │
│ qe-visual-tester qe-test-generator qe-quality-gate │
│ (screenshots) (test generation) (compliance gates) │
│ │
└────────────────────────────────────────────────────────────────┘
│
┌──────────────────────────────▼─────────────────────────────────┐
│ Learning & Memory Layer │
├────────────────────────────────────────────────────────────────┤
│ │
│ • Learning patterns (aqe/learning/patterns/accessibility/*) │
│ • Violation history (aqe/accessibility/violations/*) │
│ • Remediation success (aqe/accessibility/remediations/*) │
│ │
└────────────────────────────────────────────────────────────────┘
2. Data Models
2.1 Core Types
/**
* Comprehensive accessibility scan parameters
*/
export interface AccessibilityScanParams {
/** Target to scan */
target: URLTarget | HTMLTarget;
/** WCAG compliance level */
level: WCAGLevel;
/** Scan configuration */
config: ScanConfiguration;
/** Advanced options */
options?: AdvancedOptions;
}
export type WCAGLevel = 'A' | 'AA' | 'AAA';
export interface URLTarget {
type: 'url';
url: string;
/** Authenticate before scanning */
authentication?: {
type: 'basic' | 'bearer' | 'cookie';
credentials: Record<string, string>;
};
}
export interface HTMLTarget {
type: 'html';
html: string;
baseUrl?: string;
}
export interface ScanConfiguration {
/** Specific areas to scan */
scope?: {
include?: string[]; // CSS selectors
exclude?: string[]; // CSS selectors
pages?: string[]; // Multiple pages
};
/** Which validators to run */
validators: {
axeCore?: boolean;
playwright?: boolean;
customRules?: boolean;
};
/** Which tests to run */
tests: {
keyboard?: boolean;
screenReader?: boolean;
colorContrast?: boolean;
semanticHTML?: boolean;
focus?: boolean;
};
/** Remediation options */
remediation: {
enabled: boolean;
contextAware?: boolean;
generateTests?: boolean;
autoFixSafe?: boolean;
};
}
export interface AdvancedOptions {
/** Browser to use */
browser?: 'chromium' | 'firefox' | 'webkit';
/** Viewport size */
viewport?: { width: number; height: number };
/** Screenshot violations */
screenshots?: boolean;
/** Learning integration */
learning?: {
queryPastPatterns?: boolean;
storeLearnings?: boolean;
};
/** Performance limits */
timeout?: number;
maxConcurrency?: number;
}
2.2 Result Types
/**
* Complete scan result
*/
export interface AccessibilityScanResult {
/** Unique scan identifier */
scanId: string;
/** When scan was performed */
timestamp: string;
/** Scan metadata */
metadata: ScanMetadata;
/** Overall compliance assessment */
compliance: ComplianceStatus;
/** All violations found */
violations: Violation[];
/** Remediation suggestions */
remediations: Remediation[];
/** Generated test cases */
testCases?: TestCase[];
/** Detailed category analysis */
pourAnalysis: POURAnalysis;
/** Performance metrics */
performance: PerformanceMetrics;
/** Raw results from tools */
raw?: {
axeCore?: AxeCoreResult;
playwright?: PlaywrightResult;
};
}
export interface ScanMetadata {
target: string;
level: WCAGLevel;
scanDuration: number;
elementsAnalyzed: number;
toolsUsed: string[];
browserUsed: string;
viewport: { width: number; height: number };
}
export interface ComplianceStatus {
/** Overall status */
status: 'compliant' | 'partially-compliant' | 'non-compliant';
/** Numerical score (0-100) */
score: number;
/** Target level tested */
level: WCAGLevel;
/** By success criterion */
byCriterion: Record<string, {
passed: boolean;
violations: number;
}>;
/** Readiness for production */
productionReady: boolean;
/** Estimated fix effort */
estimatedEffort: {
hours: number;
complexity: 'trivial' | 'low' | 'medium' | 'high';
};
}
export interface Violation {
/** Unique violation ID */
id: string;
/** WCAG success criterion violated */
criterion: WCAGCriterion;
/** How severe is this */
severity: ViolationSeverity;
/** Impact on users */
impact: ImpactAssessment;
/** Elements with this violation */
elements: ViolationElement[];
/** Who detected this */
detectedBy: DetectionSource;
/** How confident are we */
confidence: number; // 0-1
/** Related violations */
relatedViolations?: string[];
}
export interface WCAGCriterion {
/** e.g., "1.4.3" */
number: string;
/** e.g., "Contrast (Minimum)" */
name: string;
/** WCAG level (A, AA, AAA) */
level: WCAGLevel;
/** POUR category */
category: 'perceivable' | 'operable' | 'understandable' | 'robust';
/** Link to WCAG documentation */
url: string;
}
export type ViolationSeverity = 'critical' | 'serious' | 'moderate' | 'minor';
export interface ImpactAssessment {
/** Human-readable description */
description: string;
/** Affected user groups */
affectedGroups: UserGroup[];
/** Estimated number of affected users */
estimatedUsers?: number;
/** Business impact */
businessImpact?: 'high' | 'medium' | 'low';
/** Legal risk */
legalRisk?: 'high' | 'medium' | 'low';
}
export type UserGroup =
| 'blind'
| 'low-vision'
| 'color-blind'
| 'deaf'
| 'hard-of-hearing'
| 'motor-impaired'
| 'cognitive-impaired'
| 'seizure-sensitive';
export interface ViolationElement {
/** CSS selector */
selector: string;
/** HTML snippet */
html: string;
/** Location on page */
location: {
x: number;
y: number;
width: number;
height: number;
};
/** Accessibility tree info */
a11yTree?: {
role: string;
name: string;
description?: string;
states?: string[];
};
/** Screenshot (if enabled) */
screenshot?: string;
}
export type DetectionSource =
| 'axe-core'
| 'playwright'
| 'custom-heuristic'
| 'ml-model';
2.3 Remediation Types
/**
* Context-aware remediation suggestion
*/
export interface Remediation {
/** Unique remediation ID */
id: string;
/** Related violation IDs */
violationIds: string[];
/** Type of fix */
type: RemediationType;
/** Priority for fixing */
priority: 'critical' | 'high' | 'medium' | 'low';
/** The actual suggestion */
suggestion: RemediationSuggestion;
/** Can this be auto-fixed? */
autoFixable: boolean;
/** Auto-fix details (if applicable) */
autoFix?: AutoFix;
/** How confident are we */
confidence: number; // 0-1
/** Validation status */
validation?: {
tested: boolean;
successful?: boolean;
testResult?: string;
};
}
export type RemediationType =
| 'aria-label'
| 'aria-describedby'
| 'alt-text'
| 'color-contrast'
| 'keyboard-navigation'
| 'focus-management'
| 'semantic-html'
| 'heading-structure'
| 'form-labels'
| 'captions'
| 'transcripts'
| 'skip-links'
| 'landmarks'
| 'other';
export interface RemediationSuggestion {
/** Human-readable title */
title: string;
/** Detailed description */
description: string;
/** Specific code changes */
codeChanges: CodeChange[];
/** Why this works */
reasoning: string;
/** Step-by-step instructions */
steps: string[];
/** Resources for learning */
resources?: Resource[];
/** Estimated effort */
effort: {
time: 'minutes' | 'hours' | 'days';
amount: number;
complexity: 'trivial' | 'low' | 'medium' | 'high';
};
/** Example implementation */
example?: {
before: string;
after: string;
explanation: string;
};
}
export interface CodeChange {
/** File path (if known) */
file?: string;
/** Element selector */
selector: string;
/** Type of change */
type: 'add-attribute' | 'modify-attribute' | 'remove-attribute' |
'add-element' | 'modify-element' | 'remove-element' |
'add-css' | 'modify-css';
/** Current state */
before: string;
/** Desired state */
after: string;
/** Explanation of change */
explanation: string;
/** Context */
context?: {
surroundingCode?: string;
relatedElements?: string[];
};
}
export interface AutoFix {
/** Safe to apply automatically */
safe: boolean;
/** Patch to apply */
patch: {
format: 'unified-diff' | 'json-patch';
content: string;
};
/** Test to verify fix */
verification?: {
test: string;
expectedResult: string;
};
/** Rollback information */
rollback?: {
enabled: boolean;
preserveOriginal?: boolean;
};
}
export interface Resource {
title: string;
url: string;
type: 'documentation' | 'tutorial' | 'example' | 'tool';
}
2.4 Context Analysis Types
/**
* Element context for intelligent remediation
*/
export interface ElementContext {
/** The element being analyzed */
element: {
tagName: string;
attributes: Record<string, string>;
innerHTML: string;
outerHTML: string;
};
/** Surrounding context */
surrounding: {
/** Text before element */
precedingText?: string;
/** Text after element */
followingText?: string;
/** Nearby headings */
nearbyHeadings?: string[];
/** Parent container description */
parentDescription?: string;
};
/** Semantic context */
semantic: {
/** Inferred purpose */
purpose?: string;
/** User flow/journey */
userFlow?: string;
/** Page section */
section?: string;
/** Related form (if applicable) */
relatedForm?: FormContext;
};
/** Visual context */
visual?: {
/** Position on page */
position: 'header' | 'footer' | 'sidebar' | 'main' | 'nav';
/** Visibility */
visible: boolean;
/** Colors */
colors?: {
foreground: string;
background: string;
contrast: number;
};
};
/** Interactive context */
interactive?: {
/** Is interactive */
isInteractive: boolean;
/** Event handlers */
eventHandlers: string[];
/** Focus state */
focusable: boolean;
/** Tab index */
tabIndex?: number;
};
}
export interface FormContext {
formId?: string;
formPurpose?: string;
fieldType?: string;
validationRules?: string[];
relatedFields?: string[];
}
3. API Specifications
3.1 MCP Tool: scan-comprehensive
Tool Name: mcp__agentic_qe__a11y_scan_comprehensive
Description: Perform comprehensive WCAG 2.2 accessibility scan with context-aware remediation
Parameters:
{
target: URLTarget | HTMLTarget;
level: 'A' | 'AA' | 'AAA';
config: ScanConfiguration;
options?: AdvancedOptions;
}
Returns:
QEToolResponse<AccessibilityScanResult>
Example Usage:
const result = await mcp__agentic_qe__a11y_scan_comprehensive({
target: {
type: 'url',
url: 'https://example.com'
},
level: 'AA',
config: {
validators: {
axeCore: true,
playwright: true
},
tests: {
keyboard: true,
screenReader: true,
colorContrast: true
},
remediation: {
enabled: true,
contextAware: true,
generateTests: true
}
},
options: {
screenshots: true,
learning: {
queryPastPatterns: true,
storeLearnings: true
}
}
});
if (result.success) {
console.log(`Compliance Score: ${result.data.compliance.score}%`);
console.log(`Violations Found: ${result.data.violations.length}`);
console.log(`Remediations: ${result.data.remediations.length}`);
}
3.2 MCP Tool: analyze-context
Tool Name: mcp__agentic_qe__a11y_analyze_context
Description: Analyze element context for intelligent remediation suggestions
Parameters:
{
element: {
selector: string;
html: string;
};
pageContext?: {
url?: string;
fullHTML?: string;
};
options?: {
inferPurpose?: boolean;
analyzeSemantics?: boolean;
visualAnalysis?: boolean;
};
}
Returns:
QEToolResponse<{
context: ElementContext;
suggestions: RemediationSuggestion[];
confidence: number;
}>
3.3 MCP Tool: generate-remediation
Tool Name: mcp__agentic_qe__a11y_generate_remediation
Description: Generate context-aware remediation for specific violations
Parameters:
{
violations: Violation[];
context: ElementContext[];
options?: {
prioritize?: boolean;
includeAutoFix?: boolean;
includeExamples?: boolean;
};
}
Returns:
QEToolResponse<{
remediations: Remediation[];
priorityOrder: string[];
estimatedTotalEffort: number;
}>
3.4 MCP Tool: test-keyboard-navigation
Tool Name: mcp__agentic_qe__a11y_test_keyboard_navigation
Description: Test keyboard navigation and focus management
Parameters:
{
target: URLTarget;
options?: {
testTabOrder?: boolean;
testFocusIndicators?: boolean;
testKeyboardTraps?: boolean;
testSkipLinks?: boolean;
};
}
Returns:
QEToolResponse<{
passed: boolean;
issues: KeyboardNavigationIssue[];
tabOrder: string[];
focusIndicators: boolean;
keyboardTraps: KeyboardTrap[];
}>
4. Algorithms
4.1 Context-Aware ARIA Label Generation
/**
* Generate appropriate ARIA label from element context
*
* Algorithm:
* 1. Extract element and surrounding context
* 2. Identify element purpose through multiple signals:
* a. Icon analysis (SVG content, classes)
* b. Surrounding text proximity scoring
* c. Parent container semantic analysis
* d. Form relationship inference
* 3. Combine signals with confidence weights
* 4. Generate natural language label
* 5. Validate against ARIA best practices
*/
async function generateARIALabel(
element: Element,
context: ElementContext
): Promise<{ label: string; confidence: number }> {
const signals: Signal[] = [];
// Signal 1: Icon analysis (weight: 0.3)
const iconSignal = analyzeIcon(element);
if (iconSignal) {
signals.push({ type: 'icon', value: iconSignal, weight: 0.3 });
}
// Signal 2: Surrounding text (weight: 0.4)
const textSignal = analyzeSurroundingText(context.surrounding);
if (textSignal) {
signals.push({ type: 'text', value: textSignal, weight: 0.4 });
}
// Signal 3: Parent container (weight: 0.2)
const parentSignal = analyzeParentContainer(context.surrounding.parentDescription);
if (parentSignal) {
signals.push({ type: 'parent', value: parentSignal, weight: 0.2 });
}
// Signal 4: Form relationship (weight: 0.1)
if (context.semantic.relatedForm) {
const formSignal = analyzeFormRelationship(context.semantic.relatedForm);
if (formSignal) {
signals.push({ type: 'form', value: formSignal, weight: 0.1 });
}
}
// Combine signals
const combinedLabel = combineSignals(signals);
const confidence = calculateConfidence(signals);
return { label: combinedLabel, confidence };
}
function analyzeIcon(element: Element): string | null {
// Check for common icon patterns
const svg = element.querySelector('svg');
if (!svg) return null;
// Icon class analysis
const classes = element.className.toLowerCase();
if (classes.includes('cart')) return 'shopping cart';
if (classes.includes('user')) return 'user profile';
if (classes.includes('search')) return 'search';
// ... more patterns
// SVG content analysis
const svgContent = svg.innerHTML.toLowerCase();
if (svgContent.includes('path') && svgContent.includes('cart')) {
return 'shopping cart';
}
return null;
}
function analyzeSurroundingText(surrounding: ElementContext['surrounding']): string | null {
const precedingText = surrounding.precedingText?.trim();
const followingText = surrounding.followingText?.trim();
// Prefer preceding text (usually more relevant)
if (precedingText && precedingText.length < 50) {
return precedingText;
}
// Check nearby headings
if (surrounding.nearbyHeadings && surrounding.nearbyHeadings.length > 0) {
return surrounding.nearbyHeadings[0];
}
// Fallback to following text
if (followingText && followingText.length < 50) {
return followingText;
}
return null;
}
function combineSignals(signals: Signal[]): string {
// Weight-based combination
let combinedLabel = '';
let totalWeight = 0;
// Prefer higher-weight signals
signals.sort((a, b) => b.weight - a.weight);
for (const signal of signals) {
if (signal.value && totalWeight < 1.0) {
if (!combinedLabel) {
combinedLabel = signal.value;
}
totalWeight += signal.weight;
}
}
return combinedLabel || 'Interactive element';
}
function calculateConfidence(signals: Signal[]): number {
if (signals.length === 0) return 0.3;
const totalWeight = signals.reduce((sum, s) => sum + s.weight, 0);
const hasHighConfidenceSignal = signals.some(s => s.weight >= 0.3);
let confidence = totalWeight;
if (hasHighConfidenceSignal) confidence += 0.1;
if (signals.length >= 3) confidence += 0.1;
return Math.min(confidence, 1.0);
}
4.2 Color Contrast Optimization
/**
* Suggest color adjustments to meet WCAG contrast requirements
*
* Algorithm:
* 1. Calculate current contrast ratio
* 2. Determine required ratio based on WCAG level
* 3. Identify which color to adjust (prefer background)
* 4. Calculate minimum adjustment needed
* 5. Suggest nearest accessible color from palette
* 6. Preserve brand colors when possible
*/
async function optimizeColorContrast(
foreground: string,
background: string,
level: WCAGLevel,
textSize: 'normal' | 'large'
): Promise<ContrastOptimization> {
// Calculate current contrast
const currentRatio = calculateContrastRatio(foreground, background);
// Determine required ratio
const requiredRatio = getRequiredRatio(level, textSize);
// Already passes
if (currentRatio >= requiredRatio) {
return {
passes: true,
currentRatio,
requiredRatio,
suggestions: []
};
}
// Calculate adjustment needed
const adjustmentNeeded = requiredRatio - currentRatio;
// Generate suggestions
const suggestions: ColorSuggestion[] = [];
// Suggestion 1: Darken background
const darkerBg = adjustColor(background, 'darken', adjustmentNeeded);
if (calculateContrastRatio(foreground, darkerBg) >= requiredRatio) {
suggestions.push({
type: 'background',
original: background,
suggested: darkerBg,
ratio: calculateContrastRatio(foreground, darkerBg),
change: 'darken'
});
}
// Suggestion 2: Lighten background
const lighterBg = adjustColor(background, 'lighten', adjustmentNeeded);
if (calculateContrastRatio(foreground, lighterBg) >= requiredRatio) {
suggestions.push({
type: 'background',
original: background,
suggested: lighterBg,
ratio: calculateContrastRatio(foreground, lighterBg),
change: 'lighten'
});
}
// Suggestion 3: Adjust foreground (less preferred)
const darkerFg = adjustColor(foreground, 'darken', adjustmentNeeded);
if (calculateContrastRatio(darkerFg, background) >= requiredRatio) {
suggestions.push({
type: 'foreground',
original: foreground,
suggested: darkerFg,
ratio: calculateContrastRatio(darkerFg, background),
change: 'darken',
priority: 'low' // Prefer background changes
});
}
// Sort by minimal change and priority
suggestions.sort((a, b) => {
if (a.priority === 'low' && b.priority !== 'low') return 1;
if (a.priority !== 'low' && b.priority === 'low') return -1;
return Math.abs(a.ratio - requiredRatio) - Math.abs(b.ratio - requiredRatio);
});
return {
passes: false,
currentRatio,
requiredRatio,
suggestions
};
}
5. Integration Patterns
5.1 Learning Integration
/**
* Learning protocol for a11y-ally agent
*/
// BEFORE task: Query past learnings
async function queryPastLearnings() {
const learnings = await mcp__agentic_qe__learning_query({
agentId: 'qe-a11y-ally',
taskType: 'accessibility-scan',
minReward: 0.8,
queryType: 'all',
limit: 10
});
// Apply learned patterns
if (learnings.success) {
applyLearnedPatterns(learnings.data);
}
}
// AFTER task: Store learnings
async function storeLearnings(scanResult: AccessibilityScanResult) {
// Calculate reward
const reward = calculateReward(scanResult);
// Store experience
await mcp__agentic_qe__learning_store_experience({
agentId: 'qe-a11y-ally',
taskType: 'accessibility-scan',
reward,
outcome: {
violationsDetected: scanResult.violations.length,
complianceScore: scanResult.compliance.score,
remediationsGenerated: scanResult.remediations.length,
contextAccuracy: calculateContextAccuracy(scanResult)
},
metadata: {
wcagLevel: scanResult.metadata.level,
toolsUsed: scanResult.metadata.toolsUsed,
targetType: 'url'
}
});
// Store successful remediation patterns
for (const remediation of scanResult.remediations) {
if (remediation.confidence >= 0.9) {
await mcp__agentic_qe__learning_store_pattern({
pattern: `${remediation.type}: ${remediation.suggestion.description}`,
confidence: remediation.confidence,
domain: 'accessibility-remediation',
metadata: {
violationType: remediation.type,
autoFixable: remediation.autoFixable
}
});
}
}
}
function calculateReward(scanResult: AccessibilityScanResult): number {
const { violations, compliance, performance } = scanResult;
// Base score from compliance
let reward = compliance.score / 100;
// Bonus for comprehensive detection
if (violations.length > 0) {
reward += 0.1; // Found issues
}
// Bonus for fast performance
if (performance.scanTime < 10000) {
reward += 0.1;
}
// Penalty for low confidence remediations
const lowConfidenceCount = scanResult.remediations.filter(
r => r.confidence < 0.7
).length;
reward -= lowConfidenceCount * 0.05;
return Math.max(0, Math.min(1, reward));
}
5.2 Fleet Coordination
/**
* Coordinate with other QE agents
*/
// Example: Full accessibility workflow
async function fullAccessibilityWorkflow(url: string) {
// 1. Scan for violations (a11y-ally)
const scanResult = await Task(
'Scan accessibility',
`Comprehensive WCAG 2.2 AA scan of ${url}`,
'qe-a11y-ally'
);
// Store results in memory for other agents
await mcp__agentic_qe__memory_store({
key: `aqe/accessibility/scan-results/${scanResult.scanId}`,
value: scanResult,
namespace: 'aqe',
persist: true
});
// 2. Generate tests from violations (test-generator)
if (scanResult.violations.length > 0) {
await Task(
'Generate a11y tests',
`Generate regression tests for ${scanResult.violations.length} violations`,
'qe-test-generator'
);
}
// 3. Visual regression for accessibility (visual-tester)
await Task(
'Visual a11y check',
'Capture annotated screenshots of violations',
'qe-visual-tester'
);
// 4. Quality gate check (quality-gate)
const gateResult = await Task(
'A11y quality gate',
`Check if compliance score ${scanResult.compliance.score}% passes threshold`,
'qe-quality-gate'
);
return {
scanResult,
gateResult,
productionReady: gateResult.passed
};
}
6. Performance Considerations
6.1 Optimization Strategies
-
Parallel Scanning
- Run axe-core and Playwright tests concurrently
- Analyze multiple pages in parallel
- Use worker threads for intensive computation
-
Caching
- Cache analyzed element contexts
- Store ARIA label suggestions
- Reuse color contrast calculations
-
Progressive Scanning
- Scan critical elements first
- Defer non-critical analysis
- Stream results as they're found
-
Resource Limits
- Maximum scan time: 30 seconds per page
- Maximum concurrent pages: 5
- Maximum elements analyzed: 1000 per page
6.2 Performance Targets
| Metric | Target | Rationale |
|---|---|---|
| Single page scan | <10s | Fast enough for CI/CD |
| Multi-page scan (10 pages) | <60s | Acceptable for comprehensive audits |
| Memory usage | <512MB | DevPod/Codespace friendly |
| CPU usage | <80% | Leave room for other processes |
7. Security Considerations
7.1 Input Validation
/**
* Validate and sanitize inputs
*/
function validateScanParams(params: AccessibilityScanParams): void {
// URL validation
if (params.target.type === 'url') {
const url = new URL(params.target.url); // Throws if invalid
// Block dangerous protocols
if (!['http:', 'https:'].includes(url.protocol)) {
throw new Error('Only HTTP/HTTPS protocols allowed');
}
// Block localhost in production
if (process.env.NODE_ENV === 'production' &&
url.hostname === 'localhost') {
throw new Error('Cannot scan localhost in production');
}
}
// HTML validation
if (params.target.type === 'html') {
// Sanitize HTML to prevent XSS
params.target.html = sanitizeHTML(params.target.html);
}
// Selector validation (prevent injection)
if (params.config.scope?.include) {
params.config.scope.include = params.config.scope.include.map(
selector => sanitizeSelector(selector)
);
}
}
7.2 Safe Auto-Fix
/**
* Ensure auto-fixes are safe to apply
*/
function validateAutoFix(autoFix: AutoFix): boolean {
// Only allow safe attribute additions/modifications
const safeChanges = [
'add-aria-label',
'add-alt-text',
'add-aria-describedby'
];
// Never allow:
// - Script injection
// - Removing security-related attributes
// - Modifying authentication elements
return autoFix.safe &&
autoFix.verification !== undefined &&
!containsDangerousPatterns(autoFix.patch.content);
}
8. Error Handling
8.1 Error Types
export enum A11yErrorCode {
// Scan errors
SCAN_TIMEOUT = 'A11Y_SCAN_TIMEOUT',
SCAN_FAILED = 'A11Y_SCAN_FAILED',
INVALID_TARGET = 'A11Y_INVALID_TARGET',
// Analysis errors
CONTEXT_ANALYSIS_FAILED = 'A11Y_CONTEXT_ANALYSIS_FAILED',
REMEDIATION_GENERATION_FAILED = 'A11Y_REMEDIATION_GENERATION_FAILED',
// Tool errors
AXE_CORE_ERROR = 'A11Y_AXE_CORE_ERROR',
PLAYWRIGHT_ERROR = 'A11Y_PLAYWRIGHT_ERROR',
// Learning errors
LEARNING_QUERY_FAILED = 'A11Y_LEARNING_QUERY_FAILED',
LEARNING_STORE_FAILED = 'A11Y_LEARNING_STORE_FAILED'
}
export interface A11yError extends QEError {
code: A11yErrorCode;
recoverable: boolean;
retryable: boolean;
}
8.2 Graceful Degradation
/**
* Handle tool failures gracefully
*/
async function scanWithGracefulDegradation(
params: AccessibilityScanParams
): Promise<AccessibilityScanResult> {
const results: Partial<AccessibilityScanResult> = {
violations: [],
remediations: []
};
// Try axe-core
try {
const axeResult = await runAxeCore(params);
results.violations.push(...axeResult.violations);
} catch (error) {
logError('axe-core failed, continuing with other tools', error);
}
// Try Playwright
try {
const playwrightResult = await runPlaywright(params);
results.violations.push(...playwrightResult.violations);
} catch (error) {
logError('Playwright failed, continuing with other tools', error);
}
// Try custom heuristics
try {
const customResult = await runCustomHeuristics(params);
results.violations.push(...customResult.violations);
} catch (error) {
logError('Custom heuristics failed', error);
}
// If all tools failed, throw
if (results.violations.length === 0) {
throw new Error('All scanning tools failed');
}
return results as AccessibilityScanResult;
}
9. Testing Specifications
9.1 Unit Test Coverage Requirements
Minimum Coverage: 95%
Critical Paths (100% coverage required):
scan-comprehensive.ts- All code pathsanalyze-context.ts- Context inference logicgenerate-remediation.ts- Suggestion generationtest-keyboard-navigation.ts- Keyboard testing
9.2 Integration Test Scenarios
- Full scan workflow
- Context analysis pipeline
- Remediation generation accuracy
- Fleet coordination
- Learning integration
9.3 Journey Test Scenarios
- Developer fixes violations
- CI/CD integration
- Fleet coordination workflow
10. Monitoring & Observability
10.1 Metrics to Track
interface A11yMetrics {
// Scan metrics
totalScans: number;
successfulScans: number;
failedScans: number;
averageScanTime: number;
// Detection metrics
violationsDetected: number;
violationsByType: Record<RemediationType, number>;
violationsBySeverity: Record<ViolationSeverity, number>;
// Remediation metrics
remediationsGenerated: number;
autoFixableCount: number;
averageConfidence: number;
// Learning metrics
patternsLearned: number;
patternAccuracy: number;
// User impact metrics
averageFixTime: number; // After vs before
complianceImprovement: number;
}
10.2 Logging
// Structured logging for observability
logger.info('A11y scan started', {
scanId,
target: params.target,
level: params.level,
timestamp: Date.now()
});
logger.info('Violations detected', {
scanId,
count: violations.length,
critical: violations.filter(v => v.severity === 'critical').length
});
logger.info('Remediations generated', {
scanId,
count: remediations.length,
autoFixable: remediations.filter(r => r.autoFixable).length
});
Document Status: Draft - Ready for Implementation Next Steps: Begin Milestone 1 development