Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

To capture content that appears only as you scroll, first scroll the page in bounded passes, wait for the content and images you need to finish loading, then call page.screenshot({ fullPage: true }). The fullPage option captures the page’s full height; it does not trigger infinite scrolling or guarantee lazy images are ready. The example below combines those steps and sets explicit stopping limits.

Why fullPage: true is not enough

Puppeteer’s Page.screenshot() can capture the full page when you set fullPage: true; the option defaults to false. But a screenshot captures what the page has rendered—it does not make an infinite-scroll site append more content. You must trigger the site’s scrolling behavior, decide when to stop, and verify that the content you want is ready. See Puppeteer’s screenshot guide and ScreenshotOptions reference (version 25.12.0).

Use a bounded scroll-and-check workflow

This Node.js example scrolls down in viewport-sized increments, tracks document height, and stops after the height stays unchanged for several passes or a maximum pass count is reached. It then waits for network activity to settle, checks that images have either loaded or failed, and saves a full-page PNG.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Install Puppeteer in a Node.js project with npm install puppeteer. Replace the example URL and, for a site with a specific completion signal, replace the stability check with that signal.

const puppeteer = require('puppeteer');

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

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

    const maxPasses = 30;
    const stablePassesNeeded = 3;
    let stablePasses = 0;
    let previousHeight = 0;

    for (let pass = 0; pass < maxPasses && stablePasses < stablePassesNeeded; pass++) {
      const height = await page.evaluate(() => document.documentElement.scrollHeight);

      await page.evaluate(() => {
        window.scrollBy(0, window.innerHeight);
      });
      await new Promise(resolve => setTimeout(resolve, 700));

      const newHeight = await page.evaluate(() => document.documentElement.scrollHeight);
      if (newHeight === previousHeight || newHeight === height) {
        stablePasses++;
      } else {
        stablePasses = 0;
      }
      previousHeight = newHeight;
    }

    // Give content triggered by the last scroll a chance to settle.
    await page.waitForNetworkIdle({ idleTime: 1000, timeout: 15000 }).catch(() => {});

    // Wait for images currently in the document to finish loading or fail.
    await page.evaluate(async () => {
      const images = Array.from(document.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 });
        });
      }));
    });

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

Adapt the stop condition to the page

Stable document height is a practical fallback, not proof that the site has no more content. Some feeds load items without changing height until later, use an inner scrolling container, or keep refreshing. If the page exposes a “load complete” state, a known final item, or a stable item count, check that instead. For a feed with no natural endpoint, stop at a chosen item boundary, elapsed time, or pass limit; “infinite” content has no universal final state.

Scroll the element that actually scrolls

The sample scrolls the window. If a feed lives inside a fixed-height container, identify that container and scroll it instead; otherwise the site may never receive the scroll event that triggers loading. Puppeteer’s page interactions guide describes locator-based interactions and scrolling mechanisms. The appropriate selector and completion check depend on the site.

Verify lazy images before capturing

Scrolling gives viewport-triggered lazy images an opportunity to load, but there is no universal Puppeteer switch that guarantees every site’s lazy images have loaded. The sample waits for each image currently in the document to reach a completed state; a broken image also counts as complete, so this check does not promise that every image rendered successfully.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some sites only create image elements as new content arrives, while others defer image requests until an element is near the viewport. If image completeness matters, combine scrolling with checks for the specific images or site state you care about. Puppeteer’s Page.evaluate() runs code in the page context and waits for a returned promise, which makes such page-specific checks possible.

Choose waits and screenshot options deliberately

Navigation and network idle

The example uses waitUntil: 'domcontentloaded' so it can begin the scroll workflow without treating initial network quiet as proof that the feed is complete. Puppeteer’s screenshot guide also demonstrates navigation with waitUntil: 'networkidle2'. Either navigation choice is only a starting point: scrolling may trigger more requests later.

page.waitForNetworkIdle() resolves when network activity is idle and waits at least the configured idle time. The documented default idle time is 500 ms in Puppeteer’s WaitForNetworkIdleOptions reference; the method is documented at Page.waitForNetworkIdle(). A quiet network does not prove that another scroll cannot append content or that all images are correct, so pair it with a page-specific completion and image check.

Full-page capture and output

Use fullPage: true to capture the full page height. Puppeteer’s screenshot format defaults to PNG; the API also documents other screenshot options, including clipping and viewport capture. Set viewport dimensions before navigation when the site’s responsive layout or loading behavior depends on them. Do not assume every page can be captured as one image: outcome depends on the site, chosen stopping rule, browser behavior, and total page size.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Troubleshoot missing content and incomplete images

  • The screenshot shows only the initial viewport: Confirm fullPage: true is set, and check whether the site uses an inner scroll container rather than window scrolling.
  • Later feed items are missing: Increase the bounded pass count or settling delay, or replace height stability with the site’s item count or completion signal. Avoid removing limits on a feed that can grow indefinitely.
  • Images are blank: Ensure scrolling reaches the image region and wait for the relevant image elements or site state. A network-idle wait alone does not establish image readiness.
  • The script hangs or exceeds its timeout: Keep navigation and network-idle timeouts bounded. Pages with ongoing requests may never become idle within your chosen timeout; handle that outcome and rely on a separate completion rule.
  • Some images remain broken: The sample treats both load and error as finished to avoid waiting forever. If successful image rendering is required, inspect the target images’ naturalWidth or the site’s own error state and decide whether to retry or report failure.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If you need a screenshot endpoint rather than a custom Puppeteer workflow, ScreenshotNeo is a website screenshot API and MCP server. Its capture flow accepts cookie and consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets; those steps can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and responses include X-Page-Verdict and X-Billed headers. AI agents can use its MCP server tools, including take_screenshot, get_page_info, and capture_pdf.

One GET request returns an image or PDF. For a WebP capture of the target URL:

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

See the ScreenshotNeo API documentation for request options. The endpoint can capture full pages, but infinite-scroll completion remains dependent on the page’s loading behavior and the capture options you choose; use Puppeteer when you need a custom scroll loop or site-specific stopping rule.

ScreenshotNeo includes 1,000 screenshots per month free with no card; paid plans start at $5 for 3,000. Sign up for the free plan.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Frequently Asked Questions

Does Puppeteer have a built-in option to load every item on an infinite-scroll page?

No. You need to trigger scrolling and define a page-specific stopping condition.

Does ScreenshotNeo automatically guarantee that an infinite-scroll feed is complete?

No. A screenshot service cannot infer a universal endpoint for an unbounded feed; use a custom browser workflow when completion depends on site-specific scrolling logic.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.