How to Screenshot a Page That Lazy-Loads Content Only When Visible
Scroll through a page before capturing it to trigger visible-only content. Learn the Playwright workflow, handle virtualized lists, and troubleshoot missing sections.
Direct answer: scroll through the page before taking a full-page screenshot. A full-page capture extends the image to the page’s scrollable height, but it does not necessarily trigger content that loads only when it becomes visible. In Playwright, scroll the page to activate that content, wait for the content you need, then capture with fullPage: true.
This works for many lazy-loaded images and sections on ordinary pages. It is not a universal loader: virtualized feeds, infinite scroll, and content inside nested scroll panels may need a different capture strategy.
Why a full-page screenshot can miss content
Playwright describes a full-page screenshot as if the page were a very tall screen. That describes the screenshot’s extent; it does not mean the browser scrolls through every part of the page first. Some sites load images, iframes, or sections when scrolling brings them into view, often through visibility observers or scroll handlers. If those triggers never fire, the content may remain unloaded when the screenshot is taken.
In short: capture height and page interaction are different things. A screenshot can complete successfully and still omit content that was never activated.
A reliable Playwright workflow
- Navigate and wait for a useful initial state. Choose a navigation readiness event appropriate to the page.
domcontentloadedis a practical starting point; it does not guarantee that lazy content has loaded. - Scroll through the document. Move the viewport down in increments so sections and images enter view. Allow the page’s own requests and rendering to proceed.
- Wait for evidence of the content you need. If you know the selector for the last expected section or item, wait for it to become visible. A fixed delay can be a fallback, but it cannot prove readiness across sites.
- Capture the full page. Use Playwright’s full-page screenshot option after the scroll pass and content checks.
- Inspect the output. Check below-the-fold areas for missing images or sections. Successful capture only proves that a file was produced.
JavaScript: scroll, wait for a known section, then capture
const { chromium } = require('playwright');
(async () => {
const url = 'https://example.com/article';
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
try {
await page.goto(url, { waitUntil: 'domcontentloaded' });
// Replace this selector with a known final section or item on the page.
const expectedLastSection = page.locator('#last-section');
const step = 700;
let previousHeight = 0;
for (let pass = 0; pass < 30; pass++) {
const height = await page.evaluate(() => document.documentElement.scrollHeight);
const y = await page.evaluate(() => window.scrollY);
if (height === previousHeight && y + 900 >= height) break;
previousHeight = height;
await page.evaluate((nextY) => window.scrollTo(0, nextY), y + step);
await page.waitForTimeout(300);
if (await expectedLastSection.isVisible().catch(() => false)) break;
}
// If the page has a known end marker, wait for it explicitly.
await expectedLastSection.waitFor({ state: 'visible', timeout: 10000 }).catch(() => {});
await page.screenshot({ path: 'page.png', fullPage: true });
} finally {
await browser.close();
}
})();
Install Playwright with npm install playwright; install its browser binaries with npx playwright install chromium. Save the script as a JavaScript file and run it with Node.js. Replace the example URL and selector. If there is no stable final-section selector, remove that condition and define a bounded stopping rule, such as a known number of feed items or maximum scroll depth.
The loop is a starting pattern, not a site-independent guarantee. Some pages append content after a delay, need smaller scroll increments, or keep extending indefinitely. The pass cap prevents an unbounded loop. Tune the viewport height and scroll step for the page, and prefer a real content condition over increasing arbitrary waits.
Python: scroll and capture with Playwright
from playwright.sync_api import sync_playwright
url = "https://example.com/article"
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page(viewport={"width": 1440, "height": 900})
page.goto(url, wait_until="domcontentloaded")
last_section = page.locator("#last-section")
previous_height = 0
for _ in range(30):
height = page.evaluate("document.documentElement.scrollHeight")
y = page.evaluate("window.scrollY")
if height == previous_height and y + 900 >= height:
break
previous_height = height
page.evaluate("next_y => window.scrollTo(0, next_y)", y + 700)
page.wait_for_timeout(300)
if last_section.is_visible():
break
try:
last_section.wait_for(state="visible", timeout=10000)
except Exception:
pass
page.screenshot(path="page.png", full_page=True)
browser.close()
Install with pip install playwright, then run playwright install chromium. Replace the URL and end-marker selector. The example bounds the scroll pass and treats the selector wait as best effort; for production automation, handle a missing expected element explicitly rather than silently accepting an incomplete page.
Navigation readiness and waiting
Use a navigation event to establish when to begin interacting, then use a page-specific condition for the content you need. networkidle is not a universal signal that a page is ready for a screenshot: lazy-loading requests may not start until scrolling, and background network activity can continue after the relevant content is visible. An arbitrary delay is also unreliable. When possible, assert that the target image, section, or last expected item is visible or has loaded.
Choose the capture strategy for the page
| Page behavior | Recommended approach | What to verify |
|---|---|---|
| Finite document with visibility-triggered images or sections | Scroll through the document, wait for target content, then take one full-page screenshot. | Expected lower-page content appears in the image. |
| Virtualized list or feed | Capture successive viewport segments while advancing through the list, or use an application export when record completeness matters. | Earlier items remain represented even if the page removes them from the DOM as they leave view. |
| Nested scroll panel | Scroll the element that owns the content, then capture the panel or its segments as needed. | Whether the document or an inner element’s scroll position changes. |
| Infinite feed | Set a stopping definition first: a target item, a bounded result count, or a maximum depth. Capture segments if the full logical feed is required. | The chosen endpoint was reached; an endless feed has no natural final page height. |
Virtualized lists
A virtualizer may render only rows near the current viewport and remove or recycle earlier rows as you scroll. In that case, scrolling first does not guarantee that one tall screenshot will contain every item: at capture time, earlier rows may no longer exist in the rendered page. Capture viewport segments as you advance, or use the site’s export/API if you need complete data rather than a visual record.
Nested scroll containers
Check which element actually scrolls. If the page stays still while an inner panel moves, scrolling window will not trigger content inside that panel. Locate the panel, scroll it directly, and decide whether the output should be a screenshot of that element or multiple viewport-sized segments. A historical Playwright issue reported incomplete capture involving a non-body scrolling element; it concerned Playwright 1.20.0, so treat it as a reason to inspect your current target rather than evidence that every current version has the same behavior.
Animations and changing page state
Scrolling can start animations, dismiss sticky elements, or otherwise change the visible state. If the screenshot must represent a specific state, define that state before scrolling and inspect whether the scroll pass altered it. A scroll-triggered capture is an interaction with the page, not a purely passive extension of the viewport.
Or skip the browser setup
ScreenshotNeo is a website screenshot API: send one GET request with a URL to receive an image or PDF. The call below requests a full-page WebP screenshot. See the ScreenshotNeo API documentation for the available parameters and response details.
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://example.com/article \
-d full_page=true \
-o page.webp
- Cookie banners are accepted and removed before capture; ScreenshotNeo also removes known consent platforms, newsletter popups, and chat widgets. Each cleanup step can be turned off.
- Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed. Response headers report the page verdict and billing status.
- An MCP server lets AI agents use
take_screenshot,get_page_info, andcapture_pdf. - The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots.
Sign up for 1,000 free screenshots a month, with no card.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Screenshot succeeds but lower images are blank or absent | The full-page capture did not trigger visibility-based loading, or capture began before the image loaded. | Scroll through the page first. Wait for the specific image or section to become visible and, when relevant, verify the image’s loaded state before capture. |
| The page height stops growing, but expected content is missing | Content may require a smaller scroll step, more time, a specific interaction, or a known trigger. Some pages do not append content at all. | Inspect the page’s scroll behavior and use a content-specific condition. Do not assume a stable height proves completeness. |
| Only the latest feed items appear | The list may be virtualized and may discard off-screen rows. | Capture sequential segments or use a data export/API when you need all records. |
| Scrolling the window does not reveal panel content | The content is inside a nested scroll container. | Identify and scroll the inner element; capture that panel or take segments. |
| The script never stops | An infinite feed or continually growing page keeps extending its height. | Set a maximum pass count, scroll depth, or known item as the endpoint. |
| The target selector wait times out | The selector is incorrect, the item never loads, or the scroll pass did not reach it. | Confirm the selector in the rendered page, scroll farther or in smaller steps, and decide how the script should report missing required content. |
| The result changes between runs | Network timing, asynchronous content, or scroll-triggered animations vary. | Wait for explicit page evidence, keep viewport and interaction settings stable, and record the intended visual state. |
Performance, reliability, and cost
Scrolling adds browser work and time because the page must process each viewport and may fetch additional resources. Smaller steps can trigger more intermediate content but take longer; larger steps reduce interactions but can skip site-specific triggers. Choose a step that fits the page’s behavior, and stop when a defined content condition is met. No single delay or scroll increment is reliable for every site.
For repeatable automation, bound the number of passes, set navigation and element timeouts, and fail clearly when required content never appears. A page that changes indefinitely needs a capture boundary. Retain the screenshot for inspection when completeness matters. Full-page capture can also produce large image files on long pages; segmenting may make the output easier to handle, especially for virtualized content.
With a self-hosted Playwright workflow, cost depends on the compute and browser time you use; this dossier provides no benchmark or universal cost estimate. ScreenshotNeo publishes plan quotas: 1,000 per month free, then Starter $5 for 3,000, Growth $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000, and Business $249 for 1,000,000. Yearly billing gives two months free, and every feature is on every plan. Its billed-shot rules exclude bot checks, blank pages, timeouts, failed loads, and cache hits.
FAQ
Does fullPage: true scroll the page for me?
It captures the full scrollable page extent. It does not guarantee that scroll-triggered content was activated first.
Should I always wait for networkidle?
No. Use an explicit condition for the content you need when possible. Network-idle state alone is not universal proof that lazy content is ready.
Can one screenshot include every item in an infinite feed?
Only if the page exposes a finite rendered result that remains present. Define an endpoint and use segments or an export when the feed is unbounded or virtualized.
How do I know the screenshot is complete?
Check expected content in the resulting image and use page-specific assertions before capture. A successful screenshot call does not validate completeness.
Primary references
- Playwright screenshot documentation for full-page screenshots.
- Playwright Page API for navigation and readiness options.
- Playwright issue on loading lazy content before full-page screenshots; this is a feature request, not documentation of a shipped automatic lazy-content option.
- Historical report on full-page capture and a non-body scrolling element, concerning Playwright 1.20.0.


