A11y-Ally Agent - Technical Specification
- 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
13
4export interface AccessibilityScanParams {
5
6 target: URLTarget | HTMLTarget;
7
8
9 level: WCAGLevel;
10
11
12 config: ScanConfiguration;
13
14
15 options?: AdvancedOptions;
16}
17
18export type WCAGLevel = 'A' | 'AA' | 'AAA';
19
20export interface URLTarget {
21 type: 'url';
22 url: string;
23
24 authentication?: {
25 type: 'basic' | 'bearer' | 'cookie';
26 credentials: Record<string, string>;
27 };
28}
29
30export interface HTMLTarget {
31 type: 'html';
32 html: string;
33 baseUrl?: string;
34}
35
36export interface ScanConfiguration {
37
38 scope?: {
39 include?: string[]; // CSS selectors
40 exclude?: string[]; // CSS selectors
41 pages?: string[]; // Multiple pages
42 };
43
44
45 validators: {
46 axeCore?: boolean;
47 playwright?: boolean;
48 customRules?: boolean;
49 };
50
51
52 tests: {
53 keyboard?: boolean;
54 screenReader?: boolean;
55 colorContrast?: boolean;
56 semanticHTML?: boolean;
57 focus?: boolean;
58 };
59
60
61 remediation: {
62 enabled: boolean;
63 contextAware?: boolean;
64 generateTests?: boolean;
65 autoFixSafe?: boolean;
66 };
67}
68
69export interface AdvancedOptions {
70
71 browser?: 'chromium' | 'firefox' | 'webkit';
72
73
74 viewport?: { width: number; height: number };
75
76
77 screenshots?: boolean;
78
79
80 learning?: {
81 queryPastPatterns?: boolean;
82 storeLearnings?: boolean;
83 };
84
85
86 timeout?: number;
87 maxConcurrency?: number;
88}
#2.2 Result Types
13
4export interface AccessibilityScanResult {
5
6 scanId: string;
7
8
9 timestamp: string;
10
11
12 metadata: ScanMetadata;
13
14
15 compliance: ComplianceStatus;
16
17
18 violations: Violation[];
19
20
21 remediations: Remediation[];
22
23
24 testCases?: TestCase[];
25
26
27 pourAnalysis: POURAnalysis;
28
29
30 performance: PerformanceMetrics;
31
32
33 raw?: {
34 axeCore?: AxeCoreResult;
35 playwright?: PlaywrightResult;
36 };
37}
38
39export interface ScanMetadata {
40 target: string;
41 level: WCAGLevel;
42 scanDuration: number;
43 elementsAnalyzed: number;
44 toolsUsed: string[];
45 browserUsed: string;
46 viewport: { width: number; height: number };
47}
48
49export interface ComplianceStatus {
50
51 status: 'compliant' | 'partially-compliant' | 'non-compliant';
52
53
54 score: number;
55
56
57 level: WCAGLevel;
58
59
60 byCriterion: Record<string, {
61 passed: boolean;
62 violations: number;
63 }>;
64
65
66 productionReady: boolean;
67
68
69 estimatedEffort: {
70 hours: number;
71 complexity: 'trivial' | 'low' | 'medium' | 'high';
72 };
73}
74
75export interface Violation {
76
77 id: string;
78
79
80 criterion: WCAGCriterion;
81
82
83 severity: ViolationSeverity;
84
85
86 impact: ImpactAssessment;
87
88
89 elements: ViolationElement[];
90
91
92 detectedBy: DetectionSource;
93
94
95 confidence: number; // 0-1
96
97
98 relatedViolations?: string[];
99}
100
101export interface WCAGCriterion {
102
103 number: string;
104
105
106 name: string;
107
108
109 level: WCAGLevel;
110
111
112 category: 'perceivable' | 'operable' | 'understandable' | 'robust';
113
114
115 url: string;
116}
117
118export type ViolationSeverity = 'critical' | 'serious' | 'moderate' | 'minor';
119
120export interface ImpactAssessment {
121
122 description: string;
123
124
125 affectedGroups: UserGroup[];
126
127
128 estimatedUsers?: number;
129
130
131 businessImpact?: 'high' | 'medium' | 'low';
132
133
134 legalRisk?: 'high' | 'medium' | 'low';
135}
136
137export type UserGroup =
138 | 'blind'
139 | 'low-vision'
140 | 'color-blind'
141 | 'deaf'
142 | 'hard-of-hearing'
143 | 'motor-impaired'
144 | 'cognitive-impaired'
145 | 'seizure-sensitive';
146
147export interface ViolationElement {
148
149 selector: string;
150
151
152 html: string;
153
154
155 location: {
156 x: number;
157 y: number;
158 width: number;
159 height: number;
160 };
161
162
163 a11yTree?: {
164 role: string;
165 name: string;
166 description?: string;
167 states?: string[];
168 };
169
170
171 screenshot?: string;
172}
173
174export type DetectionSource =
175 | 'axe-core'
176 | 'playwright'
177 | 'custom-heuristic'
178 | 'ml-model';
13
4export interface Remediation {
5
6 id: string;
7
8
9 violationIds: string[];
10
11
12 type: RemediationType;
13
14
15 priority: 'critical' | 'high' | 'medium' | 'low';
16
17
18 suggestion: RemediationSuggestion;
19
20
21 autoFixable: boolean;
22
23
24 autoFix?: AutoFix;
25
26
27 confidence: number; // 0-1
28
29
30 validation?: {
31 tested: boolean;
32 successful?: boolean;
33 testResult?: string;
34 };
35}
36
37export type RemediationType =
38 | 'aria-label'
39 | 'aria-describedby'
40 | 'alt-text'
41 | 'color-contrast'
42 | 'keyboard-navigation'
43 | 'focus-management'
44 | 'semantic-html'
45 | 'heading-structure'
46 | 'form-labels'
47 | 'captions'
48 | 'transcripts'
49 | 'skip-links'
50 | 'landmarks'
51 | 'other';
52
53export interface RemediationSuggestion {
54
55 title: string;
56
57
58 description: string;
59
60
61 codeChanges: CodeChange[];
62
63
64 reasoning: string;
65
66
67 steps: string[];
68
69
70 resources?: Resource[];
71
72
73 effort: {
74 time: 'minutes' | 'hours' | 'days';
75 amount: number;
76 complexity: 'trivial' | 'low' | 'medium' | 'high';
77 };
78
79
80 example?: {
81 before: string;
82 after: string;
83 explanation: string;
84 };
85}
86
87export interface CodeChange {
88
89 file?: string;
90
91
92 selector: string;
93
94
95 type: 'add-attribute' | 'modify-attribute' | 'remove-attribute' |
96 'add-element' | 'modify-element' | 'remove-element' |
97 'add-css' | 'modify-css';
98
99
100 before: string;
101
102
103 after: string;
104
105
106 explanation: string;
107
108
109 context?: {
110 surroundingCode?: string;
111 relatedElements?: string[];
112 };
113}
114
115export interface AutoFix {
116
117 safe: boolean;
118
119
120 patch: {
121 format: 'unified-diff' | 'json-patch';
122 content: string;
123 };
124
125
126 verification?: {
127 test: string;
128 expectedResult: string;
129 };
130
131
132 rollback?: {
133 enabled: boolean;
134 preserveOriginal?: boolean;
135 };
136}
137
138export interface Resource {
139 title: string;
140 url: string;
141 type: 'documentation' | 'tutorial' | 'example' | 'tool';
142}
#2.4 Context Analysis Types
13
4export interface ElementContext {
5
6 element: {
7 tagName: string;
8 attributes: Record<string, string>;
9 innerHTML: string;
10 outerHTML: string;
11 };
12
13
14 surrounding: {
15
16 precedingText?: string;
17
18
19 followingText?: string;
20
21
22 nearbyHeadings?: string[];
23
24
25 parentDescription?: string;
26 };
27
28
29 semantic: {
30
31 purpose?: string;
32
33
34 userFlow?: string;
35
36
37 section?: string;
38
39
40 relatedForm?: FormContext;
41 };
42
43
44 visual?: {
45
46 position: 'header' | 'footer' | 'sidebar' | 'main' | 'nav';
47
48
49 visible: boolean;
50
51
52 colors?: {
53 foreground: string;
54 background: string;
55 contrast: number;
56 };
57 };
58
59
60 interactive?: {
61
62 isInteractive: boolean;
63
64
65 eventHandlers: string[];
66
67
68 focusable: boolean;
69
70
71 tabIndex?: number;
72 };
73}
74
75export interface FormContext {
76 formId?: string;
77 formPurpose?: string;
78 fieldType?: string;
79 validationRules?: string[];
80 relatedFields?: string[];
81}
#3. API Specifications
Tool Name: mcp__agentic_qe__a11y_scan_comprehensive
Description: Perform comprehensive WCAG 2.2 accessibility scan with context-aware remediation
Parameters:
1{
2 target: URLTarget | HTMLTarget;
3 level: 'A' | 'AA' | 'AAA';
4 config: ScanConfiguration;
5 options?: AdvancedOptions;
6}
Returns:
1QEToolResponse<AccessibilityScanResult>
Example Usage:
1const result = await mcp__agentic_qe__a11y_scan_comprehensive({
2 target: {
3 type: 'url',
4 url: 'https://example.com'
5 },
6 level: 'AA',
7 config: {
8 validators: {
9 axeCore: true,
10 playwright: true
11 },
12 tests: {
13 keyboard: true,
14 screenReader: true,
15 colorContrast: true
16 },
17 remediation: {
18 enabled: true,
19 contextAware: true,
20 generateTests: true
21 }
22 },
23 options: {
24 screenshots: true,
25 learning: {
26 queryPastPatterns: true,
27 storeLearnings: true
28 }
29 }
30});
31
32if (result.success) {
33 console.log(`Compliance Score: ${result.data.compliance.score}%`);
34 console.log(`Violations Found: ${result.data.violations.length}`);
35 console.log(`Remediations: ${result.data.remediations.length}`);
36}
#3.2 MCP Tool: analyze-context
Tool Name: mcp__agentic_qe__a11y_analyze_context
Description: Analyze element context for intelligent remediation suggestions
Parameters:
1{
2 element: {
3 selector: string;
4 html: string;
5 };
6 pageContext?: {
7 url?: string;
8 fullHTML?: string;
9 };
10 options?: {
11 inferPurpose?: boolean;
12 analyzeSemantics?: boolean;
13 visualAnalysis?: boolean;
14 };
15}
Returns:
1QEToolResponse<{
2 context: ElementContext;
3 suggestions: RemediationSuggestion[];
4 confidence: number;
5}>
Tool Name: mcp__agentic_qe__a11y_generate_remediation
Description: Generate context-aware remediation for specific violations
Parameters:
1{
2 violations: Violation[];
3 context: ElementContext[];
4 options?: {
5 prioritize?: boolean;
6 includeAutoFix?: boolean;
7 includeExamples?: boolean;
8 };
9}
Returns:
1QEToolResponse<{
2 remediations: Remediation[];
3 priorityOrder: string[];
4 estimatedTotalEffort: number;
5}>
Tool Name: mcp__agentic_qe__a11y_test_keyboard_navigation
Description: Test keyboard navigation and focus management
Parameters:
1{
2 target: URLTarget;
3 options?: {
4 testTabOrder?: boolean;
5 testFocusIndicators?: boolean;
6 testKeyboardTraps?: boolean;
7 testSkipLinks?: boolean;
8 };
9}
Returns:
1QEToolResponse<{
2 passed: boolean;
3 issues: KeyboardNavigationIssue[];
4 tabOrder: string[];
5 focusIndicators: boolean;
6 keyboardTraps: KeyboardTrap[];
7}>
#4. Algorithms
#4.1 Context-Aware ARIA Label Generation
114
15async function generateARIALabel(
16 element: Element,
17 context: ElementContext
18): Promise<{ label: string; confidence: number }> {
19 const signals: Signal[] = [];
20
21 // Signal 1: Icon analysis (weight: 0.3)
22 const iconSignal = analyzeIcon(element);
23 if (iconSignal) {
24 signals.push({ type: 'icon', value: iconSignal, weight: 0.3 });
25 }
26
27 // Signal 2: Surrounding text (weight: 0.4)
28 const textSignal = analyzeSurroundingText(context.surrounding);
29 if (textSignal) {
30 signals.push({ type: 'text', value: textSignal, weight: 0.4 });
31 }
32
33 // Signal 3: Parent container (weight: 0.2)
34 const parentSignal = analyzeParentContainer(context.surrounding.parentDescription);
35 if (parentSignal) {
36 signals.push({ type: 'parent', value: parentSignal, weight: 0.2 });
37 }
38
39 // Signal 4: Form relationship (weight: 0.1)
40 if (context.semantic.relatedForm) {
41 const formSignal = analyzeFormRelationship(context.semantic.relatedForm);
42 if (formSignal) {
43 signals.push({ type: 'form', value: formSignal, weight: 0.1 });
44 }
45 }
46
47 // Combine signals
48 const combinedLabel = combineSignals(signals);
49 const confidence = calculateConfidence(signals);
50
51 return { label: combinedLabel, confidence };
52}
53
54function analyzeIcon(element: Element): string | null {
55 // Check for common icon patterns
56 const svg = element.querySelector('svg');
57 if (!svg) return null;
58
59 // Icon class analysis
60 const classes = element.className.toLowerCase();
61 if (classes.includes('cart')) return 'shopping cart';
62 if (classes.includes('user')) return 'user profile';
63 if (classes.includes('search')) return 'search';
64 // ... more patterns
65
66 // SVG content analysis
67 const svgContent = svg.innerHTML.toLowerCase();
68 if (svgContent.includes('path') && svgContent.includes('cart')) {
69 return 'shopping cart';
70 }
71
72 return null;
73}
74
75function analyzeSurroundingText(surrounding: ElementContext['surrounding']): string | null {
76 const precedingText = surrounding.precedingText?.trim();
77 const followingText = surrounding.followingText?.trim();
78
79 // Prefer preceding text (usually more relevant)
80 if (precedingText && precedingText.length < 50) {
81 return precedingText;
82 }
83
84 // Check nearby headings
85 if (surrounding.nearbyHeadings && surrounding.nearbyHeadings.length > 0) {
86 return surrounding.nearbyHeadings[0];
87 }
88
89 // Fallback to following text
90 if (followingText && followingText.length < 50) {
91 return followingText;
92 }
93
94 return null;
95}
96
97function combineSignals(signals: Signal[]): string {
98 // Weight-based combination
99 let combinedLabel = '';
100 let totalWeight = 0;
101
102 // Prefer higher-weight signals
103 signals.sort((a, b) => b.weight - a.weight);
104
105 for (const signal of signals) {
106 if (signal.value && totalWeight < 1.0) {
107 if (!combinedLabel) {
108 combinedLabel = signal.value;
109 }
110 totalWeight += signal.weight;
111 }
112 }
113
114 return combinedLabel || 'Interactive element';
115}
116
117function calculateConfidence(signals: Signal[]): number {
118 if (signals.length === 0) return 0.3;
119
120 const totalWeight = signals.reduce((sum, s) => sum + s.weight, 0);
121 const hasHighConfidenceSignal = signals.some(s => s.weight >= 0.3);
122
123 let confidence = totalWeight;
124 if (hasHighConfidenceSignal) confidence += 0.1;
125 if (signals.length >= 3) confidence += 0.1;
126
127 return Math.min(confidence, 1.0);
128}
#4.2 Color Contrast Optimization
111
12async function optimizeColorContrast(
13 foreground: string,
14 background: string,
15 level: WCAGLevel,
16 textSize: 'normal' | 'large'
17): Promise<ContrastOptimization> {
18 // Calculate current contrast
19 const currentRatio = calculateContrastRatio(foreground, background);
20
21 // Determine required ratio
22 const requiredRatio = getRequiredRatio(level, textSize);
23
24 // Already passes
25 if (currentRatio >= requiredRatio) {
26 return {
27 passes: true,
28 currentRatio,
29 requiredRatio,
30 suggestions: []
31 };
32 }
33
34 // Calculate adjustment needed
35 const adjustmentNeeded = requiredRatio - currentRatio;
36
37 // Generate suggestions
38 const suggestions: ColorSuggestion[] = [];
39
40 // Suggestion 1: Darken background
41 const darkerBg = adjustColor(background, 'darken', adjustmentNeeded);
42 if (calculateContrastRatio(foreground, darkerBg) >= requiredRatio) {
43 suggestions.push({
44 type: 'background',
45 original: background,
46 suggested: darkerBg,
47 ratio: calculateContrastRatio(foreground, darkerBg),
48 change: 'darken'
49 });
50 }
51
52 // Suggestion 2: Lighten background
53 const lighterBg = adjustColor(background, 'lighten', adjustmentNeeded);
54 if (calculateContrastRatio(foreground, lighterBg) >= requiredRatio) {
55 suggestions.push({
56 type: 'background',
57 original: background,
58 suggested: lighterBg,
59 ratio: calculateContrastRatio(foreground, lighterBg),
60 change: 'lighten'
61 });
62 }
63
64 // Suggestion 3: Adjust foreground (less preferred)
65 const darkerFg = adjustColor(foreground, 'darken', adjustmentNeeded);
66 if (calculateContrastRatio(darkerFg, background) >= requiredRatio) {
67 suggestions.push({
68 type: 'foreground',
69 original: foreground,
70 suggested: darkerFg,
71 ratio: calculateContrastRatio(darkerFg, background),
72 change: 'darken',
73 priority: 'low' // Prefer background changes
74 });
75 }
76
77 // Sort by minimal change and priority
78 suggestions.sort((a, b) => {
79 if (a.priority === 'low' && b.priority !== 'low') return 1;
80 if (a.priority !== 'low' && b.priority === 'low') return -1;
81 return Math.abs(a.ratio - requiredRatio) - Math.abs(b.ratio - requiredRatio);
82 });
83
84 return {
85 passes: false,
86 currentRatio,
87 requiredRatio,
88 suggestions
89 };
90}
#5. Integration Patterns
#5.1 Learning Integration
13
4
5// BEFORE task: Query past learnings
6async function queryPastLearnings() {
7 const learnings = await mcp__agentic_qe__learning_query({
8 agentId: 'qe-a11y-ally',
9 taskType: 'accessibility-scan',
10 minReward: 0.8,
11 queryType: 'all',
12 limit: 10
13 });
14
15 // Apply learned patterns
16 if (learnings.success) {
17 applyLearnedPatterns(learnings.data);
18 }
19}
20
21// AFTER task: Store learnings
22async function storeLearnings(scanResult: AccessibilityScanResult) {
23 // Calculate reward
24 const reward = calculateReward(scanResult);
25
26 // Store experience
27 await mcp__agentic_qe__learning_store_experience({
28 agentId: 'qe-a11y-ally',
29 taskType: 'accessibility-scan',
30 reward,
31 outcome: {
32 violationsDetected: scanResult.violations.length,
33 complianceScore: scanResult.compliance.score,
34 remediationsGenerated: scanResult.remediations.length,
35 contextAccuracy: calculateContextAccuracy(scanResult)
36 },
37 metadata: {
38 wcagLevel: scanResult.metadata.level,
39 toolsUsed: scanResult.metadata.toolsUsed,
40 targetType: 'url'
41 }
42 });
43
44 // Store successful remediation patterns
45 for (const remediation of scanResult.remediations) {
46 if (remediation.confidence >= 0.9) {
47 await mcp__agentic_qe__learning_store_pattern({
48 pattern: `${remediation.type}: ${remediation.suggestion.description}`,
49 confidence: remediation.confidence,
50 domain: 'accessibility-remediation',
51 metadata: {
52 violationType: remediation.type,
53 autoFixable: remediation.autoFixable
54 }
55 });
56 }
57 }
58}
59
60function calculateReward(scanResult: AccessibilityScanResult): number {
61 const { violations, compliance, performance } = scanResult;
62
63 // Base score from compliance
64 let reward = compliance.score / 100;
65
66 // Bonus for comprehensive detection
67 if (violations.length > 0) {
68 reward += 0.1; // Found issues
69 }
70
71 // Bonus for fast performance
72 if (performance.scanTime < 10000) {
73 reward += 0.1;
74 }
75
76 // Penalty for low confidence remediations
77 const lowConfidenceCount = scanResult.remediations.filter(
78 r => r.confidence < 0.7
79 ).length;
80 reward -= lowConfidenceCount * 0.05;
81
82 return Math.max(0, Math.min(1, reward));
83}
#5.2 Fleet Coordination
13
4
5// Example: Full accessibility workflow
6async function fullAccessibilityWorkflow(url: string) {
7 // 1. Scan for violations (a11y-ally)
8 const scanResult = await Task(
9 'Scan accessibility',
10 `Comprehensive WCAG 2.2 AA scan of ${url}`,
11 'qe-a11y-ally'
12 );
13
14 // Store results in memory for other agents
15 await mcp__agentic_qe__memory_store({
16 key: `aqe/accessibility/scan-results/${scanResult.scanId}`,
17 value: scanResult,
18 namespace: 'aqe',
19 persist: true
20 });
21
22 // 2. Generate tests from violations (test-generator)
23 if (scanResult.violations.length > 0) {
24 await Task(
25 'Generate a11y tests',
26 `Generate regression tests for ${scanResult.violations.length} violations`,
27 'qe-test-generator'
28 );
29 }
30
31 // 3. Visual regression for accessibility (visual-tester)
32 await Task(
33 'Visual a11y check',
34 'Capture annotated screenshots of violations',
35 'qe-visual-tester'
36 );
37
38 // 4. Quality gate check (quality-gate)
39 const gateResult = await Task(
40 'A11y quality gate',
41 `Check if compliance score ${scanResult.compliance.score}% passes threshold`,
42 'qe-quality-gate'
43 );
44
45 return {
46 scanResult,
47 gateResult,
48 productionReady: gateResult.passed
49 };
50}
#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
#7. Security Considerations
13
4function validateScanParams(params: AccessibilityScanParams): void {
5 // URL validation
6 if (params.target.type === 'url') {
7 const url = new URL(params.target.url); // Throws if invalid
8
9 // Block dangerous protocols
10 if (!['http:', 'https:'].includes(url.protocol)) {
11 throw new Error('Only HTTP/HTTPS protocols allowed');
12 }
13
14 // Block localhost in production
15 if (process.env.NODE_ENV === 'production' &&
16 url.hostname === 'localhost') {
17 throw new Error('Cannot scan localhost in production');
18 }
19 }
20
21 // HTML validation
22 if (params.target.type === 'html') {
23 // Sanitize HTML to prevent XSS
24 params.target.html = sanitizeHTML(params.target.html);
25 }
26
27 // Selector validation (prevent injection)
28 if (params.config.scope?.include) {
29 params.config.scope.include = params.config.scope.include.map(
30 selector => sanitizeSelector(selector)
31 );
32 }
33}
#7.2 Safe Auto-Fix
13
4function validateAutoFix(autoFix: AutoFix): boolean {
5 // Only allow safe attribute additions/modifications
6 const safeChanges = [
7 'add-aria-label',
8 'add-alt-text',
9 'add-aria-describedby'
10 ];
11
12 // Never allow:
13 // - Script injection
14 // - Removing security-related attributes
15 // - Modifying authentication elements
16
17 return autoFix.safe &&
18 autoFix.verification !== undefined &&
19 !containsDangerousPatterns(autoFix.patch.content);
20}
#8. Error Handling
#8.1 Error Types
1export enum A11yErrorCode {
2 // Scan errors
3 SCAN_TIMEOUT = 'A11Y_SCAN_TIMEOUT',
4 SCAN_FAILED = 'A11Y_SCAN_FAILED',
5 INVALID_TARGET = 'A11Y_INVALID_TARGET',
6
7 // Analysis errors
8 CONTEXT_ANALYSIS_FAILED = 'A11Y_CONTEXT_ANALYSIS_FAILED',
9 REMEDIATION_GENERATION_FAILED = 'A11Y_REMEDIATION_GENERATION_FAILED',
10
11 // Tool errors
12 AXE_CORE_ERROR = 'A11Y_AXE_CORE_ERROR',
13 PLAYWRIGHT_ERROR = 'A11Y_PLAYWRIGHT_ERROR',
14
15 // Learning errors
16 LEARNING_QUERY_FAILED = 'A11Y_LEARNING_QUERY_FAILED',
17 LEARNING_STORE_FAILED = 'A11Y_LEARNING_STORE_FAILED'
18}
19
20export interface A11yError extends QEError {
21 code: A11yErrorCode;
22 recoverable: boolean;
23 retryable: boolean;
24}
#8.2 Graceful Degradation
13
4async function scanWithGracefulDegradation(
5 params: AccessibilityScanParams
6): Promise<AccessibilityScanResult> {
7 const results: Partial<AccessibilityScanResult> = {
8 violations: [],
9 remediations: []
10 };
11
12 // Try axe-core
13 try {
14 const axeResult = await runAxeCore(params);
15 results.violations.push(...axeResult.violations);
16 } catch (error) {
17 logError('axe-core failed, continuing with other tools', error);
18 }
19
20 // Try Playwright
21 try {
22 const playwrightResult = await runPlaywright(params);
23 results.violations.push(...playwrightResult.violations);
24 } catch (error) {
25 logError('Playwright failed, continuing with other tools', error);
26 }
27
28 // Try custom heuristics
29 try {
30 const customResult = await runCustomHeuristics(params);
31 results.violations.push(...customResult.violations);
32 } catch (error) {
33 logError('Custom heuristics failed', error);
34 }
35
36 // If all tools failed, throw
37 if (results.violations.length === 0) {
38 throw new Error('All scanning tools failed');
39 }
40
41 return results as AccessibilityScanResult;
42}
#9. Testing Specifications
#9.1 Unit Test Coverage Requirements
Minimum Coverage: 95%
Critical Paths (100% coverage required):
scan-comprehensive.ts - All code paths
analyze-context.ts - Context inference logic
generate-remediation.ts - Suggestion generation
test-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
1interface A11yMetrics {
2 // Scan metrics
3 totalScans: number;
4 successfulScans: number;
5 failedScans: number;
6 averageScanTime: number;
7
8 // Detection metrics
9 violationsDetected: number;
10 violationsByType: Record<RemediationType, number>;
11 violationsBySeverity: Record<ViolationSeverity, number>;
12
13 // Remediation metrics
14 remediationsGenerated: number;
15 autoFixableCount: number;
16 averageConfidence: number;
17
18 // Learning metrics
19 patternsLearned: number;
20 patternAccuracy: number;
21
22 // User impact metrics
23 averageFixTime: number; // After vs before
24 complianceImprovement: number;
25}
#10.2 Logging
1// Structured logging for observability
2logger.info('A11y scan started', {
3 scanId,
4 target: params.target,
5 level: params.level,
6 timestamp: Date.now()
7});
8
9logger.info('Violations detected', {
10 scanId,
11 count: violations.length,
12 critical: violations.filter(v => v.severity === 'critical').length
13});
14
15logger.info('Remediations generated', {
16 scanId,
17 count: remediations.length,
18 autoFixable: remediations.filter(r => r.autoFixable).length
19});
Document Status: Draft - Ready for Implementation
Next Steps: Begin Milestone 1 development