ScreenshotNeo

BlogHow-to

Website Screenshot APIs That Wait for Lazy-Loaded Content

Wait for the page element you need, and scroll when loading depends on scrolling. Here are the API options, a runnable Playwright example, and ways to avoid incomplete captures.

By the ScreenshotNeo team4 October 202610 min read

To capture content that appears after the initial page load, wait for a CSS selector that identifies that content. If the site loads it only after scrolling, scroll the page first, then wait for the selector. Use network-idle waiting when the page’s requests settling is a useful signal; add a short fixed delay only as a buffer for known, brief rendering delays.

A selector wait answers the most useful question directly: “Has the content I need appeared?” A page-load event or network-idle condition alone cannot guarantee that a particular component has rendered. Waiting options and their parameter names vary by provider, so check the selected API’s current documentation for timeout limits and failure behavior.

1. Choose a wait condition that matches the page

Page behavior Capture strategy Watch out for
Content is inserted after an API call or client-side render Wait for a stable selector on the finished content A parent container may exist before its contents are ready. Target a meaningful child or ready state.
Content is requested only after scrolling Scroll to trigger loading, then wait for the target selector Full-page capture and pre-capture scrolling are not interchangeable across providers.
Requests settle before the final UI renders Wait for network idle, then a selector if possible Network idle does not prove the needed element exists; persistent polling can prevent idle.
The page has a short, predictable animation or render delay Wait for the selector, then add a small delay if needed A fixed delay alone is slower on fast runs and still unreliable on slow ones.

Cloudflare’s screenshot API documents waitForSelector, including visibility and timeout settings, plus gotoOptions.waitUntil choices such as load, domcontentloaded, networkidle0, and networkidle2. Its Browser Run guidance describes selector waiting as a faster alternative when you do not need to wait for all network activity. ScreenshotAPI.to documents combining network idle with a selector or delay for dynamic apps. ScreenshotAPI.com documents scrolling to trigger lazy loading. These options are provider-specific; consult the [Cloudflare API reference](https://developers.cloudflare.com/api/resources/browser_rendering/subresources/screenshot/methods/create/), [Cloudflare screenshot guide](https://developers.cloudflare.com/browser-run/quick-actions/screenshot-endpoint/), [ScreenshotAPI.to documentation](https://screenshotapi.to/docs/api/screenshot), or [ScreenshotAPI.com reference](https://screenshotapi.readme.io/reference/take-a-screenshot-get) for the service you use.

2. Identify a reliable selector

  1. Open the target page and inspect the specific content to capture.
  2. Choose a stable selector, preferably an application-owned ID, data attribute, or semantic class that remains present across page updates.
  3. Confirm that the selector refers to the loaded content, not a placeholder, skeleton, or empty wrapper.
  4. Decide whether existence is enough or whether the element must also be visible. Some APIs let you request visibility explicitly; behavior differs by provider.
  5. Set a bounded timeout appropriate to the page’s normal load time. Handle a timeout as a failed capture rather than silently saving a misleading image.

For example, if a product grid is rendered asynchronously, wait for a product card such as [data-testid="product-card"] rather than a broad <main> element that is present immediately. If the site reuses a selector for both a skeleton and loaded content, wait for a loaded-state selector or for placeholder removal if the API supports waiting for hidden elements.

3. DIY with Playwright in Node.js

When you control the browser, Playwright lets you navigate, scroll to trigger lazy loading, wait for the target element, and save the screenshot. This runnable example uses the public example domain; replace the URL and selector with the page and loaded content you need.

npm install playwright
npx playwright install chromium
// capture.mjs
import { chromium } from 'playwright';

const url = process.env.TARGET_URL ?? 'https://example.com';
const selector = process.env.TARGET_SELECTOR ?? 'h1';
const browser = await chromium.launch({ headless: true });

try {
  const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } });
  await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30_000 });

  // Scroll in viewport-sized steps to trigger common scroll-based lazy loaders.
  await page.evaluate(async () => {
    const step = Math.max(window.innerHeight, 400);
    for (let y = 0; y < document.body.scrollHeight; y += step) {
      window.scrollTo(0, y);
      await new Promise(resolve => setTimeout(resolve, 150));
    }
    window.scrollTo(0, 0);
  });

  // Wait for the actual content signal, not just navigation completion.
  await page.locator(selector).first().waitFor({ state: 'visible', timeout: 20_000 });
  await page.screenshot({ path: 'capture.png', fullPage: true });
} finally {
  await browser.close();
}

