Как справиться с падениями ChromeDriver в Kubernetes: история об устойчивой автоматизации |
01.07.2025 00:00 |
Зачем тестировать в Kubernetes?Мне, как инженеру-тестировщику, поставили задачу разработать автоматизированные UI-тесты (пользовательского интерфейса) для сложного веб-приложения с динамически генерируемым содержимым. Это означает, что у веб-элементов, с которыми мне нужно взаимодействовать, зачастую отсутствуют статические атрибуты, на которые можно легко сослаться. В результате нужно использовать более сложные стратегии поиска и взаимодействия с этими элементами. Попрыгав через ряд колец локализации веб-элементов и убедившись, что я кликаю по нужным кнопкам и имею доступ к правильным встроенным фреймам, я закончил работу над тестами. Веб-приложение запускается, как отдельная K8S (Kubernetes) копия для каждой клиентской компании, на отдельном кластере K8S, где ресурсы этой конкретной копии сгруппированы в пространства. UI-тесты автоматически запускаются перед крупными обновлениями версий веб-приложения, а также сразу после, чтобы проверить, что обновление не повредило работе приложения. Это было достигнуто через контейнеризацию кода UI-тестов в образ Docker, его отправку в репозиторий организации и использование задачи K8S для деплоя тестов в конкретном пространстве копии перед обновлением и сразу после него. Образ Docker, разворачивающийся на сотнях ресурсов, должен быть легким, поэтому тесты запускались в окружении Linux. Запуск в Linux без поддержки дисплея означал, что тесты не могли открыть обычны браузер и вынуждены были использовать режим без графического интерфейса. Вся разработка и тестирование в компании проводятся в Chrome, и поэтому я, естественно, использовал ChromeDriver для запуска настройки Chromium в деплое контейнера. Сервер, отслеживающий расписание обновлений, использовался для запуска тестов в конкретной копии веб-приложения, а тесты возвращали на сервер JSON-отчет о результатах. Начинаем работуПервая попытка запуска тестов в контейнере с использованием Docker Desktop завершилась ошибкой: selenium.common.exceptions.WebDriverException: Message: unknown error: Chrome failed to start: exited abnormally. Перепроверьте свои веб-результатыВ поисках ошибки в Интернете я нашел вроде бы рабочее решение – использование в опциях WebDriver флага --no-sandbox. Он вроде бы помог, тесты заработали, и я мог бы идти дальше, но заинтересовался, что же делает этот флаг, и не повредит ли это решение тестам или окружению, в котором они запускаются. Звучало это, как «Для целей безопасности песочница снимает ненужные привилегии с процессов, которым они в Chrome не нужны. Отключение песочницы делает ПК уязвимее к эксплойтам через веб-страницы, поэтому Google это не рекомендует». Объяснение было найдено здесь1 – опция выглядела необходимой для запуска Chrome в unix-системах в режиме без графического интерфейса. Так как мои тесты запускались в рамках краткой задачи K8S, исчезающей спустя некоторое время после завершения, о безопасности окружения волноваться не стоило. Это, однако, породило новую проблему, когда я попытался запустить тесты из окружения разработки – оно не удаляется после использования, и на нем запущена Windows 11 Pro, версия 22H2. В примерно половине случаев после прогона тестов и срабатывания строки WebDriver.quit() от Chrome оставалось два незавершенных фоновых процесса. Их нужно было убивать вручную через диспетчер задач – в противном случае неоднократный прогон тестов увеличил бы использование оперативной памяти до 100%, делая ПК бесполезным без перезагрузки. Мировое сетевое тест-сообщество вновь поспешило на помощь – эта проблема описана на GitHub2. Попытка запуска в финальном окруженииВ Docker Desktop все вроде бы нормально работало, и я мог бы опубликовать финальный образ с кодом тестов и забыть о нем, распевая знаменитое «На моей машине все работает». Я даже отправлял «свою машину» - окружение контейнера, где тесты работали – туда, где они должны запускаться, в кластер K8S, поэтому никаких проблем, правда? Ложь. При запуске тестов в том окружении, где они должны запускаться, а именно K8S, правильно выполнялись лишь первые тесты, а все остальные падали. Будем креативныЯ уже потратил на эти тесты довольно много времени – нужно было выдавать результат. У меня не было времени на детальное изучение, почему UI-тесты падают без каких-либо видимых ошибок настройки, и я решил нестандартно подойти к вопросу, применив Firefox. Это вроде бы решило проблему – тесты наконец заработали в нужном окружении. Код был передан, сделал то, что должен, но вопрос остался – почему UI-тесты падали в K8S-окружении? Будем любопытныПередав код, я мог погладить себя по голове, поздравляя с хорошо выполненной работой, и закрыть вопрос, но вопрос, почему некоторые тесты работают в Firefox, но не в Chrome, продолжал меня мучить. Я начал исследование, освободившись от груза необходимости выдать результат. Для дебага я настроил Dockerfile для запуска команды sleep infinity после создания контейнера вместо обычной команды, запускающей UI-тесты. Это поддерживало в контейнере жизнь после запуска команд внутри него, стартующих тесты. Это также дало возможность перемещать файлы между модулем K8S, прогоняющим тесты, и моей локальной машиной. Вот мой полный Dockerfile для дебага: Я хотел видеть, что происходит в интерфейсе при падении тестов, поэтому добавил логику, делающую скриншоты падений. Так как в серверном linux-окружении модуля K8S я не мог увидеть изображения, то скачал их на свой компьютер. По видимости, в ходе падения одного из тестов страница пыталась загрузить плавающий фрейм, а он не загрузился к моменту, когда страница была вызвана из модуля K8S. При запуске тестов без графического интерфейса на локальной машине проблема не возникала – не возникала она и при запуске их из контейнера в Docker Desktop на основании абсолютно такого же образа, как в Kubernetes. Теперь я знал, почему тесты упали – не загрузились плавающие фреймы. Но почему же это произошло? Будем методичныЯ подумал о процессах, работающих в ходе тестов, и поразмышлял, что могло пойти не так и помешать фрейму загрузиться. Первое, что пришло мне в голову – что браузер не может получить доступ к URL фрейма из модуля K8S. Я попытался получить URL фрейма из DevTools моего локального браузера и вызвать его из модуля K8S – он отвечал нормально. Это исключило проблемы связи. Если браузер имеет доступ к фрейму, возможно, WebDriver плохо обрабатывает ответ – логи бы тут помогли. Я попытался сохранить логи WebDriver и пройтись по ним построчно, сравнивая их с логами, полученными из контейнера Docker Desktop. Они были идентичны – то есть проблема не вызвана тем, как WebDriver обращается с подключением к фрейму. Возможно, проблема в том, как браузер выводит полученную от фрейма информацию? Были ли проблемы в логах консоли DevTools? Я добавил в код логику, перехватывающую логи консоли DevTools и сохраняющую их в файл в ходе прогона тестов. Пройдясь по файлу, я нашел виновника. {'level': 'SEVERE', 'message': 'https://www.example.com - Failed to load resource: net::ERR_INSUFFICIENT_RESOURCES', 'source': 'network', 'timestamp': 1704886353348} По виду, у браузера не хватало ресурсов для загрузки простого фрейма? Я проверил доступные модулю ресурсы – их было более чем достаточно. Затем я обратился на Stack Overflow за ответами. Там мне сообщили о баге Chromium 2011 года – на Linux этот баг заставляет браузер занимать всю доступную память3. Решения не видать – баг исправлен не был, а последний ответ был в 2018 году. Будем настойчивыНе желая сдаваться, я попробовал изучить, что делает каждая из Selenium ChromeOptions – может, какая-то из них станет решением моей проблемы? Я наткнулся на опцию --disable-dev-shm-usage в этой статье4. Там говорилось, что «партиция /dev/shm на некоторых виртуальных машинах слишком мала, и Chrome падает. Используйте этот флаг, чтобы обойти проблему (для создания анонимных общих файлов памяти будет всегда использоваться временная директория)». Это, кажется, относится к другому багу Chromium, на него там сослались. Способ использования браузером Chrome ресурсов в окружении K8S отличался от ресурсов Docker Desktop и моей локальной машины. Добьемся успехаЯ инициализировал свой ChromeDriver с этим флагом и запустил тесты. После столь долгого пути оно наконец заработало. Я теперь прогонял UI-тесты внутри браузера Chromium на linux-окружении кластера K8S. Я снова изменил код на браузер Chromium для прогона тестов и загрузил новый образ в реестр контейнеров компании. Затем я проверил копию, для которой ожидалось обновление, и подождал результатов тестов. Все сработало, и это путешествие принесло мне чувство удовлетворения, а также обогатило мой тест-опыт. ЗаключениеЕсли вы чувствуете легкий толчок мучительного вопроса «Почему», потратьте время и силы, чтобы найти на него ответ. Возможно, он заведет вас туда, где не ступала нога человека, и даст вам преимущества при путешествии по постоянно меняющемуся миру тестирования. Если найдете что-то интересное – поделитесь этим с окружающими: мы тестируем этот мир вместе, баг за багом. Если вы сталкиваетесь с проблемами, тестируя в окружении K8S, воспользуйтесь двумя опциями, которые сделали эту задачу возможной: chrome_options.add_argument('--no-sandbox') Исследование падений ChromeDriver в Kubernetes – это путь к устойчивости тест-автоматизации. Была поставлена задача разработать UI-тесты для динамически генерируемого веб-приложения, но из-за того, что исходные тесты Docker Desktop столкнулись с падениями Chrome, возникли проблемы. Это решилось флагом --no-sandbox, однако зависающие процессы Windows потребовали дальнейших разбирательств. Деплой тестов в Kubernetes привел к новым падениям, и в результате в качестве обходного пути был выбран Firefox. Любопытство побудило исследовать вопрос уже после передачи тестов – и в результате оказалось, что проблемы возникают при загрузке фрейма. Проверка ресурсов, поиск багов и исследование ChromeOptions привели к успеху – применению флага --disable-dev-shm-usage, в результате чего UI-тесты без проблем запустились в Kubernetes. Эта история подчеркивает важность любопытства, настойчивости и находчивости при преодолении проблем тестирования, и я поделился ее уроками, чтобы люди со схожими проблемами воспользовались этими знаниями. Ссылки |