ScreenshotNeo

BlogHow-to

How to Capture a Full-Page Screenshot of a Web App After Login with Selenium

Log in with Selenium, wait for the app’s authenticated state, and capture the full document. This guide covers Firefox, Chromium, session handling, and troubleshooting.

By the ScreenshotNeo team4 October 202611 min read

To capture a full-page screenshot of a web app after login, complete the login in a WebDriver session, wait for a reliable signal that the app is authenticated and ready, then capture from that same session. In Selenium’s documented Python API, Firefox has explicit full-page screenshot methods. Selenium’s generic screenshot call and Chromium approaches do not guarantee the same full-document behavior in every browser and version.

The examples below use Python with Firefox for the most direct documented full-page API. They also show a Chromium-specific Chrome DevTools Protocol (CDP) option, plus cURL, Python, and Node.js calls to ScreenshotNeo when you do not need to exercise the app’s own login flow. Selenium source links are included alongside the relevant code.

1. Choose a capture method that matches your browser

Browser and binding Full-page approach What to account for
Firefox with Python Use Firefox’s documented get_full_page_screenshot_as_file() or related methods. Keep Selenium, Firefox, and geckodriver compatible; confirm the output in your environment.
Firefox with Java Selenium provides the HasFullPageScreenshot interface. The interface is marked Beta. Check your pinned Selenium version and driver availability.
Chrome or Chromium Use a CDP-based capture if you need full-document output from Chromium. CDP support is version-sensitive and Selenium describes it as temporary and unstable.
JavaScript binding, generic screenshot Use the binding’s screenshot call only after checking its behavior in your exact setup. Selenium documents a best-effort preference order that may return the current window, visible frame, or display rather than the whole document.

Sources: Selenium Firefox guide, Python Firefox WebDriver API, Java full-page screenshot interface, Selenium CDP guide, and JavaScript WebDriver API.

2. Install the Python dependencies

Use a current Selenium release and Firefox. Selenium Manager can help resolve drivers for supported setups, but the browser and driver still need to work together. Pin versions in repeatable CI environments and verify compatibility when upgrading.

python -m pip install selenium

Set credentials through environment variables so they do not live in source code. The example expects the app to have a username and password form; adapt its selectors, login route, and authenticated-state selector to your application.

3. Log in and capture the whole page with Firefox

This runnable pattern drives the normal login form, waits for an authenticated dashboard element, optionally scrolls to trigger lazy content, and saves a PNG from the same Firefox session. Replace the example URL and selectors with values from your app.

import os
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

LOGIN_URL = "https://app.example.com/login"
USERNAME = os.environ["APP_USERNAME"]
PASSWORD = os.environ["APP_PASSWORD"]
OUTPUT = Path("full-page.png")

options = webdriver.FirefoxOptions()
# For CI without a visible desktop, enable headless mode:
options.add_argument("-headless")

driver = webdriver.Firefox(options=options)
try:
    driver.set_window_size(1440, 1000)
    driver.get(LOGIN_URL)
    wait = WebDriverWait(driver, 30)

    wait.until(EC.visibility_of_element_located((By.NAME, "username"))).send_keys(USERNAME)
    driver.find_element(By.NAME, "password").send_keys(PASSWORD)
    driver.find_element(By.CSS_SELECTOR, "button[type='submit']").click()

    # Use an application-specific signal that only appears after authentication.
    wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, "[data-testid='dashboard']")))

    # Optional: scroll in steps if the app loads lower sections only when scrolled into view.
    page_height = driver.execute_script("return document.documentElement.scrollHeight")
    viewport_height = driver.execute_script("return window.innerHeight")
    for y in range(0, page_height, max(1, viewport_height - 100)):
        driver.execute_script("window.scrollTo(0, arguments[0])", y)
        # Replace this short pause with an explicit wait if scrolling triggers a known load signal.
        driver.implicitly_wait(0)
    driver.execute_script("window.scrollTo(0, 0)")

    saved = driver.get_full_page_screenshot_as_file(str(OUTPUT))
    if not saved:
        raise RuntimeError(f"Firefox did not save screenshot to {OUTPUT}")
    print(f"Saved {OUTPUT.resolve()}")
finally:
    driver.quit()

Firefox’s Python API also documents save_full_page_screenshot(path), get_full_page_screenshot_as_png(), and get_full_page_screenshot_as_base64(). See the Firefox WebDriver API for the exact methods in your installed Selenium release.

Improve the lazy-content wait

