2025, Oct 16 17:16

RelativeBy и find_element: как избежать TypeError в Selenium 4

Почему возникает TypeError с RelativeBy в Selenium 4 при вызове find_element на WebElement, и как правильно использовать относительные локаторы через WebDriver.

Столкновение с TypeError при работе с относительными локаторами Selenium может сбить с толку, особенно когда по сигнатуре метода кажется, что всё должно работать. Если при вызове find_element на WebElement вы видите исключение о том, что RelativeBy нельзя сериализовать в JSON, этот разбор показывает, почему так происходит и как это исправить, не меняя задуманную логику селектора.

Воспроизводим проблему

Пример ниже упрощён, чтобы наглядно показать суть. Среда использует Selenium 4.33.0 с Python 3.13.6. Код пытается применить Relative Locator вместе с find_element на уровне элемента, что и вызывает ошибку.

import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.relative_locator import locate_with

site_url = "https://www.lazyvim.org/"
browser = webdriver.Chrome(webdriver.ChromeOptions())
browser.maximize_window()

browser.get(site_url)

# пауза, чтобы понаблюдать страницу во время демо
time.sleep(3)

# неявное ожидание только для демонстрации
browser.implicitly_wait(3)

# левая боковая панель
side_css = (By.CSS_SELECTOR, "div.sidebar_njMd")
side_panel = browser.find_element(*side_css)

# строим RelativeBy для кнопки каретки справа от ссылки
caret_rel = locate_with(
    By.CSS_SELECTOR, "button.clean-btn.menu__caret"
).straight_right_of({By.CSS_SELECTOR: 'a[href="/configuration"]'})

# проблемный вызов: передаём RelativeBy в element-level find_element
caret_button = side_panel.find_element(caret_rel)

# пытаемся кликнуть по каретке
caret_button.click()

time.sleep(4)
browser.quit()

Запуск завершается с ошибкой:

TypeError: Object of type RelativeBy is not JSON serializable

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

Причина кроется в том, как Selenium проводит локаторы через команды WebDriver. Относительные локаторы, созданные через locate_with и представленные как RelativeBy, поддерживаются на уровне драйвера. Напротив, при вызове find_element у WebElement Selenium ожидает обычный локатор By, а не RelativeBy. Передача RelativeBy в element.find_element заставляет Selenium сериализовать объект, которого он не ожидает в этом контексте, и это приводит к ошибке сериализации JSON.

Проще говоря: относительные локаторы работают с экземпляром WebDriver, а не с экземпляром WebElement. Если заменить вызов на driver.find_element с RelativeBy, ошибка исчезает, даже если целевой элемент на странице остаётся тем же.

Решение

Продолжайте использовать относительные локаторы, но вызывайте find_element у драйвера. Область всё равно можно сузить: сначала получите якорный элемент и передайте этот WebElement в относительную цепочку.

import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.relative_locator import locate_with

site_url = "https://www.lazyvim.org/"
browser = webdriver.Chrome(webdriver.ChromeOptions())
browser.maximize_window()

browser.get(site_url)

# пауза, чтобы наблюдать поведение в демо
time.sleep(3)

# неявное ожидание только для целей демо
browser.implicitly_wait(3)

# левая боковая панель
side_panel = browser.find_element(By.CSS_SELECTOR, "div.sidebar_njMd")

# используем Relative Locator на уровне драйвера; якорь — это найденный WebElement
caret_button = browser.find_element(
    locate_with(By.CSS_SELECTOR, "button.clean-btn.menu__caret").straight_right_of(
        side_panel.find_element(By.CSS_SELECTOR, 'a[href="/configuration"]')
    )
)

# клик по каретке
caret_button.click()

time.sleep(4)

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

Понимание того, где поддерживаются относительные локаторы, помогает избежать неочевидных падений и сэкономить время на отладке ошибок сериализации, которые напрямую не указывают на неверное использование. Это также делает тесты устойчивее, когда вы совмещаете традиционные селекторы By и относительные стратегии в одном сценарии. Разница между driver.find_element и element.find_element кажется небольшой, но в данном случае она меняет то, как Selenium упаковывает команду, отправляемую в браузер.

Ключевые выводы

Если нужно найти элемент относительно другого, сначала постройте якорь как WebElement, затем передайте его в относительный локатор на уровне драйвера. Не передавайте RelativeBy в element.find_element; вызовы на уровне элемента должны использовать обычные локаторы By. Если переход с вызова на уровне элемента на вызов на уровне драйвера меняет результат, перепроверьте якоря и границы на странице, но помните базовое правило: относительные локаторы живут в контексте драйвера.

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