How to Configure Headless Mode in Zendriver
Zendriver enables headless mode by setting headless=True in the Config object, which appends --headless=new to Chrome launch arguments and automatically masks the "Headless" user-agent token via Connection._prepare_headless().
Zendriver is an open-source Python library that controls Chromium-based browsers through the Chrome DevTools Protocol (CDP). Configuring headless mode allows you to run browser automation without a visible UI, which is essential for server deployments and CI/CD pipelines. This guide explains how to configure headless mode in Zendriver using the central Config object and how the library handles user-agent masking automatically.
Three Methods to Enable Headless Mode in Zendriver
Zendriver provides three distinct approaches to enable headless mode, depending on whether you want simplicity, full configuration control, or manual argument management.
Using Browser.create() for Quick Configuration
The simplest way to enable headless mode is passing headless=True directly to Browser.create(). In zendriver/core/browser.py, the create() method forwards this flag to a new Config instance at lines 73-91.
from zendriver import Browser
async with Browser.create(headless=True) as browser:
page = await browser.main_tab.new_page("https://example.com")
await page.wait_for_load()
print(await page.title())
When you use this approach, Config(headless=True) is created internally, handling both the launch argument and user-agent preparation automatically.
Using a Custom Config Object
For production scenarios requiring additional flags, instantiate zendriver.core.config.Config directly with headless=True. This method, defined at lines 34-38 of zendriver/core/config.py, stores the flag as self.headless while allowing you to configure sandbox settings, window size, and other options.
from zendriver.core.config import Config
from zendriver import Browser
cfg = Config(
headless=True,
sandbox=False,
window_size=(1920, 1080)
)
async with Browser.create(config=cfg) as browser:
tab = await browser.main_tab.new_page("https://httpbin.org/headers")
await tab.wait_for_load()
content = await tab.content()
Manual Argument Injection (Advanced)
If you need to add --headless=new explicitly while keeping headless=False for other logic, use Config.add_argument(). Note that when using this method, the automatic user-agent correction in Connection._prepare_headless() will not execute, as it depends on the headless attribute being True.
from zendriver.core.config import Config
from zendriver import Browser
cfg = Config()
cfg.add_argument("--headless=new")
cfg.add_argument("--disable-gpu")
async with Browser.create(config=cfg) as browser:
page = await browser.main_tab.new_page("https://example.com")
How Headless Mode Works Under the Hood
When headless=True is set, Zendriver executes two distinct operations to ensure proper headless browser behavior.
Command-Line Argument Injection
During browser launch, Config.__call__() checks the self.headless attribute. If true, it appends --headless=new to the argument list at lines 11-13 in zendriver/core/config.py. This starts Chrome without a visible window while maintaining full CDP functionality.
Automatic User-Agent Masking
After the WebSocket connection opens, Connection._prepare_headless() queries the real user-agent string, strips the "Headless" token, and invokes Network.setUserAgentOverride via the CDP. This logic resides at lines 31-50 in zendriver/core/connection.py, ensuring target websites see a normal-looking browser UA rather than a headless identifier.
Summary
- Zendriver controls headless mode through the
headlessparameter in theConfigobject, located inzendriver/core/config.py. - Setting
headless=Trueappends--headless=newto Chrome arguments viaConfig.__call__()before process launch. - The library automatically masks headless indicators by rewriting the user-agent through
Connection._prepare_headless()after connection establishment. - Three configuration methods exist: direct
Browser.create(headless=True), customConfiginstances, or manual argument addition. - Manual
--headless=newinjection bypasses automatic UA correction, which may be desirable for specific testing scenarios.
Frequently Asked Questions
What is the difference between --headless and --headless=new in Zendriver?
Zendriver specifically uses --headless=new (the new headless implementation) rather than the deprecated --headless flag. According to the source code in zendriver/core/config.py, when headless=True, the library explicitly appends --headless=new to ensure compatibility with modern Chrome versions and proper CDP functionality.
Does headless mode in Zendriver mask the user-agent automatically?
Yes. When you set headless=True in the Config object, Connection._prepare_headless() automatically queries the browser's user-agent, removes the "Headless" identifier, and overrides it via the Chrome DevTools Protocol. This occurs in zendriver/core/connection.py after the WebSocket connection is established.
Can I run Zendriver headless with a custom user-agent?
Yes, but the headless UA correction takes precedence. If you set both headless=True and a custom user_agent in Config, the _prepare_headless() method will still override the user-agent to strip the "Headless" token. To use a completely custom UA without automatic correction, set headless=False and manually add --headless=new via add_argument().
Why would I add --headless=new manually instead of using the headless flag?
Manual addition via Config.add_argument("--headless=new") is useful when you need the browser to run headless for the UI, but you want to keep headless=False in the configuration object for other logic branches, or when you explicitly want to avoid the automatic user-agent masking performed by Connection._prepare_headless(). This approach gives you raw headless behavior without Zendriver's stealth modifications.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →