Обнаружение Chrome в режиме безглавых из JavaScript
С выпуском Chrome 59 теперь доступен режим "без головы" в стабильных сборках для Linux и macOS (а вскоре и для Windows с Chrome 60). Это позволяет нам запускать полнофункциональную версию Chrome без видимого пользовательского интерфейса, что является отличной возможностью для автоматического тестирования. Вот примеры.
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, которое я мог бы встроить в самого организатора теста. Тем не менее, я не смог найти интерфейс браузера, который бы показывал, активен ли режим без головы.
Есть ли способ определить, работает ли Chrome в автономном режиме из JavaScript?
Ответы
Ответ 1
Вы можете проверить свойство navigator.webdriver
:
Свойство webdriver
только для чтения интерфейса navigator
указывает, управляется ли пользовательский агент автоматизацией.
...
Свойство navigator.webdriver
имеет значение true, если в:
Chrome Используется флаг --enable-automation
или --headless
.
Firefox --marionette
предпочтение marionette.enabled
или флаг --marionette
.
Рекомендация W3C WebDriver описывает это следующим образом:
navigator.webdriver
Определяет стандартный способ взаимодействия пользовательских агентов для информирования документа о том, что он контролируется WebDriver, например, так что альтернативные пути кода могут запускаться во время автоматизации.
Ответ 2
Строка пользовательского агента включает HeadlessChrome
вместо Chrome
. Это, вероятно, сигнал, который вы намереваетесь искать, поэтому вы можете использовать:
/\bHeadlessChrome\//.test(navigator.userAgent)
Другие интересные сигналы включают в себя:
- Похоже,
window.chrome
не определен, когда без головы. -
[innerWidth, innerHeight]
равно [800, 600]
(жестко [outerWidth, outerHeight]
в headless_browser.cc
), а [outerWidth, outerHeight]
равно [0, 0]
(что обычно не должно происходить).
Ответ 3
Просто прочитайте эту статью Антуана Вастеля, которая предлагает несколько способов:
- тестирование агента пользователя с помощью
/HeadlessChrome/.test(window.navigator.userAgent)
, но это легко подделать - тестирование плагинов с помощью
navigator.plugins.length == 0
- тестирование языков с помощью
navigator.languages == ""
- тестирование информации о поставщике и отрисовщике WebGL (подробности см. в статье)
- тестирование поддерживаемых функций, обнаруженных Modernizr: кажется, что "hairlines" не поддерживается (hailpi/retina hairlines, которые представляют собой CSS-бордюры шириной менее 1 пикселя для физического 1 пикселя на экранах hidpi). Тест это
!Modernizr["hairline"]
. - проверка размера заполнителя для отсутствующего изображения. Вставьте изображение с недействительным URL-адресом и проверьте
image.width == 0 && image.height == 0
в image.onerror
(они обнаружили, что это самое надежное изображение).
Не могу говорить за мотивацию Google (разве Headless Chrome только облегчает тестирование веб-приложений? Хммм...), но это можно рассматривать как список ошибок, которые могут быть исправлены когда-нибудь, поэтому нужно задаться вопросом, как долго эти тесты будут работать :)
Ответ 4
navigator.plugins
должен содержать массив плагинов, присутствующих в браузере (например, Flash, ActiveX или Java-апплеты). Для браузера headless
он будет нулевым.
В качестве части проверки безопасности его можно использовать alert
, для безгорта он будет игнорироваться:
var start = Date.now();
alert('Press OK');
var elapse = Date.now() - start;
if (elapse < 15) {
console.log("headless environment detected");
}
Несколько способов обнаружения безглавых браузеров обсуждаются в разделе OWASP AppSecUSA 2014 Talk Headless Browser Hide and Seek (видео, slides) Сергей Шекян и Бэй Чжан.
Ответ 5
Лучшим решением, которое я имею до сих пор, является этот хак. Я бы не использовал его в коде prod, но мог бы тестировать.
Блокировщик всплывающих окон 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>'