07 · Інструменти автоматизації
BDD-інструменти: CucumberJS і Gherkin — механіка степів
Зміст
Фіча-файл виглядає як текст, який може прочитати будь-хто в команді, а падає він як код. Саме на цьому шві й виникають питання, на які не відповідає жоден текст про BDD як підхід: чому крок пішов у зовсім інший метод, ніж ти думав; чому хук піднімає браузер для всіх сценаріїв, хоч лежить у теці однієї фічі; чому «впало десять кроків», хоч насправді впав один. Усе це не про філософію підходу, а про механіку: як інструмент зіставляє рядок із функцією, звідки в цій функції беруться аргументи й де живе стан між кроками.
Ця глава — канонічний виклад саме механіки степів на сайті: інші глави посилаються сюди. І вона має бейдж поглиблення — при першому проході розділу її можна пропустити й повернутися тоді, коли на проєкті реально зʼявиться Cucumber. Сам підхід — навіщо BDD, розмова трьох амігос, й коли Gherkin виправданий — живе у главі BDD і Gherkin: обіцянки і реальність; тут ми дивимося на нього з боку коду, який усе це виконує.
Крок Gherkin — це виклик, а не рядок тексту
Ключове формулювання, з якого варто починати: крок — це аналог виклику методу, а кроки оголошені у файлах *.feature. Тобто фіча-файл не «документація, яку потім хтось автоматизує» — це список викликів, записаний іншим синтаксисом. Сам Gherkin при цьому використовує набір спеціальних ключових слів, які надають структури й значення виконуваним специфікаціям; корисно тримати в голові й розведення понять: Cucumber — інструмент, Gherkin — назва його DSL.
Звʼязок між рядком і кодом тримає (step definition) — метод плюс вираз, що привʼязує його до одного або кількох кроків Gherkin. Два слова тут несучі:
- метод — тіло, яке виконається;
- вираз — те, за чим інструмент знаходить це тіло.
Зіставляється текст після ключового слова, а не рядок цілком. У кроці Given у кошику 3 товари на зіставлення йде у кошику 3 товари; слово Given у пошуку участі не бере взагалі. Наслідок неочевидний і його варто запамʼятати: конкретне ключове слово не розрізняє дефініції — той самий вираз під Given і під Then дає ту саму дефініцію.
Порядок дій інструмента описаний прямо, і в ньому важлива перша фаза: усі степ-дефініції завантажуються до того, як почнеться виконання тексту фічі. Далі для кожного кроку йде пошук збігу, збір груп захоплення й виклик методу.
Друга половина механіки — як інструмент розуміє, що крок упав. Тут теж є пастка з тих, що коштують годину: падіння визначається винятком, а не поверненим значенням. Повернути false чи null з кроку не означає провалити його — такий крок буде зеленим. І симетрично: крок, який іде після невизначеного (undefined), відкладеного (pending) чи впалого кроку, не виконується взагалі й позначається пропущеним. Тому в одному сценарії «десять червоних кроків» не буває: буває один впалий і девʼять пропущених.
Feature: Кошик
Scenario: Промокод дає знижку на три товари
Given у кошику 3 товари
When користувач застосовує промокод "SUMMER"
Then у кошику видно знижку 10%
import { Given } from '@cucumber/cucumber';
// Вираз, а не назва методу, звʼязує цей код із рядком фічі
Given('у кошику {int} товари', async function (count: number) {
await seedCart(count);
});
Параметризація: типізований аргумент замість розбору рядка
Одна дефініція обслуговує кілька кроків — і саме звідси починається параметризація. Вираз степ-дефініції буває двох видів: або Cucumber Expression. У регулярному кожна група захоплення передається в метод аргументом. У Cucumber Expression те саме роблять фігурні дужки з іменем типу.
Головне, що тут плутають: параметр приходить у метод уже приведеним до типу, а не рядком. Якщо вираз групи захоплення збігається з регуляркою зареєстрованого типу параметра, захоплений рядок перетворюється перед передачею в метод: {int} віддає ціле число, бо його вбудований regexp — \d+. Тобто параметризація степів — це типізовані параметри, а не «розпарсити рядок усередині кроку».
import { Given, When } from '@cucumber/cucumber';
// count тут — число, не рядок: тип задано у виразі
Given('у кошику {int} товари', async function (count: number) {
await seedCart(count);
});
// Той самий метод обслуговує будь-який промокод
When('користувач застосовує промокод {string}', async function (code: string) {
await applyPromo(code);
});
Це перший із двох рівнів параметризації. Другий живе не в коді, а в самій фічі: Scenario Outline (його синонім — Scenario Template) ганяє той самий сценарій багато разів на різних комбінаціях значень. Розподіл праці простий: Scenario Outline множить дані сценарію, а вираз степу множить рядки, які влучають в один метод.
фрагментів Given/When/Then між сценаріями — не побічний ефект, а частина задуму BDD із самого початку. Але саме тут ховається межа: чим ширший вираз, тим більше рядків він ловить — включно з тими, яких автор не мав на увазі. Про це — у розділі про степи-спагеті нижче.
World і хуки: де живе стан сценарію
World
Питання «як передати дані між кроками» має штатну відповідь, і це World — ізольований на кожен сценарій, доступний крокам і більшості хуків як this. Змінну, покладену одним кроком, читає наступний; усе покладене таким чином відкидається з кінцем сценарію.
Три властивості, які роблять цей механізм передбачуваним:
- Новий екземпляр на кожен сценарій — і (retry) не виняток: він не успадковує стан попередньої спроби, тож «на другій спробі позеленіло, бо щось лишилося» — не про World.
- World недоступний у глобальних хуках
BeforeAllіAfterAll, бо кожен із них виконується поза будь-яким сценарієм. Глобальну підготовку доводиться тримати окремо від контексту сценарію — і це не деталь стилю, а те, що визначає архітектуру звʼязки з браузером (наступний розділ). thisконфліктує зі через інший скоуп. Сучасний вихід — імпортований обʼєктworldзамістьthis; тому в кроках, які лізуть у стан, стрілочні функції не використовують без розуміння наслідків.
import { Given, Then, setWorldConstructor, World, IWorldOptions } from '@cucumber/cucumber';
class ShopWorld extends World {
cartSize = 0;
constructor(options: IWorldOptions) {
super(options);
}
}
setWorldConstructor(ShopWorld);
Given('у кошику {int} товари', function (this: ShopWorld, count: number) {
this.cartSize = count; // покладено тут
});
Then('у кошику лишається {int} товари', function (this: ShopWorld, count: number) {
if (this.cartSize !== count) throw new Error(`очікували ${count}, маємо ${this.cartSize}`);
});
Хуки
Хуки — блоки коду, які виконуються в різних точках циклу виконання, типово для підготовки й середовища перед і після кожного сценарію. Хуки сценарію виконуються для кожного сценарію; Before — перед першим кроком кожного сценарію.
І тут головна пастка структурування: місце оголошення хука нічого не звужує. Файл із хуком, покладений поруч з однією фічею, не робить хук «локальним» — він однаково виконається для всіх сценаріїв прогону. Вибірковість дають лише умовні хуки (за тегами). У JS-проєкті хуки оголошують у features/support/env.js або будь-якому іншому файлі під каталогом features/support.
CucumberJS плюс Playwright: хто тут ранер
Перше, що треба вирішити на цій звʼязці, — хто керує прогоном. (test runner) тут Cucumber: він завантажує дефініції, зіставляє кроки, викликає методи, веде хуки й статуси. Отже, Playwright у цій схемі працює не своїм ранером, а бібліотекою, і всю ієрархію доводиться будувати руками: обрати тип браузера → запустити браузер → створити контекст → створити сторінку. Дзеркально виглядає й прибирання: закриття контексту й браузера — обовʼязок твого коду. Порівняй із власним ранером Playwright, де page і context приходять як вбудовані , створюються ліниво (лише якщо тест назвав їх в аргументах) і закриваються самі — про це Playwright: фікстури, хуки і конфігурація, а про саму ієрархію — Playwright: архітектура, браузери і контексти.
Далі два факти з доки складаються в готову архітектуру. Браузер — дорогий, тож його логічно піднімати раз на прогін, у BeforeAll. Але World у BeforeAll недоступний, тож браузер живе поза World — у модульній змінній файлу підтримки. А контекст і сторінка навпаки: кожен тест (тут — сценарій) має дістати свіжий контекст, еквівалентний новому профілю браузера, з ізоляцією майже без накладних витрат. Ізоляція названа причиною відтворюваності й захисту від каскадних падінь — а World, який і так новий на кожен сценарій, для неї ідеальний дім.
// features/support/hooks.ts
import { BeforeAll, Before, After, AfterAll } from '@cucumber/cucumber';
import { chromium, Browser } from 'playwright';
import { ShopWorld } from './world'; // у класі World оголошено поля context і page
let browser: Browser; // поза World: BeforeAll його не бачить
BeforeAll(async () => {
browser = await chromium.launch();
});
Before(async function (this: ShopWorld) {
this.context = await browser.newContext(); // свіжий профіль на сценарій
this.page = await this.context.newPage();
});
After(async function (this: ShopWorld) {
await this.context.close(); // у бібліотеці прибирає твій код
});
AfterAll(async () => {
await browser.close();
});
Що змінюється у звичках разом із ранером. Модель у бібліотеки інша: там 30 секунд на більшість операцій, тоді як у ранера Playwright більшість операцій власного таймауту не мають, зате кожен тест має свій, який його валить. Тобто звичка «мене підстрахує таймаут тесту» тут не працює автоматично — межі прогону задає Cucumber, а не Playwright. Практичний наслідок: у кроках варто спиратися на очікування самої бібліотеки, а не на власні паузи, і памʼятати, що крок валить саме виняток:
import { Then } from '@cucumber/cucumber';
Then('користувач бачить повідомлення {string}', async function (this: ShopWorld, text: string) {
await this.page.getByText(text).waitFor(); // таймаут кине виняток — крок стане failed
});
Дві ролі в одному сценарії теж робляться контекстами: два контексти з різними станами замість двох браузерів. Якщо кроки виглядають як «зайшов адміном, потім користувачем», у World має бути два контексти, а не один із перелогіном.
Структура фіч-файлів у репо
Правила Gherkin задають кістяк, який ніяк не обійти:
- Кожен непорожній рядок має починатися з ключового слова. Вільного тексту між кроками не буває.
- Перше первинне (primary) ключове слово документа — завжди
Feature, іFeatureу файлі може бути лише одна. Тобто «файл на модуль» неможливий за побудовою: файл — це одна фіча. Rule— необовʼязкове ключове слово з версії 6, і воно представляє одне бізнес-правило. Це готовий рівень групування між фічею і сценаріями.Backgroundіснує проти повторення однаковихGivenу всіх сценаріях фічі.
Тепер найважливіше про репозиторій — і найменш очевидне. Оскільки всі степ-дефініції завантажуються до старту виконання фічі, простір імен виразів фактично спільний на весь прогін. Тека не є скоупом: дефініція, покладена в features/checkout/, так само доступна фічі профілю, а вираз, який випадково збігся, зіштовхне два файли, що ніколи не мали зустрітися. Те саме з хуками: місце оголошення нічого не звужує. Каталог, який дока задає прямо, лише один — features/support для хуків і файлів підтримки.
Звідси робоча розкладка виглядає так:
| Рівень | Що тут лежить | Чим тримається |
|---|---|---|
features/**/*.feature | одна фіча = один файл, Rule — групування правил, Background — спільні передумови | правилами самого Gherkin |
| каталог степів | дефініції, згруповані за доменом (кошик, оплата, профіль) | домовленістю команди, не інструментом |
features/support | хуки, World, конфігурація підтримки | прямою вказівкою доки |
Практичне правило рев'ю: якщо дефініції розкладені за теками «щоб не конфліктували» — це самообман. Конфлікт лікується унікальністю виразу, а не місцем файлу.
Степи-спагеті: як вони виглядають у коді
«Степи-спагеті» — не оцінка стилю, а конкретні механізми, які дають конкретні симптоми. Їх варто знати поіменно, бо на рев'ю вони виглядають по-різному.
Неоднозначність. Дефініції мусять бути унікальні, щоб інструмент знав, що виконувати. На два збіги він не вибирає сам і не бере «першу підхожу» — він повідомляє окремий статус ambiguous. Це машинна межа проти надто широких виразів: чим більше у виразі загальних слів («користувач бачить», «сторінка відкрита»), тим швидше він зіткнеться з чужим.
Ключове слово, яке нічого не гарантує. Оскільки Given/When/Then у пошуку не беруть участі, ніщо не забороняє тому самому методу виконуватися і як , і як перевірка. Звідси класика «Then, який щось робить», і сценарії, де перевірка тихо змінює стан системи.
Стан, який зʼїв незалежність. World робить передачу стану легальною — і тому легко зловживаною. Практичний критерій для рев'ю: якщо крок читається лише разом із попереднім, стан їде через World, і сценарій перестає бути незалежним описом поведінки.
Імперативність. Терміни офіційні: (declarative scenario) описує намір, (imperative scenario) — кроки інтерфейсу. Різниця сформульована як «what, not how»: сценарій описує задуману поведінку, а не реалізацію. Декларативні сценарії легше підтримувати й вони менш крихкі, і вигода дуже конкретна — при зміні реалізації правиться код кроків, а не сценарій, бо конкретні значення живуть у визначеннях. При цьому догми «декларативно завжди» дока не встановлює: імперативний стиль поданий без осуду, «in some contexts this style of test is appropriate».
Перевірка не того боку. Результат у Then має бути спостережуваним виходом — звітом, інтерфейсом, повідомленням, а не поведінкою, глибоко захованою в системі, як-от запис у БД. Дока каже це прямо, з наказовою інтонацією: спокусі перевіряти в Then базу — опиратися. Формально це рівень настанови (should), а не заборони, але сказано прямо, а не між рядків.
І межа, яку варто визнавати вголос: реалізація й підтримка кроків природною мовою — складна задача сама по собі, а надто складні кроки роблять дебаг важким і дорогим. Це формулювання ISTQB CTAL-TAE §3.1.4, і воно ж — коректна відповідь на «чому ваша BDD-сюїта така дорога». Там же названо й головну пастку підходу: команди зводять BDD до «тестів природною мовою», не залучаючи бізнес і розробників.
Типові помилки
- Виглядає як «крок не знайшов дефініцію через ключове слово», а насправді ключове слово в пошуку не бере участі. Той самий текст під
Givenі підThen— та сама дефініція, і жодного «пошуку серед Given-ів» не існує. - Виглядає як «інструмент узяв першу підхожу дефініцію», а насправді він не вибирає. Два збіги — це статус
ambiguous, а не тихий вибір. Якщо ви впевнені, що «спрацював не той степ», — швидше за все вираз ширший, ніж здається. - Виглядає як «хук локальний для цієї фічі», а насправді місце оголошення нічого не звужує. Хук сценарію виконується для кожного сценарію прогону; звузити його можна лише умовними хуками за тегами.
- Виглядає як «упало десять кроків», а насправді впав один. Кроки після впалого, невизначеного чи відкладеного не виконуються взагалі й позначаються пропущеними — читати треба перший червоний, а не останній.
- Виглядає як «крок повернув
false, отже провалився», а насправді falsy-значення кроку не валить. Провал — це виняток; перевірка без дає зелений крок при будь-якому результаті. - Виглядає як «стан протік між сценаріями», а насправді протекло те, що лежить поза World. Кожен сценарій дістає новий екземпляр World навіть на ретраї; тече модульна змінна, спільний браузер або дані в системі.
- Виглядає як «
Thenперевіряє результат», а насправді він перевіряє запис у БД. Правило Gherkin приписує спостережуваний вихід, а проти зазирання в базу дока застерігає прямо й наказово — «опирайтеся спокусі», а не «на ваш розсуд». - Виглядає як «детальний сценарій точніший», а насправді він імперативний. Кроки на кожен клік означають, що при зміні UI правиться фіча-файл — той самий артефакт, який мав бути стабільним описом поведінки.
Підсумок
- Степ-дефініція — це метод плюс вираз; зіставляється лише текст після ключового слова, а самі
Given/When/Thenна пошук не впливають. - Усі дефініції завантажуються до старту прогону, тому простір імен виразів спільний, а тека скоупом не є: конфлікт лікує унікальність виразу, а не структура репозиторію.
- Параметр приходить у крок типізованим (
{int}— це число), тож параметризація степів — про типи параметрів, а не про розбір рядків усередині методу. - World — ізольований скоуп сценарію: новий екземпляр на кожен сценарій навіть при ретраї, відкидається в кінці й недоступний у
BeforeAll/AfterAll; звідси й розкладка «браузер поза World, контекст і сторінка — у World». - Крок валить виняток, а не повернуте значення; кроки після впалого пропускаються — тому перший червоний у звіті і є діагнозом.
Можливі питання
- «Як рядок у фіча-файлі знаходить свій код?» Сильна відповідь: вираз степ-дефініції зіставляється з текстом після ключового слова; усі дефініції завантажені до старту; групи захоплення збираються, приводяться до типів параметрів і передаються в метод. Інтервʼюер слухає, чи звучить «ключове слово в зіставленні не бере участі».
- «Як передати дані між кроками?» Очікують World і його межі: новий екземпляр на сценарій (включно з ретраєм), стан відкидається в кінці, у глобальних хуках його немає. Відповідь «глобальна змінна» одразу показує, що людина не бачила паралельного прогону.
- «Два кроки з однаковим текстом — що станеться?» Правильно:
ambiguous, інструмент не вибирає сам. Слабка відповідь — «спрацює перший знайдений». - «Чим
Beforeвідрізняється відBeforeAll?» Перевіряють розуміння скоупу:Before— перед першим кроком кожного сценарію й з доступом до World;BeforeAll— поза сценарієм і без World. Бонус — сказати, що місце оголошення хука нічого не звужує, а звужують теги. - «Ви ганяєте Cucumber із Playwright — хто з них ранер?» Cucumber; Playwright тут бібліотека, тож
browser → context → pageбудуєш і закриваєш сам. Хороший кандидат додає, що контекст логічно тримати в World, а браузер — поза ним. - «Чому BDD-сюїти часто дорогі в підтримці?» Тут очікують не скарг, а механізмів: надто широкі вирази, кроки, що читаються лише разом, імперативні сценарії, перевірки в
Thenне того боку системи. Формулювання ISTQB — складність реалізації й підтримки кроків природною мовою плюс дорогий дебаг складних кроків — звучить як відповідь інженера, а не як враження.
Джерела
Крок Gherkin — це виклик, а не рядок тексту
- Cucumber — Step definitions — означення степ-дефініції як методу з виразом і те, що зіставляється лише текст після ключового слова.
- Cucumber — Cucumber reference — крок як аналог виклику методу, завантаження всіх дефініцій до старту фічі, три кроки зіставлення, байдужість до ключового слова, виняток проти falsy-значення і пропущені кроки після впалого.
- Cucumber — Gherkin Reference — Gherkin як набір ключових слів, що надає структури виконуваним специфікаціям.
- Martin Fowler — GivenWhenThen — розведення інструмента й мови: Cucumber — інструмент, Gherkin — назва його DSL.
- CucumberJS — World — World, доступний крокам як
this, тобто те, з чим викликається знайдений метод.
Параметризація: типізований аргумент замість розбору рядка
- Cucumber — Step definitions — одна дефініція на кілька кроків, два види виразів, групи захоплення як аргументи й приведення захопленого рядка до зареєстрованого типу параметра (
{int}→ ціле число). - Cucumber — Gherkin Reference —
Scenario Outlineяк прогін того самого сценарію на різних комбінаціях значень і синонімScenario Template. - Dan North — Introducing BDD — перевикористання фрагментів
Given/When/Thenміж сценаріями закладене в задум підходу.
World і хуки: де живе стан сценарію
- CucumberJS — World — World як ізольований скоуп сценарію й
thisкроків, новий екземпляр на кожен сценарій включно з ретраєм, відкидання стану з кінцем сценарію, недоступність уBeforeAll/AfterAllі конфлікт зі стрілочними функціями. - Cucumber — Cucumber reference — означення хуків, «хук сценарію виконується для кожного сценарію», незалежність від місця оголошення, умовні хуки як єдиний спосіб звузити дію і каталог
features/supportяк їхній дім. - Cucumber — Step definitions — крок як метод, у який приходить типізований аргумент і World.
CucumberJS плюс Playwright: хто тут ранер
- Playwright — Library — ієрархія
browser → context → page, яку в бібліотеці створюють вручну й по черзі; закриття контексту й браузера як обовʼязок коду; ліниві вбудовані фікстури ранера як протилежність; 30 секунд на більшість операцій у бібліотеці проти таймауту на тест у ранера. - Playwright — головна сторінка — свіжий контекст на кожен тест, рівносильний новому профілю браузера, з ізоляцією майже без накладних витрат.
- Playwright — Authentication — ізоляція контекстів як причина відтворюваності й захисту від каскадних падінь; дві ролі — два контексти з різними станами.
- Playwright — Test Isolation (browser contexts) — контекст як механізм ізоляції й багатокористувацьких сценаріїв.
- Playwright — Timeouts — рівні таймаутів власного ранера (таймаут тесту, окремий таймаут асерту), на тлі яких і видно, що межі прогону тут задає Cucumber.
- CucumberJS — World — недоступність World у
BeforeAll/AfterAllі новий екземпляр на кожен сценарій як підстава розкладки «браузер поза World, контекст у World». - Cucumber — Cucumber reference — хуки як точки підготовки й прибирання середовища навколо кожного сценарію; виняток як єдиний спосіб провалити крок.
- Cucumber — Gherkin Reference — вимога починати кожен непорожній рядок ключовим словом,
Featureяк перше ключове слово й однаFeatureна файл,Ruleз версії 6 як одне бізнес-правило,Backgroundпроти повторюванихGiven. - Cucumber — Cucumber reference — завантаження всіх дефініцій до старту фічі, незалежність хука від місця оголошення, каталог
features/supportяк дім хуків, вимога унікальності дефініцій. - Cucumber — Step definitions — вираз як єдиний ключ пошуку дефініції.
Степи-спагеті: як вони виглядають у коді
- Cucumber — Cucumber reference — вимога унікальності дефініцій і окремий статус
ambiguousзамість вибору навмання; байдужість пошуку до ключового слова. - Cucumber — Step definitions — вираз як єдиний ключ пошуку, через який і стикаються надто широкі степи.
- CucumberJS — World — передача стану між кроками як штатний механізм, з якого й ростуть залежні кроки.
- Cucumber Docs — Writing better Gherkin — офіційні терміни imperative / declarative, формула «what, not how», менша крихкість декларативних сценаріїв і застереження, що імперативний стиль інколи доречний.
- Cucumber — Gherkin Reference — настанова перевіряти в
Thenлише спостережуваний вихід і пряме застереження не зазирати в базу. - ISTQB® Certified Tester Advanced Level Test Automation Engineering (CTAL-TAE) v2.0 — §3.1.4: складність реалізації й підтримки кроків природною мовою, дорогий дебаг складних кроків і пастка «BDD як спосіб писати тести природною мовою».
- Cucumber — Cucumber reference — байдужість пошуку до ключового слова, статус
ambiguous, пропущені кроки після впалого, виняток проти falsy-значення, незалежність хука від місця оголошення й умовні хуки. - CucumberJS — World — новий екземпляр World на кожен сценарій включно з ретраєм і відкидання стану в кінці — тобто межа того, що World узагалі може «протекти».
- Cucumber — Gherkin Reference — правило про спостережуваний вихід у
Thenі пряма настанова не перевіряти базу. - Cucumber Docs — Writing better Gherkin — чому імперативний сценарій крихкий: конкретика має жити у визначеннях кроків.
- Cucumber — Step definitions — вираз як єдиний ключ пошуку й типізований аргумент замість розбору рядка.
- Cucumber — Step definitions — механіка зіставлення й типізованих параметрів, яку й перевіряє перше питання.
- Cucumber — Cucumber reference — порядок завантаження й зіставлення, статус
ambiguous, семантика хуків і результатів кроку. - CucumberJS — World — World як штатна відповідь на «як передати дані між кроками» і його межі в глобальних хуках.
- Playwright — Library — розкладка обовʼязків між бібліотекою й ранером, з якої випливає відповідь про звʼязку з Cucumber.
- Cucumber Docs — Writing better Gherkin — declarative проти imperative як мова відповіді про дорогу підтримку.
- ISTQB® Certified Tester Advanced Level Test Automation Engineering (CTAL-TAE) v2.0 — §3.1.4: названі поіменно вигоди й обмеження BDD, включно з ціною підтримки кроків.
Що таке крок Gherkin з погляду коду і чим його тримає степ-дефініція?
Найкоротша робоча модель: рядок у фіча-файлі — це виклик функції, просто записаний іншим синтаксисом. Файл *.feature тому й не «документація, яку колись автоматизують», а вже готовий список викликів; Gherkin тут — DSL з фіксованим набором ключових слів, а Cucumber — інструмент, який цю мову виконує. Пару «текст ↔ код» тримає (step definition): метод, що виконається, плюс вираз, за яким цей метод знаходять. Розділяти дві частини важливо, бо назва методу на пошук не впливає взагалі — ключем є лише вираз. Практичний наслідок для новачка на проєкті: щоб зрозуміти, що робить крок, шукають не за іменем функції, а грепом по тексту кроку.
Що саме зіставляється з виразом — увесь рядок кроку чи лише частина?
Тільки хвіст рядка, тобто текст після ключового слова. У кроці Given у кошику 3 товари інструмент шукатиме дефініцію для фрагмента у кошику 3 товари, а саме слово Given у пошук не потрапляє. Звідси річ, яка щоразу дивує: ключове слово не входить в ідентичність кроку, тож однаковий текст під Given і під Then веде в одну й ту саму дефініцію. Окремих «списків Given-ів» не існує, і жодної перевірки на кшталт «а чи не використали ви перевірку як передумову» інструмент не робить. Тому на ревʼю справжньою сигнатурою кроку варто вважати саме текст після ключового слова.
Крок не знайшов дефініції або знайшов дві. Що зробить інструмент?
Це два різні статуси, і жоден із них не означає «крок упав». Нуль збігів — undefined: реалізації немає, і Cucumber зазвичай ще й підкаже заготовку методу. Два і більше збігів — ambiguous: інструмент не бере «першу підхожу» й нічого не вгадує, він просто повідомляє, що вибір неоднозначний. Вимога унікальності дефініцій тут не стильова, а технічна — без неї нема як визначити, який метод виконувати. Практичний висновок: якщо здається, ніби «спрацював не той степ», реальна причина майже завжди інша — вираз ширший, ніж уявляв автор, і ловить чужі рядки.
Як Cucumber розуміє, що крок провалився?
Виключно через кинутий виняток (exception). Повернене з кроку значення інструмент не аналізує, тож return false або null дадуть зелений крок — це найдорожча пастка з усієї механіки. Наслідок: крок, у якому немає або в якому асерт загорнули в try/catch, лишається зеленим за будь-якого стану системи, і роками рапортує успіх там, де нічого не перевіряється. Тому в тілі кроку має бути або асерт бібліотеки перевірок, або явний throw; порівняння всередині if без кидка винятку перевіркою не є. Для ревʼю це простий фільтр: у кожному Then знайди рядок, здатний кинути виняток.
У звіті десять червоних кроків — скільки з них насправді впало?
Один. Після кроку, що впав (а так само після undefined чи відкладеного pending), решта кроків сценарію не виконується взагалі — вони отримують статус «пропущено» (skipped). Тобто картинка «половина сценарію червона» — це радше про читання звіту, ніж про систему: діагноз завжди в першому кроці, який змінив статус. Ця дрібниця економить години на розборі падінь у CI, бо не даєш собі шукати логіку в тому, що ніколи не запускалося. Побічний ефект теж корисний: сценарій припиняє калічити стан застосунку одразу після першої поломки.
Які бувають вирази степ-дефініцій і звідки в методі беруться аргументи?
Видів два: звичайний або Cucumber Expression. У регулярному аргументи дають групи захоплення (capture groups) — скільки груп, стільки параметрів приїде в метод, у тому ж порядку. У Cucumber Expression ту саму роль грають фігурні дужки з іменем типу — {int}, {string}, {word}, — і читається такий вираз незрівнянно легше за екранований regexp. Обидва види розвʼязують одну задачу: одна дефініція має обслуговувати цілу сімʼю схожих рядків, а не породжувати метод на кожен крок. Вибір між ними — питання читабельності: regexp лишають там, де треба тонше керувати межами збігу.
У виразі стоїть {int}. У метод прийде рядок чи число?
Число. Якщо захоплений фрагмент відповідає регулярці зареєстрованого типу параметра, він конвертується ще до виклику методу: за {int} стоїть \d+, і в аргумент приїжджає ціле число. Це варто промовити вголос, бо саме тут і ховається сенс фрази «параметризація степів»: вона про типи параметрів, а не про ручний розбір рядка всередині кроку. Наслідок для коду прямий: parseInt у тілі такого кроку — сигнал, що механізму людина не знає, а порівняння виду count === '3' у кроці з {int} просто ніколи не спрацює.
Чим Scenario Outline відрізняється від параметра у виразі степу?
Це два різні рівні множення, і плутати їх дорого. Scenario Outline (його синонім — Scenario Template) живе в самому фіча-файлі й проганяє один сценарій на різних наборах значень: множаться дані прогону. Параметр у виразі степу живе в коді й дозволяє одному методу приймати різні формулювання кроку: множаться тексти, що влучають в одну дефініцію. Тобто перший механізм відповідає на питання «скільки разів виконати сценарій», другий — «скільки рядків обслуговує ця функція». На практиці вони працюють у парі: аутлайн підставляє значення в рядок кроку, а типізований параметр доносить його в метод уже потрібним типом.
Як штатно передати дані між кроками одного сценарію?
Через World — ізольований , який Cucumber заводить на кожен сценарій і віддає крокам (та більшості хуків) як this. Один крок кладе туди значення, наступний його читає, а з завершенням сценарію все покладене зникає. Відповідь «глобальна змінна» на співбесіді читається однозначно: людина не бачила паралельного прогону, де спільний модульний стан протікає з одного сценарію в наступний. Сильна відповідь одразу окреслює межі механізму: свіжий екземпляр на кожен сценарій, порожньо після завершення, у глобальних хуках недоступний.
Чи може стан із World протекти в наступний сценарій або в повторну спробу?
Ні — і памʼятати це варто саме тому, що протікання стану на Cucumber-проєктах трапляється постійно. Кожен сценарій дістає власний екземпляр World, і (retry) не виняток: друга спроба не успадковує від першої нічого. Тому пояснення «на ретраї позеленіло, бо щось лишилося з минулої спроби» до World відношення не має. Справжні кандидати на джерело витоку інші: модульна змінна у файлі підтримки, спільний процес браузера, дані, залишені в самій системі — той самий email, той самий запис у базі. Діагностика від цього стає механічною: пережило сценарій — отже, лежить поза World.
Чому в кроках, які працюють зі станом, уникають стрілочних функцій?
Бо this у звʼязаний із зовнішнім скоупом, а не з World, тож звертання до стану сценарію працює не так, як очікує автор. Симптом упізнаваний: крок написали стрілкою — і this виявляється не тим обʼєктом. Класичний вихід — оголошувати кроки звичайною function; сучасніший — не спиратися на this взагалі, а імпортувати обʼєкт world і працювати з ним явно. Другий варіант ще й дружніший до TypeScript, бо не змушує щоразу типізувати this у сигнатурі кроку.
Чим Before відрізняється від BeforeAll?
Скоупом і доступом до стану. Before — хук сценарію: виконується перед першим кроком кожного сценарію, і World у ньому вже існує, тож саме тут готують контекст, сторінку, . BeforeAll спрацьовує один раз за прогін і, що головне, поза будь-яким сценарієм — тому World там недоступний за визначенням. Ця пара й задає архітектуру звʼязки з браузером: дороге та спільне (процес браузера) піднімають у BeforeAll і тримають у модульній змінній файлу підтримки, дешеве та ізольоване (контекст, сторінка) створюють у Before і кладуть у World. Бонусом на співбесіді звучить уточнення, що дію хука визначає не тека, у якій він лежить, а теги.
Файл із хуком лежить поруч з однією фічею — хук виконається лише для неї?
Ні. Хук сценарію відпрацює перед кожним сценарієм прогону, хоч би де його оголосили: сусідство з фіча-файлом не робить його локальним. Це прямий наслідок того, що файли підтримки й дефініції завантажуються ще до старту виконання фіч — тека жодного скоупу не створює. Єдиний штатний спосіб зробити хук вибірковим — умовний хук за тегом, тобто явно назвати сценарії, до яких він застосовується. Документація при цьому задає прямо лише один каталог — features/support для хуків і файлів підтримки; решта розкладки — домовленість команди.
Ви ганяєте CucumberJS разом із Playwright. Хто тут ранер і що це змінює в коді?
(test runner) — Cucumber: він вантажить дефініції, зіставляє кроки, викликає методи, виконує хуки й проставляє статуси. Playwright у цій схемі виступає бібліотекою, тому всю ієрархію доводиться будувати руками: вибрати рушій, підняти браузер, відкрити контекст, а вже в ньому сторінку. Симетрично зникає й автоматичне — закрити контекст і браузер зобовʼязаний твій код. Саме цим звʼязка й відрізняється від власного ранера Playwright, де page і context приїжджають : вони зʼявляються на вимогу тесту й закриваються без твоєї участі. Відповідь «ранери обидва» або «прогоном керує Playwright» одразу показує, що людина цієї конфігурації не збирала.
Де в цій звʼязці має жити браузер, а де — контекст і сторінка?
Процес браузера тримають у модульній змінній файлу підтримки, а контекст зі сторінкою — усередині World. Причина не смакова: браузер дорогий, його логічно піднімати один раз у BeforeAll, — а World там недоступний, отже, іншого місця й не лишається. Контекст навпаки дешевий і за властивостями рівносильний свіжому профілю браузера, тож кожен сценарій має отримувати власний: саме ця ізоляція дає відтворюваність і не дозволяє падінню одного сценарію тягнути за собою сусідів. World, який і так створюється заново на кожен сценарій, для контексту й сторінки — природний дім. Якщо у сценарії дві ролі (адмін і звичайний користувач), відповідь та сама: два контексти з різними станами, а не перелогін в одному й не два процеси браузера.
Що змінюється з таймаутами, коли Playwright використовують як бібліотеку?
Модель захисту від зависань стає іншою. У бібліотеки більшість операцій має власний ліміт близько 30 секунд, а от звичного «таймауту на тест», який рубає зависання цілком, тут не зʼявляється — межі прогону задає Cucumber, бо ранер саме він. Звичка «мене все одно підстрахує таймаут тесту» в цій конфігурації автоматично не працює, і тривалість кроку доводиться тримати в голові свідомо. Практично звідси два правила: спиратися на вбудовані очікування бібліотеки замість власних пауз і памʼятати, що провалити крок може лише кинутий виняток — очікування його кине, а мовчазна перевірка без асерту не кине нічого.
Які правила Gherkin визначають структуру фіча-файлу?
Кістяк тут жорсткий і обійти його неможливо. Ключове слово обовʼязкове на початку кожного рядка, у якому взагалі щось написано, тож просто дописати між кроками абзац пояснення не вийде. Першим первинним (primary) ключовим словом документа завжди йде Feature, і таке слово у файлі може бути рівно одне, тому звична для коду ідея «один файл на модуль» тут не реалізовується за побудовою: файл — це одна фіча. Проміжний рівень групування дає необовʼязкове Rule (зʼявилося в шостій версії Gherkin), яке представляє одне бізнес-правило. Повторювані виносять у Background, щоб не копіювати ідентичні Given у кожен сценарій файлу.
Степи розклали по теках модулів, щоб вони не конфліктували. Спрацює?
Ні, і це найкорисніша річ, яку варто знати про репозиторій із Cucumber. Усі дефініції завантажуються до того, як почнеться виконання фічі, тож простір імен виразів фактично один на весь прогін, а тека скоупом не є. Дефініція з каталогу оплати доступна фічі профілю рівно так само, як «своїм» сценаріям, і випадково збіглий вираз зіштовхне два файли, які ніколи не мали перетнутися. Розвʼязує колізію тільки точніший вираз — вужче формулювання, менше загальних слів, — а не переїзд файлу в іншу теку. Групування по доменах при цьому корисне, але як домовленість команди про читабельність, а не як механізм ізоляції.
Що таке «степи-спагеті» і з яких механізмів вони ростуть?
Це не оцінка стилю, а набір конкретних механізмів із впізнаваними симптомами. Перший — надто широкі вирази: чим більше в них загальних слів на кшталт «користувач бачить», тим швидше вони зіткнуться з чужим кроком і дадуть ambiguous. Другий — байдужість пошуку до ключового слова: ніщо не заважає одному методу працювати і як передумова, і як перевірка, звідси класичний Then, який тихо змінює стан системи. Третій — зловживання World: якщо крок читається лише в парі з попереднім, сценарій перестав бути незалежним описом поведінки й перетворився на процедуру. Четвертий — перевірка не того боку: у Then має стояти результат, який видно ззовні (екран, повідомлення, звіт), а зазирати звідти в базу даних документація радить не піддаватися спокусі. Загальну причину дорожнечі ISTQB формулює прямо: написати й супроводжувати кроки природною мовою — окрема інженерна робота, а заплутані кроки перетворюють розбір падінь на дорогу процедуру.
Чим декларативний сценарій відрізняється від імперативного і чому це питання про гроші?
(declarative scenario) описує намір, імперативний (imperative scenario) — послідовність дій в інтерфейсі; канонічна формула звучить як «what, not how». У гроші різниця перетворюється в момент зміни реалізації: коли конкретика (селектори, кліки, точні значення) живе у визначеннях кроків, редизайн правиться в коді, а фіча-файл лишається недоторканим. Якщо ж сценарій розписаний по кліках, кожна зміна UI тягне правку саме того артефакту, який мав бути стабільним описом поведінки — і який ще й читає бізнес. Догми тут немає: документація прямо визнає, що в певних контекстах імперативний стиль доречний. Тому сильна відповідь звучить як компроміс, а не гасло: декларативно за замовчуванням, імперативно — усвідомлено й локально.
Три ситуації, у яких механіка степів перестає бути теорією: читання звіту прогону, де «впало пів сценарію», діагностика ambiguous, який приїхав із чужої теки, і мінімальний каркас звʼязки CucumberJS з Playwright. Скрізь — на що дивитися і чому саме так.
Кейс 1. Звіт каже «впало багато», а впав один крок
Класична картинка в чаті команди: із десятком не-зелених кроків і підпис «усе розвалилося». Перш ніж заводити баг, треба прочитати статуси — вони й містять діагноз. Хвіст звіту cucumber-js виглядає приблизно так:
Failures:
1) Scenario: Промокод дає знижку на три товари # features/checkout/promo.feature:5
✔ Given у кошику 3 товари # features/steps/cart.steps.ts:9
✖ When користувач застосовує промокод "SUMMER" # features/steps/promo.steps.ts:14
Error: locator.click: Timeout 30000ms exceeded.
waiting for getByTestId('promo-apply')
- Then у кошику видно знижку 10%
- And загальна сума зменшується на 10%
1 scenario (1 failed)
4 steps (1 failed, 2 skipped, 1 passed)
Тепер той самий сценарій, але з іншою проблемою — він зелений, хоча перевірка не працює:
import { Then } from '@cucumber/cucumber';
import { ShopWorld } from '../support/world';
// Виглядає як перевірка, а насправді нічого не валить
Then('у кошику видно знижку 10%', async function (this: ShopWorld) {
const badge = await this.page.getByTestId('discount').textContent();
return badge === '-10%'; // повернене значення інструмент не дивиться
});
Робоча версія відрізняється однією властивістю — вона здатна кинути виняток:
import { expect } from '@playwright/test';
Then('у кошику видно знижку 10%', async function (this: ShopWorld) {
await expect(this.page.getByTestId('discount')).toHaveText('-10%');
});
Що дивитися і чому:
- Значок
-— це не «впало», а «не запускалося». Кроки після першого червоного не виконуються взагалі, тому шукати причину в останньому рядку безглуздо: він ніколи не працював. Читай перший крок, який змінив статус, і рядок помилки під ним. - Підсумковий рядок розкладає статуси чесно.
1 failed, 2 skipped, 1 passedодразу знімає паніку «розвалилося пів сюїти»: поламане місце одне. - Помилка вказує на дію, а не на перевірку. Тут упав
Whenна кліку — тобто до справа не дійшла, і питання не до логіки знижки, а до кнопки чи до стану кошика перед кроком. - Зелений крок без винятку — найдорожчий дефект . У першому варіанті
Thenпорівнює значення й повертає результат, а Cucumber повернене значення ігнорує: перевірка стає декоративною і не червоніє ніколи. На ревʼю в кожномуThenшукай рядок, який фізично здатен кинути виняток — асерт або явнийthrow. - Крок з усе-таки червоний. Очікування бібліотеки, вичерпавши ліміт, кидає виняток — і саме це робить крок впалим. Тому в кроках спираються на вбудовані очікування, а не на власні паузи:
sleepмовчки завершиться успіхом і поїде далі з несправним станом.
Кейс 2. ambiguous, який приїхав із сусіднього домену
Команда розклала степи по теках модулів і вважала питання конфліктів закритим. Прогін показує інше:
✖ Then користувач бачить "Знижку застосовано"
Multiple step definitions match:
користувач бачить {string} # features/steps/cart.steps.ts:22
користувач бачить {string} # features/steps/profile.steps.ts:15
Обидві дефініції написані охайно, просто в різних каталогах і різними людьми:
// features/steps/cart.steps.ts
Then('користувач бачить {string}', async function (this: ShopWorld, text: string) {
await expect(this.page.getByRole('alert')).toHaveText(text);
});
// features/steps/profile.steps.ts
Then('користувач бачить {string}', async function (this: ShopWorld, text: string) {
await expect(this.page.getByTestId('profile-toast')).toHaveText(text);
});
Лікується це не переміщенням файлів, а вужчим формулюванням — таким, яке неможливо випадково повторити в іншому домені:
// features/steps/cart.steps.ts
Then('у кошику зʼявляється сповіщення {string}', async function (this: ShopWorld, text: string) {
await expect(this.page.getByRole('alert')).toHaveText(text);
});
// features/steps/profile.steps.ts
Then('у профілі зʼявляється сповіщення {string}', async function (this: ShopWorld, text: string) {
await expect(this.page.getByTestId('profile-toast')).toHaveText(text);
});
Що дивитися і чому:
- Тека нічого не ізолює. Дефініції завантажуються до старту виконання фіч, тож простір імен виразів один на весь прогін: файл із кошика бачить фіча профілю і навпаки. Аргумент «вони ж у різних папках» не має під собою механізму.
- Інструмент не вибирає навмання — і добре.
ambiguousнеприємний, але чесний: він падає одразу й показує обидва місця з номерами рядків. Гірший сценарій — коли вираз однієї команди виявився ширшим і тихо перехопив чужі рядки, лишившись єдиним збігом. - Загальні слова у виразі — це борг. «Користувач бачить», «сторінка відкрита», «дані збережено» рано чи пізно зіткнуться з чужим кроком. Домен у формулюванні («у кошику», «у профілі») коштує пʼять символів і знімає цілий клас конфліктів.
- Ключове слово тут ні від чого не рятує. Змінити
ThenнаAndчиGivenмарно: у пошуку бере участь лише текст після ключового слова, тому обидві дефініції лишаться конкурентами. - Перевір заразом, що
Thenнічого не робить. Через ту саму байдужість до ключового слова один метод може випадково стати і , і перевіркою — після рефакторингу виразів це помітно найкраще.
Кейс 3. Каркас CucumberJS плюс Playwright: що де живе
тут виступає Cucumber, тому ієрархію Playwright доводиться будувати вручну. Розкладка не довільна: браузер дорогий і піднімається раз на прогін, а World у глобальних хуках недоступний — отже, браузеру місце в модульній змінній, а контексту зі сторінкою в World.
// features/support/world.ts
import { setWorldConstructor, World, IWorldOptions } from '@cucumber/cucumber';
import { BrowserContext, Page } from 'playwright';
export class ShopWorld extends World {
context!: BrowserContext;
page!: Page;
adminContext?: BrowserContext; // друга роль — окремий контекст
adminPage?: Page;
constructor(options: IWorldOptions) {
super(options);
}
}
setWorldConstructor(ShopWorld);
// features/support/hooks.ts
import { BeforeAll, Before, After, AfterAll } from '@cucumber/cucumber';
import { chromium, Browser } from 'playwright';
import { ShopWorld } from './world';
let browser: Browser; // поза World: BeforeAll його не бачить
BeforeAll(async () => {
browser = await chromium.launch();
});
Before(async function (this: ShopWorld) {
this.context = await browser.newContext(); // свіжий профіль на сценарій
this.page = await this.context.newPage();
});
// Умовний хук — єдиний спосіб звузити дію; тека тут ні до чого
Before({ tags: '@admin' }, async function (this: ShopWorld) {
this.adminContext = await browser.newContext({ storageState: 'admin.json' });
this.adminPage = await this.adminContext.newPage();
});
After(async function (this: ShopWorld) {
await this.adminContext?.close();
await this.context.close(); // у бібліотеці прибирає твій код
});
AfterAll(async () => {
await browser.close();
});
Кроки після цього працюють зі станом сценарію через this, а не через змінні модуля:
// features/steps/cart.steps.ts
import { Given, When } from '@cucumber/cucumber';
import { ShopWorld } from '../support/world';
Given('у кошику {int} товари', async function (this: ShopWorld, count: number) {
await seedCart(this.page, count); // count — число, а не рядок
});
When('адмін вимикає промокод {string}', async function (this: ShopWorld, code: string) {
await this.adminPage!.getByTestId(`promo-${code}`).getByRole('switch').click();
});
Що дивитися і чому:
- Браузер у модульній змінній — не недбалість, а вимушений розклад.
BeforeAllвиконується поза будь-яким сценарієм, тому World у ньому просто не існує. Спроба покласти туди браузер закінчується не помилкою дизайну, а помилкою виконання. - Контекст на сценарій — те, що замінює . Новий контекст рівносильний свіжому профілю браузера з власними кукі та сховищем, і коштує майже нічого. Чистий стан тут не вичищають після сценарію — його видають на початку, а забути видати неможливо.
- Дві ролі — два контексти, а не два браузери й не перелогін. Перелогін в одному контексті ламає ізоляцію і робить сценарій залежним від порядку кроків; другий процес браузера — просто зайві секунди й памʼять.
- Тег у хуці робить те, чого не робить структура каталогів. Звичайний
Beforeвідпрацює перед кожним сценарієм прогону, хоч би де лежав файл. Умовний варіант ізtags— єдиний штатний спосіб сказати «тільки для цих сценаріїв». close()пишеш ти. У бібліотеці немає , які приберуть за тобою: контексти закриваються вAfter, браузер — вAfterAll. Пропущенийcloseу довгому прогоні виглядає як повільне з'їдання памʼяті на агенті CI, а не як явна помилка.- Сигнатура кроку типізує
thisявно. тут не підійде:thisу ній вказує на зовнішній , а не на World, і стан сценарію стане недосяжним.
Як рядок фічі знаходить свій код
- Можу дати означення (step definition) без підказки: метод плюс вираз, причому ключем пошуку є вираз, а не назва методу, і зіставляється лише текст після ключового слова — тож однакове формулювання під
Givenі підThenведе в одну дефініцію. - Знаю порядок роботи інструмента: усі дефініції завантажені ще до старту фічі → пошук збігу → збір груп захоплення й приведення до типів → виклик методу, якому World доступний як
this. - Розрізняю
undefined(жодного збігу) іambiguous(два й більше) і памʼятаю, що інструмент не бере «першу підхожу», а зупиняється. - Можу пояснити, чому провалити крок здатен лише кинутий виняток (
return falseлишає його зеленим) і чому «десять червоних кроків» не буває: після впалого, невизначеного чи відкладеного кроку решта йде в пропущені, а діагноз — у першому червоному.
Параметри й вирази
- Знаю два види виразів: регулярний (аргументи дають групи захоплення) і Cucumber Expression із фігурними дужками —
{int},{string},{word}. - Памʼятаю, що параметр приїжджає в метод уже приведеним до типу: за
{int}стоїть\d+, тому в аргументі число, аparseIntу тілі кроку зайвий. - Розводжу два рівні параметризації:
Scenario Outline(він жеScenario Template) множить дані прогону, а вираз степу — тексти кроків, що влучають в один метод.
World: стан сценарію
- Можу пояснити, що World — ізольований сценарію, доступний крокам як
this, і що покладене в нього зникає з завершенням сценарію. - Знаю, що екземпляр створюється свіжий на кожен сценарій, включно з (retry), тому справжніх кандидатів на витік стану шукаю поза World: модульна змінна файлу підтримки, спільний процес браузера, дані, залишені в самій системі.
- Розумію конфлікт
thisзі і знаю обхід: звичайнаfunctionабо імпортований обʼєктworld.
Хуки
- Можу пояснити різницю
BeforeіBeforeAll: перший спрацьовує перед першим кроком кожного сценарію й бачить World, другий виконується раз на прогін поза сценарієм і World не має. - Памʼятаю, що хук не стає локальним від того, у якій теці лежить його файл: звузити дію можна лише умовним хуком за тегом, а дока фіксує єдиний каталог —
features/support.
CucumberJS плюс Playwright
- Без вагань відповідаю, хто (test runner): Cucumber; Playwright тут бібліотека, тож ланцюжок «браузер → контекст → сторінка» будую і закриваю сам.
- Знаю робочу розкладку і причину за нею: процес браузера піднімається в
BeforeAllі живе в модульній змінній, а контекст зі сторінкою створюються вBeforeі живуть у World, бо свіжий контекст рівносильний новому профілю браузера й не пускає падіння каскадом; дві ролі в сценарії — це два контексти з різними станами, а не перелогін в одному. - Памʼятаю про : у бібліотеки близько 30 секунд на більшість операцій, а звичного таймауту на тест немає — межі прогону задає Cucumber.
Структура фіч і репозиторію
- Знаю жорсткі правила Gherkin: кожен непорожній рядок починається з ключового слова,
Feature— перше слово й лише одне на файл,Rule(з версії 6) групує одне бізнес-правило,Backgroundвиносить спільні замість продубльованихGiven. - Розумію, що простір імен виразів спільний на весь прогін, тека скоупом не є, а колізію розвʼязує вужчий вираз, а не переїзд файлу.
Степи-спагеті й ціна підтримки
- Можу назвати механізми поіменно: надто широкі вирази,
Then, що змінює стан, кроки, звʼязані через World, — і сказати, який симптом дає кожен. - Памʼятаю настанову щодо
Then: перевіряти результат, який видно ззовні, а не запис у базі — проти зазирання в БД дока застерігає прямо. - Знаю офіційну пару термінів declarative / imperative, формулу «what, not how», те, що імперативний стиль подекуди визнається доречним, і відповідь на «чому BDD-сюїта дорога» мовою ISTQB: писати й супроводжувати кроки природною мовою — окрема інженерна робота, а заплутані кроки роблять розбір падінь дорогим.
Квіз
Перед стартом
- Питань: 16
- Поріг «зараховано»: ≥70% правильних відповідей.
- Результат впливає на прогрес; завалені питання підуть у чергу повторення.
- Квіз впливає на компліт теми: тема стає «пройдено», лише коли прочитано теорію І квіз складено на ≥70%.
Питання
Степ-дефініція (step definition) — з чого вона складається?
