Эта статья — вторая часть
В первой части статьи мы заложили фундамент:
сформулировали мотивацию: зачем нам новый DSL и как он должен выглядеть;
-
провели первые 4 итерации фреймворка scenax, включая:
минимальный
@TestCase()и поддержку Allure;добавление тегов (
@Feature,@Severity,@AllureId);параметризацию через
@TestCase.each();переход к классам (и
runTestClass,@Suite,@ParentSuite,@Layer);
Теперь — самое интересное.Во второй части увидим:
как строится архитектура
scenaxвокругLifecycleиStep Library;как вынести шаги в классы и использовать
@Step;как фреймворк превращается в полноценного "тестового сценариста";
и почему
Scenax— это логичное развитие подхода к автотестам.
Первая часть: Scenax — как мы создаём декларативный DSL для автотестов
? Структура статьи
Итерация 5:
@Suite,@ParentSuite,@SubSuite,@Layer— иерархия тестов в AllureИтерация 6:
@Setup,@Teardown,@Context,@Inject— жизненный цикл и shared stateИтерация 7:
@BeforeAll,@AfterAll,@Setup(params)— масштабирование сценариевИтерация 8:
@Step,@Scenario()и автономные классы шагов](#-итерация-8-step-scenario-и-автономные-классы-шаговКак
scenaxвписывается в стек технологий](#-как-scenax-вписывается-в-стек-технологийЗаключение: почему
scenax— это новый стандарт для API-тестов на Vitest
Итерация 5: @Suite, @ParentSuite, @SubSuite, @Layer — иерархия тестов в Allure
? Цель
Структурировать тесты как сценарии в документации: по модулям, слоям, группам и уровням ответственности. Добавим иерархические декораторы, чтобы Allure-отчёт стал навигационным.
Почему это важно?
Когда тестов становится много, нужно уметь:
- понимать, какой модуль покрыт
- находить конкретные группы тестов
- запускать только auth, или smoke, или e2e
Что добавили
- @ParentSuite(name) — верхний уровень (напр. E2E Тесты)
- @Suite(name) — модуль или раздел (напр. Auth API)
- @SubSuite(name) — подгруппа сценариев (напр. Негативные сценарии)
- @Layer(name) — технический уровень (api, unit, e2e)
- Поддержка этих декораторов в runTest
- Отображение структуры в Allure
Пример
@ParentSuite('E2E Тесты')
@Suite('Auth API')
@SubSuite('Негативные сценарии')
@Layer('api')
@Feature('Авторизация')
@Tag('regression')
@Tag('auth')
@Owner('team-auth')
class AuthNegativeTests {
@TestCase.each([
['user@example.com', 'wrongpass', 401],
['invalid@example.com', '123456', 401],
])('Логин неуспешен для %s')
@Description('Проверка отказа в доступе при неверных данных')
@Tag('login')
@Severity('critical')
@Owner('dmitry.nkt')
async negativeLogin(email: string, password: string, expectedStatus: number) {
const res = await step(`POST /login с ${email}`, () =>
axios.post('https://httpbin.org/status/' + expectedStatus, { email, password }).catch(e => e.response)
)
expect(res.status).toBe(expectedStatus)
}
}
Как это отображается в Allure
E2E Тесты
└── Auth API
└── Негативные сценарии
└── Логин неуспешен для user@example.com ✅
Что это дает
- ? Навигация по модулям
- ? Группировка по слоям (unit, api, e2e)
- ? Фильтрация по группам (@auth, @smoke)
- ? Стандартизация отчётов
- ⚙️ Возможность auto-labeling на CI/CD
Вывод
Теперь каждый тест — это:
- часть конкретной фичи
- вложен в понятную иерархию
- снабжён техническим и смысловым контекстом
Allure-отчёт стал не просто списком проверок, а живым паспортом системы.
Что дальше?
В следующей итерации:
- добавим beforeAll, afterEach, глобальные хуки
- начнём поддерживать @Setup, @Teardown, возможно — @Inject и Context
Итерация 6: @Setup, @Teardown, @Context, @Inject — жизненный цикл и shared state
? Цель
Реализовать жизненный цикл для тестов и возможность делиться состоянием между методами и шагами — декларативно и безопасно.
Проблема
Когда тесты становятся сложнее, появляется необходимость:
- подготавливать данные (логин, токен, пользователи)
- очищать ресурсы (удаление, завершение сессии)
- делиться переменными между методами
- логировать внутренние действия
В Vitest это решается beforeEach / afterEach и глобальными переменными — но это не так читаемо как хотелось бы, не типизировано и не структурировано.
Что добавили
- @Setup() — вызывается перед каждым тестом
- @Teardown() — вызывается после каждого теста
- @Context() — создаёт shared-объект для передачи между методами
- @Inject() — декларативно подставляет значения в поля из контекста- Расширили runTest() для автоматической поддержки всего этого
- Расширили runTest() для автоматической поддержки всего этого
Пример
@Feature('Сессия')
@Tag('session')
class SessionTests {
@Context()
ctx!: { token?: string; log?: string[] }
@Inject()
token!: string
@Setup()
async init() {
this.ctx.token = 'admin-token'
this.ctx.log = ['Токен создан']
}
@Teardown()
async cleanup() {
this.ctx.log?.push('Очистка контекста')
attach('Лог выполнения', this.ctx.log?.join('\n') ?? '', 'text/plain')
}
@TestCase('Получение токена')
@Description('Проверка, что токен создаётся и доступен через контекст и инжекцию')
async checkToken() {
await step('Проверка токена из @Context', () => {
expect(this.ctx.token).toBe('admin-token')
})
await step('Проверка токена из @Inject', () => {
this.token = this.ctx.token!
expect(this.token).toBe('admin-token')
})
}
}
Что это даёт
- Изоляцию логики подготовки/очистки
- Стандартизированный shared state
- Возможность вешать логику на @Teardown — даже вложения в отчёт
- Ясную и декларативную структуру сценариев
А нельзя ли более просто?
Можно. Вот так:
let token: string
beforeEach(() => {
token = 'admin-token'
})
test('тест токена', () => {
expect(token).toBe('admin-token')
})
Работает. Просто. Без магии.
Но когда тестов становится 30+, и каждый — это бизнес-сценарий с шагами, контекстом и вложениями, жизненный цикл превращается в архитектурный элемент — а не в хаотичный beforeEach().
Наш подход
- @Setup() = beforeEach() с контекстом
- @Teardown() = afterEach() + логика- @Context() = структурный shared state- @Inject() = автоматическое внедрение переменных
Вместо “вызови руками” — “объяви намерение”
Killer-фичи DSL подхода (vs обычный vitest)
Allure-Ready из коробки (
feature,severity,owner, шаги, лейблы)Автоматический жизненный цикл —
setup,teardown, логированиеМодульная архитектура с иерархией
Suite → SubSuiteПараметризация сценариев (
@TestCase.each)Тест = Документация (
@Description,@Feature)Расширяемость — auto-labeling,
Context, future hooks
Что дальше?
- @BeforeAll, @AfterAll — выполнение один раз на класс
- @Step — шаги как методы
Итерация 7: @BeforeAll, @AfterAll, @Setup(params) — масштабирование сценариев
? Цель
Добавить поддержку жизненного цикла на уровне класса (@BeforeAll, @AfterAll) и параметризированной подготовки данных (@Setup(params)).
Проблема
Когда у нас появляются десятки сценариев:
- многие требуют авторизации (но не хочется логиниться 10 раз)
- каждый сценарий может требовать временных сущностей (юзеры, сессии)
- @Setup() не знает, какие параметры передаются через .each()
Что добавили
- @BeforeAll() — выполняется один раз до всех тестов класса
- @AfterAll() — выполняется один раз после всех тестов класса
- @Setup(params) — теперь получает параметры из @TestCase.each([...])
Пример
@Context()
ctx!: { email?: string; status?: number; token?: string; log?: string[] }
@BeforeAll()
initSuite() {
this.ctx.log = ['? Начинаем']
}
@AfterAll()
finishSuite() {
this.ctx.log?.push('? Конец')
attach('Лог', this.ctx.log?.join('\n') ?? '', 'text/plain')
}
@Setup()
prepare([email, expectedStatus]) {
this.ctx.email = email
this.ctx.status = expectedStatus
this.ctx.token = email + '-token'
this.ctx.log?.push(`? Подготовка: ${email}`)
}
А откуда params в @Setup()?
Если используется @TestCase.each(...), мы передаём параметры прямо в @Setup():
@Setup()
prepare([email, status]) { ... } // email и статус приходят из each()
Если используется обычный @TestCase(...) — @Setup() вызывается без параметров.
@Setup()— подготовка перед каждым (работает какbeforeEach())@Setup(params)— подготовка с параметрами (работает с.each())@BeforeAll()— общая инициализация (один раз на весь класс)@AfterAll()— завершение, очистка (один раз после всех)
Что это даёт
- Сокращаем дублирование (login, createProject)
- Повышаем читаемость (@Setup([email, status]))
- Строим классический e2e lifecycle
- Улучшаем Allure-репорты с attach(log)
Следующий шаг
- @Step() для методов
- Переиспользуемые шаги в стиле Playwright / Serenity
Итерация 8: @Step, @Scenario() и автономные классы шагов
? Цель
Выделить шаги сценария в отдельный класс, сделать их читаемыми, переиспользуемыми и автоматически выполняемыми в Allure-отчётах — без ручного вызова step(...) и без boilerplate-кода runAllSteps().
Решение
1. @Step() — помечает любой метод как шаг для Allure
@Step('Проверка email')
async checkEmail() { ... }
2. @Scenario() — помечает класс, в котором шаги должны выполняться последовательно
@Scenario()
class AccessSteps { ... }
3. runSteps(instance, ctx) — универсальный раннер для таких классов
await runSteps(AccessSteps, this.ctx)
Что мы сделали
- Создали @Step() — минимальный декларативный шаг
- Добавили @Scenario() — метку класса для автоматического исполнения
- Написали runSteps() — запуск шагов по порядку
- Добавили поддержку @Context() внутри step-класса
Пример использования
@Scenario()
class AccessSteps {
@Context()
ctx!: { email?: string; token?: string; status?: number }
@Step('Проверка email')
async checkEmail() {
expect(this.ctx.email).toMatch(/@example\.com/)
}
@Step('Проверка токена')
async checkToken() {
expect(this.ctx.token).toBe(this.ctx.email + '-token')
}
}
И в тесте:
await runSteps(AccessSteps, this.ctx)
Что это дает?
- ? Выделить шаги в отдельные модули (шаги = lego-блоки сценария)
- ♻️ Использовать один и тот же набор шагов в разных сценариях и классах
- ? Читаемые отчёты Allure с понятными шагами
- ? Автоматическое исполнение — порядок = порядок в коде
- ? Запускать шаги автоматически, без ручного вызова — даже в других раннерах
Это решение вдохновлено практиками Serenity и Playwright, но адаптировано под декларативный DSL.
? Идея на будущее
Можно автоматически вызывать шаги по условию, по профилю, по пользовательской логике. Этот фундамент пригодится нам и для более сложных кейсов: например, вложенных шагов, UI-цепочек, сценариев c branching.
Ключевая мысль
@Step+@Scenario()превращают набор методов в декларативный сценарий. (runSteps()— сценарный раннер)
?️ Как scenax вписывается в стек технологий
Scenax не заменяет Vitest, Playwright или Allure — он добавляет архитектурный слой поверх них, структурируя сценарии, шаги и отчёты:
[ Vitest ] — запускает тесты, проверки
↑
[ allure-vitest ] — интеграция с Allure отчётами
↑
scenax — DSL, шаги, сценарии, архитектура
- Vitest обеспечивает запуск, тайминг, изоляцию тестов.
- Allure даёт визуальный отчёт.
- Scenax структурирует поведение, добавляет шаги, метаинформацию и декларативность.
А что насчёт Playwright, Jest, других?
scenax построен как архитектурный слой, а не как зависимость от конкретного раннера.
? В будущем планируется:
- @scenax/core — движок, не завязанный на Vitest
- @scenax/vitest — адаптер под Vitest
- @scenax/playwright — адаптер под Playwright
- Возможность автоопределения среды (vitest, playwright) через runTest()
Что это даёт?
- Возможность масштабировать архитектуру на любой стек
- Повторное использование сценариев, шагов и логики
- Отвязка от инфраструктурных особенностей раннера
- Потенциал для единой системы тестирования в проекте
В scenax мы делаем не формат, а подход — который можно применить где угодно, где нужны сценарии, шаги и отчёты.
Заключение: почему scenax — это новый стандарт для API-тестов на Vitest
Восемь итераций. Каждая — шаг к читаемым, структурированным, декларативным тестам, где код становится сценарием, а сценарий — частью продукта.
Изначально мы просто хотели сделать DSL-обёртку над vitest и allure-js, чтобы улучшить читаемость и отчётность API-тестов. Но по пути мы пришли к архитектурному паттерну с необходимыми нам фичами, которые:
- давали бы декларативный DSL для API-сценариев на TypeScript
- строили бы тест-кейсы как классы с аннотированными шагами
- автоматически генерировали бы Allure-отчёты без дублирования
- масштабировались бы по BDD-образцу, но без Gherkin.
Так мы пришли к scenax — на первый взгляд просто обёртке, но при детальном рассмотрении отдельной архитектуре и open-source DSL-фреймворку для построения тестов.
Что даёт scenax прямо сейчас
- ? Читаемые API-тесты, написанные как сценарии
- ? Точная привязка к Allure TestOps: allureId, severity, feature
- ? Сценарные шаги (@Step, @Scenario, runSteps())
- ? Шаги с контекстом, передаваемым автоматически (@Context())
- ? Один DSL — один стиль — вся команда пишет одинаково
? «Зачем классы? Все уходят в функции!»
Да, в UI-фреймворках функции вытеснили классы — там нужна реактивность, локальность и гибкость. Но в сценарном тестировании:
- Класс = сценарий (UseCase)
- Метод = шаг
- Декоратор = декларативное описание поведения
- Контекст и расширения интегрируются естественно
? Мы вдохновились Serenity, NestJS и Playwright, но собрали их лучшее — в class-based DSL на TypeScript. Архитектурно, гибко, читаемо.
Что за паттерн мы реализовали?
scenax реализует архитектуру, которую мы называем Scenario-oriented DSL.
Вдохновлено:
- ? Serenity BDD (Java): идея шагов и сценариев
- ⚙️ NestJS: классы, декораторы, DI
- ? Playwright: изоляция, fixtures, контекст
Но объединено и упрощено для TypeScript-разработки:
- Сценарий = декларативный класс с мета-информацией
- Поведение = управляется методами + контекстом
- Шаги = аннотированные методы, которые легко вызывать
- Расширение = через слои, DI и классы без магии.
Кто должен использовать это прямо сейчас
- Команды, которым нужен читаемый Allure-отчёт, а не набор console.log
- Команды, где есть TestOps или QA, которым нужен живой сценарий
- Архитекторы, которые устали от copy-paste шагов в тестах
- Разработчики, которым важно писать чисто, предсказуемо и гибко
Есть ли аналоги?
Serenity BDD (Java). Сложный вход, громоздкая структура
Playwright test (TS). Фокус на UI, нет сценариев-классов
vitest+ allure (TS). Нет DSL, мета-инфо и автоматизации сценариев✅
scenax(TS). Простой DSL, шаги + сценарии, class-based архитектура
? Потенциал развития
scenax — это не «утилита». Это библиотека тестовой архитектуры.
В будущем мы планируем:
- ? StepLibrary() — lego-блоки шагов для переиспользования в сценариях
- ? @Inject() для nested-инъекций и dependency tree
- ? Интеграция с UI/Playwright тестами на том же DSL
- ? Авто-трекинг статистики шагов, сценариев и покрытия через Allure
- ?️ CLI и VS Code плагины для генерации шаблонов шагов
- ? Архитектурные пресеты для монореп, микросервисов и CI-интеграций
? Заключительная мысль
scenax— это когда API-тест превращается в намерение,
когда сценарий читается как документация,
и когда команда начинает говорить на одном языке.
Если вы устали от «тестов ради тестов», если вам нужен внятный отчёт, живой DSL и архитектура, которая масштабируется — будем рады вам и вашему вкладу в развитие. Добро пожаловать в scenax.
Попробуй. Подключи. Покажи команде.
? GitHub: https://github.com/dmitry-nkt/scenax
? Документация: в каждом тесте
? Установи: npm i @scenax/core -D