Skip to main content
Info

In short: a modern browser is not one program but a small family of programs. One browser process owns the windows and the tab list, one renderer process per site runs the page’s HTML, CSS and JavaScript, and separate processes handle graphics and the network. Automation talks to that family from outside, picks a tab (a target), and starts the whole thing with flags that make it controllable.

Many processes, one browser#

Open Chrome’s Task Manager (Shift+Esc) and you will see far more than one entry. The split is deliberate: a crash in one web page must not take down the others, and a page that has been taken over by an attacker must not be able to read the rest of your computer.

The main processes of Chrome. Arrows mean “starts and talks to”.

ProcessWhat it doesWhy it matters when you automate
BrowserThe one trusted process: draws the window and address bar, owns the tab list, decides what a page may do, reads and writes the profile folderThe debugging port (--remote-debugging-port) is served from here, so this is what you connect to
RendererRuns one site’s pages: parses HTML and CSS, builds the DOM, runs JavaScript, lays out the pageRuntime.evaluate and the DOM commands end up here; if it crashes, the tab shows “Aw, Snap!” and the others carry on
Network serviceDoes the actual network work for every renderer: DNS, TLS, HTTP/1.1, HTTP/2 and HTTP/3, the HTTP cacheInterception, proxies and HAR hook in at this layer; TLS fingerprints come from here
GPUTurns the page into pixels and runs WebGL and video decodingWithout a GPU (a container, a CI machine), Chrome falls back to software rendering, which the page can notice
Utility and extensionShort-lived helpers (audio, decoding) and one process per extensionBrowser extensions run here, not in the page

Site isolation and the sandbox#

Two ideas protect the user, and both shape what automation sees.

The sandbox. A renderer process is started with almost no rights. It cannot open files or sockets itself: to read a file or reach the network it must ask the browser process, which checks. A bug in the JavaScript engine therefore gives an attacker a process that can still do very little.

Site isolation. Chrome puts pages from different sites (roughly, the registrable domain such as example.com) into different renderer processes. A page on evil.test cannot share memory with the bank in another tab, even when it embeds the bank in an iframe. That embedded bank frame is then drawn from a different process than the page around it, which is called an out-of-process iframe.

Note

Site isolation is why a cross-site iframe is its own target for automation: it lives in a different process, so a tool has to attach to it separately. A same-site iframe stays in the same process and is part of its parent’s target. See the DevTools Protocol.

Tabs, frames and workers: the things you can control#

Automation does not say “the browser”, it says “this tab” or “this frame”. The browser lists what can be controlled as targets:

TargetWhat it isNotes
browserThe browser process itselfUsed for new tabs, windows, permissions and browser contexts
pageA tab (or a popup window)What Browser::default_page() returns as a Page
iframeA cross-site frame with its own processA same-site frame is not a target of its own
service_workerA background script that outlives a tab and can answer its requestsSee modern web technologies
shared_worker, workerJavaScript running beside a page, with no DOMWorkers have no window and no document
background_page or extension service workerAn extension’s own scriptSee browser extensions

A frame is a document inside a document: the top page is the main frame, and every iframe element holds another. A browser context is a separate profile-like bucket (cookies, storage, cache) inside one running browser, the protocol’s name for what Chrome shows as an incognito window. See sessions, cookies and storage. In seleniumbase-rs Browser::new_context() creates one and Browser::default_page() returns the first tab.

Headed and headless#

A headed browser has a window you can see. A headless browser runs the same engine with no window and no need for a display. That is why CI machines and containers use it.

Old headless: a separate program

For years --headless started a stripped-down implementation of the browser that shared the rendering engine but not the rest. It was fast and small, and it behaved differently: no extensions, a different default window, and its own telltale signs for sites that look.

New headless: the real browser, with no window

Since Chrome 112 --headless means new headless: the same code as headed Chrome, with the window hidden. It supports extensions, behaves like the headed browser and is much harder to tell apart from it. You can ask for it by name with --headless=new.

The old one lives on as chrome-headless-shell

