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.
@@ -1,11 +1,11 @@
1
1
  /**
2
- * Handles the rendering of HTML reports using EJS templates.
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 an HTML template and saves it to the specified file.
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 EJS.
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
  */
@@ -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 EJS templates.
40
+ * Handles the rendering of HTML reports using static templates and JSON data injection.
42
41
  */
43
42
  class A11yHtmlRenderer {
44
43
  /**
45
- * Renders an HTML template and saves it to the specified file.
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 EJS.
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 templatePath = path.join(__dirname, 'templates', templateName);
54
- let templateContent = '';
55
- try {
56
- templateContent = fs.readFileSync(templatePath, 'utf8');
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
- let html = '';
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
- html = ejs.render(templateContent, data);
73
+ if (fs.existsSync(cssPath))
74
+ fs.copyFileSync(cssPath, outCssPath);
64
75
  }
65
- catch (error) {
66
- console.error(`[A11yHtmlRenderer] EJS Render Error (${templateName}):`, error);
67
- throw error;
76
+ catch (e) {
77
+ console.error('Error copying CSS:', e);
68
78
  }
69
- if (!fs.existsSync(outputFolder)) {
70
- fs.mkdirSync(outputFolder, { recursive: true });
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 small retry to ensure Playwright has finished flushing the file.
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
  /**
@@ -57,22 +57,24 @@ class A11yReportAssets {
57
57
  }
58
58
  /**
59
59
  * Copies the test video if available.
60
- * Includes a small retry to ensure Playwright has finished flushing the file.
60
+ * Includes a more robust retry to ensure Playwright has finished flushing the file.
61
61
  */
62
62
  async copyTestVideo(result, destFolder) {
63
- const videoAttachments = result.attachments.filter(a => a.name === 'video');
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 2 seconds)
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 < 10) {
73
+ while (attempts < 15) {
73
74
  if (fs.existsSync(attachment.path)) {
74
75
  try {
75
- if (fs.statSync(attachment.path).size > 0) {
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
- return this.writeBuffer(destFolder, `screenshot-${Date.now()}.png`, a.body);
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
- name = this.writeBuffer(destFolder, a.name, a.body);
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) && !a.name.endsWith('.png'))
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) {
@@ -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
- const axeResults = await axeBuilder.analyze();
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 video capture or manual inspection during debug
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(2000);
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();
@@ -6,4 +6,8 @@ export declare class A11yTimeUtils {
6
6
  * Formats milliseconds into a human-readable duration string.
7
7
  */
8
8
  static formatDuration(ms: number): string;
9
+ /**
10
+ * Formats a Date object into a human-readable string.
11
+ */
12
+ static formatDate(date: Date): string;
9
13
  }
@@ -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;
@@ -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 browser = ((_a = test.parent.project()) === null || _a === void 0 ? void 0 : _a.name) || 'unknown';
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 skipTypes = new Set(['Pre Condition', 'Post Condition', 'Description', 'A11y']);
99
- const steps = test.annotations
100
- .filter(a => !skipTypes.has(a.type))
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 = ((_b = this.options.colors) === null || _b === void 0 ? void 0 : _b.critical) || '#c92a2a';
180
- reportData.seriousColor = ((_c = this.options.colors) === null || _c === void 0 ? void 0 : _c.serious) || '#e67700';
181
- reportData.moderateColor = ((_d = this.options.colors) === null || _d === void 0 ? void 0 : _d.moderate) || '#ca8a04';
182
- reportData.minorColor = ((_e = this.options.colors) === null || _e === void 0 ? void 0 : _e.minor) || '#0891b2';
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.errors && reportData.errors.length > 0) {
227
+ if (reportData.a11yErrors && reportData.a11yErrors.length > 0) {
210
228
  // Aggregate counts
211
- const scanErrorCount = reportData.errors.reduce((sum, err) => sum + (err.total || 0), 0);
229
+ const scanErrorCount = reportData.a11yErrors.reduce((sum, err) => sum + (err.total || 0), 0);
212
230
  a11yErrorCount += scanErrorCount;
213
- aggregatedA11yErrors.push(...reportData.errors);
214
- reportData.errors.forEach((err) => {
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: ((_f = this.options.colors) === null || _f === void 0 ? void 0 : _f.critical) || '#c92a2a',
309
- serious: ((_g = this.options.colors) === null || _g === void 0 ? void 0 : _g.serious) || '#e67700',
310
- moderate: ((_h = this.options.colors) === null || _h === void 0 ? void 0 : _h.moderate) || '#ca8a04',
311
- minor: ((_j = this.options.colors) === null || _j === void 0 ? void 0 : _j.minor) || '#0891b2'
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', { result: testStats, colors }, testResultsFolder, indexFile);
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', { results: this.executionSummary, colors }, this.outputFolder, summaryFile);
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
  }
@@ -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
- errors: A11yError[];
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;