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

If Selenium saves the same element image on every loop iteration, make sure each iteration actually changes the page or target, wait for that change to finish, locate the current element again, and save it to a different filename. A changing Python variable by itself does not change the browser. The example below captures a different visible .item element on each pass and avoids reusing an element object or output path.

A reliable pattern for capturing each element

This example assumes the page is already open in driver and that the elements you want to capture match .item. It re-finds the target inside the loop, scrolls it into view, and assigns each image a distinct numbered path.

from pathlib import Path

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

wait = WebDriverWait(driver, 10)
out = Path("screenshots")
out.mkdir(parents=True, exist_ok=True)

# Count the matching items, but do not keep these WebElements for later use.
item_count = len(driver.find_elements(By.CSS_SELECTOR, ".item"))

for index in range(item_count):
    # Re-locate on each pass. CSS nth-of-type is one-based.
    selector = f".item:nth-of-type({index + 1})"
    current = wait.until(
        EC.visibility_of_element_located((By.CSS_SELECTOR, selector))
    )

    driver.execute_script(
        "arguments[0].scrollIntoView({block: 'center'});", current
    )
    path = out / f"item-{index:03d}.png"
    current.screenshot(str(path))
    print(index, current.text, path)

The selector shown is an illustration, not a guarantee that every page’s markup uses matching siblings in that exact arrangement. If the page has a stable identifier, such as a unique data-id, prefer that over a positional selector. If the contents change between iterations, update the selector or perform the interaction that selects the next item before locating it.

First identify what is repeating

There are two different problems that look alike: Selenium can capture the same browser state repeatedly, or it can capture different states but overwrite the same file. Log enough information immediately before saving to tell which one is happening.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
print({
    "index": index,
    "url": driver.current_url,
    "target_text": current.text,
    "target_id": current.get_attribute("data-id"),
    "path": str(path),
})

Use an attribute that actually distinguishes the targets on your page; data-id is only an example. If the URL, target text or identifier never changes, the browser has not advanced to a new target. If those values change but the directory contains only one image or its contents seem old, inspect the generated paths and screenshot scope.

Check the browser state

  • Does the loop click a next button, change a tab, navigate to a detail page, open a modal, or otherwise select a new item?
  • Does that action use the current index or a distinct target? Incrementing index without using it in the interaction or locator has no effect on the page.
  • Does the URL, a heading, visible text or another page-specific signal change before the capture?

Check the output path

Print the full path for every pass. Include an index or a stable item identifier in the filename, and check that different values produce different paths. screenshot() writes to the path you pass it; a loop that reuses one name can replace the preceding image even when the page state changed.

Use the right capture scope

Choose the capture method that matches what you intend to save:

  • current.screenshot(str(path)) captures the located element.
  • driver.save_screenshot(str(path)) captures the current browser window.

The window method does not mean “capture the current loop item.” It captures the window as it looks at that moment. For a window screenshot, keep the unique-path and synchronization checks, and call driver.save_screenshot(str(path)) after the browser has reached the intended state. For an element screenshot, locate the intended element in the current DOM and call its screenshot() method.

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

For pages that change on each iteration

If each pass opens a detail page, advances pagination, refreshes the page or replaces a component, the capture must happen after the transition—not merely after the click command returns. Selenium’s waiting-strategies documentation describes explicit waits as polling for a specified condition before continuing. Choose a condition that proves the particular transition you need.

Wait for a URL or visible content

For navigation, wait for the expected URL or a page-specific heading. If a heading or label changes for each item, waiting for the expected text is more meaningful than waiting an arbitrary number of seconds. For example, after you have performed the action that should open the next item:

from selenium.webdriver.support import expected_conditions as EC

wait.until(EC.url_contains("/detail/"))
heading = wait.until(
    EC.visibility_of_element_located((By.CSS_SELECTOR, "h1"))
)
print("Current detail:", heading.text)
# Locate the element to capture now, after the transition.

url_contains is appropriate only if the destination URLs share a useful substring. In a loop, a generic condition can become true too early if it was already true before the action. Prefer waiting for the expected next URL, changed heading text, or other value specific to that iteration.

Wait for a replacement element

When a JavaScript framework removes an old node and inserts a new one, an earlier Selenium element reference may no longer point to anything attached to the DOM. Selenium documents this condition as a stale element reference; refreshes and framework-driven replacement are examples. Keep the old reference only when it helps prove replacement, wait for it to become stale, and then find the new target.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
old_element = driver.find_element(By.CSS_SELECTOR, ".item")
# Perform the action that replaces this item.
wait.until(EC.staleness_of(old_element))
new_element = wait.until(
    EC.visibility_of_element_located((By.CSS_SELECTOR, ".item"))
)
new_element.screenshot("screenshots/replaced-item.png")

Do not use staleness_of if the page updates an element in place and keeps the same DOM node. In that case, wait for a changed text value, attribute, URL, or another observable result of the action.

Wait for an actionable control before clicking

If the loop has to click a control, a wait for clickability can establish that it is visible and enabled before interaction. It does not, by itself, prove that the following page transition has finished; use a second wait for the resulting state before capturing.

next_button = wait.until(
    EC.element_to_be_clickable((By.CSS_SELECTOR, "button.next"))
)
next_button.click()
# Add a wait for the specific resulting state here, such as the next
# heading text, URL, or old element becoming stale.

