← Java

Selenium, WebDriver і відмінність від Selenide

Відкрити в YouTube ↗

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

0:00

Selenium, WebDriver і відмінність від Selenide

Selenium представлено як велику бібліотеку для керування браузером, до екосистеми якої належать WebDriver, Selenium Manager і Selenium IDE. У попередньому Selenide-тесті ініціалізація та закриття драйвера, очікування видимості й повторні спроби приховані за компактним API. У чистому Selenium ці кроки потрібно описувати явно.

Для першого тесту створюється ChromeDriver, відкривається сторінка, а в кінці викликається driver.quit(). Якщо не закрити драйвер, запущений браузер і пов'язані процеси можуть залишитися в оперативній пам'яті.

2:40

Перенесення Selenide-тесту на чистий Selenium

Проєкт розділяється на окремі пакети для Selenide і Selenium, після чого той самий сценарій переписується через driver.findElement(...), By.cssSelector(...), sendKeys(...) і click(). Для текстового локатора, якому немає прямого аналога By.text, використовується XPath.

Механічна заміна Selenide-викликів допомагає швидко отримати початковий варіант, але залишає багатослівний код і не додає очікувань. Тому наступний крок — явно визначити, коли елемент готовий до пошуку, перевірки або дії.

6:30

Чому implicit wait не підходить динамічним сторінкам

В уроці радять не змішувати implicit і explicit waits та для динамічних React/Angular-сторінок покладатися на explicit waits. Implicit wait очікує, доки елемент з'явиться в DOM під час findElement, але сама наявність вузла ще не означає, що він видимий, enabled або придатний до взаємодії.

Після пошуку Selenium працює з ідентифікатором конкретного елемента. Якщо сторінка перерендерила вузол, старе посилання більше не відповідає поточному DOM і команда може завершитися StaleElementReferenceException. Explicit wait через WebDriverWait.until(...) і ExpectedConditions дозволяє чекати саме потрібного стану, наприклад видимості або очікуваного тексту.

13:50

`WebElement` проти `By` усередині очікування

Перший запуск падає з NoSuchElementException, хоча елемент згодом з'являється на сторінці. Причина — findElement виконується ще до входу в wait.until(...): Selenium спочатку намагається передати готовий WebElement, і очікування не отримує можливості повторювати пошук.

Коли в ExpectedConditions передається By, локатор залишається всередині циклу очікування. Тоді WebDriverWait протягом заданого timeout повторно перевіряє, чи знайдений елемент і чи містить він потрібний текст. Для динамічної сторінки варіанти на кшталт visibilityOfElementLocated(By) і textToBePresentInElementLocated(By, text) гнучкіші за умови, які приймають уже знайдений WebElement.

17:00

Навіщо потрібен власний врапер і де він стає технічним боргом

Чистий Selenium API придатний до роботи, але тест швидко стає багатослівним. На проєктах це часто приводить до самописних фреймворків, у яких різні Page Objects використовують різні реалізації кліку, пошуку й очікувань. Без одного спільного контракту така обгортка накопичує дублювання та ускладнює супровід.

Принципи Selenium однакові для Java, Python, C#, Ruby та інших bindings: клієнт надсилає команди WebDriver, драйвер взаємодіє з браузером. Тому важливіше розуміти модель драйвера, waits, локатори й test runner, ніж запам'ятовувати лише синтаксис однієї мови.

21:40

Життєвий цикл драйвера в JUnit 5

Ініціалізація WebDriver і WebDriverWait переноситься з тіла тесту до lifecycle methods. @BeforeEach створює новий стан перед кожним тестом, тоді як @BeforeAll дає змогу один раз підготувати спільний драйвер для тестів класу. Для стандартного @BeforeAll метод і пов'язані поля робляться static.

