How to Check Whether an Image Appears on a Webpage with Selenium
Use Selenium visibility plus complete and naturalWidth checks to verify that an image element appears and loaded successfully.

Use an explicit wait for the image, then combine Selenium visibility with the HTML image properties complete and naturalWidth > 0. Visibility alone means Selenium considers the element displayed; it does not prove that the image file loaded.
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
image = WebDriverWait(driver, 10).until(
EC.visibility_of_element_located((By.CSS_SELECTOR, "img.hero"))
)
loaded = driver.execute_script(
"return arguments[0].complete && arguments[0].naturalWidth > 0",
image,
)
assert loaded, "The hero image is visible but its image data did not load"
Replace img.hero with a selector that matches the page under test. The explicit wait handles pages where JavaScript adds or reveals the image after navigation.
What the assertion actually proves
| Check | What it establishes | What it does not establish |
|---|---|---|
| Presence | Selenium found a matching DOM element. | That it is visible or that image data loaded. |
| Visibility | Selenium considers the element displayed, with a rendered size greater than zero. | That the network request succeeded or that the correct pixels are shown. |
complete |
The browser finished the image loading process. | That loading succeeded; a failed request can also complete. |
naturalWidth > 0 |
The browser has nonzero intrinsic image width. | That the image is unobscured, correct, or visible to a user. |
| Visibility + both properties | A standard <img> is displayed and has usable image data. |
That overlays are absent or the image content is semantically correct. |
Selenium describes displayedness as an approximation of whether an element is displayed. Its Python visibility condition requires DOM presence and nonzero width and height. MDN documents that naturalWidth can be zero when intrinsic image data is unavailable. See the Selenium element information documentation, Selenium waiting strategies, Python expected-conditions API, and MDN naturalWidth reference.
Complete Python example
This example opens a page, waits for an image to become visible, checks that image data loaded, and reports useful diagnostics.

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
URL = "https://example.com/gallery"
SELECTOR = "img.hero"
options = webdriver.ChromeOptions()
options.add_argument("--headless=new")
driver = webdriver.Chrome(options=options)
try:
driver.get(URL)
image = WebDriverWait(driver, 15).until(
EC.visibility_of_element_located((By.CSS_SELECTOR, SELECTOR))
)
loaded, natural_width, natural_height, current_src = driver.execute_script(
"""
const img = arguments[0];
return [img.complete, img.naturalWidth, img.naturalHeight, img.currentSrc];
""",
image,
)
assert loaded and natural_width > 0, (
f"Image did not load: complete={loaded}, "
f"naturalWidth={natural_width}, src={current_src}"
)
print(f"Image appears and loaded: {current_src} ({natural_width}x{natural_height})")
finally:
driver.quit()
Choose the check that matches the requirement
- Element exists: use
presence_of_element_located. - Element is rendered: use
visibility_of_element_located. - Image bytes loaded: check
complete && naturalWidth > 0. - Loaded image appears: combine the visibility wait and the load predicate.
# Presence only
image = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "img.hero"))
)
# Visibility only
image = WebDriverWait(driver, 10).until(
EC.visibility_of_element_located((By.CSS_SELECTOR, "img.hero"))
)
# Load state only, after locating the element
loaded = driver.execute_script(
"return arguments[0].complete && arguments[0].naturalWidth > 0",
image,
)
Waiting for dynamic and lazy-loaded images
Navigation normally waits for a configured document readyState; the default strategy waits for complete. That does not mean a JavaScript application has finished inserting images or that lazy-loaded assets have entered the viewport. Use an explicit wait for the condition your test needs.
Wait for an image to load after it is present
from selenium.webdriver.support.ui import WebDriverWait
image = WebDriverWait(driver, 15).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "img.lazy"))
)
WebDriverWait(driver, 15).until(
lambda d: d.execute_script(
"return arguments[0].complete && arguments[0].naturalWidth > 0",
image,
)
)
assert image.is_displayed()
Trigger viewport-based lazy loading
driver.execute_script(
"arguments[0].scrollIntoView({block: 'center'});", image
)
WebDriverWait(driver, 15).until(
lambda d: d.execute_script(
"return arguments[0].complete && arguments[0].naturalWidth > 0",
image,
)
)
Wait for a specific source
expected_src = "https://cdn.example.com/hero.webp"
WebDriverWait(driver, 15).until(
lambda d: d.execute_script(
"return arguments[0].currentSrc === arguments[1] && "
"arguments[0].complete && arguments[0].naturalWidth > 0",
image,
expected_src,
)
)
Selectors and responsive images
Prefer stable attributes such as data-testid or an accessible component hook over generated class names.