Expected conditions such as visibility, clickability and staleness are synchronization signals, not interchangeable delays. Choose the one that establishes the fact your next command depends on.

Make the locator reflect the loop target

A common cause is using find_element with the same selector each time. It returns one matching element, so repeating the same lookup often means repeatedly targeting the first match. There are three practical locator strategies:

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

Use a stable business identifier when available

If each item has a unique attribute, build the locator from the identifier for the current record. This is usually easier to verify than relying on the order of elements. Confirm the matched element’s identifier or text before saving, especially if the page can reorder or filter results.

Use a collection and an index for a static list

For a list that remains unchanged throughout capture, find_elements can return a collection and an index can select a member. But if navigation, refresh or a framework update replaces those nodes, the saved collection contains references from the old state. Re-query after such transitions rather than assuming the original objects remain valid.

Use a positional selector only when page structure supports it

A selector such as :nth-of-type() can address an item by position, but CSS positional selectors count according to the relevant element-type siblings. They may not correspond to the position in a broader collection if other element types or nested markup intervene. Test the locator against the page structure, then check the selected element’s distinguishing text or attribute before the screenshot.

Handle asynchronous content without timing guesses

Page navigation can return control to Python while JavaScript is still changing the page. A fixed time.sleep() can be too short on a slow response and waste time on a fast one. Prefer an explicit wait for an observable condition tied to the next step: visibility of the target, expected text, clickability of a control, a changed URL, or staleness of the old node.

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

Avoid mixing implicit and explicit waits. Selenium warns that combining them can produce unpredictable wait durations. If you use explicit waits in this workflow, keep synchronization based on explicit conditions rather than adding a global implicit delay and assuming the total timing will be straightforward.

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

Troubleshooting checklist

  • Every log line has the same URL and identifier: the loop is not changing browser state, or the interaction is not selecting the target associated with the index. Make the action or locator depend on the current item and verify the result before saving.
  • The text changes but the images do not: confirm that the output path changes and that you are opening the newly written files. Also confirm whether you intend an element capture or a window capture.
  • StaleElementReferenceException appears after a click or refresh: discard the old element reference after the DOM update. Wait for its staleness when appropriate, then locate the replacement.
  • The capture is blank or shows the prior item: the relevant content may not yet be visible or updated. Wait for a state-specific condition rather than assuming the click completed the rendering.
  • The first result is captured repeatedly: inspect whether the selector is invariant and whether it returns one match. Use a locator tied to the current item’s identifier or index, and verify the result’s text or attribute.
  • The expected target is not found: confirm the selector against the current DOM, whether the content has loaded, and whether it is inside an iframe. If it is, switch to the correct frame before locating it; switch back to default content before working on an unrelated page.
  • Images disappear or overwrite each other: print every output path, include an index or unique identifier, and check that the target directory is writable.
  • Scroll-triggered items are missing: ensure the scroll or other load-triggering action occurs before the wait and capture. Wait for the newly loaded item to become visible rather than relying on the loop continuing quickly enough.

Choose a synchronization and identity strategy

Situation Useful signal Locator choice Filename identity
Static list, no DOM replacement Target visibility Index in a verified collection or stable item attribute Index or item identifier
Navigation to detail pages Expected URL or page-specific heading Re-locate on the destination page Stable record identifier
Framework replaces a node Old node becomes stale, then new target is visible Fresh lookup after replacement Index or identifier for the new item
Control triggers a state change Clickability before action; changed text, URL or staleness after Locator for the intended control and then current target Value that distinguishes the selected state

The wait answers “when is it safe to proceed?” The locator answers “which element is this?” The path answers “which file belongs to this iteration?” A correct fix checks all three.

Or skip the browser setup

If you need screenshots from URLs rather than Selenium-controlled interactions, ScreenshotNeo is a website screenshot API and MCP server. A single request can return an image or PDF; its capture options include full-page and element screenshots, waits, custom CSS and JavaScript, and device settings. The Python example below saves a WebP response:

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)

See the ScreenshotNeo API documentation for request options. ScreenshotNeo accepts cookie or consent banners before capture and removes more than 60 known consent platforms, newsletter popups and chat widgets; those steps can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and responses identify page verdict and billing status in headers. Its MCP server provides take_screenshot, get_page_info and capture_pdf for Claude, Cursor and other MCP clients. The free plan includes 1,000 shots per month without a card; paid plans start at $5 for 3,000 shots. Sign up for ScreenshotNeo and get 1,000 free screenshots a month with no card.

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.

Operational notes for Selenium loops

For a long run, make failures diagnosable: log the iteration, target identifier, current URL and path, and stop or record an error when a required condition times out. This distinguishes a selector bug from a slow or failed transition. Keep output names deterministic if you want to resume or compare runs, but ensure repeated runs cannot silently overwrite files you need to retain.

Use a wait timeout that reflects how long the page is reasonably allowed to take, and make the timeout fail visibly rather than capturing an uncertain state. A wait is a maximum polling window, not proof that the page will always become ready. On timeout, inspect the URL and last observed target state, then decide whether to retry, skip that item, or abort; do not label a capture successful if the intended element never appeared.

Finally, locate elements as late as possible. Element objects represent particular nodes in a particular DOM state, not durable identities for business records. A locator plus a current item identifier is safer across pagination and rendering changes, and a unique path makes it possible to confirm that each successful iteration produced its own output.

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.

Free tools Windows power users keep installed

One-click scans. No signup required.

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