Подтвердить что ты не робот

Обнаружение 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?

4b9b3361

Ответ 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>'