Реальный проект редко тестируется в одной среде. Серверная логика хочет чистый Node, компоненты - DOM, а часть кода стоит гонять в настоящем браузере. Vitest покрывает это через test.projects - массив конфигураций внутри одного запуска, где у каждого проекта своя среда, свой include и свои настройки. Прежнее слово workspace для этого в четвёртой версии убрали: конфигурацию проектов теперь держат прямо в поле projects секции test.
Разделение по проектам решает частую боль - один конфиг на разнородный код. Вместо того чтобы гонять серверные тесты в jsdom без нужды, а компонентные оставлять без DOM, заводят два проекта: один с environment 'node' и своим include на серверные файлы, другой с 'jsdom' на компоненты. Vitest прогоняет оба в одном вызове и сводит отчёт вместе, а каждый тип кода едет в своей среде.
Отдельная тема - pools, механизм, в котором физически исполняются тесты. По умолчанию в четвёртой версии это forks: каждый воркер - отдельный процесс, что даёт настоящую изоляцию, в том числе для нативных модулей и глобального состояния. Альтернатива threads на воркер-потоках обычно быстрее за счёт меньших накладных расходов, но слабее изолирует: разделяемое состояние и капризные нативные аддоны там ведут себя менее предсказуемо.
Выбор между ними - это обмен скорости на изоляцию. threads берут, когда тесты чистые и независимые, а важна скорость на большом наборе. forks оставляют, когда в игре нативные модули, глобальные синглтоны или тесты, которые нельзя пускать в общей памяти. Значение по умолчанию неслучайно: изоляция важнее пары процентов скорости, пока не доказано обратное.
Настройки пула в четвёртой версии стали плоскими. Раньше их прятали в poolOptions с вложенностью по типу пула; теперь ключевые опции вроде isolate и maxWorkers задают прямо в секции test, без лишнего уровня. isolate: false отключает пересоздание окружения между файлами и ускоряет прогон, но снимает часть изоляции - его включают осознанно, когда точно знают, что тесты не пачкают общий мир.
Browser Mode - это исполнение тестов в настоящем браузере вместо эмуляции jsdom. Включают его в секции browser: enabled, провайдер и список instances с конкретными браузерами. В четвёртой версии провайдер задают функцией из отдельного пакета - import { playwright } from '@vitest/browser-playwright', и передают provider: playwright(). Каждый instance описывает браузер, например chromium, и все они прогоняются как проекты.
Смысл Browser Mode - в доступе к настоящим API платформы. jsdom эмулирует DOM, но не является браузером: реальные layout, geometry, события ввода, буфер обмена, фокус ведут себя в нём приблизительно. Когда тест зависит именно от такого поведения - измерение размеров, hover, настоящий клик по перекрытому элементу - его гоняют в браузере, где API не эмуляция, а сам движок. Цена - скорость и инфраструктура, поэтому в браузер выносят не всё, а то, что jsdom честно не воспроизводит.
Типичные провалы здесь про несоответствие среды задаче. Гонять всё в jsdom и удивляться, что тест на измерение размеров врёт, - jsdom их не считает по-настоящему. Тащить весь набор в Browser Mode ради пары DOM-зависимых тестов - медленно и дорого. И держать разнородный код в одном проекте вместо test.projects - значит навязывать серверным тестам DOM, а компонентным отнимать его; правильнее разложить по проектам и средам.
// vitest.config.ts - один запуск, разные среды
export default defineConfig({
test: {
projects: [
{ test: { name: 'node', environment: 'node', include: ['src/server/**/*.test.ts'] } },
{ test: { name: 'dom', environment: 'jsdom', include: ['src/ui/**/*.test.tsx'] } },
],
},
})npm i -D @vitest/browser-playwright
import { playwright } from '@vitest/browser-playwright'
export default defineConfig({
test: {
browser: {
enabled: true,
provider: playwright(),
instances: [{ browser: 'chromium' }],
},
},
})