Кажется, уже сложно представить себе веб-приложение, которое не использует микрофронтендную архитектуру для возможности реализовать гибкое и функциональное приложение. И как в любом архитектурном подходе, в микрофронтенде необходимо обеспечивать качественное тестирование разрабатываемых компонентов. Но с чего стоит начать и что ждет каждого, кто вступит на путь автоматизации микрофронтенда, когда многие привыкли тестировать уже собранные из кусочков приложения?  Привет. Меня зовут Александр Воробей, я ведущий специалист по автоматизации тестирования в Тинькофф. В этой статье я постараюсь вкратце рассказать, с чего мы начинали автоматизацию микрофронтенда, с какими проблемами встретились и какие результаты получили. Статья основана на моем докладе с конференции Heisenbug. Она состоит из двух разделов: Те, кто хочет как можно скорее узнать о способах тестирования микрофронтенда, могут смело переходить к разделу «Автоматизация тестирования микрофронтенда». Позвольте начать с рассказа о самом предмете тестирования — микрофронтенде в Тинькофф. По этой ссылке можно почитать что такое микрофронтенд в общих чертах. До перехода на микрофронтенд все используемые компоненты — формы, блоки с текстами, иконками и изображениями — находились в одном репозитории и над всеми компонентами работали разные команды. Сейчас почти все компоненты (или даже все) разрабатываются разными командами, подключаются каждый по отдельности через пакеты или другие технологии. Далее речь пойдет об одном из таких компонентов — о сложных формах и их составляющих.  Сложными я их называю по той причине, что, кроме выполнения стандартной функции отправки введенных данных на сервер, форма: И таких форм в Тинькофф более 20 штук для разных продуктов с разным поведением (это только для неавторизованной зоны, на самом деле их гораздо больше). Все они имеют одни и те же строительные блоки: инпуты, кнопки, наборы полей, плагины и разные экраны. Поэтому, кроме форм, существует микрофронтенд под названием Form Builder, основной ролью которого является поставка более мелких компонентов для форм. Поэтому дальше речь пойдет о формах и Form Builder Раньше тестировщики проводили регресс только собранного приложения. Например, изменилась кнопка или набор полей — ее могли проверить только в собранном приложении. В микрофронтенде нет собранного приложения — есть только компоненты, из которых он состоит. В связи с этим небольшие микрофронтенды могут релизится чаще, так как они меньше по объему и имеют меньшее количество зависимостей, а соответственно регресс для конкретного микрофронтенда может проводиться чаще, возможно даже несколько раз в день. Поэтому первой и основной целью является автоматизация регресса тестирования. Немаловажно и то, что сегодня хорошей практикой считается хранение автоматизированных тестов рядом с кодом приложения. Мы также последовали этой практике, но поняли, что поддерживать такие тесты в любом случае придется разработчикам, которые раньше не имели особого опыта в автоматизации тестирования UI. Поэтому второй целью стало построение простой и удобной инфраструктуры тестирования. А третьей — привлечение разработчиков к написанию автотестов.
В результате имеем три цели: Большинство QA-инженеров привыкли к тестированию собранного приложения. В микрофронтенде, как я уже говорил, есть только компоненты, которые нужно как-то отображать и уметь проверить функционал. При анализе инструментов, позволяющих отображать каждый из компонентов в разных состояниях, мы выбрали Storybook. Внешний вид стенда с использованием Storybook:  На стенде имеется: В результате этот инструмент позволил сделать: Вот главные требования к инструменту: Выбор пал на инструмент CodeceptJS. Так как данный инструмент позволяет, написав один раз тест, запускать его на любом движке — будь то Puppeteer, WebDriverIO, Appium или даже новый Playwright, — подключив который не придется переписывать тесты. Пример синтаксиса: Для тестировщиков такой синтаксис прост в понимании, и они могут самостоятельно писать тесты. При этом по результатам теста генерируется понятный Allure-отчет, который содержит и каждый шаг, и время выполнения шага, и разные аттачментсы. Почему не использовали другие инструменты? Собственно в решении наших потребностей помог CodeceptJS. В первую очередь важно отметить, что для CI/CD мы используем Gitlab, соответственно стенд и тесты запускаются на каждый мерж-реквест, создаваемый в Gitlab, тем самым проверяя качество кода. Так как одной из целей было привлечение разработчиков к покрытию автотестами кода, а разработчики уже согласились писать тесты на выбранных инструментах, мы решили, что для более качественного контроля за написанием тестов необходимо внедрить тестировщика в процесс ревью мердж-реквеста.  Но на самом деле нам хватило несколько таких ревью, и мы поняли, что это только замедляет разработку. Почему? Потому что в этот момент разработчик сам придумал тесты на свой код, реализовал их. И, когда проходило ревью тестировщиком, он находил кучу проблем с тестами, которые нужно дорабатывать. Согласитесь, не каждый разработчик будет рад ждать заново пайплайна только для того, чтобы поправить пару тестов. Кроме того, поздно написанные тесты увеличивали время жизни МРа. Поэтому решением возникшей проблемы стало то, что в процессе мы вернули тестировщика на ранний этап после создания задачи, где основной целью тестировщика является написание тест-кейсов. В результате, когда разработчик реализует новый функционал или исправляет дефект, у него уже есть тест-кейс, по которому нужно написать автотест, и больше не тратится время на переписывание тестов.  Дальше — интереснее. Дальше — автоматизация! В этом разделе я рассмотрю несколько пунктов: Поехали. Сторибук нас устроил не только тем, что он является отличным демостендом, но и тем, что с его помощью можно качественно и удобно построить автоматизацию тестирования. Начну с одного из важных, на мой взгляд, способов тестирования компонентов — с тестирования верстки компонентов. В основном все компоненты имеют какие-то состояния, которые достаточно проверить визуально: например, размер кнопок или их цвет, или состояние чек-бокса, или состояние заполненного инпута. Но для того, чтобы тестировать верстку, необходимо иметь актуальный стенд (тестируемый), на котором будет сниматься скриншот, и эталонный скриншот, с которым мы можем сравнить. Обычно эталонные скриншоты держат внутри репозитория, используя LFS. Но мы решили, что не хотим так, потому что хранение эталона в репозитории влечет за собой необходимость поддержки этих эталонов в актуальном состоянии, а при локальном обновлении скриншотов и тестировании обязательно запускать в докер-контейнере (чтобы не было эффектов от использования разных ОС) и, естественно, поддержка всего этого. А еще мы не хотели писать тесты на каждый новый компонент при тестировании верстки, нам нужно было автоматическое решение. Так как мы используем Storybook, то все необходимые компоненты хранятся в нём. При этом в нашем случае есть два вида Storybook: Чтобы автоматически тестировать верстку без участия разработчиков, нам пришлось покопаться в возможностях сторибука, где мы обнаружили, что можно получить список всех существующих компонентов в сторибуке, используя его API — window.__STORYBOOK_CLIENT_API.getStorybook(). Но важной особенностью данного API стало то, что он доступен только в собранном сторибуке. То есть только если мы переходим явно на урл сторибука, открываем консоль и вводим там. В nodejs этот API не доступен. Но решением оказался следующий код: Так как собранный сторибук — это набор js-файлов, представленный код с помощью конструкции require внедряет собранный js-код внутрь самой node, в результате чего из node теперь есть доступ к API сторибука. Имея данную возможность, мы можем сформировать список компонентов и урлов к ним.

