sitesmith capture: show the whole page as side-by-side strips (max 9000 px)
The capture stopped at 3000 CSS px, so the reviewer never saw the bottom or footer. Now the full page height H is rendered at its normal width (1280). H <= 3000: one image, as before. Otherwise N = min(3, ceil(H/3000)) equal-height strips (each <= 3000 px) are laid out side by side, left to right (strip 1 = page top), with 24 px #808080 gutters, in one JPEG; H > 9000 shows the top 9000 px (cropped). Geometry is the pure stripGeometry() (unit-tested); JPEG quality steps 0.8 -> 0.5 before "too large" (3 MB cap, server limit 8 MB). CaptureResult gains strips/pageHeight; runSelfReview returns them and says when only the top of a long page was reviewed. Real eval pages: 2 strips, ~0.8-1.1 s, 150-270 KB in Chromium and Firefox; e2e checks strip order/alignment/gutters/cropping. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
41d0721028
commit
04c0b0a08a
6 files changed
+186
-13
No files matched your search
@@ -5,7 +5,9 @@ import { sanitizeCaptureHtml } from '../src/utils/sanitize-capture-html';
|
||||
(window as any).sanitizeCaptureHtml = sanitizeCaptureHtml;
|
||||
/** Capture through the real path, then decode the JPEG and return RGB samples at the given points. */
|
||||
(window as any).captureAndSample = async (html: string, points: Array<[number, number]>) => {
|
||||
const t0 = performance.now();
|
||||
const r = await captureHtml(html, { settleMs: 200, injectFontAwesome: false });
|
||||
const ms = Math.round(performance.now() - t0);
|
||||
if (!r.ok) return { ok: false, reason: r.reason };
|
||||
const url = URL.createObjectURL(r.blob);
|
||||
const img = new Image();
|
||||
@@ -15,5 +17,5 @@ import { sanitizeCaptureHtml } from '../src/utils/sanitize-capture-html';
|
||||
const ctx = c.getContext('2d')!;
|
||||
ctx.drawImage(img, 0, 0);
|
||||
URL.revokeObjectURL(url);
|
||||
return { ok: true, w: c.width, h: c.height, samples: points.map(([x, y]) => Array.from(ctx.getImageData(x, y, 1, 1).data.slice(0, 3))) };
|
||||
return { ok: true, w: c.width, h: c.height, strips: r.strips, cropped: r.cropped, pageHeight: r.pageHeight, bytes: r.blob.size, ms, samples: points.map(([x, y]) => Array.from(ctx.getImageData(x, y, 1, 1).data.slice(0, 3))) };
|
||||
};
|
||||
@@ -80,6 +80,21 @@ const SCANS = [
|
||||
['placehold.co CSS background shows its label in the fg colour', 1290, 300, 980, [0xee, 0xee, 0xee]],
|
||||
];
|
||||
|
||||
// Tall pages -> side-by-side strips (the geometry the server-side review prompt describes): bands of 2500 px
|
||||
// (or 4000 px) each with a 100 px black bar on top, so each strip's colour AND its top edge can be checked.
|
||||
const band = (h, colors) => `<!doctype html><html><head><style>body{margin:0}</style></head><body>${colors.map((c) => `<div style="height:${h}px;background:${c}"><div style="height:100px;background:#000"></div></div>`).join('')}</body></html>`;
|
||||
const TALL = [
|
||||
{ label: '7500 px page', html: band(2500, ['#c03030', '#30c030', '#3030c0']), strips: 3, w: 3888, h: 2500, cropped: false,
|
||||
pts: [[640, 1250, [0xc0, 0x30, 0x30]], [1292, 1250, [0x80, 0x80, 0x80]], [1944, 1250, [0x30, 0xc0, 0x30]], [2596, 1250, [0x80, 0x80, 0x80]], [3248, 1250, [0x30, 0x30, 0xc0]],
|
||||
[640, 50, [0, 0, 0]], [1944, 50, [0, 0, 0]], [3248, 50, [0, 0, 0]], [1944, 2490, [0x30, 0xc0, 0x30]]] },
|
||||
{ label: '5000 px page', html: band(2500, ['#c03030', '#30c030']), strips: 2, w: 2584, h: 2500, cropped: false,
|
||||
pts: [[640, 1250, [0xc0, 0x30, 0x30]], [1292, 1250, [0x80, 0x80, 0x80]], [1944, 1250, [0x30, 0xc0, 0x30]], [1944, 50, [0, 0, 0]]] },
|
||||
// 12000 px: top 9000 only, 3 x 3000; strip 2 starts at page y 3000 (inside band 1: red), strip 3 at 6000 (green), bars at 0/4000/8000.
|
||||
{ label: '12000 px page (cropped)', html: band(4000, ['#c03030', '#30c030', '#3030c0']), strips: 3, w: 3888, h: 3000, cropped: true,
|
||||
pts: [[640, 50, [0, 0, 0]], [1944, 50, [0xc0, 0x30, 0x30]], [1944, 1050, [0, 0, 0]], [3248, 50, [0x30, 0xc0, 0x30]], [3248, 2050, [0, 0, 0]], [3248, 2990, [0x30, 0x30, 0xc0]]] },
|
||||
{ label: '1500 px page (no strips)', html: band(1500, ['#c03030']), strips: 1, w: 1280, h: 1500, cropped: false, pts: [[640, 750, [0xc0, 0x30, 0x30]]] },
|
||||
];
|
||||
|
||||
const srv = http.createServer((q, r) => { r.setHeader('content-type', 'text/html'); r.end('<!doctype html><title>host</title><body>host</body>'); }).listen(0);
|
||||
const port = srv.address().port;
|
||||
let failed = 0;
|
||||
@@ -120,6 +135,14 @@ async function run(name, launcher, exe) {
|
||||
check(name, label, hits >= 3, `(${hits} pixels near rgb(${want.join(',')}))`);
|
||||
}
|
||||
}
|
||||
for (const t of TALL) {
|
||||
const r = await page.evaluate(([h, pts]) => window.captureAndSample(h, pts), [t.html, t.pts.map((p) => [p[0], p[1]])]);
|
||||
check(name, `${t.label}: captured`, r.ok === true, r.ok ? `(${r.bytes} bytes, ${r.ms} ms)` : r.reason);
|
||||
if (!r.ok) continue;
|
||||
check(name, `${t.label}: ${t.strips} strip(s), ${t.w}x${t.h}, cropped=${t.cropped}`, r.strips === t.strips && r.w === t.w && r.h === t.h && r.cropped === t.cropped,
|
||||
`got ${r.strips} strip(s) ${r.w}x${r.h} cropped=${r.cropped} pageHeight=${r.pageHeight}`);
|
||||
t.pts.forEach(([x, y, want], i) => check(name, `${t.label}: pixel (${x},${y})`, near(r.samples[i], want), `got rgb(${r.samples[i].join(',')}) want rgb(${want.join(',')})`));
|
||||
}
|
||||
await browser.close();
|
||||
}
|
||||
await run('chromium', chromium, process.env.CHROMIUM_PATH);
|
||||
|
||||
Reference in new issue
Block a user