vyvchy
    Теми розділу

    07 · Інструменти автоматизації

    CodeceptJS: актор, хелпери і сценарний стиль

    Зміст

    Ви відкриваєте чужий репозиторій і бачите тест без , без обʼєкта сторінки і без жодного await:

    Scenario('гість не бачить кабінет', ({ I }) => {
      I.amOnPage('/');
      I.dontSee('Кабінет', 'nav');
    });

    Перше враження — «ще один синтаксис для тих самих кліків». Насправді це інший рівень абстракції: сценарій каже, що робить користувач, а чим це виконується, вирішує конфігурація. Той самий текст може поїхати в браузер через Playwright, через WebDriver-протокол або в мобільний застосунок через Appium — і саме ця розвʼязка є причиною, чому інструмент узагалі існує.

    Практичних інтересів тут два. Перший — читати такі репозиторії: не розуміючи, хто саме відповідає за крок, ви півгодини шукаєте відповідь у доці Playwright, поки причина живе на рівні надбудови. Другий — питання «коли доречний CodeceptJS»: сильна відповідь тут про рівень абстракції та його ціну, а не про список фіч. Три розібрані в главі «Ландшафт інструментів»; ця глава — про те, як надбудова над ними влаштована зсередини.

    Актор I: делегування замість виклику

    Тести пишуться як лінійний сценарій дій користувача, і кожен тест — функція Scenario з переданим у неї обʼєктом I.

    I — не глобальна змінна для скорочення запису. Дока називає його (actor): це абстракція користувача-тестувальника і водночас до ввімкнених хелперів. Усі команди тесту делегуються бекендам — сам тест дій не виконує.

    Звідси головна межа відповідальності: I.click('Увійти') не є ні кліком, ні браузера. Це запис у чергу, який хелпер перекладе на свій протокол. Тому фраза «у CodeceptJS не працює X» зазвичай означає «хелпер, який у нас увімкнений, не вміє X», і перше, що варто відкрити при розслідуванні, — не сторінку про сценарії, а сторінку конкретного хелпера.

    Чому тест виглядає синхронним

    await у прикладі вище немає не тому, що дії синхронні. Усі дії загорнуті в глобальний ланцюг і зчеплені між собою, тож звичайні команди ставляться в чергу автоматично.

    Правило, де ця автоматика закінчується, дока формулює жорстко: await обовʼязковий для команд, що починаються з grab, і для викликів імпортованих функцій та методів . Звичайні дії (I.click(), I.fillField(), I.see()) його не потребують. Логіка проста: черга покриває дії актора, а не ваш власний код і не значення, які тест забирає зі сторінки.

    Два наслідки, які варто знати одразу:

    • Очікування вбудоване в дії. Інструмент сам чекає на елемент перед кліком, заповненням і більшістю інших взаємодій, тож явні очікування потрібні рідко.
    • Невдалий крок повторюється автоматично. Це на рівні кроку — рівень нижче за ретрай тесту, і плутати їх у розмові про не варто.

    Для чутливих значень є власна обгортка: I.fillField('password', secret('123456')) не світить значення в логу кроків.

    Хелпери: один сценарій, різні бекенди

    Хелпер (helper) — це бекенд, який насправді виконує кроки. Актор називає дію, а яким протоколом і яким браузером вона зробиться, вирішує підключений хелпер.

    Бекенди дока називає поіменно, і вони перекривають різні моделі керування браузером одразу: Playwright (власний протокол інструмента), WebDriver (wire-протокол W3C), Puppeteer (протокол DevTools), Appium (мобільні застосунки).

    Scenario з обʼєктом I

    актор I
    проксі до хелперів

    хелпер Playwright

    хелпер WebDriver

    хелпер Puppeteer

    власний протокол

    wire-протокол W3C

    протокол DevTools

    Scenario з обʼєктом I

    актор I
    проксі до хелперів

    хелпер Playwright

    хелпер WebDriver

    хелпер Puppeteer

    власний протокол

    wire-протокол W3C

    протокол DevTools

    Вибір робиться в конфігурації, і два найважливіші налаштування дока називає прямо: який хелпер (тобто який рушій виконує) і базовий URL застосунку. Хелпери вмикаються й конфігуруються секцією helpers:

    // codecept.conf.ts — той самий набір сценаріїв, інший виконавець
    helpers: {
      Playwright: { url: 'https://mysite.com', browser: 'firefox' }
    }

    І тут головний компроміс, через який цю главу взагалі варто читати уважно. Хелпери мають спільну API, тож перемкнути бекенд легко. Але дока сама ставить межу: через відмінності й обмеження бекендів вони не гарантовано сумісні між собою — і контрприклад наводить власний: заголовки запиту можна ставити в Playwright і Puppeteer, а у WebDriver ні. Порада сформульована як вибір, а не як обіцянка універсальності: узяти один хелпер, а міграцію робити, якщо зміняться вимоги.

    Різниця між «легко мігрувати» і «працює однаково» — це різниця між правдою і тим, що зазвичай обіцяють на дошці. Архітектурні причини цих відмінностей — у главі «Playwright: архітектура, браузери і контексти».

    Семантичні локатори: рядок, який читається як сторінка

    У прикладі на початку глави замість селектора стоїть рядок 'Кабінет'. Це не спрощення для документації, а один із двох штатних видів локаторів.

    • Локатор із явною стратегією — обʼєкт, ключ якого прямо називає стратегію пошуку: { css: 'button' }, { xpath: '//td[1]' }, { id: 'email' }, а для ролі — { role: 'button', name: 'Submit' }, де другий ключ уточнює доступне імʼя. Стратегія відома наперед, тож хелпер робить рівно один запит. Дока називає такий локатор «строгим» — і тут легко наштовхнутися на омонім: строгість тут про однозначність стратегії пошуку, а не про кількість знайдених елементів. Строгий режим локаторів Playwright — зовсім інше поняття, і воно розібране в главі «Playwright: локатори і дії з елементами».
    • Семантичний локатор — звичайний рядок на кшталт 'Sign In' або 'Email'. Інструмент зіставляє його з підписами, текстом кнопок, плейсхолдерами й aria-* атрибутами — так, як сторінку читає користувач.

    Обидва види ідіоматичні, але найсильнішим патерном дока називає семантичний локатор у контексті — тобто з другим аргументом, який обмежує зону пошуку (I.click('Зберегти', '.modal')). Мотив названо прямо: дублікати підписів деінде на сторінці більше не дають нестабільних збігів. Це рекомендований дефолт для стабільних сценаріїв, а не скорочення для прототипу.

    Ціна семантичного пошуку теж названа чесно: він робить кілька запитів замість одного, але кожен запит дешевий, а контекст різко звужує простір пошуку. Для fillField і схожих дій порядок спроб такий:

    рядок або обʼєкт-локатор

    ARIA-роль — через дерево доступності

    локатор із явною стратегією: css, xpath, id

    точний збіг: name, id + label[for], placeholder
    або label з таким текстом навколо поля

    той самий збіг частково + aria-label, aria-labelledby, title

    атрибут name

    той самий рядок як CSS-селектор

    ElementNotFound

    рядок або обʼєкт-локатор

    ARIA-роль — через дерево доступності

    локатор із явною стратегією: css, xpath, id

    точний збіг: name, id + label[for], placeholder
    або label з таким текстом навколо поля

    той самий збіг частково + aria-label, aria-labelledby, title

    атрибут name

    той самий рядок як CSS-селектор

    ElementNotFound

    Ланцюг варто прочитати уважно один раз, бо він знімає два непорозуміння. Перше: рядок, який ви вважали текстом підпису, на останньому кроці трактується як CSS-селектор — тож дивна помилка на локаторі 'input' має цілком логічне . Друге: ролевий локатор резолвиться через дерево доступності (accessibility tree), тобто напряму залежить від семантики розмітки — тієї самої, про яку йдеться в главі «Семантичний HTML і доступність».

    CSS дока при цьому не забороняє: вона називає його найшвидшим типом локатора, який більшість фронтендерів читає без запинки, і дає сценарій, де швидкість вирішує, — гарячий цикл із тисячами викликів локатора.

    Кастомні кроки і page object: розширюють актора, а не базовий клас

    Патерн page object тут той самий, що всюди: обʼєкт обгортає сторінку або її фрагмент застосунковим API, щоб тест не длубався в HTML, і в лишається рівно одне місце, яке знає структуру розмітки конкретної частини сторінки. Змінюється не патерн, а механіка доставки — і вона в цьому інструменті інша, ніж у решті.

    По-перше, спільні кроки розширюють актора. Під час ініціалізації проєкт пропонує створити файл кастомних кроків, і саме через нього до I додаються власні дії. У класичному ту саму роль зазвичай грає базовий клас тесту або ; тут спільна поведінка живе на акторі.

    По-друге, page object не імпортують, а інжектять за іменем. Обʼєкти оголошуються в секції include конфігурації, а отримати їх можна параметром сценарію або глобальним викликом inject():

    // codecept.conf.ts
    include: { I: './steps_file.js', loginPage: './pages/Login.js' }
    
    // tests/login_test.ts
    Scenario('вхід із валідними даними', async ({ I, loginPage }) => {
      await loginPage.signIn('user@example.com', secret('123456'));
      I.see('Кабінет', 'nav');
    });

    Механізм — (dependency injection): клас експортується як є, а екземпляр створює . Виклик inject() на початку файла повертає ледачий проксі, тож I та інші обʼєкти резолвляться в момент виклику — деструктуризувати їх до означення класу безпечно. Звідси й await у прикладі: метод page object — це вже ваш код, а не дія актора, і черга його не покриває.

    По-третє, технічно тотожні page object: різниця лише концептуальна — фрагмент описує автономну частину сторінки (модалка, віджет, компонент). Імена і page object, і фрагментів оголошуються в тій самій секції include.

    Варто розвести два «актори», які легко змішати. У патерні Screenplay актор — один із пʼяти будівельних блоків (актори, здібності, інтеракції, задачі, питання), він представляє людей і зовнішні системи, а сценарій може мати кількох акторів: один готує дані, інший працює через UI. Тут I — насамперед проксі до хелперів, тобто одиниця абстракції інша: не «задача актора», а «крок сценарію». Питання «page object чи Screenplay» — територія розділу про стратегію автоматизації, і відповідь там не «краще/гірше», а «яка одиниця абстракції».

    Плагіни: ретрай кроку, логін раз на прогін, артефакти

    Плагіни тут постачаються разом із фреймворком, тож увімкнути потрібне — питання конфігурації, а не пошуку сторонньої залежності. Це важлива деталь для читання чужого репозиторію: значна частина поведінки, яку ви сприймаєте як «магію фреймворку», стоїть рядком у секції plugins.

    Що варто знати поіменно:

    • retryFailedStep — повторює кожен невдалий крок у тесті. Разом із вбудованим ретраєм кроку це другий важіль на тому самому рівні; таксономія причин й політика ретраїв — територія розділу про стратегію автоматизації, а механіка засобами інструмента — глави «Боротьба з флаком засобами інструмента».
    • auth — логінить користувача в першому тесті й перевикористовує сесію далі: кукі зберігаються в памʼять або файл, а якщо сесія протухає, плагін логіниться знову сам. Якщо у вашому конспекті стоїть autoLogin — у чинному реєстрі плагінів такого рядка немає; функцію несе auth.
    • Артефакти падіння — окремі плагіни, а не вбудована поведінка: при падінні, збір інформації зі сторінки після кожного невдалого тесту (дока прямо радить вмикати його, якщо ви ганяєте тести на CI й вам треба дебажити падіння) і запис відео у WebM через screencast API Playwright.
    • Звіт JUnit XML — теж окремий плагін: після прогону він генерує XML-звіт, сумісний із JUnit, — саме його зазвичай і читає CI; репортинг детальніше — у главі «Playwright: дебаг, trace viewer і репортинг».
    • кроку ставиться глобально окремим плагіном. Сусідній плагін додає між діями — і його варто читати як маскування повільності застосунку, а не як лікування.

    Порівняння з Playwright тут показове. Там живе у файлі, а за протуханням інструмент не стежить — видалити протухлий файл має автор тестів (глава «Playwright: автентифікація і повторне використання стану»). Тут ту саму роботу бере на себе плагін. Це не «краще»: автоматичне поновлення сесії зручне, поки ви не тестуєте саму поведінку протухлої сесії.

    Окремо про аварійний вихід. Сценарний стиль — абстракція, і абстракції протікають; фреймворк це визнає сам, бо один із плагінів віддає в сценарій внутрішній обʼєкт хелпера (наприклад, page із Playwright). Тобто спуститися на рівень нижче можна штатно — і саме там закінчується «один сценарій для всіх бекендів»: код, який торкнувся внутрішнього обʼєкта, привʼязаний до конкретного хелпера. У поставці є ще й клас плагінів на AI: самовідновлення тестів, аналіз падінь і у форматі, розрахованому на AI-агента.

    Коли CodeceptJS доречний

    Відповідь на це питання прямо випливає з хелперів: інструмент доречний тоді, коли вам потрібен один читабельний сценарний шар над різними бекендами — наприклад, веб і мобільний застосунок в однаковому стилі, або команда, у якій сценарії читають не лише автоматизатори. Дока сама називає синтаксис BDD-подібним — але «BDD-подібний» тут про читабельність запису в JavaScript, а не про Gherkin: у цьому стилі сценарій лишається кодом, а не текстом фіч-файла, зіставленим зі (їхня механіка — у главі «BDD-інструменти: CucumberJS і Gherkin»).

    Ціна цього шару — теж із хелперів, і назвати її треба самому, не чекаючи на уточнювальне питання:

    • Спільна API уніфікує запис, а не поведінку: гарантованої сумісності бекендів немає.
    • Між тестом і інструментом стоїть додатковий шар, і дебажити ви будете крізь нього — треба знати обидва рівні, а не один.
    • Аварійний вихід до внутрішнього обʼєкта хелпера існує, і кожне його використання привʼязує сценарій до конкретного бекенда.

    Дзеркальна ситуація, де інструмент нічого не додає: одна команда, один браузерний стек, тести пишуть інженери, які й так знають API інструмента напряму. Тоді читабельність сценарію купується лише зайвим рівнем непрямості — і рекомендація доки «взяти один хелпер» перетворюється на «взяти інструмент, який цим хелпером і є».

    Типові помилки

    Виглядає як «перемкнемо хелпер — і сюїта поїде на WebDriver». Насправді спільна API сумісності не гарантує, і дока дає власний контрприклад: заголовки запиту ставляться в Playwright і Puppeteer, а у WebDriver ні. Обіцянка інструмента — «легко мігрувати», а не «працює однаково».

    Виглядає як «тут await не потрібен ніде». Насправді правило вужче й жорсткіше: grab-команди, імпортовані функції й методи page object чекають вручну. Черга покриває дії актора, а не ваш код.

    Виглядає як флак локатора. Насправді семантичний рядок збігся з дублікатом підпису деінде на сторінці. Лікується другим аргументом (контекстом), а не втечею на CSS — рекомендований дефолт саме такий.

    Виглядає як «строгий локатор — це однозначний збіг». Насправді строгість тут про явно названу стратегію пошуку, тобто про один запит замість ланцюга спроб. Кількість знайдених елементів — інше поняття й інший інструмент.

    Виглядає як «ретраї ввімкнені, флак під контролем». Насправді retryFailedStep працює на рівні кроку, і це не те саме, що ретрай тесту: різні рівні ловлять різні причини, а розмова, у якій їх не розрізняють, зводиться до «просто перезапустимо».

    Виглядає як «autoLogin зламався». Насправді в чинному реєстрі плагінів такої назви немає — потрібну функцію несе auth.

    Виглядає як «page object не імпортується, значить не працює». Насправді його й не імпортують: обʼєкт оголошується в include і приходить у сценарій за іменем, а екземпляр створює контейнер впровадження залежностей.

    Виглядає як «затримка між діями полагодила флак». Насправді вона його прикрила: плагін додає паузи, а не прибирає причину, чому застосунок не встигає.

    Підсумок

    1. Актор нічого не виконує. I — проксі до ввімкнених хелперів; хто робить крок насправді, вирішує конфігурація, і туди ж варто дивитися першим при розслідуванні.
    2. Спільна API уніфікує запис, а не поведінку. Перемкнути бекенд легко, гарантованої сумісності бекендів немає — це формулювання самої доки, разом із її контрприкладом.
    3. Синхронний вигляд тесту — це глобальний промісний ланцюг. Межа автоматики названа поіменно: grab, імпортовані функції й методи page object — з await.
    4. Семантичний локатор плюс контекст — рекомендований дефолт. Ціна в кількох запитах реальна, але контекст її знімає, а він же прибирає збіги з дублікатами підписів.
    5. Помітна частина поведінки живе в плагінах, і вони в поставці. Ретрай кроку, логін раз на прогін із автоматичним перелогіном, артефакти падіння і JUnit XML вмикаються конфігурацією.

    Можливі питання

    Питання тут конкретні — по механіці, яку не вигадаєш.

    «Що таке I — питання-фільтр. Відповідь «обʼєкт із методами для дій» проходить за трієчку; точна відповідь: актор, тобто абстракція користувача-тестувальника і проксі до ввімкнених хелперів, який делегує всі команди бекенду. Інтервʼюер дивиться, чи розумієте ви, що між тестом і браузером є шар.

    «Чому в тестах немає await — перевірка, чи ви читали доку, а не чужі приклади. Сильна відповідь: дії зчеплені в глобальний промісний ланцюг і ставляться в чергу автоматично, але правило має межу — grab-команди, імпортовані функції й методи page object треба чекати вручну.

    «Чим це відрізняється від Playwright?» — тут очікують рівень абстракції, а не список фіч. Playwright сам є бекендом, і саме він може стояти хелпером під сценарієм; надбудова дає спільний сценарний запис поверх кількох бекендів і платить за це додатковим шаром. Назвіть межу самі: спільна API не означає гарантованої сумісності.

    «Як тут роблять page object?» — питання на механіку. Патерн той самий, доставка інша: обʼєкт оголошується в include, приходить у сценарій за іменем через впровадження залежностей, а спільні кроки розширюють актора, а не базовий клас тесту. Згадка про inject() і про те, що методи page object вимагають await, читається як реальний досвід.

    «Які плагіни вмикаєте на проєкті?» — практичне питання. Мінімум, який варто назвати: ретрай кроку, логін із сесії, скріншот і збір даних зі сторінки при падінні, JUnit XML для CI. Якщо назвете плагін затримки між діями — одразу додайте, що це маскування, інакше відповідь прочитають як звичку глушити симптоми.

    «Коли обереш CodeceptJS?» — відповідь через хелпери: коли потрібен один читабельний сценарний шар над різними бекендами, наприклад веб і мобайл в одному стилі. І одразу назвіть ціну: додатковий рівень непрямості й відсутність гарантованої взаємозамінності бекендів. Якщо інструмент вам незнайомий, найкраща відповідь — назвати межу свого знання, а не вигадувати деталь архітектури.

    Джерела

    Актор I: делегування замість виклику

    • CodeceptJS — Basics — тест як лінійний сценарій із обʼєктом I; I як актор і проксі до ввімкнених хелперів; делегування команд бекендам; глобальний промісний ланцюг і жорстке правило await для grab і методів page object; вбудоване очікування й автоматичний ретрай невдалого кроку; secret() для значень, які не мають потрапляти в лог.

    Хелпери: один сценарій, різні бекенди

    • CodeceptJS — Basics — делегування команд хелперу-бекенду; перелік бекендів і протоколів, які за ними стоять; спільна API без гарантії сумісності з контрприкладом про заголовки запиту; порада взяти один хелпер і мігрувати за зміни вимог; два найважливіші налаштування конфігурації.
    • CodeceptJS — Configuration — секція helpers як місце, де хелпер задає рушій виконання разом із базовим URL.

    Семантичні локатори: рядок, який читається як сторінка

    • CodeceptJS — Locators — два види локаторів: обʼєкт із явно названою стратегією (один запит) і семантичний рядок, що зіставляється з підписами, текстом кнопок, плейсхолдерами й aria-*; семантичний локатор у контексті як рекомендований дефолт і мотив «дублікати підписів більше не дають нестабільних збігів»; повний порядок розвʼязання локатора аж до трактування рядка як CSS-селектора; ціна в кількох запитах; CSS як найшвидший тип.

    Кастомні кроки і page object: розширюють актора, а не базовий клас

    • CodeceptJS — Page Objects — мотивація (спільні зони взаємодії, щоб не дублювати локатори й методи); впровадження залежностей замість імпортів, inject() як ледачий проксі й автоматичне створення екземпляра контейнером; кастомні кроки як розширення актора I; фрагменти сторінки, технічно тотожні page object.
    • CodeceptJS — Configuration — секція include як реєстр обʼєктів, що інжектяться в сценарій за іменем.
    • Martin Fowler — PageObject — сам патерн: обгортка сторінки або фрагмента застосунковим API замість роботи з HTML у тесті.
    • Selenium — Page object models — наскрізний інваріант патерна: рівно одне місце в сюїті знає структуру HTML конкретної частини сторінки.
    • Serenity/JS — Screenplay Pattern — актор у Screenplay: пʼять будівельних блоків, актор як людина або зовнішня система, один або кілька акторів у сценарії.

    Плагіни: ретрай кроку, логін раз на прогін, артефакти

    • CodeceptJS — Plugins — плагіни як частина поставки; retryFailedStep як ретрай кожного невдалого кроку; плагін логіну з перевикористанням сесії, збереженням кукі в памʼять або файл і автоматичним перелогіном при протуханні; артефакти падіння (скріншот, збір даних зі сторінки з прямою рекомендацією для CI, відео); звіт JUnit XML; таймаут кроку й затримка між діями; доступ до внутрішнього обʼєкта хелпера; плагіни на AI.
    • Playwright — Authentication — інша половина порівняння: інструмент не стежить за протуханням збереженого стану, видалити протухлий файл має автор тестів.

    Коли CodeceptJS доречний

    • CodeceptJS — Basics — сценарний шар над різними бекендами як призначення інструмента; BDD-подібний синтаксис; спільна API без гарантії сумісності; порада взяти один хелпер, а міграцію робити за зміни вимог.
    • CodeceptJS — Configuration — хелпер і базовий URL як два налаштування, що визначають, чим виконується той самий набір сценаріїв.
    • CodeceptJS — Plugins — штатний вихід до внутрішнього обʼєкта хелпера як межа переносності сценарію.

    Типові помилки

    • CodeceptJS — Basics — спільна API без гарантованої сумісності з контрприкладом про заголовки запиту; правило await для grab і методів page object; автоматичний ретрай кроку як рівень нижче за ретрай тесту.
    • CodeceptJS — Locators — контекст як засіб проти збігів із дублікатами підписів; «строгість» локатора як явно названа стратегія пошуку.
    • CodeceptJS — PluginsretryFailedStep на рівні кроку; плагін логіну під назвою auth; затримка між діями як окремий плагін.
    • CodeceptJS — Page Objects — page object приходить у сценарій через впровадження залежностей, а не через імпорт.

    Підсумок

    • CodeceptJS — Basics — актор як проксі до хелперів і делегування; спільна API без гарантії сумісності; промісний ланцюг і правило await.
    • CodeceptJS — Locators — семантичний локатор у контексті як рекомендований дефолт і його ціна.
    • CodeceptJS — Plugins — вбудовані плагіни: ретрай кроку, логін із перевикористанням сесії, артефакти падіння, JUnit XML.
    • CodeceptJS — Configuration — конфігурація як місце, де задається хелпер, базовий URL і реєстр інжектованих обʼєктів.

    Можливі питання

    • CodeceptJS — Basics — матеріал для відповідей про I, делегування, промісний ланцюг і правило await, а також про доречність надбудови й межу спільної API.
    • CodeceptJS — Page Objects — механіка page object через впровадження залежностей і кастомні кроки як розширення актора.
    • CodeceptJS — Plugins — перелік вбудованих плагінів, з якого складається відповідь про налаштування проєкту.

    Пояснення

    «Поясни» працює з власним API-ключем Anthropic: запит іде з вашого браузера прямо до Anthropic.

    Свій API-ключ (BYOK)

    Вставте власний ключ Anthropic — пояснення працюватиме на реальній моделі. Ключ зберігається лише у вашому браузері (localStorage), нікуди не надсилається, крім api.anthropic.com, і не логується.