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

A blank Puppeteer screenshot is evidence, not a diagnosis. The page may never have navigated, may have returned an error document, may have failed in browser-side JavaScript, or may be waiting for resources or an application state that your script never checks. Diagnose it without breakpoints by collecting evidence in layers: navigation, visual state, browser events, network activity, application readiness, and finally DevTools protocol and browser-process logs.

1. Prove what navigation did

Start by logging the URL you requested, the response returned by page.goto(), the final URL, and any exception. A navigation response is the main-frame response, but it can be null. That is expected for cases such as about:blank or a same-URL hash change; do not treat every null as a failed load.

import puppeteer from 'puppeteer';

const target = 'https://example.com/app';
const browser = await puppeteer.launch();
const page = await browser.newPage();

try {
  const response = await page.goto(target, {
    waitUntil: 'domcontentloaded',
    timeout: 30_000
  });

  console.log({
    requested: target,
    finalUrl: page.url(),
    status: response ? response.status() : null,
    statusText: response ? response.statusText() : null
  });
} catch (error) {
  console.error('Navigation failed', {
    message: error.message,
    url: page.url()
  });
} finally {
  await browser.close();
}

Thrown errors point to a different class of problem: invalid URLs, SSL failures, timeouts, unreachable or unresponsive servers, failed main resources, and blocked URLs are documented navigation failure cases. A valid HTTP status does not necessarily mean your application rendered. In headless shell mode, for example, valid 404 and 500 responses may not throw, so inspect the status explicitly. Headless shell also cannot navigate to PDF documents; keep that limitation scoped to that mode rather than all headless operation.

2. Capture the state Puppeteer actually saw

Save a screenshot immediately after navigation (and again after any readiness wait). Include the URL and a small amount of DOM text in your log so a blank image can be distinguished from a page whose content is merely outside the viewport or still loading.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#1 Best Overall
Elebase USB to USB C Adapter for iPhone 18 Pro Max,USBC Car Charger Adapter
  • Read Before You Buy — No Video Output: These adapters support charging and USB 2.0 data transfer, but cannot transmit video signals. Except for standard USB webcams (which use USB data only), they are not compatible with HDMI/DisplayPort cables, video-capable USB-C hubs, or docking stations with video output.
  • Convert USB-A Ports to USB-C: Designed to connect USB-C earphones, cables, flash drives, card readers, and other USB-C accessories to standard USB-A ports. Plug-and-play with no drivers or software required.
  • Aluminum Alloy Housing: Built with a sturdy aluminum alloy shell that aids in heat dissipation and protects against daily wear and scratches. Designed to maintain a stable and secure connection.
  • Compact & Travel-Friendly: The ultra-compact design allows the adapter to stay plugged into your device without blocking adjacent ports or adding bulk, reducing wear and tear on your original USB ports.
  • 12-Month Warranty: Backed by a 12-month manufacturer warranty for peace of mind. Designed to meet strict quality control standards for reliable everyday performance.
await page.screenshot({
  path: 'diagnostic.png',
  fullPage: true
});

console.log('title:', await page.title());
console.log('body text:', (await page.locator('body').innerText().catch(() => '')).slice(0, 500));

A screenshot tells you what was painted at that instant, not why it is blank. A white image can mean an empty document, a crashed client application, hidden content, a blocked resource, or a capture taken before rendering completed. Run a headful sanity check as a separate experiment:

const browser = await puppeteer.launch({
  headless: false,
  slowMo: 100
});

headless: false lets you see the browser display, while slowMo spaces out Puppeteer operations. Neither is a universal fix; they expose timing, redirects, consent dialogs, and browser-visible errors that a screenshot may conceal.

3. Forward browser console and page errors

Page JavaScript runs inside Chromium. Its console.log output does not automatically appear in Node.js, so install listeners before navigation. Also record uncaught page errors and the URL at the time of the event.

page.on('console', async msg => {
  const values = await Promise.all(msg.args().map(arg => arg.jsonValue().catch(() => undefined)));
  console.log(`[browser:${msg.type()}]`, msg.text(), values);
});

page.on('pageerror', error => {
  console.error('[pageerror]', { message: error.message, url: page.url() });
});

page.on('error', error => {
  console.error('[page crashed]', error.message);
});

await page.goto(target, { waitUntil: 'domcontentloaded' });

