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 driver.save_screenshot() inside the loop, but make the workflow reliable by creating the output directory first, waiting for the exact page state you need, generating a different .png path for every iteration, and checking Selenium’s Boolean result. The example below works with Selenium’s Python API and avoids the most common causes of overwritten, blank, or misleading captures.

A reliable loop, end to end

This script navigates to each URL, waits for a meaningful element, saves an indexed PNG, and raises an error if Selenium reports an I/O failure. The generic body wait is only a baseline; replace it with an application-specific condition whenever possible.

from pathlib import Path
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

urls = [
    "https://example.com/",
    "https://www.selenium.dev/",
]

output_dir = Path("screenshots")
output_dir.mkdir(parents=True, exist_ok=True)

driver = webdriver.Chrome()
wait = WebDriverWait(driver, 15)

try:
    for index, url in enumerate(urls, start=1):
        driver.get(url)

        # Replace this with the element or state that defines readiness.
        wait.until(EC.presence_of_element_located((By.TAG_NAME, "body")))

        path = output_dir / f"page_{index:03}.png"
        if not driver.save_screenshot(str(path)):
            raise OSError(f"Selenium could not save screenshot: {path}")
        print(f"Saved {path}")
finally:
    driver.quit()

Selenium documents save_screenshot(filename) as saving the current window to a PNG file. It returns True unless an I/O error occurs, in which case it returns False (Selenium Python WebDriver API). The filename should end in .png and resolve to a path writable by the process.

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

Why each part matters

Create the directory before navigation

Selenium accepts a filename; it does not create missing parent folders for you. Path.mkdir(parents=True, exist_ok=True) creates the complete path and remains safe when the folder already exists. If your process runs in a container, CI worker, or service account, also verify that this runtime user can write there.

Use a unique filename per iteration

A constant name such as screenshot.png points every iteration at the same file. The next save replaces the previous image, leaving you with only the last page. An index with zero padding—page_001.png, page_002.png—sorts naturally and is stable across a run.

If multiple runs share one directory, put a run identifier in the directory name (for example, a timestamp generated by your program) or include it in each filename. Do not place raw URLs or page titles directly into filenames without sanitizing slashes, colons, query characters, and other filesystem-reserved symbols.

Wait for the state you intend to capture

driver.get() returning does not prove that a single-page application, chart, image, or API-backed component has finished rendering. WebDriverWait polls a condition until it succeeds or the timeout expires; Selenium’s documented default polling interval is 0.5 seconds (WebDriverWait API).

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

Choose a condition that represents the screenshot’s purpose:

  • Specific component: EC.visibility_of_element_located((By.CSS_SELECTOR, "main.dashboard")).
  • Text or status: wait for an element containing “Loaded” or for a loading indicator to become invisible.
  • Image or chart: wait for the component’s container and, if needed, verify its dimensions with a custom predicate.
  • Interaction result: click, submit, or select first, then wait for the changed URL, text, or DOM state.

A fixed time.sleep() can be useful for diagnosing a race, but it is usually less dependable than an explicit condition: it may be too short on a slow run and waste time on a fast one.

Choosing the capture scope

Current window screenshot

driver.save_screenshot() captures the current browser window or browsing context. It is appropriate when you want what the user currently sees, including the viewport and the active tab’s state. The Python driver method should not be described as a guaranteed full-page capture; viewport size, browser behavior, and binding support determine what appears.

One element only

When the target is a card, chart, or component, use Selenium’s element screenshot capability rather than cropping a full-window image. Selenium’s window documentation shows separate driver- and element-level screenshot approaches (Working with windows and tabs).

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
element = wait.until(
    EC.visibility_of_element_located((By.CSS_SELECTOR, "article"))
)
path = output_dir / f"article_{index:03}.png"
if not element.screenshot(str(path)):
    raise OSError(f"Could not save element screenshot: {path}")

Element capture is useful when browser chrome, surrounding navigation, or unrelated page content should not be included. Make sure the element is displayed and its layout has settled before saving.

Keep results instead of bytes (or work in memory)

Saving directly is simplest for archival jobs. If the next step uploads or transforms images, Selenium also exposes in-memory alternatives:

  • driver.get_screenshot_as_png() returns PNG bytes.
  • driver.get_screenshot_as_base64() returns a Base64 representation.

For example:

png_bytes = driver.get_screenshot_as_png()
with open(output_dir / f"raw_{index:03}.png", "wb") as image_file:
    image_file.write(png_bytes)

Use one output strategy consistently. A successful byte response does not itself prove that a later upload or filesystem write succeeded, so handle those errors separately.

