How to Screenshot a Logged-In Page Using Selenium and a Chrome User Data Directory
Reuse a dedicated Chrome profile with Selenium, verify the page is authenticated, and save a screenshot. Includes setup, troubleshooting, and an API alternative.
To screenshot a logged-in page with Selenium and Chrome, pass Chrome a dedicated profile directory with --user-data-dir=..., open the target page, wait for an element that proves the user is authenticated, then save the current window as a PNG. The profile supplies browser state; it does not guarantee the site’s session is still valid.
Runnable Python example
Install Selenium with python -m pip install selenium. Replace the profile and output paths with absolute paths writable by the machine running Chrome. Replace the example URL and authentication selector with values for your site.
from pathlib import Path
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import TimeoutException
profile_dir = Path("/absolute/path/to/selenium-chrome-profile")
screenshot_path = Path("/absolute/path/to/output/page.png")
screenshot_path.parent.mkdir(parents=True, exist_ok=True)
options = webdriver.ChromeOptions()
options.add_argument(f"--user-data-dir={profile_dir}")
driver = webdriver.Chrome(options=options)
try:
driver.get("https://example.com/account")
# Replace this with a stable element visible only when authenticated.
WebDriverWait(driver, 15).until(
lambda d: d.find_element("css selector", "[data-authenticated]")
)
saved = driver.save_screenshot(str(screenshot_path))
if not saved:
raise OSError(f"Could not write screenshot to {screenshot_path}")
finally:
driver.quit()
Selenium’s Python API documents save_screenshot() as saving the current window to a PNG file and returning a boolean success value. This example checks that value and always closes the browser, including when navigation, waiting, or writing fails. Selenium Python WebDriver API
Set up and use the Chrome profile
- Choose a profile directory. Use a dedicated directory for automation. It must be writable by the Chrome process. Selenium’s Chrome options accept Chrome arguments, including
--user-data-dir=.... Selenium Chrome documentation - Authenticate through an allowed flow. Sign in to the site in the profile using its normal login flow, or establish the profile state using an approved test setup. The first run may open a browser that requires an interactive login. Do not assume the profile path alone logs in.
- Use the same profile on later runs. Pass the same directory to Chrome, then navigate to the protected page. The site may expire or revoke the session, require additional security checks, or bind authentication to conditions that have changed.
- Verify authentication before capture. Wait for a page element unique to the authenticated view, such as an account navigation item. Also consider detecting a login URL or login form and reporting that authentication failed, rather than saving a misleading screenshot.
- Save to a writable path and check success. Use a PNG filename and create its parent directory before calling
save_screenshot(). - Close the driver. Call
quit()in afinallyblock so the browser process is closed if any step raises an exception.
Selenium documents Chrome’s --user-data-dir argument. Its JavaScript Chrome driver documentation says new sessions use a clean profile unless configured through Options. These establish how profile selection works; they do not promise that a particular site’s authentication will remain valid. Selenium JavaScript Chrome driver API
Choose a reliable authentication check
The example selector [data-authenticated] is a placeholder, not a universal locator. Choose a selector that is stable and only present after sign-in. Prefer an account-specific heading, profile control, or authenticated navigation element over a generic page container that also appears to logged-out visitors.
If the site redirects to login, the authenticated selector will not appear and Selenium will raise a timeout. You can make that failure clearer by checking the URL or a login form after the wait expires:
try:
WebDriverWait(driver, 15).until(
lambda d: d.find_element("css selector", "[data-authenticated]")
)
except TimeoutException as exc:
if "/login" in driver.current_url:
raise RuntimeError("The profile is not authenticated; the site redirected to login") from exc
raise RuntimeError("The authenticated page marker did not appear") from exc
Adjust the URL test and selectors for the site. Applications can render asynchronously after the document loads, so waiting for the application state is more useful than treating navigation completion as proof of login.
Profile directory versus cookies
| Approach | What it provides | Limit to account for |
|---|---|---|
| Chrome user data directory | Selects a browser profile when Chrome starts, which can include its stored browser state. | The site session may be expired or invalid in the current environment. Avoid concurrent runs that use the same profile directory. |
| WebDriver cookies | Lets a script inspect or add cookies for the current browser context. | Navigate to the cookie’s domain before adding it. Cookies may not be all the state a site requires for sign-in. |
Selenium notes that cookies are commonly used to recognize users and load stored information, and its cookie operations require the browser to be on a domain where a cookie is valid before adding it. Selenium: Working with cookies
A profile is often convenient when the site’s normal browser login state is already established. Cookie operations can be useful for controlled test setups, but are not automatically a complete replacement for a profile or the site’s authentication flow.
Screenshot scope and page size
save_screenshot() captures the current window. It should not be described as a full-document screenshot: content below the visible browser viewport may not be included. If you need a viewport image, set the browser window dimensions for the intended viewport before capture. If you need the entire long page, use a separately documented and verified full-page method for your browser and Selenium setup.
For example, a viewport size can be configured through Chrome’s window sizing API after starting the driver:
driver.set_window_size(1440, 1000)
Window sizing can vary with the environment and browser chrome. For consistent capture dimensions, verify the resulting screenshot in the same local, container, or remote environment where the job will run.
Compatibility and deployment
Selenium’s Chrome documentation states that Selenium 4 is compatible with Chrome v75 and greater, and that the Chrome and ChromeDriver major versions should match. The documentation page was last modified July 17, 2026; check the actual browser and driver in your runtime rather than assuming a local setup matches a container or managed browser. Selenium Chrome documentation
- Local WebDriver: the profile path is on the machine that runs Chrome.
- Remote WebDriver or Grid: Chrome runs on the remote browser host or container. A path that exists only on the Python client is not automatically available to that browser. Configure and verify profile access on the browser host.
- Parallel runs: give simultaneous browser jobs separate profile directories. A shared active profile can prevent reliable startup or create competing state.
- Permissions: ensure the Chrome process can read and write the profile directory and the script can write the screenshot output.
Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| The page is logged out | The wrong profile directory was selected, the session expired, or the site requires another login step. | Check the exact absolute path, sign in through an allowed flow, inspect redirects, and wait for a site-specific authenticated element. |
| Chrome will not start with the profile | Another Chrome process may be using the same directory, or the process cannot access it. | Close the other process, use a dedicated directory for this run, and check directory permissions. Use separate directories for concurrent jobs. |
| The screenshot file is missing | The output parent directory does not exist, the path is not writable, or the API returned failure. | Create the parent directory, use an absolute writable path, and check the boolean result from save_screenshot(). |
| The screenshot is blank or captures the login screen | Authentication was not established, or the app had not rendered its authenticated state when capture ran. | Check the current URL and login state, then wait for a stable authenticated element before taking the screenshot. |
| Some page content is missing | The documented method captures the current window, not necessarily the full document; dynamic content may also not be ready. | Set an appropriate viewport and wait for the relevant content. Use a verified full-page approach if the whole document is required. |
| Driver startup reports a version problem | Chrome and ChromeDriver major versions do not match, or the runtime uses unexpected binaries. | Inspect versions in the environment running Chrome and align their major versions, following Selenium’s Chrome guidance. |
| The wait times out | The selector is only an example, the page redirected, or the application has not reached the expected state. | Use a site-specific stable selector, inspect the URL and page state, and choose a timeout appropriate to the app and environment. |
Performance, reliability, and cost
- Startup and capture time: starting Chrome, loading the page, and waiting for the authenticated state are separate costs in elapsed time. Reuse a profile when the workflow requires its browser state, but keep each concurrent run’s profile isolated.
- Reliability: treat authentication as an explicit precondition and fail if the expected logged-in marker is absent. Always close the driver, and verify the screenshot write result.
- Storage and access: a user data directory contains browser profile state. Keep it in an access-controlled location appropriate to the account and environment, and avoid copying it into logs or artifacts.
- Cost: Selenium, Chrome, and the compute environment have no price specified by the cited research. Operational cost depends on where and how long the browser runs. ScreenshotNeo’s plan prices and billing behavior are listed in its product section below.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server for developers from Yorker Media. Its one-call API can return an image or PDF; see the ScreenshotNeo API documentation for options. For a publicly reachable page, a request looks like this:
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}`);
ScreenshotNeo accepts cookie and consent banners like 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 and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and each response identifies the page verdict and billing status in headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots.
Sign up free for 1,000 screenshots a month, with no card required.
FAQ
Does --user-data-dir guarantee that I stay logged in?
No. It selects a Chrome profile. The site can expire or revoke its session or require additional checks, so verify the authenticated page state on every run.
Can I use my normal desktop Chrome profile?
A dedicated automation profile is easier to manage. Avoid running automation and desktop Chrome against the same active profile directory at the same time.
Does save_screenshot() capture the entire page?
It captures the current window. Use a separately verified full-page method if you need content beyond that window.
Will the example work unchanged with a remote Grid?
The profile directory must be accessible to the machine or container running Chrome. A local client path alone does not establish that access.