Run it with TARGET_URL and TARGET_SELECTOR environment variables set for your page. The script’s scroll loop handles common lazy loaders, but it is not a universal substitute for application-specific behavior: some pages need a particular scroll container, a click, authentication, or a wait for a specific state. For a viewport screenshot, change fullPage to false. For a selected element screenshot, use page.locator(selector).screenshot({ path: 'element.png' }) after the wait.

4. Or skip the browser setup

ScreenshotNeo is a screenshot API and MCP server. Its API supports wait_for_selector, full-page scrolling for lazy content, and a configurable wait_until condition. This request waits for the product card and saves the image:

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://example.com/products \
  --data-urlencode wait_for_selector='[data-testid="product-card"]' \
  -d full_page=true \
  -o products.webp

Equivalent Python:

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={
        "access_key": "YOUR_API_KEY",
        "url": "https://example.com/products",
        "wait_for_selector": '[data-testid="product-card"]',
        "full_page": "true",
    },
    timeout=90,
)
r.raise_for_status()
open("products.webp", "wb").write(r.content)

Equivalent Node.js (built-in fetch):

const q = new URLSearchParams({
  access_key: process.env.SCREENSHOTNEO_API_KEY,
  url: 'https://example.com/products',
  wait_for_selector: '[data-testid="product-card"]',
  full_page: 'true',
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status} ${await res.text()}`);
const image = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('products.webp', image));

ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before the shot; each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots. Sign up for 1,000 free screenshots a month, no card required.

5. Configure selector waits, scrolling, and timeouts

Selector wait

Use a CSS selector for the smallest meaningful signal that indicates readiness. In ScreenshotNeo, wait_for_selector (also wait_for) waits up to 20 seconds for a matching element to exist. If it never appears, the request fails and is not billed. The separate selector option captures only the first matching element; it does not mean “wait for this element.” Keep those two jobs distinct: use wait_for_selector to wait, and selector to crop the capture to an element.

Scroll-triggered loading

ScreenshotNeo’s full_page=true captures the whole scrollable page, and full_page_scroll scrolls down first so lazy images and sections can load; it defaults to the value of full_page. Set full_page_max_height to limit very tall pages. For other APIs, verify whether scroll is automatic and whether the wait happens before or after scrolling. When using your own browser, scroll the relevant page or nested scrolling container explicitly, then wait for the content selector.

ScreenshotNeo supports wait_until=auto, load, domcontentloaded, networkidle0, and networkidle2. Its default auto waits for the load event and then up to three seconds for the network to settle. Network idle can help when the page’s requests precede rendering, but polling, analytics, or streaming can keep the network active. A selector is usually a more precise readiness condition for one known component. The delay option adds 0 to 20 seconds after waiting; use a small value only when there is a known lag after the relevant signal.

Capture scope and visual setup

  • full_page=true captures the whole page. Use full_page_max_height (500–16,000 pixels) to cap a long or endless page.
  • selector captures only the first matching element. Confirm that the element is visible and has the dimensions you expect.
  • Set viewport dimensions and device options when responsive layout affects the target content. ScreenshotNeo supports 12 device presets, custom viewports, mobile mode, landscape, and retina scale.
  • Use format for PNG, JPEG, WebP, or PDF output. ScreenshotNeo also supports custom CSS and JavaScript, clicks, hiding selectors, and request blocking when the page needs additional setup.
  • For authenticated content, configure custom headers, cookies, user agent, or Authorization as appropriate. Keep credentials on the server and out of public page code.

See the [ScreenshotNeo parameter reference](https://screenshotneo.com/docs/) for accepted values and aliases. For other providers, do not assume these parameter names or defaults apply.

6. Handle common edge cases

  • Element exists, but content is still empty: wait for a child element or a loaded-state attribute/text signal rather than the initial container.
  • Content appears only after scroll: scroll before waiting. For infinite feeds, define a stopping condition such as a target item count or maximum height to avoid an unbounded capture.
  • Element is hidden: check whether the API waits for existence or visibility. A hidden tab’s content may exist in the DOM without being visible.
  • Selector matches many elements: scope it to the intended section and use a unique selector where possible. Some capture APIs select only the first match.
  • Network never becomes idle: prefer a selector wait or use a provider’s bounded network-idle behavior. Do not keep extending timeouts without identifying the background request.
  • Content is behind consent or login: provide the required cookie or authentication state when permitted. A screenshot service cannot render data that the page does not expose to that browser session.
  • Bot challenge or blocked page: a longer wait will not make a challenge page become the requested content. Check the returned page status/verdict and use a permitted access path.
  • Full-page image is excessively tall: use a height cap or capture a specific region. Infinite scrolling pages may continue to add content as the browser scrolls.

7. Troubleshooting

Symptom or error Likely cause Fix
Screenshot is blank or missing the module Capture occurred at navigation completion before client rendering Wait for a selector on the final content and verify the selector against the live DOM.
Selector wait times out Wrong selector, content never loaded, or loading depends on a scroll/click Check the selector, trigger the required interaction first, and choose a bounded timeout suited to the provider.
Network-idle timeout Persistent requests or polling prevent the idle condition Use a known selector as the primary signal or select a less strict documented wait mode.
Lazy images remain unloaded The page never scrolled far enough to trigger their loading Scroll through the content or enable the provider’s documented lazy-load/full-page scroll behavior.
Capture shows skeletons or placeholders The wait selector matches a wrapper rendered before data is ready Wait for a loaded item, a success state, or a placeholder’s disappearance.
Rate or concurrency error Provider request limit or concurrent render cap reached Respect any Retry-After value, reduce parallel requests, and retry with bounded backoff.
ScreenshotNeo returns selector_not_found The requested wait/capture selector did not match Inspect the exact CSS selector and verify the page state; this failure is not billed.
ScreenshotNeo returns timeout or navigation_failed The site did not finish loading or rendering within the configured limit Check the URL and page accessibility, reduce unnecessary waits, or set a suitable timeout. These failures are not billed.
ScreenshotNeo returns bot_check or blank_page The target blocked automated browsing or rendered no usable content Check the page verdict and access conditions. These outcomes are not billed.
ScreenshotNeo returns bad_request A parameter is missing, misspelled, or outside its allowed range Read the error’s parameter name and compare it with the current documentation.
ScreenshotNeo returns 401 or 402 API key is missing/invalid, or plan quota is exhausted Check the key, verify the account, and review usage or the reset date.

8. Performance, reliability, and cost

Every extra wait can add latency. A selector wait typically avoids waiting for unrelated requests, while network idle may be useful when several requests drive the UI. Avoid large fixed delays as the only synchronization method. Scroll-based loading adds work proportional to the page length, so use it for pages that need it and cap unbounded pages.

For reliability, target stable selectors, use explicit timeouts, and treat a timeout as a failed capture. Record the requested URL, wait condition, selector, timeout, status, and response headers so you can distinguish a page problem from a capture configuration problem. Retry transient navigation or rate-limit errors with bounded exponential backoff; do not blindly retry deterministic selector failures.

Cost and timeout behavior are service-specific. The cited vendor documents do not establish a universal limit or billing rule. ScreenshotNeo states that only clean screenshots are billed: bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and its responses include X-Page-Verdict and X-Billed headers. Its plans are Free (1,000 screenshots/month, no card), 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); annual billing gives two months free. Every feature is on every plan.

9. FAQ

Should I wait for network idle or for a selector?

Use a selector when you know the content that must be present. Use network idle when the page’s request activity reliably settles before it is ready; combine both only when both conditions reflect the page’s behavior.

Does a full-page screenshot automatically load every lazy image?

Not necessarily. Some APIs scroll before capture or provide a lazy-load option; others may capture before scroll-triggered assets load. Check the provider’s documented behavior and test the target page’s loading pattern.

Can a fixed delay make a flaky capture reliable?

It can absorb a known, short post-render pause, but it is not a readiness signal. Pair it with a selector wait where possible.

Does the selector wait crop the screenshot?

Usually these are separate settings. A wait selector signals when to proceed; a capture selector or element option can limit the screenshot area. Check your API’s parameter semantics.

References