snap-ally 0.2.7-beta → 0.3.0

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.
Files changed (75) hide show
  1. package/dist/A11yReportAssets.d.ts +5 -12
  2. package/dist/A11yReportAssets.js +16 -82
  3. package/dist/A11yScanner.d.ts +2 -21
  4. package/dist/A11yScanner.js +16 -22
  5. package/dist/A11yTimeUtils.js +11 -23
  6. package/dist/A11yVisualReporter.d.ts +50 -0
  7. package/dist/A11yVisualReporter.js +188 -0
  8. package/dist/AccessibilityReporterOptions.d.ts +24 -0
  9. package/dist/AccessibilityReporterOptions.js +5 -0
  10. package/dist/ResolvedColors.d.ts +15 -0
  11. package/dist/ResolvedColors.js +20 -0
  12. package/dist/SnapAllyReporter.d.ts +12 -72
  13. package/dist/SnapAllyReporter.js +218 -329
  14. package/dist/core/A11yHtmlRenderer.d.ts +17 -0
  15. package/dist/core/A11yHtmlRenderer.js +118 -0
  16. package/dist/core/A11yReportAssets.d.ts +30 -0
  17. package/dist/core/A11yReportAssets.js +127 -0
  18. package/dist/core/A11yScanner.d.ts +8 -0
  19. package/dist/core/A11yScanner.js +178 -0
  20. package/dist/core/A11yVisualReporter.d.ts +50 -0
  21. package/dist/core/A11yVisualReporter.js +188 -0
  22. package/dist/core/HtmlRenderer.d.ts +14 -0
  23. package/dist/core/HtmlRenderer.js +106 -0
  24. package/dist/core/ReportAssets.d.ts +29 -0
  25. package/dist/core/ReportAssets.js +126 -0
  26. package/dist/core/Scanner.d.ts +7 -0
  27. package/dist/core/Scanner.js +162 -0
  28. package/dist/core/VisualReporter.d.ts +54 -0
  29. package/dist/core/VisualReporter.js +192 -0
  30. package/dist/index.d.ts +6 -6
  31. package/dist/index.js +13 -12
  32. package/dist/models/A11yDataSource.d.ts +15 -0
  33. package/dist/models/A11yDataSource.js +2 -0
  34. package/dist/models/A11yError.d.ts +34 -0
  35. package/dist/models/A11yError.js +11 -0
  36. package/dist/models/A11yScannerOptions.d.ts +24 -0
  37. package/dist/models/A11yScannerOptions.js +2 -0
  38. package/dist/models/AccessibilityReporterOptions.d.ts +24 -0
  39. package/dist/models/AccessibilityReporterOptions.js +5 -0
  40. package/dist/models/DataSource.d.ts +15 -0
  41. package/dist/models/DataSource.js +2 -0
  42. package/dist/models/ImagePath.d.ts +5 -0
  43. package/dist/models/ImagePath.js +3 -0
  44. package/dist/models/ReportData.d.ts +24 -0
  45. package/dist/models/ReportData.js +2 -0
  46. package/dist/models/ReporterOptions.d.ts +24 -0
  47. package/dist/models/ReporterOptions.js +5 -0
  48. package/dist/models/ResolvedColors.d.ts +16 -0
  49. package/dist/models/ResolvedColors.js +24 -0
  50. package/dist/models/ScannerOptions.d.ts +30 -0
  51. package/dist/models/ScannerOptions.js +2 -0
  52. package/dist/models/Severity.d.ts +7 -0
  53. package/dist/models/Severity.js +11 -0
  54. package/dist/models/Target.d.ts +10 -0
  55. package/dist/models/Target.js +3 -0
  56. package/dist/models/TestResults.d.ts +41 -0
  57. package/dist/models/TestResults.js +2 -0
  58. package/dist/models/TestStatusIcon.d.ts +8 -0
  59. package/dist/models/TestStatusIcon.js +12 -0
  60. package/dist/models/TestSummary.d.ts +34 -0
  61. package/dist/models/TestSummary.js +2 -0
  62. package/dist/models/Violation.d.ts +13 -0
  63. package/dist/models/Violation.js +2 -0
  64. package/dist/models/index.d.ts +12 -113
  65. package/dist/models/index.js +26 -16
  66. package/dist/templates/accessibility-report.html +62 -95
  67. package/dist/templates/execution-summary.html +37 -103
  68. package/dist/templates/global-report-styles.css +400 -9
  69. package/dist/templates/report-app.js +170 -72
  70. package/dist/templates/test-execution-report.html +84 -121
  71. package/dist/utils/A11yTimeUtils.d.ts +13 -0
  72. package/dist/utils/A11yTimeUtils.js +40 -0
  73. package/dist/utils/TimeUtils.d.ts +13 -0
  74. package/dist/utils/TimeUtils.js +39 -0
  75. package/package.json +2 -2
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.A11yHtmlRenderer = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ /**
40
+ * Handles the rendering of HTML reports using static templates and JSON data injection.
41
+ */
42
+ class A11yHtmlRenderer {
43
+ /**
44
+ * Renders a static HTML template by copying it and generating the accompanied data payload.
45
+ * @param templateName The template file name in the templates folder.
46
+ * @param data The data object to pass to the client-side JS app.
47
+ * @param outputFolder The folder where the rendered file will be saved.
48
+ * @param outputFileName The full path of the output file.
49
+ */
50
+ async render(templateName, data, outputFolder, outputFileName) {
51
+ // Resolve path relative to this file (dist/core/A11yHtmlRenderer.js)
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)) {
57
+ throw new Error(`[A11yHtmlRenderer] Template not found: ${templatePath}`);
58
+ }
59
+ if (!fs.existsSync(outputFolder)) {
60
+ fs.mkdirSync(outputFolder, { recursive: true });
61
+ }
62
+ // Derive a unique data filename from the HTML filename to avoid collisions
63
+ // when multiple reports (e.g. accessibility + execution) share the same folder.
64
+ const htmlBaseName = path.basename(outputFileName, '.html');
65
+ const dataJsName = `data-${htmlBaseName}.js`;
66
+ // 1. Copy the HTML template and patch the data.js reference to the unique name
67
+ let templateHtml = fs.readFileSync(templatePath, 'utf8');
68
+ templateHtml = templateHtml.replace(/(<script\s+src=")data\.js(")/, `$1${dataJsName}$2`);
69
+ fs.writeFileSync(outputFileName, templateHtml, 'utf8');
70
+ // 2. Wrap the report data in a JS variable and write the per-report data file
71
+ const outputDir = path.dirname(outputFileName);
72
+ const dataJsPath = path.join(outputDir, dataJsName);
73
+ const jsContent = `window.snapAllyData = ${JSON.stringify(data)};`;
74
+ fs.writeFileSync(dataJsPath, jsContent, 'utf8');
75
+ // 3. Copy the global CSS and JS rendering engine next to the HTML file
76
+ const outCssPath = path.join(outputDir, 'global-report-styles.css');
77
+ const outJsPath = path.join(outputDir, 'report-app.js');
78
+ try {
79
+ if (fs.existsSync(cssPath))
80
+ fs.copyFileSync(cssPath, outCssPath);
81
+ }
82
+ catch (e) {
83
+ console.error('Error copying CSS:', e);
84
+ }
85
+ try {
86
+ if (fs.existsSync(jsPath))
87
+ fs.copyFileSync(jsPath, outJsPath);
88
+ }
89
+ catch (e) {
90
+ console.error('Error copying JS:', e);
91
+ }
92
+ }
93
+ /**
94
+ * Converts ANSI color codes to HTML spans for nicer error display.
95
+ */
96
+ ansiToHtml(text) {
97
+ const map = {
98
+ '\u001b[30m': '<span style="color:black">',
99
+ '\u001b[31m': '<span style="color:red">',
100
+ '\u001b[32m': '<span style="color:green">',
101
+ '\u001b[33m': '<span style="color:yellow">',
102
+ '\u001b[34m': '<span style="color:blue">',
103
+ '\u001b[35m': '<span style="color:magenta">',
104
+ '\u001b[36m': '<span style="color:cyan">',
105
+ '\u001b[37m': '<span style="color:white">',
106
+ '\u001b[0m': '</span>',
107
+ '\u001b[2m': '<span style="opacity:0.5">',
108
+ '\u001b[22m': '</span>',
109
+ '\u001b[39m': '</span>',
110
+ };
111
+ let result = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
112
+ for (const [code, tag] of Object.entries(map)) {
113
+ result = result.split(code).join(tag);
114
+ }
115
+ return result;
116
+ }
117
+ }
118
+ exports.A11yHtmlRenderer = A11yHtmlRenderer;
@@ -0,0 +1,30 @@
1
+ import { TestResult } from '@playwright/test/reporter';
2
+ /**
3
+ * Utilities for managing and copying report assets like videos and screenshots.
4
+ */
5
+ export declare class A11yReportAssets {
6
+ /**
7
+ * Copies a file from source to a destination folder.
8
+ */
9
+ copyToFolder(destFolder: string, srcPath: string, fileName?: string): string;
10
+ /**
11
+ * Copies all video attachments to the report folder for portability.
12
+ * @returns An array of filenames written to the destination folder.
13
+ */
14
+ copyVideos(result: TestResult, destFolder: string): string[];
15
+ /**
16
+ * Copies all screenshots found in the test attachments.
17
+ */
18
+ copyScreenshots(result: TestResult, destFolder: string): string[];
19
+ /**
20
+ * Copies all other attachments (traces, logs, etc.) to the report folder.
21
+ */
22
+ copyAllOtherAttachments(result: TestResult, destFolder: string): {
23
+ path: string;
24
+ name: string;
25
+ }[];
26
+ /**
27
+ * Persists an in-memory buffer to a file in the destination folder.
28
+ */
29
+ saveBuffer(destFolder: string, fileName: string, buffer: Buffer): string;
30
+ }
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.A11yReportAssets = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ /**
40
+ * Utilities for managing and copying report assets like videos and screenshots.
41
+ */
42
+ class A11yReportAssets {
43
+ /**
44
+ * Copies a file from source to a destination folder.
45
+ */
46
+ copyToFolder(destFolder, srcPath, fileName) {
47
+ if (!srcPath || !fs.existsSync(srcPath)) {
48
+ return '';
49
+ }
50
+ const name = fileName || path.basename(srcPath);
51
+ const destFile = path.join(destFolder, name);
52
+ if (!fs.existsSync(destFolder)) {
53
+ fs.mkdirSync(destFolder, { recursive: true });
54
+ }
55
+ fs.copyFileSync(srcPath, destFile);
56
+ return name;
57
+ }
58
+ /**
59
+ * Copies all video attachments to the report folder for portability.
60
+ * @returns An array of filenames written to the destination folder.
61
+ */
62
+ copyVideos(result, destFolder) {
63
+ return result.attachments
64
+ .filter((a) => (a.name === 'video' || (a.contentType || '').startsWith('video/')) && a.path)
65
+ .map((attachment) => this.copyToFolder(destFolder, attachment.path))
66
+ .filter((p) => !!p);
67
+ }
68
+ /**
69
+ * Copies all screenshots found in the test attachments.
70
+ */
71
+ copyScreenshots(result, destFolder) {
72
+ return result.attachments
73
+ .filter((a) => a.name === 'screenshot' ||
74
+ a.name.endsWith('.png') ||
75
+ (a.contentType || '').startsWith('image/'))
76
+ .map((a) => {
77
+ if (a.path) {
78
+ return this.copyToFolder(destFolder, a.path, a.name !== 'screenshot' ? a.name : undefined);
79
+ }
80
+ else if (a.body) {
81
+ const timestamp = Date.now();
82
+ const name = a.name === 'screenshot'
83
+ ? `screenshot-${timestamp}.png`
84
+ : a.name.endsWith('.png')
85
+ ? a.name
86
+ : `${a.name}.png`;
87
+ return this.saveBuffer(destFolder, name, a.body);
88
+ }
89
+ return '';
90
+ })
91
+ .filter((path) => path !== '');
92
+ }
93
+ /**
94
+ * Copies all other attachments (traces, logs, etc.) to the report folder.
95
+ */
96
+ copyAllOtherAttachments(result, destFolder) {
97
+ const excludedNames = ['screenshot', 'video', 'A11y'];
98
+ return result.attachments
99
+ .filter((a) => !excludedNames.includes(a.name) &&
100
+ !a.name.toLowerCase().endsWith('.png') &&
101
+ !(a.contentType || '').startsWith('image/') &&
102
+ !(a.contentType || '').startsWith('video/'))
103
+ .map((a) => {
104
+ let name = '';
105
+ if (a.path) {
106
+ name = this.copyToFolder(destFolder, a.path, a.name);
107
+ }
108
+ else if (a.body) {
109
+ name = this.saveBuffer(destFolder, a.name, a.body);
110
+ }
111
+ return name ? { path: name, name: a.name } : null;
112
+ })
113
+ .filter((item) => item !== null);
114
+ }
115
+ /**
116
+ * Persists an in-memory buffer to a file in the destination folder.
117
+ */
118
+ saveBuffer(destFolder, fileName, buffer) {
119
+ if (!fs.existsSync(destFolder)) {
120
+ fs.mkdirSync(destFolder, { recursive: true });
121
+ }
122
+ const destFile = path.join(destFolder, fileName);
123
+ fs.writeFileSync(destFile, buffer);
124
+ return fileName;
125
+ }
126
+ }
127
+ exports.A11yReportAssets = A11yReportAssets;
@@ -0,0 +1,8 @@
1
+ import type { Page, TestInfo } from '@playwright/test';
2
+ import { A11yScannerOptions } from '../models';
3
+ /**
4
+ * Performs an accessibility audit using Axe and Lighthouse.
5
+ */
6
+ export declare function scanA11y(page: Page, testInfo: TestInfo, options?: A11yScannerOptions): Promise<void>;
7
+ /** Alias for backward compatibility */
8
+ export declare const checkAccessibility: typeof scanA11y;
@@ -0,0 +1,178 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.checkAccessibility = void 0;
7
+ exports.scanA11y = scanA11y;
8
+ const playwright_1 = __importDefault(require("@axe-core/playwright"));
9
+ const A11yVisualReporter_1 = require("./A11yVisualReporter");
10
+ const models_1 = require("../models");
11
+ const A11yTimeUtils_1 = require("../utils/A11yTimeUtils");
12
+ /**
13
+ * Sanitizes a string to be safe for use in file paths and prevents path traversal attacks.
14
+ * Removes or replaces dangerous characters and path separators.
15
+ */
16
+ function sanitizePageKey(input) {
17
+ return (input
18
+ // Remove protocol
19
+ .replace(/^https?:\/\//, '')
20
+ // Remove or replace path separators and dangerous characters
21
+ .replace(/[\/\\:*?"<>|]/g, '-')
22
+ // Remove any remaining path traversal attempts
23
+ .replace(/\.\./g, '')
24
+ // Replace multiple consecutive dashes with a single dash
25
+ .replace(/-+/g, '-')
26
+ // Remove leading/trailing dashes
27
+ .replace(/^-+|-+$/g, '')
28
+ // Convert to lowercase for consistency
29
+ .toLowerCase()
30
+ // Limit length to prevent filesystem issues
31
+ .substring(0, 200));
32
+ }
33
+ /**
34
+ * Performs an accessibility audit using Axe and Lighthouse.
35
+ */
36
+ async function scanA11y(page, testInfo, options = {}) {
37
+ var _a, _b;
38
+ const showTerminal = (_a = options.verbose) !== null && _a !== void 0 ? _a : true;
39
+ const showBrowser = (_b = options.consoleLog) !== null && _b !== void 0 ? _b : true;
40
+ // Sanitize pageKey to prevent path traversal attacks
41
+ const rawPageKey = options.pageKey || page.url();
42
+ const pageKey = sanitizePageKey(rawPageKey);
43
+ const overlay = new A11yVisualReporter_1.A11yVisualReporter(page);
44
+ // Configure Axe
45
+ let axeBuilder = new playwright_1.default({ page });
46
+ const target = options.include || options.box;
47
+ if (target) {
48
+ if (typeof target === 'string') {
49
+ axeBuilder = axeBuilder.include(target);
50
+ }
51
+ else {
52
+ // AxeBuilder for playwright also supports locators/elements in include
53
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
54
+ axeBuilder = axeBuilder.include(target);
55
+ }
56
+ }
57
+ if (options.rules) {
58
+ axeBuilder = axeBuilder.options({ rules: options.rules });
59
+ }
60
+ if (options.tags) {
61
+ axeBuilder = axeBuilder.withTags(options.tags);
62
+ }
63
+ if (options.axeOptions) {
64
+ axeBuilder = axeBuilder.options(options.axeOptions);
65
+ }
66
+ let axeResults;
67
+ try {
68
+ axeResults = await axeBuilder.analyze();
69
+ }
70
+ catch (error) {
71
+ if (error instanceof Error &&
72
+ (error.message.includes('Test ended') ||
73
+ error.message.includes('Target page, context or browser has been closed'))) {
74
+ console.warn(`[SnapAlly] Accessibility scan skipped: ${error.message}`);
75
+ return;
76
+ }
77
+ throw error;
78
+ }
79
+ const violationCount = axeResults.violations.length;
80
+ if ((showTerminal || showBrowser) && violationCount > 0) {
81
+ const mainMsg = `[A11yScanner] Violations found: ${violationCount}`;
82
+ // Prepare all detail messages
83
+ const detailMessages = axeResults.violations.map((v, i) => ` ${i + 1}. ${v.id} [${v.impact}] - ${v.help}`);
84
+ // Log to terminal
85
+ if (showTerminal) {
86
+ console.log(`\n${mainMsg}`);
87
+ detailMessages.forEach((msg) => console.log(msg));
88
+ }
89
+ // Batch log to Browser Console in a single evaluate call
90
+ if (showBrowser) {
91
+ await page.evaluate(([mainMsg, details, color]) => {
92
+ console.log(`%c ${mainMsg}`, `color: ${color}; font-weight: bold; font-size: 12px;`);
93
+ details.forEach((msg) => console.log(msg));
94
+ }, [mainMsg, detailMessages, models_1.DEFAULT_COLORS.serious]);
95
+ }
96
+ }
97
+ // Fail the test if violations found (softly)
98
+ // Dynamically require to avoid eager loading @playwright/test during config evaluation
99
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
100
+ const { expect } = require('@playwright/test');
101
+ expect
102
+ .soft(violationCount, `Accessibility audit failed with ${violationCount} violations.`)
103
+ .toBe(0);
104
+ // Run Axe Audit
105
+ const errors = [];
106
+ // Process violations for the report
107
+ for (const violation of axeResults.violations) {
108
+ let errorIdx = 0;
109
+ const targets = [];
110
+ const severityColor = (0, models_1.getSeverityColor)(violation.impact);
111
+ for (const node of violation.nodes) {
112
+ for (const selector of node.target) {
113
+ const elementSelector = selector.toString();
114
+ const locator = page.locator(elementSelector);
115
+ await overlay.showBanner({ id: violation.id, help: violation.help }, severityColor);
116
+ if (await locator.isVisible()) {
117
+ await overlay.highlightElement(elementSelector, severityColor);
118
+ // Allow a small time for overlay highlight to be visible in video
119
+ // eslint-disable-next-line playwright/no-wait-for-timeout
120
+ await page.waitForTimeout(100);
121
+ const screenshotName = `a11y-${violation.id}-${errorIdx++}.png`;
122
+ const buffer = await overlay.captureScreenshot(screenshotName, testInfo);
123
+ // Capture execution steps for context
124
+ const excluded = new Set([
125
+ 'Pre Condition',
126
+ 'Post Condition',
127
+ 'Description',
128
+ 'A11y',
129
+ ]);
130
+ const contextSteps = (testInfo.annotations || [])
131
+ .filter((a) => !excluded.has(a.type))
132
+ .map((a) => a.description || '');
133
+ const nodeHtml = node.html || '';
134
+ const friendlySnippet = elementSelector; // Use full CSS selector path from Axe core
135
+ targets.push({
136
+ element: elementSelector,
137
+ snippet: friendlySnippet,
138
+ html: nodeHtml,
139
+ screenshot: screenshotName,
140
+ steps: contextSteps,
141
+ stepsJson: JSON.stringify(contextSteps),
142
+ screenshotBase64: buffer.toString('base64'),
143
+ });
144
+ await overlay.removeHighlight();
145
+ }
146
+ }
147
+ }
148
+ errors.push({
149
+ id: violation.id,
150
+ description: violation.description,
151
+ severity: violation.impact || 'unknown',
152
+ helpUrl: violation.helpUrl,
153
+ help: violation.help,
154
+ guideline: violation.tags[1] || 'N/A',
155
+ wcagRule: violation.tags.find((t) => t.startsWith('wcag')) || violation.tags[1] || 'N/A',
156
+ total: targets.length || violation.nodes.length, // Fallback to node count if no screenshots
157
+ target: targets,
158
+ });
159
+ }
160
+ // Prepare data for the reporter
161
+ const reportData = {
162
+ pageKey,
163
+ pageUrl: page.url(),
164
+ accessibilityScore: 0, // No longer used, derivation from Lighthouse removed
165
+ a11yErrors: errors,
166
+ criticalColor: models_1.DEFAULT_COLORS.critical,
167
+ seriousColor: models_1.DEFAULT_COLORS.serious,
168
+ moderateColor: models_1.DEFAULT_COLORS.moderate,
169
+ minorColor: models_1.DEFAULT_COLORS.minor,
170
+ adoOrganization: process.env.ADO_ORGANIZATION || '',
171
+ adoProject: process.env.ADO_PROJECT || '',
172
+ timestamp: A11yTimeUtils_1.A11yTimeUtils.formatDate(new Date()),
173
+ };
174
+ await overlay.attachData(testInfo, 'A11y', JSON.stringify(reportData));
175
+ await overlay.clean();
176
+ }
177
+ /** Alias for backward compatibility */
178
+ exports.checkAccessibility = scanA11y;
@@ -0,0 +1,50 @@
1
+ import type { Page, TestInfo } from '@playwright/test';
2
+ /**
3
+ * Manages visual feedback on the page during an accessibility scan.
4
+ * Handles element highlights, violation banners, and report attachments.
5
+ */
6
+ export declare class A11yVisualReporter {
7
+ private readonly page;
8
+ private readonly rootId;
9
+ private static readonly BANNER_ID;
10
+ private static readonly HIGHLIGHT_ID;
11
+ constructor(page: Page);
12
+ /**
13
+ * Shows a violation banner at the bottom of the page.
14
+ */
15
+ showBanner(violation: {
16
+ id: string;
17
+ help: string;
18
+ }, color: string): Promise<void>;
19
+ /**
20
+ * Highlights an element on the page.
21
+ */
22
+ highlightElement(selector: string, color: string): Promise<void>;
23
+ /**
24
+ * Removes all visual feedback from the page.
25
+ */
26
+ clean(): Promise<void>;
27
+ /**
28
+ * Removes the element highlight.
29
+ */
30
+ removeHighlight(): Promise<void>;
31
+ /**
32
+ * Attaches JSON data to the test report.
33
+ */
34
+ attachData(testInfo: TestInfo, name: string, data: string): Promise<void>;
35
+ /**
36
+ * Captures and attaches a screenshot to the test report.
37
+ */
38
+ captureScreenshot(name: string, testInfo: TestInfo): Promise<Buffer>;
39
+ /**
40
+ * Injects helper functions into the page context.
41
+ */
42
+ private ensureHelpers;
43
+ private safeEvaluate;
44
+ }
45
+ declare global {
46
+ interface Window {
47
+ snapAllyGetRoot: (id: string) => ShadowRoot;
48
+ snapAllyToAlpha: (color: string, alpha: number) => string;
49
+ }
50
+ }