Какие оси в XPath вы используете для поиска элементов в автотестах?

«Какие оси в XPath вы используете для поиска элементов в автотестах?» — вопрос из категории Selenium и Selenide, который задают на 24% собеседований AQA / Automation. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

При написании селекторов для Selenium/Selenide я активно использую оси XPath, чтобы создавать устойчивые и точные локаторы, особенно когда у элементов нет уникальных id или class.

Наиболее часто используемые оси:

  1. ancestor и ancestor-or-self: Незаменимы для поиска общего контейнера. Например, чтобы найти строку таблицы (<tr>), в которой находится определенная ячейка.

    // Найти кнопку 'Delete' в той же строке, где есть текст 'Project Alpha'
    // $x("//td[text()='Project Alpha']/ancestor::tr//button[text()='Delete']")
    WebElement deleteBtn = driver.findElement(
        By.xpath("//td[.='Project Alpha']/ancestor::tr//button[.='Delete']")
    );
  2. following-sibling и preceding-sibling: Полезны для навигации по элементам одного уровня, например, по пунктам списка (<li>) или строкам.

    // В Selenide: кликнуть на чекбокс, который следует за лейблом с текстом 'Accept terms'
    $x("//label[contains(., 'Accept terms')]/following-sibling::input[@type='checkbox']")
        .click();
  3. parent: Быстрый способ подняться на один уровень вверх, часто используется в комбинации с другими осями.

    // Найти родительский div элемента с ошибкой, чтобы получить его текст
    String errorContainerText = driver.findElement(
        By.xpath("//span[@class='error-message']/parent::div")
    ).getText();
  4. descendant (неявно) и child: Я предпочитаю использовать // (неявный descendant) для поиска вглубь, когда структура DOM может меняться, и / (явный child), когда путь должен быть строгим.

    // Неявный descendant (более гибкий): //div[@id='modal']//input
    // Явный child (более строгий): //form[@id='login']/div[1]/input
  5. Ось attribute (сокращенно @): Используется постоянно для поиска по атрибутам id, class, data-testid, name и т.д.

Практический совет по устойчивости: Я стараюсь избегать построения длинных цепочек осей, которые сильно зависят от структуры HTML. Вместо этого комбинирую оси с предикатами (условиями в квадратных скобках) для поиска ближайшего устойчивого контейнера.

// ПЛОХО: Хрупкий локатор, зависит от точной позиции div
// //label[.='Email']/../../div[2]/input

// ЛУЧШЕ: Ищем input, который находится после label, но внутри общего родителя form
// //form//label[.='Email']/following::input[1]
// ИЛИ ЕЩЕ ЛУЧШЕ: Использовать data-атрибуты, если это возможно
// //input[@data-qa='email-input']

В Selenide, благодаря лаконичному синтаксису, это выглядит еще читаемее: $("//td[.='Project Alpha']/ancestor::tr").$(".delete-btn").click();.