← Design Patterns для автоматизаторів

Конспект і таймкоди

0:00

Селектори, KISS і Proof of Concept

Для селекторів потрібна або впевнена навігація DOM-деревом, або командна домовленість про стабільні data-* атрибути; за потреби тестувальник може сам додати їх у frontend і перевірити зміни локально. Автоматизацію радять починати з найпростішого Proof of Concept, щоб рано виявити bot protection, проблемні локатори та інші обмеження системи.

Сценарій має підтверджувати бізнес-ціль, а не просто взаємодію з UI. Для e-commerce початковою перевіркою обрано додавання товару до кошика з подальшим підтвердженням результату.

2:01

Прямий Selenide-сценарій і перевірка результату дій

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

Другий сценарій додає товар прямо зі сторінки результатів пошуку. Демонстрація показує, що слабка перевірка тексту може дати хибнопозитивний результат; після переходу на exact text тест очікувано падає. Тестові дані варто винести в одну змінну, щоб назва товару не розходилася між кроками.

9:21

DRY, DAMP, KISS і YAGNI у тестах

Спільне відкриття сторінки переноситься в beforeEach, а повторювані кроки пошуку та перевірки — у методи з явними назвами. Метод має описувати дію в контексті поточної сторінки або компонента; якщо контекст відрізняється, поведінку краще рознести.

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

16:31

OOP як підготовка до патернів

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

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

20:45

Page Object для сторінок і компонентів

Page Object визначається як опис поведінки логічної частини сторінки. Такою частиною може бути ціла сторінка, popup, header, footer, фільтр або картка товару; поділ на pages і components є організаційним уточненням цього самого підходу.

Прямий тест послідовно розкладається на HomePage, SearchResultPage, ProductPage і CartPage. У кожен клас переносяться лише дії та перевірки його UI-контексту: пошук, відкриття товару, додавання до кошика, відкриття кошика та підтвердження завантаження. Після перенесення тест читається як послідовність бізнес-дій, але реальний запуск усе одно потрібен, бо помилка в регістрі назви товару вже спричинила падіння.

36:16

DTO і Builder для комплексних тестових даних

Одного рядка з назвою товару недостатньо, коли пошук і перевірки використовують також id, ціну, рейтинг, доступність чи інші поля. ProductInfoDto групує ці значення в одну сутність, щоб не розширювати кожен метод двома, трьома або більшою кількістю аргументів.

Builder робить ініціалізацію Java-об'єкта читабельною: значення прив'язуються до названих полів, а не передаються довгим позиційним конструктором. У прикладі boilerplate генерує Lombok. Автор також згадує Java records як коротший immutable-варіант, але залишає DTO змінюваним для сценаріїв, де тестові дані потрібно оновлювати.

45:48

Factory Method та централізовані набори даних

Створення великої тестової сутності переноситься у фабричний метод на кшталт отримання цільового товару. Це ховає довгу ініціалізацію, дає тесту коротке ім'я даних і дозволяє зібрати підготовлені продукти в окремому test-data класі, коли їх стає більше.

Як альтернативу показано enum, де іменоване значення на кшталт SIGMA_BOX містить потрібні поля або готовий DTO. Тест тоді використовує стабільне доменне ім'я без додаткової локальної змінної. Автор радить не фіксувати структуру папок завчасно: спершу дані можуть жити поруч із тестом, а окремий пакет з'являється після появи реальної кількості сутностей.

56:31

Single Responsibility і повторно використовувані UI-компоненти

Пошук і відкриття кошика доступні на багатьох сторінках, тому вони не повинні залишатися відповідальністю HomePage. Їх переносять у Header, а сторінки отримують інстанс компонента через композицію. У відео такий зв'язок також описується термінами Delegate/Adapter.

Так само однаковий список товарів на сторінці категорії та в результатах пошуку варто моделювати окремими компонентами, а не дублювати всі дії в обох Page Object. Розділення починають лише після появи повторного використання, зберігаючи Single Responsibility без передчасної деталізації.

1:00:00