Console output can reveal a module that failed to load, a configuration exception, or an API call rejected by client code. pageerror reports uncaught errors, while the broader error event is useful when the page itself crashes. Event details can vary with your Puppeteer version, so verify behavior against the reference for the version you run (the cited documentation covered releases around 25.10.0–25.12.0).

Free tools Windows power users keep installed

One-click scans. No signup required.

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

4. Separate failed requests from HTTP error responses

Network diagnostics have two distinct signals:

  • Failed request: the request could not complete at the network or browser level. Puppeteer emits requestfailed instead of requestfinished; request.failure() may provide an errorText, but failure text is not guaranteed.
  • HTTP error response: the server answered with a status such as 404 or 503. That request can still emit requestfinished, so failed-request events alone will miss it.
page.on('requestfailed', request => {
  console.error('[requestfailed]', {
    method: request.method(),
    url: request.url(),
    failure: request.failure()
  });
});

page.on('response', response => {
  const status = response.status();
  if (status >= 400) {
    console.error('[http-error]', {
      status,
      url: response.url(),
      resourceType: response.request().resourceType()
    });
  }
});

Look especially for failed JavaScript bundles, stylesheet responses, API calls, fonts, and image requests. A 200 response for an HTML error page can be just as damaging as a 404, so inspect the body or content type when the URL is suspicious.

Rank #2
Anker USB-C Hub, 5-in-1 USB Hub for Laptops, 4K HDMI Multiport Adapter
  • 5-in-1 USB-C Hub: Experience comprehensive connectivity featuring a Power Delivery input, two USB-A 2.0 ports, a USB-A 3.0 port, and an HDMI port. (Note: The USB-C power delivery input port is only for connecting an external wall charger to power your laptop and cannot power peripheral devices.)
  • 90W Pass-Through Charging: Achieve optimal charging with 90W pass-through power to your laptop, supported by a total input of 100W, with the hub reserving 10W for operational efficiency. (Note: Wall charger not included.)
  • Quick Data Transfers: Accelerate your productivity with rapid data transfers using a high-speed 5Gbps USB 3.0 port and two 480Mbps USB 2.0 ports.
  • 4K HDMI Display: Enhance your visual experience with a hub capable of delivering 4K resolution at 30Hz in both mirror and extend modes. Please note that this hub is compatible with MacBook (macOS 12 and newer), Windows 10 and 11, ChromeOS, and laptops equipped with DP Alt Mode and Power Delivery. Note: This device is not compatible with Linux.
  • What You Get: Anker USB-C Hub (5-in-1, 4K HDMI), welcome guide, 18-month warranty, and our friendly customer service.

5. Wait for the application’s visible state

Navigation completion and network quietness are transport signals, not proof that your app rendered. Choose a selector or condition that represents usable content: a dashboard heading, a product card, a table row, or an application-specific “ready” marker. Puppeteer locators wait for elements to be present and, where required, visible and stable.

await page.goto(target, { waitUntil: 'domcontentloaded' });

try {
  await page.locator('[data-testid="app-ready"]').wait({
    timeout: 20_000
  });
  await page.screenshot({ path: 'ready.png', fullPage: true });
} catch (error) {
  console.error('Expected application state never appeared', {
    message: error.message,
    url: page.url()
  });
  await page.screenshot({ path: 'not-ready.png', fullPage: true });
}

If your version does not expose the exact locator method shown, use the equivalent selector wait documented for that version. Prefer a visible, application-specific condition over an arbitrary sleep. A delay can be useful for an experiment, but it does not establish readiness and makes tests slower and less deterministic.

6. Use one diagnostic script instead of breakpoints

The following compact harness installs listeners first, records navigation, waits for a meaningful selector, and always saves evidence.

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.
import puppeteer from 'puppeteer';

const url = process.argv[2] ?? 'https://example.com/app';
const readySelector = process.argv[3] ?? '[data-testid="app-ready"]';

const browser = await puppeteer.launch();
const page = await browser.newPage();

page.on('console', msg => console.log(`[console:${msg.type()}] ${msg.text()}`));
page.on('pageerror', err => console.error('[pageerror]', err.message));
page.on('error', err => console.error('[page-crash]', err.message));
page.on('requestfailed', req => console.error('[requestfailed]', req.url(), req.failure()));
page.on('response', res => {
  if (res.status() >= 400) console.error('[http]', res.status(), res.url());
});

