ScreenshotNeo

BlogHow-to

BrowserCat Screenshots Are Missing Images: Troubleshooting Guide

Find out whether missing images are caused by capture timing, lazy loading, failed requests, or an access challenge, then fix the right problem.

By the ScreenshotNeo team4 October 202610 min read

When images are missing from a BrowserCat screenshot, first determine which of four states you have: the capture ran before images finished loading; lazy-loaded images never entered the viewport; image requests failed or were blocked; or the site served an access-denied, CAPTCHA, or otherwise altered page. Inspect the page in the same browser session immediately before capture, then apply the matching fix.

BrowserCat documents using Playwright to connect to its managed browser and take screenshots. The BrowserCat documentation reviewed for this guide does not document a dedicated missing-images or “wait for images” screenshot option. The waitForImages option sometimes shown in search results belongs to Browserless’s BAP API; it is not a BrowserCat setting. See BrowserCat’s quick start and the separate Browserless screenshot guide.

1. Diagnose the page before changing capture code

A screenshot records the rendered browser state at capture time. It does not, by itself, tell you whether an image was removed, still loading, lazy-loaded, or blocked. Use the same session, URL, cookies, viewport, and browser connection as the failing capture.

  1. Inspect the rendered page. Before taking the screenshot, check whether the image elements exist, whether their src or currentSrc is populated, and whether you see placeholders, broken-image icons, a consent overlay, or an access challenge.
  2. Check timing. If the images appear after a short delay or the failure varies between runs, wait for the relevant images or a stable element that indicates the content is ready.
  3. Check position. If images are missing mainly lower on a long page, scroll through those sections to trigger lazy loading before taking the full-page screenshot.
  4. Check requests. If the elements exist but the images never appear, inspect the image request and response. Look for failed responses, redirects, authorization requirements, expired signed URLs, hotlink protection, mixed content, or content-policy blocks.
  5. Check for a challenge. A CAPTCHA, 403, access-denied page, blank page, or output substantially different from an ordinary visit points to an access or automation-blocking issue, not just image timing. Browserless lists such symptoms as general blocking clues; they are not a BrowserCat-specific guarantee. See its screenshot documentation.

For a quick DOM inventory, run this in the same Playwright page before capture:

const imageState = await page.locator('img').evaluateAll(images =>
  images.map(img => ({
    alt: img.alt,
    src: img.getAttribute('src'),
    currentSrc: img.currentSrc,
    complete: img.complete,
    naturalWidth: img.naturalWidth,
    naturalHeight: img.naturalHeight,
    loading: img.loading,
  }))
);
console.dir(imageState, { depth: null });

An image with complete: true and naturalWidth: 0 did not decode into a usable image; that is a useful clue to investigate its request and the page’s state. It is not a diagnosis of the underlying cause by itself. Background images set in CSS will not appear in this img inventory, so inspect the relevant element’s computed background-image and network activity too.

2. Connect Playwright to BrowserCat and wait for images

The script below connects to BrowserCat’s documented cloud endpoint, navigates to a target page, scrolls to trigger common lazy-loading behavior, waits for currently present img elements to load or fail, and saves a full-page PNG. Replace the environment variable values with your own API key and target URL. This is general Playwright code; the wait logic is not a BrowserCat-specific configuration option.

Install the Playwright package and local browser binaries as described in the BrowserCat quick start. The BrowserCat cloud session runs Chromium, so Chromium is the relevant local browser for development.

npm install -D playwright
npx playwright install chromium

Save as screenshot.mjs and run with BROWSERCAT_API_KEY=... TARGET_URL=https://example.com node screenshot.mjs:

import { chromium } from 'playwright';

const apiKey = process.env.BROWSERCAT_API_KEY;
const targetUrl = process.env.TARGET_URL;

if (!apiKey || !targetUrl) {
  throw new Error('Set BROWSERCAT_API_KEY and TARGET_URL before running.');
}

const browser = await chromium.connect('wss://api.browsercat.com/connect', {
  headers: { 'Api-Key': apiKey },
});

