Chrome's Headless mode gets an upgrade: introducing --headless=new

Chrome's Headless mode just got a whole lot better!

Peter Kvitek
Peter Kvitek

Chrome's Headless mode just got a whole lot better! This article presents an overview of recent engineering efforts to make Headless more useful for developers by bringing Headless closer to Chrome's regular "headful" mode.

Background

Back in 2017, Chrome 59 introduced the so-called Headless mode, which lets you run the browser in an unattended environment without any visible UI. Essentially, running 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 of using Headless mode to create a PDF file of a given URL:

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

What's new in Headless?

Before we dive into the recent Headless improvements, it's important to understand how the "old" Headless worked. The command-line snippet we showed earlier 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. Technically, 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.

As you might imagine, implementing and maintaining this separate Headless browser came with a lot of engineering overhead — but that wasn't the only problem. Because Headless was a separate implementation, it had its own bugs and features that weren't present in headful Chrome. This created a confusing situation where any automated browser test might pass in headful mode but fail in Headless mode, or vice versa — a major pain point for automation engineers. It also excluded any automated testing that relied on having a browser extension installed, for example. The same goes for any other browser-level functionality: unless Headless had its own, separate implementation of it, it wasn't supported.

In 2021, the Chrome team set out to solve this problem, and unify Headless and headful modes once and for all.

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

We're excited to announce that the new Headless mode is now available in Chrome 112! In this mode, Chrome creates but doesn't display any platform windows. All other functionality, existing and future, is available with no limitations.

Try out the new Headless

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

chrome --headless=new

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

chrome --headless=old

Currently, passing the --headless command-line flag without an explicit value still activates the old Headless mode — but we plan to change this default to new Headless over time.

We plan to completely remove the old Headless from the Chrome binary and stop supporting this mode in Puppeteer later this year. As part of this removal, we'll make the old Headless available as a separate standalone binary for those users who can't upgrade yet.

New Headless 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();

New Headless 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.

Headless-specific command-line flags

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

--dump-dom

The --dump-dom flag prints the serialized DOM of the target page to stdout. Here's an example:

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

Note that this is different from simply 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, and 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. It's especially useful in combination with the --window-size flag. Here's an 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. Here's an 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/

--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 enables time travel! Well, to some extent. Virtual Time 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 page 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 starting in Chrome 123. Here's an example:

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

Debugging

Because Chrome is effectively invisible in Headless mode, it might sound tricky to figure out what's going on in case of issues. Luckily, it's possible to debug Headless Chrome in a way that's very similar to headful Chrome. The trick is to 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 regular headful Chrome instance, we can then use Chrome DevTools remote debugging to connect to the Headless target and inspect it. To do so, go to chrome://inspect, click the Configure… button, and enter the IP address and port number from the WebSocket URL. In the above example, I entered 127.0.0.1:60926. Click Done and you should see a Remote Target appear with all its tabs and other targets listed below. Click inspect and you now have access to Chrome DevTools inspecting 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 you run into any issues, please report them.