ScreenshotNeo

BlogHow-to

Fix Lazy-Loaded Content from a Hindi Blog Not Appearing in Screenshots

If a Hindi blog’s lazy-loaded text or images are missing from a screenshot, scroll the page to trigger loading, wait for the content, then capture.

By the ScreenshotNeo team4 October 202610 min read

If text or images from a Hindi blog are missing from a screenshot, the page’s lazy loader may not have run. A full-page screenshot captures the scrollable document, but that does not necessarily trigger the scrolling behavior some sites use to load off-screen content. In Playwright, scroll through the relevant page regions, wait for the target text or image to be ready, and then capture.

This applies to Hindi and other-language blogs alike. The language usually is not the cause: the key question is whether the missing content is loaded only after scrolling, and whether its request and page code succeed.

1. Confirm what the screenshot is capturing

First check whether you are taking a viewport screenshot or a full-page screenshot. In Playwright, fullPage: true captures the full scrollable page. The documentation describes it as capturing the page as if it were very tall and could fit entirely on screen. That defines the capture extent; it does not promise to trigger every scroll-driven loader. Playwright: Screenshots

await page.screenshot({ path: 'blog.png', fullPage: true });

If the missing content appears after you manually scroll the page in a normal browser, that is a strong clue that the page requires scroll events before capture. If it remains missing, investigate the page’s own loading behavior as described below.

2. Reproduce the problem and identify the target

  1. Open the blog page in a browser at the same URL and, as far as possible, the same viewport and browser conditions as the screenshot job.
  2. Note one specific missing item: for example, a heading, a paragraph, or an image with a known selector or accessible description.
  3. Scroll manually to that item. If it appears only after scrolling, the page likely has a scroll-triggered loader, such as one driven by IntersectionObserver.
  4. Record how the page behaves: does text appear, does an image request begin, and does the image finish loading?

A Playwright GitHub issue describes this failure mode for off-screen images and IntersectionObserver-triggered content, and proposes scrolling the page before taking a screenshot. It is a feature request, not evidence that a new screenshot option exists. The practical fix is to make the page perform its normal scroll-triggered behavior before capture. Playwright issue #40941

3. Scroll incrementally, wait for the content, then capture

Here is a runnable Playwright example for Node.js. It scrolls through the document in viewport-sized steps, waits briefly between steps to give page handlers and requests time to run, waits for a known target, checks that images have finished loading, and then saves a full-page screenshot. Replace the URL and target selector with the ones for the blog you are capturing.

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch({ headless: true });
  const page = await browser.newPage({ viewport: { width: 1365, height: 900 } });

  try {
    await page.goto('https://example.com/hindi-blog-post', {
      waitUntil: 'domcontentloaded',
      timeout: 60000
    });

    // Replace this with a selector for content that must be present.
    const target = page.locator('article .missing-section');

    // Trigger scroll-based loaders throughout the document.
    await page.evaluate(async () => {
      const step = Math.max(1, window.innerHeight * 0.8);
      const pause = ms => new Promise(resolve => setTimeout(resolve, ms));
      let previousHeight = 0;
      let stableHeightPasses = 0;

      while (stableHeightPasses < 3) {
        const height = document.documentElement.scrollHeight;
        const maxY = Math.max(0, height - window.innerHeight);

        for (let y = 0; y <= maxY; y += step) {
          window.scrollTo(0, y);
          await pause(150);
        }
        window.scrollTo(0, maxY);
        await pause(300);

        const newHeight = document.documentElement.scrollHeight;
        if (newHeight === previousHeight) {
          stableHeightPasses += 1;
        } else {
          stableHeightPasses = 0;
          previousHeight = newHeight;
        }
      }

      window.scrollTo(0, 0);
    });

    // Tie readiness to the content you actually need, not an arbitrary long sleep.
    await target.waitFor({ state: 'visible', timeout: 15000 });

    // Wait for images in the article to finish loading or fail. A failed image
    // should be diagnosed rather than silently treated as successful content.
    await page.locator('article img').evaluateAll(async images => {
      await Promise.all(images.map(img => {
        if (img.complete) return Promise.resolve();
        return new Promise(resolve => {
          img.addEventListener('load', resolve, { once: true });
          img.addEventListener('error', resolve, { once: true });
        });
      }));
    });

    const targetText = (await target.innerText()).trim();
    if (!targetText) {
      throw new Error('Target section is visible but contains no text');
    }

    await page.screenshot({ path: 'hindi-blog.png', fullPage: true });
    console.log('Saved hindi-blog.png');
  } finally {
    await browser.close();
  }
})().catch(error => {
  console.error(error);
  process.exitCode = 1;
});

