Ответ 1
Проблема
Проблема, с которой вы, вероятно, сталкиваетесь, заключается в том, что метод возвращает правый (и действительный!) элемент, но когда вы пытаетесь получить к нему доступ через секунду, он устарел и бросает.
Это обычно возникает, когда:
- Вы щелкаете то, что загружает новую страницу асинхронно или, по крайней мере, меняет ее.
- Вы сразу же (до того, как загрузка страницы может закончиться), найдите элемент... и вы его найдете!
- Страница, наконец, выгружается, а новая загружается.
- Вы пытаетесь получить доступ к своему ранее найденному элементу, но теперь он устарел, даже если новая страница также содержит его.
Решения
Есть четыре способа решить эту проблему:
-
Использовать правильные ожидания
Использовать надлежащие ожидания после каждой ожидаемой загрузки страницы при обращении к асинхронным страницам. Вставьте явное ожидание после первого щелчка и дождитесь загрузки новой страницы/нового содержимого. Только после этого вы можете попытаться найти нужный элемент. Это должно быть первое, что вы сделаете. Это значительно повысит надежность ваших тестов.
-
Как вы это сделали
Я использую вариант вашего метода уже два года (вместе с техникой, описанной выше в решении 1), и он работает большую часть времени и терпит неудачу только на чужих ошибках WebDriver. Попробуйте получить доступ к найденному элементу сразу после его обнаружения (перед возвратом из метода) с помощью метода
.isDisplayed()
или чего-то еще. Если он выбрасывает, вы уже знаете, как искать снова. Если он проходит, у вас есть еще одна (ложная) уверенность. -
Используйте WebElement, который снова обнаруживается при устаревших
Напишите декоратор
WebElement
, который помнит, как он был найден, и повторно находил его при его доступе и бросках. Это явно заставляет использовать пользовательские методыfindElement()
, которые возвращают экземпляры вашего декоратора (или, еще лучше, украшенныеWebDriver
, которые возвращают ваши экземпляры из обычных методовfindElement()
иfindElemens()
). Сделайте это так:public class NeverStaleWebElement implements WebElement { private WebElement element; private final WebDriver driver; private final By foundBy; public NeverStaleWebElement(WebElement element, WebDriver driver, By foundBy) { this.element = element; this.driver = driver; this.foundBy = foundBy; } @Override public void click() { try { element.click(); } catch (StaleElementReferenceException e) { // log exception // assumes implicit wait, use custom findElement() methods for custom behaviour element = driver.findElement(foundBy); // recursion, consider a conditioned loop instead click(); } } // ... similar for other methods, too }
Обратите внимание, что, хотя я думаю, что информация
foundBy
должна быть доступна из общих WebElements, чтобы сделать это проще, разработчики Selenium считают ошибкой попробовать что-то подобное и выбрали не публиковать эту информацию. Вероятно, это плохая практика для повторного поиска на устаревших элементах, потому что вы неявно проверяете элементы без какого-либо механизма проверки того, оправдано ли это. Механизм повторного поиска потенциально мог бы найти совершенно другой элемент, а не тот же самый. Кроме того, он терпит неудачу сfindElements()
, когда есть много найденных элементов (вам либо нужно запретить повторное обнаружение элементов, найденных с помощьюfindElements()
, либо запомнить многозначность вашего элемента из возвращаемогоList
).Я думаю, что это было бы полезно иногда, но верно, что никто никогда не использовал бы варианты 1 и 2, которые, очевидно, намного лучше подходят для надежности ваших тестов. Используйте их и только после того, как вы уверены, что вам это нужно, идите на это.
-
Используйте очередь задач (которая может повторять задачи)
Внедрите весь свой рабочий процесс по-новому!
- Сделать центральную очередь заданий для запуска. Заставьте эту очередь помнить прошлые задания.
- Реализовать каждую необходимую задачу ( "найти элемент и щелкнуть по нему", "найти элемент и отправить ему ключи" и т.д.) с помощью шаблона Command. При вызове добавьте задачу в центральную очередь, которая затем (либо синхронно, либо асинхронно, не имеет значения) запустит ее.
- Аннотировать каждую задачу с помощью
@LoadsNewPage
,@Reversible
и т.д. по мере необходимости. - Большинство ваших задач будут обрабатывать свои исключения сами по себе, они должны быть автономными.
- Когда очередь будет сталкиваться с отсутствием устаревшего элемента, она должна выполнить последнюю задачу из истории задач и повторно запустить ее, чтобы повторить попытку.
Это, очевидно, потребует больших усилий и, если не будет продумано очень хорошо, может скоро возразить. Я использовал (более сложный и мощный) вариант этого для возобновления неудачных тестов после того, как я вручную исправил страницу, на которой они были. В некоторых условиях (например, на
StaleElementException
) сбой не закончил бы тест сразу, но подождал бы (до окончательного истечения времени через 15 секунд), появляя информативное окно и предоставляя пользователю возможность вручную обновите страницу/нажмите правую кнопку/исправьте форму/что угодно. Затем он перезапустит неудачную задачу или даже предоставит возможность вернуться к предыдущим событиям (например, к последнему заданию@LoadsNewPage
).
Конечные нитки
Все сказанное, ваше оригинальное решение может использовать некоторую полировку. Вы могли бы объединить два метода в один, более общий (или, по крайней мере, сделать их делегатом для этого, чтобы уменьшить повторение кода):
WebElement getStaleElem(By by, WebDriver driver) {
try {
return driver.findElement(by);
} catch (StaleElementReferenceException e) {
System.out.println("Attempting to recover from StaleElementReferenceException ...");
return getStaleElem(by, driver);
} catch (NoSuchElementException ele) {
System.out.println("Attempting to recover from NoSuchElementException ...");
return getStaleElem(by, driver);
}
}
В Java 7 достаточно даже одного многоканального блока:
WebElement getStaleElem(By by, WebDriver driver) {
try {
return driver.findElement(by);
} catch (StaleElementReferenceException | NoSuchElementException e) {
System.out.println("Attempting to recover from " + e.getClass().getSimpleName() + "...");
return getStaleElem(by, driver);
}
}
Таким образом, вы можете значительно уменьшить объем кода, который вам нужно поддерживать.