The optional loop requests each part of the page by scrolling, but scrolling alone does not prove that deferred requests completed. If the app exposes a loading indicator or a section-specific element, wait on it after each scroll. For example:

driver.execute_script("window.scrollTo(0, arguments[0])", y)
wait.until(EC.invisibility_of_element_located((By.CSS_SELECTOR, ".section-loading")))
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, f"[data-section='{section_id}']")))

Replace those selectors with real application signals. Avoid arbitrary sleeps when an observable condition is available. Selenium’s get() waits for the page load event, but a modern app can continue rendering after that event.

4. Keep authentication in the same browser context

The simplest workflow is to log in through the app, then capture with the same driver. The authenticated state belongs to that browser session and browsing context. Do not close the driver, start a separate browser, or switch to a fresh profile between login and capture.

Selenium also has cookie-management APIs, but copying one cookie is not necessarily equivalent to logging in. An app may rely on multiple cookies, local storage, server-side session state, CSRF state, or identity-provider redirects. If your test setup deliberately injects a test cookie, first visit a page on the matching domain, add only the test-safe cookie with its required attributes, then visit the protected route and verify that the authenticated page actually loaded. Selenium documents that cookies are scoped to the current browsing context and that the driver must first be on a domain valid for the cookie: Selenium cookie interactions.

Keep session secrets out of source control, logs, and screenshot artifacts. Prefer a dedicated test account and the application’s documented test-authentication fixture where one exists.

5. Chromium option: capture through CDP

For Chrome or Chromium, a CDP command can request a capture beyond the visible viewport. This example logs in with Selenium Python, waits for the authenticated state, gets the document dimensions, and asks Chrome’s Page domain for a capture. It uses a data URL response and writes the decoded PNG bytes.

import base64
import os

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

options = webdriver.ChromeOptions()
options.add_argument("--headless=new")
driver = webdriver.Chrome(options=options)
try:
    driver.set_window_size(1440, 1000)
    driver.get("https://app.example.com/login")
    wait = WebDriverWait(driver, 30)
    wait.until(EC.visibility_of_element_located((By.NAME, "username"))).send_keys(os.environ["APP_USERNAME"])
    driver.find_element(By.NAME, "password").send_keys(os.environ["APP_PASSWORD"])
    driver.find_element(By.CSS_SELECTOR, "button[type='submit']").click()
    wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, "[data-testid='dashboard']")))

    metrics = driver.execute_cdp_cmd("Page.getLayoutMetrics", {})
    size = metrics["contentSize"]
    result = driver.execute_cdp_cmd("Page.captureScreenshot", {
        "format": "png",
        "captureBeyondViewport": True,
        "clip": {
            "x": 0,
            "y": 0,
            "width": size["width"],
            "height": size["height"],
            "scale": 1
        }
    })
    with open("full-page.png", "wb") as image:
        image.write(base64.b64decode(result["data"]))
finally:
    driver.quit()

This route is tied to Chrome DevTools Protocol behavior. Selenium says its CDP support is temporary until WebDriver BiDi is implemented, is not designed for testing or stability, and depends heavily on browser versions. Treat this as version-specific infrastructure: pin and verify Selenium, Chrome, and the corresponding DevTools behavior, and re-check after upgrades. See Selenium’s CDP documentation.

6. Make the capture represent the page you intend

  • Wait for authenticated state: use a dashboard heading, account menu, route change, or app-ready marker that cannot appear on the login screen.
  • Wait for content: the page load event may precede client-side data rendering. Wait for the key content or for a known loading state to finish.
  • Trigger lazy loading: scroll where the app loads images or sections on demand, then wait for those sections to appear.
  • Choose a viewport: set a consistent window size before login and capture. Responsive layouts can change page height and content.
  • Inspect the artifact: confirm the top and bottom are present, there is no login redirect, and sticky elements or lazy sections look as expected.
  • Watch for sensitive information: authenticated pages may include personal or account data. Store the output where access is appropriate.

A full-document screenshot method captures the document area available to the browser; it does not guarantee that every app-specific deferred request has completed or that every visual element will be laid out as desired.

7. Java and JavaScript notes

Java with Firefox

Selenium’s Java API has a HasFullPageScreenshot interface, and the documentation lists FirefoxDriver as an implementing class. The interface is marked Beta, so check the API available in your pinned version before relying on it. The relevant method is getFullPageScreenshotAs(OutputType.FILE); then move or copy the returned file to the desired path.

Reference: Selenium Java HasFullPageScreenshot API.

JavaScript

