Svelte
Тестирование: Vitest и Playwright
Команда фронтенда выкатывает корзину интернет-магазина. Кнопка 'Купить' визуально на месте, но после рефакторинга реактивного счётчика она перестаёт обновлять сумму. Ручное тестирование пропускает этот случай: тестировщик кликал по другому товару. В проде сумма заказа застывает на нуле, и за выходные теряется выручка. Один юнит-тест на пересчёт `$derived` и один e2e-сценарий 'добавить в корзину и оформить' поймали бы регрессию за секунды до деплоя.
- Svelte и SvelteKit сами тестируются на Vitest и Playwright - это дефолтный стек, который ставит create-svelte при выборе опции тестирования
- Playwright (Microsoft) гоняет один и тот же e2e-сценарий в Chromium, Firefox и WebKit, ловя браузерные различия до пользователей
- @testing-library/svelte заставляет проверять то, что видит пользователь (текст, роли), а не внутренности компонента - тесты переживают рефакторинг
- Vercel и Cloudflare прогоняют Playwright-сценарии в CI на каждый pull request перед промоушеном превью в прод
- Vitest переиспользует конфиг Vite проекта, поэтому алиасы, плагин svelte и обработка .svelte работают в тестах без отдельной настройки
Предварительные знания
- Понимание компонентов Svelte: props, разметка, события
- Базовые руны: `$state` и `$derived` на уровне 'состояние меняется - вычисляемое пересчитывается'
- Идея разницы между юнит-тестом (одна функция или компонент) и e2e-тестом (весь сценарий в браузере)
Vitest: юнит-тесты на конфиге Vite
Vitest - тест-раннер, построенный на Vite. Главное преимущество для проекта на Svelte: тот же конфиг Vite, тот же плагин svelte, те же алиасы путей. Не нужен отдельный transformer для .svelte или ts-jest. Чистая логика (валидаторы, форматтеры, стора) тестируется как обычный TypeScript, без браузера.
Vitest различает два окружения. Для чистой логики хватает окружения node - быстро и без эмуляции браузера. Для компонентов нужен DOM, его даёт jsdom или happy-dom. Окружение задаётся в конфиге глобально или через комментарий-аннотацию в начале файла теста.
Чистую логику стоит выносить из компонентов в отдельные модули .ts. Такой код тестируется в окружении node за миллисекунды, без рендера. Чем меньше логики внутри .svelte, тем дешевле и быстрее тесты.
Почему Vitest удобнее Jest для проекта на Svelte?
@testing-library/svelte: тестируем как пользователь
@testing-library/svelte рендерит компонент в jsdom и даёт API для поиска элементов так, как их находит человек: по роли, по тексту, по подписи. Принцип библиотеки - проверять наблюдаемое поведение, а не внутреннее устройство. Тест, который ищет кнопку по тексту 'Купить', переживёт переименование внутренней переменной; тест, который лезет в приватное поле, сломается на первом рефакторинге.
Метод userEvent имитирует реальное поведение пользователя (наведение, фокус, клик) точнее, чем низкоуровневый fireEvent. Каждое действие userEvent возвращает промис и сам дожидается, пока Svelte применит изменения к DOM, поэтому после await кнопка уже показывает новое значение.
| Запрос | Что ищет | Когда применять |
|---|---|---|
| getByRole | Элемент по ARIA-роли (button, heading, textbox) | Основной способ: совпадает с тем, как видит элемент скринридер |
| getByLabelText | Поле формы по связанной подписи | Инпуты, селекты, чекбоксы с label |
| getByText | Узел по видимому тексту | Заголовки, абзацы, статичный контент |
| getByTestId | Элемент по data-testid | Крайний случай, когда нет роли и текста |
Почему @testing-library советует искать элементы по роли и тексту, а не по CSS-классу или data-testid?
Тестирование рун и реактивного состояния
Руны Svelte 5 работают вне компонентов - в модулях .svelte.ts. Это позволяет тестировать реактивную логику изолированно, без рендера. Тонкость: обновления DOM в Svelte батчатся и применяются асинхронно. После изменения `$state` сама величина меняется сразу, но если проверяется разметка, нужно дождаться применения - через flushSync для синхронного слива или await на tick.
Файл с рунами обязан иметь расширение .svelte.ts (или .svelte.js), иначе компилятор не обработает руны и `$state` останется неопределённым. Обычный .ts-файл руны не понимает.
Когда `$derived` или `$effect` зависят от реактивного контекста, их оборачивают в `$effect.root`. Эта функция создаёт область, где эффекты и производные живут вне компонента, и возвращает cleanup-функцию для остановки. Для проверки DOM после изменения состояния в тестах компонентов вызывают flushSync, чтобы Svelte немедленно применил отложенные обновления.
Идея в том, что реактивную логику стоит держать в .svelte.ts-модулях и тестировать её отдельно от разметки. Тогда тест на бизнес-правило (как пересчитывается сумма корзины) не зависит от вёрстки и не требует jsdom.
После вызова метода, который меняет `$state`, тест проверяет текст в DOM и видит старое значение. В чём причина?
Playwright: end-to-end по реальным сценариям
Playwright запускает настоящий браузер и проходит пользовательский сценарий целиком: открыть страницу, заполнить форму, нажать кнопку, проверить результат. Это ловит то, что не видят юнит-тесты: реальную навигацию SvelteKit, серверные load-функции, гидратацию, поведение в Chromium, Firefox и WebKit одновременно.
Ключевая особенность Playwright - авто-ожидание. Метод expect с локатором повторяет проверку, пока она не пройдёт или не истечёт таймаут. Не нужны ручные паузы вида 'подожди 500 мс'. Локатор getByRole здесь работает по тому же принципу, что в @testing-library: единый словарь запросов снижает порог входа.
- Юнит и компонентные тесты (Vitest) — Быстрые, изолированные, запускаются сотнями за секунды. Проверяют логику и отдельные компоненты в jsdom. Дёшево держать много.
- End-to-end тесты (Playwright) — Медленнее, поднимают реальный браузер и сервер. Проверяют целые сценарии и интеграцию. Держат немного, только критичные пути.
Разумная пропорция - много дешёвых юнит- и компонентных тестов и несколько e2e на самые ценные сценарии (регистрация, оплата, оформление заказа). Покрывать каждую мелочь через Playwright дорого: такие наборы становятся медленными и хрупкими.
Какой сценарий разумнее покрыть end-to-end тестом на Playwright, а не компонентным тестом в jsdom?
Связь с другими темами
Тестирование - фундамент качества. Дальше курс показывает, что именно стоит проверять:
- Производительность — Тесты дают сетку безопасности: можно агрессивно оптимизировать реактивность, не боясь сломать поведение
- Доступность — Playwright + axe проверяют a11y в браузере, дополняя статические предупреждения компилятора
Итог
- Дефолтный стек тестирования Svelte в 2026 году: Vitest для юнитов и компонентов, Playwright для end-to-end
- Vitest переиспользует конфиг Vite, поэтому плагин svelte и алиасы работают в тестах без дублирования настроек
- @testing-library/svelte рендерит компонент и проверяет то, что видит пользователь (роли, текст), а не приватные детали
- Реактивность рун асинхронна по обновлению DOM - после изменения `$state` нужно дождаться flushSync или await tick перед проверкой разметки
- Playwright гоняет один сценарий в трёх движках (Chromium, Firefox, WebKit) и подходит для критичных пользовательских путей, не для каждой мелочи
Связанные уроки
- sv-38-svelte-performance — После того как тесты ловят регрессии, имеет смысл смотреть на производительность - тесты дают сетку безопасности для оптимизаций
- sv-39-accessibility-a11y-warnings — Playwright умеет проверять доступность через axe, а компилятор Svelte ловит a11y-проблемы ещё до тестов