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

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Use a Java WebDriverWait with a JavaScript predicate that checks each current-document <img> for complete and naturalWidth > 0. This waits for the images present when the condition is checked to finish loading successfully; it does not, by itself, trigger offscreen lazy-loaded images or cover CSS backgrounds and other frames.

Wait for successful image loads with an explicit wait

Selenium’s page-load wait is not a guarantee that every image your test cares about is ready. A page can continue changing after navigation, and lazy-loaded images can still be pending after the browser fires its load event. An explicit wait lets the test poll for the condition it actually needs.

For successful loads of the current document’s <img> elements, use this Java condition:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(20));

Boolean imagesLoaded = wait.until(d -> (Boolean) ((JavascriptExecutor) d).executeScript(
    "return Array.from(document.images).every(img => img.complete && img.naturalWidth > 0);"
));

The timeout is an example you can tune for your application, not a recommended universal limit. The returned value is true when the predicate succeeds. If it does not succeed before the timeout, Selenium’s wait fails with a timeout exception.

Imports

import java.time.Duration;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.ui.WebDriverWait;

WebDriverWait takes the driver and a Duration, and its inherited until method polls the condition until it returns a value that is neither null nor false, or the timeout is reached. Selenium documents explicit waits as a way to specify the precise condition required at each point in a test.

What the predicate checks

  • document.images selects the <img> elements in the current document.
  • img.complete means that the image has completed loading or has reached a state where no image data is available. It does not prove the image loaded successfully.
  • img.naturalWidth > 0 excludes images with no usable intrinsic width, including broken images and images without a usable source.
  • Array.from(...).every(...) requires every image in the collection to satisfy both checks. If there are no images yet, the empty collection passes; it does not prove the application will not add images later.

Choose what “all images” means for your test

The predicate is intentionally scoped: it checks the current document’s image elements at the time the wait is evaluated. Before relying on it, decide which assets and which point in the application’s lifecycle your test needs to cover.

Images inserted by JavaScript

If the application adds image elements after the page initially renders, the predicate could pass before those elements exist. First wait for the component, result list, or other application state that inserts them; then wait for their images. If the relevant state and images can be expressed together, combine both checks in one predicate so the wait cannot finish between them.

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

For example, after the application exposes a component with a stable selector, wait for that component before checking images:

wait.until(d -> !d.findElements(By.cssSelector("#results img")).isEmpty());
wait.until(d -> (Boolean) ((JavascriptExecutor) d).executeScript(
    "return Array.from(document.querySelectorAll('#results img'))"
        + ".every(img => img.complete && img.naturalWidth > 0);"
));

This example checks only images inside #results. Replace the selector with one that identifies the component in your application. If the component can legitimately contain no images, use a different readiness condition rather than waiting for a non-empty image list.

Images that failed versus images that settled

The recommended predicate treats a failed image as a failure of the wait, because its intrinsic width is not positive. That is appropriate when the test requires usable image content. If the test only needs image requests to stop being pending, use img.complete alone and assert broken images separately:

Boolean imageRequestsSettled = wait.until(d -> (Boolean) ((JavascriptExecutor) d).executeScript(
    "return Array.from(document.images).every(img => img.complete);"
));

Boolean noBrokenImages = (Boolean) ((JavascriptExecutor) driver).executeScript(
    "return Array.from(document.images).every(img => img.naturalWidth > 0);"
);

These checks answer different questions: completion says the image is no longer pending, while the width check is a success criterion. Decide which outcome the test contract requires rather than treating the two as interchangeable.

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

CSS images and frames

document.images does not include images used as CSS backgrounds. Images inside an iframe belong to that frame’s document, not the top-level document. If either asset type matters, add a separate check for it: switch to the relevant frame before evaluating its document, and use an application-appropriate method for background assets. The sample predicate does not cover them.

Make lazy-loaded images load before waiting

Native lazy loading postpones fetching images until they approach the viewport. Consequently, an offscreen lazy image may not delay the window load event. A wait over document.images does not make the browser fetch an image that has not yet been requested; depending on the page, a deferred image may keep the condition false until it is requested, or the collection may pass before deferred content is added.

If your test needs images throughout a long page, scroll through the relevant content to bring lazy images near the viewport, then wait for the image condition. A simple approach is to advance down the page in increments and finish at the bottom:

