keepcalculator

06 Numbers and Powers

Why Playwright Screenshots Show Blank Lazy Images

Card No. 06·63172 · Updated · Standard formula, shown below

Playwright screenshots often show blank spaces where lazy-loaded images should appear on the first run because the browser hasn’t scrolled to those images or waited for them to load before the screenshot is taken. Lazy loading defers image requests until they’re near the viewport, so a screenshot taken immediately after page load captures only the images that were visible at that moment. This article explains the mechanics, how to fix it, and related pitfalls.

How Lazy Loading Works

Lazy loading is a web performance technique that defers the loading of images (or iframes) until they are about to enter the viewport. The most common implementation uses the loading="lazy" attribute on <img> tags. Browsers only fetch the image when the user scrolls near it. This reduces initial page load time and bandwidth, but it means that a screenshot taken right after the page loads will only show images that were in the initial viewport.

Playwright, like a real user, sees the page as a browser does. When you call page.screenshot() after page.goto(), the browser has loaded the HTML and CSS, but it hasn’t necessarily triggered the lazy-loading mechanism for images below the fold. The screenshot captures what the viewport shows, which is blank placeholders.

Why the First Run Is Different

On the first run, the browser has no cached images. On subsequent runs, the images may be cached, so they appear instantly even without scrolling—but that’s not reliable. The “first run” issue is simply the default behavior: no scroll, no load. Even if you scroll programmatically, you must wait for the network requests to complete after each scroll. Playwright’s auto-waiting doesn’t automatically wait for lazy images because they aren’t part of the initial page load.

How to Fix Blank Lazy Images in Screenshots

To capture a full-page screenshot with all lazy images loaded, you need to force the browser to load them. Here are the standard methods:

  • Scroll through the page: Use page.mouse.wheel() or page.evaluate() to scroll to the bottom, then back to top, waiting for network idle after each step.
  • Disable lazy loading: For testing, you can inject JavaScript to remove the loading="lazy" attribute from all images before the page loads, using page.addInitScript().
  • Wait for specific images: Use page.waitForSelector('img[src]', { state: 'visible' }) or wait for the load event on each image.
  • Use a viewport that covers the whole page: Set the viewport height to the full page height before taking the screenshot, so all images are in the viewport at once.

The most robust approach is to scroll incrementally and wait for network requests to settle. A common pattern is:

  1. Navigate to the page.
  2. Wait for the initial load (e.g., networkidle).
  3. Scroll to the bottom in steps, waiting a short time after each step.
  4. Scroll back to the top.
  5. Take the screenshot.

Common Problems and Misconceptions

A frequent misconception is that waitForLoadState('networkidle') waits for all images. It doesn’t—it waits for the network to be idle for 500ms, but lazy images may not have been requested yet. Another issue is that page.screenshot({ fullPage: true }) captures the entire scrollable area, but it still only loads images that are in the viewport at the time of capture. You must scroll first.

Another problem: some sites use IntersectionObserver to load images only when they’re a certain distance from the viewport. Scrolling too fast may skip the trigger. A slow, stepwise scroll is more reliable.

Alternatives to Scrolling

If scrolling is too slow or flaky, consider these alternatives:

  • Block lazy loading: Use Playwright’s page.route() to intercept image requests and force them to load, or use a browser extension that disables lazy loading.
  • Use a real browser profile: Some browsers have settings to disable lazy loading, but that’s not available in Playwright’s default context.
  • Take multiple screenshots: Capture each section after scrolling to it, then stitch them together—but that’s complex.
  • Use a different tool: If you only need a screenshot, a tool like Puppeteer has similar issues, but some headless browsers have flags to disable lazy loading (e.g., --disable-lazy-loading in Chrome). Playwright doesn’t expose that flag directly, but you can launch Chromium with it via launchOptions.args.

How to Choose the Right Method

Choose based on your goal:

  • If you need a full-page screenshot for documentation, use the scroll-and-wait approach.
  • If you’re testing that images load correctly, use page.waitForSelector() on each image and assert its natural width is greater than 0.
  • If you’re in a hurry and the page is simple, disabling lazy loading via addInitScript is fastest.

Always test your method on a few pages with different lazy-loading implementations, because some sites use custom JavaScript that may not respond to simple scrolling.

Frequently Asked Questions

Why doesn’t fullPage: true capture all images?

fullPage: true expands the viewport to the full page height, but it doesn’t trigger lazy loading for images that are below the initial viewport. The browser still only loads images that are “near” the viewport, and if the viewport is instantly resized to the full height, the browser may load all images because they’re all in the viewport—but that’s not guaranteed, especially with IntersectionObserver-based lazy loading.

Does waitForLoadState('networkidle') help?

It helps only if the lazy images are requested during the initial load. In most cases, they aren’t, so networkidle returns before those images are even requested. You need to scroll first, then wait for network idle again.

Can I disable lazy loading in Playwright?

Yes, you can inject a script that removes the loading attribute from all images before the page loads. Use page.addInitScript(() => { document.querySelectorAll('img').forEach(img => img.loading = 'eager'); }). This works for attribute-based lazy loading, but not for custom JavaScript lazy loading.

Why do some images load but others don’t?

Images that are in the initial viewport load normally. Images below the fold only load when you scroll. If you scroll too fast, the browser may skip some. Also, some images may have data-src instead of src, and the actual src is set by JavaScript when the image nears the viewport—your scroll must trigger that JavaScript.

Is this a bug in Playwright?

No, it’s expected browser behavior. Playwright is a browser automation tool, not a rendering engine. It faithfully reproduces what a real user would see. To capture lazy images, you must simulate user behavior (scrolling) or alter the page.

What about iframes with lazy loading?

Iframes can also have loading="lazy". The same principles apply: you need to scroll the iframe into view and wait for it to load. Playwright can handle iframes with frameLocator(), but you still need to scroll the parent page to bring the iframe into view.

Related

Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.

More cards in this drawer

The whole Numbers and Powers drawer →