snap-ally 0.0.4 → 0.1.0-beta
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/dist/A11yHtmlRenderer.d.ts +3 -3
- package/dist/A11yHtmlRenderer.js +31 -18
- package/dist/A11yReportAssets.d.ts +1 -1
- package/dist/A11yReportAssets.js +20 -12
- package/dist/A11yScanner.js +18 -5
- package/dist/A11yTimeUtils.d.ts +4 -0
- package/dist/A11yTimeUtils.js +15 -0
- package/dist/SnapAllyReporter.js +44 -23
- package/dist/models/index.d.ts +11 -1
- package/dist/templates/accessibility-report.html +205 -1324
- package/dist/templates/execution-summary.html +155 -644
- package/dist/templates/global-report-styles.css +1536 -0
- package/dist/templates/report-app.js +857 -0
- package/dist/templates/test-execution-report.html +151 -536
- package/package.json +6 -7
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Handles the rendering of HTML reports using
|
|
2
|
+
* Handles the rendering of HTML reports using static templates and JSON data injection.
|
|
3
3
|
*/
|
|
4
4
|
export declare class A11yHtmlRenderer {
|
|
5
5
|
/**
|
|
6
|
-
* Renders
|
|
6
|
+
* Renders a static HTML template by copying it and generating the accompanied data payload.
|
|
7
7
|
* @param templateName The template file name in the templates folder.
|
|
8
|
-
* @param data The data object to pass to
|
|
8
|
+
* @param data The data object to pass to the client-side JS app.
|
|
9
9
|
* @param outputFolder The folder where the rendered file will be saved.
|
|
10
10
|
* @param outputFileName The full path of the output file.
|
|
11
11
|
*/
|
package/dist/A11yHtmlRenderer.js
CHANGED
|
@@ -36,40 +36,53 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.A11yHtmlRenderer = void 0;
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
|
-
const ejs = __importStar(require("ejs"));
|
|
40
39
|
/**
|
|
41
|
-
* Handles the rendering of HTML reports using
|
|
40
|
+
* Handles the rendering of HTML reports using static templates and JSON data injection.
|
|
42
41
|
*/
|
|
43
42
|
class A11yHtmlRenderer {
|
|
44
43
|
/**
|
|
45
|
-
* Renders
|
|
44
|
+
* Renders a static HTML template by copying it and generating the accompanied data payload.
|
|
46
45
|
* @param templateName The template file name in the templates folder.
|
|
47
|
-
* @param data The data object to pass to
|
|
46
|
+
* @param data The data object to pass to the client-side JS app.
|
|
48
47
|
* @param outputFolder The folder where the rendered file will be saved.
|
|
49
48
|
* @param outputFileName The full path of the output file.
|
|
50
49
|
*/
|
|
51
50
|
async render(templateName, data, outputFolder, outputFileName) {
|
|
52
51
|
// Resolve path relative to this file (dist/A11yHtmlRenderer.js)
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
catch {
|
|
52
|
+
const templatesDir = path.join(__dirname, 'templates');
|
|
53
|
+
const templatePath = path.join(templatesDir, templateName);
|
|
54
|
+
const cssPath = path.join(templatesDir, 'global-report-styles.css');
|
|
55
|
+
const jsPath = path.join(templatesDir, 'report-app.js');
|
|
56
|
+
if (!fs.existsSync(templatePath)) {
|
|
59
57
|
throw new Error(`[A11yHtmlRenderer] Template not found: ${templatePath}`);
|
|
60
58
|
}
|
|
61
|
-
|
|
59
|
+
if (!fs.existsSync(outputFolder)) {
|
|
60
|
+
fs.mkdirSync(outputFolder, { recursive: true });
|
|
61
|
+
}
|
|
62
|
+
// 1. Copy the pure HTML template to the output location
|
|
63
|
+
fs.copyFileSync(templatePath, outputFileName);
|
|
64
|
+
// 2. Wrap the report data in a JS variable and write data.js next to the HTML file
|
|
65
|
+
const outputDir = path.dirname(outputFileName);
|
|
66
|
+
const dataJsPath = path.join(outputDir, 'data.js');
|
|
67
|
+
const jsContent = `window.snapAllyData = ${JSON.stringify(data)};`;
|
|
68
|
+
fs.writeFileSync(dataJsPath, jsContent, 'utf8');
|
|
69
|
+
// 3. Copy the global CSS and JS rendering engine next to the HTML file
|
|
70
|
+
const outCssPath = path.join(outputDir, 'global-report-styles.css');
|
|
71
|
+
const outJsPath = path.join(outputDir, 'report-app.js');
|
|
62
72
|
try {
|
|
63
|
-
|
|
73
|
+
if (fs.existsSync(cssPath))
|
|
74
|
+
fs.copyFileSync(cssPath, outCssPath);
|
|
64
75
|
}
|
|
65
|
-
catch (
|
|
66
|
-
console.error(
|
|
67
|
-
throw error;
|
|
76
|
+
catch (e) {
|
|
77
|
+
console.error('Error copying CSS:', e);
|
|
68
78
|
}
|
|
69
|
-
|
|
70
|
-
fs.
|
|
79
|
+
try {
|
|
80
|
+
if (fs.existsSync(jsPath))
|
|
81
|
+
fs.copyFileSync(jsPath, outJsPath);
|
|
82
|
+
}
|
|
83
|
+
catch (e) {
|
|
84
|
+
console.error('Error copying JS:', e);
|
|
71
85
|
}
|
|
72
|
-
fs.writeFileSync(outputFileName, html);
|
|
73
86
|
}
|
|
74
87
|
/**
|
|
75
88
|
* Converts ANSI color codes to HTML spans for nicer error display.
|
|
@@ -9,7 +9,7 @@ export declare class A11yReportAssets {
|
|
|
9
9
|
copyToFolder(destFolder: string, srcPath: string, fileName?: string): string;
|
|
10
10
|
/**
|
|
11
11
|
* Copies the test video if available.
|
|
12
|
-
* Includes a
|
|
12
|
+
* Includes a more robust retry to ensure Playwright has finished flushing the file.
|
|
13
13
|
*/
|
|
14
14
|
copyTestVideo(result: TestResult, destFolder: string): Promise<string>;
|
|
15
15
|
/**
|
package/dist/A11yReportAssets.js
CHANGED
|
@@ -57,22 +57,24 @@ class A11yReportAssets {
|
|
|
57
57
|
}
|
|
58
58
|
/**
|
|
59
59
|
* Copies the test video if available.
|
|
60
|
-
* Includes a
|
|
60
|
+
* Includes a more robust retry to ensure Playwright has finished flushing the file.
|
|
61
61
|
*/
|
|
62
62
|
async copyTestVideo(result, destFolder) {
|
|
63
|
-
|
|
63
|
+
// More flexible matching for video attachments
|
|
64
|
+
const videoAttachments = result.attachments.filter(a => a.name === 'video' || (a.contentType || '').startsWith('video/'));
|
|
64
65
|
let bestVideo = null;
|
|
65
66
|
let maxSize = -1;
|
|
66
67
|
for (const attachment of videoAttachments) {
|
|
67
68
|
if (!attachment.path)
|
|
68
69
|
continue;
|
|
69
|
-
// Retry logic: Wait for file to exist and have non-zero size (up to
|
|
70
|
+
// Retry logic: Wait for file to exist and have non-zero size (up to 3 seconds)
|
|
70
71
|
let attempts = 0;
|
|
71
72
|
let isReady = false;
|
|
72
|
-
while (attempts <
|
|
73
|
+
while (attempts < 15) {
|
|
73
74
|
if (fs.existsSync(attachment.path)) {
|
|
74
75
|
try {
|
|
75
|
-
|
|
76
|
+
const stats = fs.statSync(attachment.path);
|
|
77
|
+
if (stats.size > 0) {
|
|
76
78
|
isReady = true;
|
|
77
79
|
break;
|
|
78
80
|
}
|
|
@@ -97,12 +99,12 @@ class A11yReportAssets {
|
|
|
97
99
|
}
|
|
98
100
|
}
|
|
99
101
|
else {
|
|
100
|
-
console.warn(`[SnapAlly] Video attachment found but file is missing or empty: ${attachment.path}`);
|
|
102
|
+
console.warn(`[SnapAlly] Video attachment found but file is missing or empty after retry: ${attachment.path}`);
|
|
101
103
|
}
|
|
102
104
|
}
|
|
103
105
|
if (bestVideo) {
|
|
104
106
|
try {
|
|
105
|
-
return this.copyToFolder(destFolder, bestVideo);
|
|
107
|
+
return this.copyToFolder(destFolder, bestVideo, 'video.webm');
|
|
106
108
|
}
|
|
107
109
|
catch (e) {
|
|
108
110
|
console.error(`[SnapAlly] Failed to copy video: ${e}`);
|
|
@@ -116,13 +118,15 @@ class A11yReportAssets {
|
|
|
116
118
|
*/
|
|
117
119
|
copyScreenshots(result, destFolder) {
|
|
118
120
|
return result.attachments
|
|
119
|
-
.filter(a => a.name === 'screenshot')
|
|
121
|
+
.filter(a => a.name === 'screenshot' || (a.contentType || '').startsWith('image/'))
|
|
120
122
|
.map(a => {
|
|
121
123
|
if (a.path) {
|
|
122
124
|
return this.copyToFolder(destFolder, a.path);
|
|
123
125
|
}
|
|
124
126
|
else if (a.body) {
|
|
125
|
-
|
|
127
|
+
const timestamp = Date.now();
|
|
128
|
+
const name = a.name === 'screenshot' ? `screenshot-${timestamp}.png` : (a.name.endsWith('.png') ? a.name : `${a.name}.png`);
|
|
129
|
+
return this.writeBuffer(destFolder, name, a.body);
|
|
126
130
|
}
|
|
127
131
|
return '';
|
|
128
132
|
})
|
|
@@ -133,14 +137,15 @@ class A11yReportAssets {
|
|
|
133
137
|
*/
|
|
134
138
|
copyPngAttachments(result, destFolder) {
|
|
135
139
|
return result.attachments
|
|
136
|
-
.filter(a => a.name.endsWith('.png') && a.name !== 'screenshot')
|
|
140
|
+
.filter(a => (a.name.endsWith('.png') || (a.contentType || '') === 'image/png') && a.name !== 'screenshot')
|
|
137
141
|
.map(a => {
|
|
138
142
|
let name = '';
|
|
139
143
|
if (a.path) {
|
|
140
144
|
name = this.copyToFolder(destFolder, a.path, a.name);
|
|
141
145
|
}
|
|
142
146
|
else if (a.body) {
|
|
143
|
-
|
|
147
|
+
const safeName = a.name.endsWith('.png') ? a.name : `${a.name}.png`;
|
|
148
|
+
name = this.writeBuffer(destFolder, safeName, a.body);
|
|
144
149
|
}
|
|
145
150
|
return name ? { path: name, name: a.name } : null;
|
|
146
151
|
})
|
|
@@ -152,7 +157,10 @@ class A11yReportAssets {
|
|
|
152
157
|
copyAllOtherAttachments(result, destFolder) {
|
|
153
158
|
const excludedNames = ['screenshot', 'video', 'A11y'];
|
|
154
159
|
return result.attachments
|
|
155
|
-
.filter(a => !excludedNames.includes(a.name) &&
|
|
160
|
+
.filter(a => !excludedNames.includes(a.name) &&
|
|
161
|
+
!a.name.endsWith('.png') &&
|
|
162
|
+
!(a.contentType || '').startsWith('image/') &&
|
|
163
|
+
!(a.contentType || '').startsWith('video/'))
|
|
156
164
|
.map(a => {
|
|
157
165
|
let name = '';
|
|
158
166
|
if (a.path) {
|
package/dist/A11yScanner.js
CHANGED
|
@@ -9,6 +9,7 @@ const playwright_1 = __importDefault(require("@axe-core/playwright"));
|
|
|
9
9
|
const test_1 = require("@playwright/test");
|
|
10
10
|
const A11yAuditOverlay_1 = require("./A11yAuditOverlay");
|
|
11
11
|
const models_1 = require("./models");
|
|
12
|
+
const A11yTimeUtils_1 = require("./A11yTimeUtils");
|
|
12
13
|
/**
|
|
13
14
|
* Sanitizes a string to be safe for use in file paths and prevents path traversal attacks.
|
|
14
15
|
* Removes or replaces dangerous characters and path separators.
|
|
@@ -62,7 +63,17 @@ async function scanA11y(page, testInfo, options = {}) {
|
|
|
62
63
|
if (options.axeOptions) {
|
|
63
64
|
axeBuilder = axeBuilder.options(options.axeOptions);
|
|
64
65
|
}
|
|
65
|
-
|
|
66
|
+
let axeResults;
|
|
67
|
+
try {
|
|
68
|
+
axeResults = await axeBuilder.analyze();
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
if (error.message && (error.message.includes('Test ended') || error.message.includes('Target page, context or browser has been closed'))) {
|
|
72
|
+
console.warn(`[SnapAlly] Accessibility scan skipped: ${error.message}`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
66
77
|
const violationCount = axeResults.violations.length;
|
|
67
78
|
if ((showTerminal || showBrowser) && violationCount > 0) {
|
|
68
79
|
const mainMsg = `[A11yScanner] Violations found: ${violationCount}`;
|
|
@@ -103,9 +114,9 @@ async function scanA11y(page, testInfo, options = {}) {
|
|
|
103
114
|
await overlay.showViolationOverlay({ id: violation.id, help: violation.help }, severityColor);
|
|
104
115
|
if (await locator.isVisible()) {
|
|
105
116
|
await overlay.highlightElement(elementSelector, severityColor);
|
|
106
|
-
// Allow time for
|
|
117
|
+
// Allow a small time for overlay highlight to be visible in video
|
|
107
118
|
// eslint-disable-next-line playwright/no-wait-for-timeout
|
|
108
|
-
await page.waitForTimeout(
|
|
119
|
+
await page.waitForTimeout(100);
|
|
109
120
|
const screenshotName = `a11y-${violation.id}-${errorIdx++}.png`;
|
|
110
121
|
const buffer = await overlay.captureAndAttachScreenshot(screenshotName, testInfo);
|
|
111
122
|
// Capture execution steps for context
|
|
@@ -143,15 +154,17 @@ async function scanA11y(page, testInfo, options = {}) {
|
|
|
143
154
|
// Prepare data for the reporter
|
|
144
155
|
const reportData = {
|
|
145
156
|
pageKey,
|
|
157
|
+
pageUrl: page.url(),
|
|
146
158
|
accessibilityScore: 0, // No longer used, derivation from Lighthouse removed
|
|
147
|
-
errors,
|
|
159
|
+
a11yErrors: errors,
|
|
148
160
|
video: 'a11y-scan-video.webm', // Reference name for reporter
|
|
149
161
|
criticalColor: models_1.Severity.critical,
|
|
150
162
|
seriousColor: models_1.Severity.serious,
|
|
151
163
|
moderateColor: models_1.Severity.moderate,
|
|
152
164
|
minorColor: models_1.Severity.minor,
|
|
153
165
|
adoOrganization: process.env.ADO_ORGANIZATION || '',
|
|
154
|
-
adoProject: process.env.ADO_PROJECT || ''
|
|
166
|
+
adoProject: process.env.ADO_PROJECT || '',
|
|
167
|
+
timestamp: A11yTimeUtils_1.A11yTimeUtils.formatDate(new Date())
|
|
155
168
|
};
|
|
156
169
|
await overlay.addTestAttachment(testInfo, 'A11y', JSON.stringify(reportData));
|
|
157
170
|
await overlay.hideViolationOverlay();
|
package/dist/A11yTimeUtils.d.ts
CHANGED
package/dist/A11yTimeUtils.js
CHANGED
|
@@ -20,5 +20,20 @@ class A11yTimeUtils {
|
|
|
20
20
|
const remainingSeconds = seconds % 60;
|
|
21
21
|
return `${minutes}m ${remainingSeconds.toFixed(0)}s`;
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Formats a Date object into a human-readable string.
|
|
25
|
+
*/
|
|
26
|
+
static formatDate(date) {
|
|
27
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
28
|
+
const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
29
|
+
const month = monthNames[date.getMonth()];
|
|
30
|
+
const year = date.getFullYear();
|
|
31
|
+
let hours = date.getHours();
|
|
32
|
+
const ampm = hours >= 12 ? 'PM' : 'AM';
|
|
33
|
+
hours = hours % 12 || 12;
|
|
34
|
+
const formattedHours = String(hours).padStart(2, '0');
|
|
35
|
+
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
36
|
+
return `${day} ${month} ${year}, ${formattedHours}:${minutes} ${ampm}`;
|
|
37
|
+
}
|
|
23
38
|
}
|
|
24
39
|
exports.A11yTimeUtils = A11yTimeUtils;
|
package/dist/SnapAllyReporter.js
CHANGED
|
@@ -65,7 +65,8 @@ class SnapAllyReporter {
|
|
|
65
65
|
groupedResults: {},
|
|
66
66
|
wcagErrors: {},
|
|
67
67
|
totalA11yErrorCount: 0,
|
|
68
|
-
browserSummaries: {}
|
|
68
|
+
browserSummaries: {},
|
|
69
|
+
date: ''
|
|
69
70
|
};
|
|
70
71
|
// Track async tasks to ensure they finish before onEnd
|
|
71
72
|
this.tasks = [];
|
|
@@ -79,7 +80,7 @@ class SnapAllyReporter {
|
|
|
79
80
|
this.tasks.push(this.processTestResult(test, result));
|
|
80
81
|
}
|
|
81
82
|
async processTestResult(test, result) {
|
|
82
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
83
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
83
84
|
this.testIndex++;
|
|
84
85
|
const sanitizedTitle = test.title.replace(/[^a-z0-9]+/gi, '-').toLowerCase();
|
|
85
86
|
const testFolderName = `${this.testIndex}-${sanitizedTitle}`;
|
|
@@ -91,14 +92,14 @@ class SnapAllyReporter {
|
|
|
91
92
|
}
|
|
92
93
|
const tags = test.tags.map(t => t.replace('@', ''));
|
|
93
94
|
const statusIcon = models_1.TestStatusIcon[result.status] || 'help';
|
|
94
|
-
const
|
|
95
|
+
const projectUse = ((_a = test.parent.project()) === null || _a === void 0 ? void 0 : _a.use) || {};
|
|
96
|
+
const browser = ((_b = test.parent.project()) === null || _b === void 0 ? void 0 : _b.name) || projectUse.browserName || projectUse.defaultBrowserType || 'chromium';
|
|
95
97
|
const descAnnotation = test.annotations.find(a => a.type === 'Description');
|
|
96
98
|
const description = (descAnnotation === null || descAnnotation === void 0 ? void 0 : descAnnotation.description) || 'No Description';
|
|
97
|
-
// Prepare steps from annotations
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
.
|
|
101
|
-
.map(a => a.description || 'Step');
|
|
99
|
+
// Prepare steps from actual test.step calls instead of just annotations
|
|
100
|
+
const steps = result.steps
|
|
101
|
+
.filter(s => s.category === 'test.step')
|
|
102
|
+
.map(s => s.title);
|
|
102
103
|
const preConditions = test.annotations
|
|
103
104
|
.filter(a => a.type === 'Pre Condition')
|
|
104
105
|
.map(a => a.description || '');
|
|
@@ -129,6 +130,7 @@ class SnapAllyReporter {
|
|
|
129
130
|
let a11yReportPath = undefined;
|
|
130
131
|
let a11yErrorCount = 0;
|
|
131
132
|
let aggregatedA11yErrors = [];
|
|
133
|
+
let testStatsPageUrl = undefined;
|
|
132
134
|
// Loop through all accessibility scans in this test
|
|
133
135
|
for (const [index, source] of a11yDataSources.entries()) {
|
|
134
136
|
let reportData;
|
|
@@ -176,10 +178,10 @@ class SnapAllyReporter {
|
|
|
176
178
|
// Set the main report path to the LAST one (or maybe first? using last for now)
|
|
177
179
|
a11yReportPath = a11yReportName;
|
|
178
180
|
// Re-apply configuration
|
|
179
|
-
reportData.criticalColor = ((
|
|
180
|
-
reportData.seriousColor = ((
|
|
181
|
-
reportData.moderateColor = ((
|
|
182
|
-
reportData.minorColor = ((
|
|
181
|
+
reportData.criticalColor = ((_c = this.options.colors) === null || _c === void 0 ? void 0 : _c.critical) || '#c92a2a';
|
|
182
|
+
reportData.seriousColor = ((_d = this.options.colors) === null || _d === void 0 ? void 0 : _d.serious) || '#e67700';
|
|
183
|
+
reportData.moderateColor = ((_e = this.options.colors) === null || _e === void 0 ? void 0 : _e.moderate) || '#ca8a04';
|
|
184
|
+
reportData.minorColor = ((_f = this.options.colors) === null || _f === void 0 ? void 0 : _f.minor) || '#0891b2';
|
|
183
185
|
if (this.options.ado) {
|
|
184
186
|
reportData.adoOrganization = this.options.ado.organization || reportData.adoOrganization;
|
|
185
187
|
reportData.adoProject = this.options.ado.project || reportData.adoProject;
|
|
@@ -187,8 +189,24 @@ class SnapAllyReporter {
|
|
|
187
189
|
// Sync video name
|
|
188
190
|
if (video)
|
|
189
191
|
reportData.video = video;
|
|
192
|
+
// Backfill steps from actual test.step calls if annotations were empty
|
|
193
|
+
const filteredSteps = steps.filter(s => !s.includes('Capture A11y screenshot'));
|
|
194
|
+
if (filteredSteps.length > 0) {
|
|
195
|
+
reportData.a11yErrors.forEach(err => {
|
|
196
|
+
err.target.forEach(t => {
|
|
197
|
+
if (!t.steps || t.steps.length === 0) {
|
|
198
|
+
t.steps = filteredSteps;
|
|
199
|
+
t.stepsJson = JSON.stringify(filteredSteps);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
}
|
|
190
204
|
const auditFile = path.join(testResultsFolder, a11yReportName);
|
|
191
205
|
await this.renderer.render('accessibility-report.html', { data: reportData, folderTest: testResultsFolder }, testResultsFolder, auditFile);
|
|
206
|
+
// Capture the first page URL for the testStats if not already set
|
|
207
|
+
if (reportData.pageUrl && !testStatsPageUrl) {
|
|
208
|
+
testStatsPageUrl = reportData.pageUrl;
|
|
209
|
+
}
|
|
192
210
|
// --- 3. Update Browser-Specific Summary (Partial Aggregation) ---
|
|
193
211
|
if (!this.executionSummary.browserSummaries[browser]) {
|
|
194
212
|
this.executionSummary.browserSummaries[browser] = {
|
|
@@ -206,12 +224,12 @@ class SnapAllyReporter {
|
|
|
206
224
|
};
|
|
207
225
|
}
|
|
208
226
|
const bSummary = this.executionSummary.browserSummaries[browser];
|
|
209
|
-
if (reportData.
|
|
227
|
+
if (reportData.a11yErrors && reportData.a11yErrors.length > 0) {
|
|
210
228
|
// Aggregate counts
|
|
211
|
-
const scanErrorCount = reportData.
|
|
229
|
+
const scanErrorCount = reportData.a11yErrors.reduce((sum, err) => sum + (err.total || 0), 0);
|
|
212
230
|
a11yErrorCount += scanErrorCount;
|
|
213
|
-
aggregatedA11yErrors.push(...reportData.
|
|
214
|
-
reportData.
|
|
231
|
+
aggregatedA11yErrors.push(...reportData.a11yErrors);
|
|
232
|
+
reportData.a11yErrors.forEach((err) => {
|
|
215
233
|
const rule = err.id;
|
|
216
234
|
// Local Browser aggregation
|
|
217
235
|
if (!bSummary.wcagErrors[rule]) {
|
|
@@ -278,13 +296,15 @@ class SnapAllyReporter {
|
|
|
278
296
|
steps,
|
|
279
297
|
postConditions,
|
|
280
298
|
statusIcon,
|
|
299
|
+
pageUrl: testStatsPageUrl,
|
|
281
300
|
videoPath: video,
|
|
282
301
|
screenshotPaths: screenshots,
|
|
283
302
|
attachments: allAttachments,
|
|
284
303
|
errors: errorLogs,
|
|
285
304
|
a11yReportPath,
|
|
286
305
|
a11yErrorCount,
|
|
287
|
-
a11yErrors: aggregatedA11yErrors
|
|
306
|
+
a11yErrors: aggregatedA11yErrors,
|
|
307
|
+
colors: this.options.colors
|
|
288
308
|
};
|
|
289
309
|
this.executionSummary.groupedResults[fileGroup].push(testStats);
|
|
290
310
|
// Update summary counts
|
|
@@ -305,14 +325,14 @@ class SnapAllyReporter {
|
|
|
305
325
|
this.executionSummary.total++;
|
|
306
326
|
// Create color config for template
|
|
307
327
|
const colors = {
|
|
308
|
-
critical: ((
|
|
309
|
-
serious: ((
|
|
310
|
-
moderate: ((
|
|
311
|
-
minor: ((
|
|
328
|
+
critical: ((_g = this.options.colors) === null || _g === void 0 ? void 0 : _g.critical) || '#c92a2a',
|
|
329
|
+
serious: ((_h = this.options.colors) === null || _h === void 0 ? void 0 : _h.serious) || '#e67700',
|
|
330
|
+
moderate: ((_j = this.options.colors) === null || _j === void 0 ? void 0 : _j.moderate) || '#ca8a04',
|
|
331
|
+
minor: ((_k = this.options.colors) === null || _k === void 0 ? void 0 : _k.minor) || '#0891b2'
|
|
312
332
|
};
|
|
313
333
|
// Render Step Report
|
|
314
334
|
const indexFile = path.join(testResultsFolder, `execution-${sanitizedTitle}.html`);
|
|
315
|
-
await this.renderer.render('test-execution-report.html', {
|
|
335
|
+
await this.renderer.render('test-execution-report.html', { ...testStats, colors }, testResultsFolder, indexFile);
|
|
316
336
|
}
|
|
317
337
|
async onEnd(result) {
|
|
318
338
|
var _a, _b, _c, _d;
|
|
@@ -322,13 +342,14 @@ class SnapAllyReporter {
|
|
|
322
342
|
this.executionSummary.duration = A11yTimeUtils_1.A11yTimeUtils.formatDuration(result.duration);
|
|
323
343
|
this.executionSummary.status = result.status;
|
|
324
344
|
this.executionSummary.statusIcon = models_1.TestStatusIcon[result.status] || 'help';
|
|
345
|
+
this.executionSummary.date = A11yTimeUtils_1.A11yTimeUtils.formatDate(new Date());
|
|
325
346
|
const colors = {
|
|
326
347
|
critical: ((_a = this.options.colors) === null || _a === void 0 ? void 0 : _a.critical) || '#c92a2a',
|
|
327
348
|
serious: ((_b = this.options.colors) === null || _b === void 0 ? void 0 : _b.serious) || '#e67700',
|
|
328
349
|
moderate: ((_c = this.options.colors) === null || _c === void 0 ? void 0 : _c.moderate) || '#ca8a04',
|
|
329
350
|
minor: ((_d = this.options.colors) === null || _d === void 0 ? void 0 : _d.minor) || '#0891b2'
|
|
330
351
|
};
|
|
331
|
-
await this.renderer.render('execution-summary.html', {
|
|
352
|
+
await this.renderer.render('execution-summary.html', { ...this.executionSummary, colors }, this.outputFolder, summaryFile);
|
|
332
353
|
console.log(`\n[SnapAlly] Reports generated in: ${path.resolve(this.outputFolder)}`);
|
|
333
354
|
}
|
|
334
355
|
}
|
package/dist/models/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export interface ReportData {
|
|
2
2
|
pageKey: string;
|
|
3
|
+
pageUrl?: string;
|
|
3
4
|
accessibilityScore: number;
|
|
4
5
|
video: string;
|
|
5
|
-
|
|
6
|
+
a11yErrors: A11yError[];
|
|
6
7
|
criticalColor: string;
|
|
7
8
|
seriousColor: string;
|
|
8
9
|
moderateColor: string;
|
|
@@ -10,6 +11,7 @@ export interface ReportData {
|
|
|
10
11
|
adoOrganization?: string;
|
|
11
12
|
adoProject?: string;
|
|
12
13
|
adoPat?: string;
|
|
14
|
+
timestamp?: string;
|
|
13
15
|
}
|
|
14
16
|
export interface A11yError {
|
|
15
17
|
id: string;
|
|
@@ -50,6 +52,7 @@ export interface TestResults {
|
|
|
50
52
|
duration: string;
|
|
51
53
|
description: string;
|
|
52
54
|
status: string;
|
|
55
|
+
pageUrl?: string;
|
|
53
56
|
browser: string;
|
|
54
57
|
tags: string[];
|
|
55
58
|
preConditions: string[];
|
|
@@ -67,8 +70,15 @@ export interface TestResults {
|
|
|
67
70
|
executionReportPath?: string;
|
|
68
71
|
a11yErrors?: A11yError[];
|
|
69
72
|
a11yErrorCount?: number;
|
|
73
|
+
colors?: {
|
|
74
|
+
critical?: string;
|
|
75
|
+
serious?: string;
|
|
76
|
+
moderate?: string;
|
|
77
|
+
minor?: string;
|
|
78
|
+
};
|
|
70
79
|
}
|
|
71
80
|
export interface TestSummary {
|
|
81
|
+
date?: string;
|
|
72
82
|
duration: string;
|
|
73
83
|
status: string;
|
|
74
84
|
statusIcon: string;
|