Appearance
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.pageerror | uncaught exception в page context | TypeError: Cannot read properties of undefined в hydration |
page.console (type === 'error') | сообщение матчит один из 7 regex'ов Vue hydration mismatch | Hydration text mismatch, Hydration completed but contains mismatches |
Что НЕ ловит (намеренно):
| Источник | Почему |
|---|---|
console.warn | Vue в 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 |
/catalog | SSR с серверной пагинацией и фильтрами + cursor pagination в payload |
/product/1 | SSR карточки товара (динамический 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.