Закриття браузера переноситься до @AfterAll. Під час демонстрації зайва ініціалізація створює кілька драйверів, тому setup спрощується до одного місця, а teardown залишається обов'язковим. WebDriverWait має бути побудований на тому самому instance драйвера, з яким працює тест.

26:30

Page Object і проблема передавання драйвера

Login-сценарій переноситься до LoginPage: тест передає login і password, а Page Object виконує технічні дії з полями та кнопкою. Поширений варіант — передавати WebDriver у constructor кожного Page Object, але це повторює однакову залежність у тестах і сторінках.

Для навчального врапера обирається централізований provider, з якого Page Objects отримуватимуть поточний драйвер без constructor plumbing. Водночас пакети розділяються за призначенням: web pages залишаються окремо від common-коду обгортки та від можливих API tests.

30:30

Потокобезпечний `WebDriverProvider` через `ThreadLocal`

WebDriverProvider зберігає driver instance у ThreadLocal. Test runner створює потоки для запусків, а provider повертає драйвер, пов'язаний із поточним thread id: якщо його ще немає, створюється ChromeDriver; якщо є — повторно використовується поточний instance.

Окремий метод закриття викликає quit() і видаляє значення з ThreadLocal. Це готує основу до паралельних запусків: тести в різних потоках не повинні ділити один браузерний session instance.

34:30

Розділення пошуку, дій і очікувань

Обгортка розділяється на вузькі частини: пошук елементів, операції над ними та waits. Один великий helper із усіма кліками, селекторами й очікуваннями складніше читати, змінювати та паралельно редагувати без merge conflicts.

Початкова форма new ElementActions().click(driver.findElement(...)) читається у зворотному до наміру порядку. API перебудовується так, щоб сценарій спочатку знаходив ціль, а потім викликав дію над результатом. ElementActions інкапсулює цільовий елемент і надає click() та sendKeys(...), завдяки чому тест ближчий до послідовності користувацьких дій.

42:30

Єдиний рядковий локатор для CSS і XPath

Метод find(String locator) визначає тип локатора: рядок, що починається зі /, перетворюється на By.xpath(...), інакше використовується By.cssSelector(...). У відео це реалізовано компактним ternary operator; також показано еквівалент через if/else і обговорено компроміс між короткістю та читабельністю.

find повертає ElementActions, а сам driver береться з WebDriverProvider. Після заміни прямих driver.findElement(...) тест стає компактнішим, але зберігає проблему: пошук усе ще може виконатися до того, як елемент стане видимим або доступним для дії.

50:30

Рефакторинг малими кроками та вимога динамічного пошуку

Після кожної невеликої заміни тест запускається знову. Такий короткий feedback loop показує, на якому саме кроці обгортка змінила поведінку, і не дозволяє накопичити кілька незалежних причин падіння.

Для стабільної роботи wrapper має чекати готовності елемента перед дією. Простого eager findElement недостатньо: потрібен механізм, у якому пошук або відповідна дія беруть участь у polling loop очікування.

55:00

`WebDriverWait`, `FluentWait` і polling interval

Окремий клас waits створює WebDriverWait для поточного драйвера з timeout 10 секунд. pollingEvery(Duration.ofMillis(100)) означає, що до завершення timeout умова перевірятиметься приблизно кожні 100 мс.

WebDriverWait наслідує FluentWait, тому обидва мають спільну основу: timeout, polling і можливість ігнорувати визначені exceptions. Різниця в API не заважає передати до until власну lambda або ExpectedCondition, яка перевіряє кілька властивостей за один виклик.

1:00:50

API очікувань для visibility, invisibility і text

У класі waits додаються методи для очікування видимості, невидимості та тексту. Негативну умову можна виразити через not(...), а готові комбінації також доступні через and(...) та or(...). Перед вибором методу відкривається реалізація ExpectedConditions, щоб перевірити, чи він приймає By або WebElement і яку саме умову оцінює.