Install Playwright and its browser before running the script: npm install playwright followed by npx playwright install chromium. Save the example as screenshot.js and run node screenshot.js.

The image wait above allows failed images to finish their error event so the script does not hang forever; it does not claim those images loaded successfully. To verify a specific image, check its naturalWidth as well as complete, and fail or log when naturalWidth is zero. The wait for the target section is similarly specific: change it to a selector and readiness condition that match the content you need in the final shot.

4. Choose a readiness condition that proves the content is ready

Use a condition tied to the content that matters. A fixed sleep can be useful as a short pause between scroll steps, but it is a weak final readiness check because network and rendering time vary.

For text

Wait for a locator to become visible, then verify expected text:

const heading = page.getByRole('heading', { name: 'Expected Hindi heading' });
await heading.waitFor({ state: 'visible', timeout: 15000 });
await page.getByText('Expected Hindi text', { exact: false }).waitFor();

Use the actual heading or phrase on the page. If the page’s Hindi text varies, target a stable section selector and verify that it contains non-empty text instead.

For an image

Wait for the image element and verify that the browser decoded an image with non-zero dimensions:

const image = page.locator('article img.hero-image');
await image.waitFor({ state: 'attached', timeout: 15000 });
await page.waitForFunction(selector => {
  const img = document.querySelector(selector);
  return img && img.complete && img.naturalWidth > 0;
}, 'article img.hero-image', { timeout: 15000 });

An image element can exist before its source is loaded. Checking only that the selector exists is not enough. If the page uses a background image or replaces a placeholder node, inspect the actual element and adapt the check to the site’s implementation.

For several sections or images

Wait for the most important targets individually, or check a list of required selectors and report which one failed. This makes a missing item actionable and avoids declaring success because some other part of the page loaded.

5. Handle nested scroll containers and dynamic page height

The basic scrolling example scrolls the document. Some blogs instead place content inside a scrollable panel, embed, or nested container. In that case, moving window may never bring the target near the observer. Find the container that actually scrolls and scroll that element.

await page.locator('.article-scroll-panel').evaluate(async element => {
  const pause = ms => new Promise(resolve => setTimeout(resolve, ms));
  const step = Math.max(1, element.clientHeight * 0.8);
  const maxY = element.scrollHeight - element.clientHeight;

  for (let y = 0; y <= maxY; y += step) {
    element.scrollTop = y;
    await pause(150);
  }
  element.scrollTop = maxY;
  await pause(300);
});

Lazy loading can add more content as you approach the bottom, changing the document height during the scroll. The earlier example makes repeated passes until the height is stable for several passes. If the page has an infinite feed, a continuously changing height may never settle; set an explicit stopping condition, such as the target section appearing or a maximum number of scroll passes.

6. Diagnose the page if scrolling does not fix it

  1. Check the DOM. Does the missing text or image element exist before scrolling? If not, the page may fetch or render the section only after another trigger.
  2. Check requests. Does scrolling start a request? Does it succeed? A failed or blocked request cannot be fixed by making the screenshot full-page.
  3. Check the console. JavaScript errors may stop the code that observes visibility or inserts the content.
  4. Check image state. Confirm the final image URL is set and that naturalWidth is greater than zero.
  5. Check the correct scroller. Determine whether the document, a nested panel, or an embedded frame receives the scroll.
  6. Check site-specific rules. The page may require a click, a consent choice, authentication, or some other application-specific action before content loads.

The exact cause cannot be identified without the page URL, capture code, and browser details. Treat scrolling as a way to trigger the site’s normal behavior, not as a cure for a broken request or site-specific loader.

7. Keep visual stability and content readiness separate

