Проектирование многоуровневой архитектуры автотестов на Playwright JS для фичи публикации статьи в RealWorld App

Практическое руководство по созданию комплексной стратегии тестирования функционала Conduit API/SPA. Материал включает дерево тестов по 6 уровням, матрицу стратегий автоматизации и примеры кода на Playwright.

1. Архитектурное дерево тестов FEATURE-ARTICLE-01: Создание и публикация статьи

Архитектурное проектирование автотестов: Фича публикации статьи (FEATURE-ARTICLE-01)

Проектирование тестовой модели для современной SPA-системы требует понимания того, что один и тот же функционал «Создание статьи» должен проверяться на разных физических и логических уровнях. Ошибка в логике валидации заголовка на бэкенде не всегда ловится UI-тестом, а визуальный регресс формы ввода невозможно обнаружить через API. Ниже представлена детальная декомпозиция уровней тестирования для функционала публикации контента в приложении RealWorld (Conduit).

Дерево тестов FEATURE-ARTICLE-01: Создание и публикация статьи

В основе структуры лежит принцип изоляции ответственности. Каждый уровень решает свою задачу: [UNIT] проверяет чистые функции, [COMPONENT] — реакцию интерфейса на данные, [API] — бизнес-логику сервера, [CONTRACT] — целостность интерфейса взаимодействия, [E2E] — пользовательский сценарий целиком, [VISUAL] — корректность отрисовки.

🌳 FEATURE-ARTICLE-01 ├── 🟢 Позитивные │ ├── [Публикация статьи с валидными данными] → [Статья создана, редирект на страницу статьи] │ │ └→ test_unit_slugify_title() [UNIT, @fast] │ │ └→ test_comp_editor_form_submission() [COMPONENT, @ui] │ │ └→ test_api_create_article_success() [API, @smoke] │ │ └→ test_contract_article_schema() [CONTRACT, @schema] │ │ └→ test_e2e_full_publication_flow() [E2E, @regression] │ │ └→ test_visual_editor_layout() [VISUAL, @layout] │ └── [Добавление тегов при создании] → [Теги отображаются в списке и в метаданных] │ └→ test_unit_tags_parser() [UNIT, @fast] │ └→ test_comp_tag_input_chips() [COMPONENT, @ui] │ └→ test_api_tags_persistence() [API, @functional] │ └→ test_contract_tags_array_format() [CONTRACT, @schema] │ └→ test_e2e_tags_in_published_article() [E2E, @regression] │ └→ test_visual_tags_style() [VISUAL, @layout] ├── 🔴 Негативные │ ├── [Публикация с пустым заголовком] → [Ошибка 422, сообщение "can't be blank"] │ │ └→ test_unit_validation_logic() [UNIT, @fast] │ │ └→ test_comp_error_message_display() [COMPONENT, @ui] │ │ └→ test_api_empty_title_rejection() [API, @data-driven] │ │ └→ test_contract_error_response_structure() [CONTRACT, @schema] │ │ └→ test_e2e_ui_validation_feedback() [E2E, @regression] │ │ └→ test_visual_error_states() [VISUAL, @layout] │ └── [Публикация дублирующего заголовка] → [Ошибка уникальности Slug] │ └→ test_unit_slug_collision_resolver() [UNIT, @fast] │ └→ [N/A] (UI не знает о дублях до ответа) │ └→ test_api_duplicate_slug_error() [API, @functional] │ └→ test_contract_error_detail_schema() [CONTRACT, @schema] │ └→ test_e2e_duplicate_handling() [E2E, @regression] │ └→ [N/A] (Визуально идентично обычной ошибке) ├── 🟡 Граничные │ ├── [Статья с максимально допустимым телом (100k+ символов)] → [Успешно или 413] │ │ └→ test_unit_string_truncation() [UNIT, @boundary] │ │ └→ test_comp_textarea_performance() [COMPONENT, @performance] │ │ └→ test_api_payload_limit() [API, @boundary] │ │ └→ [N/A] (Контракт не зависит от длины) │ │ └→ test_e2e_large_article_render() [E2E, @performance] │ │ └→ test_visual_long_text_overflow() [VISUAL, @boundary] │ └── [Заголовок ровно в 1 символ] → [Успешная публикация] │ └→ test_unit_min_length_validator() [UNIT, @boundary] │ └→ test_comp_input_min_length() [COMPONENT, @ui] │ └→ test_api_min_title_length() [API, @boundary] │ └→ [N/A] (Контракт статичен) │ └→ test_e2e_short_title_flow() [E2E, @regression] │ └→ test_visual_short_title_alignment() [VISUAL, @layout] └── 🔒 Безопасность & Контракт ├── [Проверка JWT токена в заголовке Authorization] │ └→ [N/A] (Unit не проверяет заголовки HTTP) │ └→ [N/A] (Компонент лишь хранит токен) │ └→ test_api_auth_header_requirement() [API, @security] │ └→ test_contract_auth_header_format() [CONTRACT, @security] │ └→ test_e2e_expired_token_redirect() [E2E, @security] │ └→ [N/A] (Безопасность не имеет визуала) └── [Валидация JSON-схемы ответа сервера] └→ [N/A] (Unit работает с объектами) └→ [N/A] (Component доверяет типам TS/JS) └→ test_api_response_integrity() [API, @functional] └→ test_contract_full_body_schema() [CONTRACT, @schema] └→ [N/A] (E2E проверяет бизнес-результат) └→ [N/A] (Схема не влияет на пиксели)

Матрица стратегий автоматизации

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

