Не понятно почему вываливается StaleElementReferenceException

Добрый день!

Есть несколько тестов к одной форме, которые используют следующий код:

def select_drop_down(context, text, id):
WebDriverWait(context.browser, TIME_FOR_WAIT).until(
    EC.element_to_be_clickable((By.XPATH, '//div[@id = "%s"]/a' % id))
)
element = context.browser.find_element_by_xpath('//div[@id = "%s"]/a' % id)
scroll_element_into_view(context.browser, element)
context.browser.find_element_by_xpath('//div[@id = "%s"]/a' % id).click()
WebDriverWait(context.browser, TIME_FOR_WAIT).until(
    EC.presence_of_element_located((By.XPATH, "//*[@id='select2-drop']/div/input"))
)
context.browser.find_element_by_xpath("//*[@id='select2-drop']/div/input").send_keys(text)
WebDriverWait(context.browser, TIME_FOR_WAIT).until(
    EC.visibility_of_element_located((By.XPATH, '//*/li//*[contains(text(), "%s")]' % text))
)
context.browser.find_element_by_xpath('//*/li//*[contains(text(), "%s")]' % text).click()

Код выбирает элемент из выпадающего списка.
В некоторых тестах работает, в некоторых нет.
При этом если запустить тесты в которых код не работает отдельно – тесты проходят.

Проблемное место:
context.browser.find_element_by_xpath("//*[@id='select2-drop']/div/input").send_keys(text) WebDriverWait(context.browser, TIME_FOR_WAIT).until( EC.visibility_of_element_located((By.XPATH, '//*/li//*[contains(text(), "%s")]' % text))

В этом месте иногда вываливается ошибка – StaleElementReferenceException: Message: Element not found in the cache.

Почему это может происходить?
Спасибо.

P.S. Используется firefox 24

Select2 - это вообще отдельная песня. Он на каждый чих обновляет список. Время загрузки самого списка может сильно зависеть от кол-ва элементов.

Вообще говоря, я в свое время отказался от затеи вбивать текст в его инпут из-за динамической фильтрации. В итоге, пришлось писать обертку над Select2 и обращаться к этому компоненту через его нативные API.

К примеру, чтобы раскрыть список, можно вызвать следующий JS:

executeJS("$(\"" + rootElementSelector + "\").select2("\open"\);");

При этом, rootElementSelector всегда имеет id, и почти всегда некорректно распознается FirePath’ом при подсветке. Как правило, выбирается 1 из вложенных компонентов. Но, немного потренировавшись, можно без труда его определять и на глаз. Выпадающий список имеет постоянную XPath структуру, т.е. вполне реально создать динамический локатор и селектить элемент по строгому вхождению текста, либо via contains.

В целом, если список небольшой, можно скомбинировать JS раскрытие + WebDriverWait для выбора из списка.

Проще всего будет 1 раз написать обертку для Select2 и использовать ее по всему коду. Тестировать Select2 api можете прямо в консоли браузера.

2 лайка

Тоесть проблема в select2, и с ним не стоит использовать webdriver, а использовать его api для выпадающих списков на select2?

Ну скажем так, для Select2 пока нет готового компонента у драйвера, в отличие от первой версии Select. Использовать его с WebDriver вполне реально, но из-за динамической фильтрации появляется множество подводных камней. Драйвер то вбивает текст посимвольно, что постоянно тригерит ивент фильтрации списка. А постоянное обновление чревато появлению стейлов. Ну можете еще попробовать вводить текст целиком, по типу paste. Тогда обновление пройдет лишь единожды. Но с нативным открытием списка имхо гораздо проще, т.к. по сути вам нужно подождать лишь пока он прогрузится. А дальше, простым кликом выбираете элемент по вхождению / равенству текста.

2 лайка

Ясно, спасибо!
Буду разбираться.

Если кому-нибудь поможет вот мое решение:

  1. Добавил time.sleep(2) перед каждым взаимодействием со списком
  2. Повесил на клик:
while True: 
try: 
      driver.find_element_by_xpath(xpath).click()
      break
  except StaleElementReferenceException:
      continue

Костыль жуткий, и по сути просто пропускаем эту ошибку.
p.s. как тут код нормально вставлять? спасибо!

В этом случае лучше все-же с явными ожиданиями поигрться… бесконечные циклы - это больно :smile:

Может такой вариант, без бесконечных циклов, подойдет

def action(xpath):
    try:
        driver.find_element_by_xpath(xpath).click()
        return True
    except StaleElementReferenceException:
        return False

WebDriverWait(driver, timeout).until(
    lambda vardriver: action(xpath)
)
1 лайк

Ты по сути сделал то что есть в классе ExpectedConditions, который как раз для явных ожиданий очень удобен, но это в java