JavascriptExecutor js = (JavascriptExecutor) driver;
long previousHeight = -1;
int maxScrolls = 100; // A safety limit; adjust for the page under test.

for (int i = 0; i < maxScrolls; i++) {
    long height = ((Number) js.executeScript(
        "return document.documentElement.scrollHeight;"
    )).longValue();
    long viewport = ((Number) js.executeScript(
        "return window.innerHeight;"
    )).longValue();

    if (height == 0 || viewport == 0) {
        break;
    }

    for (long y = 0; y < height; y += viewport) {
        js.executeScript("window.scrollTo(0, arguments[0]);", y);
    }
    js.executeScript("window.scrollTo(0, document.documentElement.scrollHeight);");

    long newHeight = ((Number) js.executeScript(
        "return document.documentElement.scrollHeight;"
    )).longValue();
    if (newHeight == previousHeight) {
        break;
    }
    previousHeight = newHeight;
}

wait.until(d -> (Boolean) ((JavascriptExecutor) d).executeScript(
    "return Array.from(document.images).every(img => img.complete && img.naturalWidth > 0);"
));

The loop is a practical trigger, not a universal lazy-loading solution. It uses a safety limit so a page that keeps growing cannot scroll indefinitely. If scrolling inserts more content or the page height changes, adapt the trigger to that application—for example, wait for the specific content region to finish expanding. A page-specific check is more reliable than assuming that one trip down the document covers every deferred image.

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.

Page-load strategy is not an image-ready condition

Selenium offers normal, eager, and none page-load strategies. The default, normal, waits for the document’s ready state to be complete; eager waits for it to be interactive; none does not block for a ready state. None of those settings expresses the test-specific condition that all required image elements loaded successfully. Ready-state completion also does not ensure that a JavaScript-driven application has finished inserting or updating content.

Use a page-load strategy to choose how navigation proceeds, then use an explicit wait for the application condition needed by the test. Avoid mixing implicit and explicit waits: Selenium warns that the interaction can make the resulting wait duration unpredictable.

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

Common failures and how to diagnose them

The wait times out although the page looks loaded

Inspect the images the predicate is checking. A broken image, an empty or missing source, or a lazy image that has not been requested can prevent the success predicate from passing. If failed requests should count as settled, wait on complete and make a separate assertion for broken images. If the image is lazy, trigger loading by scrolling the relevant content into view.

The wait succeeds but a later image is missing

The collection may have been empty or complete at the instant the predicate passed, while application code inserted another image afterward. Wait for the state that creates the images, or combine that state with the image condition. A short fixed sleep does not establish that the application has reached the required state.

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

Some visible page artwork is not checked

Confirm whether the artwork is an <img> in the current document. CSS backgrounds and images in child frames are outside this predicate. Handle the relevant frame separately or add a check suited to the asset type.

The wait behaves unpredictably

Check whether the test has an implicit wait configured as well as this explicit wait. Selenium cautions against combining the two because nested waiting can produce unpredictable elapsed times. Prefer explicit waits for the conditions that matter to this test.

Performance and reliability considerations

An explicit wait repeatedly evaluates its condition until it passes or times out. Keep the JavaScript predicate focused and avoid doing unrelated work in every poll. A check over every image has work proportional to the number of image elements, so for a page with many images or a test concerned with one component, query only that component rather than scanning the entire document.

Set the timeout to match the expected behavior of the application and the environment in which the test runs. A larger timeout can accommodate slower loads but also makes a genuine failure take longer to report; a smaller one gives quicker feedback but may fail when the application is still legitimately loading. The cited Selenium and browser documentation establishes no universal performance number for this wait.

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

Or skip the browser setup

If your goal is to obtain a page screenshot rather than assert Selenium test behavior, ScreenshotNeo offers a website screenshot API and MCP server. It is not a Selenium wait and does not replace assertions in a browser test. Its capture options include full-page screenshots with lazy images loaded, and its cleanup can accept cookie or consent banners and remove known consent platforms, newsletter popups, and chat widgets before the capture; each cleanup step can be turned off.

One GET request returns a screenshot or PDF. This cURL example saves a WebP capture of the specified page:

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

See the ScreenshotNeo API documentation for request options and setup. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed; response headers identify the page verdict and billed status. An MCP server exposes take_screenshot, get_page_info, and capture_pdf to AI agents and MCP clients. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.

Sign up for ScreenshotNeo’s free plan to get 1,000 screenshots a month with no card.

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

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.