Как пришлось стать немногой 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, у каждой из которых свои очень мудрёные костыли и подводные табуретки, про которые вы узнаете только после тесной интеграции вашего приложения и тестов с чужим окружением.

Комментарии (2)


  1. qwez
    14.05.2019 10:53

    Первые два варианта меня точно не вдохновили, а третий, как не странно, помог.
    должен заработать, но обещают, что это в будущем тоже «пофиксят»
    Так не на долго же? Или думаете к тому времени найдут нового автоматизатора и это будут уже его проблемы? :)


    1. Loriowar Автор
      14.05.2019 11:08

      Нет ничего более постоянного, чем временное. У нас главная цель была: поскорее вернуть к жизни автотесты, чтобы они были чаще зелёные, чем красные и все разработчики ими снова начали пользоваться. Дальше да, мы обязательно найдём нового тестировщика и он сделает более благопристойное решение, когда проксик отвалится в следующий раз.