diffs: apply PDF preflight size and page limits

This commit is contained in:
Gustavo Madeira Santana
2026-03-02 03:24:26 -05:00
parent a5f231e3a9
commit ad4157ca00
2 changed files with 57 additions and 5 deletions

View File

@@ -144,6 +144,45 @@ describe("PlaywrightDiffScreenshotter", () => {
await expect(fs.readFile(pdfPath, "utf8")).resolves.toContain("%PDF-1.7");
});
it("fails fast when PDF render exceeds size limits", async () => {
const pages: Array<{
close: ReturnType<typeof vi.fn>;
screenshot: ReturnType<typeof vi.fn>;
pdf: ReturnType<typeof vi.fn>;
}> = [];
const browser = createMockBrowser(pages, {
boundingBox: { x: 40, y: 40, width: 960, height: 60_000 },
});
launchMock.mockResolvedValue(browser);
const { PlaywrightDiffScreenshotter } = await import("./browser.js");
const screenshotter = new PlaywrightDiffScreenshotter({
config: createConfig(),
browserIdleMs: 1_000,
});
const pdfPath = path.join(rootDir, "oversized.pdf");
await expect(
screenshotter.screenshotHtml({
html: '<html><head></head><body><main class="oc-frame"></main></body></html>',
outputPath: pdfPath,
theme: "light",
image: {
format: "pdf",
qualityPreset: "standard",
scale: 2,
maxWidth: 960,
maxPixels: 8_000_000,
},
}),
).rejects.toThrow("Diff frame did not render within image size limits.");
expect(launchMock).toHaveBeenCalledTimes(1);
expect(pages).toHaveLength(1);
expect(pages[0]?.pdf).toHaveBeenCalledTimes(0);
expect(pages[0]?.screenshot).toHaveBeenCalledTimes(0);
});
it("fails fast when maxPixels is still exceeded at scale 1", async () => {
const pages: Array<{
close: ReturnType<typeof vi.fn>;
@@ -192,10 +231,11 @@ function createMockBrowser(
screenshot: ReturnType<typeof vi.fn>;
pdf: ReturnType<typeof vi.fn>;
}>,
options?: { boundingBox?: { x: number; y: number; width: number; height: number } },
) {
const browser = {
newPage: vi.fn(async () => {
const page = createMockPage();
const page = createMockPage(options);
pages.push(page);
return page;
}),
@@ -205,7 +245,10 @@ function createMockBrowser(
return browser;
}
function createMockPage() {
function createMockPage(options?: {
boundingBox?: { x: number; y: number; width: number; height: number };
}) {
const box = options?.boundingBox ?? { x: 40, y: 40, width: 640, height: 240 };
const screenshot = vi.fn(async ({ path: screenshotPath }: { path: string }) => {
await fs.writeFile(screenshotPath, Buffer.from("png"));
});
@@ -221,7 +264,7 @@ function createMockPage() {
emulateMedia: vi.fn(async () => {}),
locator: vi.fn(() => ({
waitFor: vi.fn(async () => {}),
boundingBox: vi.fn(async () => ({ x: 40, y: 40, width: 640, height: 240 })),
boundingBox: vi.fn(async () => box),
})),
setViewportSize: vi.fn(async () => {}),
screenshot,

View File

@@ -9,6 +9,8 @@ import { VIEWER_ASSET_PREFIX, getServedViewerAsset } from "./viewer-assets.js";
const DEFAULT_BROWSER_IDLE_MS = 30_000;
const SHARED_BROWSER_KEY = "__default__";
const IMAGE_SIZE_LIMIT_ERROR = "Diff frame did not render within image size limits.";
const PDF_REFERENCE_PAGE_HEIGHT_PX = 1_056;
const MAX_PDF_PAGES = 50;
export type DiffScreenshotter = {
screenshotHtml(params: {
@@ -177,11 +179,18 @@ export class PlaywrightDiffScreenshotter implements DiffScreenshotter {
if (!pdfBox) {
throw new Error("Diff frame was lost before PDF render.");
}
const pdfWidth = Math.max(Math.ceil(pdfBox.width), 1);
const pdfHeight = Math.max(Math.ceil(pdfBox.height), 1);
const estimatedPixels = pdfWidth * pdfHeight;
const estimatedPages = Math.ceil(pdfHeight / PDF_REFERENCE_PAGE_HEIGHT_PX);
if (estimatedPixels > params.image.maxPixels || estimatedPages > MAX_PDF_PAGES) {
throw new Error(IMAGE_SIZE_LIMIT_ERROR);
}
await page.pdf({
path: params.outputPath,
width: `${Math.max(Math.ceil(pdfBox.width), 1)}px`,
height: `${Math.max(Math.ceil(pdfBox.height), 1)}px`,
width: `${pdfWidth}px`,
height: `${pdfHeight}px`,
printBackground: true,
margin: {
top: "0",