difflens 0.0.1

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.
@@ -0,0 +1,61 @@
1
+ import { Result } from 'axe-core';
2
+
3
+ interface DiffLensConfig {
4
+ baseUrl?: string;
5
+ viewports?: {
6
+ [key: string]: {
7
+ width: number;
8
+ height: number;
9
+ };
10
+ };
11
+ scenarios: Scenario[];
12
+ threshold?: number;
13
+ outDir?: string;
14
+ format?: 'default' | 'json' | 'ai';
15
+ failOnActionError?: boolean;
16
+ failOnNavigationError?: boolean;
17
+ }
18
+ interface Viewport {
19
+ width: number;
20
+ height: number;
21
+ label?: string;
22
+ }
23
+ interface Action {
24
+ type: 'click' | 'type' | 'wait';
25
+ selector?: string;
26
+ value?: string;
27
+ timeout?: number;
28
+ }
29
+ interface Scenario {
30
+ label: string;
31
+ path: string;
32
+ viewports?: Viewport[];
33
+ actions?: Action[];
34
+ maskSelectors?: string[];
35
+ hideSelectors?: string[];
36
+ }
37
+ interface TestResult {
38
+ scenario: string;
39
+ timestamp: string;
40
+ status: 'pass' | 'fail' | 'new';
41
+ diffPixels: number;
42
+ diffPercentage: number;
43
+ screenshotPath: string;
44
+ diffPath?: string;
45
+ baselinePath?: string;
46
+ a11yViolations: Result[];
47
+ dimensionMismatch?: boolean;
48
+ }
49
+ interface ReportData {
50
+ timestamp: string;
51
+ results: TestResult[];
52
+ }
53
+
54
+ declare const DEFAULT_CONFIG: DiffLensConfig;
55
+ declare function loadConfig(configPath?: string): Promise<DiffLensConfig>;
56
+
57
+ declare function runTests(config: DiffLensConfig): Promise<boolean>;
58
+
59
+ declare function formatAiReport(results: TestResult[], outDir: string): string;
60
+
61
+ export { type Action, DEFAULT_CONFIG, type DiffLensConfig, type ReportData, type Scenario, type TestResult, type Viewport, formatAiReport, loadConfig, runTests };
package/dist/index.js ADDED
@@ -0,0 +1,537 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
31
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
+
33
+ // src/core/reporters/ai-reporter.ts
34
+ var ai_reporter_exports = {};
35
+ __export(ai_reporter_exports, {
36
+ formatAiReport: () => formatAiReport
37
+ });
38
+ function formatAiReport(results, outDir) {
39
+ let output = "DiffLens Test Report\n====================\n\n";
40
+ const failures = results.filter((r) => r.status === "fail" || r.a11yViolations.length > 0);
41
+ if (failures.length === 0) {
42
+ return output + "All tests passed. No visual regressions or accessibility violations found.\n";
43
+ }
44
+ output += `Found ${failures.length} issues:
45
+
46
+ `;
47
+ failures.forEach((result, index) => {
48
+ output += `Issue #${index + 1}: ${result.scenario}
49
+ `;
50
+ output += `----------------------------------------
51
+ `;
52
+ if (result.status === "fail") {
53
+ if (result.dimensionMismatch) {
54
+ output += `[Dimension Mismatch]
55
+ `;
56
+ output += ` Baseline and current images have different dimensions.
57
+ `;
58
+ } else if (result.diffPixels > 0) {
59
+ output += `[Visual Regression]
60
+ `;
61
+ output += ` Diff Pixels: ${result.diffPixels} (${result.diffPercentage.toFixed(2)}%)
62
+ `;
63
+ if (result.diffPath) {
64
+ output += ` Diff Image: ${import_path3.default.relative(process.cwd(), result.diffPath)}
65
+ `;
66
+ }
67
+ }
68
+ }
69
+ if (result.a11yViolations.length > 0) {
70
+ output += `[Accessibility Violations]
71
+ `;
72
+ result.a11yViolations.forEach((v) => {
73
+ output += ` - [${v.impact}] ${v.help} (${v.id})
74
+ `;
75
+ output += ` Target: ${v.nodes.map((n) => n.target.join(", ")).join(" | ")}
76
+ `;
77
+ });
78
+ }
79
+ output += "\n";
80
+ });
81
+ return output;
82
+ }
83
+ var import_path3;
84
+ var init_ai_reporter = __esm({
85
+ "src/core/reporters/ai-reporter.ts"() {
86
+ "use strict";
87
+ import_path3 = __toESM(require("path"));
88
+ }
89
+ });
90
+
91
+ // src/index.ts
92
+ var index_exports = {};
93
+ __export(index_exports, {
94
+ DEFAULT_CONFIG: () => DEFAULT_CONFIG,
95
+ formatAiReport: () => formatAiReport,
96
+ loadConfig: () => loadConfig,
97
+ runTests: () => runTests
98
+ });
99
+ module.exports = __toCommonJS(index_exports);
100
+
101
+ // src/config.ts
102
+ var import_path = __toESM(require("path"));
103
+ var import_fs = __toESM(require("fs"));
104
+ var import_esbuild = require("esbuild");
105
+ var DEFAULT_CONFIG = {
106
+ scenarios: [],
107
+ threshold: 0.1,
108
+ outDir: ".difflens"
109
+ };
110
+ async function loadConfig(configPath) {
111
+ const searchPlaces = [
112
+ "difflens.config.ts",
113
+ "difflens.config.js",
114
+ "difflens.config.mjs",
115
+ "difflens.config.cjs"
116
+ ];
117
+ let resolvedPath;
118
+ if (configPath) {
119
+ resolvedPath = import_path.default.resolve(process.cwd(), configPath);
120
+ } else {
121
+ for (const place of searchPlaces) {
122
+ const p = import_path.default.resolve(process.cwd(), place);
123
+ if (import_fs.default.existsSync(p)) {
124
+ resolvedPath = p;
125
+ break;
126
+ }
127
+ }
128
+ }
129
+ if (!resolvedPath) {
130
+ console.warn("No configuration file found. Using default config.");
131
+ return DEFAULT_CONFIG;
132
+ }
133
+ try {
134
+ let importPath = resolvedPath;
135
+ let isTs = resolvedPath.endsWith(".ts");
136
+ let tempFile = null;
137
+ if (isTs) {
138
+ const outfile = resolvedPath.replace(/\.ts$/, ".js.tmp.mjs");
139
+ await (0, import_esbuild.build)({
140
+ entryPoints: [resolvedPath],
141
+ outfile,
142
+ bundle: true,
143
+ platform: "node",
144
+ format: "esm",
145
+ external: ["difflens"]
146
+ // Exclude self if referenced?
147
+ });
148
+ importPath = outfile;
149
+ tempFile = outfile;
150
+ }
151
+ const importUrl = `file://${importPath}`;
152
+ const userConfigModule = await import(importUrl);
153
+ const userConfig = userConfigModule.default || userConfigModule;
154
+ if (tempFile && import_fs.default.existsSync(tempFile)) {
155
+ import_fs.default.unlinkSync(tempFile);
156
+ }
157
+ return {
158
+ ...DEFAULT_CONFIG,
159
+ ...userConfig
160
+ };
161
+ } catch (error) {
162
+ console.error(`Failed to load configuration from ${resolvedPath}:`, error);
163
+ throw error;
164
+ }
165
+ }
166
+
167
+ // src/core/runner.ts
168
+ var import_playwright2 = require("playwright");
169
+ var import_path4 = __toESM(require("path"));
170
+ var import_fs4 = __toESM(require("fs"));
171
+
172
+ // src/core/capture.ts
173
+ async function captureScreenshot(page, url, outputPath, options = {}) {
174
+ if (url) {
175
+ await page.goto(url, { waitUntil: "networkidle" });
176
+ }
177
+ await page.evaluate(() => document.fonts.ready);
178
+ await page.addStyleTag({
179
+ content: `
180
+ *, *::before, *::after {
181
+ animation-duration: 0s !important;
182
+ transition-duration: 0s !important;
183
+ animation-iteration-count: 1 !important;
184
+ }
185
+ `
186
+ });
187
+ if (options.hideSelectors) {
188
+ for (const selector of options.hideSelectors) {
189
+ await page.locator(selector).evaluate((el) => el.style.visibility = "hidden");
190
+ }
191
+ }
192
+ if (options.maskSelectors) {
193
+ for (const selector of options.maskSelectors) {
194
+ await page.locator(selector).evaluate((el) => el.style.backgroundColor = "#FF00FF");
195
+ }
196
+ }
197
+ await page.screenshot({
198
+ path: outputPath,
199
+ fullPage: options.fullPage ?? true,
200
+ animations: "disabled"
201
+ });
202
+ }
203
+
204
+ // src/core/compare.ts
205
+ var import_fs2 = __toESM(require("fs"));
206
+ var import_pngjs = require("pngjs");
207
+ var import_pixelmatch = __toESM(require("pixelmatch"));
208
+ function compareImages(img1Path, img2Path, diffPath, threshold = 0.1) {
209
+ const img1 = import_pngjs.PNG.sync.read(import_fs2.default.readFileSync(img1Path));
210
+ const img2 = import_pngjs.PNG.sync.read(import_fs2.default.readFileSync(img2Path));
211
+ const { width, height } = img1;
212
+ const diff = new import_pngjs.PNG({ width, height });
213
+ if (img1.width !== img2.width || img1.height !== img2.height) {
214
+ return {
215
+ diffPixels: -1,
216
+ diffPercentage: -1,
217
+ isSameDimensions: false
218
+ };
219
+ }
220
+ const diffPixels = (0, import_pixelmatch.default)(
221
+ img1.data,
222
+ img2.data,
223
+ diff.data,
224
+ width,
225
+ height,
226
+ { threshold }
227
+ );
228
+ import_fs2.default.writeFileSync(diffPath, import_pngjs.PNG.sync.write(diff));
229
+ const totalPixels = width * height;
230
+ const diffPercentage = diffPixels / totalPixels * 100;
231
+ return {
232
+ diffPixels,
233
+ diffPercentage,
234
+ isSameDimensions: true
235
+ };
236
+ }
237
+
238
+ // src/core/a11y.ts
239
+ var import_playwright = __toESM(require("@axe-core/playwright"));
240
+ async function runAxeAudit(page) {
241
+ const results = await new import_playwright.default({ page }).analyze();
242
+ if (results.violations.length > 0) {
243
+ console.error(`Found ${results.violations.length} accessibility violations:`);
244
+ results.violations.forEach((violation) => {
245
+ console.error(`- [${violation.impact}] ${violation.help} (${violation.id})`);
246
+ violation.nodes.forEach((node) => {
247
+ console.error(` Target: ${node.target}`);
248
+ });
249
+ });
250
+ }
251
+ return {
252
+ violations: results.violations
253
+ };
254
+ }
255
+
256
+ // src/core/report.ts
257
+ var import_fs3 = __toESM(require("fs"));
258
+ var import_path2 = __toESM(require("path"));
259
+ function generateHtmlReport(data, outDir) {
260
+ const html = `
261
+ <!DOCTYPE html>
262
+ <html lang="en">
263
+ <head>
264
+ <meta charset="UTF-8">
265
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
266
+ <title>DiffLens Report</title>
267
+ <style>
268
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
269
+ .container { max-width: 1200px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
270
+ h1 { color: #333; border-bottom: 2px solid #eee; padding-bottom: 10px; }
271
+ .summary { margin-bottom: 20px; padding: 15px; background: #eef; border-radius: 4px; }
272
+ .scenario { border: 1px solid #ddd; margin-bottom: 20px; border-radius: 4px; overflow: hidden; }
273
+ .scenario-header { padding: 10px 15px; background: #f9f9f9; border-bottom: 1px solid #ddd; display: flex; justify-content: space-between; align-items: center; }
274
+ .scenario-title { font-weight: bold; font-size: 1.1em; }
275
+ .status { padding: 4px 8px; border-radius: 4px; font-size: 0.9em; font-weight: bold; color: white; }
276
+ .status.pass { background: #28a745; }
277
+ .status.fail { background: #dc3545; }
278
+ .status.new { background: #17a2b8; }
279
+ .scenario-body { padding: 15px; }
280
+ .images { display: flex; gap: 20px; margin-bottom: 15px; overflow-x: auto; }
281
+ .image-container { flex: 0 0 auto; }
282
+ .image-container img { max-width: 300px; border: 1px solid #ddd; border-radius: 4px; }
283
+ .image-label { font-size: 0.9em; color: #666; margin-bottom: 5px; }
284
+ .a11y { margin-top: 15px; border-top: 1px solid #eee; padding-top: 10px; }
285
+ .a11y-title { font-weight: bold; color: #666; margin-bottom: 5px; }
286
+ .violation { margin-bottom: 5px; color: #d63384; }
287
+ </style>
288
+ </head>
289
+ <body>
290
+ <div class="container">
291
+ <h1>DiffLens Report</h1>
292
+ <div class="summary">
293
+ <p><strong>Generated at:</strong> ${new Date(data.timestamp).toLocaleString()}</p>
294
+ <p><strong>Total Scenarios:</strong> ${data.results.length}</p>
295
+ <p><strong>Failed:</strong> ${data.results.filter((r) => r.status === "fail").length}</p>
296
+ </div>
297
+
298
+ ${data.results.map((result) => `
299
+ <div class="scenario">
300
+ <div class="scenario-header">
301
+ <span class="scenario-title">${result.scenario}</span>
302
+ <span class="status ${result.status}">${result.status.toUpperCase()}</span>
303
+ </div>
304
+ <div class="scenario-body">
305
+ <div class="images">
306
+ ${result.baselinePath ? `
307
+ <div class="image-container">
308
+ <div class="image-label">Baseline</div>
309
+ <img src="${import_path2.default.relative(import_path2.default.resolve(outDir), import_path2.default.resolve(result.baselinePath))}" alt="Baseline">
310
+ </div>
311
+ ` : ""}
312
+ <div class="image-container">
313
+ <div class="image-label">Current</div>
314
+ <img src="${import_path2.default.relative(import_path2.default.resolve(outDir), import_path2.default.resolve(result.screenshotPath))}" alt="Current">
315
+ </div>
316
+ ${result.diffPath ? `
317
+ <div class="image-container">
318
+ <div class="image-label">Diff</div>
319
+ <img src="${import_path2.default.relative(import_path2.default.resolve(outDir), import_path2.default.resolve(result.diffPath))}" alt="Diff">
320
+ </div>
321
+ ` : ""}
322
+ </div>
323
+
324
+ ${result.diffPixels === -1 ? `
325
+ <p style="color: #dc3545; font-weight: bold;">[FAIL] Dimension mismatch! Baseline and current images have different sizes.</p>
326
+ ` : `
327
+ <p><strong>Diff:</strong> ${result.diffPixels} pixels (${result.diffPercentage.toFixed(2)}%)</p>
328
+ `}
329
+
330
+ ${result.a11yViolations.length > 0 ? `
331
+ <div class="a11y">
332
+ <div class="a11y-title">Accessibility Violations (${result.a11yViolations.length})</div>
333
+ ${result.a11yViolations.map((v) => `
334
+ <div class="violation">
335
+ [${v.impact}] ${v.help} (${v.id})
336
+ </div>
337
+ `).join("")}
338
+ </div>
339
+ ` : '<div class="a11y"><div class="a11y-title">No Accessibility Violations</div></div>'}
340
+ </div>
341
+ </div>
342
+ `).join("")}
343
+ </div>
344
+ </body>
345
+ </html>
346
+ `;
347
+ import_fs3.default.writeFileSync(import_path2.default.join(outDir, "index.html"), html);
348
+ import_fs3.default.writeFileSync(import_path2.default.join(outDir, "report.json"), JSON.stringify(data, null, 2));
349
+ console.log(`Report generated: ${import_path2.default.join(outDir, "index.html")}`);
350
+ }
351
+
352
+ // src/core/runner.ts
353
+ async function runTests(config) {
354
+ const browser = await import_playwright2.chromium.launch();
355
+ const context = await browser.newContext();
356
+ const page = await context.newPage();
357
+ const outDir = config.outDir || ".difflens";
358
+ const dirs = {
359
+ baseline: import_path4.default.join(outDir, "baseline"),
360
+ current: import_path4.default.join(outDir, "current"),
361
+ diff: import_path4.default.join(outDir, "diff")
362
+ };
363
+ Object.values(dirs).forEach((dir) => {
364
+ if (!import_fs4.default.existsSync(dir)) {
365
+ import_fs4.default.mkdirSync(dir, { recursive: true });
366
+ }
367
+ });
368
+ const results = [];
369
+ if (!config.scenarios || config.scenarios.length === 0) {
370
+ console.error("Error: No scenarios defined in configuration.");
371
+ return false;
372
+ }
373
+ for (const scenario of config.scenarios) {
374
+ const viewports = scenario.viewports || config.viewports && Object.entries(config.viewports).map(([label, { width, height }]) => ({ label, width, height })) || [{ width: 1280, height: 720, label: "default" }];
375
+ for (const viewport of viewports) {
376
+ const viewportLabel = viewport.label || `${viewport.width}x${viewport.height}`;
377
+ const label = viewports.length > 1 || viewport.label ? `${scenario.label}-${viewportLabel}` : scenario.label;
378
+ console.error(`Running scenario: ${label}`);
379
+ const url = config.baseUrl ? new URL(scenario.path, config.baseUrl).toString() : scenario.path;
380
+ await page.setViewportSize({ width: viewport.width, height: viewport.height });
381
+ let retries = 2;
382
+ let navigationSuccess = false;
383
+ while (retries >= 0) {
384
+ try {
385
+ await page.goto(url, { waitUntil: "networkidle" });
386
+ navigationSuccess = true;
387
+ break;
388
+ } catch (e) {
389
+ if (retries === 0) {
390
+ console.error(` [WARN] Navigation failed: ${e}`);
391
+ } else {
392
+ console.error(` [WARN] Navigation failed, retrying... (${retries} attempts left)`);
393
+ retries--;
394
+ await page.waitForTimeout(1e3);
395
+ }
396
+ }
397
+ }
398
+ if (!navigationSuccess) {
399
+ if (config.failOnNavigationError) {
400
+ console.error(` [FAIL] Navigation failed.`);
401
+ results.push({
402
+ scenario: label,
403
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
404
+ status: "fail",
405
+ diffPixels: 0,
406
+ diffPercentage: 0,
407
+ screenshotPath: "",
408
+ a11yViolations: []
409
+ });
410
+ continue;
411
+ }
412
+ }
413
+ let actionFailed = false;
414
+ if (scenario.actions) {
415
+ for (const action of scenario.actions) {
416
+ console.error(` Action: ${action.type} ${action.selector || ""}`);
417
+ try {
418
+ if (action.type === "click" && action.selector) {
419
+ await page.click(action.selector);
420
+ } else if (action.type === "type" && action.selector && action.value) {
421
+ await page.fill(action.selector, action.value);
422
+ } else if (action.type === "wait" && action.timeout) {
423
+ await page.waitForTimeout(action.timeout);
424
+ }
425
+ } catch (e) {
426
+ console.error(` [WARN] Action failed: ${e}`);
427
+ actionFailed = true;
428
+ if (config.failOnActionError) {
429
+ break;
430
+ }
431
+ }
432
+ }
433
+ await page.waitForTimeout(500);
434
+ }
435
+ const currentPath = import_path4.default.join(dirs.current, `${label}.png`);
436
+ retries = 2;
437
+ while (retries >= 0) {
438
+ try {
439
+ await captureScreenshot(page, null, currentPath, {
440
+ fullPage: true,
441
+ maskSelectors: scenario.maskSelectors,
442
+ hideSelectors: scenario.hideSelectors
443
+ });
444
+ break;
445
+ } catch (e) {
446
+ if (retries === 0) throw e;
447
+ console.error(` [WARN] Screenshot failed, retrying... (${retries} attempts left)`);
448
+ retries--;
449
+ await page.waitForTimeout(1e3);
450
+ }
451
+ }
452
+ console.error(` Screenshot captured: ${currentPath}`);
453
+ const a11yResult = await runAxeAudit(page);
454
+ if (a11yResult.violations.length > 0) {
455
+ console.error(` Accessibility violations: ${a11yResult.violations.length}`);
456
+ }
457
+ const baselinePath = import_path4.default.join(dirs.baseline, `${label}.png`);
458
+ const diffPath = import_path4.default.join(dirs.diff, `${label}.png`);
459
+ let status = "new";
460
+ let diffPixels = 0;
461
+ let diffPercentage = 0;
462
+ let finalDiffPath;
463
+ let finalBaselinePath;
464
+ let dimensionMismatch = false;
465
+ if (import_fs4.default.existsSync(baselinePath)) {
466
+ finalBaselinePath = baselinePath;
467
+ console.error(" Comparing with baseline...");
468
+ const result = compareImages(baselinePath, currentPath, diffPath, config.threshold);
469
+ diffPixels = result.diffPixels;
470
+ diffPercentage = result.diffPercentage;
471
+ dimensionMismatch = !result.isSameDimensions;
472
+ if (!result.isSameDimensions) {
473
+ status = "fail";
474
+ console.error(` [FAIL] Dimension mismatch! Baseline and current images have different sizes.`);
475
+ } else if (result.diffPixels > 0) {
476
+ status = "fail";
477
+ finalDiffPath = diffPath;
478
+ console.error(` [FAIL] Visual regression detected! Diff pixels: ${result.diffPixels} (${result.diffPercentage.toFixed(2)}%)`);
479
+ console.error(` Diff image saved to: ${diffPath}`);
480
+ } else {
481
+ status = "pass";
482
+ console.error(" [PASS] No visual regression detected.");
483
+ }
484
+ } else {
485
+ console.error(" Baseline not found. Saving current as baseline.");
486
+ import_fs4.default.copyFileSync(currentPath, baselinePath);
487
+ console.error(` Baseline saved to: ${baselinePath}`);
488
+ finalBaselinePath = baselinePath;
489
+ }
490
+ if (a11yResult.violations.length > 0) {
491
+ status = "fail";
492
+ console.error(` [FAIL] Accessibility violations detected: ${a11yResult.violations.length}`);
493
+ }
494
+ if (actionFailed && config.failOnActionError) {
495
+ status = "fail";
496
+ console.error(` [FAIL] Action execution failed.`);
497
+ }
498
+ results.push({
499
+ scenario: label,
500
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
501
+ status,
502
+ diffPixels,
503
+ diffPercentage,
504
+ screenshotPath: currentPath,
505
+ diffPath: finalDiffPath,
506
+ baselinePath: finalBaselinePath,
507
+ a11yViolations: a11yResult.violations,
508
+ dimensionMismatch
509
+ });
510
+ }
511
+ }
512
+ await browser.close();
513
+ const reportData = {
514
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
515
+ results
516
+ };
517
+ if (config.format === "json") {
518
+ console.log(JSON.stringify(results, null, 2));
519
+ } else if (config.format === "ai") {
520
+ const { formatAiReport: formatAiReport2 } = await Promise.resolve().then(() => (init_ai_reporter(), ai_reporter_exports));
521
+ console.log(formatAiReport2(results, outDir));
522
+ } else {
523
+ generateHtmlReport(reportData, outDir);
524
+ }
525
+ const hasFailures = results.some((r) => r.status === "fail");
526
+ return !hasFailures;
527
+ }
528
+
529
+ // src/index.ts
530
+ init_ai_reporter();
531
+ // Annotate the CommonJS export names for ESM import in node:
532
+ 0 && (module.exports = {
533
+ DEFAULT_CONFIG,
534
+ formatAiReport,
535
+ loadConfig,
536
+ runTests
537
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,14 @@
1
+ import {
2
+ DEFAULT_CONFIG,
3
+ loadConfig,
4
+ runTests
5
+ } from "./chunk-LVDSHTE4.mjs";
6
+ import {
7
+ formatAiReport
8
+ } from "./chunk-4Q2LAHUO.mjs";
9
+ export {
10
+ DEFAULT_CONFIG,
11
+ formatAiReport,
12
+ loadConfig,
13
+ runTests
14
+ };
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -0,0 +1,2 @@
1
+
2
+ export { }