`ProductList` і `ProductItem` замість великого Page Object

Якщо всі дії з карткою — перевірка назви, ціни, рейтингу, availability, додавання до кошика й відкриття товару — залишити в SearchResultPage, клас швидко розростеться, а на сторінці категорії цю поведінку доведеться дублювати. Тому виділяються ProductList і ProductItem.

SearchResultPage та ProductCategoryPage можуть повторно використовувати поведінку списку, зберігаючи власні перевірки на кшталт title або breadcrumbs. ProductList також отримує перевірку кількості елементів, яку можна застосувати до pagination, а ProductItem інкапсулює дії з конкретною карткою.

1:07:10

State для вибраного товару та локальний selector scope

ProductInfoDto передається в конструктор ProductItem один раз і стає станом компонента. Методи hasTitle, addToCart та openProductPage використовують той самий товар без повторення аргумента в кожному виклику. Це корисно, коли багато методів одного класу працюють з одними й тими самими даними.

ProductList надає фабричний метод, який створює ProductItem для потрібного товару. Всередині компонента спільний батьківський selector картки формується один раз з id, а конкретні методи звужують пошук до title чи кнопки. Так selector scope і стан залишаються локальними для картки, а тест не знає її DOM-структури.

1:20:11

Fluent chain of invocation і межі повернення сторінок

Для послідовних дій методи можуть повертати this, щоб після перевірки компонента викликати наступну дію через крапку. У відео це названо chain of invocation або chain of responsibility; Builder наведено як споріднений fluent-приклад.

Ланцюжки потрібно форматувати за логічними частинами й переносити, коли рядок стає важко читати на code review. Автор переважно радить повертати той самий тип у межах сторінки чи компонента, а наступну сторінку створювати явно. Інакше, наприклад після submit, важко залишитися в контексті форми та перевірити validation errors; повернення іншого типу доречне лише за зрозумілою домовленістю команди.

1:29:05

Loadable Component проти flaky UI-тестів

Після кожної дії потрібно підтверджувати її наслідок на поточній сторінці, а перед роботою з новою сторінкою чи компонентою — чекати, що вони готові. Це важливо для асинхронного UI: картка може вже відображатися, але stock або delivery methods ще завантажуються, тому ранній клік addToCart не дасть очікуваного результату.

Спільний інтерфейс Loadable з методом isLoaded робить цю вимогу явною для pages і components без великого base class. isLoaded може містити кілька очікувань: видимість title, image, кнопки й залежних даних. Автор дозволяє такі очікування в Page Object, бо вони визначають готовність системи до наступної дії, і пропонує перевіряти на code review, що новий компонент реалізує контракт завантаження.

1:40:50

Utility-класи, Decorator та extension-підходи

Великі Utils і base classes названо code smell: туди часто потрапляють непов'язані операції з файлами, датами, рядками й числами, частина яких не використовується. Мінімальне покращення — розділити helpers за типом або конкретною відповідальністю, наприклад date чи string operations.

Для перетворення дати показано object-oriented альтернативу: Decorator отримує початкове значення як стан і надає пов'язані операції без глобального статичного контейнера. Для Kotlin, TypeScript і C# автор також згадує extension-підходи, що дозволяють викликати перетворення біля самого типу; вибір залежить від можливостей мови та кількості потрібних операцій.

1:46:30

Підсумок патернів і подальші теми

Підсумкова структура розділяє Page Object на сторінки й компоненти, підключає спільні компоненти композицією, передає робочі дані як State, створює їх через Factory Method і перевіряє готовність через Loadable Component. Fluent-виклики роблять сценарій коротшим, якщо не приховують переходи між контекстами, а helpers, decorators і utilities залишаються вузькими за відповідальністю.

Автор анонсує окремий розбір Playwright-обгортки з Singleton, Observable і Command: один інстанс для тестів, очікування стану UI та об'єкти команд/conditions на зразок Selenide. Відео завершується нагадуванням структурувати тестовий проєкт поступово й подякою за збір коштів для військових.