Do not assume a generic JavaScript WebDriver screenshot is full-page. Selenium describes its screenshot API as best-effort and may choose a current window, visible frame, or display. Check your binding and browser documentation, then verify the actual image dimensions and content. For Chromium, a CDP implementation is browser-version-specific; Selenium’s CDP stability warning applies.

Reference: Selenium JavaScript WebDriver API.

8. Or skip the browser setup

If you need a website capture without reproducing an authenticated session, ScreenshotNeo can return an image or PDF from one GET request. For private pages that require an interactive login, use Selenium in the same authenticated browser session as described above. ScreenshotNeo’s request options include custom headers, cookies, and Authorization, but use them only when the target site’s access method is appropriate for that request.

ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, with response headers indicating the page verdict and billing status. Its MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000.

See the ScreenshotNeo API documentation. Replace the target URL as needed:

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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', new Uint8Array(await res.arrayBuffer()));

The API supports PNG, JPEG, WebP, and PDF output, full-page capture, selector-based element capture, custom CSS and JavaScript, waits, viewport and device settings, request blocking, caching, async jobs, bulk capture, signed image links, and a usage API. See the docs for supported parameters and response details. Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.

9. Troubleshooting

Symptom Likely cause Fix
The screenshot shows the login page. The login did not complete, the session was lost, or the readiness condition matched too early. Wait for an authenticated-only element or expected route, and capture in the same driver context that logged in.
The screenshot only contains the viewport. The selected API captures a window or visible region, or the binding/browser combination lacks full-page support. Use Firefox’s documented full-page API, or a version-matched Chromium CDP method; inspect the resulting dimensions.
Firefox reports that the full-page method is missing. The installed Selenium binding or browser driver does not expose the expected API/version combination. Check the Python API for the installed Selenium release and align the pinned Selenium, Firefox, and geckodriver versions.
CDP command fails or output is clipped. Browser and Selenium CDP behavior differ by version; page metrics or clipping dimensions may be stale. Check compatibility, read dimensions immediately before capture, and re-verify after upgrades.
Lower sections or images are blank. Content is lazy-loaded and was not requested before capture, or the app’s data load had not finished. Scroll to trigger loading and wait for application-specific content/image readiness markers before taking the screenshot.
Cookie injection does not authenticate. The cookie is for another domain or authentication depends on additional state. Navigate to the correct domain first, use the required test-safe cookie attributes, and verify whether the app also needs local storage, CSRF, or redirect state.
Capture is inconsistent in CI. Browser/driver versions, viewport, fonts, or app readiness vary between runs. Pin browser and Selenium versions, set a consistent viewport, and wait on observable application conditions.
Page is unusually tall or capture fails. The document exceeds practical browser image limits or contains an expanding layout. Check the document dimensions, wait for layout to settle, or capture meaningful sections separately when one image is impractical.

10. Performance, reliability, and cost

For self-hosted Selenium, the dominant work is opening the app, completing authentication, waiting for application data, and rendering the document. Full-page images can consume substantial memory for very tall pages; keep the viewport and output format appropriate to the task, and avoid capturing pages that expand without bound. There is no universal capture-time or success-rate figure: page weight, authentication, browser version, and application behavior vary.

For reliability, use explicit waits around login and content, pin browser/driver versions where reproducibility matters, and inspect a sample artifact after changes to the app or browser stack. Selenium’s documented CDP approach carries more upgrade sensitivity than Firefox’s explicit Python full-page API. Selenium itself has no per-screenshot API fee; infrastructure and browser execution costs depend on where and how you run it.

For ScreenshotNeo, check the published plan limits and response headers for your usage. The stated plans are Free: 1,000 shots/month; Starter: $5 for 3,000; Growth: $15 for 15,000; Pro: $39 for 60,000; Scale: $99 for 250,000; Business: $249 for 1,000,000. Yearly billing gives two months free, and every feature is on every plan. Only clean shots are billed; inspect X-Page-Verdict and X-Billed in responses.

11. FAQ

Does Selenium’s get() wait until a single-page app is ready?

It waits for the page load event, but app rendering or data requests can continue afterward. Wait for an application-specific readiness condition.

Does Firefox full-page capture log in for me?

No. It captures the current browser state. Complete login and verify the authenticated page first.

Is Chrome CDP a stable cross-browser API?

No. It is Chromium-specific and Selenium documents CDP support as temporary and version-dependent.

Can I capture a private dashboard with ScreenshotNeo?

The API accepts custom headers, cookies, and Authorization, but an interactive or multi-step login may require the authenticated Selenium session. Use the approach that matches the site’s authentication design.