try {
  const context = await browser.newContext({
    viewport: { width: 1440, height: 1000 },
  });
  const page = await context.newPage();

  // Collect failed requests to help distinguish timing from network problems.
  const failedRequests = [];
  page.on('requestfailed', request => {
    failedRequests.push({
      url: request.url(),
      error: request.failure()?.errorText,
    });
  });

  // domcontentloaded avoids waiting indefinitely for long-lived network activity.
  await page.goto(targetUrl, { waitUntil: 'domcontentloaded', timeout: 60_000 });

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

  // Wait for all img elements present at this point to finish or fail.
  await page.waitForFunction(() => {
    return [...document.images].every(img => img.complete);
  }, { timeout: 30_000 }).catch(() => {
    console.warn('At least one image did not finish before the wait timeout.');
  });

  const imageState = await page.locator('img').evaluateAll(images =>
    images.map(img => ({
      src: img.currentSrc || img.src,
      complete: img.complete,
      naturalWidth: img.naturalWidth,
      naturalHeight: img.naturalHeight,
      loading: img.loading,
    }))
  );

  console.log(JSON.stringify({ imageState, failedRequests }, null, 2));
  await page.screenshot({ path: 'page.png', fullPage: true });
} finally {
  await browser.close();
}

The wait predicate considers a failed image “complete” too; that prevents a broken asset from holding the capture open forever. Check naturalWidth and failedRequests to separate a completed image from a successfully rendered one. This only covers DOM img elements: CSS backgrounds, images added after the check, and content inside frames may need separate inspection. For a page with a known image or ready marker, waiting for that specific content is usually more reliable than waiting for every image on the page.

Wait for a known image or readiness marker

If the page has a stable selector for the image or content block, wait for it to appear and decode. This avoids waiting on unrelated analytics, ads, or images that are not part of the screenshot.

const hero = page.locator('img.hero-image');
await hero.waitFor({ state: 'visible', timeout: 20_000 });
await hero.evaluate(img => img.decode());
await page.screenshot({ path: 'hero-page.png', fullPage: true });

If the page’s application exposes a stable readiness marker, wait for that instead of guessing with a fixed delay:

await page.locator('[data-page-ready="true"]').waitFor({ timeout: 20_000 });

Some sites replace image elements after hydration or load image URLs from CSS. A selector becoming visible may therefore not mean the final asset is ready. Verify the relevant element or request, and wait for the page-specific condition that actually indicates readiness.

3. Trigger lazy-loaded images on long pages

Many pages defer off-screen images until scrolling brings them near the viewport. A full-page screenshot does not guarantee that the page’s scripts were prompted to request every lazy asset first. Scroll through the content, pause briefly at each section if needed, then return to the desired starting position before capture. General screenshot guidance describes this approach; it is not a BrowserCat-specific feature. See Browserless’s notes on image waits and scrolling.

The example script uses incremental scrolling, which works for many intersection-based lazy loaders. It may not cover every site: some pages require scrolling a specific container rather than the window, clicking “load more,” or waiting for an application-specific event. If a page uses an inner scroll area, scroll that element instead. If the site has a “load more” control, use the intended control when authorized and appropriate.

For exceptionally long pages, avoid capturing while the scroll loop is still running. Confirm that the expected images have populated before calling screenshot(). A full-page capture can also produce a very tall image; consider capturing sections or a specific element when that better matches the output you need.

4. Inspect failed image requests and page state

If waiting and scrolling do not resolve the issue, find the exact image URL and inspect its request outcome in the same session. The following listener reports request failures; it does not report every HTTP error response, because a server can return an HTTP status such as 403 without the browser emitting a transport-level requestfailed event.

page.on('response', async response => {
  const request = response.request();
  if (request.resourceType() === 'image' && response.status() >= 400) {
    console.warn('Image HTTP error', response.status(), response.url());
  }
});

page.on('requestfailed', request => {
  if (request.resourceType() === 'image') {
    console.warn('Image request failed', request.url(), request.failure()?.errorText);
  }
});

When you identify a failed URL, check whether it depends on a session cookie or authorization header, whether a signed URL expired, whether the response redirects to a login or challenge page, and whether the page’s security policy or mixed-content rules block it. Compare the image request in the automation session with an ordinary browser visit using the same region and access conditions. These are diagnostic checks, not evidence that BrowserCat itself caused the failure.

5. Handle access challenges separately

If the captured page contains a CAPTCHA, access-denied response, 403 page, blank output, or content different from a normal visit, first confirm that the target site permits the requested automation. A challenge is not fixed by increasing an image wait timeout. Do not assume that a proxy or a different browser service will bypass the site’s controls.