Handling navigation and loop failures

Continue after one bad URL

For batch work, catch errors per URL, record the failure, and continue. Keep the index or a sanitized identifier in the log so a failed item can be retried without confusing its output with another page.

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

for index, url in enumerate(urls, start=1):
    try:
        driver.get(url)
        wait.until(EC.visibility_of_element_located((By.TAG_NAME, "body")))
        path = output_dir / f"page_{index:03}.png"
        if not driver.save_screenshot(str(path)):
            raise OSError("save_screenshot returned False")
    except Exception as exc:
        failures.append({"index": index, "url": url, "error": str(exc)})

if failures:
    for failure in failures:
        print(failure)

Whether to continue or fail fast depends on the job. A visual regression gate normally stops or marks the build failed; a monitoring batch may collect all failures and report them at the end.

Reset state between pages

Cookies, local storage, open tabs, modal dialogs, and in-page filters can carry state into later iterations. If each URL must be independent, use a fresh browser session, clear the relevant storage, or explicitly reset the application. If a page opens a new tab, switch to the intended window before waiting and saving.

Common symptoms, causes, and fixes

Symptom Likely cause Fix
Only one image remains Every iteration uses the same path. Add an index, run ID, or sanitized unique key to the filename.
No file is created Parent directory is missing, unwritable, or the API returned False. Create the directory, check permissions, and test the Boolean result explicitly.
Screenshot shows a spinner or empty panel Capture happened before meaningful content rendered. Wait for the application-specific element, text, or loading state.
Wrong tab or page is captured The loop navigated or opened windows without selecting the intended browsing context. Switch to the correct window handle and verify the URL or title before saving.
Timeout from WebDriverWait The expected condition never became true, the selector changed, or the page failed. Inspect the page, confirm the selector, increase the timeout only when justified, and capture diagnostics such as URL and HTML.
Image is mistaken for full-page output Driver screenshot covers the current window, not necessarily the entire document. Use a verified full-page technique for your browser/binding, or capture the required element and document the scope.
Local save works but remote execution does not In a grid or remote driver, the file may be written on the browser node rather than the test runner. Confirm the grid’s file-transfer behavior and retrieve artifacts through the environment’s supported mechanism.

Reliability and performance practices

  • Reuse the driver carefully: one session avoids startup overhead, but reset state when pages must be isolated.
  • Set a bounded wait: every explicit wait needs a timeout so a broken site cannot block the entire batch indefinitely.
  • Use deterministic names: map each URL to one predictable artifact and log the mapping.
  • Keep diagnostics: on failure, record the URL, current title, current URL, exception, and (when possible) a small HTML or screenshot artifact.
  • Separate navigation and capture errors: a timeout, a blocked page, and an I/O failure need different remedies.
  • Do not claim reliability from a fixed delay: no single sleep duration is universally correct, and the cited Selenium documentation provides no screenshot failure-rate benchmark.

The Selenium Python API reference identified for this guidance is version 4.49.0. Match examples to the binding installed in your environment and consult that binding’s API when behavior is version-sensitive (Python WebDriver source documentation).

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

Or skip the browser setup

If your goal is a clean image or PDF of a URL rather than browser-automation state, ScreenshotNeo provides a single HTTP request. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server lets Claude, Cursor, and other MCP clients call take_screenshot, get_page_info, and capture_pdf.

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

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,
)
r.raise_for_status()
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}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

See the parameter reference and options in the ScreenshotNeo documentation. Every plan includes features such as full-page capture with lazy images loaded, CSS-selector element capture, device presets, custom waits, headers and cookies, request blocking, PDF output, signed links, asynchronous jobs, bulk capture of up to 100 URLs per call, and a usage API. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Sign up free for ScreenshotNeo.

FAQ

What extension should a Selenium screenshot file use?

Use .png with save_screenshot(), as documented by Selenium’s Python API.

Can I save screenshots from a remote WebDriver?

Yes, but verify where the remote environment writes files and how it exposes them; local-path assumptions may not hold on a grid.

Is presence_of_element_located always enough?

No. Presence only confirms that a matching node exists. Visibility, text, a completed network-driven state, or another application-specific condition may be required.

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

Frequently Asked Questions

What extension should a Selenium screenshot file use?

Use .png with save_screenshot(), as documented by Selenium’s Python API.

Can I save screenshots from a remote WebDriver?

Yes, but verify where the remote environment writes files and how it exposes them; local-path assumptions may not hold on a grid.

Is presence_of_element_located always enough?

No. Presence only confirms that a matching node exists; the required condition depends on the page state you need.

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.