Тестирование
Chef запускает тесты в реальном браузере через Playwright. Поддерживаются два вида тестов: unit-тесты (Mocha + Chai) и E2E-тесты (Playwright Test API). E2E-тесты можно писать как для отдельного расширения, так и на уровне модуля — для сценариев, в которых участвует сразу несколько расширений.
Подготовка
Инициализируйте тестовое окружение:
chef init testsКоманда создаёт два файла в корне проекта:
| Файл | Описание |
|---|---|
playwright.config.ts | Конфиг Playwright для запуска unit и E2E тестов в браузере |
.env.test | Учётные данные для автоматической аутентификации при тестировании |
Заполните учётные данные вашей локальной установки Bitrix:
BASE_URL=http://localhost
LOGIN=admin
PASSWORD=your_password| Переменная | Описание |
|---|---|
BASE_URL | URL локальной установки Bitrix |
LOGIN | Логин тестового пользователя |
PASSWORD | Пароль тестового пользователя |
MOCHA_WRAPPER | Путь или полный URL страницы запуска unit-тестов. По умолчанию /dev/ui/cli/mocha-wrapper.php |
Переменную MOCHA_WRAPPER нужно задать, если на вашей установке страница запуска unit-тестов доступна по другому адресу — например, main/dev/public смонтирована с другим префиксом:
MOCHA_WRAPPER=/internal/dev/ui/cli/mocha-wrapper.phpWARNING
Не коммитьте .env.test в систему контроля версий — файл содержит конфиденциальные данные.
Установите браузеры Playwright:
npx playwright installТипы для IDE
mocha, chai и их типы включены в Chef и используются при запуске chef test. Для работы автодополнения в IDE установите типы локально:
npm install --save-dev @types/mocha @types/chai @playwright/testЗапуск тестов
# Все тесты расширения
chef test vendor.my-extension
# Только unit-тесты
chef test unit vendor.my-extension
# Только e2e-тесты
chef test e2e vendor.my-extension
# Сценарные тесты уровня модуля (несколько расширений)
chef test module crm
# Конкретный файл
chef test unit vendor.my-extension ./utils.test.ts
# Тесты по паттерну
chef test vendor.* --grep "should render"
# Список тестов без прогона
chef test vendor.my-extension --list
# Watch-режим — перезапуск при изменениях
chef test vendor.my-extension -wЖивой вывод
Во время прогона chef показывает единый статус-бар по браузерам — от старта до финала в одном виде. Пока движок разогревается, у него видна стадия; как только пошли тесты — счётчик, и какой тест сейчас выполняется:
○ Chromium starting · ○ Firefox · ○ WebKit · 0.7s
○ Chromium 12/132 · ○ Firefox preparing · ○ WebKit 8/132 · 5.3s
✓ Chromium 132/132 · ○ Firefox 90/132 · ○ WebKit building · 12.1sРезультаты сгруппированы по describe-блокам: путь suite печатается заголовком один раз, тесты — под ним с отступом. Тест, прошедший в нескольких браузерах, — одна строка с тегом всех движков (◌ — ещё выполняется):
ui.notification > Position
✓ has TOP_LEFT [Chromium ✓ · Firefox ✓ · WebKit ◌]
✗ has BOTTOM_RIGHT [Chromium ✗ · Firefox ✓ · WebKit ✓]Тесты, прошедшие только после повторной попытки (retry в Playwright), помечаются как flaky:
✓ opens the dialog (passed on attempt 2) [Chromium ✓]В итоговой сводке flaky-тесты выносятся отдельной цифрой — после общего числа, потому что они уже посчитаны в passed и не образуют четвёртую категорию:
Extensions 1 passed (1)
Tests 130 passed (130) 2 flaky
Flaky tests: failed at first, passed on a retry
↻ ui.dialog › Sharing > opens the dialog
↻ ui.dialog › Sharing > closes on EscapeКаждый flaky-тест назван и отмечен расширением, в котором он прошёл с ретрая — по одной цифре 2 flaky найти их было бы нельзя.
Зелёный прогон с ретраями — слабое доказательство
На первой (упавшей) попытке Playwright дописывает отсутствующий эталонный снимок, и ретрай затем сравнивается с эталоном, который создал этот же прогон. Чтобы снять baseline осознанно: сначала прогон с --ignore-snapshots — он доказывает, что ассерты проходят, — и только потом --update-snapshots.
Расхождение с числом отобранных тестов
Сумма напечатанных категорий должна совпадать с числом тестов, которые отобрал раннер. Если какой-то тест не отчитался ни одним результатом, chef печатает Mismatch и роняет прогон — сводка, которая покрывает не весь набор, ничего не доказывает, и выходить с кодом 0 на ней нельзя:
Tests 6 passed (6)
Mismatch 5 unreported — selected tests with no resultСчёт идёт в прогонах, а не в уникальных тестах: один тест в трёх движках — это три прогона. Поэтому дедупликация строк в выводе (4 passed при 12 прогонах) сама по себе расхождением не считается.
Битый тестовый файл
Если тестовый файл не собирается (синтаксическая ошибка, импорт несуществующего модуля) или падает при загрузке (ошибка на верхнем уровне файла или в теле describe, вне it), остальные файлы всё равно выполняются. Битый файл попадает в отчёт отдельной группой с упавшей записью Failed to build или Failed to load — с причиной и code frame на месте ошибки — и роняет прогон:
test/parser/broken.test.js
✗ Failed to build [Chromium ✗ · Firefox ✗ · WebKit ✗]
Failed Tests (1):
ui.bbcode.parser › test/parser/broken.test.js > Failed to build [Chromium · Firefox · WebKit]
Unexpected token (3:12)
1 | describe('broken', () => {
2 | it('x', () => {
> 3 | const a = ;
| ^
4 | });
at /…/ui/bbcode/parser/test/parser/broken.test.js:3:13- Ошибка в модуле, который импортируют несколько тестовых файлов (общий хелпер), даёт одну запись с именем этого модуля и списком файлов, которые из-за него не запустились.
- Ошибка в коде, общем для всех тестовых файлов (обычно в исходниках самого расширения), не оставляет ничего для запуска — задача завершается статусом
build failed. - Ошибка внутри
describeостанавливает только этот блок: тесты, объявленные до неё, и следующие блоки файла выполняются. - Если модуль, общий для тестовых файлов, бросил ошибку уже при загрузке, запись называется
test bundle: файлы после этой точки не загрузились. - E2E: Playwright не запускает ни одного теста, если не загрузился хотя бы один spec. chef повторяет прогон без битых файлов и показывает их так же, отдельными записями. Если указан конкретный файл (
chef test e2e <name> <file>), ошибка загрузки выводится как есть. - С
--reporter jsonи--reporter teamcityтакая запись — обычный упавший тест в сьюте с именем файла. В--listбитые файлы перечисляются в блокеErrors.
Отладка
# Открыть браузер с DevTools
chef test vendor.my-extension --debug
# С видимым окном браузера
chef test vendor.my-extension --headed
# В конкретном браузере
chef test vendor.my-extension --project chromiumВ режиме --debug включаются source maps и открываются DevTools — можно ставить breakpoints прямо в исходном TypeScript-коде.
Листинг без прогона
--list перечисляет тесты, которые были бы запущены, но не запускает их — удобно, чтобы быстро увидеть состав набора или подобрать паттерн для --grep:
chef test vendor.my-extension --listТесты сгруппированы по describe-блокам, отложенные (skip/fixme) помечены. В конце — сводка Summary с разбивкой по видам: отдельно unit, отдельно e2e (для модуля — только e2e), с числом тестов и сколько из них запускается / отложено:
Summary
Unit 132 tests · 132 runnable
E2E 25 tests · 25 runnableРаботает для расширений (unit + e2e), модулей (chef test module) и всех репортеров (--reporter default|json|teamcity). С --watch несовместимо.
Аргументы Playwright
У chef свои опции, и он намеренно не повторяет весь CLI Playwright. Всё, что chef не считает своим, уходит раннеру как есть:
# Обновить эталонные снимки
chef test e2e vendor.my-extension --update-snapshots
# Прогнать тест несколько раз подряд (ловля флейков)
chef test e2e vendor.my-extension --repeat-each=3 --workers=1
# Собрать trace для разбора упавшего теста
chef test e2e vendor.my-extension --trace=onХодовые опции перечислены в chef test e2e --help, но список там не полный: любая опция Playwright работает, даже добавленная в свежей версии раннера — обновлять chef для этого не нужно.
Работает для e2e — у unit-тестов нет отдельного процесса раннера, им передавать опции некуда, и chef скажет об этом явно.
За chef остаются опции, которые управляют им самим: --watch, --path, --console, --list, --reporter, --cdp-port, а также --headed, --debug, --grep и --project — их chef переводит в аргументы Playwright сам. Всё прочее — Playwright.
Опечатку в имени опции поймает раннер, обычно с подсказкой:
error: unknown option '--headles'
(Did you mean --headed?)Аргументы дописываются последними, поэтому при совпадении побеждают они, а не то, что подставил chef.
Прямой вызов Playwright
Если понадобилось запустить Playwright руками, минуя chef, берите проектный бинарник:
./node_modules/.bin/playwright test <spec>Бинарник из поставки chef для этого не годится: @playwright/test загрузится дважды — из дерева chef и из дерева проекта — и запуск упадёт с ошибкой:
Playwright Test did not expect test.describe() to be called here
You have two different versions of @playwright/testСам chef test в эту ловушку не попадает — он всегда запускает раннер из корня проекта. Если версии Playwright в проекте и в поставке chef разошлись, перед e2e-прогоном появится предупреждение с обеими версиями и напоминанием про проектный бинарник.
Массовый запуск
chef test без аргументов или с glob-паттерном (im.v2.**) проходит по всем подходящим расширениям. Расширения без тестов пропускаются молча — в выводе появляются только те, у которых есть тесты или какие-то проблемы.
Браузерный консольный вывод по умолчанию скрыт, чтобы не замусоривать массовый отчёт. Если нужно — добавь --console:
chef test im.v2.** --consoleСтатусы задач
Рядом с расширением chef показывает причину неудачи прямо в строке задачи:
| Статус | Что значит |
|---|---|
✓ Unit tests | Все тесты прошли |
✗ Unit tests (3 failed) | Часть тестов упала, число — сколько |
✗ Unit tests (build failed) | Не собрался код, общий для всех тестовых файлов, — обычно исходники расширения. Битый тестовый файл роняет только себя (см. Битый тестовый файл) |
✗ Unit tests (crashed before any tests ran) | Упало до первого it — обычно ошибка в setup |
⚠ Unit tests (no tests collected) | Файлы есть, но Mocha не нашёл it (пустой describe, .skip) |
— Unit tests (no test files) | В директории test/unit/ (или test/) нет *.test.{ts,js} |
— E2E tests (no test files) | То же для e2e |
Расширение или модуль, у которого нет тестов, помечается как skipped (не passed) — в сводке отдельной цифрой.
Сводка в конце
После прогона chef печатает агрегированный отчёт:
- Failed Tests (N) — все упавшие тесты со стек-трейсами и code frame, сгруппированные по расширениям. Для e2e рядом печатаются пути к артефактам Playwright (screenshot, video, trace), сгруппированные по браузеру, — можно сразу открыть в редакторе.
- Errors (N) — ошибки сборки и runtime-крэши, по одной строке на причину.
- Issues — список расширений с количеством ошибок/предупреждений.
- Extensions / Tests / Time — общие цифры: сколько расширений и тестов прошло, упало или было пропущено, сколько заняло. В строке Tests отдельной цифрой отмечаются flaky-тесты (прошли после ретрая). Для
chef test moduleстрока называется Modules.
Советы
Изоляция тестов
Каждый тест должен быть независимым. Используйте beforeEach/afterEach для настройки и очистки:
describe('TodoList', () => {
let list: TodoList;
beforeEach(() => {
list = new TodoList();
});
afterEach(() => {
list.destroy();
});
it('should add item', () => {
list.add('Buy milk');
assert.equal(list.getCount(), 1);
});
it('should start empty', () => {
assert.equal(list.getCount(), 0);
});
});Организация тестов
Группируйте тесты по функциональности:
describe('UserService', () => {
describe('create', () => {
it('should create user with valid data', () => { /* ... */ });
it('should throw on duplicate email', () => { /* ... */ });
});
describe('update', () => {
it('should update user name', () => { /* ... */ });
it('should not allow empty name', () => { /* ... */ });
});
describe('delete', () => {
it('should soft delete user', () => { /* ... */ });
});
});