Як виправити помилку NotFoundException у Selenium?
Виняток NotFoundException у Selenium — одна з найпоширеніших помилок під час написання скриптів автоматизації. Зазвичай, вона виникає при спробі знайти той чи інший елемент на сторінці методом find_element:
element = driver.find_element(By.ID, "buton")
Ця помилка ніколи не виникає, якщо користуватися методом find_elements
, тому що він повертає порожній список []
в тому випадку, якщо елементи не знайдені на сторінці.
Selenium WebDriver підтримує 8 способів, як знайти елемент на сторінці:
- за ID елемента (By.ID)
- за XPath (By.XPATH)
- за текстом посилання (By.LINK_TEXT)
- за частковим текстом посилання (By.PARTIAL_LINK_TEXT)
- за атрибутом NAME елемента (By.NAME)
- за тегом TAG_NAME (By.TAG_NAME)
- на ім'я CSS-класу або комбінації (By.CLASS_NAME)
- за CSS-селектором (By.CSS_SELECTOR)
Комбінація способу пошуку та рядка називається локатором. Наприклад, локатор By.ID, "button"
знайде елемент <a href="..." id="button">Click me</a>
, а локатор By.NAME, "login"
знайде елемент <input type="text" name="login">
.
Чому виникає помилка NoSuchElementException? Якщо неправильно вказати локатор при пошуку елемента або пошукати його на невідповідній сторінці або в невідповідний час, може виникнути помилка NoSuchElementException.
Як виглядає повідомлення про помилку NoSuchElementException у консолі
Зазвичай, у консолі подібне повідомлення має три важливі фрагменти:
- клас виключення
selenium.common.exceptions.NoSuchElementException
- повідомлення
Message: no such element: Unable to locate element
- і яким локатором шукали елемент:
{"method":"css selector","selector":"[id="buton"]"}
(цей фрагмент буде в кожному окремому випадку різний, в даному прикладі локторBy. ID, "buton"
).
Повний текст повідомлення (локатор у кожному окремому випадку буде різним):
selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {"method":"css selector","selector":"[id="buton"]"}
Неправильний локатор
Найперший крок у виправленні помилки NotFoundException — це перевіряти ще раз локатор.
- Потрібно переконатися, що використовується правильний спосіб пошуку елемента (By.ID, By.NAME, By.XPATH тощо).
- Переконатись, що передається правильний рядок разом із способом.
У цьому допомагає інструмент Google Chrome DevTools, який може показати HTML-структуру сторінки та здійснити пошук за CSS-селектором або XPath-селектором.
Потрібно відкрити сторінку, що тестується, і вибрати в контекстному меню пункт "Inspect":
Потім вибрати вкладку "Elements", і натиснути Ctrl+F (Cmd+F), потім у пошуковому рядку вписати необхідний CSS-селектор або Xpath і переконатися, що знаходиться щонайменше один елемент:
Якщо елемент не знайдено, то локатор неправильний. У цьому вікні можна спробувати різні способи пошуку елемента (різні селектори) і підібрати робочий варіант.
Іноді складно зрозуміти, який саме оператор find_element
викликає виняток NoSuchElementException, особливо якщо таких операторів багато. Тут може стати в нагоді відладчик PyCharm.
Для початку потрібно відкрити меню Run - View Breakpoints...
Потім поставити галочку навпроти Python Exception Breakpoint - Any Exception, як показано на скріншоті нижче:
Потім потрібно запустити скрипт знову. У момент виникнення виключення NoSuchElementException відладчик зупиниться. Потрібно вибрати в стеку викликів ім'я нашого скрипта і PyCharm покаже рядок коду, в якому відбувається виняток:
Не та сторінка
Якщо ви перевірили ще раз локатор і переконалися, що він коректний: правильний спосіб пошуку і елемент дійсно присутній на сторінці, то потрібно переконатися, що в момент пошуку find_element
відкрита необхідна сторінка. Адже якщо ми знаходимося не на тій сторінці, то звичайно ми нічого не знайдемо. Дуже складно шукати чорну кішку у темній кімнаті, особливо якщо її там немає.
Для діагностики такої проблеми рекомендую встановити брейкпоінт на тому операторі find_element (як показано на скріншоті нижче), який призводить до виключення NoSuchElementException і перезапустити скрипт. У момент зупинки зверніть увагу, яка відкрита сторінка в браузері, чи є там шуканий елемент. Це можна перевірити за допомогою Google Chrome DevTools (див. попередній пункт).
Якщо в даний момент у браузері відображається якась неправильна сторінка, то потрібно перевірити ще раз попередні кроки скрипту і розібратися, чому ці кроки не призводять до відкриття бажаної сторінки.
Елемент ще не з'явився в момент пошуку
Якщо на попередніх кроках ви переконалися, що локатор правильний і що в момент пошуку ви знаходитесь на потрібній сторінці, то ймовірно, що потрібно дочекатися появи елемента в коді сторінки.
Python-скрипти працюють дуже швидко, а інтернет працює відносно повільно. Розглянемо приклад нижче, в якому ми робимо дві дії: клацаємо по кнопці "Логін" і відразу ж після логіну клацаємо за посиланням "Profile":
# Знаходимо кнопку входу та логінимся.
driver.find_element(By.ID, "login").click()
# Відкриваємо профіль.
driver.find_element(By.LINK_TEXT, "Profile").click()
З т. з. користувача все виглядає логічно: дві послідовні дії: логінимся, клацаємо на профіль.
Тут потрібно знати один важливий факт: процес скрипту автоматизації та процесу веб-програми працюють паралельно, і ніяк не синхронізуються між собою. Тому скрипт автоматизації після логіну відразу переходить до наступної інструкції: клік по профілю. Але всі веб-сервери мають невелику затримку між діями, тому в момент кліка по профілю ця сторінка ще не завантажена повністю.
Типовими симптомами такої помилки NoSuchElementException є той факт, що навіть під відладчиком відображається правильна сторінка і що елемент знаходиться на сторінці.
Вирішення цієї проблеми – це очікування наявності елемента на сторінці за допомогою класу WebDriverWait. Зверніть увагу, що під наявністю елемента на сторінці мається на увазі присутність елемента в HTML DOM, а не візуальне присутність на сторінці.
Рішення, в якому скрипт автоматизації буде очікувати на появу посилання з текстом Profile на сторінці:
from selenium.webdriver.support import expected_conditions as EC
# Знаходимо кнопку входу та логінимся.
driver.find_element(By.ID, "login").click()
# Очікуємо появи посилання на сторінці.
WebDriverWait(driver, timeout=10).until(
EC.presence_of_element_located((By.LINK_TEXT, "Profile"))
)
# Відкриваємо профіль.
driver.find_element(By.LINK_TEXT, "Profile").click()
У такому разі скрипт автоматизації перед тим, як натиснути на посилання, дочекається появи її на сторінці.
Висновки
Якщо ви зіткнулися з помилкою NoSuchElementException, алгоритм перевірки наступний:
- Перевірте за допомогою Google Chrome DevTools, що елемент із таким локатором існує на сторінці.
- Переконайтеся, що під час пошуку елемента скрипт доходить до потрібної сторінки.
- Додайте явне очікування елемента за допомогою WebDriverWait.