SELECTOR = 'img[data-testid="product-image"]'
For responsive images, currentSrc is the resource selected by the browser from srcset and sizes. Assert currentSrc when the selected asset matters; do not assume the value of the src attribute is the fetched URL.
Images that are not standard HTML img elements
CSS background images
naturalWidth applies to HTMLImageElement, not a CSS background. Check the computed style and, if required, verify the relevant element’s dimensions.
background = driver.find_element(By.CSS_SELECTOR, ".hero")
url = driver.execute_script(
"return getComputedStyle(arguments[0]).backgroundImage;",
background,
)
assert url and url != "none"
assert background.is_displayed()
This confirms that a background declaration exists. It does not independently prove that the referenced file loaded; use browser network instrumentation or an application-specific readiness signal when that distinction matters.
SVG, canvas, and CSS-generated content
Inline SVG is part of the DOM and should be tested with SVG-specific selectors or a component-level readiness condition. A canvas has no naturalWidth; assert its dimensions or application state. An image produced by a pseudo-element requires a computed-style or visual assertion tied to that component.
When visibility is not enough
Selenium can report an image as displayed even when another element covers it, the wrong image is shown, or the image is clipped. Add an assertion for the page behavior that matters:
- Check a stable
alt,src, orcurrentSrcvalue. - Check the element’s bounding rectangle for expected dimensions.
- Use a screenshot comparison when pixel-level correctness is the requirement.
- Check for overlays or consent dialogs that can obscure the image.
JavaScript Selenium equivalent
const { Builder, By, until } = require('selenium-webdriver');
(async function checkImage() {
const driver = await new Builder().forBrowser('chrome').build();
try {
await driver.get('https://example.com/gallery');
const image = await driver.wait(
until.elementIsVisible(
await driver.findElement(By.css('img.hero'))
),
15000
);
const loaded = await driver.executeScript(
'return arguments[0].complete && arguments[0].naturalWidth > 0',
image
);
if (!loaded) throw new Error('Image is visible but did not load');
} finally {
await driver.quit();
}
})();
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
TimeoutException while waiting for visibility |
Selector is wrong, the image is inserted later, or it remains hidden. | Verify the selector in DevTools, wait for presence first, scroll to trigger lazy loading, or wait for the component’s ready state. |
Element is found but naturalWidth is zero |
Request failed, URL is invalid, image data is blocked, or loading has not finished. | Wait on complete && naturalWidth > 0, then log currentSrc and inspect browser/network errors. |
complete is true but the test fails |
A failed image request can still be complete. | Always require positive naturalWidth. |
| Test passes although the wrong image is displayed | The assertion checks only that some image loaded. | Assert src, currentSrc, dimensions, or application-specific metadata. |
| Image is loaded but appears covered | A modal, cookie banner, or other overlay is on top. | Dismiss the overlay or assert unobscured state separately. |
| Works locally but fails in CI | Different viewport, timing, browser version, network, or headless behavior. | Set a deterministic window size, use explicit waits, capture diagnostics, and avoid fixed sleeps. |
Background image cannot be checked with naturalWidth |
It is CSS, not an <img>. |
Inspect computed style and use a CSS or component-specific readiness assertion. |
Reliability and performance practices
- Use explicit waits with a timeout appropriate for the environment; fixed sleeps make tests slower and less predictable.
- Keep selectors specific enough to identify the intended image but stable across presentation changes.
- Record
currentSrc, dimensions, and a screenshot on failure to make remote failures diagnosable. - Run the smallest assertion needed: presence is cheaper than a full visual comparison.
- Use a deterministic viewport when responsive image selection affects the result.
- Do not treat a successful page navigation as proof that every script-driven image is ready.
Or skip the browser setup
If your goal is to obtain a page image rather than drive a browser test, ScreenshotNeo provides a single screenshot request. Its API can capture PNG, JPEG, WebP, or PDF, and it supports element capture, waits, custom JavaScript and CSS, device settings, headers, cookies, blocking rules, caching, asynchronous jobs, and bulk capture. See the ScreenshotNeo API documentation.
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)
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}`);
Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed; response headers identify the page verdict and billing result. ScreenshotNeo also has an MCP server so Claude, Cursor, and other MCP clients can take screenshots. The Free plan includes 1,000 screenshots per month with no card, and paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
FAQ
Does is_displayed() prove that an image loaded?
No. It reports Selenium’s displayedness calculation. Add complete and naturalWidth > 0 for a standard HTML image.
Why use an explicit wait if Selenium waits for page load?
Page-load completion does not guarantee that JavaScript-driven or lazy-loaded content has settled.
Can I use naturalWidth for a CSS background?
No. It is an HTML image property. Inspect the computed background style and use a page-specific readiness check.
How do I prove the image is the correct one?
Assert a stable URL, currentSrc, accessible metadata, dimensions, or a visual comparison according to the test’s purpose.


