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.
Steps to run a headless browser in Selenium:
- Create selenium-headless-browser-run.py with a local page and ChromeOptions headless settings.
- selenium-headless-browser-run.py
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.
- Run the script and confirm that Selenium reads the hidden page.
$ python3 selenium-headless-browser-run.py title: Selenium headless demo heading: Headless browser ready window_size: 1280x720 browser: chrome
- Move the options block into the project driver fixture.
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 - Keep a page assertion after navigation.
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.
- Remove the temporary smoke-test script.
$ rm selenium-headless-browser-run.py
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.