Почему браузер в CI или Docker совсем не похож на настоящую машину
Браузер, запущенный внутри задачи CI или голого образа Docker, работает на железе и файловой системе, не похожих ни на чей ноутбук, и каждая настройка по умолчанию в этом окружении сливает то, чего вы локально никогда не заметите. Ничего экзотического здесь нет. Это пять обычных умолчаний, сложенных друг на друга.
Нет видеокарты, поэтому WebGL называет запасной вариант
Сайты читают строки рендерера и вендора WebGL, чтобы понять, какое графическое железо рисует страницу. Контейнеру без видеокарты сообщать нечего, поэтому Chromium откатывается к программному растеризатору, а тот представляется ровно тем, чем является: SwiftShader или, на Linux, llvmpipe из Mesa, а не моделью настоящей видеокарты. Настоящий десктоп или ноутбук почти никогда не возвращает строку программного рендерера. Дешёвого решения тут нет: пробросить настоящую или виртуальную видеокарту возможно, но это реальные инженерные затраты, и большинству команд стоит считать это жёстким сигналом, что прогон не сойдёт за пользовательскую машину, и планировать вокруг, а не воевать.
В голом образе почти нет шрифтов
Тонкие базовые образы несут малую долю шрифтов настоящего десктопа. Перечисление шрифтов, прямым API или измерением ширины отрисованного текста по списку кандидатов, вернёт короткий список там, где настоящая машина на Windows или macOS возвращает от десятков до сотен имён. Это чинится: ставьте пакеты шрифтов явно, например обычные пакеты Debian с базовыми веб-шрифтами и более широким набором Noto, а не полагайтесь на умолчания базового образа. Точный перечень шрифтов конкретной машины это не воспроизведёт, но осознанно наполненный список лучше голого.
Нет аудиоустройства
Аудиоотпечаток читает вывод графа AudioContext, включая осциллятор и компрессор, а этот вывод зависит от настоящего аудиобэкенда. У контейнера обычно нет устройства ALSA или PulseAudio, поэтому браузер откатывается к заглушке, бросает исключение там, где настоящая машина не бросила бы, или возвращает плоский результат, не похожий ни на одну реальную звуковую карту. Виртуальное или пустое аудиоустройство закрывает пробел «устройства нет», хотя аналоговых причуд настоящей карты изготовить не может. Считайте этот сигнал нереалистичным, пока вы не занялись им намеренно.
Серверный IP и всё, что обязано с ним сходиться
Хост задачи сидит в адресном пространстве облачного провайдера, которое тривиально отделяется от домашних и мобильных диапазонов и оценивается соответственно. Хуже самого IP то, что он выбивает из строя: часы контейнера сообщают UTC или то, что задал базовый образ, пока IP геолоцируется в другом месте, - противоречие, поймать которое дешевле, чем сам серверный диапазон. Привязка прокси и вывод часового пояса, локали и геолокации из его выхода этот пробел закрывают. Во что обходится сам класс IP - в статье резидентные и серверные прокси.
Чистый профиль на каждом прогоне
Контейнеры по замыслу пересобираются или выбрасываются между прогонами, поэтому куки, локальное хранилище и история профиля исчезают вместе с ними. Совершенно новое устройство у возвращающегося аккаунта, и так каждый прогон, - само по себе необычный шаблон. Решение обычное: подмонтируйте каталог профиля томом вне жизненного цикла контейнера, чтобы перезапуск переиспользовал тот же профиль на диске, а не начинал с нуля. О том, чтобы относиться к этому каталогу как к главному выживающему, - в статье резервное копирование и перенос профиля.
Режим headless и Xvfb - не одно и то же
Отдельный режим headless запускает браузер вообще без дисплейного сервера. Xvfb поднимает настоящий виртуальный дисплей X и позволяет обычной сборке с интерфейсом рисовать в него, так что браузер рисует в настоящее окно на настоящем, пусть и виртуальном, дисплее, а не работает в урезанном режиме. На Linux Xvfb вместе с полной сборкой с интерфейсом в целом ближе к настоящей десктопной сессии. Чем различаются эти два варианта - в статье детект headless и headful.
Когда ничего из этого делать не стоит
Если задача - набор тестов, проверяющих поведение вашего же приложения, или скрапинг цели, которая не снимает отпечатки агрессивно, пропустите всё вышеописанное. Контейнер CI по умолчанию - едва ли не худшее окружение для того, чтобы выглядеть настоящей машиной, но стоит вам это чего-то только в том случае, если другая сторона действительно оценивает вас как устройство, а большинство задач CI никогда не оценивают. Потратьте инженерное время на тесты, а закалку приберегите для прогонов, которые касаются настоящих аккаунтов; где проходит эта граница - в статье скрапинг и автоматизация аккаунтов. Для работы с аккаунтами постоянная машина, сохраняющая между прогонами свою видеокарту, шрифты, аудиостек и диск, часто обходится дешевле, чем тюнинг контейнера ради подделки всех пяти пунктов.
Что этот SDK здесь чинит, а что нет
Привязка прокси к профилю заставляет часовой пояс, локаль и геолокацию следовать за выходным IP автоматически, а профили выгружаются в переносимый архив, что закрывает описанные выше проблемы серверного IP и чистого профиля. Он не даёт контейнеру видеокарту, шрифты или аудиоустройство - это по-прежнему устанавливать вам. Один пробел, о котором стоит знать: опция headless в AntiBrow на macOS ничего не делает, а на Linux полной сборке с интерфейсом по-прежнему нужен Xvfb.
Проверьте сами
Запустите свой образ CI, откройте страницу, которая показывает рендерер WebGL, количество шрифтов и аудиоотпечаток, и сравните эти числа с той же страницей на своём ноутбуке. Разрыв будет очевиден, и он покажет, какие из перечисленных исправлений стоят вашего времени.
Попробуйте на бесплатном тарифе.
Неограниченное число локальных профилей, без карты. Проверьте результат детекторами сами.