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-проблемы ещё до тестов
Тестирование: Vitest и Playwright

0

1

Войти