snap-ally 0.1.1-beta → 0.2.2-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/README.md +13 -0
- package/dist/A11yAuditOverlay.d.ts +47 -18
- package/dist/A11yAuditOverlay.js +211 -107
- package/dist/A11yHtmlRenderer.js +10 -4
- package/dist/A11yScanner.d.ts +1 -1
- package/dist/A11yScanner.js +5 -3
- package/dist/SnapAllyReporter.d.ts +56 -10
- package/dist/SnapAllyReporter.js +305 -240
- package/dist/templates/report-app.js +50 -17
- package/package.json +1 -1
package/dist/SnapAllyReporter.js
CHANGED
|
@@ -39,20 +39,39 @@ const A11yHtmlRenderer_1 = require("./A11yHtmlRenderer");
|
|
|
39
39
|
const A11yTimeUtils_1 = require("./A11yTimeUtils");
|
|
40
40
|
const path = __importStar(require("path"));
|
|
41
41
|
const fs = __importStar(require("fs"));
|
|
42
|
+
/** Default severity colors used when the user doesn't override them. */
|
|
43
|
+
const DEFAULT_COLORS = {
|
|
44
|
+
critical: '#c92a2a',
|
|
45
|
+
serious: '#e67700',
|
|
46
|
+
moderate: '#ca8a04',
|
|
47
|
+
minor: '#0891b2',
|
|
48
|
+
};
|
|
49
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
50
|
+
// Reporter
|
|
51
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
42
52
|
/**
|
|
43
53
|
* Playwright reporter for accessibility audits and test steps.
|
|
44
|
-
*
|
|
54
|
+
*
|
|
55
|
+
* Generates:
|
|
56
|
+
* - A per-test execution report (steps, video, screenshots, errors)
|
|
57
|
+
* - A per-scan accessibility report (violations, evidence, ADO integration)
|
|
58
|
+
* - A global execution summary with per-browser breakdowns
|
|
45
59
|
*/
|
|
46
60
|
class SnapAllyReporter {
|
|
47
|
-
printsToStdio() {
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
61
|
constructor(options = {}) {
|
|
51
|
-
|
|
62
|
+
var _a, _b, _c, _d;
|
|
52
63
|
this.assetsManager = new A11yReportAssets_1.A11yReportAssets();
|
|
53
64
|
this.renderer = new A11yHtmlRenderer_1.A11yHtmlRenderer();
|
|
54
65
|
this.projectRoot = 'tests';
|
|
55
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Monotonically increasing test counter.
|
|
68
|
+
* Incremented synchronously in {@link onTestEnd} to avoid race conditions
|
|
69
|
+
* when multiple async {@link processTestResult} calls run concurrently.
|
|
70
|
+
*/
|
|
71
|
+
this.testIndex = 0;
|
|
72
|
+
/** Async tasks queued by `onTestEnd`; drained in `onEnd`. */
|
|
73
|
+
this.tasks = [];
|
|
74
|
+
/** Aggregated data for the final summary report. */
|
|
56
75
|
this.executionSummary = {
|
|
57
76
|
duration: '',
|
|
58
77
|
status: '',
|
|
@@ -68,221 +87,293 @@ class SnapAllyReporter {
|
|
|
68
87
|
browserSummaries: {},
|
|
69
88
|
date: '',
|
|
70
89
|
};
|
|
71
|
-
// Track async tasks to ensure they finish before onEnd
|
|
72
|
-
this.tasks = [];
|
|
73
90
|
this.options = options;
|
|
74
91
|
this.outputFolder = path.resolve(process.cwd(), options.outputFolder || 'steps-report');
|
|
92
|
+
this.colors = {
|
|
93
|
+
critical: ((_a = options.colors) === null || _a === void 0 ? void 0 : _a.critical) || DEFAULT_COLORS.critical,
|
|
94
|
+
serious: ((_b = options.colors) === null || _b === void 0 ? void 0 : _b.serious) || DEFAULT_COLORS.serious,
|
|
95
|
+
moderate: ((_c = options.colors) === null || _c === void 0 ? void 0 : _c.moderate) || DEFAULT_COLORS.moderate,
|
|
96
|
+
minor: ((_d = options.colors) === null || _d === void 0 ? void 0 : _d.minor) || DEFAULT_COLORS.minor,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
printsToStdio() {
|
|
100
|
+
return false;
|
|
75
101
|
}
|
|
76
102
|
onBegin(config) {
|
|
77
103
|
this.projectRoot = config.rootDir || 'tests';
|
|
78
104
|
}
|
|
79
105
|
onTestEnd(test, result) {
|
|
80
|
-
|
|
106
|
+
// Increment index synchronously so concurrent tasks never share an index.
|
|
107
|
+
const index = ++this.testIndex;
|
|
108
|
+
this.tasks.push(this.processTestResult(test, result, index));
|
|
109
|
+
}
|
|
110
|
+
async onEnd(result) {
|
|
111
|
+
await Promise.all(this.tasks);
|
|
112
|
+
this.executionSummary.duration = A11yTimeUtils_1.A11yTimeUtils.formatDuration(result.duration);
|
|
113
|
+
this.executionSummary.status = result.status;
|
|
114
|
+
this.executionSummary.statusIcon =
|
|
115
|
+
models_1.TestStatusIcon[result.status] || 'help';
|
|
116
|
+
this.executionSummary.date = A11yTimeUtils_1.A11yTimeUtils.formatDate(new Date());
|
|
117
|
+
const summaryFile = path.join(this.outputFolder, 'summary.html');
|
|
118
|
+
await this.renderer.render('execution-summary.html', { ...this.executionSummary, colors: this.colors }, this.outputFolder, summaryFile);
|
|
119
|
+
console.log(`\n[SnapAlly] Reports generated in: ${path.resolve(this.outputFolder)}`);
|
|
81
120
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
121
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
122
|
+
// Core per-test processing
|
|
123
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
124
|
+
async processTestResult(test, result, index) {
|
|
85
125
|
const sanitizedTitle = test.title.replace(/[^a-z0-9]+/gi, '-').toLowerCase();
|
|
86
|
-
const testFolderName = `${
|
|
126
|
+
const testFolderName = `${index}-${sanitizedTitle}`;
|
|
87
127
|
const testResultsFolder = path.join(this.outputFolder, testFolderName);
|
|
88
|
-
// --- 1. Functional Step Reporting ---
|
|
89
128
|
const fileGroup = path.relative(this.projectRoot, test.location.file);
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
129
|
+
this.ensureGroupExists(fileGroup);
|
|
130
|
+
const browser = this.resolveBrowser(test);
|
|
131
|
+
const testMeta = this.extractTestMetadata(test, result);
|
|
132
|
+
// Copy assets
|
|
133
|
+
const video = await this.assetsManager.copyTestVideo(result, testResultsFolder);
|
|
134
|
+
const screenshots = this.assetsManager.copyScreenshots(result, testResultsFolder);
|
|
135
|
+
const allAttachments = [
|
|
136
|
+
...this.assetsManager.copyPngAttachments(result, testResultsFolder),
|
|
137
|
+
...this.assetsManager.copyAllOtherAttachments(result, testResultsFolder),
|
|
138
|
+
];
|
|
139
|
+
const errorLogs = this.extractErrorLogs(result);
|
|
140
|
+
// Accessibility processing
|
|
141
|
+
const a11yResult = await this.processAccessibilityData(test, result, sanitizedTitle, testResultsFolder, testMeta.steps, video, browser, errorLogs);
|
|
142
|
+
// Update browser & global summary counts
|
|
143
|
+
this.updateBrowserSummary(browser, result.status);
|
|
144
|
+
this.updateGlobalSummary(test, result);
|
|
145
|
+
// Build the test stats object
|
|
146
|
+
const executionReportName = `execution-${sanitizedTitle}.html`;
|
|
147
|
+
const testStats = {
|
|
148
|
+
num: index,
|
|
149
|
+
folderName: testFolderName,
|
|
150
|
+
executionReportPath: `${testFolderName}/${executionReportName}`,
|
|
151
|
+
title: test.title,
|
|
152
|
+
fileName: fileGroup,
|
|
153
|
+
timeDuration: result.duration,
|
|
154
|
+
duration: A11yTimeUtils_1.A11yTimeUtils.formatDuration(result.duration),
|
|
155
|
+
description: testMeta.description,
|
|
156
|
+
status: result.status,
|
|
157
|
+
browser,
|
|
158
|
+
tags: testMeta.tags,
|
|
159
|
+
preConditions: testMeta.preConditions,
|
|
160
|
+
steps: testMeta.steps,
|
|
161
|
+
postConditions: testMeta.postConditions,
|
|
162
|
+
statusIcon: testMeta.statusIcon,
|
|
163
|
+
pageUrl: a11yResult.pageUrl,
|
|
164
|
+
videoPath: video,
|
|
165
|
+
screenshotPaths: screenshots,
|
|
166
|
+
attachments: allAttachments,
|
|
167
|
+
errors: errorLogs,
|
|
168
|
+
a11yReportPath: a11yResult.reportPath,
|
|
169
|
+
a11yErrorCount: a11yResult.errorCount,
|
|
170
|
+
a11yErrors: a11yResult.errors,
|
|
171
|
+
colors: this.options.colors,
|
|
172
|
+
};
|
|
173
|
+
this.executionSummary.groupedResults[fileGroup].push(testStats);
|
|
174
|
+
// Render the per-test execution report
|
|
175
|
+
const indexFile = path.join(testResultsFolder, executionReportName);
|
|
176
|
+
await this.renderer.render('test-execution-report.html', { ...testStats, colors: this.colors }, testResultsFolder, indexFile);
|
|
177
|
+
}
|
|
178
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
179
|
+
// Metadata extraction
|
|
180
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
181
|
+
/** Extracts structured metadata from test annotations and result steps. */
|
|
182
|
+
extractTestMetadata(test, result) {
|
|
93
183
|
const tags = test.tags.map((t) => t.replace('@', ''));
|
|
94
184
|
const statusIcon = models_1.TestStatusIcon[result.status] || 'help';
|
|
95
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
96
|
-
const projectUse = ((_a = test.parent.project()) === null || _a === void 0 ? void 0 : _a.use) || {};
|
|
97
|
-
const browser = ((_b = test.parent.project()) === null || _b === void 0 ? void 0 : _b.name) ||
|
|
98
|
-
projectUse.browserName ||
|
|
99
|
-
projectUse.defaultBrowserType ||
|
|
100
|
-
'chromium';
|
|
101
185
|
const descAnnotation = test.annotations.find((a) => a.type === 'Description');
|
|
102
186
|
const description = (descAnnotation === null || descAnnotation === void 0 ? void 0 : descAnnotation.description) || 'No Description';
|
|
103
|
-
|
|
104
|
-
|
|
187
|
+
const steps = result.steps
|
|
188
|
+
.filter((s) => s.category === 'test.step')
|
|
189
|
+
.map((s) => s.title);
|
|
105
190
|
const preConditions = test.annotations
|
|
106
191
|
.filter((a) => a.type === 'Pre Condition')
|
|
107
192
|
.map((a) => a.description || '');
|
|
108
193
|
const postConditions = test.annotations
|
|
109
194
|
.filter((a) => a.type === 'Post Condition')
|
|
110
195
|
.map((a) => a.description || '');
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
196
|
+
return { tags, statusIcon, description, steps, preConditions, postConditions };
|
|
197
|
+
}
|
|
198
|
+
/** Determines the browser name for the current test. */
|
|
199
|
+
resolveBrowser(test) {
|
|
200
|
+
var _a, _b;
|
|
201
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
202
|
+
const projectUse = ((_a = test.parent.project()) === null || _a === void 0 ? void 0 : _a.use) || {};
|
|
203
|
+
return (((_b = test.parent.project()) === null || _b === void 0 ? void 0 : _b.name) ||
|
|
204
|
+
projectUse.browserName ||
|
|
205
|
+
projectUse.defaultBrowserType ||
|
|
206
|
+
'chromium');
|
|
207
|
+
}
|
|
208
|
+
/** Converts Playwright error objects into HTML-safe strings. */
|
|
209
|
+
extractErrorLogs(result) {
|
|
210
|
+
return (result.errors || []).map((err) => {
|
|
119
211
|
const fullMsg = err.stack
|
|
120
212
|
? `${err.message}\n${err.stack}`
|
|
121
213
|
: err.message || 'Error occurred';
|
|
122
214
|
return this.renderer.ansiToHtml(fullMsg);
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
218
|
+
// Accessibility data processing
|
|
219
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
220
|
+
/** Return value for {@link processAccessibilityData}. */
|
|
221
|
+
async processAccessibilityData(test, result, sanitizedTitle, testResultsFolder, steps, video, browser, errorLogs) {
|
|
222
|
+
var _a;
|
|
223
|
+
const sources = this.collectA11yDataSources(test, result);
|
|
224
|
+
if (sources.length === 0) {
|
|
225
|
+
return { errorCount: 0, errors: [] };
|
|
133
226
|
}
|
|
134
|
-
let
|
|
135
|
-
let
|
|
136
|
-
|
|
137
|
-
let
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
try {
|
|
142
|
-
if (source.type === 'attachment') {
|
|
143
|
-
const attach = source.data;
|
|
144
|
-
if (attach.body) {
|
|
145
|
-
reportData = JSON.parse(attach.body.toString());
|
|
146
|
-
}
|
|
147
|
-
else if (attach.path && fs.existsSync(attach.path)) {
|
|
148
|
-
reportData = JSON.parse(fs.readFileSync(attach.path, 'utf-8'));
|
|
149
|
-
}
|
|
150
|
-
else {
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
else {
|
|
155
|
-
const annot = source.data;
|
|
156
|
-
reportData = JSON.parse(annot.description || '{}');
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
catch (e) {
|
|
160
|
-
console.error(`[SnapAlly] Failed to parse A11y ${source.type}: ${e}`);
|
|
161
|
-
errorLogs.push(this.renderer.ansiToHtml(`[SnapAlly] Internal error parsing accessibility data from ${source.type}: ${e}`));
|
|
227
|
+
let reportPath;
|
|
228
|
+
let errorCount = 0;
|
|
229
|
+
const aggregatedErrors = [];
|
|
230
|
+
let pageUrl;
|
|
231
|
+
for (const [index, source] of sources.entries()) {
|
|
232
|
+
const reportData = this.parseA11ySource(source, errorLogs);
|
|
233
|
+
if (!reportData)
|
|
162
234
|
continue;
|
|
235
|
+
const reportName = this.buildA11yReportName(sanitizedTitle, reportData.pageKey, index, sources.length);
|
|
236
|
+
reportPath = reportName;
|
|
237
|
+
this.applyReportConfig(reportData, video);
|
|
238
|
+
this.backfillSteps(reportData, steps);
|
|
239
|
+
const auditFile = path.join(testResultsFolder, reportName);
|
|
240
|
+
await this.renderer.render('accessibility-report.html', { data: reportData, folderTest: testResultsFolder }, testResultsFolder, auditFile);
|
|
241
|
+
if (reportData.pageUrl && !pageUrl) {
|
|
242
|
+
pageUrl = reportData.pageUrl;
|
|
163
243
|
}
|
|
164
|
-
//
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
244
|
+
// Aggregate a11y errors into browser & global summaries
|
|
245
|
+
if ((_a = reportData.a11yErrors) === null || _a === void 0 ? void 0 : _a.length) {
|
|
246
|
+
const scanCount = this.aggregateA11yErrors(reportData.a11yErrors, browser);
|
|
247
|
+
errorCount += scanCount;
|
|
248
|
+
aggregatedErrors.push(...reportData.a11yErrors);
|
|
168
249
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
250
|
+
}
|
|
251
|
+
return { reportPath, errorCount, errors: aggregatedErrors, pageUrl };
|
|
252
|
+
}
|
|
253
|
+
/** Collects all A11y data sources (attachments + annotations) for a test. */
|
|
254
|
+
collectA11yDataSources(test, result) {
|
|
255
|
+
const attachments = (result.attachments || [])
|
|
256
|
+
.filter((a) => a.name === 'A11y')
|
|
257
|
+
.map((a) => ({ type: 'attachment', data: a }));
|
|
258
|
+
const annotations = (test.annotations || [])
|
|
259
|
+
.filter((a) => a.type === 'A11y')
|
|
260
|
+
.map((a) => ({ type: 'annotation', data: a }));
|
|
261
|
+
return [...attachments, ...annotations];
|
|
262
|
+
}
|
|
263
|
+
/** Attempts to parse a single A11y data source into a ReportData object. */
|
|
264
|
+
parseA11ySource(source, errorLogs) {
|
|
265
|
+
try {
|
|
266
|
+
if (source.type === 'attachment') {
|
|
267
|
+
const attach = source.data;
|
|
268
|
+
if (attach.body) {
|
|
269
|
+
return JSON.parse(attach.body.toString());
|
|
181
270
|
}
|
|
271
|
+
if (attach.path && fs.existsSync(attach.path)) {
|
|
272
|
+
return JSON.parse(fs.readFileSync(attach.path, 'utf-8'));
|
|
273
|
+
}
|
|
274
|
+
return null;
|
|
182
275
|
}
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
t.stepsJson = JSON.stringify(filteredSteps);
|
|
206
|
-
}
|
|
207
|
-
});
|
|
208
|
-
});
|
|
276
|
+
// annotation
|
|
277
|
+
const annot = source.data;
|
|
278
|
+
return JSON.parse(annot.description || '{}');
|
|
279
|
+
}
|
|
280
|
+
catch (e) {
|
|
281
|
+
console.error(`[SnapAlly] Failed to parse A11y ${source.type}: ${e}`);
|
|
282
|
+
errorLogs.push(this.renderer.ansiToHtml(`[SnapAlly] Internal error parsing accessibility data from ${source.type}: ${e}`));
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/** Generates a sanitized HTML filename for an accessibility report. */
|
|
287
|
+
buildA11yReportName(sanitizedTitle, pageKey, index, totalScans) {
|
|
288
|
+
const hasMultiple = totalScans > 1;
|
|
289
|
+
const suffix = hasMultiple ? `-${index + 1}` : '';
|
|
290
|
+
if (pageKey) {
|
|
291
|
+
const sanitizedKey = pageKey
|
|
292
|
+
.replace(/https?:\/\//, '')
|
|
293
|
+
.replace(/[^a-z0-9]+/gi, '-')
|
|
294
|
+
.replace(/^-+|-+$/g, '')
|
|
295
|
+
.toLowerCase();
|
|
296
|
+
if (sanitizedKey) {
|
|
297
|
+
return `${sanitizedKey}${suffix}.html`;
|
|
209
298
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
299
|
+
}
|
|
300
|
+
return `accessibility-${sanitizedTitle}${suffix}.html`;
|
|
301
|
+
}
|
|
302
|
+
/** Applies reporter-level configuration (colors, ADO, video) to a ReportData object. */
|
|
303
|
+
applyReportConfig(reportData, video) {
|
|
304
|
+
reportData.criticalColor = this.colors.critical;
|
|
305
|
+
reportData.seriousColor = this.colors.serious;
|
|
306
|
+
reportData.moderateColor = this.colors.moderate;
|
|
307
|
+
reportData.minorColor = this.colors.minor;
|
|
308
|
+
if (this.options.ado) {
|
|
309
|
+
reportData.adoOrganization =
|
|
310
|
+
this.options.ado.organization || reportData.adoOrganization;
|
|
311
|
+
reportData.adoProject = this.options.ado.project || reportData.adoProject;
|
|
312
|
+
}
|
|
313
|
+
if (video) {
|
|
314
|
+
reportData.video = video;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Backfills reproduction steps from `test.step` calls into a11y targets
|
|
319
|
+
* that have no steps recorded (e.g. violations found via static scan).
|
|
320
|
+
*/
|
|
321
|
+
backfillSteps(reportData, steps) {
|
|
322
|
+
const filteredSteps = steps.filter((s) => !s.includes('Capture A11y screenshot'));
|
|
323
|
+
if (filteredSteps.length === 0)
|
|
324
|
+
return;
|
|
325
|
+
for (const err of reportData.a11yErrors) {
|
|
326
|
+
for (const target of err.target) {
|
|
327
|
+
if (!target.steps || target.steps.length === 0) {
|
|
328
|
+
target.steps = filteredSteps;
|
|
329
|
+
target.stepsJson = JSON.stringify(filteredSteps);
|
|
330
|
+
}
|
|
215
331
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
// ──────────────────────────────���─────────────────────────────────────────
|
|
335
|
+
// Summary aggregation
|
|
336
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
337
|
+
/**
|
|
338
|
+
* Aggregates a11y error counts into both the browser-specific and global
|
|
339
|
+
* summaries. Returns the total error count for this scan.
|
|
340
|
+
*/
|
|
341
|
+
aggregateA11yErrors(errors, browser) {
|
|
342
|
+
const bSummary = this.getOrCreateBrowserSummary(browser);
|
|
343
|
+
let scanErrorCount = 0;
|
|
344
|
+
for (const err of errors) {
|
|
345
|
+
const count = err.total || 0;
|
|
346
|
+
scanErrorCount += count;
|
|
347
|
+
// Browser-level aggregation
|
|
348
|
+
if (!bSummary.wcagErrors[err.id]) {
|
|
349
|
+
bSummary.wcagErrors[err.id] = {
|
|
350
|
+
count: 0,
|
|
351
|
+
severity: err.severity,
|
|
352
|
+
helpUrl: err.helpUrl,
|
|
353
|
+
description: err.description,
|
|
230
354
|
};
|
|
231
355
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
if (!bSummary.wcagErrors[rule]) {
|
|
242
|
-
bSummary.wcagErrors[rule] = {
|
|
243
|
-
count: 0,
|
|
244
|
-
severity: err.severity,
|
|
245
|
-
helpUrl: err.helpUrl,
|
|
246
|
-
description: err.description,
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
bSummary.wcagErrors[rule].count += err.total || 0;
|
|
250
|
-
// Global aggregation (always add to ensure summary is not empty)
|
|
251
|
-
if (!this.executionSummary.wcagErrors[rule]) {
|
|
252
|
-
this.executionSummary.wcagErrors[rule] = {
|
|
253
|
-
count: 0,
|
|
254
|
-
severity: err.severity,
|
|
255
|
-
helpUrl: err.helpUrl,
|
|
256
|
-
description: err.description,
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
this.executionSummary.wcagErrors[rule].count += err.total || 0;
|
|
260
|
-
});
|
|
261
|
-
// Update total error counts
|
|
262
|
-
bSummary.totalA11yErrorCount += scanErrorCount;
|
|
263
|
-
this.executionSummary.totalA11yErrorCount += scanErrorCount;
|
|
356
|
+
bSummary.wcagErrors[err.id].count += count;
|
|
357
|
+
// Global aggregation
|
|
358
|
+
if (!this.executionSummary.wcagErrors[err.id]) {
|
|
359
|
+
this.executionSummary.wcagErrors[err.id] = {
|
|
360
|
+
count: 0,
|
|
361
|
+
severity: err.severity,
|
|
362
|
+
helpUrl: err.helpUrl,
|
|
363
|
+
description: err.description,
|
|
364
|
+
};
|
|
264
365
|
}
|
|
366
|
+
this.executionSummary.wcagErrors[err.id].count += count;
|
|
265
367
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
total: 0,
|
|
274
|
-
totalFailed: 0,
|
|
275
|
-
totalFlaky: 0,
|
|
276
|
-
totalPassed: 0,
|
|
277
|
-
totalSkipped: 0,
|
|
278
|
-
groupedResults: {},
|
|
279
|
-
wcagErrors: {},
|
|
280
|
-
totalA11yErrorCount: 0,
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
const bSummary = this.executionSummary.browserSummaries[browser];
|
|
368
|
+
bSummary.totalA11yErrorCount += scanErrorCount;
|
|
369
|
+
this.executionSummary.totalA11yErrorCount += scanErrorCount;
|
|
370
|
+
return scanErrorCount;
|
|
371
|
+
}
|
|
372
|
+
/** Updates the browser-specific test counts (passed/failed/skipped). */
|
|
373
|
+
updateBrowserSummary(browser, status) {
|
|
374
|
+
const bSummary = this.getOrCreateBrowserSummary(browser);
|
|
284
375
|
bSummary.total++;
|
|
285
|
-
switch (
|
|
376
|
+
switch (status) {
|
|
286
377
|
case 'passed':
|
|
287
378
|
bSummary.totalPassed++;
|
|
288
379
|
break;
|
|
@@ -293,35 +384,9 @@ class SnapAllyReporter {
|
|
|
293
384
|
bSummary.totalSkipped++;
|
|
294
385
|
break;
|
|
295
386
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
folderName: testFolderName,
|
|
300
|
-
executionReportPath: `${testFolderName}/${executionReportName}`,
|
|
301
|
-
title: test.title,
|
|
302
|
-
fileName: fileGroup,
|
|
303
|
-
timeDuration: result.duration,
|
|
304
|
-
duration: A11yTimeUtils_1.A11yTimeUtils.formatDuration(result.duration),
|
|
305
|
-
description,
|
|
306
|
-
status: result.status,
|
|
307
|
-
browser,
|
|
308
|
-
tags,
|
|
309
|
-
preConditions,
|
|
310
|
-
steps,
|
|
311
|
-
postConditions,
|
|
312
|
-
statusIcon,
|
|
313
|
-
pageUrl: testStatsPageUrl,
|
|
314
|
-
videoPath: video,
|
|
315
|
-
screenshotPaths: screenshots,
|
|
316
|
-
attachments: allAttachments,
|
|
317
|
-
errors: errorLogs,
|
|
318
|
-
a11yReportPath,
|
|
319
|
-
a11yErrorCount,
|
|
320
|
-
a11yErrors: aggregatedA11yErrors,
|
|
321
|
-
colors: this.options.colors,
|
|
322
|
-
};
|
|
323
|
-
this.executionSummary.groupedResults[fileGroup].push(testStats);
|
|
324
|
-
// Update summary counts
|
|
387
|
+
}
|
|
388
|
+
/** Updates the global execution summary counts. */
|
|
389
|
+
updateGlobalSummary(test, result) {
|
|
325
390
|
const isFlaky = test.results.length > 1 && result.status === 'passed';
|
|
326
391
|
if (isFlaky)
|
|
327
392
|
this.executionSummary.totalFlaky++;
|
|
@@ -337,35 +402,35 @@ class SnapAllyReporter {
|
|
|
337
402
|
break;
|
|
338
403
|
}
|
|
339
404
|
this.executionSummary.total++;
|
|
340
|
-
// Create color config for template
|
|
341
|
-
const colors = {
|
|
342
|
-
critical: ((_g = this.options.colors) === null || _g === void 0 ? void 0 : _g.critical) || '#c92a2a',
|
|
343
|
-
serious: ((_h = this.options.colors) === null || _h === void 0 ? void 0 : _h.serious) || '#e67700',
|
|
344
|
-
moderate: ((_j = this.options.colors) === null || _j === void 0 ? void 0 : _j.moderate) || '#ca8a04',
|
|
345
|
-
minor: ((_k = this.options.colors) === null || _k === void 0 ? void 0 : _k.minor) || '#0891b2',
|
|
346
|
-
};
|
|
347
|
-
// Render Step Report
|
|
348
|
-
const indexFile = path.join(testResultsFolder, `execution-${sanitizedTitle}.html`);
|
|
349
|
-
await this.renderer.render('test-execution-report.html', { ...testStats, colors }, testResultsFolder, indexFile);
|
|
350
405
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
this.executionSummary.
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
406
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
407
|
+
// Helpers
|
|
408
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
409
|
+
/** Ensures a file group key exists in the grouped results map. */
|
|
410
|
+
ensureGroupExists(fileGroup) {
|
|
411
|
+
if (!this.executionSummary.groupedResults[fileGroup]) {
|
|
412
|
+
this.executionSummary.groupedResults[fileGroup] = [];
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
/** Lazily initialises and returns the browser summary for the given browser name. */
|
|
416
|
+
getOrCreateBrowserSummary(browser) {
|
|
417
|
+
const summaries = this.executionSummary.browserSummaries;
|
|
418
|
+
if (!summaries[browser]) {
|
|
419
|
+
summaries[browser] = {
|
|
420
|
+
duration: '0s',
|
|
421
|
+
status: '',
|
|
422
|
+
statusIcon: '',
|
|
423
|
+
total: 0,
|
|
424
|
+
totalFailed: 0,
|
|
425
|
+
totalFlaky: 0,
|
|
426
|
+
totalPassed: 0,
|
|
427
|
+
totalSkipped: 0,
|
|
428
|
+
groupedResults: {},
|
|
429
|
+
wcagErrors: {},
|
|
430
|
+
totalA11yErrorCount: 0,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
return summaries[browser];
|
|
369
434
|
}
|
|
370
435
|
}
|
|
371
436
|
exports.default = SnapAllyReporter;
|