Коротко:
- Каждый модуль тестируют в изоляции: компонентные тесты, контракт экспортов, визуальная стабильность.
- На уровне сборки проверяют загрузку через shell, маршрутизацию, общий стейт и совместимость версий зависимостей.
- Самые частые баги живут не внутри модуля, а на стыке: конфликт версий React, утечка CSS, сломанный роутинг при переходе между модулями.
- Module Federation добавляет отдельный слой рисков: удалённые модули могут не загрузиться, версии shared-зависимостей расходятся, fallback отсутствует.
- E2E нужен, но не заменяет изолированные проверки - он слишком медленный, чтобы покрывать логику каждого модуля.
Почему стандартный подход к QA здесь не работает
Когда фронтенд - монолит, граница ответственности понятна: есть одно приложение, один CI-пайплайн, один деплой. Если что-то сломалось, ищешь в одном месте.
С микрофронтендами картина другая. Приложение собирается из нескольких независимо задеплоенных модулей: корзина, каталог, авторизация, профиль. Каждый разрабатывает отдельная команда. Каждый может обновляться в своём ритме. Shell-приложение (оболочка) собирает их вместе во время загрузки в браузере.
QA-инженер оказывается в ситуации, когда модуль работает идеально в своём репозитории - а в сборке ломается. Причина может быть в версии React, в CSS, в типах данных, которые shell передаёт не так, как ожидает модуль. Искать такие баги без стратегии - долго и непредсказуемо.
В этой статье разбираем, как выстроить тестирование в проектах с микрофронтендной архитектурой: что проверять на уровне каждого модуля, что - на уровне сборки, и где чаще всего прячутся дефекты, которые не видны в happy path.
Как устроена архитектура и что это значит для QA
Микрофронтенд - это независимо развёртываемая часть пользовательского интерфейса. Несколько таких частей объединяются в одно приложение через shell - оболочку, которая управляет маршрутизацией, загружает нужные модули и, возможно, передаёт им общий контекст (токен авторизации, язык, тему).
Самый распространённый механизм интеграции сегодня - Webpack Module Federation. Он позволяет одному приложению динамически загружать код из другого во время выполнения. Альтернативы: iframe-интеграция, web components, server-side composition. У каждого подхода свои риски для тестирования.
Для QA это означает три принципиально разных уровня проверки:
- Уровень модуля - проверяем, что компонент работает корректно сам по себе, его API стабилен, поведение предсказуемо.
- Уровень интеграции - проверяем, как модуль ведёт себя внутри shell: загружается, получает данные, не конфликтует с соседними.
- Уровень пользовательского сценария - E2E через всё приложение: пользователь переходит из каталога в корзину, оформляет заказ.
Ошибка, которую делают многие команды, - пропускать второй уровень и сразу идти к E2E. В результате интеграционные баги находятся поздно и дорого воспроизводятся.
Тестирование модуля в изоляции
Каждый микрофронтенд должен иметь собственный набор тестов, которые запускаются в его репозитории независимо от сборки. Это базовая гарантия того, что внутренняя логика модуля не регрессирует при обновлениях.
Компонентные тесты
Компонентное тестирование в изоляции проверяет отдельные UI-компоненты модуля: рендеринг, поведение при взаимодействии, корректность отображения данных. Инструменты - Vitest + React Testing Library, Jest, или Cypress Component Testing.
Что проверять:
- Рендер с корректными пропсами и с граничными значениями (пустые строки, null, длинные тексты).
- Поведение при взаимодействии: клик, фокус, ввод данных.
- Условный рендер: компонент правильно скрывает/показывает элементы при разных состояниях.
- Обработка ошибок: что показывается, если данные не пришли или пришли в неожиданном формате.
Важный момент: тестируйте компоненты с реалистичными данными, не только с минимальными. Модуль может прекрасно рендериться с тестовой строкой из 5 символов - и ломать вёрстку с реальным текстом из CMS.
Проверка публичного контракта
У каждого микрофронтенда есть «публичный интерфейс» - то, что он экспортирует для shell и других модулей. Это могут быть React-компоненты, функции, события, типы данных.
Если этот контракт меняется без уведомления - ломается сборка. QA должен убедиться, что публичный интерфейс стабилен и изменения в нём не проходят незамеченными.
На практике это означает:
- Тесты на экспортируемые компоненты с обязательными пропсами.
- Snapshot-тесты для критичных экспортов (с осторожностью - они часто дают ложные позитивы при несущественных изменениях вёрстки).
- TypeScript-типы как живая документация контракта. Если типы ломаются - CI падает раньше, чем попадёт в сборку.
Визуальное регрессионное тестирование
Компонентный тест не поймает, что кнопка стала на 2px выше или цвет изменился. Для этого нужны визуальные снимки.
Storybook + Chromatic или Percy позволяют делать скриншоты компонентов в разных состояниях и сравнивать с baseline. Это особенно полезно для дизайн-системы, которую используют несколько модулей - изменение токена цвета в одном месте может неожиданно затронуть другие.
Тестирование на уровне интеграции через shell
Когда каждый модуль зелёный в своём репозитории, начинается самое интересное. Shell загружает их вместе - и здесь появляются дефекты, которые нельзя поймать в изоляции.
Загрузка и инициализация
Первое, что нужно проверить - что модуль вообще загружается в shell без ошибок в консоли. Звучит тривиально, но при динамической загрузке через Module Federation это не гарантировано.
Что проверять:
- Модуль загружается при переходе на нужный маршрут.
- Нет ошибок в консоли браузера при загрузке.
- Если загрузка провалилась (сеть упала, endpoint недоступен) - shell показывает fallback, а не белый экран.
- Время загрузки модуля укладывается в допустимое: при динамическом импорте пользователь не должен ждать 5 секунд до появления контента.
Конфликты версий зависимостей
Это один из самых коварных классов проблем в микрофронтендной архитектуре. Модуль A использует React 18.2, модуль B - React 18.3. Module Federation может загрузить обе версии одновременно, что приводит к ошибкам вида «Hooks can only be called inside a function component» - даже если хуки используются правильно.
Как проверять:
- Убедитесь, что shared-зависимости в webpack.config для каждого модуля совместимы. Проверяйте это как часть CI - скрипт, который сравнивает версии зависимостей в remotes и host.
- После обновления любого модуля запускайте интеграционные тесты в сборке, не только юниты.
- В браузере смотрите на количество загруженных chunk'ов - если одна библиотека загружается дважды, это симптом конфликта singleton-зависимостей.
Частая ошибка: разработчики указывают singleton: true для React в настройках Module Federation, но забывают синхронизировать эту настройку между всеми модулями. В результате один модуль ожидает singleton, другой загружает свою копию - и хуки ломаются в рантайме.
Изоляция стилей
CSS в браузере глобален по умолчанию. Если модуль не изолирует свои стили, они «вытекают» на соседние модули и на shell. Это особенно болезненно при использовании глобальных CSS-классов или сбросе стилей (reset.css).
Что проверять:
- Стили модуля не меняют внешний вид элементов вне его контейнера.
- Shell не теряет свои стили при загрузке модуля.
- При удалении (размонтировании) модуля его стили не остаются в DOM.
- Темная тема и другие CSS-переменные, определённые в shell, корректно наследуются внутри модуля.
Лучший способ проверить изоляцию: открыть DevTools, переключить классы на элементах вне текущего модуля и посмотреть, меняется ли что-то неожиданное.
Маршрутизация и история браузера
Маршрутизация в микрофронтендах - традиционно больное место. Shell управляет верхним уровнем маршрутов, модули - вложенными. Когда они используют разные роутеры (например, один на React Router 5, другой на React Router 6), начинаются конфликты.
Сценарии для проверки:
- Переход из модуля А в модуль Б через ссылку - URL обновляется корректно.
- Прямой переход по URL внутри модуля (например, /catalog/product/123) - страница загружается без ошибок, а не показывает 404.
- Кнопка «Назад» браузера работает ожидаемо: возвращает на предыдущую страницу, а не на неопределённое состояние.
- Перезагрузка страницы на глубоком маршруте - приложение восстанавливает состояние корректно.
Общий стейт и передача данных
Shell часто выступает «шиной» данных: передаёт токен авторизации, выбранный язык, ID пользователя. Модули либо получают эти данные через пропсы, либо читают из глобального стора, либо через кастомные события.
Что проверять:
- Модуль получает актуальные данные при монтировании.
- При смене языка или темы в shell - модуль реагирует без перезагрузки страницы.
- При логауте - все модули очищают свои данные и не показывают данные предыдущего пользователя.
- Если shell передаёт данные с задержкой (например, токен ещё не пришёл с сервера), модуль не падает с ошибкой, а корректно ждёт или показывает состояние загрузки.
Module Federation тестирование: специфические риски
Webpack Module Federation - самый популярный способ реализации микрофронтендов в 2024-2025. Он добавляет несколько уникальных классов проблем, которые стоит проверять отдельно.
| Риск | Симптом | Как проверять |
|---|---|---|
| Remote недоступен | Белый экран или JavaScript-ошибка при переходе на маршрут | Отключить remote-сервер, проверить fallback |
| Конфликт singleton | Хуки не работают, «Invalid hook call» | Проверить настройки shared в webpack.config всех модулей |
| Кэширование старой версии | После деплоя пользователь видит старый модуль | Проверить cache-busting стратегию, загрузить с hard refresh |
| Несовместимые типы | Модуль падает в рантайме при получении данных от shell | Интеграционные тесты с реальными данными от shell |
| Circular dependency | Зависание при загрузке или бесконечная рекурсия | Анализ графа зависимостей, проверка консоли при загрузке |
Как организовать тесты в CI/CD
При микрофронтендной архитектуре CI/CD пайплайны у каждого модуля - свои. Это создаёт проблему: как убедиться, что обновление одного модуля не сломало сборку?
Практичная схема выглядит так:
- В пайплайне каждого модуля: компонентные тесты, юниты, проверка TypeScript-типов, визуальная регрессия.
- После деплоя модуля: триггер интеграционных тестов в общем репозитории сборки.
- В общем пайплайне сборки: интеграционные тесты и E2E на staging-окружении, где загружены все актуальные версии модулей.
Инструменты для интеграционных и E2E тестов в сборке: Playwright или Cypress. Они хорошо работают с динамически загружаемыми приложениями, умеют ждать загрузки ленивых чанков.
Пример: команда маркетплейса разделила QA на два слоя. Каждый из четырёх модулей (каталог, корзина, оформление заказа, профиль) имеет 150-200 компонентных тестов. Общий staging-пайплайн запускает 40 E2E-сценариев, покрывающих ключевые пользовательские маршруты. Время полного прогона компонентных тестов - 3 минуты на модуль. E2E в сборке - 12 минут. Интеграционные баги стали находить на staging, а не в продакшне.
Что чаще всего ломается на стыках
По опыту команд, работающих с микрофронтендной архитектурой, большинство продакшн-инцидентов приходит не из-за ошибок внутри модуля, а именно из-за проблем на стыках. Вот самые частые сценарии:
- Несовпадение версий shared-библиотек. Один модуль обновился, другой нет. В prod появляются странные ошибки рендера.
- Утечка CSS. Модуль использует глобальный класс
.container, который переопределяет стили shell или другого модуля. - Broken маршрутизация. После деплоя нового модуля перестают работать deep links в соседнем модуле.
- Гонка при инициализации. Модуль пытается читать данные из глобального стора раньше, чем shell его инициализировал.
- Memory leak при переключении. Модуль не очищает слушателей событий при размонтировании - при переходах между разделами память растёт.
- Сломанный fallback. Remote-endpoint модуля недоступен, но shell не обрабатывает эту ситуацию - пользователь видит белый экран вместо понятного сообщения.
Чеклист для QA перед релизом модуля
- Компонентные тесты зелёные, покрывают граничные состояния (пустые данные, ошибки, длинные строки).
- TypeScript-типы компилируются без ошибок, публичный контракт не менялся (или изменения задокументированы).
- Визуальные снимки не изменились неожиданно.
- Модуль загружается в интеграционной сборке без ошибок в консоли.
- Стили модуля не влияют на элементы вне его контейнера.
- Маршрутизация работает: прямой переход по URL, кнопка «Назад», переход из соседнего модуля.
- Данные от shell принимаются корректно, включая задержку их появления.
- При логауте данные очищаются.
- Если remote недоступен, shell показывает fallback, а не падает.
- Версии shared-зависимостей не расходятся с другими модулями сборки.
Инструменты для разных уровней
| Уровень | Инструменты | Для чего |
|---|---|---|
| Компонентные тесты | Vitest, Jest, React Testing Library, Cypress Component Testing | Логика компонентов в изоляции |
| Визуальная регрессия | Storybook + Chromatic, Percy, Playwright screenshots | Стабильность внешнего вида |
| Интеграционные тесты | Playwright, Cypress | Загрузка в shell, маршрутизация, стейт |
| E2E | Playwright, Cypress | Сквозные пользовательские сценарии |
| Анализ зависимостей | webpack-bundle-analyzer, custom scripts | Поиск дублированных библиотек |
Когда изолированные тесты не спасают
Важно честно обозначить ограничения. Даже отличные компонентные тесты в каждом модуле не гарантируют, что сборка будет работать.
Проверки в изоляции не покрывают: реальное поведение при динамической загрузке через сеть, конфликты версий в рантайме, взаимодействие между модулями через события или глобальный стор, работу маршрутизации в контексте реального shell.
Именно поэтому интеграционные тесты в сборке - не опция, а обязательный элемент стратегии. Без них команда узнаёт об интеграционных проблемах либо от пользователей, либо во время ручного smoke на staging.
Как версионировать публичный контракт и договориться об изменениях между командами
Одна из самых недооценённых проблем в проектах с несколькими независимыми командами - отсутствие явного процесса управления изменениями в публичных интерфейсах. Когда команда каталога меняет имя события или формат данных, которые ожидает корзина, баг появляется не сразу: модули продолжают работать порознь, а ломаются только в сборке.
QA здесь выступает не просто исполнителем тестов, а человеком, который должен поднять вопрос: а как вообще команды договариваются об изменениях контракта?
Практические шаги:
- Любое изменение публичного интерфейса (события, пропсы, формат данных) фиксируется в CHANGELOG модуля до деплоя, а не после.
- Breaking changes требуют согласования с командами-потребителями до выхода в staging. QA проверяет, что потребители обновили свою сторону.
- Версионирование remote-точки: если модуль экспортирует несколько версий компонента (v1, v2), shell явно указывает, какую использует. Это даёт возможность мигрировать постепенно.
- Contract-тесты (например, через Pact) фиксируют ожидания потребителя и проверяются в пайплайне провайдера. Если контракт нарушен - CI провайдера падает до деплоя.
Как тестировать производительность загрузки в сборке
Динамическая загрузка частей приложения удобна для команд, но создаёт риск для пользователя: каждый переход между разделами может инициировать сетевой запрос за новым чанком. Если remote-сервер медленный или чанк большой, пользователь видит задержку или пустой экран.
QA должен включать в чеклист перед релизом не только функциональные, но и базовые перфоманс-проверки:
- Время до появления контента при первом переходе на маршрут не превышает принятый командой порог (обычно 2-3 секунды на среднем соединении).
- Повторный переход на тот же раздел не скачивает чанк заново: браузер использует кэш.
- Размер чанка не вырос неожиданно после рефакторинга: добавьте проверку bundle size в CI с допустимым порогом.
- При медленном соединении (throttling в DevTools до «Slow 3G») приложение показывает skeleton или spinner, а не пустую область.
Инструменты: Lighthouse CI для автоматического трекинга метрик в пайплайне, webpack-bundle-analyzer для ручного анализа после крупных изменений. Playwright умеет эмулировать медленные соединения прямо в тестах через параметры контекста.
Тестирование error boundaries и деградации интерфейса
Когда одна часть приложения падает с необработанным исключением, вся страница не должна становиться белым экраном. Для этого в React-приложениях используют error boundaries - компоненты, которые перехватывают ошибки дочерних элементов и показывают fallback-UI.
Проверка этого слоя часто пропускается при тестировании, потому что в happy path он никогда не активируется.
Что стоит проверить:
- Каждая часть приложения обёрнута в error boundary на уровне shell. Если одна область упала, остальные продолжают работать.
- Fallback-UI информативен: пользователь видит понятное сообщение, а не технический стектрейс.
- После ошибки пользователь может перейти в другой раздел без перезагрузки страницы.
- Ошибки попадают в систему мониторинга (Sentry или аналог): QA проверяет, что при намеренно сломанном компоненте событие фиксируется.
Способ воспроизвести: в тесте подменить компонент версией, которая выбрасывает исключение в render. Playwright позволяет перехватывать JS-ошибки через page.on('pageerror').
Хорошая практика: добавьте в интеграционный тест сценарий «падение одной области». Заблокируйте загрузку одной части приложения и убедитесь, что навигация и другие разделы остаются работоспособными. Это занимает 10 минут на написание, но даёт уверенность в том, что частичный сбой не выкладывает весь продукт.
Особенности ручного тестирования в архитектуре с несколькими командами
Автоматизация не отменяет ручные проверки полностью. В проектах, где несколько команд деплоят независимо, ручной smoke важен именно потому, что никакой пайплайн не воспроизведёт все возможные комбинации версий, которые могут оказаться в проде одновременно.
Несколько правил, которые упрощают ручное тестирование в таком контексте:
- Договоритесь с командами о едином staging-окружении, где всегда стоят актуальные версии всех частей. Тестировать на локальных сборках с заглушками недостаточно.
- Ведите матрицу совместимости: какая версия каждой части сейчас в проде и какая на staging. Это помогает быстро локализовать регрессию при инциденте.
- При ручном smoke переходите между разделами через навигацию, а не только через прямые URL. Переходы часто провоцируют баги маршрутизации, которые не воспроизводятся при прямом заходе.
- Проверяйте поведение при обновлении страницы на каждом маршруте: это воспроизводит сценарий, когда пользователь открыл закладку или поделился ссылкой.
| Что проверять вручную | Почему нельзя только автоматически | Частота |
|---|---|---|
| Визуальная целостность после деплоя | Автоматические снимки не покрывают все комбинации браузер+ОС | При каждом деплое в staging |
| Переходы между разделами в браузере | Реальный браузер ведёт себя иначе, чем headless | При изменении маршрутизации |
| Поведение при медленной сети | Throttling в тестах не всегда точен | Перед крупными релизами |
| Совместимость в Safari | Playwright-webkit не идентичен реальному Safari на iOS | При изменениях в CSS или JS |
| Работа при параллельных вкладках | Сложно воспроизвести в автотесте | Для критичных сценариев с авторизацией |