Chrome Headless mode

Mathias Bynens
Mathias Bynens
Peter Kvitek
Peter Kvitek

In 2017, Chrome 59 introduced Headless mode, which let you run the browser in an unattended environment, without any visible UI. Essentially, you could run Chrome without chrome.

Headless mode is a popular choice for browser automation, through projects like Puppeteer or ChromeDriver. Here's a minimal command-line example using Headless mode to create a PDF file of a given URL:

chrome --headless --print-to-pdf https://developer.chrome.com/

How does Headless work

Before we review how Headless works now, it's important to understand how the "old" Headless worked. The previous command-line snippet uses the --headless command-line flag, suggesting that Headless is just a mode of operation of the regular Chrome browser. Perhaps surprisingly, this wasn't actually true. In fact, the old Headless was a separate, alternate browser implementation that happened to be shipped as part of the same Chrome binary. It doesn't share any of the Chrome browser code in //chrome.

Implementing and maintaining a separate Headless browser came with a lot of engineering overhead. And, as Headless was a separate implementation, it had its own bugs and features that weren't present in headful Chrome. This created confusion for automated browser tests, which might pass in headful mode but fail in Headless mode, or conversely.

Further, Headless excluded any automated testing that relied on browser extension installation. The same goes for any other browser-level functions; unless Headless had its own, separate implementation of it, it wasn't supported.

The Chrome team has now unified Headless and headful modes.

The new Chrome Headless is no longer a separate browser implementation, and now instead shares code with Chrome.

New Headless mode is available from Chrome 112. In this mode, Chrome creates, but doesn't display, any platform windows. All other functions, existing and future, are available with no limitations.

Use Headless mode

To use new Headless mode, pass the --headless=new command-line flag:

chrome --headless=new

For now, the old Headless mode is still available with:

chrome --headless=old

In Puppeteer

To opt in to the new Headless mode in Puppeteer:

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch({
  headless: 'new',
  // `headless: true` (default) enables old Headless;
  // `headless: 'new'` enables new Headless;
  // `headless: false` enables "headful" mode.
});

const page = await browser.newPage();
await page.goto('https://developer.chrome.com/');

// …

await browser.close();

In Selenium-WebDriver

To use the new Headless mode in Selenium-WebDriver:

const driver = await env
  .builder()
  .setChromeOptions(options.addArguments('--headless=new'))
  .build();

await driver.get('https://developer.chrome.com/');

// …

await driver.quit();

See the Selenium team's blog post for more information, including examples using other language bindings.

Command-line flags

The following command-line flags are available in new Headless mode.

--dump-dom

The --dump-dom flag prints the serialized DOM of the target page to stdout. For example:

chrome --headless=new --dump-dom https://developer.chrome.com/

This is different from printing the HTML source code, which you might do with curl. To bring you the output of --dump-dom, Chrome first parses the HTML code into a DOM, executes any <script> that might alter the DOM, then turns that DOM back into a serialized string of HTML.

--screenshot

The --screenshot flag takes a screenshot of the target page and saves it as screenshot.png in the current working directory. This is especially useful in combination with the --window-size flag.

For example:

chrome --headless=new --screenshot --window-size=412,892 https://developer.chrome.com/

--print-to-pdf

The --print-to-pdf flag saves the target page as a PDF named output.pdf in the current working directory. For example:

chrome --headless=new --print-to-pdf https://developer.chrome.com/

Optionally, you can add the --no-pdf-header-footer flag to omit the print header (with the current date and time) and footer (with the URL and the page number).

chrome --headless=new --print-to-pdf --no-pdf-header-footer https://developer.chrome.com/

Not: The functionality behind the --no-pdf-header-footer flag was previously available with the --print-to-pdf-no-header flag. You may need to fall back to the old flag name, if using a previous version.

--timeout

The --timeout flag defines the maximum wait time (in milliseconds) after which the page's content is captured by --dump-dom, --screenshot, and --print-to-pdf even if the page is still loading.

chrome --headless=new --print-to-pdf --timeout=5000 https://developer.chrome.com/

The --timeout=5000 flag tells Chrome to wait up to 5 seconds before printing the PDF. Thus, this process takes at most 5 seconds to run.

--virtual-time-budget

The --virtual-time-budget acts as a "fast-forward" for any time-dependent code (for example, setTimeout/setInterval). It forces the browser to execute any of the page's code as fast as possible while making the page believe that the time actually goes by.

To illustrate its use, consider this demo, which increments, logs, and displays a counter every second using setTimeout(fn, 1000). Here's the relevant code:

<output>0</output>
<script>
  const element = document.querySelector('output');
  let counter = 0;
  setInterval(() => {
    counter++;
    console.log(counter);
    element.textContent = counter;
  }, 1_000);
</script>

After one second, the page contains "1"; after two seconds, "2", and so on. Here's how you'd capture the page's state after 42 seconds and save it as a PDF:

chrome --headless=new --print-to-pdf --virtual-time-budget=42000 https://mathiasbynens.be/demo/time

--allow-chrome-scheme-url

The --allow-chrome-scheme-url flag is required to access chrome:// URLs. This flag is available from Chrome 123. Here's an example:

chrome --headless=new --print-to-pdf --allow-chrome-scheme-url chrome://gpu

Debug

Because Chrome is effectively invisible in Headless mode, it might sound tricky to solve an issue. It's possible to debug Headless Chrome in a way that's very similar to headful Chrome.

Launch Chrome in Headless mode with the --remote-debugging-port command-line flag.

chrome --headless=new --remote-debugging-port=0 https://developer.chrome.com/

This prints a unique WebSocket URL to stdout, for example:

DevTools listening on ws://127.0.0.1:60926/devtools/browser/b4bd6eaa-b7c8-4319-8212-225097472fd9

In a headful Chrome instance, we can then use Chrome DevTools remote debugging to connect to the Headless target and inspect it.

  1. Go to chrome://inspect and click the Configure… button.
  2. Enter the IP address and port number from the WebSocket URL.
    • In the previous example, I entered 127.0.0.1:60926.
  3. Click Done. You should see a Remote Target appear with all its tabs and other targets listed.
  4. Click inspect to access to Chrome DevTools and inspect the remote Headless target, including a live view of the page.

Chrome DevTools can inspect a remote Headless target page

Feedback

We look forward to hearing your feedback about the new Headless mode. If encounter any issues, file a bug.