С учетом этих данных был написан скрипт, который генерирует автотесты на CodeceptJS, следующего вида:
 Входными данными для такого теста является список компонентов. Алгоритм теста следующий: Всё. Больше ничего не нужно делать.
Но не все было так гладко — этот подход оказался достаточно медленным. Вот пример:
 42 компонента тестировались 3 минуты. Согласитесь, это слишком долго! Причина была в том, что для переходов между компонентами иногда тратилось от 2 до 14 секунд. Причиной могли быть перегруженные раннеры, долгий рендер компонентов. Решением стало опять же API сторибука, которое появилось в более поздних версиях — window.__STORYBOOK_CLIENT_API__._storyStore.setSelection(). Этот метод позволяет переключать сторисы, используя их id, без необходимости перезагружать страницу — прямо в момент работы сторибука. Изменив способ перехода между компонентами, скорость выполнения тестов стала значительно привлекательнее — за то же самое время, 3 минуты, мы стали тестировать 554 компонента:
 Результаты: Ранее я упоминал, что у нас используется 20+ форм. И при каком-либо изменении функционала в Form Builder мы достаточно поздно узнавали об ошибках, так как новая версия Form Builder могла быть использована через несколько дней. Для ускорения обратной связи мы придумали запускать тесты ключевых форм внутри пайплайна Form Builder. Для реализации данной идеи нам потребовалось немного усилий: Мы сделали джобу, внутри которой выгружали репозиторий формы через git clone. Дальше перемещались в директорию формы и с помощью команды yarn link form-builder прилинковывали текущую версию From Builder, которая хранится локально. А потом запускали тесты на Happy-path этой формы. В результате наши разработчики могли узнавать о проблемах внутри форм еще до релиза библиотеки Form Builder, не беспокоясь, что в формах сломается основной функционал. Со временем жизни проектов From Builder и форм количество функциональных тестов начало значительно расти и время jobs с тестами уже переваливало за 30 минут, а время всего пайплайна могло перевалить и за 50 минут. Естественно, для нас это стало проблемой, и мы искали решение уменьшить скорость. Для решения мы поняли, что нам нужно нарушить одну цель, к которой мы шли, — запуск одного теста с использованием любого драйвера (Puppeteer или Wdio). Мы попробовали запускать тесты внутри браузера, используя инструмент karma. Что значит запуск внутри браузера? При использовании Puppeteer или Wdio взаимодействие с браузером происходит через протоколы CDP (Chrome DevTools Protocol) и WebDriver Protocol соответственно. На примере Puppeteer можно взять метод type, входящий в состав его API. В официальной документации к этому методу написано: "Sends a keydown, keypress/input, and keyup event for each character in the text". То есть Puppeteer посылает как минимум три запроса через протокол DevTools браузеру на каждый вводимый символов. Это достаточно хорошо имитирует поведение пользователя, но скорость тестов из-за этого все равно увеличивается. Подход при тестировании в браузере (с использованием karma) заключается в следующем: В результате в проекте Form Builder была переписана часть тестов на использование karma, что позволило ускорить тестирование компонентов с 70 тестов за 3 минуты до 366 тестов за 50 секунд. До:

После:
 Недостатки данного подхода: Но если ничего из этого списка вам и не нужно, можно смело пользоваться инструментом и получать удовольствие от скорости тестирования. И последняя тема, о которой хочется рассказать, — это анализ изменившихся файлов и запуск соответствующих тестов. Спустя какое-то время мы поняли, что нет смысла нагружать наши машины лишним тестированием, если изменилась только одна кнопочка. Представляете, раньше мы запускали 1000 тестов, в состав которых входят чек-боксы, инпуты, радио при исправлении какого-нибудь незначительного дефекта в кнопке. В этом нет смысла. Поэтому мы написали инструмент, похожий на lerna, который умеет анализировать, какие файлы были изменены, соответственно, какой компонент изменится и какие зависящие от этого компоненты могут быть затронуты. На основе этой информации решаем, какой набор тестов запускать. Например: Результат: При использовании описанных мною подходов к автоматизации тестирования микрофронтенда в Тинькофф получили следующие результаты: Team Lead QA Automation at OZON