2025, Oct 22 08:32

Почему относительные локаторы в Selenium 4 для Python работают, несмотря на предупреждения IDE

Показываем, что относительные локаторы (RelativeBy) в Selenium 4 для Python работают с find_elements, даже если IDE/Pyright ругаются. Пример кода и выводы.

В Selenium 4 для Python появились относительные локаторы, но во многих средах разработки всплывает предупреждение: driver.find_elements якобы не принимает RelativeBy. Официальный сниппет выглядит корректно, однако сигнатура метода будто бы допускает только By и value. Ниже — как понимать это расхождение и почему ваш код по‑прежнему отлично работает на Selenium 4.33.0 и Python 3.13.5.

Минимальный пример, который вводит в заблуждение

Следующий фрагмент повторяет официальный приём с locate_with и относительным ограничением. Имена символов произвольные, логика идентична.

from selenium.webdriver.common.by import By
from selenium.webdriver.support.relative_locator import locate_with
anchor_el = browser.find_element(By.ID, "below")
para_nodes = browser.find_elements(locate_with(By.CSS_SELECTOR, "p").above(anchor_el))

Многие IDE и инструменты уровня Pyright ругаются здесь, потому что driver.find_elements объявлен с сигнатурой вроде find_elements(self, by=By.ID, value=None), которая выглядит несовместимой с RelativeBy. Это не означает, что вызов упадёт во время выполнения.

Что происходит на самом деле

В Selenium 4 методы driver.find_element и driver.find_elements учитывают экземпляр RelativeBy, если он передан в аргумент by. Определение функции не навязывает By как строгий тип, даже если значение по умолчанию — By.ID. На практике это предупреждение — ложноположительный результат статического анализа: Pyright вывел тип для by и решил, что это обязан быть enum By. Запуск кода показывает, что локатор работает. Если целевой элемент, на который вы ссылаетесь, отсутствует в DOM, вы, конечно, получите обычную ошибку, не связанную с относительными локаторами.

Рабочий запуск с относительными локаторами

Пример ниже запускается против https://books.toscrape.com. На странице нет элемента с id="below", поэтому в качестве якоря берётся первая кнопка на странице по имени тега. Остальное без изменений: находим все теги p выше этого якоря.

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.relative_locator import locate_with
import selenium
import sys
print("selenium:", selenium.__version__)
print("python:", sys.version)
app = webdriver.Chrome()
app.get("https://books.toscrape.com")
pivot_el = app.find_element(By.TAG_NAME, "button")
above_items = app.find_elements(locate_with(By.CSS_SELECTOR, "p").above(pivot_el))
print("\n--- above ---\n")
for idx, node in enumerate(above_items, 1):
    print(idx, ">", node.text)
all_items = app.find_elements(By.CSS_SELECTOR, "p")
print("\n--- all ---\n")
for idx, node in enumerate(all_items, 1):
    print(idx, ">", node.text)

На этой странице относительный поиск срабатывает. По результатам запуска видно, что возвращаются только узлы p из области над первой видимой строкой кнопок, причём их порядок отличается от полного списка.

Возвращаются p только выше первой строки кнопок. Кроме того, p над кнопкой идут в другом порядке — снизу вверх, тогда как полный список печатается сверху вниз.

Описанное поведение — просто наблюдение для этой страницы в момент запуска. Важное здесь — паттерн вызова с locate_with(...).above(...) работает в Selenium 4.

И что же «исправлять»?

Для этого сценария в Selenium ничего «исправлять» не нужно. API относительных локаторов валиден. Если ваша IDE или Pyright предупреждает вызов find_elements, код всё равно можно безопасно запускать. Если в HTML нет элемента, используемого как якорь, вы получите ожидаемую ошибку, поэтому заранее убедитесь, что опорный локатор действительно существует. Мейнтейнеры Selenium отмечали, что могут подправить type hints, чтобы удовлетворить строгие типизаторы; если хотите ускорить это, откройте issue на github.com/SeleniumHQ/selenium/issues с подробностями о ложноположительном срабатывании.

Почему это важно

Статический анализ полезен, но он не является источником истины для динамических библиотек вроде Selenium. Доверяйте документации относительно формы вызова и проверяйте поведение запуском. Когда IDE помечает, казалось бы, официальный пример как неверный, это чаще ограничение типизации или выведения типов, а не реальная проблема рантайма. Указывайте версии Python и Selenium в отчётах — так другим проще воспроизвести и подтвердить поведение.

Выводы

Относительные локаторы в Selenium 4 действительно работают в Python. Когда тайп‑чекер ругается на передачу RelativeBy в find_elements, в данном контексте это предупреждение можно игнорировать. Проверьте наличие якорного элемента, на который ссылаетесь, запустите код, чтобы подтвердить поведение на вашей странице, а если диагностическое сообщение всё ещё мешает, поделитесь воспроизводимым примером с версиями в трекере Selenium. Так вы продолжите развивать тесты, пока экосистема подтягивает типовые подсказки.

Статья основана на вопросе на StackOverflow от B1LLP4RK и ответе furas.