@dallask/a11y-mcp-srv 1.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/NOTICE +9 -0
- package/README.md +1311 -0
- package/bin/server.js +8 -0
- package/dist/core/accessibility-runner.d.ts +115 -0
- package/dist/core/accessibility-runner.d.ts.map +1 -0
- package/dist/core/accessibility-runner.js +419 -0
- package/dist/core/accessibility-runner.js.map +1 -0
- package/dist/core/config.d.ts +42 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +159 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/error-handler.d.ts +61 -0
- package/dist/core/error-handler.d.ts.map +1 -0
- package/dist/core/error-handler.js +264 -0
- package/dist/core/error-handler.js.map +1 -0
- package/dist/core/progress-streamer.d.ts +44 -0
- package/dist/core/progress-streamer.d.ts.map +1 -0
- package/dist/core/progress-streamer.js +160 -0
- package/dist/core/progress-streamer.js.map +1 -0
- package/dist/core/result-processor.d.ts +69 -0
- package/dist/core/result-processor.d.ts.map +1 -0
- package/dist/core/result-processor.js +461 -0
- package/dist/core/result-processor.js.map +1 -0
- package/dist/core/session-manager.d.ts +73 -0
- package/dist/core/session-manager.d.ts.map +1 -0
- package/dist/core/session-manager.js +243 -0
- package/dist/core/session-manager.js.map +1 -0
- package/dist/server.d.ts +6 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +1298 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/aggregate.d.ts +26 -0
- package/dist/tools/aggregate.d.ts.map +1 -0
- package/dist/tools/aggregate.js +308 -0
- package/dist/tools/aggregate.js.map +1 -0
- package/dist/tools/analysis.d.ts +68 -0
- package/dist/tools/analysis.d.ts.map +1 -0
- package/dist/tools/analysis.js +1232 -0
- package/dist/tools/analysis.js.map +1 -0
- package/dist/tools/audit.d.ts +38 -0
- package/dist/tools/audit.d.ts.map +1 -0
- package/dist/tools/audit.js +422 -0
- package/dist/tools/audit.js.map +1 -0
- package/dist/tools/comparison.d.ts +27 -0
- package/dist/tools/comparison.d.ts.map +1 -0
- package/dist/tools/comparison.js +488 -0
- package/dist/tools/comparison.js.map +1 -0
- package/dist/tools/export.d.ts +43 -0
- package/dist/tools/export.d.ts.map +1 -0
- package/dist/tools/export.js +729 -0
- package/dist/tools/export.js.map +1 -0
- package/dist/tools/filter.d.ts +26 -0
- package/dist/tools/filter.d.ts.map +1 -0
- package/dist/tools/filter.js +227 -0
- package/dist/tools/filter.js.map +1 -0
- package/dist/tools/session.d.ts +26 -0
- package/dist/tools/session.d.ts.map +1 -0
- package/dist/tools/session.js +227 -0
- package/dist/tools/session.js.map +1 -0
- package/dist/tools/visualize.d.ts +26 -0
- package/dist/tools/visualize.d.ts.map +1 -0
- package/dist/tools/visualize.js +946 -0
- package/dist/tools/visualize.js.map +1 -0
- package/dist/types/index.d.ts +755 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +6 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +57 -0
|
@@ -0,0 +1,946 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Visualization & dashboard tools
|
|
3
|
+
* Implements: generate_dashboard, generate_summary_report
|
|
4
|
+
*/
|
|
5
|
+
import { auditUrl } from './audit.js';
|
|
6
|
+
/**
|
|
7
|
+
* Generate ASCII bar chart
|
|
8
|
+
*/
|
|
9
|
+
function generateBarChart(data, maxWidth = 50) {
|
|
10
|
+
const entries = Object.entries(data).sort((a, b) => b[1] - a[1]);
|
|
11
|
+
const maxValue = Math.max(...entries.map(([, value]) => value), 1);
|
|
12
|
+
const lines = [];
|
|
13
|
+
entries.forEach(([label, value]) => {
|
|
14
|
+
const barLength = Math.round((value / maxValue) * maxWidth);
|
|
15
|
+
const bar = '█'.repeat(barLength);
|
|
16
|
+
const padding = ' '.repeat(Math.max(0, maxWidth - barLength));
|
|
17
|
+
lines.push(`${label.padEnd(15)} │${bar}${padding} ${value}`);
|
|
18
|
+
});
|
|
19
|
+
return lines.join('\n');
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Generate score gauge visualization
|
|
23
|
+
*/
|
|
24
|
+
function generateScoreGauge(score) {
|
|
25
|
+
const filled = Math.round(score / 2); // Each block represents 2 points
|
|
26
|
+
const empty = 50 - filled;
|
|
27
|
+
const bar = '█'.repeat(filled) + '░'.repeat(empty);
|
|
28
|
+
let status;
|
|
29
|
+
let emoji;
|
|
30
|
+
if (score >= 90) {
|
|
31
|
+
status = 'Excellent';
|
|
32
|
+
emoji = '🟢';
|
|
33
|
+
}
|
|
34
|
+
else if (score >= 75) {
|
|
35
|
+
status = 'Good';
|
|
36
|
+
emoji = '🟡';
|
|
37
|
+
}
|
|
38
|
+
else if (score >= 60) {
|
|
39
|
+
status = 'Needs Improvement';
|
|
40
|
+
emoji = '🟠';
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
status = 'Critical';
|
|
44
|
+
emoji = '🔴';
|
|
45
|
+
}
|
|
46
|
+
return `${emoji} ${score}/100 ${status}\n${bar}`;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Format dashboard as markdown
|
|
50
|
+
*/
|
|
51
|
+
function formatDashboardAsMarkdown(results, includeCharts) {
|
|
52
|
+
const resultsArray = Array.isArray(results) ? results : [results];
|
|
53
|
+
const isMultiple = resultsArray.length > 1;
|
|
54
|
+
const parts = [];
|
|
55
|
+
// Title
|
|
56
|
+
if (isMultiple) {
|
|
57
|
+
parts.push('# Accessibility Dashboard');
|
|
58
|
+
parts.push(`\n**Summary of ${resultsArray.length} audit(s)**\n`);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
const url = resultsArray[0].metadata?.url || 'Unknown URL';
|
|
62
|
+
parts.push(`# Accessibility Dashboard`);
|
|
63
|
+
parts.push(`\n**URL:** ${url}\n`);
|
|
64
|
+
}
|
|
65
|
+
// Overall metrics
|
|
66
|
+
if (isMultiple) {
|
|
67
|
+
const totalIssues = resultsArray.reduce((sum, r) => sum + r.summary.totalIssues, 0);
|
|
68
|
+
const avgScore = resultsArray.reduce((sum, r) => sum + r.summary.score, 0) /
|
|
69
|
+
resultsArray.length;
|
|
70
|
+
const avgWCAG = {
|
|
71
|
+
A: resultsArray.reduce((sum, r) => sum + r.summary.wcagCompliance.A, 0) /
|
|
72
|
+
resultsArray.length,
|
|
73
|
+
AA: resultsArray.reduce((sum, r) => sum + r.summary.wcagCompliance.AA, 0) /
|
|
74
|
+
resultsArray.length,
|
|
75
|
+
AAA: resultsArray.reduce((sum, r) => sum + r.summary.wcagCompliance.AAA, 0) / resultsArray.length,
|
|
76
|
+
};
|
|
77
|
+
parts.push('## Overall Metrics\n');
|
|
78
|
+
parts.push(`- **Total Issues:** ${totalIssues}`);
|
|
79
|
+
parts.push(`- **Average Score:** ${Math.round(avgScore)}/100`);
|
|
80
|
+
parts.push(`- **WCAG Compliance:** A: ${Math.round(avgWCAG.A)}%, AA: ${Math.round(avgWCAG.AA)}%, AAA: ${Math.round(avgWCAG.AAA)}%`);
|
|
81
|
+
if (includeCharts) {
|
|
82
|
+
parts.push('\n### Score Distribution\n');
|
|
83
|
+
parts.push('```');
|
|
84
|
+
parts.push(generateScoreGauge(Math.round(avgScore)));
|
|
85
|
+
parts.push('```');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
const result = resultsArray[0];
|
|
90
|
+
parts.push('## Overall Metrics\n');
|
|
91
|
+
parts.push(`- **Total Issues:** ${result.summary.totalIssues}`);
|
|
92
|
+
parts.push(`- **Accessibility Score:** ${result.summary.score}/100`);
|
|
93
|
+
parts.push(`- **WCAG Compliance:** A: ${result.summary.wcagCompliance.A}%, AA: ${result.summary.wcagCompliance.AA}%, AAA: ${result.summary.wcagCompliance.AAA}%`);
|
|
94
|
+
if (includeCharts) {
|
|
95
|
+
parts.push('\n### Score Gauge\n');
|
|
96
|
+
parts.push('```');
|
|
97
|
+
parts.push(generateScoreGauge(result.summary.score));
|
|
98
|
+
parts.push('```');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Impact breakdown
|
|
102
|
+
if (isMultiple) {
|
|
103
|
+
const impactCounts = {
|
|
104
|
+
critical: 0,
|
|
105
|
+
serious: 0,
|
|
106
|
+
moderate: 0,
|
|
107
|
+
minor: 0,
|
|
108
|
+
};
|
|
109
|
+
resultsArray.forEach((result) => {
|
|
110
|
+
Object.entries(result.summary.byImpact).forEach(([impact, count]) => {
|
|
111
|
+
impactCounts[impact] = (impactCounts[impact] || 0) + count;
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
parts.push('\n## Issues by Impact Level\n');
|
|
115
|
+
if (includeCharts) {
|
|
116
|
+
parts.push('```');
|
|
117
|
+
parts.push(generateBarChart(impactCounts));
|
|
118
|
+
parts.push('```');
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
Object.entries(impactCounts)
|
|
122
|
+
.sort((a, b) => b[1] - a[1])
|
|
123
|
+
.forEach(([impact, count]) => {
|
|
124
|
+
parts.push(`- **${impact}:** ${count}`);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
const result = resultsArray[0];
|
|
130
|
+
parts.push('\n## Issues by Impact Level\n');
|
|
131
|
+
if (includeCharts) {
|
|
132
|
+
parts.push('```');
|
|
133
|
+
parts.push(generateBarChart(result.summary.byImpact));
|
|
134
|
+
parts.push('```');
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
Object.entries(result.summary.byImpact)
|
|
138
|
+
.sort((a, b) => b[1] - a[1])
|
|
139
|
+
.forEach(([impact, count]) => {
|
|
140
|
+
parts.push(`- **${impact}:** ${count}`);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Category breakdown
|
|
145
|
+
if (isMultiple) {
|
|
146
|
+
const categoryCounts = {};
|
|
147
|
+
resultsArray.forEach((result) => {
|
|
148
|
+
Object.entries(result.summary.byCategory).forEach(([cat, count]) => {
|
|
149
|
+
categoryCounts[cat] = (categoryCounts[cat] || 0) + count;
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
parts.push('\n## Issues by Category\n');
|
|
153
|
+
if (includeCharts) {
|
|
154
|
+
parts.push('```');
|
|
155
|
+
parts.push(generateBarChart(categoryCounts));
|
|
156
|
+
parts.push('```');
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
Object.entries(categoryCounts)
|
|
160
|
+
.sort((a, b) => b[1] - a[1])
|
|
161
|
+
.forEach(([category, count]) => {
|
|
162
|
+
parts.push(`- **${category}:** ${count}`);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
const result = resultsArray[0];
|
|
168
|
+
parts.push('\n## Issues by Category\n');
|
|
169
|
+
if (includeCharts) {
|
|
170
|
+
parts.push('```');
|
|
171
|
+
parts.push(generateBarChart(result.summary.byCategory));
|
|
172
|
+
parts.push('```');
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
Object.entries(result.summary.byCategory)
|
|
176
|
+
.sort((a, b) => b[1] - a[1])
|
|
177
|
+
.forEach(([category, count]) => {
|
|
178
|
+
parts.push(`- **${category}:** ${count}`);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// Critical blockers and quick wins
|
|
183
|
+
if (!isMultiple) {
|
|
184
|
+
const result = resultsArray[0];
|
|
185
|
+
if (result.criticalBlockers.length > 0) {
|
|
186
|
+
parts.push('\n## 🚨 Critical Blockers\n');
|
|
187
|
+
parts.push(`Found ${result.criticalBlockers.length} critical blocker(s) that must be fixed before launch.\n`);
|
|
188
|
+
result.criticalBlockers.slice(0, 5).forEach((blocker, index) => {
|
|
189
|
+
parts.push(`${index + 1}. **${blocker.description}** (${blocker.affectedElements} element(s))`);
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
if (result.quickWins.length > 0) {
|
|
193
|
+
parts.push('\n## ✨ Quick Wins\n');
|
|
194
|
+
parts.push(`Found ${result.quickWins.length} quick win(s) - easy fixes with high impact.\n`);
|
|
195
|
+
result.quickWins.slice(0, 5).forEach((win, index) => {
|
|
196
|
+
parts.push(`${index + 1}. **${win.description}** (${win.affectedElements} element(s), ${win.estimatedTime})`);
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// Per-URL breakdown for multiple results
|
|
201
|
+
if (isMultiple) {
|
|
202
|
+
parts.push('\n## Per-URL Breakdown\n');
|
|
203
|
+
parts.push('| URL | Issues | Score | WCAG A | WCAG AA | WCAG AAA |');
|
|
204
|
+
parts.push('|-----|--------|-------|--------|---------|----------|');
|
|
205
|
+
resultsArray.forEach((result) => {
|
|
206
|
+
const url = result.metadata?.url || 'Unknown';
|
|
207
|
+
const shortUrl = url.length > 50 ? url.substring(0, 47) + '...' : url;
|
|
208
|
+
parts.push(`| ${shortUrl} | ${result.summary.totalIssues} | ${result.summary.score}/100 | ${result.summary.wcagCompliance.A}% | ${result.summary.wcagCompliance.AA}% | ${result.summary.wcagCompliance.AAA}% |`);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
return parts.join('\n');
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Format dashboard as text
|
|
215
|
+
*/
|
|
216
|
+
function formatDashboardAsText(results, includeCharts) {
|
|
217
|
+
const resultsArray = Array.isArray(results) ? results : [results];
|
|
218
|
+
const isMultiple = resultsArray.length > 1;
|
|
219
|
+
const parts = [];
|
|
220
|
+
// Title
|
|
221
|
+
if (isMultiple) {
|
|
222
|
+
parts.push('ACCESSIBILITY DASHBOARD');
|
|
223
|
+
parts.push(`Summary of ${resultsArray.length} audit(s)\n`);
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
const url = resultsArray[0].metadata?.url || 'Unknown URL';
|
|
227
|
+
parts.push('ACCESSIBILITY DASHBOARD');
|
|
228
|
+
parts.push(`URL: ${url}\n`);
|
|
229
|
+
}
|
|
230
|
+
// Overall metrics
|
|
231
|
+
if (isMultiple) {
|
|
232
|
+
const totalIssues = resultsArray.reduce((sum, r) => sum + r.summary.totalIssues, 0);
|
|
233
|
+
const avgScore = resultsArray.reduce((sum, r) => sum + r.summary.score, 0) /
|
|
234
|
+
resultsArray.length;
|
|
235
|
+
const avgWCAG = {
|
|
236
|
+
A: resultsArray.reduce((sum, r) => sum + r.summary.wcagCompliance.A, 0) /
|
|
237
|
+
resultsArray.length,
|
|
238
|
+
AA: resultsArray.reduce((sum, r) => sum + r.summary.wcagCompliance.AA, 0) /
|
|
239
|
+
resultsArray.length,
|
|
240
|
+
AAA: resultsArray.reduce((sum, r) => sum + r.summary.wcagCompliance.AAA, 0) / resultsArray.length,
|
|
241
|
+
};
|
|
242
|
+
parts.push('OVERALL METRICS');
|
|
243
|
+
parts.push(`Total Issues: ${totalIssues}`);
|
|
244
|
+
parts.push(`Average Score: ${Math.round(avgScore)}/100`);
|
|
245
|
+
parts.push(`WCAG Compliance: A: ${Math.round(avgWCAG.A)}%, AA: ${Math.round(avgWCAG.AA)}%, AAA: ${Math.round(avgWCAG.AAA)}%`);
|
|
246
|
+
if (includeCharts) {
|
|
247
|
+
parts.push('\nSCORE DISTRIBUTION');
|
|
248
|
+
parts.push(generateScoreGauge(Math.round(avgScore)));
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
const result = resultsArray[0];
|
|
253
|
+
parts.push('OVERALL METRICS');
|
|
254
|
+
parts.push(`Total Issues: ${result.summary.totalIssues}`);
|
|
255
|
+
parts.push(`Accessibility Score: ${result.summary.score}/100`);
|
|
256
|
+
parts.push(`WCAG Compliance: A: ${result.summary.wcagCompliance.A}%, AA: ${result.summary.wcagCompliance.AA}%, AAA: ${result.summary.wcagCompliance.AAA}%`);
|
|
257
|
+
if (includeCharts) {
|
|
258
|
+
parts.push('\nSCORE GAUGE');
|
|
259
|
+
parts.push(generateScoreGauge(result.summary.score));
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
// Impact breakdown
|
|
263
|
+
if (isMultiple) {
|
|
264
|
+
const impactCounts = {
|
|
265
|
+
critical: 0,
|
|
266
|
+
serious: 0,
|
|
267
|
+
moderate: 0,
|
|
268
|
+
minor: 0,
|
|
269
|
+
};
|
|
270
|
+
resultsArray.forEach((result) => {
|
|
271
|
+
Object.entries(result.summary.byImpact).forEach(([impact, count]) => {
|
|
272
|
+
impactCounts[impact] = (impactCounts[impact] || 0) + count;
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
parts.push('\nISSUES BY IMPACT LEVEL');
|
|
276
|
+
if (includeCharts) {
|
|
277
|
+
parts.push(generateBarChart(impactCounts));
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
Object.entries(impactCounts)
|
|
281
|
+
.sort((a, b) => b[1] - a[1])
|
|
282
|
+
.forEach(([impact, count]) => {
|
|
283
|
+
parts.push(`${impact}: ${count}`);
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
const result = resultsArray[0];
|
|
289
|
+
parts.push('\nISSUES BY IMPACT LEVEL');
|
|
290
|
+
if (includeCharts) {
|
|
291
|
+
parts.push(generateBarChart(result.summary.byImpact));
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
Object.entries(result.summary.byImpact)
|
|
295
|
+
.sort((a, b) => b[1] - a[1])
|
|
296
|
+
.forEach(([impact, count]) => {
|
|
297
|
+
parts.push(`${impact}: ${count}`);
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
// Category breakdown
|
|
302
|
+
if (isMultiple) {
|
|
303
|
+
const categoryCounts = {};
|
|
304
|
+
resultsArray.forEach((result) => {
|
|
305
|
+
Object.entries(result.summary.byCategory).forEach(([cat, count]) => {
|
|
306
|
+
categoryCounts[cat] = (categoryCounts[cat] || 0) + count;
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
parts.push('\nISSUES BY CATEGORY');
|
|
310
|
+
if (includeCharts) {
|
|
311
|
+
parts.push(generateBarChart(categoryCounts));
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
Object.entries(categoryCounts)
|
|
315
|
+
.sort((a, b) => b[1] - a[1])
|
|
316
|
+
.forEach(([category, count]) => {
|
|
317
|
+
parts.push(`${category}: ${count}`);
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
const result = resultsArray[0];
|
|
323
|
+
parts.push('\nISSUES BY CATEGORY');
|
|
324
|
+
if (includeCharts) {
|
|
325
|
+
parts.push(generateBarChart(result.summary.byCategory));
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
Object.entries(result.summary.byCategory)
|
|
329
|
+
.sort((a, b) => b[1] - a[1])
|
|
330
|
+
.forEach(([category, count]) => {
|
|
331
|
+
parts.push(`${category}: ${count}`);
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return parts.join('\n');
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Format dashboard as HTML
|
|
339
|
+
*/
|
|
340
|
+
function formatDashboardAsHTML(results, includeCharts) {
|
|
341
|
+
const resultsArray = Array.isArray(results) ? results : [results];
|
|
342
|
+
const isMultiple = resultsArray.length > 1;
|
|
343
|
+
const parts = [];
|
|
344
|
+
parts.push(`<!DOCTYPE html>
|
|
345
|
+
<html lang="en">
|
|
346
|
+
<head>
|
|
347
|
+
<meta charset="UTF-8">
|
|
348
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
349
|
+
<title>Accessibility Dashboard</title>
|
|
350
|
+
<style>
|
|
351
|
+
body {
|
|
352
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
353
|
+
max-width: 1200px;
|
|
354
|
+
margin: 0 auto;
|
|
355
|
+
padding: 20px;
|
|
356
|
+
background: #f5f5f5;
|
|
357
|
+
}
|
|
358
|
+
.dashboard {
|
|
359
|
+
background: white;
|
|
360
|
+
border-radius: 8px;
|
|
361
|
+
padding: 30px;
|
|
362
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
363
|
+
}
|
|
364
|
+
h1 { color: #333; border-bottom: 3px solid #4CAF50; padding-bottom: 10px; }
|
|
365
|
+
h2 { color: #555; margin-top: 30px; }
|
|
366
|
+
.metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin: 20px 0; }
|
|
367
|
+
.metric-card {
|
|
368
|
+
background: #f9f9f9;
|
|
369
|
+
padding: 15px;
|
|
370
|
+
border-radius: 6px;
|
|
371
|
+
border-left: 4px solid #4CAF50;
|
|
372
|
+
}
|
|
373
|
+
.metric-value { font-size: 2em; font-weight: bold; color: #333; }
|
|
374
|
+
.metric-label { color: #666; font-size: 0.9em; }
|
|
375
|
+
.chart { background: #f9f9f9; padding: 15px; border-radius: 6px; margin: 10px 0; font-family: monospace; }
|
|
376
|
+
table { width: 100%; border-collapse: collapse; margin: 20px 0; }
|
|
377
|
+
th, td { padding: 12px; text-align: left; border-bottom: 1px solid #ddd; }
|
|
378
|
+
th { background: #4CAF50; color: white; }
|
|
379
|
+
tr:hover { background: #f5f5f5; }
|
|
380
|
+
.critical { color: #d32f2f; font-weight: bold; }
|
|
381
|
+
.serious { color: #f57c00; }
|
|
382
|
+
.moderate { color: #fbc02d; }
|
|
383
|
+
.minor { color: #388e3c; }
|
|
384
|
+
</style>
|
|
385
|
+
</head>
|
|
386
|
+
<body>
|
|
387
|
+
<div class="dashboard">`);
|
|
388
|
+
// Title
|
|
389
|
+
if (isMultiple) {
|
|
390
|
+
parts.push(`<h1>Accessibility Dashboard</h1>`);
|
|
391
|
+
parts.push(`<p><strong>Summary of ${resultsArray.length} audit(s)</strong></p>`);
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
const url = resultsArray[0].metadata?.url || 'Unknown URL';
|
|
395
|
+
parts.push(`<h1>Accessibility Dashboard</h1>`);
|
|
396
|
+
parts.push(`<p><strong>URL:</strong> ${escapeHtml(url)}</p>`);
|
|
397
|
+
}
|
|
398
|
+
// Overall metrics
|
|
399
|
+
if (isMultiple) {
|
|
400
|
+
const totalIssues = resultsArray.reduce((sum, r) => sum + r.summary.totalIssues, 0);
|
|
401
|
+
const avgScore = resultsArray.reduce((sum, r) => sum + r.summary.score, 0) /
|
|
402
|
+
resultsArray.length;
|
|
403
|
+
const avgWCAG = {
|
|
404
|
+
A: resultsArray.reduce((sum, r) => sum + r.summary.wcagCompliance.A, 0) /
|
|
405
|
+
resultsArray.length,
|
|
406
|
+
AA: resultsArray.reduce((sum, r) => sum + r.summary.wcagCompliance.AA, 0) /
|
|
407
|
+
resultsArray.length,
|
|
408
|
+
AAA: resultsArray.reduce((sum, r) => sum + r.summary.wcagCompliance.AAA, 0) / resultsArray.length,
|
|
409
|
+
};
|
|
410
|
+
parts.push(`<h2>Overall Metrics</h2>`);
|
|
411
|
+
parts.push(`<div class="metrics">`);
|
|
412
|
+
parts.push(`<div class="metric-card"><div class="metric-value">${totalIssues}</div><div class="metric-label">Total Issues</div></div>`);
|
|
413
|
+
parts.push(`<div class="metric-card"><div class="metric-value">${Math.round(avgScore)}</div><div class="metric-label">Average Score</div></div>`);
|
|
414
|
+
parts.push(`<div class="metric-card"><div class="metric-value">${Math.round(avgWCAG.A)}%</div><div class="metric-label">WCAG A</div></div>`);
|
|
415
|
+
parts.push(`<div class="metric-card"><div class="metric-value">${Math.round(avgWCAG.AA)}%</div><div class="metric-label">WCAG AA</div></div>`);
|
|
416
|
+
parts.push(`</div>`);
|
|
417
|
+
if (includeCharts) {
|
|
418
|
+
parts.push(`<div class="chart"><pre>${generateScoreGauge(Math.round(avgScore))}</pre></div>`);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
const result = resultsArray[0];
|
|
423
|
+
parts.push(`<h2>Overall Metrics</h2>`);
|
|
424
|
+
parts.push(`<div class="metrics">`);
|
|
425
|
+
parts.push(`<div class="metric-card"><div class="metric-value">${result.summary.totalIssues}</div><div class="metric-label">Total Issues</div></div>`);
|
|
426
|
+
parts.push(`<div class="metric-card"><div class="metric-value">${result.summary.score}</div><div class="metric-label">Accessibility Score</div></div>`);
|
|
427
|
+
parts.push(`<div class="metric-card"><div class="metric-value">${result.summary.wcagCompliance.A}%</div><div class="metric-label">WCAG A</div></div>`);
|
|
428
|
+
parts.push(`<div class="metric-card"><div class="metric-value">${result.summary.wcagCompliance.AA}%</div><div class="metric-label">WCAG AA</div></div>`);
|
|
429
|
+
parts.push(`</div>`);
|
|
430
|
+
if (includeCharts) {
|
|
431
|
+
parts.push(`<div class="chart"><pre>${generateScoreGauge(result.summary.score)}</pre></div>`);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
// Impact breakdown
|
|
435
|
+
if (isMultiple) {
|
|
436
|
+
const impactCounts = {
|
|
437
|
+
critical: 0,
|
|
438
|
+
serious: 0,
|
|
439
|
+
moderate: 0,
|
|
440
|
+
minor: 0,
|
|
441
|
+
};
|
|
442
|
+
resultsArray.forEach((result) => {
|
|
443
|
+
Object.entries(result.summary.byImpact).forEach(([impact, count]) => {
|
|
444
|
+
impactCounts[impact] = (impactCounts[impact] || 0) + count;
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
parts.push(`<h2>Issues by Impact Level</h2>`);
|
|
448
|
+
if (includeCharts) {
|
|
449
|
+
parts.push(`<div class="chart"><pre>${generateBarChart(impactCounts)}</pre></div>`);
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
parts.push(`<ul>`);
|
|
453
|
+
Object.entries(impactCounts)
|
|
454
|
+
.sort((a, b) => b[1] - a[1])
|
|
455
|
+
.forEach(([impact, count]) => {
|
|
456
|
+
parts.push(`<li class="${impact}"><strong>${impact}:</strong> ${count}</li>`);
|
|
457
|
+
});
|
|
458
|
+
parts.push(`</ul>`);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
const result = resultsArray[0];
|
|
463
|
+
parts.push(`<h2>Issues by Impact Level</h2>`);
|
|
464
|
+
if (includeCharts) {
|
|
465
|
+
parts.push(`<div class="chart"><pre>${generateBarChart(result.summary.byImpact)}</pre></div>`);
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
parts.push(`<ul>`);
|
|
469
|
+
Object.entries(result.summary.byImpact)
|
|
470
|
+
.sort((a, b) => b[1] - a[1])
|
|
471
|
+
.forEach(([impact, count]) => {
|
|
472
|
+
parts.push(`<li class="${impact}"><strong>${impact}:</strong> ${count}</li>`);
|
|
473
|
+
});
|
|
474
|
+
parts.push(`</ul>`);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
// Category breakdown
|
|
478
|
+
if (isMultiple) {
|
|
479
|
+
const categoryCounts = {};
|
|
480
|
+
resultsArray.forEach((result) => {
|
|
481
|
+
Object.entries(result.summary.byCategory).forEach(([cat, count]) => {
|
|
482
|
+
categoryCounts[cat] = (categoryCounts[cat] || 0) + count;
|
|
483
|
+
});
|
|
484
|
+
});
|
|
485
|
+
parts.push(`<h2>Issues by Category</h2>`);
|
|
486
|
+
if (includeCharts) {
|
|
487
|
+
parts.push(`<div class="chart"><pre>${generateBarChart(categoryCounts)}</pre></div>`);
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
parts.push(`<ul>`);
|
|
491
|
+
Object.entries(categoryCounts)
|
|
492
|
+
.sort((a, b) => b[1] - a[1])
|
|
493
|
+
.forEach(([category, count]) => {
|
|
494
|
+
parts.push(`<li><strong>${category}:</strong> ${count}</li>`);
|
|
495
|
+
});
|
|
496
|
+
parts.push(`</ul>`);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
else {
|
|
500
|
+
const result = resultsArray[0];
|
|
501
|
+
parts.push(`<h2>Issues by Category</h2>`);
|
|
502
|
+
if (includeCharts) {
|
|
503
|
+
parts.push(`<div class="chart"><pre>${generateBarChart(result.summary.byCategory)}</pre></div>`);
|
|
504
|
+
}
|
|
505
|
+
else {
|
|
506
|
+
parts.push(`<ul>`);
|
|
507
|
+
Object.entries(result.summary.byCategory)
|
|
508
|
+
.sort((a, b) => b[1] - a[1])
|
|
509
|
+
.forEach(([category, count]) => {
|
|
510
|
+
parts.push(`<li><strong>${category}:</strong> ${count}</li>`);
|
|
511
|
+
});
|
|
512
|
+
parts.push(`</ul>`);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
// Per-URL breakdown for multiple results
|
|
516
|
+
if (isMultiple) {
|
|
517
|
+
parts.push(`<h2>Per-URL Breakdown</h2>`);
|
|
518
|
+
parts.push(`<table>`);
|
|
519
|
+
parts.push(`<tr><th>URL</th><th>Issues</th><th>Score</th><th>WCAG A</th><th>WCAG AA</th><th>WCAG AAA</th></tr>`);
|
|
520
|
+
resultsArray.forEach((result) => {
|
|
521
|
+
const url = result.metadata?.url || 'Unknown';
|
|
522
|
+
parts.push(`<tr><td>${escapeHtml(url)}</td><td>${result.summary.totalIssues}</td><td>${result.summary.score}/100</td><td>${result.summary.wcagCompliance.A}%</td><td>${result.summary.wcagCompliance.AA}%</td><td>${result.summary.wcagCompliance.AAA}%</td></tr>`);
|
|
523
|
+
});
|
|
524
|
+
parts.push(`</table>`);
|
|
525
|
+
}
|
|
526
|
+
parts.push(` </div>
|
|
527
|
+
</body>
|
|
528
|
+
</html>`);
|
|
529
|
+
return parts.join('\n');
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Escape HTML special characters
|
|
533
|
+
*/
|
|
534
|
+
function escapeHtml(text) {
|
|
535
|
+
return text
|
|
536
|
+
.replace(/&/g, '&')
|
|
537
|
+
.replace(/</g, '<')
|
|
538
|
+
.replace(/>/g, '>')
|
|
539
|
+
.replace(/"/g, '"')
|
|
540
|
+
.replace(/'/g, ''');
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* generate_dashboard - Create visual dashboard summary of audit results
|
|
544
|
+
*
|
|
545
|
+
* Generates a visual dashboard with key metrics, charts, and summaries
|
|
546
|
+
* in various formats (text, markdown, HTML, JSON).
|
|
547
|
+
*
|
|
548
|
+
* @param input - Dashboard input (results or URL/array, format, includeCharts)
|
|
549
|
+
* @returns Formatted dashboard with key metrics, charts, and summaries
|
|
550
|
+
*/
|
|
551
|
+
export async function generateDashboard(input) {
|
|
552
|
+
const { results, format = 'markdown', includeCharts = true, } = input;
|
|
553
|
+
let auditResults;
|
|
554
|
+
// If input is a URL string, run an audit first
|
|
555
|
+
if (typeof results === 'string') {
|
|
556
|
+
const singleResult = await auditUrl({ url: results });
|
|
557
|
+
auditResults = singleResult;
|
|
558
|
+
}
|
|
559
|
+
else if (Array.isArray(results)) {
|
|
560
|
+
// If array contains URLs, audit them
|
|
561
|
+
const urlResults = await Promise.all(results.map(async (r) => {
|
|
562
|
+
if (typeof r === 'string') {
|
|
563
|
+
return await auditUrl({ url: r });
|
|
564
|
+
}
|
|
565
|
+
return r;
|
|
566
|
+
}));
|
|
567
|
+
auditResults = urlResults;
|
|
568
|
+
}
|
|
569
|
+
else {
|
|
570
|
+
auditResults = results;
|
|
571
|
+
}
|
|
572
|
+
// Format dashboard based on requested format
|
|
573
|
+
let dashboard;
|
|
574
|
+
switch (format) {
|
|
575
|
+
case 'text':
|
|
576
|
+
dashboard = formatDashboardAsText(auditResults, includeCharts);
|
|
577
|
+
break;
|
|
578
|
+
case 'html':
|
|
579
|
+
dashboard = formatDashboardAsHTML(auditResults, includeCharts);
|
|
580
|
+
break;
|
|
581
|
+
case 'json':
|
|
582
|
+
const resultsArray = Array.isArray(auditResults)
|
|
583
|
+
? auditResults
|
|
584
|
+
: [auditResults];
|
|
585
|
+
dashboard = JSON.stringify({
|
|
586
|
+
summary: resultsArray.map((r) => ({
|
|
587
|
+
url: r.metadata?.url || 'Unknown',
|
|
588
|
+
totalIssues: r.summary.totalIssues,
|
|
589
|
+
score: r.summary.score,
|
|
590
|
+
wcagCompliance: r.summary.wcagCompliance,
|
|
591
|
+
byImpact: r.summary.byImpact,
|
|
592
|
+
byCategory: r.summary.byCategory,
|
|
593
|
+
criticalBlockers: r.criticalBlockers.length,
|
|
594
|
+
quickWins: r.quickWins.length,
|
|
595
|
+
})),
|
|
596
|
+
}, null, 2);
|
|
597
|
+
break;
|
|
598
|
+
case 'markdown':
|
|
599
|
+
default:
|
|
600
|
+
dashboard = formatDashboardAsMarkdown(auditResults, includeCharts);
|
|
601
|
+
break;
|
|
602
|
+
}
|
|
603
|
+
return {
|
|
604
|
+
dashboard,
|
|
605
|
+
format,
|
|
606
|
+
includeCharts,
|
|
607
|
+
totalResults: Array.isArray(auditResults) ? auditResults.length : 1,
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Generate executive summary report
|
|
612
|
+
*/
|
|
613
|
+
function generateExecutiveSummary(results, level) {
|
|
614
|
+
const resultsArray = Array.isArray(results) ? results : [results];
|
|
615
|
+
const isMultiple = resultsArray.length > 1;
|
|
616
|
+
const parts = [];
|
|
617
|
+
if (level === 'executive') {
|
|
618
|
+
// High-level summary for executives
|
|
619
|
+
if (isMultiple) {
|
|
620
|
+
const totalIssues = resultsArray.reduce((sum, r) => sum + r.summary.totalIssues, 0);
|
|
621
|
+
const avgScore = resultsArray.reduce((sum, r) => sum + r.summary.score, 0) /
|
|
622
|
+
resultsArray.length;
|
|
623
|
+
parts.push('## Executive Summary');
|
|
624
|
+
parts.push(`\nAn accessibility audit was conducted on ${resultsArray.length} page(s), identifying ${totalIssues} accessibility issue(s).`);
|
|
625
|
+
parts.push(`\n**Key Findings:**`);
|
|
626
|
+
parts.push(`- Average Accessibility Score: ${Math.round(avgScore)}/100`);
|
|
627
|
+
parts.push(`- Total Issues Found: ${totalIssues}`);
|
|
628
|
+
const criticalCount = resultsArray.reduce((sum, r) => sum + (r.summary.byImpact.critical || 0), 0);
|
|
629
|
+
if (criticalCount > 0) {
|
|
630
|
+
parts.push(`- Critical Issues: ${criticalCount} (must be addressed before launch)`);
|
|
631
|
+
}
|
|
632
|
+
const quickWinsCount = resultsArray.reduce((sum, r) => sum + r.quickWins.length, 0);
|
|
633
|
+
if (quickWinsCount > 0) {
|
|
634
|
+
parts.push(`- Quick Wins Available: ${quickWinsCount} (easy fixes with high impact)`);
|
|
635
|
+
}
|
|
636
|
+
parts.push(`\n**Recommendation:** ${avgScore >= 80 ? 'The site demonstrates good accessibility practices. Continue monitoring and address remaining issues.' : avgScore >= 60 ? 'The site needs improvement to meet accessibility standards. Prioritize critical issues and quick wins.' : 'The site requires significant accessibility improvements before launch. Address critical blockers immediately.'}`);
|
|
637
|
+
}
|
|
638
|
+
else {
|
|
639
|
+
const result = resultsArray[0];
|
|
640
|
+
parts.push('## Executive Summary');
|
|
641
|
+
parts.push(`\nAn accessibility audit was conducted, identifying ${result.summary.totalIssues} accessibility issue(s).`);
|
|
642
|
+
parts.push(`\n**Key Findings:**`);
|
|
643
|
+
parts.push(`- Accessibility Score: ${result.summary.score}/100`);
|
|
644
|
+
parts.push(`- Total Issues Found: ${result.summary.totalIssues}`);
|
|
645
|
+
if (result.criticalBlockers.length > 0) {
|
|
646
|
+
parts.push(`- Critical Blockers: ${result.criticalBlockers.length} (must be fixed before launch)`);
|
|
647
|
+
}
|
|
648
|
+
if (result.quickWins.length > 0) {
|
|
649
|
+
parts.push(`- Quick Wins Available: ${result.quickWins.length} (easy fixes with high impact)`);
|
|
650
|
+
}
|
|
651
|
+
parts.push(`\n**Recommendation:** ${result.summary.score >= 80 ? 'The page demonstrates good accessibility practices. Continue monitoring and address remaining issues.' : result.summary.score >= 60 ? 'The page needs improvement to meet accessibility standards. Prioritize critical issues and quick wins.' : 'The page requires significant accessibility improvements before launch. Address critical blockers immediately.'}`);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
else if (level === 'detailed') {
|
|
655
|
+
// Detailed summary with more information
|
|
656
|
+
if (isMultiple) {
|
|
657
|
+
const totalIssues = resultsArray.reduce((sum, r) => sum + r.summary.totalIssues, 0);
|
|
658
|
+
const avgScore = resultsArray.reduce((sum, r) => sum + r.summary.score, 0) /
|
|
659
|
+
resultsArray.length;
|
|
660
|
+
const avgWCAG = {
|
|
661
|
+
A: resultsArray.reduce((sum, r) => sum + r.summary.wcagCompliance.A, 0) / resultsArray.length,
|
|
662
|
+
AA: resultsArray.reduce((sum, r) => sum + r.summary.wcagCompliance.AA, 0) / resultsArray.length,
|
|
663
|
+
AAA: resultsArray.reduce((sum, r) => sum + r.summary.wcagCompliance.AAA, 0) / resultsArray.length,
|
|
664
|
+
};
|
|
665
|
+
parts.push('## Detailed Summary');
|
|
666
|
+
parts.push(`\nAn accessibility audit was conducted on ${resultsArray.length} page(s), identifying ${totalIssues} accessibility issue(s).`);
|
|
667
|
+
parts.push(`\n**Overall Metrics:**`);
|
|
668
|
+
parts.push(`- Average Accessibility Score: ${Math.round(avgScore)}/100`);
|
|
669
|
+
parts.push(`- Total Issues: ${totalIssues}`);
|
|
670
|
+
parts.push(`- WCAG Compliance: Level A: ${Math.round(avgWCAG.A)}%, Level AA: ${Math.round(avgWCAG.AA)}%, Level AAA: ${Math.round(avgWCAG.AAA)}%`);
|
|
671
|
+
// Impact breakdown
|
|
672
|
+
const impactCounts = {
|
|
673
|
+
critical: 0,
|
|
674
|
+
serious: 0,
|
|
675
|
+
moderate: 0,
|
|
676
|
+
minor: 0,
|
|
677
|
+
};
|
|
678
|
+
resultsArray.forEach((result) => {
|
|
679
|
+
Object.entries(result.summary.byImpact).forEach(([impact, count]) => {
|
|
680
|
+
impactCounts[impact] = (impactCounts[impact] || 0) + count;
|
|
681
|
+
});
|
|
682
|
+
});
|
|
683
|
+
parts.push(`\n**Issues by Impact:**`);
|
|
684
|
+
Object.entries(impactCounts)
|
|
685
|
+
.sort((a, b) => b[1] - a[1])
|
|
686
|
+
.forEach(([impact, count]) => {
|
|
687
|
+
if (count > 0) {
|
|
688
|
+
parts.push(`- ${impact}: ${count}`);
|
|
689
|
+
}
|
|
690
|
+
});
|
|
691
|
+
// Category breakdown
|
|
692
|
+
const categoryCounts = {};
|
|
693
|
+
resultsArray.forEach((result) => {
|
|
694
|
+
Object.entries(result.summary.byCategory).forEach(([cat, count]) => {
|
|
695
|
+
categoryCounts[cat] = (categoryCounts[cat] || 0) + count;
|
|
696
|
+
});
|
|
697
|
+
});
|
|
698
|
+
parts.push(`\n**Issues by Category:**`);
|
|
699
|
+
Object.entries(categoryCounts)
|
|
700
|
+
.sort((a, b) => b[1] - a[1])
|
|
701
|
+
.forEach(([category, count]) => {
|
|
702
|
+
parts.push(`- ${category}: ${count}`);
|
|
703
|
+
});
|
|
704
|
+
const criticalCount = resultsArray.reduce((sum, r) => sum + (r.summary.byImpact.critical || 0), 0);
|
|
705
|
+
const quickWinsCount = resultsArray.reduce((sum, r) => sum + r.quickWins.length, 0);
|
|
706
|
+
parts.push(`\n**Priority Actions:**`);
|
|
707
|
+
if (criticalCount > 0) {
|
|
708
|
+
parts.push(`- Address ${criticalCount} critical issue(s) that prevent users with disabilities from accessing content`);
|
|
709
|
+
}
|
|
710
|
+
if (quickWinsCount > 0) {
|
|
711
|
+
parts.push(`- Implement ${quickWinsCount} quick win(s) for immediate accessibility improvements`);
|
|
712
|
+
}
|
|
713
|
+
parts.push(`\n**Next Steps:** ${avgScore >= 80 ? 'Continue monitoring accessibility and address remaining issues. Consider implementing automated accessibility testing in your CI/CD pipeline.' : avgScore >= 60 ? 'Prioritize critical issues and quick wins. Develop an accessibility remediation plan with clear timelines.' : 'Immediately address critical blockers. Conduct a comprehensive accessibility review and develop a detailed remediation plan.'}`);
|
|
714
|
+
}
|
|
715
|
+
else {
|
|
716
|
+
const result = resultsArray[0];
|
|
717
|
+
parts.push('## Detailed Summary');
|
|
718
|
+
parts.push(`\nAn accessibility audit was conducted, identifying ${result.summary.totalIssues} accessibility issue(s).`);
|
|
719
|
+
parts.push(`\n**Overall Metrics:**`);
|
|
720
|
+
parts.push(`- Accessibility Score: ${result.summary.score}/100`);
|
|
721
|
+
parts.push(`- Total Issues: ${result.summary.totalIssues}`);
|
|
722
|
+
parts.push(`- WCAG Compliance: Level A: ${result.summary.wcagCompliance.A}%, Level AA: ${result.summary.wcagCompliance.AA}%, Level AAA: ${result.summary.wcagCompliance.AAA}%`);
|
|
723
|
+
parts.push(`\n**Issues by Impact:**`);
|
|
724
|
+
Object.entries(result.summary.byImpact)
|
|
725
|
+
.sort((a, b) => b[1] - a[1])
|
|
726
|
+
.forEach(([impact, count]) => {
|
|
727
|
+
if (count > 0) {
|
|
728
|
+
parts.push(`- ${impact}: ${count}`);
|
|
729
|
+
}
|
|
730
|
+
});
|
|
731
|
+
parts.push(`\n**Issues by Category:**`);
|
|
732
|
+
Object.entries(result.summary.byCategory)
|
|
733
|
+
.sort((a, b) => b[1] - a[1])
|
|
734
|
+
.forEach(([category, count]) => {
|
|
735
|
+
parts.push(`- ${category}: ${count}`);
|
|
736
|
+
});
|
|
737
|
+
if (result.criticalBlockers.length > 0) {
|
|
738
|
+
parts.push(`\n**Critical Blockers:**`);
|
|
739
|
+
result.criticalBlockers.slice(0, 5).forEach((blocker, index) => {
|
|
740
|
+
parts.push(`${index + 1}. ${blocker.description} (${blocker.affectedElements} element(s))`);
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
if (result.quickWins.length > 0) {
|
|
744
|
+
parts.push(`\n**Quick Wins:**`);
|
|
745
|
+
result.quickWins.slice(0, 5).forEach((win, index) => {
|
|
746
|
+
parts.push(`${index + 1}. ${win.description} (${win.affectedElements} element(s), ${win.estimatedTime})`);
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
parts.push(`\n**Next Steps:** ${result.summary.score >= 80 ? 'Continue monitoring accessibility and address remaining issues. Consider implementing automated accessibility testing.' : result.summary.score >= 60 ? 'Prioritize critical issues and quick wins. Develop an accessibility remediation plan.' : 'Immediately address critical blockers. Conduct a comprehensive accessibility review and develop a detailed remediation plan.'}`);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
else {
|
|
753
|
+
// Technical summary with detailed information
|
|
754
|
+
if (isMultiple) {
|
|
755
|
+
const totalIssues = resultsArray.reduce((sum, r) => sum + r.summary.totalIssues, 0);
|
|
756
|
+
const avgScore = resultsArray.reduce((sum, r) => sum + r.summary.score, 0) /
|
|
757
|
+
resultsArray.length;
|
|
758
|
+
parts.push('## Technical Summary');
|
|
759
|
+
parts.push(`\nAccessibility audit results for ${resultsArray.length} page(s):`);
|
|
760
|
+
parts.push(`\n**Statistics:**`);
|
|
761
|
+
parts.push(`- Total Issues: ${totalIssues}`);
|
|
762
|
+
parts.push(`- Average Score: ${Math.round(avgScore)}/100`);
|
|
763
|
+
// Per-URL breakdown
|
|
764
|
+
parts.push(`\n**Per-URL Breakdown:**`);
|
|
765
|
+
resultsArray.forEach((result, index) => {
|
|
766
|
+
const url = result.metadata?.url || 'Unknown';
|
|
767
|
+
parts.push(`\n${index + 1}. ${url}`);
|
|
768
|
+
parts.push(` - Issues: ${result.summary.totalIssues}`);
|
|
769
|
+
parts.push(` - Score: ${result.summary.score}/100`);
|
|
770
|
+
parts.push(` - WCAG: A: ${result.summary.wcagCompliance.A}%, AA: ${result.summary.wcagCompliance.AA}%, AAA: ${result.summary.wcagCompliance.AAA}%`);
|
|
771
|
+
if (result.criticalBlockers.length > 0) {
|
|
772
|
+
parts.push(` - Critical Blockers: ${result.criticalBlockers.length}`);
|
|
773
|
+
}
|
|
774
|
+
if (result.quickWins.length > 0) {
|
|
775
|
+
parts.push(` - Quick Wins: ${result.quickWins.length}`);
|
|
776
|
+
}
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
else {
|
|
780
|
+
const result = resultsArray[0];
|
|
781
|
+
const url = result.metadata?.url || 'Unknown';
|
|
782
|
+
parts.push('## Technical Summary');
|
|
783
|
+
parts.push(`\nAccessibility audit results for: ${url}`);
|
|
784
|
+
parts.push(`\n**Statistics:**`);
|
|
785
|
+
parts.push(`- Total Issues: ${result.summary.totalIssues}`);
|
|
786
|
+
parts.push(`- Score: ${result.summary.score}/100`);
|
|
787
|
+
parts.push(`- WCAG Compliance: A: ${result.summary.wcagCompliance.A}%, AA: ${result.summary.wcagCompliance.AA}%, AAA: ${result.summary.wcagCompliance.AAA}%`);
|
|
788
|
+
parts.push(`\n**Impact Breakdown:**`);
|
|
789
|
+
Object.entries(result.summary.byImpact).forEach(([impact, count]) => {
|
|
790
|
+
parts.push(`- ${impact}: ${count}`);
|
|
791
|
+
});
|
|
792
|
+
parts.push(`\n**Category Breakdown:**`);
|
|
793
|
+
Object.entries(result.summary.byCategory).forEach(([category, count]) => {
|
|
794
|
+
parts.push(`- ${category}: ${count}`);
|
|
795
|
+
});
|
|
796
|
+
if (result.criticalBlockers.length > 0) {
|
|
797
|
+
parts.push(`\n**Critical Blockers (${result.criticalBlockers.length}):**`);
|
|
798
|
+
result.criticalBlockers.forEach((blocker) => {
|
|
799
|
+
parts.push(`- ${blocker.ruleId}: ${blocker.description}`);
|
|
800
|
+
parts.push(` - Affected Elements: ${blocker.affectedElements}`);
|
|
801
|
+
parts.push(` - WCAG Level: ${blocker.wcagLevel}`);
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
if (result.quickWins.length > 0) {
|
|
805
|
+
parts.push(`\n**Quick Wins (${result.quickWins.length}):**`);
|
|
806
|
+
result.quickWins.forEach((win) => {
|
|
807
|
+
parts.push(`- ${win.ruleId}: ${win.description}`);
|
|
808
|
+
parts.push(` - Affected Elements: ${win.affectedElements}`);
|
|
809
|
+
parts.push(` - Estimated Time: ${win.estimatedTime}`);
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
return parts.join('\n');
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Format summary report as markdown
|
|
818
|
+
*/
|
|
819
|
+
function formatSummaryReportAsMarkdown(results, level) {
|
|
820
|
+
const parts = [];
|
|
821
|
+
parts.push('# Accessibility Summary Report');
|
|
822
|
+
parts.push(`\n**Report Date:** ${new Date().toISOString().split('T')[0]}`);
|
|
823
|
+
const summary = generateExecutiveSummary(results, level);
|
|
824
|
+
parts.push(summary);
|
|
825
|
+
return parts.join('\n');
|
|
826
|
+
}
|
|
827
|
+
/**
|
|
828
|
+
* Format summary report as text
|
|
829
|
+
*/
|
|
830
|
+
function formatSummaryReportAsText(results, level) {
|
|
831
|
+
const parts = [];
|
|
832
|
+
parts.push('ACCESSIBILITY SUMMARY REPORT');
|
|
833
|
+
parts.push(`Report Date: ${new Date().toISOString().split('T')[0]}\n`);
|
|
834
|
+
const summary = generateExecutiveSummary(results, level);
|
|
835
|
+
// Convert markdown-style formatting to plain text
|
|
836
|
+
const textSummary = summary
|
|
837
|
+
.replace(/## /g, '\n')
|
|
838
|
+
.replace(/\*\*/g, '')
|
|
839
|
+
.replace(/# /g, '');
|
|
840
|
+
parts.push(textSummary);
|
|
841
|
+
return parts.join('\n');
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Format summary report as HTML
|
|
845
|
+
*/
|
|
846
|
+
function formatSummaryReportAsHTML(results, level) {
|
|
847
|
+
const parts = [];
|
|
848
|
+
parts.push(`<!DOCTYPE html>
|
|
849
|
+
<html lang="en">
|
|
850
|
+
<head>
|
|
851
|
+
<meta charset="UTF-8">
|
|
852
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
853
|
+
<title>Accessibility Summary Report</title>
|
|
854
|
+
<style>
|
|
855
|
+
body {
|
|
856
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
857
|
+
max-width: 900px;
|
|
858
|
+
margin: 0 auto;
|
|
859
|
+
padding: 20px;
|
|
860
|
+
background: #f5f5f5;
|
|
861
|
+
}
|
|
862
|
+
.report {
|
|
863
|
+
background: white;
|
|
864
|
+
border-radius: 8px;
|
|
865
|
+
padding: 30px;
|
|
866
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
867
|
+
}
|
|
868
|
+
h1 { color: #333; border-bottom: 3px solid #4CAF50; padding-bottom: 10px; }
|
|
869
|
+
h2 { color: #555; margin-top: 30px; }
|
|
870
|
+
p { line-height: 1.6; color: #333; }
|
|
871
|
+
ul { line-height: 1.8; }
|
|
872
|
+
.date { color: #666; font-size: 0.9em; }
|
|
873
|
+
</style>
|
|
874
|
+
</head>
|
|
875
|
+
<body>
|
|
876
|
+
<div class="report">`);
|
|
877
|
+
parts.push(`<h1>Accessibility Summary Report</h1>`);
|
|
878
|
+
parts.push(`<p class="date"><strong>Report Date:</strong> ${new Date().toISOString().split('T')[0]}</p>`);
|
|
879
|
+
const summary = generateExecutiveSummary(results, level);
|
|
880
|
+
// Convert markdown to HTML
|
|
881
|
+
const htmlSummary = summary
|
|
882
|
+
.replace(/## (.+)/g, '<h2>$1</h2>')
|
|
883
|
+
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
|
|
884
|
+
.replace(/\n- (.+)/g, '<li>$1</li>')
|
|
885
|
+
.replace(/(<li>.*<\/li>)/s, '<ul>$1</ul>')
|
|
886
|
+
.replace(/\n\n/g, '</p><p>')
|
|
887
|
+
.replace(/^/, '<p>')
|
|
888
|
+
.replace(/$/, '</p>');
|
|
889
|
+
parts.push(htmlSummary);
|
|
890
|
+
parts.push(` </div>
|
|
891
|
+
</body>
|
|
892
|
+
</html>`);
|
|
893
|
+
return parts.join('\n');
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
896
|
+
* generate_summary_report - Generate executive summary report
|
|
897
|
+
*
|
|
898
|
+
* Generates an executive summary report with key findings and recommendations
|
|
899
|
+
* in various formats (text, markdown, HTML) and detail levels.
|
|
900
|
+
*
|
|
901
|
+
* @param input - Summary report input (results or URL/array, format, level)
|
|
902
|
+
* @returns Summary report with key findings and recommendations
|
|
903
|
+
*/
|
|
904
|
+
export async function generateSummaryReport(input) {
|
|
905
|
+
const { results, format = 'markdown', level = 'executive', } = input;
|
|
906
|
+
let auditResults;
|
|
907
|
+
// If input is a URL string, run an audit first
|
|
908
|
+
if (typeof results === 'string') {
|
|
909
|
+
const singleResult = await auditUrl({ url: results });
|
|
910
|
+
auditResults = singleResult;
|
|
911
|
+
}
|
|
912
|
+
else if (Array.isArray(results)) {
|
|
913
|
+
// If array contains URLs, audit them
|
|
914
|
+
const urlResults = await Promise.all(results.map(async (r) => {
|
|
915
|
+
if (typeof r === 'string') {
|
|
916
|
+
return await auditUrl({ url: r });
|
|
917
|
+
}
|
|
918
|
+
return r;
|
|
919
|
+
}));
|
|
920
|
+
auditResults = urlResults;
|
|
921
|
+
}
|
|
922
|
+
else {
|
|
923
|
+
auditResults = results;
|
|
924
|
+
}
|
|
925
|
+
// Format report based on requested format
|
|
926
|
+
let report;
|
|
927
|
+
switch (format) {
|
|
928
|
+
case 'text':
|
|
929
|
+
report = formatSummaryReportAsText(auditResults, level);
|
|
930
|
+
break;
|
|
931
|
+
case 'html':
|
|
932
|
+
report = formatSummaryReportAsHTML(auditResults, level);
|
|
933
|
+
break;
|
|
934
|
+
case 'markdown':
|
|
935
|
+
default:
|
|
936
|
+
report = formatSummaryReportAsMarkdown(auditResults, level);
|
|
937
|
+
break;
|
|
938
|
+
}
|
|
939
|
+
return {
|
|
940
|
+
report,
|
|
941
|
+
format,
|
|
942
|
+
level,
|
|
943
|
+
totalResults: Array.isArray(auditResults) ? auditResults.length : 1,
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
//# sourceMappingURL=visualize.js.map
|