CI runners and remote test hosts often need browser coverage without a desktop session. Running Chrome headless through Selenium keeps the normal WebDriver command flow while the browser renders pages off screen, so a test can open a page, locate elements, and assert content from a terminal job.
In Python, headless mode belongs in ChromeOptions before the webdriver.Chrome() session starts. The --headless=new argument selects the current Chrome headless implementation, and --window-size gives responsive layouts, screenshots, and element positions a repeatable starting size.
The browser and driver still have to match before the session can start. Selenium Manager can resolve ChromeDriver when no driver is supplied, while locked-down CI images can pass an explicit service path. Headless runs can still differ from visible browser runs around viewport size, fonts, GPU behavior, downloads, and sandbox limits, so keep one visible-browser debug path for failures that reproduce only in automation.
from pathlib import Path from tempfile import TemporaryDirectory import os import shutil from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.chrome.service import Service from selenium.webdriver.common.by import By HTML = """<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>Selenium headless demo</title> </head> <body> <h1>Headless browser ready</h1> </body> </html> """ options = Options() options.add_argument("--headless=new") options.add_argument("--window-size=1280,720") for browser_name in ("google-chrome", "chromium", "chromium-browser"): browser_path = shutil.which(browser_name) if browser_path: options.binary_location = browser_path break if hasattr(os, "geteuid") and os.geteuid() == 0: options.add_argument("--no-sandbox") options.add_argument("--disable-dev-shm-usage") driver_path = shutil.which("chromedriver") service = Service(driver_path) if driver_path else Service() with TemporaryDirectory() as tmpdir: page = Path(tmpdir) / "headless-demo.html" page.write_text(HTML, encoding="utf-8") driver = webdriver.Chrome(service=service, options=options) try: driver.get(page.as_uri()) window_size = driver.get_window_size() print(f"title: {driver.title}") print(f"heading: {driver.find_element(By.TAG_NAME, 'h1').text}") print(f"window_size: {window_size['width']}x{window_size['height']}") print(f"browser: {driver.capabilities.get('browserName')}") finally: driver.quit()
The script uses a temporary local page, so the smoke test does not depend on external network access. The root-only flags are for disposable containers and CI jobs that run Chrome as root; omit them on normal workstations.
$ python3 selenium-headless-browser-run.py title: Selenium headless demo heading: Headless browser ready window_size: 1280x720 browser: chrome
options = Options() options.add_argument("--headless=new") options.add_argument("--window-size=1280,720") driver = webdriver.Chrome(options=options)
Set options.binary_location and a Service path only when the runner uses a pinned browser or cannot use Selenium Manager.
Related: How to configure ChromeDriver for Selenium
driver.get("https://app.example.com/login") assert driver.title == "Sign in" assert driver.find_element(By.TAG_NAME, "h1").text == "Sign in"
A started WebDriver session only proves the browser launched. A title or element assertion proves the headless browser rendered the target page.
$ rm selenium-headless-browser-run.py