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

To capture lazy-loaded content with Selenium, scroll the page—or the nested element that actually scrolls—until the content appears, wait for a page-specific condition, then take the screenshot. A successful navigation or document.readyState value alone does not prove that JavaScript-driven content has finished loading.

Why a Selenium screenshot can miss lazy-loaded content

Selenium navigation waits are tied to document readiness. That can happen before a single-page application has fetched and rendered its later content. Selenium’s Browser Options documentation notes that ready state does not necessarily mean the page has finished loading, particularly when JavaScript loads content dynamically after the ready state returns complete.

Lazy-loading behavior is controlled by the site. It may load images or items when they approach the viewport, and the trigger may depend on the browser window or an inner scroll container. Selenium lets you run JavaScript in the selected frame or window, but it cannot infer which element the site uses or when the site considers loading complete.

Reliable workflow

  1. Open the page and identify the target. Choose a selector for the content that must appear, or identify a meaningful loading indicator or item count.
  2. Find the scrolling target. Determine whether scrolling the window or a nested container reveals the content. Scroll the element that owns the relevant overflow when the page uses an inner scroller.
  3. Scroll in increments. Repeatedly move through the page or container to trigger deferred loading. Avoid assuming that one jump to the bottom will trigger every site’s loading logic.
  4. Wait for evidence of readiness. After scrolling, wait for the target to become visible or present, for an item count to increase, for an image to finish loading, or for the site’s loading indicator to disappear. Selenium explicit waits poll a condition until it becomes true or the wait expires.
  5. Stop deliberately. On an infinite-scroll page, stop when the target item is present, a known page boundary is reached, or repeated scrolls produce no new content. Set a maximum number of iterations so the script cannot scroll forever.
  6. Capture and inspect. Take the page or element screenshot appropriate to your goal, then verify that the image contains the expected items and is not clipped.

Python example: scroll, wait for the target, and save a screenshot

This runnable pattern uses Selenium 4 with Chrome and a site-specific CSS selector. Replace the URL and TARGET_SELECTOR with values from the page you are automating. The example scrolls the window in increments and stops when the target is visible. It intentionally does not claim to handle every infinite-scroll design: use the nested-container variant below if the page’s content scrolls inside an element.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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
from selenium.common.exceptions import TimeoutException

URL = "https://example.com/page"
TARGET_SELECTOR = "#content-loaded-below-the-fold"

options = webdriver.ChromeOptions()
# Uncomment to run without opening a visible browser window:
# options.add_argument("--headless=new")

driver = webdriver.Chrome(options=options)
wait = WebDriverWait(driver, 10)

try:
    driver.get(URL)
    # Wait for the initial structure, then the page-specific content condition.
    wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "body")))

    max_scrolls = 30
    for _ in range(max_scrolls):
        target = driver.find_elements(By.CSS_SELECTOR, TARGET_SELECTOR)
        if target and target[0].is_displayed():
            break

        driver.execute_script("window.scrollBy(0, Math.max(window.innerHeight * 0.8, 400));")
        try:
            wait.until(
                lambda d: (
                    (lambda elements: bool(elements) and elements[0].is_displayed())(
                        d.find_elements(By.CSS_SELECTOR, TARGET_SELECTOR)
                    )
                )
                or d.execute_script(
                    "return window.scrollY + window.innerHeight >= document.documentElement.scrollHeight;"
                )
            )
        except TimeoutException:
            # No useful page change within the wait period; the loop limit still applies.
            pass
    else:
        raise TimeoutException(f"Target did not appear after {max_scrolls} scrolls")

    wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, TARGET_SELECTOR)))
    driver.save_screenshot("page.png")
finally:
    driver.quit()

The example uses an explicit wait around observable page conditions rather than treating navigation completion as the capture signal. The scroll loop has a maximum count; adjust the wait duration and count for the site and network conditions. If reaching the document bottom is itself the goal, test that stopping condition on the target page—some infinite-scroll pages keep extending the document.

Scroll a nested container instead

If the page has an inner scrolling panel, replace the window scroll call with a script that scrolls that element. For example, given a container selector such as .results-panel:

container = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, ".results-panel")))
driver.execute_script(
    "arguments[0].scrollTop += Math.max(arguments[0].clientHeight * 0.8, 300);",
    container,
)

Repeat this operation in the loop and wait for the target content or another meaningful change inside the panel. A window-bottom check is not a valid stop condition for an inner scroller; compare the container’s scrollTop, clientHeight, and scrollHeight if you need a boundary test.

Wait for an image to finish loading

When the target is an image, wait for the image element’s complete property and nonzero natural width after it has been brought into view. The condition is specific to image loading and does not prove that unrelated page content has settled:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
image = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "img.lazy-image")))
driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", image)
wait.until(lambda d: d.execute_script(
    "return arguments[0].complete && arguments[0].naturalWidth > 0;", image
))

Choose page or element capture intentionally

driver.save_screenshot(...) captures the current browsing context. An element screenshot, such as element.screenshot("element.png"), captures the visible region bounded by that element’s rectangle; do not assume it captures an element’s entire off-screen contents or the whole document.

Full-page screenshots are not uniformly supported by every language binding and browser-driver combination. Check the API for your binding and verify the result with the driver you deploy. Selenium’s Ruby screenshot API, for example, documents full-page capture as dependent on support. If you need a particular viewport, make it explicit through the browser window size before capture; if you need a long page, confirm that the resulting image includes content outside the initial viewport.

Rank #4
The Web Testing Handbook
  • Used Book in Good Condition

Common problems and fixes

  • The screenshot is taken too early: Navigation returned, but the app has not rendered the target. Wait for the target, a changed count, a completed image, or the disappearance of a loading indicator.
  • Scrolling does not load anything: You may be scrolling the window while a nested panel owns scrolling. Inspect the page and send scroll commands to the correct element.
  • The target selector never matches: Confirm that the selector exists in the current frame and matches the rendered DOM. If the content is inside an iframe, switch into that frame before searching and executing page-context scripts.
  • The wait times out intermittently: The site or network may be slower than the chosen timeout, or the condition may not represent the page’s actual completion state. Increase the timeout judiciously and choose a condition tied to the desired content rather than adding only a fixed sleep.
  • An infinite-scroll loop runs too long: Add a maximum iteration count and stop on the target item, a known boundary, or a stable item count across repeated attempts. A universal stopping rule cannot be inferred from Selenium alone.
  • The image is present but blank: Presence in the DOM does not establish that image data loaded. Scroll it into view and wait for its load state, then inspect the saved screenshot.
  • The result is clipped or only shows the viewport: Check whether you used a visible-element screenshot, a current-context screenshot, or a driver-supported full-page method. These scopes are not interchangeable.
  • An asynchronous script never completes: Selenium’s asynchronous script API requires the script to signal completion through its callback. For ordinary scrolling and polling, synchronous scripts plus explicit waits are often simpler.
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 you only need a screenshot rather than Selenium-controlled interaction, ScreenshotNeo is a website screenshot API and MCP server. Its screenshot API can capture a URL as an image or PDF. For a basic capture, use the following cURL request; see the ScreenshotNeo API documentation for request options and response details.

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

ScreenshotNeo accepts cookie or consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response identifies the page verdict and billing status in headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents and other 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 the free plan.

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

Frequently Asked Questions

Can I use a fixed sleep after scrolling?

You can, but it may be too short on a slow load and unnecessarily long on a fast one. An explicit wait tied to the target page condition is generally more reliable.

Does Selenium’s page-load wait wait for lazy-loaded content?

Not necessarily. It waits for document readiness, which does not guarantee that JavaScript-driven content has appeared.

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.