Skip to content

E2E: Hydration-guard fixture

Последнее обновление: 2026-06-01 (DEV-424)

Playwright fixture, защищающий публичные SSR-страницы от silent регрессии (de)serialization SSR-payload после CVE-bump транзитивных deps (devalue, unhead, vite, protobufjs).

Зачем

Nuxt SSR сериализует payload (Date, Map, Set, useAsyncData результаты) через devalue. Если bump транзитивной dep сломает wire-формат — гидратация падает silent в production:

  • Vue в prod-сборке подавляет [Vue warn] для hydration mismatch (логируется только в dev).
  • Catch-all pageerror может быть единственным сигналом.
  • Unit-smoke (devalue-roundtrip.smoke.test.ts) проверяет API библиотеки, но не реальный путь Nuxt SSR → browser hydration.

Fixture закрывает этот разрыв через настоящий browser в Playwright.

Где живёт

apps/main/tests/e2e/
├── helpers/
│   └── hydration-guard.ts        ← fixture (extend Playwright `test`)
└── smoke/
    └── ssr-hydration.spec.ts     ← smoke на /, /catalog, /product/:id

Что ловит

ИсточникУсловиеПример
page.pageerroruncaught exception в page contextTypeError: Cannot read properties of undefined в hydration
page.console (type === 'error')сообщение матчит один из 7 regex'ов Vue hydration mismatchHydration text mismatch, Hydration completed but contains mismatches

Что НЕ ловит (намеренно):

ИсточникПочему
console.warnVue в prod-сборке подавляет [Vue warn] для hydration, в dev шумит без отражения prod-риска
Сетевые 5xxсмежная зона мониторинга, не hydration scope
Visual regressionтребует separate snapshot infra

Использование

ts
// Вместо стандартного `@playwright/test`:
import { test, expect } from '../helpers/hydration-guard'

test('страница SSR без runtime-ошибок', async ({ page }) => {
  await page.goto('/some-ssr-route')
  await page.waitForLoadState('networkidle')
  // fixture в afterEach сам кинет с агрегированным отчётом если что-то поймал
})

При срабатывании в HTML report Playwright прикрепляется attachment hydration-guard-report с агрегированным списком pageerror + hydration messages.

Что покрыто smoke-spec'ом

RouteЧто проверяет
/главная: SSR + useAsyncData каталог/категории + i18n hydration
/catalogSSR с серверной пагинацией и фильтрами + cursor pagination в payload
/product/1SSR карточки товара (динамический route), soft-skip если 404

Admin app не покрывается — SPA (ssr: false), hydration не применима.

Когда использовать в новых spec'ах

СценарийИспользовать guard?
SSR-страница (ssr: true) — публичные, SEO-критичныеда — импортируй test из helpers/hydration-guard
SPA-only (cabinet, admin)нет — нет hydration, ничего не поймает
API-only E2E без UI (vendor CRUD через page.request)нет — нет page-контекста
Тесты ошибочного UX (404, validation errors на форме)внимание — pageerror от UI-ошибок может ложно срабатывать. Использовать стандартный @playwright/test если ловить не нужно

Связанное

  • DEV-422 — bump devalue (исходный CVE-trigger)
  • DEV-418/419/420/421 — bumps protobufjs (через centrifuge wire был бы JSON, не protobuf — см. ниже)
  • DEV-423 — unit smoke (centrifuge ctor + devalue roundtrip)
  • DEV-424 — этот fixture (Lite scope follow-up к DEV-423)

Centrifuge mock-WS integration (не реализовано, контекст)

В оригинальной acceptance DEV-424 был integration через mock WS-сервер для протокол-фреймов centrifuge ↔ protobufjs. Не реализовано после research:

  • useCentrifugo.ts создаёт клиент без protocol: 'protobuf' опции → centrifuge по умолчанию использует JSON на wire.
  • protobufjs участвует только в init parsing schema definitions, не в wire-format → centrifuge-library.smoke.test.ts (4 unit-теста) уже косвенно покрывает init.
  • Mock-WS integration добавил бы проверку JSON encode/decode subscribe→publish — но не protobuf wire. Value-add ниже ожидаемого.

Если в будущем проект переключится на protobuf protocol (new Centrifuge(url, { protocol: 'protobuf' })) — завести отдельный тикет на integration через mock-socket или @centrifugal/centrifugo-test.