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.