Как проверить отображение веб-элемента и его реакцию на событие blur
Что проверяют
Знаете ли вы разницу между «элемент есть в DOM» и «элемент виден», умеете ли надёжно вызвать потерю фокуса и дождаться реакции без time.sleep. Следом часто спрашивают, почему такой тест мигает в CI и чем настоящий blur отличается от вызванного через JavaScript.
Как отвечать
Каркас на Selenium 4 для Python:
- поиск:
driver.find_element(By.ID, "email"). Методы видаfind_element_by_idудалены в Selenium 4.3, код с ними сегодня не запустится; - видимость:
WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.ID, "email"))). Самis_displayed()смотрит на CSS и размеры, но не на то, перекрыт ли элемент другим и попал ли он в видимую часть окна; - потеря фокуса: ввести значение, затем
send_keys(Keys.TAB)или клик по другому элементу. Это ближе всего к действию пользователя; - проверка фокуса:
driver.switch_to.active_elementдолжен перестать указывать на наше поле, иначеblurпросто не произошёл; - реакция: ожидание конкретного состояния, например появления текста ошибки через
EC.visibility_of_element_located, атрибутаaria-invalid="true"или класса у поля.
Проверять стоит обе стороны: невалидное значение после blur показывает ошибку, валидное её не показывает или убирает.
Если стек позволяет Playwright, код короче: page.get_by_label("Email").fill("abc"), затем locator.blur(), а expect(error).to_be_visible() сам ждёт до таймаута. Для фокуса есть to_be_focused() и not_to_be_focused().
Где спотыкаются
Ставят time.sleep(1). На медленном CI секунды не хватает, на быстрой машине она просто тратится. Нужно явное ожидание конкретного условия.
Вызывают событие через execute_script с dispatchEvent(new Event("blur")). Обработчик сработает, но фокус останется в поле, и тест пройдёт даже там, где у пользователя логика сломана.
Путают отсутствие и невидимость. find_element на отсутствующем элементе бросит NoSuchElementException, а скрытый элемент найдётся и вернёт False из is_displayed(). Для проверки «ошибки нет» подходит EC.invisibility_of_element_located, который устраивает оба случая.