How to Capture a Screenshot of a Webpage That Requires Login with Selenium
Log in with Selenium, wait for a page-specific sign of success, and save the authenticated page as a screenshot. Includes runnable Python, troubleshooting, and an API alternative.
To capture a page that requires login, use one Selenium WebDriver session for both the site’s normal login flow and the screenshot. After logging in, open the protected page, wait for a page-specific element that proves the authenticated content is ready, and call driver.save_screenshot("screenshot.png"). The example below uses Python and Chrome; replace its URL, selectors, and any site-specific authentication steps with values for an account you are authorized to use.
1. Install Selenium and prepare credentials
Install the Selenium Python package and a compatible browser. Selenium Manager can manage drivers for supported browsers when you create a driver in current Selenium releases; consult the Selenium documentation for setup details. Do not commit credentials to a repository or print them in logs. Supply them through environment variables or your deployment’s secret manager.
python -m pip install selenium
Set LOGIN_URL, TARGET_URL, LOGIN_USER, and LOGIN_PASSWORD in your environment. The selectors below are examples, not universal locators: inspect the target site’s authorized login page to identify the right fields and submit control.
2. Log in, confirm the protected page, and capture it
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 = os.environ["LOGIN_URL"]
TARGET_URL = os.environ["TARGET_URL"]
USERNAME = os.environ["LOGIN_USER"]
PASSWORD = os.environ["LOGIN_PASSWORD"]
OUTPUT = Path("screenshot.png")
options = webdriver.ChromeOptions()
# Uncomment for a headless run, such as in CI:
# options.add_argument("--headless=new")
# Set a deliberate viewport so captures are consistent.
options.add_argument("--window-size=1440,1000")
driver = webdriver.Chrome(options=options)
try:
wait = WebDriverWait(driver, 20)
driver.get(LOGIN_URL)
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()
# If the site requires MFA, complete its authorized flow here and wait
# for the next step or authenticated page before continuing.
driver.get(TARGET_URL)
# Choose a locator that appears only when the expected protected content
# has loaded, not a generic element shared with the login or error page.
wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, "main h1")))
if not driver.save_screenshot(str(OUTPUT)):
raise RuntimeError("WebDriver did not save the screenshot")
print(f"Saved {OUTPUT.resolve()}")
finally:
driver.quit()
The try/finally closes the browser even if navigation, waiting, or capture fails. The screenshot is a PNG of the current browser window. The main h1 wait is only an example: use a distinctive element or condition that establishes the correct account and page are visible.
3. Make the wait prove the right page is ready
A navigation call completing does not necessarily mean a JavaScript-rendered page is finished. Selenium’s navigation waits for a document readiness state, but scripts can update the page afterward. Use an explicit wait for content that matters to the capture. Selenium describes explicit waits as a way to wait for a specific condition, and cautions that mixing implicit and explicit waits can cause unpredictable timing. See Selenium’s waiting strategies.
- Wait for visibility when the screenshot needs an element displayed on screen.
- Wait for presence when the element may exist in the DOM but be offscreen or hidden.
- Wait for a changed URL if successful authentication reliably redirects to a known route.
- Wait for a specific account marker if the page must be captured under a particular user or tenant.
A generic selector such as body is usually a weak success condition: login pages, error pages, and the desired page all have a body. If the target app shows a loading indicator, wait for it to disappear and for the desired content to appear. Avoid a fixed sleep as the main synchronization method; it can be too short on slow runs and waste time on fast ones.
4. Choose the screenshot scope
| Need | Approach | Notes |
|---|---|---|
| Visible browser window | driver.save_screenshot("screenshot.png") |
Captures the current window viewport as PNG. |
| One component | Locate the element and call its screenshot method. |
Useful for a chart, card, or panel without surrounding content. |
| Entire long page | Use a browser/driver-specific full-page technique or capture in sections. | The basic WebDriver screenshot method does not promise a portable full-page capture across browsers. Verify behavior for the chosen browser and driver. |
For an element image, after the authenticated page is ready:
panel = driver.find_element(By.CSS_SELECTOR, "main .report-panel")
panel.screenshot("report-panel.png")
Element screenshots depend on the element being present and capturable. If it is outside the viewport or covered, scroll it into view and wait until it is visible before taking the shot.
5. Handle MFA and session state correctly
Some sites require an authenticator code, a security key, an approval prompt, or another challenge after the password step. Implement the site’s authorized flow for the account and environment. Do not assume that submitting a username and password completes login, and do not try to bypass a site’s authentication controls. For unattended capture, use an account and authentication method approved by the site owner and your organization.
The simplest reliable approach is to complete authentication in the active WebDriver session, then navigate to the protected page in that same session. Selenium exposes cookie APIs, but copying or injecting a session cookie is site-specific: domain, path, expiry, security attributes, and the site’s authentication design all matter. Cookie reuse is not a universal login shortcut. Prefer the normal login flow unless the application explicitly provides a supported session mechanism for automation.
6. cURL, Python, and Node.js alternatives
cURL does not control a browser or execute the site’s JavaScript application. It can save a page response, but that is not equivalent to a rendered browser screenshot. Python Selenium is the browser-based approach shown above. Node.js can use Selenium’s JavaScript bindings for a similar flow; install the package and configure a compatible browser and driver as described in the Selenium WebDriver documentation.
npm install selenium-webdriver
const { Builder, By, until } = require('selenium-webdriver');
const chrome = require('selenium-webdriver/chrome');
async function capture() {
const options = new chrome.Options().windowSize({ width: 1440, height: 1000 });
// For headless CI, configure the browser's headless option as appropriate.
const driver = await new Builder().forBrowser('chrome').setChromeOptions(options).build();
try {
const loginUrl = process.env.LOGIN_URL;
const targetUrl = process.env.TARGET_URL;
if (!loginUrl || !targetUrl || !process.env.LOGIN_USER || !process.env.LOGIN_PASSWORD) {
throw new Error('Set LOGIN_URL, TARGET_URL, LOGIN_USER, and LOGIN_PASSWORD');
}
await driver.get(loginUrl);
await driver.wait(until.elementLocated(By.name('username')), 20000);
await driver.findElement(By.name('username')).sendKeys(process.env.LOGIN_USER);
await driver.findElement(By.name('password')).sendKeys(process.env.LOGIN_PASSWORD);
await driver.findElement(By.css("button[type='submit']")).click();
// Complete any authorized MFA step required by the site here.
await driver.get(targetUrl);
await driver.wait(until.elementIsVisible(
await driver.findElement(By.css('main h1'))
), 20000);
const png = await driver.takeScreenshot();
require('node:fs').writeFileSync('screenshot.png', png, 'base64');
} finally {
await driver.quit();
}
}
capture().catch((error) => {
console.error(error.message);
process.exitCode = 1;
});
For a command-line capture service, an API may be simpler than provisioning a browser and maintaining its session workflow. ScreenshotNeo’s one-call screenshot API accepts a URL and returns an image or PDF; it is intended for pages that can be reached by the capture service, so a private page still requires an access method supported by the site and product configuration. Review the ScreenshotNeo API documentation for supported request options and authentication behavior.
7. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
NoSuchElementException on a login field |
The locator is wrong, the form has not rendered, or the form is inside an iframe. | Inspect the authorized page’s DOM, wait for the field, and switch into the correct iframe if applicable. |
TimeoutException waiting for the protected-page marker |
Login failed, MFA is pending, the selector is wrong, or the page is still loading. | Check the current URL and visible page state, complete the site’s required step, and use a marker specific to the expected page. |
| Screenshot shows the login page | The session was not authenticated, a redirect returned to login, or the target page was opened in a different browser session. | Keep login and capture in the same driver, inspect redirects, and wait for an authenticated-page marker before capturing. |
| Screenshot is blank or incomplete | Capture happened before client-side rendering, content is lazy-loaded, or the viewport does not include the desired region. | Wait for the content itself, scroll to trigger lazy loading where appropriate, and choose a suitable viewport or element screenshot. |
| Browser fails to start | Browser installation, driver compatibility, permissions, or headless dependencies are missing. | Check Selenium’s setup guidance, install a supported browser, and review the driver startup error for the missing dependency. |
| Works locally but fails in CI | Different browser environment, smaller viewport, missing secrets, network restrictions, or MFA behavior. | Set secrets in CI, use a deliberate viewport, configure the CI browser environment, and use an approved automation authentication flow. |
| Cookie injection does not preserve login | Cookie scope or security attributes do not match, or authentication uses more than a cookie. | Use the site’s normal flow or its documented automation/session mechanism; do not treat arbitrary cookie copying as portable. |
8. Performance, reliability, and cost
Browser startup and page rendering are usually the main time costs in a Selenium capture. Reuse one driver for a batch of pages when they share an authorized session, but isolate jobs whose accounts or permissions differ. Wait on the specific content needed rather than using a long fixed delay. Set a finite wait timeout, save output to an explicit path, and always quit the driver so failed jobs do not leave browser processes behind.
For repeatable results, pin the browser/runtime environment used in automation, set a consistent viewport, and make the success condition explicit. A screenshot can still vary when the page content changes, network resources fail, or the site serves different account data. Selenium itself is software; its documented screenshot method does not establish a hosted per-shot price. Account for the compute and maintenance cost of running browsers in your environment.
Or skip the browser setup
If the page is publicly reachable, ScreenshotNeo can return a screenshot with one GET request. This example captures a public page; see the API docs for request options and access requirements.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
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 removes cookie banners, popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents use screenshot tools. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Learn more at ScreenshotNeo, then sign up for 1,000 free screenshots a month with no card.
FAQ
Does Selenium save screenshots as PNG?
Yes. driver.save_screenshot("screenshot.png") saves a PNG of the current window. Selenium also documents screenshot capture for individual elements.
Can Selenium capture a page after MFA?
Yes, if the authorized automation flow completes the site’s MFA requirements in the same WebDriver session before navigating to and capturing the protected page.
Will document.readyState tell me the page is ready?
Not necessarily. Client-side scripts can change the page after the document readiness state. Wait for a meaningful element or state tied to the content you need.
Can I use this to capture any private URL?
No. The browser session must have legitimate access to the page, and the site’s authentication and automation rules apply.