ElementActions.waitFor() повертає об'єкт waits для поточної цілі. Це дозволяє будувати виклики на кшталт find(...).waitFor().visibility() або очікувати потрібний текст, не розміщуючи WebDriverWait безпосередньо в тесті.

1:06:30

Лінивий пошук як основа стабільного врапера

Тест знову падає до виконання text wait, бо ElementActions отримує готовий WebElement: eager пошук завершується помилкою раніше, ніж запускається очікування. Додавання visibility wait безпосередньо до find теж створює суперечність для сценарію, який навмисно очікує invisibility.

Рішення — зберігати в ElementActions не WebElement, а By. Реальний пошук виконується лише тоді, коли потрібна дія або конкретна умова. Так один і той самий locator можна використати для visibility, invisibility чи text check, не нав'язуючи стан на етапі створення wrapper object.

1:14:30

Очікування перед `click` і `sendKeys`

Перед click() і sendKeys(...) wrapper викликає visibility wait для збереженого By, а потім виконує дію. Окремо пояснюється різниця між visible і enabled: видимий елемент має координати та розмір, але браузер усе ще може вважати його непридатним до взаємодії.

Selenium представляє знайдений вузол через element id, який WebDriver використовує в наступних командах. Якщо DOM замінить вузол між перевіркою стану та дією, посилання може застаріти; саме цей проміжок пояснює частину нестабільних StaleElementReferenceException.

1:19:30

Case-insensitive перевірка тексту

Перевірка очікуваного тексту падає через різний регістр. Готовий textToBePresentInElement перевіряє входження, але не розв'язує вимогу case-insensitive comparison у потрібній формі.

Для textMatches створюється Pattern із прапорцем CASE_INSENSITIVE; очікуваний текст передається до pattern як конкретне значення. Після цієї зміни тест проходить, а фінальний wrapper має окремі реалізації пошуку, actions і waits замість дублювання Selenium-викликів у Page Objects.

1:25:20

JUnit 5 extensions для driver lifecycle і login

Повторювані @BeforeAll і @AfterAll замінюються JUnit 5 extension. WebDriverLifecycleExtension реалізує BeforeAllCallback та AfterAllCallback: до тестів ініціалізує driver через provider, після тестів закриває його. Клас підключається через @ExtendWith, тому setup/teardown більше не дублюються в кожному test class.

Окремий login extension виконує авторизацію перед тестовим класом. Такі розширення дозволяють комбінувати потрібні preconditions без спільного base class. Наприкінці до wrapper додається findByText(...), який будує XPath і повертає той самий тип actions; тест запускається після виправлення синтаксису локатора.

1:35:10

Діагностика waits і власні `ExpectedCondition`

WebDriverWait можна доповнити власним timeout message і переліком exceptions, які ігноруються під час polling. Як приклад розглядається StaleElementReferenceException: якщо DOM оновився між двома перевірками, wait може повторити умову замість негайного падіння. Ігнорувати всі exceptions без розбору не радять — перелік має відповідати очікуваним перехідним станам.

Власна умова може через lambda знайти елемент і послідовно перевірити isDisplayed(), isEnabled() та текст. Її текстове представлення або окремий message потрапить до timeout diagnostics. Для складної перевірки це дає контроль над поведінкою, але готові ExpectedConditions варто перевикористовувати, доки вони виражають потрібний контракт.

1:41:30

Реалізація `ExpectedConditions` і межа між перевіркою та дією

Наприкінці відкривається код готових conditions. elementToBeClickable спочатку перевіряє visibility, а потім enabled state. Між цими операціями DOM може оновитися, тому навіть складена умова має коротке вікно для StaleElementReferenceException; polling та обробка перехідної помилки визначають, чи буде зроблена наступна спроба.

Створений Selenium-врапер слугує підготовкою до наступної теми — аналогічної обгортки над Playwright. Принципи залишаться подібними, але API та спосіб взаємодії з браузером матимуть інший синтаксис.