The old implementation was removed from the main Chrome binary in version 132. It is still published as a separate download, chrome-headless-shell, for people who want the small and fast one for screenshots and PDF. See the glossary.

Tip

Headless and headed runs are not identical in timing, in how big the viewport is or in what the page can detect. If a test passes headed and fails headless, check the window size first, then the detection page.

In seleniumbase-rs, LaunchOptions::builder().headless(true) or .headless(false) chooses explicitly, and LaunchOptions::is_headless() tells you what was chosen. When you do not choose, a launch is headless only on Linux, where there is often no display. On a machine with no display at all, a virtual one (Xvfb) lets a headed browser run; see Headless and headed.

Chrome, Chromium and Chrome for Testing#

Three things are all called “Chrome”, and automation cares which one it has.

What it isUpdatesGood for
ChromeGoogle’s branded browser, built from ChromiumUpdates itself in the backgroundEveryday use; what real users have
ChromiumThe open-source project Chrome is built from; also Edge, Brave and OperaYou or your Linux distributionServers and containers
Chrome for TestingA Chrome build for automation, published for every releaseNever updates itself; every version stays downloadableRepeatable tests: the browser and its driver can be pinned to match

Two details trip people up. First, branded Chrome stopped honouring the --load-extension flag in version 137, which is why automation that loads an extension uses a different route (see browser extensions). Second, a driver such as chromedriver must match the browser’s major version; Chrome for Testing publishes both side by side. See WebDriver and drivers and the glossary entry Chrome for Testing.

Profiles: where a browser keeps its state#

Everything a browser remembers lives in one folder, the user data directory (the profile): cookies, local storage, IndexedDB, the HTTP cache, saved passwords, preferences and installed extensions. Chrome uses a subfolder (Default, Profile 1) for each user inside it.

  • One folder can be used by one running Chrome at a time. A second start on the same folder hands its request to the first and exits. A launcher that does not know this seems to “hang” or open a window in the wrong browser.
  • Automation almost always uses a fresh temporary folder, so each run starts with no cookies and no history. That is also what makes a run look unlike a person’s browser: no history, no logins, an empty cache.
  • To keep a login between runs, point the next launch at the same folder. seleniumbase-rs keeps this in its own types (SessionStore, Identity; see sessions, cookies and storage).

Why automation starts the browser with flags#

A browser started by double-clicking an icon cannot be controlled by another program. Automation starts it with command-line flags, switches the browser reads at launch. These are the usual ones:

FlagWhat it does
--remote-debugging-port=0Opens the DevTools Protocol on a free local port and writes it to the DevToolsActivePort file in the profile; this is how CDP starts
--user-data-dir=PATHUses this profile folder instead of your own
--headless=newRuns with no window, as the real browser
--window-size=W,HSets the first window’s size (a headless default is small)
--no-first-run, --no-default-browser-checkSkips the welcome screens that would block a script
--disable-dev-shm-usageUses /tmp instead of the small shared memory of a container
--enable-automationMarks the browser as automated: it sets navigator.webdriver and shows the “controlled by automated software” bar. Stealth modes leave it out
Warning

--no-sandbox turns off the sandbox described above. It is needed in some containers that run as root, but while it is on, a malicious page in the browser has far more reach. Use it only for pages you trust, and prefer a container with a non-root user.

A flag is also a fingerprint: a few of them change how the browser looks to a page. Detection and spoofing explains which, and why seleniumbase-rs chooses its default flags with that in mind.

How seleniumbase-rs uses it#

  • Starting a browser. Browser::launch(LaunchOptions) starts Chrome with its flags and a debugging port, then waits for the port to appear (20 seconds by default; startup_timeout changes that). LaunchOptionsBuilder::debugging_port fixes the port.
  • Many browsers. BrowserPool (with PoolOptions) runs several browsers at once, each with its own profile, for parallel work.
  • Closing. Browser::close() ends the browser and its child processes; the library also registers a graceful-shutdown hook so that an interrupted run does not leave processes behind.
  • WebDriver engine. BaseCase and BrowserConfig start the browser through a driver instead; the same processes are underneath.

Where to go next#