Playwright’s screenshot assertion waits for two consecutive screenshots to produce the same result. That helps detect visual stability, but it does not prove that a lazy section has ever been triggered. If no scroll or other event caused the content to load, two matching screenshots can both show the same placeholder. Playwright: PageAssertions

Use both kinds of checks when appropriate: first trigger and verify required content, then use a screenshot or screenshot assertion to capture or compare the rendered result.

8. cURL, Python, and Node.js options for ScreenshotNeo

If you want a hosted screenshot API instead of maintaining browser setup, ScreenshotNeo takes a URL and returns a PNG, JPEG, WebP, or PDF. Its full-page capture option loads lazy images before capture. Use the documented API options for the capture you need; the examples below show the basic one-request flow. See the ScreenshotNeo API documentation for parameter names and configuration.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://example.com/hindi-blog-post \
  -o shot.webp

Python

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={
        "access_key": "YOUR_API_KEY",
        "url": "https://example.com/hindi-blog-post",
    },
    timeout=90,
)
r.raise_for_status()
with open("shot.webp", "wb") as image_file:
    image_file.write(r.content)

Node.js

const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://example.com/hindi-blog-post'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await require('node:fs/promises').writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

Keep the API key out of public source code and logs. When a site’s behavior needs a specific selector or custom interaction, check the docs for supported options and verify the returned capture against the content you require.

9. Troubleshooting checklist

Symptom Likely cause What to do
Full-page screenshot has blank or placeholder sections Scroll-triggered loader did not run Scroll incrementally through the document or actual content container, wait for the target, then capture.
Text appears, but an image is still blank Image request is pending, failed, or the placeholder was mistaken for the final image Wait for complete and check naturalWidth > 0; inspect the request and final source URL.
Document scrolling has no effect The relevant panel or frame has its own scroll position Identify and scroll the nested container; handle frames according to the page structure.
Target locator times out Wrong selector, content not inserted, request failed, or another prerequisite is missing Inspect the DOM and console, confirm the selector, and check whether scrolling starts a successful request.
Screenshot assertion passes but content is absent The page is visually stable while showing a placeholder Trigger the loader and assert content readiness before the screenshot assertion.
Some lower sections appear inconsistently Content is added dynamically while the page is being scrolled Repeat the scroll pass until the target appears or the height settles; cap passes for infinite feeds.

10. Performance, reliability, and cost

Scrolling the whole document adds work: every step can trigger page code, image requests, and layout changes. Keep the viewport and scroll step large enough to cover the page efficiently, but pause long enough for the site’s observer and request handlers to react. Use a targeted readiness check so you do not wait for unrelated content. Avoid unbounded scrolling on infinite feeds.

For reliable captures, record the target selector, whether it appeared, and whether required images decoded successfully. Set navigation and readiness timeouts, report failures with the target name, and take the screenshot only after required content is present. A longer timeout cannot repair a blocked request or JavaScript error; inspect the page when the readiness condition fails.

With a self-managed Playwright script, account for browser installation and the compute and network time needed for each capture. With ScreenshotNeo, only clean shots are billed: bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing. Responses identify the page verdict and billing status in X-Page-Verdict and X-Billed headers. Pricing is Free for 1,000 shots per month with no card; Starter is $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.

Or skip the browser setup

One GET request returns the screenshot. ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot; bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. You get 1,000 screenshots a month free with no card, and paid plans start at $5 for 3,000.

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://example.com/hindi-blog-post \
  -o shot.webp

Read the ScreenshotNeo API docs, then sign up free for 1,000 screenshots a month, with no card.

FAQ

Does Hindi text need a special screenshot setting?

Not for this loading problem. First establish that the content has loaded and is present in the page. If it is present but rendered incorrectly, investigate fonts and browser rendering separately.

Will a longer fixed delay always solve missing sections?

No. A delay cannot trigger an observer that has not been activated, and it cannot fix a failed request. Scroll to trigger the page behavior, then wait for a condition tied to the required content.

Should I scroll to the bottom once or in steps?

Use incremental steps. A single jump to the bottom may skip intermediate regions that a page’s visibility-based loader expects to observe.

Why can a screenshot be stable while still incomplete?

Stability means the rendered pixels stopped changing over the check interval. It does not show that an untriggered section was ever requested or loaded.