How to Capture a Full-Page Screenshot of a Page That Changes After Scrolling
Full-page capture does not always trigger scroll-dependent content. Use Playwright to scroll, wait for a page-specific signal, and verify the saved image.
For a page that changes as you scroll, first make the page scroll so its scripts can load or reveal the content, then capture the full document. In Playwright, fullPage: true controls how much of the page goes into the image; it does not guarantee that actual scroll events have fired. Wait for a page-specific signal that the content you need is present, take the screenshot, and inspect it for missing, stale, or repeated sections.
1. Understand full-page capture and scrolling
A full-page screenshot is a capture of the full scrollable page, as though it fit on one tall screen. That is different from actually moving the page through its scroll positions. Some sites load images lazily, reveal sections, or append items only after real scrolling. A Playwright issue report describes cases where off-screen content can be rendered without moving the visual viewport, so scroll-dependent behavior may not run. Treat this as a possible page-specific problem and check the output.
- Full-page capture sets the extent of the screenshot.
- Scrolling can trigger content creation, loading, or reveal behavior.
- A readiness condition tells your script when the relevant content has appeared.
- Image inspection catches blank lazy images, missing sections, duplicates, and changes during capture.
Official references: Playwright screenshots guide, Playwright Page API, and Playwright issue #40941.
2. Capture a changing page with Playwright
Install Playwright and its Chromium browser in a project, for example with npm install playwright followed by npx playwright install chromium. Save the following as capture.mjs and run node capture.mjs. Replace the URL, footer locator, and completion condition with ones that match the target site.
import { chromium } from 'playwright';
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1365, height: 900 } });
try {
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
// Bring a meaningful bottom element into view to trigger scroll behavior.
await page.getByText('Footer text', { exact: false }).scrollIntoViewIfNeeded();
// Replace this with a signal that proves the content you need is present.
await page.locator('[data-content-ready="true"]').waitFor({ state: 'attached' });
await page.screenshot({ path: 'page.png', fullPage: true });
} finally {
await browser.close();
}
This is an example shape, not a universal recipe: the locator and readiness selector must exist on the page. If there is no footer, use the scrolling pattern below. Playwright’s documented primitives include scrolling a bottom element into view, using the mouse wheel, and changing the scrollTop of a chosen scrolling element. See its scrolling documentation.
Infinite feeds and pages without a footer
For an infinite list, continue scrolling while checking whether new items appeared or an explicit end marker became visible. Do not rely on a fixed number of pixels or a universal delay. Here is a template for a page whose items have a stable selector and whose end marker is known:
const items = page.locator('.feed-item');
const endMarker = page.locator('[data-feed-end="true"]');
let previousCount = 0;
for (let attempt = 0; attempt < 30; attempt++) {
if (await endMarker.isVisible().catch(() => false)) break;
const currentCount = await items.count();
if (currentCount > previousCount) previousCount = currentCount;
await page.mouse.wheel(0, 700);
// Wait for a site-specific change, such as a new item or end marker.
await page.waitForFunction(
({ selector, prior }) => document.querySelectorAll(selector).length > prior ||
Boolean(document.querySelector('[data-feed-end="true"]')),
{ selector: '.feed-item', prior: previousCount },
{ timeout: 5000 }
).catch(() => {});
}
await page.screenshot({ path: 'feed.png', fullPage: true });
Adapt the selectors and stopping rule. The example caps iterations to avoid an unbounded loop if a feed never signals completion; reaching that cap is not proof the feed is complete. Check the end marker or expected item count before treating the capture as complete.
Nested scrolling panels
If the changing content is inside a panel rather than the document, scroll that panel. For a known scrollable element:
const panel = page.locator('.results-panel');
await panel.evaluate(element => { element.scrollTop = element.scrollHeight; });
await page.locator('.results-loaded').waitFor({ state: 'visible' });
await page.screenshot({ path: 'panel-page.png', fullPage: true });
A full-page screenshot concerns the document’s scrollable page. It may not expand an inner panel to show all its contents. If the goal is only the panel, capture that element with locator.screenshot(); if the goal is the whole panel contents, first make the panel load them and decide whether to capture the panel or the page.
Lazy images and reveal animations
Scroll through the parts of the page that contain lazy images or scroll-triggered reveals, then wait for the specific images or sections you need. You can check images before capture:
await page.evaluate(async () => {
const images = [...document.images];
await Promise.all(images.map(image => {
if (image.complete) return Promise.resolve();
return new Promise(resolve => {
image.addEventListener('load', resolve, { once: true });
image.addEventListener('error', resolve, { once: true });
});
}));
});
This waits for images currently in the document to finish loading or fail; it does not itself trigger lazy loading, guarantee successful image downloads, or wait for content that has not yet been inserted. Scroll first, then use page-specific checks where possible.
3. Use Firefox for a one-off manual screenshot
Firefox Developer Tools can capture the entire page. To enable the control:
- Open Developer Tools and its settings.
- Under “Available Toolbox Buttons,” enable “Take a screenshot of the entire page.”
- If the site loads content only when scrolled, scroll through the relevant sections first.
- Use the full-page screenshot control and inspect the saved image in the browser Downloads directory.
See Mozilla’s Taking screenshots documentation. The built-in capture is convenient for a one-off, but its documentation does not promise that it triggers every site’s scroll-dependent loading.
4. Choose a method that matches the page
| Method | Best fit | Check before relying on it |
|---|---|---|
| Firefox Developer Tools | One-off manual capture | Scroll first if needed; inspect the saved image. |
| Playwright full-page screenshot | Repeatable automation or documentation | Add real scrolling and a site-specific readiness check when content depends on scrolling. |
| Viewport screenshots stitched together | Fallback when content must load through stepwise viewport movement | Changing layouts can create seams or duplicate content; verify the assembled image. |
Choose based on whether the task is manual or repeatable, whether the content belongs to the document or a nested container, whether real viewport scrolling is required, and how you can verify the stopping condition.
5. Make captures more reliable
- Wait for evidence, not an arbitrary delay. Look for the expected item, section, image state, or end marker. A fixed sleep can be too short on a slow page and waste time on a fast one.
- Record the completion condition. For repeatable jobs, make the expected selector or item count part of the capture configuration.
- Inspect important captures. Check the top, middle, and bottom for blank images, missing sections, repeated areas, or content that changed while scrolling.
- Stabilize only when appropriate. Playwright screenshot options support a stylesheet to hide or modify dynamic elements for repeatable images. This changes the rendered appearance and cannot load content that never appeared. See the Page API.
- Bound loops and retries. Infinite feeds may never reach an end marker; set an operational limit and report incomplete captures rather than silently calling them complete.
6. Troubleshooting
| Symptom | Likely cause | What to do |
|---|---|---|
| Bottom sections are absent | The script captured the full document without triggering scroll behavior, or the readiness check matched too early. | Scroll to the relevant area and wait for a page-specific signal before capturing. |
| Lazy images are blank | The image never entered the site’s loading threshold, is still loading, or failed. | Scroll the image into view, wait for its load state, and inspect its source or error state. |
| Infinite feed ends too soon | The page has not appended new items yet, or the loop’s stopping condition is incorrect. | Wait for the item count to change or an explicit end marker; validate selectors and report when a safety cap is reached. |
| Scrolling does nothing | The page may use a nested scroll container, or the selector may target the wrong element. | Identify the actual scrolling element and scroll it. Playwright documents wheel input and direct scrollTop changes. |
| Screenshot has repeated or shifted content | The page layout changed during capture, or a stitched viewport fallback encountered moving content. | Wait for relevant updates to settle, stabilize the page if that suits the task, and inspect the final image. |
| Wait times out | The readiness selector is absent, hidden, or never reached because loading failed. | Check the selector against the live page, distinguish visible from attached state, and handle a genuine load failure explicitly. |
7. Performance, reliability, and cost
Scrolling and waiting add work because the browser must run page scripts and allow content to load. Use the smallest page-specific completion condition that proves the needed content is ready; avoid waiting for every network request if the site keeps long-lived requests open. A full-page image can also be large for a very long document, so choose output dimensions and format according to the use case and inspect the file before storing or transferring it.
Reliability comes from explicit readiness checks, bounded scrolling, handling load failures, and reviewing the result. There is no universal scroll distance or delay that guarantees completeness across sites. Cost depends on where the automation runs and how often it runs; this browser workflow has no screenshot API price, though your browser infrastructure and storage may have their own costs.
8. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. Its one-call API captures a URL as an image or PDF, and its documented options include full-page capture, waits, custom JavaScript, CSS selectors, caching, and async jobs. See the ScreenshotNeo API docs. For example, this cURL request captures a full-page WebP; adapt the target URL to your page:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -d full_page=true -o shot.webp
Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents take screenshots. The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, no card required.
FAQ
Does fullPage: true scroll the page?
It requests a screenshot of the full scrollable page. It does not, by itself, guarantee that site scripts received actual scroll events.
How do I know when an infinite page is complete?
Use a site-provided end marker or another explicit signal. If the page offers no completion signal, define and report a deliberate stopping rule, such as an expected number of items.
Can I capture only one changing section?
Yes. Trigger and wait for that section’s content, then use a locator screenshot when the desired output is that element rather than the full document.


