Angular
Тестирование: Vitest и Playwright
Тесты компонента шли 40 секунд на холодном старте: Karma поднимала реальный браузер через WebDriver, грузила Webpack-бандл и только потом запускала спеки. Watch-режим реагировал на правку с заметной паузой. В Angular 21 (ноябрь 2025) дефолтным раннером стал Vitest: он исполняет тесты на esbuild, переиспользует трансформацию из application builder и запускает компоненты в jsdom или в реальном браузере через Playwright по выбору. Тот же тестовый файл с TestBed теперь стартует за доли секунды, а watch пересобирает только затронутое.
- Angular 21 сделал Vitest рекомендованным раннером по умолчанию, а Karma официально выведена из эксплуатации после многих лет в этой роли
- Component harnesses из Angular CDK тестируют Material-компоненты без привязки к их внутренней разметке - один и тот же тест переживает рефакторинг шаблона
- Playwright в крупных проектах гоняет e2e в Chromium, Firefox и WebKit параллельно, с авто-ожиданиями и трейсами при падении
- Монорепо команд: unit-тесты на Vitest дают мгновенный фидбек в watch, e2e на Playwright проверяют критичные пути в CI
- Zoneless-приложения: тесты сигналов опираются на явный fixture.detectChanges и whenStable вместо неявной магии Zone.js
Предварительные знания
- Компоненты, их шаблоны, signal inputs и outputs
- Базовое понимание автотеста: arrange, act, assert
- Идея обнаружения изменений (change detection): почему вид обновляется после изменения данных
От Karma к Vitest
Karma была раннером Angular с самого начала: она запускала тесты в реальном браузере и долго оставалась стандартом. Со временем её модель устарела - подъём браузера через WebDriver и сборка на Webpack делали запуск медленным, а проект Karma был объявлен устаревшим ещё в 2023 году. Команда Angular искала замену и остановилась на Vitest, раннере из экосистемы Vite на esbuild. В Angular 20 поддержка Vitest появилась экспериментально, а в Angular 21 (ноябрь 2025) он стал рекомендованным раннером по умолчанию для новых проектов, переиспользуя ту же сборочную трансформацию, что и application builder.
Vitest вместо Karma: почему и что изменилось
Karma поднимала настоящий браузер и собирала тесты на Webpack, отчего холодный старт был медленным, а watch-режим реагировал с паузой. Vitest исполняет тесты на esbuild и переиспользует ту же трансформацию, что и application builder Angular, поэтому компиляция быстрая, а пересборка в watch затрагивает только изменённый граф. По умолчанию компоненты исполняются в jsdom - облегчённой реализации DOM в Node, а при необходимости настоящего браузера Vitest подключает browser mode через Playwright.
| Аспект | Karma (прошлое) | Vitest (Angular 21) |
|---|---|---|
| Среда исполнения | Реальный браузер через WebDriver | jsdom по умолчанию, браузер по запросу |
| Сборка тестов | Webpack | esbuild, та же трансформация, что у билда |
| Watch-режим | Перезапуск, заметная пауза | Инкрементально, только затронутое |
| Статус в Angular 21 | Выведена из эксплуатации | Рекомендованный раннер по умолчанию |
API тестов почти не изменился: describe, it, expect, beforeEach остались на месте, и TestBed работает как раньше. Поэтому миграция существующих спеков с Karma на Vitest для большинства проектов сводится к смене конфигурации раннера, а не переписыванию тестов.
Почему Vitest стартует и работает в watch-режиме заметно быстрее, чем Karma?
TestBed и компонентные тесты
TestBed - это движок тестового окружения Angular. Он собирает миниатюрный модуль с нужными провайдерами и компонентами, а метод createComponent поднимает экземпляр компонента в тестовом DOM и возвращает fixture. Из fixture доступны componentInstance (сам объект компонента), nativeElement (корневой DOM-узел) и componentRef.setInput для подачи значений в signal inputs. Метод detectChanges запускает обнаружение изменений и обновляет разметку.
Порядок строгий: настроить модуль, создать компонент, подать входы через setInput, вызвать detectChanges и только потом читать DOM. setInput - правильный способ задать signal input в тесте: он эмулирует привязку от родителя и помечает компонент на проверку. Чтение textContent через ?? '' и optional chaining обходится без приведений типов и non-null.
Частая ошибка - проверять DOM до вызова detectChanges. Сразу после createComponent шаблон ещё не отрисован первым проходом обнаружения изменений, и nativeElement будет пустым. Сначала setInput и detectChanges, потом ассерты по разметке.
Зависимости компонента подменяются через providers в configureTestingModule: реальный сервис заменяется на фейк или объект-заглушку. Для чистого dumb-компонента подменять обычно нечего - в этом и состоит выгода разделения smart/dumb для тестируемости.
В тесте сразу после TestBed.createComponent проверяется nativeElement.textContent, но он пустой. В чём причина?
Component harnesses: тесты без привязки к разметке
Тест, который ищет элементы по CSS-селекторам внутренней разметки компонента, ломается при любом рефакторинге шаблона. Component harness из Angular CDK решает это: harness - это стабильный объектный API над компонентом, через который тест взаимодействует с ним как пользователь, не зная внутреннего HTML. У Material-компонентов есть готовые harness, и для своих компонентов можно написать собственный.
Тест находит кнопку по её тексту, кликает и проверяет состояние disabled - всё через методы harness, ни одного CSS-селектора внутренней разметки. Если Material поменяет внутренний HTML кнопки в новой версии, harness обновится вместе с библиотекой, а тест останется рабочим. Это разрывает связь теста с деталями реализации компонента.
Harness работает в двух средах через единый API: TestbedHarnessEnvironment для unit-тестов в Vitest и аналог для e2e. Один и тот же набор обращений к компоненту можно переиспользовать на разных уровнях пирамиды тестов.
В чём главное преимущество тестирования через component harness вместо поиска элементов по CSS-селекторам внутренней разметки?
Playwright для e2e и тесты в zoneless
Unit- и компонентные тесты проверяют части в изоляции, но не отвечают на вопрос работает ли приложение целиком в настоящем браузере. Это задача end-to-end тестов, и стандартный инструмент для них в современном Angular - Playwright. Он запускает приложение в реальных Chromium, Firefox и WebKit, имитирует действия пользователя и автоматически ждёт появления элементов, что убирает целый класс flaky-тестов из ручных задержек.
Playwright обращается к элементам по доступной роли и имени (getByRole), как это делает скринридер, поэтому e2e заодно проверяют доступность. Метод expect с toBeVisible встроенно ждёт, пока элемент появится, без ручных таймаутов. При падении Playwright сохраняет трейс со скриншотами и логом действий, что резко ускоряет разбор.
Отдельный нюанс - тестирование в zoneless-режиме. Без Zone.js Angular не перехватывает асинхронность неявно, поэтому в компонентных тестах вид обновляют явным вызовом fixture.detectChanges, а завершения асинхронных задач ждут через await fixture.whenStable. Сигналы при этом тестируются прямо: установить writable-сигнал, вызвать detectChanges и проверить, что зависимый computed и разметка обновились.
Здоровая пирамида: много быстрых unit- и компонентных тестов на Vitest внизу, заметно меньше e2e на Playwright наверху - только критичные пользовательские пути. E2e дорогие и медленные, поэтому ими покрывают сквозные сценарии, а логику деталей оставляют unit-тестам.
Чем отличается тестирование обновления вида в zoneless-режиме от привычного режима с Zone.js?
Связь с другими темами
Урок про то, как проверять то, что построено в остальных уроках. Связи:
- Архитектура состояния — Smart/dumb разделение делает dumb-компоненты тривиальными для unit-тестов через signal inputs
- Обнаружение изменений и zoneless — В zoneless тесты опираются на явный detectChanges, понимание CD здесь обязательно
- Сборка и CLI — Vitest переиспользует трансформацию application builder, поэтому тесты и сборка делят пайплайн
Итог
- В Angular 21 Vitest заменил Karma как рекомендованный раннер по умолчанию: запуск на esbuild, watch только по затронутому
- TestBed настраивает тестовый модуль и создаёт компоненты, fixture даёт доступ к экземпляру и DOM
- Component harnesses (CDK) тестируют компоненты через стабильный API, не завязываясь на внутреннюю разметку
- Playwright покрывает end-to-end: реальный браузер, авто-ожидания, кросс-браузерность, трейсы при падении
- В zoneless обновление вида вызывается явно через fixture.detectChanges, асинхронность ждут через whenStable
- Пирамида тестов: много быстрых unit/компонентных на Vitest и точечные e2e на Playwright для критичных путей
Связанные уроки
- ng-03-components — Компонентные тесты создают и проверяют компоненты, поэтому нужна базовая модель компонентов
- ng-38-state-architecture — Чистые dumb-компоненты из урока архитектуры тестируются особенно легко через signal inputs
- ng-21-change-detection-zoneless — Тестирование сигналов в zoneless требует понимания того, как работает обнаружение изменений