QA Interview Hub · База знань · Тести
Selenium + Java
Локатори й очікування
Локатори: By, CSS проти XPath, relative locators
Елемент знаходять через driver.findElement(By...). Стратегії By: id, name, className, tagName, linkText, partialLinkText, cssSelector, xpath.
Порядок пріоритету для стійких тестів: спеціальний тестовий атрибут (data-testid) або стабільний id → осмислений CSS за роллю й структурою → XPath лише коли CSS не вистачає. Уникайте довгих ланцюжків від кореня (/html/body/div[3]/div[2]/button) і згенерованих класів (.css-1x2y3z): вони ламаються від будь-якої зміни верстки.
| CSS | XPath |
| Читабельність | коротший, звичний фронтендерам | багатослівніший |
| Пошук за текстом | ні | так: //button[normalize-space()='Увійти'] |
| Рух до батька/предка | обмежено: :has() вибирає батька за нащадком (у всіх сучасних браузерах) | так: .., ancestor:: |
| Швидкість | різниця у сучасних браузерах непомітна | — те саме |
Швидкість — не аргумент: обирають за зручністю й стійкістю.
driver.findElement(By.cssSelector("[data-testid='login-submit']")).click();
driver.findElement(By.xpath("//tr[td[text()='Іван']]//button[@title='Видалити']")).click();
findElement кидає NoSuchElementException, якщо елемента немає, а findElements повертає порожній список. Це зручно для перевірки відсутності.
Relative locators (Selenium 4) шукають елемент за розташуванням відносно іншого: above, below, toLeftOf, toRightOf, near (до 50 пікселів).
WebElement password = driver.findElement(
RelativeLocator.with(By.tagName("input")).below(By.id("email")));
Вони корисні, коли в елемента немає надійних атрибутів, але покладатися на розкладку крихкіше, ніж на атрибут, тому це запасний варіант. Для shadow DOM є element.getShadowRoot(), а потім пошук усередині кореня.
Очікування: implicit, explicit, fluent
Сучасна сторінка змінюється асинхронно, тому тест має чекати стану, а не час. Thread.sleep(5000) — найгірший варіант: або надто довго (повільні тести), або надто мало (флейки).
У Selenium три механізми:
- Implicit wait — глобальний таймаут пошуку елемента для всієї сесії (за замовчуванням 0):
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(2)). Чекає лише появи в DOM, а не видимості чи клікабельності.
- Explicit wait (
WebDriverWait) — чекає конкретної умови, опитуючи сторінку, доки умова не виконається або не спливе таймаут (тоді TimeoutException).
- FluentWait — те саме з налаштуванням інтервалу опитування й винятків, які ігнорувати.
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement button = wait.until(ExpectedConditions.elementToBeClickable(By.id("pay")));
button.click();
Wait<WebDriver> fluent = new FluentWait<>(driver)
.withTimeout(Duration.ofSeconds(15))
.pollingEvery(Duration.ofMillis(300))
.ignoring(NoSuchElementException.class, StaleElementReferenceException.class);
Умова може бути ExpectedConditions (visibilityOfElementLocated, elementToBeClickable, invisibilityOfElementLocated, textToBe, urlContains) або лямбда d -> ....
Правило: документація Selenium прямо радить не змішувати implicit і explicit очікування: сумарний час стає непередбачуваним (наприклад, до сум обох таймаутів). Обирайте explicit для керованих умов; implicit залишайте 0.
Окремо від очікувань елемента є таймаут завантаження сторінки (pageLoad) і таймаут скриптів (script).
StaleElementReferenceException та інші типові винятки взаємодії
WebElement — це не сам елемент, а посилання на нього в поточному DOM. Якщо сторінка перемальовує цей вузол (оновлення списку, SPA-перехід, innerHTML), посилання «протухає» і будь-яка дія кидає StaleElementReferenceException.
Як лікувати:
- не зберігати
WebElement надовго; зберігати By і шукати елемент перед дією;
- почекати стабільного стану:
ExpectedConditions.refreshed(...), stalenessOf(old) перед пошуком нового, або FluentWait, що ігнорує цей виняток;
- не «ковтати» виняток у
try/catch з довільним sleep: так ховається причина.
Інші типові винятки:
- NoSuchElementException — елемента немає (ще не з’явився, інший iframe, помилковий локатор);
- ElementClickInterceptedException — клік перехопив інший елемент (спінер, модалка, липка шапка); чекають, поки перекриття зникне (
invisibilityOfElementLocated), або прокручують елемент у видиму область;
- ElementNotInteractableException — елемент у DOM, але його не можна використати (прихований, нульовий розмір);
- TimeoutException — явне очікування не дочекалось умови;
- SessionNotCreatedException — драйвер і браузер несумісні або браузер не запустився;
- NoSuchSessionException — сесію вже закрито (
quit()) або браузер впав.
JavascriptExecutor-клік (executeScript("arguments[0].click()", el)) обходить перехоплення, але й ховає справжню проблему: користувач такий клік зробити не зміг би. Це крайній засіб із поясненням у коді.