The document grows while you walk it
A feed can append another viewport on every intersection. Without a maximum height or step count, the supposedly finite capture never becomes ready.
`page.screenshot({fullPage: true})` is correct for a finite document whose content already exists. Real pages often create content only after it enters the viewport, keep fixed elements attached while the viewport moves, or grow while you measure them.
The output has blank image slots below the first fold, a sticky header repeated through the image, or a blank/truncated tail on a very tall document.
Walk the viewport before capture, bound the walk, return to the top and only then ask Puppeteer for the document image. This is a useful baseline, not a universal algorithm.
import puppeteer from "puppeteer";
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 900 });
await page.goto("https://example.com", { waitUntil: "load" });
for (let step = 0; step < 80; step++) {
const done = await page.evaluate(() => {
const before = scrollY;
scrollBy(0, innerHeight);
return scrollY === before || scrollY + innerHeight >= document.documentElement.scrollHeight;
});
await new Promise((resolve) => setTimeout(resolve, 400));
if (done) break;
}
await page.evaluate(() => scrollTo(0, 0));
await page.screenshot({ path: "full.png", fullPage: true });
await browser.close();A feed can append another viewport on every intersection. Without a maximum height or step count, the supposedly finite capture never becomes ready.
Sticky headers, chat launchers and animations can paint differently at each scroll position. A scroll-through changes page state; taking one final native screenshot does not undo every mutation.
Very tall captures can return blank or truncated pixels even when navigation succeeded. Slice-and-stitch is a separate algorithm with overlap and memory tradeoffs.
Analytics, polling and websockets make `networkidle0` wait forever, while lazy images may start after a network-idle window already passed.
One PNG of the bounded document. Above the native threshold the worker captures bands and stitches them; `full_page_slices=true` can also expose the individual bands.
One successful, non-cached capture costs one credit. Failures, including platform failures, cost zero; cache hits cost zero.
Viewport, scale or final dimensions exceed the pixel budget.
HTTP 400 · terminal
The page or its chosen readiness state consumes the total capture budget.
HTTP 500 · retryable
The runner above carries this exact configuration into the playground — no retyping, no starting over.
Written by PageCapture Engineering. Reviewed by PageCapture render maintainers. Verified 2026-08-12 by running the published code against a fixture.
The API recipe is normalized in unit tests; generated cURL, TypeScript and Python pass syntax checks; full-page fallback and scroll bounds are asserted against the render implementation.
Sources support the browser and API behaviors named above. PageCapture-specific limits and billing are taken from the public contract; external sources are used for the underlying browser behavior.
Defines the native Puppeteer screenshot call and `fullPage` behavior.
Documents the public scroll, height and slice contract used by the recipe.