Урок перетворює сирий Selenium-сценарій на читабельні Page Objects: driver передається в `BasePage`, локатори зберігаються окремо від дій, а explicit waits ховаються у невеликі повторно використовувані helpers. Порівнюються три способи представлення елементів — locator tuple, CSS-рядок і lazy `WebElement` через `@property` — та пояснюється, як уникати stale references і конфлікту implicit/explicit waits.
Примітка: конспект укладено за автоматичними українськими субтитрами; назви Selenium API, patterns і exceptions нормалізовано за контекстом відео.
Після цього уроку ви зможете
Організувати Selenium-код через BasePage і Page Objects без дублювання driver та waits.
Обрати між locator tuple, CSS-рядком і WebElement з урахуванням повторного пошуку.
Побудувати wait-aware click і type helpers з явним контрактом стану.
Пояснити причину StaleElementReferenceException і уникнути конфлікту implicit/explicit waits.
Стартовий тест напряму викликає driver.find_element(), WebDriverWait(...).until(...) і браузерні дії. Він придатний для перевірки ідеї, але змішує бізнес-сценарій із технікою Selenium, тому наступний крок — дати діям читабельні назви й рознести їх по Page Objects.
Замість Playwright Page у конструктор Page Object передається Selenium WebDriver. Спільний BasePage зберігає driver і один раз ініціалізує стандартне очікування, щоб кожен клас сторінки не дублював timeout та polling configuration.
Найпрактичніше представлення локатора — tuple на кшталт (By.CSS_SELECTOR, "..."). Воно зберігає і стратегію, і значення пошуку та напряму сумісне з locator-based expected conditions.
Термін
Page Object Model
Організаційний підхід, у якому page-specific structure і services інкапсулюються в об’єкті сторінки, зменшуючи дублювання та локалізуючи зміни UI.
Приклад коду
Мінімальний wait-aware BasePage
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
class BasePage:
def __init__(self, driver, timeout: float = 10):
self.driver = driver
self.wait = WebDriverWait(driver, timeout)
def click(self, locator) -> None:
self.wait.until(EC.element_to_be_clickable(locator)).click()
def type_text(self, locator, value: str) -> None:
self.wait.until(EC.visibility_of_element_located(locator)).send_keys(value)
Page Objects передають locator tuple у BasePage, а кожна дія очікує власну перевірювану передумову.
Очікуваний результат: click() працює лише з видимим і enabled елементом; type_text() — з видимим елементом.
Потрібно: selenium
Практика
Зібрати Page Object без довгоживучих WebElement
Створіть BasePage з wait-aware click() і type_text().
Збережіть локатори login page як tuple (By.CSS_SELECTOR, value).
Створіть предметний login() і перевірку success state.
Перерендерте один компонент у test page та переконайтеся, що дія повторно знаходить елемент за locator.
Результат: Тест читається через предметні Page Object methods і не використовує implicit wait або довгоживучі WebElement fields.
На Python-проєктах можна зустріти три підходи: зберігати locator tuple, лише CSS/XPath-рядок або вже знайдений WebElement. Рядок коротший, але не містить тип пошуку; WebElement зручний лише поки DOM-вузол не перерендерився; locator дає змогу безпечно виконувати повторний пошук.
Якщо helper має приймати і locator, і WebElement, це можна відобразити union type та всередині визначити потрібну expected condition. Така гнучкість виправдана лише коли обидва представлення реально використовуються; для нового коду один locator-based контракт простіший.
Wait helper отримує default timeout, але дозволяє локально передати довший для справді повільної операції. Page Object може застосовувати Loadable Component approach: сторінка вважається готовою лише після появи її ключових елементів, наприклад email і password inputs.
Термін
Locator-based expected condition
Expected condition, яка приймає locator tuple й виконує пошук під час polling, на відміну від condition для вже знайденого WebElement.
У BasePage демонструються helpers для open, refresh, find, find_all, click і введення тексту. Перед дією helper чекає потрібний стан: visibility для введення або clickability для кліку. Так exception вказує на невиконану передумову, а не на випадковий наступний Selenium command.
send_keys() вводить символи та може передавати спеціальні клавіші на кшталт Enter або Tab. На відміну від високорівневого fill у Playwright, він не гарантує очищення поля, тому clear() додається лише там, де сценарій справді починає з порожнього input.
Не варто приховувати clear() у кожному введенні «про запас». Тест має явно задавати стартовий стан: іноді потрібен refresh, бо попередня невдала авторизація залишила validation message або інший стан, який просте очищення полів не скидає.
Page Object збирає технічні дії у предметні кроки авторизації. Перед введенням він чекає готовність полів, після Sign in — очікуваний success state. Це робить тест коротшим, але не приховує важливі переходи стану.
Для параметризованих негативних login cases кожен приклад має стартувати з контрольованого стану. Інакше message, що з’явився після першої спроби, може залишитися для наступної та дати false positive. Залежно від продукту потрібні нова сторінка, refresh або явне очищення стану.
Повернення self з кожного click() чи type_text() дозволяє chaining, але урок ставиться до цього обережно: ланцюжок не повинен створювати операції, які предметно не мають сенсу. Читабельна окрема дія часто краща за універсальний fluent interface.
Другий поширений варіант Page Object — оголошувати element getter як метод із @property. З тесту він виглядає як поле, але find_element() виконується лише під час звернення. Це відкладає пошук і зменшує ризик зберегти element reference надто рано.
Через property можна викликати clear(), send_keys(), click() або читати is_selected(). Для дії, яка потребує очікування, все одно краще мати окремий метод: property не перетворює Selenium на lazy locator і сама по собі не додає retry.
Якщо агент або IDE має працювати з незнайомою бібліотекою, урок радить дати йому локальний source code як контекст. Це допомагає звірити реальні методи та актуальну реалізацію замість вигадування API за пам’яттю.
Термін
StaleElementReferenceException
Exception, який означає, що збережений reference більше не відповідає елементу, прикріпленому до поточного DOM; повторна дія потребує актуального пошуку або відновлення правильного page/window/frame context.
CSS-селектори залишаються основним варіантом. XPath потрібен у Selenium для окремих випадків пошуку за текстом або зв’язком parent/child, але швидко ускладнюється: видимий текст може бути розбитий між кількома DOM-вузлами.
Стійкі data-testid, id або name краще узгодити з frontend-командою, ніж будувати крихкий XPath навколо layout. Це не «деталь тестів», а тестований контракт між UI та автоматизацією.
Рекомендований у відео компроміс: Page Object зберігає locator tuples, а спільні click/type helpers автоматично чекають релевантний стан. Поряд із цим кожен тест повинен відновлювати потрібний стан, бо локальний success не гарантує стабільність у повному suite.
send_keys() використовується не лише для тексту: file input можна передати шлях до локального файлу, після чого браузер виконає upload. У такому спеціальному сценарії взаємодія з невидимим input може бути виправдана, тому generic helper не повинен безумовно вимагати visibility для кожної операції.
Application object може один раз ініціалізувати всі Page Objects і дати тестам єдину точку доступу. Це прибирає повторну ініціалізацію з кожного тесту; обсяг статичних селекторів у пам’яті тут не є практичною проблемою.
Той самий контейнер дозволяє поступово переводити Python-suite із Selenium на Playwright: старі Page Objects продовжують працювати, нові сценарії отримують Playwright implementation. Переписувати весь набір одразу не потрібно, особливо коли корисні API/database fixtures уже живуть у цьому pytest-проєкті.
WebDriver перевіряє стани елементів через браузер і JavaScript. Надто частий polling може впливати на сторінку, яку тест вимірює, особливо якщо UI одночасно виконує важку клієнтську логіку. Тому частоту й timeout варто тримати розумними, а кількість UI-тестів — достатньою для критичних journeys, не максимальною.
Іноді короткий sleep може бути прагматичним, якщо саме polling блокує потрібний browser work, але це виняток із виміряною причиною. За замовчуванням очікується конкретний спостережуваний стан через explicit wait.
StaleElementReferenceException виникає, коли WebDriver зберіг internal ID елемента, а компонент перерендерився й старий DOM-вузол зник. Повторний виклик дії на старому WebElement уже не може спрацювати; потрібен повторний пошук за locator.
Locator-based explicit wait вирішує stale reference тим, що на кожній спробі знаходить актуальний DOM-вузол. Це ще одна причина зберігати локатор, а не довгоживучий WebElement.
Implicit wait радять залишити нульовим і не змішувати з explicit waits: інакше внутрішнє очікування кожного find_element() додається до зовнішнього polling, через що реальний timeout стає непередбачуваним. Практичне завдання — зробити BasePage, окремий wait helper і Page Object у вибраному locator-based стилі.
Увага
Implicit wait не повинен множити timeout explicit wait
Selenium прямо попереджає не змішувати ці стратегії, бо фактичний час очікування стає непередбачуваним.