Skip to content

Тестирование ​

Chef запускает тесты в реальном браузере через Playwright. Поддерживаются два вида тестов: unit-тесты (Mocha + Chai) и E2E-тесты (Playwright Test API). E2E-тесты можно писать как для отдельного расширения, так и на уровне модуля — для сценариев, в которых участвует сразу несколько расширений.

Подготовка ​

Инициализируйте тестовое окружение:

bash
chef init tests

Команда создаёт два файла в корне проекта:

ФайлОписание
playwright.config.tsКонфиг Playwright для запуска unit и E2E тестов в браузере
.env.testУчётные данные для автоматической аутентификации при тестировании

Заполните учётные данные вашей локальной установки Bitrix:

env
BASE_URL=http://localhost
LOGIN=admin
PASSWORD=your_password
ПеременнаяОписание
BASE_URLURL локальной установки Bitrix
LOGINЛогин тестового пользователя
PASSWORDПароль тестового пользователя
MOCHA_WRAPPERПуть или полный URL страницы запуска unit-тестов. По умолчанию /dev/ui/cli/mocha-wrapper.php

Переменную MOCHA_WRAPPER нужно задать, если на вашей установке страница запуска unit-тестов доступна по другому адресу — например, main/dev/public смонтирована с другим префиксом:

env
MOCHA_WRAPPER=/internal/dev/ui/cli/mocha-wrapper.php

WARNING

Не коммитьте .env.test в систему контроля версий — файл содержит конфиденциальные данные.

Установите браузеры Playwright:

bash
npx playwright install

Типы для IDE ​

mocha, chai и их типы включены в Chef и используются при запуске chef test. Для работы автодополнения в IDE установите типы локально:

bash
npm install --save-dev @types/mocha @types/chai @playwright/test

Запуск тестов ​

bash
# Все тесты расширения
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.

Отладка ​

bash
# Открыть браузер с 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:

bash
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 не считает своим, уходит раннеру как есть:

bash
# Обновить эталонные снимки
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, берите проектный бинарник:

bash
./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:

bash
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 для настройки и очистки:

ts
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);
  });
});

Организация тестов ​

Группируйте тесты по функциональности:

ts
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', () => { /* ... */ });
  });
});

Распространяется под лицензией MIT.