QA How-To
Selenium element screenshot in Python (2026)
Master selenium element screenshot python APIs with pytest-ready PNG capture, stable waits, report attachments, artifact naming, image checks, and debugging.
24 min read | 2,604 words
TL;DR
Call element.screenshot('path.png') after the component reaches a stable visible state. For report integrations use element.screenshot_as_png or element.screenshot_as_base64, keep artifacts unique per pytest worker, and remember that saving an image is not the same as performing a visual comparison.
Key Takeaways
- Use WebElement.screenshot(path) for a saved PNG, screenshot_as_png for bytes, or screenshot_as_base64 for text transport.
- Create the destination directory first and check the boolean result when using screenshot(path).
- Wait for component-specific readiness such as skeleton removal, image decode, final text, and completed animation.
- Give each pytest worker and retry a collision-safe artifact path.
- Capture screenshots as focused failure evidence, and use a visual comparison workflow only when pixels are the assertion.
- Control sensitive data, report retention, viewport, browser build, fonts, locale, and dynamic content.
selenium element screenshot python code can save the pixels of one located WebElement without manually cropping a full-page image. Use element.screenshot(path) for a PNG file, element.screenshot_as_png for bytes, or element.screenshot_as_base64 for a Base64 string.
The capture call is easy. Making the result reliable in pytest requires deliberate readiness checks, artifact naming, failure-hook behavior, environment control, and privacy rules. This guide shows complete patterns that keep screenshot evidence useful instead of producing a folder of unexplained, flaky images.
TL;DR
from pathlib import Path
from selenium.webdriver.common.by import By
card = driver.find_element(By.CSS_SELECTOR, "[data-testid='order-card']")
target = Path("artifacts/screenshots/order-card.png")
target.parent.mkdir(parents=True, exist_ok=True)
if not card.screenshot(str(target)):
raise RuntimeError(f"Could not save element screenshot to {target}")
| Python API | Result | Good fit |
|---|---|---|
element.screenshot(path) |
bool and a PNG file |
Normal pytest or CI artifacts |
element.screenshot_as_png |
bytes |
Image libraries, hashing, direct uploads |
element.screenshot_as_base64 |
str |
Report APIs that accept encoded text |
driver.save_screenshot(path) |
bool and viewport PNG |
Surrounding failure context |
1. What selenium element screenshot python Records
The WebDriver element screenshot command captures the element's rendered rectangle in the current browsing context. It is different from driver.save_screenshot, which records the current top-level viewport. Element capture is ideal for a validation message, product tile, receipt, chart, form panel, or any component where surrounding pixels add noise.
The screenshot is PNG data. Supplying a filename ending in .jpg does not convert it to JPEG. Use .png, serve it with image/png, and preserve its bytes without text encoding. The element reference must be valid in the active document or frame. If a re-render makes it stale, locate it again after waiting for the final state.
A screenshot is evidence about rendering, not an automatic assertion. It cannot prove that a control has the correct accessible role, keyboard behavior, backend effect, or semantic text. Pair it with targeted WebDriver assertions. If you are designing the wider automation approach, Selenium vs Cypress provides context on runner and debugging tradeoffs.
2. Install Selenium and Build a Small Capture Helper
Create an isolated environment and pin versions through your project's dependency file. Selenium 4.45.0 is a concrete stable 2026 example. Selenium Manager normally finds or downloads a compatible local driver during webdriver.Chrome() startup, subject to environment and network policy.
python -m venv .venv
source .venv/bin/activate
python -m pip install "selenium==4.45.0" "pytest==8.4.1"
A helper should accept an already located element and a Path, create parent directories, check Selenium's boolean return value, and verify that a nonempty file exists. Keep capture separate from assertions so tests can decide when evidence is valuable.
from pathlib import Path
from selenium.webdriver.remote.webelement import WebElement
def save_element_png(element: WebElement, target: Path) -> Path:
target.parent.mkdir(parents=True, exist_ok=True)
saved = element.screenshot(str(target))
if not saved or not target.is_file() or target.stat().st_size == 0:
raise RuntimeError(f"Element screenshot was not saved: {target}")
return target
Do not catch every exception and return None. File permissions, stale elements, closed sessions, and driver errors need to be visible. A reporting hook may log a secondary capture failure, but a direct helper should give its caller a precise exception.
3. Take a Runnable selenium element screenshot python Example
This script fixes the browser window size, loads a public page, waits for its heading, saves the element PNG, and always quits the browser. Run it from the repository root so the relative artifact path is predictable.
from pathlib import Path
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
def main() -> None:
options = webdriver.ChromeOptions()
options.add_argument("--window-size=1440,1000")
driver = webdriver.Chrome(options=options)
try:
driver.get("https://www.selenium.dev/")
heading = WebDriverWait(driver, 10).until(
EC.visibility_of_element_located((By.CSS_SELECTOR, "main h1"))
)
path = save_element_png(
heading, Path("artifacts/screenshots/selenium-heading.png")
)
print(path.resolve())
finally:
driver.quit()
if __name__ == "__main__":
main()
visibility_of_element_located is enough for this static heading, but richer components require richer conditions. Never assume one universal wait makes all screenshots stable. The application state that matters should be named in the test or page component.
4. Wait for Fonts, Images, Data, and Animation
A visible card can still contain a loading skeleton, fallback font, undecoded image, animated total, or stale data. Wait for the signals that define its final state. For nested images, JavaScript can check complete and naturalWidth. For web fonts, document.fonts.ready is a promise, so an async script can wait within Selenium's script timeout.
from selenium.webdriver.support.ui import WebDriverWait
def wait_for_component(driver, locator, timeout: float = 10):
wait = WebDriverWait(driver, timeout)
element = wait.until(EC.visibility_of_element_located(locator))
wait.until(lambda d: d.execute_script(
"return Array.from(arguments[0].querySelectorAll('img'))"
".every(img => img.complete && img.naturalWidth > 0);",
element,
))
driver.set_script_timeout(timeout)
driver.execute_async_script(
"const done = arguments[arguments.length - 1];"
"document.fonts.ready.then(() => done(true), () => done(false));"
)
return element
card = wait_for_component(
driver, (By.CSS_SELECTOR, "[data-testid='profile-card']")
)
save_element_png(card, Path("artifacts/profile-card.png"))
Also wait for the component's loader to disappear and for expected data text to arrive. Font readiness across the document does not prove that a chart animation ended. Prefer a product signal such as data-render-state="complete" when the team can add one. A test hook should reflect real readiness, not skip required work.
Fixed sleeps are poor substitutes. A two-second delay penalizes every fast run and still loses when CI takes longer. If an animation has a known transition event, wait for the resulting state or disable motion through a documented test preference that users can also select, such as reduced motion.
5. Work With PNG Bytes and Base64 Safely
Use screenshot_as_png when the next step accepts binary data. The property returns bytes, which can be written with Path.write_bytes, passed to an image library, or uploaded with Content-Type: image/png. Use screenshot_as_base64 only when the receiving API requires a string.
import base64
from pathlib import Path
png: bytes = card.screenshot_as_png
assert png.startswith(b"\x89PNG\r\n\x1a\n")
Path("artifacts/profile-card-bytes.png").write_bytes(png)
encoded: str = card.screenshot_as_base64
decoded = base64.b64decode(encoded, validate=True)
assert decoded.startswith(b"\x89PNG")
The signature check confirms a PNG container, not correct pixels. Avoid assertions based on byte count or a raw hash in ordinary functional tests. Browser and font changes can produce different compression even when a person sees no meaningful regression. A raw hash is useful only when exact binary identity is truly expected in a tightly controlled renderer.
Base64 adds storage overhead and makes console logs unreadable. Keep it in memory only long enough to call the report API. Do not prefix data:image/png;base64, unless the consumer specifically expects a data URL, because Selenium's property returns the encoded content without that prefix.
6. Name Artifacts for Pytest Workers and Retries
Parallel pytest processes can execute the same test node at once across browsers or retries. A single failure.png is guaranteed to become ambiguous and may be overwritten. Build the path from sanitized node ID, browser, worker, and attempt or unique run identifier. Organize by CI run outside the function when the platform provides one.
import os
import re
from pathlib import Path
def artifact_path(nodeid: str, browser: str, label: str) -> Path:
safe_node = re.sub(r"[^a-zA-Z0-9._-]+", "-", nodeid).strip("-")
safe_label = re.sub(r"[^a-zA-Z0-9._-]+", "-", label).strip("-")
worker = os.getenv("PYTEST_XDIST_WORKER", "main")
return Path("artifacts", worker, browser, safe_node, f"{safe_label}.png")
Node IDs can be very long when parametrized values contain data. Truncate the human-readable part and append a short digest if your file system or report service has path limits. Do not put passwords, email addresses, or raw test data into filenames. Use an opaque case ID and store approved metadata separately.
A good label describes state, such as shipping-address-error, not chronology like step-12. When a retry runs, retain the original failure evidence with an attempt directory. Overwriting it with a passing retry destroys valuable flakiness information.
7. Integrate Focused Evidence With Pytest Failures
A generic pytest failure hook usually has access to the driver but not the exact failing element. It can capture the viewport. To capture a component, make the locator or capture callback available through a fixture, or save the element near the assertion. This explicit approach produces meaningful evidence and avoids guessing which node mattered.
def assert_element_text(element, expected: str, evidence: Path) -> None:
actual = element.text
if actual != expected:
capture_error = None
try:
save_element_png(element, evidence)
except Exception as exc:
capture_error = exc
message = f"expected {expected!r}, got {actual!r}"
if capture_error:
message += f"; screenshot failed: {capture_error}"
else:
message += f"; evidence: {evidence}"
raise AssertionError(message)
banner = driver.find_element(By.CSS_SELECTOR, "[role='alert']")
assert_element_text(
banner,
"Your card was declined",
Path("artifacts/payment-decline-banner.png"),
)
The code preserves the actual assertion even when capture fails. In a larger framework, attach the PNG through the reporting library and keep the path in structured metadata. Allure vs Extent Reports covers report selection at a higher level.
One focused image plus one viewport image is often enough. Capturing every element after failure increases noise and can trigger additional stale-element errors while the original page is already changing.
8. Capture Elements Inside Iframes and Shadow Roots
Selenium locates elements relative to the current browsing context. Switch to an iframe before finding and screenshotting its contents, then restore the default context in finally. An element obtained from the wrong context cannot be made valid by the screenshot method.
frame = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.ID, "payment-frame"))
)
driver.switch_to.frame(frame)
try:
form = WebDriverWait(driver, 10).until(
EC.visibility_of_element_located((By.CSS_SELECTOR, "form.checkout"))
)
save_element_png(form, Path("artifacts/payment-form.png"))
finally:
driver.switch_to.default_content()
For an open shadow root, locate the host, access host.shadow_root, then find the descendant in that root. Closed shadow roots are intentionally unavailable to normal page automation. Do not use unsupported JavaScript tricks to claim coverage of a state customers cannot access through the same interaction path.
If a component is inside a scrollable container, bring it to a stable position and wait for the scroll to settle. Sticky elements and scroll-driven animations can change pixels. Capture the current logical state rather than trying to create a stitched image of all hidden scroll content.
9. Scroll Without Creating an Artificial State
Element screenshot implementations can scroll a node into view, but explicit centering makes results easier to reason about when sticky headers or lazy rendering are involved. Use the DOM method with options, then wait until the element rectangle stops changing across short observations.
def center_and_wait(driver, element, timeout: float = 5.0):
driver.execute_script(
"arguments[0].scrollIntoView({block: 'center', inline: 'nearest'});",
element,
)
previous = None
stable_reads = 0
def rectangle_is_stable(_):
nonlocal previous, stable_reads
current = element.rect
stable_reads = stable_reads + 1 if current == previous else 0
previous = current
return stable_reads >= 2
WebDriverWait(driver, timeout, poll_frequency=0.1).until(rectangle_is_stable)
This is appropriate only when position is not itself the behavior under test. For a sticky-header defect, preserve the natural scroll sequence and capture the viewport too. Avoid changing CSS position, overflow, or display to force a clean element image. That creates an artificial DOM state and can conceal the real defect.
If scrolling triggers data loading, wait for that data and relocate the element because a frontend re-render may invalidate the original reference. A StaleElementReferenceException is a cue to re-evaluate lifecycle, not to retry screenshots indefinitely.
10. Decide Between Diagnostic Capture and Visual Regression
Most teams need screenshots to explain failures, not to compare every component pixel by pixel. Diagnostic capture has no baseline and should never decide pass or fail beyond whether evidence was produced. Visual regression testing compares a current image with a reviewed expected image under controlled conditions.
| Question | Functional assertion | Diagnostic screenshot | Visual comparison |
|---|---|---|---|
| Is the error text correct? | Primary | Helpful on failure | Usually unnecessary |
| Is the control keyboard usable? | Primary | Insufficient | Insufficient |
| Did the card layout shift? | Geometry can help | Helpful | Strong option |
| Did brand styling regress? | Limited | Helpful | Primary option |
| Is the API response valid? | Direct API or DOM result | Incidental | Wrong layer |
A visual workflow needs approved baselines, meaningful diff thresholds, masking rules, standardized rendering, artifact review, and ownership for updates. Install the same fonts, pin the browser image, set viewport and device scale, control locale and timezone, remove nondeterministic data, and wait for final rendering.
Do not set a huge tolerance until tests pass. Investigate differences and mask only regions outside the requirement. A screenshot library can produce a diff, but the team still needs a policy for whether the difference is intentional.
11. Protect Data and Control Storage Cost
Element capture is naturally narrower than a viewport image, which helps privacy, but the component may still contain customer names, addresses, balances, or test credentials. Prefer synthetic data. Do not capture secrets, and never publish screenshots from production sessions to ordinary CI artifacts. If test evidence is regulated, define encryption, access, retention, and deletion responsibilities with the same care used for logs.
Keep PNGs as artifacts rather than Base64 console output. Attach only failures by default, then add selected passing evidence where it supports an audit or release decision. Compressing PNGs after capture is acceptable if the pipeline preserves enough quality and does not silently change baseline inputs. Measure artifact volume before adding screenshots to every step.
Metadata should include test node ID, component label, browser version, viewport, operating system image, locale, timestamp, and attempt. Avoid full URLs containing tokens or personal query values. A good report makes the image searchable without putting sensitive details into its filename.
12. Troubleshoot Blank, Stale, or Cropped Captures
A blank image can mean the element had zero dimensions, a canvas had not painted, a child image had not decoded, the browser session had closed, or the file path was invalid. Log the element rectangle, displayed state, page route, browser version, and target path. Verify the eight-byte PNG signature to distinguish image data from a corrupt write.
A stale-element failure means the DOM node reference was replaced. Wait for the final application state and locate the component again. Do not wrap element.screenshot in an unlimited stale retry, because a continuously re-rendering component may never be stable and is itself useful evidence.
Cropping often reflects CSS overflow, transforms, or the element's actual bounding box. The command does not promise to include content intentionally outside that box. When overlap causes an interaction failure, diagnose the overlay with Selenium ElementClickInterceptedException causes and fixes. Capture the viewport as context instead of assuming the element PNG tells the entire story.
13. Verify Screenshot Artifacts in CI
A local PNG proves little if the CI pipeline never publishes it. Add a small artifact-health check that runs on failure or in one dedicated smoke test: save an element, confirm the file exists, validate its PNG signature, and verify the CI artifact rule includes the directory. This checks plumbing without turning byte details into a product assertion.
RemoteWebDriver returns screenshot data to the Python test process, so the target path belongs to that worker's file system. Upload from the worker-visible artifact directory. Do not reference a path inside a Selenium Grid node or browser container unless the provider explicitly exposes it. For distributed runners, give every worker a separate directory and let the CI platform merge artifacts without overwriting identical names.
A report attachment should include content type image/png, a component label, the failing test node ID, and browser metadata. Link to session video or trace when the provider supplies one, but keep the element image because it offers a fast, focused view. Verify retention and access policies in the pipeline configuration, especially when screenshots contain business data. Finally, test the failure path periodically. Reporting code that runs only during rare failures can silently break after a plugin, path, or permission change.
Interview Questions and Answers
Q: What Python API saves a screenshot of one WebElement?
Call element.screenshot(path). It writes a PNG and returns a boolean. Create the directory first and verify the result when artifact creation matters.
Q: How do screenshot_as_png and screenshot_as_base64 differ?
The first returns PNG bytes suited to binary upload or image processing. The second returns a Base64 string for text-based consumers. Base64 is larger and should not be dumped into console logs.
Q: Why can a visible element screenshot still be flaky?
Visibility does not guarantee that images decoded, fonts loaded, data settled, or animation completed. I wait for component-specific readiness and control the rendering environment.
Q: How do you capture an element inside an iframe?
Switch into the frame, locate and stabilize the element in that context, capture it, and restore default content in a finally block. The element reference is tied to its browsing context.
Q: Is a raw screenshot hash a good regression check?
Usually not. Harmless rendering or compression differences change the hash. A visual comparison system needs controlled inputs, a meaningful pixel algorithm, thresholds, baselines, and review.
Q: How should a pytest failure hook choose an element?
It should receive an explicit component reference, locator, or capture callback from the test fixture. Guessing from the exception is unreliable. A general hook can always capture the viewport for context.
Q: What screenshot metadata helps debugging?
Test node ID, component label, browser and OS version, viewport, locale, timestamp, worker, and attempt make the image traceable. Paths and metadata should not contain secrets or personal data.
Common Mistakes
- Calling
element.screenshotbefore creating the parent directory. - Ignoring its boolean return value and attaching a path that was never written.
- Using a
.jpgextension even though Selenium returned PNG data. - Sharing
failure.pngacross pytest workers and retries. - Waiting only for visibility while images, data, or fonts are still changing.
- Catching screenshot errors and accidentally hiding the original test assertion.
- Logging a Base64 image string, which bloats logs and can leak sensitive pixels.
- Treating capture as visual comparison without baselines, controls, or review.
Conclusion
A dependable selenium element screenshot python pipeline does six things: reaches the intended browsing context, waits for meaningful readiness, captures the right representation, saves to a collision-safe path, attaches useful metadata, and protects sensitive data. The Selenium API provides the PNG, while the test framework provides trust.
Add focused element evidence where it reduces investigation time, then pair it with a viewport capture only when context matters. If the release decision depends on pixels, establish controlled visual baselines and a review process rather than relying on file existence, byte length, or raw hashes.
Interview Questions and Answers
Which three element screenshot forms does Selenium Python provide?
WebElement.screenshot(path) saves a PNG and returns a boolean. screenshot_as_png returns bytes, and screenshot_as_base64 returns encoded text. I choose the form required by the artifact or report consumer.
How do you prevent screenshot collisions in pytest-xdist?
I include sanitized test node ID, browser, worker, attempt, and component label in the artifact path. I keep retries separate and organize them under the CI run so an earlier failure is not overwritten.
What waits are important before element capture?
I wait for visibility plus product-specific readiness, including skeleton disappearance, expected data, decoded images, loaded fonts, and completed motion where relevant. Fixed sleeps are not the readiness condition.
How do you preserve the original failure when screenshot capture also fails?
I keep capture in a secondary try block, record its error as additional diagnostic context, and raise or preserve the original assertion. Evidence collection must not replace the true stack trace.
How does screenshotting inside an iframe work?
The element must be located in the active frame context. I switch to the frame, wait and capture, then restore default content in finally. A reference from another context is not valid for the operation.
What is missing from a screenshot-only visual test?
It lacks a reviewed baseline, comparison logic, threshold and masking policy, deterministic environment, and an approval workflow for intended changes. Selenium provides pixels, not the visual verdict.
Why are narrow element captures useful for privacy?
They can exclude unrelated account and page information that a viewport capture would include. I still use synthetic data, avoid secrets, and enforce artifact access and retention because the component itself may contain sensitive content.
Frequently Asked Questions
How do I screenshot one element with Selenium Python?
Locate the WebElement, create the destination directory, and call element.screenshot with a .png path. Check its boolean result and confirm the file is nonempty when capture is required.
What does screenshot_as_png return in Selenium Python?
It returns the element screenshot as Python bytes containing PNG data. This is convenient for binary report uploads, image libraries, or writing through Path.write_bytes.
Why does element.screenshot return false?
The remote capture or local file write did not complete successfully. Check the path, directory, permissions, session state, element validity, and driver logs, and do not attach the missing path as evidence.
Can Selenium Python screenshot an element in an iframe?
Yes. Switch into the iframe, locate the element there, take the screenshot, and restore the default browsing context afterward.
How do I take screenshots on pytest failure?
Use a fixture or hook that owns the driver and captures the viewport. For a specific element, pass an explicit locator or capture callback near the assertion so the hook does not guess.
Are Selenium element screenshots always PNG?
Yes, the WebDriver element screenshot payload is PNG. Give files a .png extension and send binary attachments with image/png content type.
Can I compare element screenshots for visual regression?
Yes, but capture alone is not enough. Control the browser environment and dynamic content, maintain reviewed baselines, use a suitable comparison algorithm, and define masking and update policies.
Related Guides
- Selenium DevTools in Selenium 4 in Python (2026)
- How to Use Selenium DevTools in Selenium 4 in Java (2026)
- How to Use Selenium element screenshot in Java (2026)
- Selenium full page screenshot in Python (2026)
- How to Debug a failing test in VS Code in Selenium (2026)
- How to Run tests in headed mode in Selenium (2026)