Когда тестов становится больше сотни, любая «красивая обертка» превращается в тыкву, если за ней нет архитектуры. Именно поэтому BDR - это не просто "красивая обертка".
Я никогда не был фанатом Cucumber. Да им даже толком не пользовался. Мне хватило просто посмотреть со стороны, сколько сил уходит у людей на поддержку всей этой магии с регулярками и текстовыми файлами. Это дорого. И технически, и по времени.
BDR (Business-Driven Living Requirements) — это не попытка «сделать как в Cucumber, только на TS». Это попытка выжить и не превратить 1000 тестов в ад поддержки. Сегодня разберем «железо»: слои, композицию и то, почему это работает без костылей.
1. Когда «просто оберток» мало
Скептики правы, если просто оборачивать каждый клик в BDR.step, вы получите тот же мусор, только в профиль. Масштабируемость — это про разделение ответственности.
Достаточно все поделить на три уровня абстракции. И это не «бумажные» правила, а способ не сойти с ума при рефакторинге.
ВНИМАНИЕ
Antipattern: Смешивание слоев. Если вы пишете селектор прямо во Flow или (не дай бог) в Тесте, вы соз��аете хрупкий код. Flow должен «командовать» Page Object-ом, а не знать про его верстку.
Уровень 1: Inline BDR
Это когда код теста и описание живут в самом .spec.ts. Подходит для одноразовых проверок или уникальных кейсов, которые не повторяются.
await BDR.When('User performs unique action', async () => { await page.click('#unique-button'); });
ОСТОРОЖНО!
Antipattern: Злоупотребление инлайнами. Моё эмпирическое правило: если больше ~30% тестов используют Inline BDR — это серьезный маркер будущего legacy.
Уровень 2: Business Flows (Замена Step Definitions)
Как только действие повторяется хотя бы дважды, оно должно уйти во Flow.
Важно разделять POM (где кнопка) и Flow (как мы на неё нажимаем). Page Objects отвечают за верстку и селекторы, а Flows — только за бизнес-действия. Это эволюция классического POM, которая не дает коду превратиться в кашу.
Мы используем декораторы @Step, чтобы превратить обычные классы в бизнес-модули.
export class LoginFlow { // Вызываем технический Page Object внутри бизнес-потока constructor(private loginPage: LoginPage) {} @Step('Login as {username}') async login(username: string, pass: string) { await this.loginPage.open(); await this.loginPage.fillForm(username, pass); await this.loginPage.submit(); } }
Уровень 3: Domain Layer (Композиция)
На больших проектах Flows начинают зависеть друг от друга. Вы собираете сценарий из готовых блоков, как конструктор.
Domain Layer — это уровень, где описываются высокоуровневые бизнес-сценарии: «Пользователь покупает товар», «Пользователь восстанавливает доступ». Здесь нет селекторов и UI-деталей — только композиция из уже готовых Flows.
2. Решение «Загадки Масштабирования»
Главная проблема больших проектов — рефакторинг. В Cucumber переименование шага — это боль. В BDR это делается одной кнопкой Rename Symbol в IDE. Вы меняете название метода в одном месте, и магия, IDE обновляет его во всех 500 тестах, а Allure подхватывает новый текст из декоратора.
DI и Композиция через Fixtures
Чтобы не таскать за собой инициализацию классов, мы используем фикстуры Playwright. Это делает тесты чистыми:
test('Full Purchase Flow', async ({ loginFlow, inventoryFlow, cartFlow }) => { await loginFlow.login('standard_user', 'secret_sauce'); await inventoryFlow.addItem('Backpack'); await cartFlow.checkout(); });
На огромных проектах инициализация 100+ фикстур может стать громоздкой. Чтобы избежать "Fixture Hell", используйте ленивую инициализацию (
getter) или группируйте фикстуры по доменам (Auth, Checkout, Profile).
Data-Driven без боли
Забудьте про таблицы Examples в .feature файлах. У вас есть мощь языка. Обычный forEach по массиву данных дает вам идеальный отчет, где каждая итерация — это отдельная история.
const regions = ['US', 'EU', 'JP']; regions.forEach(region => { test(`Verification for ${region}`, async ({ settingsFlow }) => { await settingsFlow.switchRegion(region); // ... }); });
API + UI: Unified Business Story
В E2E тестах много времени уходит на подготовку данных. Ждать, пока браузер прогрузит страницу регистрации — это преступление.
Мы миксуем слои: создаем юзера через API, а проверяем бизнес-логику в UI. В отчете Allure это выглядит как одна связная цепочка событий. Менеджер видит процесс, а инженер видит, где именно затык.

4. Rich Diagnostics: Рентген для отчетов
Видео — это хорошо, но это «детектив», который нужно смотреть. BDR дает «спойлер».
В Handbook добавлена фича Rich Diagnostics. Если тест упал на несовпадении данных, BDR приложит таблицу-сверку.
// В коде это одна строка: await attachCompareTable('User Data Check', expected, actual);
В отчете это выглядит как чистая таблица с ✅ и ❌. Разраб за 2 секунды все видит.

5. Командный процесс: Кто тянет лямку?
BDR меняет не только код, но и то, как команда общается:
AQA/Разработчик: Пишет Flows и покрывает критические пути. Фокус на стабильности и типизации.
Manual QA / Доменный эксперт: Становится «цензором качества». Ему не нужно знать код, но он наконец-то может провести аудит автотестов, просто читая отчеты Allure. Если сценарий в отчете выглядит неполным или описан техническим жаргоном — это баг автоматизации. Так мы убираем стену между «ручным» и «авто» миром.
BDR-Review: На код-ревью мы смотрим не только на ассерты, но и на то, как это будет выглядеть в Allure.
Это создает плацдарм для перехода к Low-Code. Когда у вас есть библиотека Flows с понятными описаниями, порог входа для понимания тестов снижается до минимума. А в будущем, с помощью визуальных редакторов, сборка тестов из этих кубиков станет реальностью даже без написания кода.
6. Rich Diagnostics под капотом
Чтобы не быть голословным, вот как реализуются «умные таблицы» без внешних зависимостей. Это обычный TypeScript, который генерирует HTML-строку для Allure:
export async function attachCompareTable(name, expected, actual) { const keys = Array.from(new Set([...Object.keys(expected), ...Object.keys(actual)])); const comparison = keys.map(k => ({ Field: k, Expected: JSON.stringify(expected[k]), Actual: JSON.stringify(actual[k]), Result: expected[k] === actual[k] ? '✅' : '❌' })); await attachTable(name, comparison); }
Просто? Да. Эффективно? Бесконечно. Это и есть дух BDR: минимум магии, максимум контроля.
7. BDR vs Cucumber
Если сравнивать эти подходы на длинной дистанции, получается интересная картина:
Фича | Cucumber (Gherkin) | BDR (Code-First) |
Рефакторинг | Медленно (поиск по тексту/регулярки) | Лока��ьно ( Rename Symbol + ручная правка декоратора в одном файле) |
Написание | Двойная работа (Feature + Steps) | Один слой (Flows) |
Масштабирование | Плодится много файлов | Рост через типизацию и данные |
Костыли | Нужен парсер, кодогенерация | Нулевая зависимость (чистый TS) |
ВНИМАНИЕ
Давайте будем честными: IDE не поменяет текст внутри строки
@Step('Login...')магическим образом при переименовании метода. Вам все равно придется поправить описание руками. Но! Вы делаете это в том же файле, где лежит сам метод. Это не мучительный поиск по папкам в Cucumber, а секундная правка в контексте задачи.
Минус BDR-подхода в том, что он требует от команды нормальных TS-скиллов и архитектурной дисциплины. Это не «серебряная пуля», а просто более честный способ организовать тестовый код.
Итого
Главное преимущество BDR — нулевой уровень "магии". Это просто чистый инженерный подход. Вам не нужны внешние парсеры или кодогенерация, которую страшно трогать.
Вы просто пишете код, который сам документирует себя в отчете. В следующей серии мы пойдем еще дальше и научим BDR делать «авто-триаж» багов.
Забирайте обновленный шаблон и пробуйте масштабировать:
https://github.com/dmitryAQA/playwright-bdr-template
Все «грязные подробности» реализации: борьба с Fixture Hell, тонкая настройка DI и примеры ленивой инициализации я вынес в README репозитория. Там это удобнее изучать прямо в контексте кода.
Пишите в комментариях, как вы боретесь с поддержкой на 1000+ тестах? Или всё еще верите в магию Gherkin?
