使用JavaScript检测Chrome是否在无头模式下运行

58

随着Chrome 59的发布,在Linux和macOS的稳定版本中,“无头”模式已经可用(很快也将在Chrome 60的Windows版本中提供)。这使我们能够运行一个完整功能的Chrome版本,而没有任何可见的UI,这是自动化测试中非常有用的功能。 这里有一些示例。

chrome --headless --disable-gpu --dump-dom https://stackoverflow.com/

在我的JavaScript测试运行器中,我喜欢记录尽可能多有关使用的浏览器信息,以帮助隔离问题。例如,我会记录navigator的许多属性,包括当前浏览器插件:

JSON.stringify(Array.from(navigator.plugins).map(p => p.name))
["Chrome PDF Viewer","Widevine Content Decryption Module","Shockwave Flash","Native Client","Chrome PDF Viewer"]

我的理解是Chrome在无头模式下的行为应该是相同的,但我有足够的经验对可能会显著改变渲染流程的新功能持怀疑态度。

目前,我将在两种模式下运行测试。我想让测试运行器记录是否正在使用无头模式。我可以在测试配置中传递这些信息,但我更愿意采用一种纯JavaScript的解决方案,将其构建到测试运行器本身中。但是,我无法找到任何浏览器接口来显示无头模式是否处于活动状态。

有没有办法从JavaScript中检测Chrome是否在无头模式下运行?

5个回答

36

用户代理字符串中,包含HeadlessChrome而不是Chrome。这很可能是你要寻找的信号,所以你可以使用:

/\bHeadlessChrome\//.test(navigator.userAgent)

其他有趣的信号包括:

  • 当使用无头浏览器时,window.chrome 未定义。
  • [innerWidth, innerHeight][800, 600](在headless_browser.cc中硬编码),而[outerWidth, outerHeight][0, 0](这通常不应该发生)。

2
用户代理字符串可以通过设置 Chrome 选项来覆盖。 - Corey Goldberg
在Firefox和Safari中,window.chrome是未定义的,所以我会避免使用它... - Jason Lydon

20
您可以查看navigator.webdriver属性,它是关于以下内容的:

navigator接口的只读属性webdriver指示用户代理是否由自动化控制。

...

当在以下情况下使用时,navigator.webdriver属性为true:

Chrome使用--enable-automation--headless标志。
Firefox传递marionette.enabled偏好设置或--marionette标志。

W3C WebDriver建议将其描述如下:

navigator.webdriver定义了一种标准的方式,使协作用户代理通知文档它由WebDriver控制,例如,以便在自动化期间触发替代代码路径。


9

刚读了Antoine Vastel的这篇文章,提供了几种方法:

  • 使用/HeadlessChrome/.test(window.navigator.userAgent)测试用户代理,但这很容易被欺骗。
  • 使用navigator.plugins.length == 0测试插件。
  • 使用navigator.languages == ""测试语言。
  • 测试WebGL供应商和渲染器信息(有关详细信息,请参见文章)。
  • 测试由Modernizr检测到的支持功能:似乎不支持“hairlines”(hidpi / retina hairlines是CSS边框,宽度小于1px,在hidpi屏幕上物理上为1px)。测试为!Modernizr["hairline"]
  • 测试缺失图像的占位符大小。插入一个具有无效URL的图像,并在image.onerror中测试image.width == 0 && image.height == 0(他们发现这是最可靠的方法)。

不能确定谷歌的动机(Headless Chrome是否只是为了方便测试Web应用程序?嗯...),但这可能被视为一个可能会在某一天得到修复的错误列表,因此必须想知道这些测试会持续多长时间 :)


我刚刚发现Josh Lee在昨天的问题下面的评论中提到了这一点。我读了同样的文章,记得上个月有这个问题...我希望我在为原作者做广告和不抄袭之间找到了正确的平衡。如果这太过分了,我可以编辑、删除等。(感谢Jeremy的编辑) - Hugues M.
navigator.languages == "",似乎不是一个有效的测试。我的首选浏览器语言是“en-GB”,而我的首选系统语言是“en-US”。在普通的Chromium浏览器中,navigator.languages返回["en-GB","en-US","en"],而在无头Chromium中返回['en-US']。 - theAnubhav

5

navigator.plugins应包含在浏览器中存在的插件数组(如Flash、ActiveX或Java小程序)。 对于无头浏览器,它将是可空的。

作为安全检查的一部分,可以使用alert,对于无头浏览器,它将被忽略:

var start = Date.now();
alert('Press OK');
var elapse = Date.now() - start;
if (elapse < 15) {
    console.log("headless environment detected");
}

在 OWASP AppSecUSA 2014 的演讲中,Sergey Shekyan 和 Bei Zhang 讨论了检测无头浏览器的几种技术。该演讲名称为“Headless Browser Hide & Seek”(视频幻灯片)。


我确认在无头Chrome中看到 navigator.plugins.length === 0。测试命令:chrome --headless --disable-gpu --dump-dom 'data:text/html,<!doctype html><body><script>document.body.innerHTML = \plugins: ${navigator.plugins.length} ${Array.from(navigator.plugins).map(p => p.name)}`;</script>'` - Jeremy
3
但是我在你的情况下得到了一个“假阳性”。我正在使用我的安卓手机上的Chrome浏览器,它返回navigator.plugins.length === 0。所以最好不要使用这个方法。 - WhiteAngel
1
是的,在Linux上使用Chromium进行测试也会返回插件数量为0。因此,这不是一个可靠的测试。 - pouya

5
我目前能提供的最好解决方案是这种方法。我不会在生产代码中使用它,但可能会在测试中使用。
Chrome 的弹出窗口阻止器通常对所有网站启用,但在无头模式下禁用。我们可以利用打开弹出窗口的能力作为相当准确的代理来判断是否处于无头模式。实现很简单:尝试使用open(...)打开一个窗口,并检查我们是否收到null(表示已被阻止)而不是一个Window对象。如果成功打开一个,请尽快关闭。
function canPopUp() {
  var w = open("");
  if (w !== null) {
    w.close();
    return true;
  } else {
    return false;
  }
}

var isHeadless = canPopUp;

举个快速的例子,您可以尝试以下内容,其中包括和不包括--headless标志:

chrome --headless --disable-gpu --dump-dom 'data:text/html,<!doctype html><body><script>document.body.innerHTML = `headless: ${open("") !== null}`;</script>'

(注:该代码用于演示如何使用Chrome浏览器的无头模式,并输出相关信息。)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接