Тестирование микрофронтендов: как проверять изолированные модули и их интеграцию

Коротко:

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

Практичная схема выглядит так:

  1. В пайплайне каждого модуля: компонентные тесты, юниты, проверка TypeScript-типов, визуальная регрессия.
  2. После деплоя модуля: триггер интеграционных тестов в общем репозитории сборки.
  3. В общем пайплайне сборки: интеграционные тесты и 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, маршрутизация, стейт
E2EPlaywright, 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 в тестах не всегда точенПеред крупными релизами
Совместимость в SafariPlaywright-webkit не идентичен реальному Safari на iOSПри изменениях в CSS или JS
Работа при параллельных вкладкахСложно воспроизвести в автотестеДля критичных сценариев с авторизацией

часто задаваемые вопросы

В SPA весь код собирается вместе в момент сборки. В микрофронтендной архитектуре модули загружаются динамически в рантайме, каждый из своего источника. Это добавляет риски недоступности remote, конфликтов версий зависимостей и проблем изоляции - всего этого нет в классическом SPA.

Создайте отдельное интеграционное окружение, где каждый модуль деплоится в staging-версии. CI-пайплайн после деплоя любого модуля автоматически запускает интеграционные тесты в этом окружении. Для локальной разработки используйте мок-данные remote-модулей или запускайте несколько dev-серверов параллельно.

Нет. E2E должен покрывать сквозные пользовательские сценарии, которые проходят через несколько модулей. Внутреннюю логику каждого модуля закрывайте компонентными тестами - это быстрее и надёжнее.

Добавьте в CI скрипт, который сравнивает версии shared-зависимостей (React, ReactDOM, router) во всех modülях сборки. Если версии расходятся - билд падает с понятным сообщением, до того как это попадёт в staging.

Это проблема изоляции CSS. Решения: CSS Modules (локальные классы), Shadow DOM, CSS-in-JS. С точки зрения тестирования: добавьте визуальные тесты, которые проверяют внешний вид shell и соседних модулей после загрузки проблемного. Это ловит регрессию при каждом обновлении.

Используйте перехват сетевых запросов в Playwright или Cypress: заблокируйте endpoint модуля и проверьте, что shell корректно обрабатывает ошибку загрузки. Shell должен показывать fallback-компонент, а не выбрасывать необработанное исключение.

Итог

Стратегия QA в проектах с микрофронтендной архитектурой строится на трёх уровнях: компонентные тесты внутри каждого модуля, интеграционные проверки в сборке и E2E для ключевых пользовательских маршрутов. Пропускать средний уровень - значит узнавать об интеграционных проблемах в продакшне.

Самые болезненные баги живут не в логике компонентов, а на стыках: версии зависимостей, CSS-утечки, маршрутизация, передача данных от shell. Именно эти зоны требуют отдельного внимания и специфических проверок, которые нельзя закрыть ни юнит-тестами, ни обычными E2E.

Начните с малого: добавьте проверку загрузки каждого модуля в сборке в CI, договоритесь о синхронизации версий shared-зависимостей, напишите 3-5 интеграционных тестов на маршрутизацию и передачу данных. Это уже даст видимость туда, где раньше был слепой участок.