Detect Chrome running in headless mode from JavaScript
JavascriptGoogle ChromeHeadless BrowserJavascript Problem Overview
With the release of Chrome 59, "headless" mode is now available in stable builds for Linux and macOS (and soon also Windows with Chrome 60). This allows us to run a full-featured version of Chrome without any visible UI, a great capability to have for automated testing. Here are examples.
chrome --headless --disable-gpu --dump-dom https://stackoverflow.com/
In my JavaScript test runner, I like to record as much information as possible about the browser being used, to help isolate issues. For example, I record many of the properties of navigator
, including the current browser plugins:
JSON.stringify(Array.from(navigator.plugins).map(p => p.name))
["Chrome PDF Viewer","Widevine Content Decryption Module","Shockwave Flash","Native Client","Chrome PDF Viewer"]
My understanding is that Chrome should behave identically in headless mode, but I have enough experience to be skeptical of a new feature that may significantly change the rendering pipeline.
For now, I am going to run tests in both modes. I would like to the test runner to record whether headless mode is being used. I could pass this information in the test configurations, but I'd rather have a pure JavaScript solution that I can build into the test runner itself. However, I haven't been able to find any browser interface that reveals whether headless mode is active.
Is there any way to detect if Chrome is running in headless mode from JavaScript?
Javascript Solutions
Solution 1 - Javascript
The user agent string includes HeadlessChrome
instead of Chrome
. This is probably the signal that you're intended to look for, so you could use:
/\bHeadlessChrome\//.test(navigator.userAgent)
Other interesting signals include:
- It looks like
window.chrome
is undefined when headless. [innerWidth, innerHeight]
is[800, 600]
(hardcoded inheadless_browser.cc
), while[outerWidth, outerHeight]
is[0, 0]
(which shouldn't usually happen).
Solution 2 - Javascript
You can check navigator.webdriver
property which is:
> The webdriver
read-only property of the navigator
interface indicates
> whether the user agent is controlled by automation.
>
> ...
>
> The navigator.webdriver
property is true when in:
>
> Chrome The --enable-automation
or the --headless
flag is used.
> Firefox The marionette.enabled
preference or --marionette
flag is passed.
The W3C WebDriver Recommendation describes it as follows:
> navigator.webdriver
Defines a standard way for co-operating user agents to inform the document that it is controlled by WebDriver, for example so that alternate code paths can be triggered during automation.
Solution 3 - Javascript
Just read this article by Antoine Vastel which provides a few ways:
- testing user agent with
/HeadlessChrome/.test(window.navigator.userAgent)
, but this is easily spoofed - testing plugins with
navigator.plugins.length == 0
- testing languages with
navigator.languages == ""
- testing WebGL vendor and renderer info (see article for interesting details)
- testing supported features as detected by Modernizr: it seems "hairlines" are not supported (hidpi/retina hairlines, which are CSS borders with less than 1px in width, for being physically 1px on hidpi screens). Test is
!Modernizr["hairline"]
. - testing size of placeholder for missing image. Insert an image with invalid URL, and test for
image.width == 0 && image.height == 0
inimage.onerror
(they found this one to be the most robust).
Can't speak for Google's motivations (is Headless Chrome only about facilitating tests of web applications? Hmmm...), but this could be seen as a list of bugs that might get fixed someday, so one has to wonder for how long those tests will work :)
Solution 4 - Javascript
navigator.plugins
should contain an array of plugins that are present within the browser (like Flash, ActiveX, or Java applets). For headless
browser it'll be nullable.
As a part of security check it can be used alert
, for headless it'll be ignored:
var start = Date.now();
alert('Press OK');
var elapse = Date.now() - start;
if (elapse < 15) {
console.log("headless environment detected");
}
Several techniques for detecting headless browsers are discussed in the OWASP AppSecUSA 2014 talk Headless Browser Hide & Seek (video, slides) by Sergey Shekyan and Bei Zhang.
Solution 5 - Javascript
The best solution I have so far is this hack. I wouldn't use it in prod code, but might in testing.
Chrome's pop-up blocker is usually enabled for all web sites, but it's disabled in headless mode. We can use the ability to open pop-ups as a fairly accurate proxy for being in headless mode. The implementation is simple: try to open(...)
a window, and check whether we get null
(indicating that it was blocked) instead of a Window
object. If we do open one, close it as quickly as possible.
function canPopUp() {
var w = open("");
if (w !== null) {
w.close();
return true;
} else {
return false;
}
}
var isHeadless = canPopUp;
For a quick example, you can try the following with and without the --headless
flag:
> chrome --headless --disable-gpu --dump-dom 'data:text/html,<!doctype html><body><script>document.body.innerHTML = `headless: ${open("") !== null}`;</script>'