init project

This commit is contained in:
root
2026-07-31 13:12:54 -04:00
parent 0da92d5e02
commit f3863f760c
7215 changed files with 1860260 additions and 1 deletions
+89
View File
@@ -0,0 +1,89 @@
import { PNG } from 'pngjs';
import pixelmatch from 'pixelmatch';
import { z } from 'zod';
import { config } from '../../config.js';
import { readBaseline, resolveBaselinePath } from '../../lib/baselines.js';
import { errorResult, jsonResult, toErrorMessage } from '../shared.js';
import { browserManager } from './browserManager.js';
function toDataUri(png) {
return `data:image/png;base64,${png.toString('base64')}`;
}
export function comparePngBuffers(baselinePng, currentPng, threshold = 0.1) {
const baseline = PNG.sync.read(baselinePng);
const current = PNG.sync.read(currentPng);
if (baseline.width !== current.width || baseline.height !== current.height) {
throw new Error(`Screenshot dimensions differ from baseline (${baseline.width}x${baseline.height} vs ${current.width}x${current.height}). ` +
'Recapture the baseline at the same viewport/selector size.');
}
const { width, height } = baseline;
const diff = new PNG({ width, height });
const mismatchedPixels = pixelmatch(baseline.data, current.data, diff.data, width, height, {
threshold,
});
const total = width * height;
const mismatchPercent = total === 0 ? 0 : (mismatchedPixels / total) * 100;
return {
width,
height,
mismatchedPixels,
mismatchPercent,
diffPng: PNG.sync.write(diff),
};
}
export function registerBrowserVisualDiffTool(server) {
server.registerTool('browser_visual_diff', {
title: 'Visual Screenshot Diff',
description: 'Capture the current page/element and compare it to a previously saved baseline with pixelmatch. Returns mismatch stats and a diff PNG data URI.',
inputSchema: {
name: z.string().min(1).describe('Baseline name previously saved with browser_screenshot_baseline.'),
selector: z.string().optional().describe('Optional CSS selector to screenshot for comparison.'),
fullPage: z
.boolean()
.optional()
.default(false)
.describe('Capture the full scrollable page when selector is omitted.'),
threshold: z
.number()
.min(0)
.max(1)
.optional()
.default(0.1)
.describe('pixelmatch threshold (01). Lower is stricter.'),
},
}, async ({ name, selector, fullPage, threshold }) => {
try {
const baselinePng = readBaseline(name);
const session = await browserManager.getSession();
let currentPng;
if (selector) {
const element = await session.page.locator(selector).first();
await element.waitFor({ state: 'visible', timeout: 5000 });
currentPng = await element.screenshot({ type: 'png' });
}
else {
currentPng = await session.page.screenshot({ fullPage, type: 'png' });
}
const comparison = comparePngBuffers(baselinePng, currentPng, threshold ?? 0.1);
const diffDataUri = toDataUri(comparison.diffPng);
return jsonResult({
name,
baselinePath: resolveBaselinePath(name),
selector: selector ?? null,
width: comparison.width,
height: comparison.height,
mismatchedPixels: comparison.mismatchedPixels,
mismatchPercent: Number(comparison.mismatchPercent.toFixed(4)),
passed: comparison.mismatchedPixels === 0,
threshold: threshold ?? 0.1,
diffDataUri,
truncatedDiffDataUri: diffDataUri.length > config.maxToolOutputChars
? `${diffDataUri.slice(0, config.maxToolOutputChars)}...`
: diffDataUri,
});
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
}
//# sourceMappingURL=visualDiff.js.map