try {
  const response = await page.goto(url, {
    waitUntil: 'domcontentloaded',
    timeout: 30_000
  });
  console.log('navigation', {
    finalUrl: page.url(),
    status: response?.status() ?? null
  });

  await page.screenshot({ path: 'after-navigation.png', fullPage: true });
  await page.locator(readySelector).wait({ timeout: 20_000 });
  await page.screenshot({ path: 'after-ready.png', fullPage: true });
} catch (error) {
  console.error('diagnostic failure', error.message);
  await page.screenshot({ path: 'failure-state.png', fullPage: true }).catch(() => {});
} finally {
  await browser.close();
}

Run it with node diagnose.js https://your-site.test '#main-content'. Compare the three screenshots and the event timeline. If the first image is blank but the second becomes populated, the issue is a readiness assumption. If both are blank and console or request logs show errors, follow those errors before changing launch flags.

7. Escalate to protocol and browser-process logs

When page-level evidence is inconclusive, inspect the automation layer. Enable Puppeteer’s protocol logging with the environment variable below:

Rank #3
Sale
Anker USB C Hub, 7in1 Multi-Port USB Adapter, 4K@60Hz USBC to HDMI Splitter
  • Sleek 7-in-1 USB-C Hub: Features an HDMI port, two USB-A 3.0 ports, and a USB-C data port, each providing 5Gbps transfer speeds. It also includes a USB-C PD input port for charging up to 100W and dual SD and TF card slots, all in a compact design.
  • Flawless 4K@60Hz Video with HDMI: Delivers exceptional clarity and smoothness with its 4K@60Hz HDMI port, making it ideal for high-definition presentations and entertainment. (Note: Only the HDMI port supports video projection; the USB-C port is for data transfer only.)
  • Double Up on Efficiency: The two USB-A 3.0 ports and a USB-C port support a fast 5Gbps data rate, significantly boosting your transfer speeds and improving productivity.
  • Fast and Reliable 85W Charging: Offers high-capacity, speedy charging for laptops up to 85W, so you spend less time tethered to an outlet and more time being productive.
  • What You Get: Anker USB-C Hub (7-in-1), welcome guide, 18-month warranty, and our friendly customer service.
NODE_DEBUG="puppeteer:*" node diagnose.js https://your-site.test '#main-content'

Protocol output can expose a command that never received a response or an operation that triggered a pending callback. Puppeteer also documents browser.debugInfo.pendingProtocolErrors; inspect it before closing the browser when you suspect a stuck protocol call. These logs can contain URLs, headers, or other sensitive data, so redact them before sharing.

For browser startup and crash investigation, launch with dumpio: true to forward the browser process’s standard streams:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const browser = await puppeteer.launch({ dumpio: true });

Use this escalation only after navigation, screenshots, page events, and network status have produced no explanation. It is verbose and often points to environment, Chromium, sandbox, or protocol problems rather than application code.

How to read the evidence

Signal Layer What it can prove What it cannot prove
Navigation response, URL, exception Main-frame navigation Destination, status, timeout or load failure A null response is not automatically an error; a successful status does not prove rendering
Screenshot or headful run Rendered visual state What the browser displayed at capture time The root cause of a blank image
Console and page events Browser-side application code Client logs, uncaught exceptions, crashes Server-side causes unless the page reports them
Request events plus response status Network resources Failed loads and HTTP error responses That an application used the response successfully
Protocol and browser logs Automation and browser internals Pending calls, startup and process output A simple explanation when the application itself is broken

Common blank-page patterns and fixes

Navigation failed before any document rendered

Look for a thrown goto() error, a timeout, SSL message, or an unchanged URL. Confirm DNS, certificates, proxy settings, and the target URL outside Puppeteer, then raise the timeout only if the server is legitimately slow.

The document loaded but the app crashed

A normal navigation status followed by pageerror, console exceptions, or a missing ready selector points to browser-side code. Fix the bundle, runtime configuration, or API assumptions; changing headless mode will not repair the application.

