init project
This commit is contained in:
Vendored
+89
@@ -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 (0–1). 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
|
||||
Reference in New Issue
Block a user