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): вони ламаються від будь-якої зміни верстки.

CSSXPath
Читабельністькоротший, звичний фронтендерамбагатослівніший
Пошук за текстомнітак: //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)) обходить перехоплення, але й ховає справжню проблему: користувач такий клік зробити не зміг би. Це крайній засіб із поясненням у коді.