Эта статья — вторая часть
В первой части статьи мы заложили фундамент:
-
сформулировали мотивацию: зачем нам новый 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
ссылка на оригинал статьи https://habr.com/ru/articles/914604/
Добавить комментарий