| Параметр | API-First Strategy | Contract-Driven Strategy | Risk-Based Strategy | | :--- | :--- | :--- | :--- | | UNIT | 10% (базовая логика) | 5% (минимум) | 5% (критичные расчеты) | | COMPONENT | 10% (основные формы) | 10% (состояния UI) | 5% (только сложные виджеты) | | API | 50% (основной упор) | 10% (функциональные) | 30% (Happy Path + Ошибки) | | CONTRACT | 20% (валидация схем) | 60% (Consumer-Driven) | 10% (только ключевые API) | | E2E | 5% (дымный тест) | 10% (интеграция) | 40% (сценарии юзеров) | | VISUAL | 5% (главная страница) | 5% (библиотека комп.) | 10% (кросс-браузерность) | | PR CI Tags | @fast, @schema | @contract, @ui | @smoke, @api | | Nightly Tags | @api, @functional | @regression, @api | @regression, @visual | | Release Tags | @smoke, @security | @smoke, @e2e | @smoke, @performance | | Обоснование | Максимальная скорость и стабильность. Идеально для микросервисов. | Гарантия совместимости фронта и бэка. Снижает риск "разлома" интеграции. | Фокус на том, что реально видит клиент. Высокая уверенность в бизнес-процессе. |

Реализация на Playwright JS

Ниже представлены примеры реализации трех ключевых уровней для фичи FEATURE-ARTICLE-01. Каждый пример демонстрирует специфику работы с инструментарием Playwright.

1. CONTRACT-тест: Валидация схемы ответа

Этот тест гарантирует, что бэкенд возвращает данные именно в том формате, который ожидает фронтенд. Мы не проверяем здесь бизнес-логику (например, правильно ли обрезался заголовок), мы проверяем структуру.

2. COMPONENT-тест: Изолированная проверка UI через перехват трафика

Здесь мы используем page.route(), чтобы полностью исключить влияние бэкенда. Мы проверяем, как UI обрабатывает ошибку сервера. Это позволяет тестировать фронтенд даже если бэкенд еще не готов или нестабилен.

3. E2E-тест: Критический путь (Happy Path)

Полносвязный тест, который проходит путь реального пользователя от ввода данных до проверки результата в базе (через UI). Никаких моков.

Углубление в уровни: Нюансы и граничные случаи

При проектировании архитектуры для Junior-инженеров важно пояснить, почему мы не можем ограничиться только E2E тестами. Основная проблема E2E — это их «хрупкость» (flakiness) и высокая стоимость поддержки. Если изменится селектор кнопки, упадут все E2E тесты. Если же мы разнесем логику, то:

  • UNIT проверит, что функция generateSlug("Hello World") всегда возвращает hello-world. Это стоит микросекунды.
  • API проверит, что при отправке дублирующего слага база данных возвращает конфликт.
  • VISUAL проверит, что кнопка "Publish" не уехала за пределы экрана на iPhone SE.
  • Визуальное тестирование [VISUAL]

    Для фичи публикации статьи визуальное тестирование критично в двух местах:
  • Редактор (Markdown Editor): Нужно убедиться, что панель инструментов не перекрывает поле ввода и корректно отображается на разных разрешениях.
  • Отрисовка статьи: Проверка того, что стили (заголовки, списки, цитаты) применяются правильно.
  • В Playwright это реализуется через expect(page).toHaveScreenshot(). Важно использовать этот уровень только там, где логика бессильна (цвета, отступы, шрифты).

    Контрактное тестирование [CONTRACT]

    Часто путают API и Contract тесты.
  • API тест проверяет функциональность: "Если я создам статью, она появится в списке".
  • Contract тест проверяет соглашение: "Поле createdAt всегда должно быть в формате ISO-8601, и оно не может исчезнуть из ответа".
  • Контрактные тесты — это страховка фронтенда. Если бэкенд-разработчик решит переименовать tagList в tags, контрактный тест упадет первым, еще до того, как сломается логика UI.

    Тактические правила для инженеров

    Для эффективной работы в команде и поддержания чистоты архитектуры, следуйте следующим правилам:

  • Принцип "Где логика?": Если вы проверяете регулярное выражение для валидации email — это UNIT тест. Если вы проверяете, что сообщение об ошибке красного цвета — это VISUAL. Если вы проверяете, что данные сохранились в БД — это API или E2E. Никогда не проверяйте регулярки через браузер.
  • Мокируй с умом: Используйте page.route() (Component level) для тестирования редких состояний: 500 ошибка сервера, долгий ответ (тайм-аут), пустые списки. Не нужно ждать реальной ошибки сервера, чтобы проверить, как UI ее отобразит.
  • Тегирование — это не декор:
  • - @smoke — только 1-2 теста на фичу (самый важный путь). Должны проходить за < 2 минут на весь проект. - @regression — все остальные позитивные и негативные сценарии. - @performance — тесты с большими данными или замером времени. - @flaky — временно помеченные нестабильные тесты, которые не должны блокировать CI.
  • Избегайте зависимостей между тестами: Каждый тест должен сам создавать себе данные. Если для теста "Редактирование статьи" нужна статья — создайте её через API request.post() в beforeEach, а не полагайтесь на то, что её создал предыдущий тест "Создание статьи".
  • Проектирование по этой модели позволяет создать тестовый набор, который легко масштабируется. При добавлении новой функциональности (например, загрузка изображений в статью) вы просто добавляете новую ветку в дерево, распределяя проверки по тем же шести уровням. Это делает процесс тестирования прозрачным для всей команды разработки.