BrowserCat supports a user-configured third-party proxy and says it does not currently provide a built-in proxy service. A proxy may help investigate region- or IP-dependent resource access, but it will not fix capture timing or lazy loading. Review BrowserCat’s third-party proxy configuration and account for the provider, credentials, cost, speed, and reliability yourself.

6. Common errors and fixes

Symptom Likely cause Next step
Top images appear; lower images are absent Lazy loading did not trigger below the fold Scroll the relevant page or container before capture; verify the lower image URLs populate.
Image count or results vary between runs Capture races image loading, hydration, or delayed content Wait for a specific image or stable readiness marker; use a bounded timeout.
Broken icons remain after waiting Image request failed, was blocked, or returned unusable content Inspect response status, URL, redirects, credentials, and browser console/network clues.
DOM has no expected image element Page content has not rendered, requires interaction, or the wrong route/state loaded Check page title and URL, wait for an application marker, and perform the required ordinary navigation steps.
Only some images fail behind a login Image host may require cookies or authorization that the request lacks Confirm the browser is authenticated and that image requests use the needed session state.
Screenshot shows CAPTCHA, 403, or an access-denied page Site access or automation policy challenge Check permission and access requirements; do not treat it as an image-loading timeout.
waitForFunction times out An image never reached complete, or the page keeps inserting images Use a bounded wait for the required subset and inspect failed requests; avoid indefinite waits.
Images look different in a full-page capture Responsive layout, sticky elements, viewport, or page-specific lazy loading affects rendering Set a deterministic viewport and compare viewport capture with full-page capture; inspect page behavior while scrolling.

7. Reliability, performance, and cost considerations

  • Prefer condition-based waits. Waiting for a target image or readiness marker is generally more predictable than an arbitrary long sleep. Keep a timeout so a missing asset cannot stall the job indefinitely.
  • Do not wait for network idle by default on every page. Analytics, polling, and long-lived requests can prevent an idle condition. Choose the narrowest page-specific signal that means the required content is ready.
  • Limit the work to the output you need. Scrolling an entire long page and waiting for every image increases session time and may trigger extra requests. For a screenshot of one component, capture that component and wait for its assets.
  • Keep the environment consistent. Use the same viewport, URL, authentication state, and network region when reproducing the issue. This makes responsive behavior and region-specific access easier to compare.
  • Record enough evidence to reproduce it. Log the final URL, image source URLs, failed requests, relevant response statuses, and the screenshot timestamp. Avoid logging secrets in cookies, headers, or signed URLs.

BrowserCat describes its cloud browser connection in its quick start; the cost of your run depends on your BrowserCat account and usage terms, which you should check in its current product documentation. No published prevalence figure for this missing-image failure mode is established by the sources cited here.

Or skip the browser setup

ScreenshotNeo takes a screenshot or PDF with one API request. Its capture removes cookie and consent banners, newsletter popups, and chat widgets before the shot; each cleanup step can be turned off. Only clean shots are billed: bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and responses identify the page verdict and billing status in headers. It also provides an MCP server for AI agents using Claude, Cursor, or another MCP client.

See the ScreenshotNeo API documentation for the available parameters.

cURL

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

Python

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

Node.js

const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://stripe.com',
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', new Uint8Array(await res.arrayBuffer()));

The examples use the supplied Stripe URL; replace it with the page you need to capture. The API accepts the parameter names used by other screenshot APIs as well. Plans include 1,000 screenshots per month free with no card, then paid plans start at $5 for 3,000 screenshots; every feature is on every plan. Sign up for 1,000 free screenshots a month with no card.

Frequently asked questions

Does BrowserCat have a waitForImages option?

The BrowserCat pages reviewed for this guide do not document that option. The named option belongs to Browserless BAP documentation. In BrowserCat, use the supported Playwright or Puppeteer APIs for waits and verify behavior against the relevant library’s current docs.

Will fullPage: true load images below the fold?

Do not rely on it to trigger every site’s lazy-loading behavior. Scroll the relevant content first, then confirm the images are present before taking the full-page capture.

Should I add a proxy to fix missing images?

Only investigate a proxy when evidence points to region- or IP-dependent access. BrowserCat supports third-party proxy configuration but does not include a built-in proxy service; a proxy does not address ordinary loading delays or lazy loading.