Rank #4
Sale
UGREEN USB to USB C Adapter Combo 4-Pack, 10Gbps USB C Converter Space Gray
  • Dual Converters, Infinite Potential:Includes 2× USB C male to USB A female adapters and 2× USB A male to USB C female adapters. Perfect for a wide range of uses—tablets with Bluetooth keyboards, expand USB ports on macbook, and more. Two different converters for all your daily needs
  • Next-Level 10Gbps & 3A Charging: No more slow 480Mbps, this usb to usb c adapter has a transfer speed of up to 10Gbps, allowing you to do more transferring in less time. This usb adapter fits both USB A and USB C charger, supporting up to 3A fast charging
  • Upgraded Exquisite Craftsmanship: With an aluminum alloy housing and metal connector, the usbc to usb adapter is extremely durable and sturdy. Rigorously tested to withstand more than 10,000 times of plugging and unplugging, ensuring long-lasting performance
  • Broad Compatible: The usb c to usb adapter widely supports all USB C/ USB A devices like laptops, tablets, cellphones, car chargers, and phone chargers. Such as compatible with MacBook Pro/Air 2023/2022, Thunderbolt 4/3 Devices,Apple MagSafe Watch 9/8/7/SE/Ultra, iPad Pro 2022/2021, Samsung Galaxy S23/S20/S10, and iPhone 17/16/15 Pro. Plug and play
  • Please Note: To reach 10Gbps speed, keep the cable under 3.3 ft. For USB A Male to USB C adapters, try flipping the USB C connector. USB C Male to USB A adapters support bidirectional 10Gbps transfer within 3.3 ft

An asset or API returned an error page

Use both listeners. A 404 or 503 may finish normally, while a network failure emits requestfailed. Check the exact URL, response status, content type, and authentication headers.

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

The capture happened too early

If headful mode eventually shows content or a later screenshot is populated, wait for a visible application marker. Avoid relying solely on networkidle; analytics, WebSockets, and polling can keep a page busy or quiet at the wrong time.

Only headless mode is blank

Compare viewport, user agent, permissions, fonts, GPU-related behavior, and site checks between headful and headless runs. Keep the headful/slow-motion run as a reproducer, then use event logs to identify the differing request or script.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

ScreenshotNeo provides a website screenshot API when you need a clean capture without maintaining a Puppeteer browser. It accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be disabled. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing result in X-Page-Verdict and X-Billed headers.

One GET request returns PNG, JPEG, WebP, or PDF. The API supports full-page screenshots with lazy images loaded, CSS-selector element capture, dark mode, device presets and custom viewports, retina scale, PDF paper and margin controls, custom CSS and JavaScript, clicks, selector or network-idle waits, ad/tracker/request blocking, headers, cookies, user agents, authorization, timezone and geolocation, transparent backgrounds, resizing, TTL caching, signed image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, usage reporting, and an OpenAPI specification. Parameter names used by other screenshot APIs also work, easing migration.

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

For developers using Claude, Cursor, or another MCP client, ScreenshotNeo also provides an MCP server with take_screenshot, get_page_info, and capture_pdf tools.

Best Value
Anker USB C Hub, 5-in-1 USBC to HDMI Splitter with 4K Display
  • 5-in-1 Connectivity: Equipped with a 4K HDMI port, a 5 Gbps USB-C data port, two 5 Gbps USB-A ports, and a USB C 100W PD-IN port. Note: The USB C 100W PD-IN port supports only charging and does not support data transfer devices such as headphones or speakers.
  • Powerful Pass-Through Charging: Supports up to 85W pass-through charging so you can power up your laptop while you use the hub. Note: Pass-through charging requires a charger (not included). Note: To achieve full power for iPad, we recommend using a 45W wall charger.
  • Transfer Files in Seconds: Move files to and from your laptop at speeds of up to 5 Gbps via the USB-C and USB-A data ports. Note: The USB C 5Gbps Data port does not support video output.
  • HD Display: Connect to the HDMI port to stream or mirror content to an external monitor in resolutions of up to 4K@30Hz. Note: The USB-C ports do not support video output.
  • What You Get: Anker 332 USB-C Hub (5-in-1), welcome guide, our worry-free 18-month warranty, and friendly customer service.

See the ScreenshotNeo API documentation for the current request options. A minimal cURL call is:

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)
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}`);

The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; yearly billing gives two months free, and every feature is available on every plan. Create a free ScreenshotNeo account to try it.

Frequently Asked Questions

Does a 404 from page.goto() always throw?

No. Depending on the headless mode and browser behavior, an HTTP error response can be returned normally. Log and inspect the response status instead of relying only on exceptions.

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

Why is requestfailed empty when an API is returning 503?

A 503 is an HTTP response, not a network failure. Listen for response events and record status codes in addition to requestfailed.

Should I use networkidle to fix a blank screenshot?

Use it only as one timing signal. A visible, application-specific selector is stronger evidence that the interface is ready.

Are Puppeteer protocol logs safe to publish in a bug report?

Not automatically. They may contain URLs, headers, or other sensitive data; redact them before sharing.

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.

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