Как пришлось стать немногой qa-автоматизитором: что при этом ощутил, пережил и про дивную архитектуру и инфраструктуру автоматического тестирования глазами проходившего мимо разработчика.
Введение
Как говорится, я не гинеколог, просто проходил мимо и решил заглянуть. Поэтому, для начала, скажу о причинах написания. Думается мне, что специалист справился бы с проблемой на порядок быстрее и не собрал бы столько грабелек на своём пути. Но ему было бы не так интересно как мне, а вам было бы нечего читать.
Ещё одна причина, которая сподвигла это написать: я довольно долго не понимал что там такого особенного в инфраструктуре автотестов. Более того, многие руководителя от ПМ и выше тоже не до конца понимали что же там такого космического и почему модульные тесты разработчики пишут на раз-два и сами, а для фичетестов берутся отдельные ребята, которые тесты пишут медленнее, постоянно что-то чинят и шанс прохода всех фичеспек строго меньше 100% при условии достаточно большой выборки.
Проблема
Третьего дня упало у нас порядком фичеспек на рельсовом проекте. Где-то за неделю до этого наш единственный автоматизатор решил пойти работать в Чикаго и мы ещё не нашли нового специалиста. Поэтому пришлось засучить рукава и прикинуться QA. О том, как это было и попробую рассказать.
Проблема выглядит довольно безобидно. Но, для начала, немного предыстории и описания окружения. У нас в платформе много селекторов адресов. Честно говоря, это одна из основных сущностей платформы. Селекторы ходят в гугл api за данными. В автотестах все запросы застаблены, дабы сэкономить денег и ускорить тесты. Так же добавлено немного логики, чтобы отдавалась примерно та же строка адреса, которая была запрошена без походов во внешние сервисы.
Что сломалось: мы вводим в строку адреса желаемый адрес, появляется выпадашечка с несколькии вариантами, мы выбираем желаемый и… в инпут подставляется значение соседнего элемента. Всегда.
Долгий путь к истине
Первые гипотезы и наивный подход
Не мудрствуя лукаво, я взял ближайший падающий тест, нашёл строку, на которой происходит выбор адреса и начал пристально разглядывать её и соседей. Выглядит строка безобидно: new_order_page.destination_address.select(baker_street)
. Но мы же понимаем, что за красивым кодом всегда стоит куча странненьких конструкций и нелицеприятных внутренностей.
Довольно быстро вспомнилось, что всё это хозяйство работает на SitePrism. Эта штука даёт возможность заворачивать страницу и элементы на ней в класс и методы класса соответственно. За клики и прочие действия отвечают Capybara и RSpec. Но в них сомневаться не приходится, они надёжны, как весь гражданский флот. А раз так, сразу напрашивается первая гипотеза: либо кто-то плохо написал селекторы для призмы, либо кто-то покрутил вёрстку на фронте.
Первая часть гипотезы быстро отпала, селекторы написаны отлично. Никаких xpath
с выбором третьего li
внутри элемента там не обнаружилось и сам код не меняли последний год.
Однако, в районе метода select
наворочено логики с регекспами по выбору нужного варианта из выпадающего списка. Я, конечно же, агрюсь на регэкспы и иду их проверять. Трачу полчасика и понимаю, что всё работает прекрасно. Выбирается ровно та строка, которая нужна. На ней же вызывается click
. И всё должно работать. То есть вторая часть гипотезы про вёрстку тоже отпадает. Но появляется мысль про кривой js
. Ведь элемент на страничке у нас кастомный, js
-а вокруг него порядком и, мало того, в этом js
совсем недавно ковырялись.
Во всём виноват js
Это стандартная причина для всех непонятных проблем. Что-то вроде "вали на js
, ибо он и так контуженный". И, кажется, в моём случае тоже не обошлось без js
. В общем, недолго думая я бегу к команде фронтендеров и тычу пальцем в падающие тесты, заявляя "с нашей стороны всё работает, почините, пожалуйста, вашу сторону".
Но, ребята из франта не промах, ковыряются там пару часов, находят пару багов не относящихся к повествованию и заявляют, что js
не виноват! Заодно они же подкидывают интересную информацию о том, что одного запроса для селекта недостаточно и он делает ещё один, ответ на который в точности совпадает с неверным содержимым инпута.
Такого поворота я не ожидал. Приходится снова лезть в бэк и разбираться как у нас работает мок запросов.
Обстоятельный подход
Итак, помощи ждать больше неоткуда, Москва за нами и всё в таком духе.
Первым делом смотрим на способы мока запросов к гуглу. Точнее сначала ищем где это происходит. Это уже нетривиальная задача. Оказывается у нас целый модуль MockServices::Base
, отвечающий за мок разных запросов с использованием VCR. Он же хитро замешивается в базовый контроллер и просто так по имени сервиса, отвечающего за внешние запросы, его не найти.
Ладно, нашли моки. Теперь смотрим на их реализацию. Первый запрос мокается просто: берётся информация из params
и подставляется в шаблон с ответом. На всякий случай проверил содержимое params
и, ожидаемо, там всё приходит как надо.
Способ мока следующего запроса более интересен. Там появляется какая-то mock_data
. Это не params
и надо бы разобраться откуда эта дата берётся. Провалившись пяток раз вглубь обнаруживается, что данные эти берутся из RequestStore по ключу x_mock_data
. Уже интереснее.
Возвращаемся к исходному тесту и замечаем, что там есть штука set_mock_header
, которая, при ближайшем рассмотрении, складывает некоторые данные в тот же RequestStore
. Ещё интереснее!
Где-то в этот момент в голове происходит когнитивный диссонанс: с одной стороны, вон она вероятная причина проблемы, сломались глобальные переменные, которые даёт нам реквест стор. Но есть нюанс: сервер для фичеспек и сами фичеспеки — это два независимых процесса (на самом деле, сервер это — минимум 3 процесса), поэтому де?бет с кре?дитом никак сойтись не может, ибо в этот мир глобальных переменных между процессами пока не завезли. Да и при многопоточном веб-сервере это будет лютая дичь, которая физически работать не будет. Значит я что-то продолбал и надо искать.
Смотрим дальше и находим некий bm
, которому проставляют хидеры. Идём дальше и понимаем, что bm
— это BrowserMob. Тут я немного прифигел, ибо это прокси на джаве в рубийной обёртке. Прямо рояль в кустах.
Начинаем ковырять дальше и понимаем, что для "глобальных" переменных между клиентом с rspec
и сервером с приложением (например, puma) используются те самые X-Mock-Data
хидеры в запросе. Проблема в том, что приложением не должно знать ничего про эти хидеры. Как раз для этого и нужен проксик, через который полетят все запросы и который позаботится о простановке хидеров. Хитро, ничего не скажешь.
Идём тестировать и обнаруживает, что как раз эта штука и не работает. Хидеров нигде не видно: ни в запросах, ни в ответах. Но RequestStore заполнен на стороне rspec
и пуст на стороне веб-сервера. Значит точно — дело в проксике.
Тут же, между делом оказывается, что у нас не только тесты с адресами валятся, но и всё, что использует вышеозначенных set_mock_header
.
Отлично. Осталось понять как это исправить.
Разбираемся с проксиком
Опустим моменты раскопок в районе запуска jar
-файла и последующего управления им через Ruby. Обратим лучше внимание на способ указания проксика для браузера. Мы используем Chrome и передаёт информацию о прокси в одном из множества аргументов командрой строки при его запуске. Особенность проксика в том, что мы используем pac
файл, который генерируем из шаблона, чтобы не пускать через прокси трафик от веб-сокетов.
Где-то здесь появляется желание пойти и погуглить что там у хрома с конфигом проксика. Оказывается, далеко ходить не надо и в верси 72+ ребята "допилили" его работу. По этому поводу даже баг отдельный завели. Самый понравившийся мне комментарий:
"Can you please stop REMOVING functionality?"
Печаль в том, что это считается фичей и в будущем обещают ещё больше жести в плане "секурности".
Короче говоря, нет в Chrome больше поддержки протокола file:
в аргументе proxy-pac-url
. Пути решения один лучше другого:
- передать в аргументе
js
, который прочитаетpac
-файл и превратить его в base64:--proxy-pac-url='data:application/x-javascript-config;base64,'$(base64 -w0 /path/to/pac/script)
; - поднять свой веб-сервер на питоне, дабы раздать один файл по более "правильному" протоколу, который поддерживается в аргументе для
pac
прокси; - выключить
NetworkService
и тогда протоколfile:
должен заработать, но обещают, что это в будущем тоже "пофиксят".
Первые два варианта меня точно не вдохновили, а третий, как ни странно, помог.
Недолгая радость
Возрадовавшись, что найдена хитрая связь между неработающими дропдаунами и обновившимся хромом я недолго радовался. Оказывается наш CI обновил не только хром, но и все прилежащие пакеты и теперь у нас валится ещё больше тестов из-за неведомой ошибки Selenium::WebDriver::Error::NoSuchDriverError
, которая, как ни странно, не связана с chromedriver, но связана с конфигом хрома, версиями библиотек и параллельным выполнением спек.
Но это уже задача для следующего рабочего дня...
Забегая в будущее: там помог аргумент disable-dev-shm-usage
.
Выводы
Не ругайте автоматизатора. Он, кажись, больше всего страдает от внешних обстоятельств, которые от него не зависят.
Лучше подружите автоматизатора с девопсом, чтобы они организовали свою инфраструктуру с преферансом и куртизанками с фиксированными версиями и контролируемым окружением для тестов. По мне так это лучше, нежели страдали от проприетарных CI, у каждой из которых свои очень мудрёные костыли и подводные табуретки, про которые вы узнаете только после тесной интеграции вашего приложения и тестов с чужим окружением.
qwez
Loriowar Автор
Нет ничего более постоянного, чем временное. У нас главная цель была: поскорее вернуть к жизни автотесты, чтобы они были чаще зелёные, чем красные и все разработчики ими снова начали пользоваться. Дальше да, мы обязательно найдём нового тестировщика и он сделает более благопристойное решение, когда проксик отвалится в следующий раз.