Есть отличная удаленная работа для php+codeception+jenkins+allure+docker спецов. 100% remote! Присоединиться к проекту

Происходит клик не по нужному элементу, хотя селектор введен правильно (Selenium, Python)


#1

Добрый день.
Подскажите, пожалуйста, как справиться с такой проблемой:
пытаюсь кликнуть на кнопку (селектор верно указан), но клик происходит не по кнопке, а по другому элементу (по loader); как сделать так, чтобы клик по кнопке происходил уже после того, как этот лоадер прогрузится.
Пытаюсь использовать явные ожидания, но ничего не выходит, в результате чего приходится мешать их со sleep, что в корне не правильно, да и срабатывает не всегда.
Заранее благодарю откликнувшихся.

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

success = True
wd = webdriver.Firefox()
wd.get('url')

try:
    wd.element = WebDriverWait(wd, 3).until(EC.element_to_be_clickable((By.CSS_SELECTOR, '.form--body .inputDef:nth-child(3) .inputDef--input')))
    wd.find_element_by_css_selector(".form--body .inputDef:nth-child(3) .inputDef--input").click()
    wd.find_element_by_css_selector(".form--body .inputDef:nth-child(3) .inputDef--input").clear()
    wd.find_element_by_css_selector(".form--body .inputDef:nth-child(3) .inputDef--input").send_keys("lalala")
    wd.find_element_by_css_selector(".form--body .inputDef:last-child .inputDef--input").click()
    wd.find_element_by_css_selector(".form--body .inputDef:last-child .inputDef--input").clear()
    wd.find_element_by_css_selector(".form--body .inputDef:last-child .inputDef--input").send_keys("****")
    wd.find_element_by_css_selector(".button .button__primary").click()
    wd.element = WebDriverWait(wd, 10).until(EC.element_to_be_clickable((By.CSS_SELECTOR, '.leftPanel .leftPanel--menu .leftPanel--menuItem:nth-child(4)')))
    wd.find_element_by_css_selector(".leftPanel .leftPanel--menu .leftPanel--menuItem:nth-child(4)").click()
    wd.element = WebDriverWait(wd, 10, 1).until(EC.invisibility_of_element_located((By.CSS_SELECTOR, '.form-root .component-loader')))
    wd.element = WebDriverWait(wd, 10, 1).until(EC.element_to_be_clickable((By.CSS_SELECTOR, '.decoratedOpacity .sectionWrap .secondaryTopPanel .secondaryTopPanel--left .button')))
    wd.find_element_by_css_selector(".decoratedOpacity .sectionWrap .secondaryTopPanel .secondaryTopPanel--left .button").click()
    wd.element = WebDriverWait(wd, 10).until(EC.element_to_be_clickable((By.CSS_SELECTOR, '.form--body .inputDef:first-child .inputDef--input')))
    wd.find_element_by_css_selector(".form--body .inputDef:first-child .inputDef--input").click()
    wd.find_element_by_css_selector(".form--body .inputDef:first-child .inputDef--input").clear()
    wd.find_element_by_css_selector(".form--body .inputDef:first-child .inputDef--input").send_keys("lalala")
    wd.find_element_by_css_selector(".form--body .inputDef:nth-child(2) .inputDef--input").click()
    wd.find_element_by_css_selector(".form--body .inputDef:nth-child(2) .inputDef--input").clear()
    wd.find_element_by_css_selector(".form--body .inputDef:nth-child(2) .inputDef--input").send_keys("999999999")
    wd.find_element_by_css_selector(".form .button").click()
    if ("Поле обязательно для заполнения" in wd.find_element_by_css_selector(".form--globalError").text):
        success = True
        print("Адрес не введен")
finally:
    wd.quit()
    if not success:
        raise Exception("Test failed.")

(Sergey Pirogov) #2

Отличный код :slight_smile:


(Yury) #3

Без обид, но это шедевр:

finally:
    wd.quit()
    if not success:
        raise Exception("Test failed.")

:slight_smile: Где вы так научились?

По существу вопроса - хорошо бы код страницы видеть (ту часть, где искомый элемент).


#4

Да без обид, конечно. Я начинаю только.
Это так получается, если сначала в билдере действия записать, а потом экспорт сделать в python.
Если можете, поделиться - как будет правильнее - спасибо.
Достаточно ли этого?


(Yury) #5

Если учитесь, то лучше не пользуйтесь всякими билдерами/рекордерами. Творите код руками.

По вашей задаче - попробуйте построить локаторы по XPATH.


#6

Спасибо большое.
Именно к этой кнопке пробовала обратиться по XPATH, результат был аналогичный. Попробую сейчас еще раз, возможно, что-то не учла.
Могу ли попросить совет: с точки зрения явных ожиданий - все ли использовано верно?


(Yury) #7

Синтаксически ожидание выглядит правильно. Но так как в целом весь код представляет собой жуткую мешанину, то разбираться в нем трудно. Попробуйте определить локаторы в отдельном месте и не переназначать переменную element. Чтобы получилось что-то типа:

wd = Webdriver.Firefox()

button1 = wd.find_element_by_xpath(locator1)
textbox1 = wd.find_element_by_xpath(locator2)

button1.click()
textbox1.clear()
textbox1.send_keys(value)

# и т.п.

То есть приучайте себя к тому, что определение драйвера должно быть в одном месте, представление о странице - в другом, а логика собственно теста - в третьем. Потом будете разносить это по разным модулям и классам. Почитайте про паттерн проектирования Page Object.


#8

Большое спасибо за помощь.


(Sergey QA) #9

У элемента “лоадер” есть какой-то атрибут обычно, или не у него - который изменяется в течении времени.
Можно подождать, пока элемент с таким атрибутом будет в наличии, указав к нему путь по XPath, например.
После изменения атрибута - старый локатор уже будет невалидный, элемент не будет найден на странице, и после этого уже спокойно кликайте на нужный элемент.
Тобишь вейт должен ожидать изменения элемента по сути(или появления элемента с нужным локатором).


#10

Спасибо.


#11

Здравствуйте.
Подскажите, пожалуйста, в правильном ли направлении двигаюсь или нет?
Спасибо.

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

success = True
wd = webdriver.Firefox()
wd.get('url')

textbox1 = wd.find_element_by_css_selector(".form--body .inputDef:nth-child(3) .inputDef--input")
textbox2 = wd.find_element_by_css_selector(".form--body .inputDef:last-child .inputDef--input")
button1 = wd.find_element_by_css_selector(".form .button:nth-child(4)")
button2 = wd.find_element_by_css_selector(".leftPanel .leftPanel--menu .leftPanel--menuItem:nth-child(4) .leftPanel--menuLink")
button3 = wd.find_element_by_css_selector(".decoratedOpacity .sectionWrap .secondaryTopPanel .secondaryTopPanel--left .button")
textbox3 = wd.find_element_by_css_selector(".form--body .inputDef:first-child .inputDef--input")
textbox4 = wd.find_element_by_css_selector(".form--body .inputDef:nth-child(2) .inputDef--input")
button4 = wd.find_element_by_css_selector(".form .button")
loader = wd.find_element_by_css_selector(".form-root .component-loader")

try:
    textbox1 = WebDriverWait(wd, 10).until(EC.element_to_be_clickable((By.CSS_SELECTOR, '.form--body .inputDef:nth-child(3) .inputDef--input')))
    textbox1.click()
    textbox1.clear()
    textbox1.send_keys("lalala")
    textbox2.click()
    textbox2.clear()
    textbox2.send_keys("****")
    button1.click()
    button2 = WebDriverWait(wd, 10).until(EC.element_to_be_clickable((By.CSS_SELECTOR, '.leftPanel .leftPanel--menu .leftPanel--menuItem:nth-child(4) .leftPanel--menuLink')))
    button2.click()
    loader = WebDriverWait(wd, 10, 1).until(EC.invisibility_of_element_located((By.CSS_SELECTOR, '.form-root .component-loader')))
    button3 = WebDriverWait(wd, 10, 1).until(EC.element_to_be_clickable((By.CSS_SELECTOR, '.decoratedOpacity .sectionWrap .secondaryTopPanel .secondaryTopPanel--left .button')))
    button3.click()
    textbox3 = WebDriverWait(wd, 10).until(EC.element_to_be_clickable((By.CSS_SELECTOR, '.form--body .inputDef:first-child .inputDef--input')))
    textbox3.click()
    textbox3.clear()
    textbox3.send_keys("Новый магазин")
    textbox4.click()
    textbox4.clear()
    textbox4.send_keys("999999999")
    button4.click()
    if ("Поле обязательно для заполнения" in wd.find_element_by_css_selector(".form--globalError").text):
        success = True
        print("Адрес не введен")
finally:
    wd.quit()
    if not success:
        raise Exception("Test failed.")

#12

У нас в проекте была похожая ситуация: во время работы javascript’а с сервером приложений появлялся некий визуальный элемент загрузки, говорящий о том, что стоит подождать, пока все запросы выполнятся и только после этого уже разрешается работать со страницей. Причем этот элемент мог появиться и исчезнуть несколько раз в течении в 1 - 3 секунд.
В коде мы выразили это так:

def wait_loading():
    """Процесс загрузки"""
    driver = get_webdriver()
    locator = (By.CSS_SELECTOR, 'body > div[class="loading"]')
    while True:
        try:
            WebDriverWait(driver, 1).until(
                visibility_of_element_located(locator))
        except TimeoutException:
            return
        WebDriverWait(driver, 300).until(
            invisibility_of_element_located(locator))

Таким образом мы сначала ждем 1 секунду пока появится элемент загрузки, а потом ждем пока он исчезнет. И только после этого уже работаем со страницей (нажимаем по кнопкам, вводим данные и т.п.).


#13

Большое спасибо.