How to Take a Website Screenshot with Selenium When Chrome Runs as a Non-Root User
Run headless Chrome as a non-root user, wait for the page you need, and save a verified PNG to a writable absolute path.
Use Selenium’s Chrome options to start headless Chrome, navigate to the page, wait until the content you need is ready, and call save_screenshot() with an absolute .png path in a directory writable by the non-root user. Check the method’s Boolean result. A properly configured container user does not need --no-sandbox just because it is non-root.
This guide uses Python. The Selenium API details are from the Selenium WebDriver API reference, and Chrome options and version guidance are in Selenium’s Chrome documentation.
1. Install Selenium and prepare a writable output directory
Install Selenium in the same Python environment that will run the capture script:
python -m pip install selenium
Choose a directory the runtime user can write to. The example uses /tmp/selenium-output; whether that path is suitable depends on your host or container. Create it and set its ownership or permissions as part of your image or deployment setup. Do not assume the working directory is writable.
Selenium’s current Chrome guidance says Selenium 4 supports Chrome 75 and later and that Chrome and ChromeDriver major versions must match. Selenium Manager can manage drivers in supported setups; otherwise, install a compatible ChromeDriver and make it available to the process. See the Chrome-specific Selenium documentation for the applicable setup details.
2. Capture the current browser window as a PNG
This complete example starts headless Chrome, waits for the document to finish loading, and saves the current browser window. If your page renders important content asynchronously, replace the document-ready wait with a wait for the specific content you need, as shown in the next section.
from pathlib import Path
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
url = "https://example.com"
output = Path("/tmp/selenium-output/page.png")
output.parent.mkdir(parents=True, exist_ok=True)
options = Options()
options.add_argument("--headless=new")
# Optional: set a deliberate browser window size for predictable dimensions.
options.add_argument("--window-size=1440,1000")
driver = webdriver.Chrome(options=options)
try:
driver.get(url)
WebDriverWait(driver, 30).until(
lambda browser: browser.execute_script(
"return document.readyState"
) == "complete"
)
saved = driver.save_screenshot(str(output))
if not saved:
raise OSError(f"Could not save screenshot to {output}")
if not output.is_file() or output.stat().st_size == 0:
raise OSError(f"Screenshot file is missing or empty: {output}")
finally:
driver.quit()
print(f"Saved screenshot to {output}")
save_screenshot(filename) captures the current window as a PNG. Selenium recommends a full path with a .png extension; the method returns False on an I/O error and True when it succeeds. The extra file check makes it easier to catch a missing or empty output in automated jobs.
3. Wait for the content you actually need
A completed document load does not necessarily mean a single-page app, lazy-loaded image, or client-rendered chart is ready. Prefer a condition tied to the target content over a fixed sleep. For example, wait for a product heading:
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
WebDriverWait(driver, 30).until(
EC.visibility_of_element_located((By.CSS_SELECTOR, "h1.product-title"))
)
Use a selector that indicates the visible state you need, not merely that an element exists if it can appear before it is populated. For a page with a known application-ready marker, wait for that marker. If the content appears after scrolling, scroll it into view or down the page and wait for the relevant images or components before capture. The screenshot method captures the current browser window; it does not by itself guarantee that every below-the-fold item has loaded.
4. Configure the capture for the intended result
| Need | What to configure | What to know |
|---|---|---|
| Headless browser | options.add_argument("--headless=new") |
Chrome’s headless documentation shows this option for Selenium. Headless mode does not change the output path behavior of Selenium’s screenshot API. |
| Stable visible area | options.add_argument("--window-size=1440,1000") |
Choose dimensions that match the viewport you need. Confirm the resulting capture in your target environment if exact layout dimensions matter. |
| Dynamic content | Wait for a meaningful selector or application state | A document load event may happen before asynchronous page content is ready. Avoid assuming one fixed delay works for every site. |
| PNG output | Use save_screenshot("/absolute/path/file.png") |
The parent directory must be writable by the process UID. Check the returned Boolean or inspect the file. |
| Full-page capture | Use a method specifically intended for full-page output in your chosen browser tooling | The Selenium Python API cited here documents a current-window screenshot. Full-page behavior varies by tool and binding; do not assume this call captures the entire document. |
For lower-level output, Selenium also exposes screenshot bytes with get_screenshot_as_png() and base64 with get_screenshot_as_base64(). Those are useful when another part of your program handles storage or transport. The file-saving method is simpler when the deliverable is a PNG on disk.
5. Run Chrome without treating no-sandbox as a default
Run the process under the intended non-root account and make sure that account has a valid writable home or temporary directory and permission to write the screenshot destination. Chrome’s FAQ says --no-sandbox is not needed when a user is properly set up in the container. It should not be added automatically merely because the process is non-root.
If Chrome still fails during startup, inspect the actual browser and driver error first. Confirm the Chrome and ChromeDriver major versions match, then investigate the container’s user setup, filesystem permissions, and runtime policy. The precise sandbox prerequisites can depend on the host and container configuration.
6. Diagnose common failures
| Symptom | Likely cause | Fix |
|---|---|---|
save_screenshot() returns False, or no file appears |
The destination is not writable, the parent directory does not exist, or the path is invalid. | Use an absolute path, create the parent directory, ensure the runtime UID can write there, and check the Boolean result and file existence. |
| Chrome fails to start or the session cannot be created | Chrome and ChromeDriver are incompatible, or the runtime environment cannot start Chrome. | Check the major versions first. Then review the specific startup error and the container user/runtime configuration before changing sandbox settings. |
| Screenshot is blank or content is missing | The capture ran before the page or its asynchronous content reached the desired state. | Wait for a visible content selector or app-ready condition. Increase a timeout only when the page’s expected behavior justifies it. |
| Screenshot dimensions or layout are unexpected | The browser viewport differs from the intended size, or the requirement is for a full-page capture. | Set the window size deliberately and distinguish a current-window screenshot from a full-page capture. |
| Works locally but not in a container | The container may use a different UID, path permissions, browser installation, driver version, or runtime policy. | Check the process UID and directory permissions inside the running container, then verify browser and driver versions and inspect the Chrome startup error. |
| Script exits while Chrome remains running | The driver was not closed after an exception. | Put capture work in a try block and call driver.quit() in finally, as in the example. |
7. Selenium API versus Chrome’s screenshot command
Selenium is useful when the capture needs browser automation: navigation, interaction, explicit waits, and Python-side control. Chrome also documents a direct headless command-line screenshot flow using --screenshot and --window-size. Its CLI example writes screenshot.png in the current working directory. That output behavior is separate from Selenium’s API, where your script supplies the filename.
For repeatable jobs that need page-specific waits or browser interactions, keep the Selenium flow. For a simple command-line capture, Chrome’s documented CLI can be enough. In either case, choose the viewport and output directory deliberately.
8. Performance, reliability, and cost considerations
- Wait for a condition, not an arbitrary long delay. A targeted wait can avoid capturing too early while not forcing every page to pay the same delay.
- Always close the driver. Calling
quit()infinallyreleases the browser session even when navigation or file writing fails. - Make output handling explicit. Use a known writable directory and verify the result. If screenshots are temporary, choose a location whose lifecycle matches the job.
- Control dimensions. A fixed window size helps produce consistent viewport captures, though page behavior can still vary by browser version and site state.
- Account for browser setup and maintenance. Selenium requires a compatible browser/driver environment and enough resources to launch Chrome. The research sources provide no benchmark or universal cost figure; measure runtime and resource use in your deployment.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. Its one-request API can return a screenshot or PDF without you managing a Chrome process in your script. See the ScreenshotNeo API documentation for request options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
Python:
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.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()));
- Cookie banners are accepted and removed before capture; 60+ known consent platforms, newsletter popups, and chat widgets can be removed, and each step can be turned off.
- Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed. Response headers report the page verdict and billing status.
- An MCP server lets AI agents use
take_screenshot,get_page_info, andcapture_pdf. - 1,000 screenshots a month are free with no card. Paid plans start at $5 for 3,000 screenshots; every feature is available on every plan.
Create a free ScreenshotNeo account to get 1,000 screenshots a month with no card.
FAQ
Does Selenium save screenshots as PNG?
Yes. Selenium’s Python save_screenshot() method saves the current browser window as a PNG.
Does non-root Chrome always need --no-sandbox?
No. Chrome’s FAQ says it is not needed when a user is properly set up in the container. Investigate the environment’s actual startup error if Chrome still fails.
Why use an absolute output path?
It makes the destination explicit regardless of the process working directory. The parent directory must still be writable by the runtime user.
Will this capture the entire web page?
The documented Selenium method captures the current window. Use tooling with explicit full-page capture support when the whole document is required.


