Подтвердить что ты не робот

Как разрешить исключение Stale? если элемент больше не привязан к DOM?

У меня вопрос о том, что элемент больше не привязан к DOM.

Я пробовал разные решения, но они работают прерывисто. Пожалуйста, предложите решение, которое может быть постоянным.

WebElement getStaleElemById(String id, WebDriver driver) {
    try {
        return driver.findElement(By.id(id));
    } catch (StaleElementReferenceException e) {
        System.out.println("Attempting to recover from StaleElementReferenceException ...");
        return getStaleElemById(id, driver);
    }
}

WebElement getStaleElemByCss(String css, WebDriver driver) {
    try {
        return driver.findElement(By.cssSelector(css));
    } catch (StaleElementReferenceException e) {
        System.out.println("Attempting to recover from StaleElementReferenceException ...");
        return getStaleElemByCss(css, driver);
    } catch (NoSuchElementException ele) {
         System.out.println("Attempting to recover from NoSuchElementException ...");
         return getStaleElemByCss(css, driver);
    }
}

Спасибо, Ана

4b9b3361

Ответ 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);
    }
}

Таким образом, вы можете значительно уменьшить объем кода, который вам нужно поддерживать.

Ответ 2

Я решаю это с помощью 1. сохранения устаревшего элемента и опроса его до тех пор, пока он не выдает исключение, а затем 2. дождитесь, пока элемент снова будет виден.

    boolean isStillOnOldPage = true;
    while (isStillOnOldPage) {
        try {
            theElement.getAttribute("whatever");
        } catch (StaleElementReferenceException e) {
            isStillOnOldPage = false;
        }
    }
    WebDriverWait wait = new WebDriverWait(driver, 15);
    wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("theElementId")));

Ответ 3

Если вы пытаетесь нажать на ссылку, это приведет вас к новой странице. После этого перейдите назад и нажмите на другие ссылки. Они под кодом могут помочь вам.

public int getNumberOfElementsFound(By by) {
    return  driver.findElements(by).size();
  }

public WebElement getElementWithIndex(By by, int pos) {
    return driver.findElements(by).get(pos);
  }

/**click on each link */
public void getLinks()throws Exception{
try {
List<WebElement> componentList = driver.findElements(By.tagName("a"));
System.out.println(componentList.size()); 

    for (WebElement component : componentList)
    {
        //click1();
        System.out.println(component.getAttribute("href"));
    }
 int numberOfElementsFound = getNumberOfElementsFound(By.tagName("a"));
for (int pos = 0; pos < numberOfElementsFound; pos++) {
     if (getElementWithIndex(By.tagName("a"), pos).isDisplayed()){

  getElementWithIndex(By.tagName("a"), pos).click();
  Thread.sleep(200);
  driver.navigate().back();
  Thread.sleep(200);                                                       
}
  }
    }catch (Exception e){
        System.out.println("error in getLinks "+e);
    }
}

Ответ 4

Решения для их решения:

  • Сохранение локаторов в ваших элементах вместо ссылок
driver = webdriver.Firefox();
driver.get("http://www.github.com");
search_input = lambda: driver.find_element_by_name('q');
search_input().send_keys('hello world\n'); 
time.sleep(5);


search_input().send_keys('hello frank\n') // no stale element exception
  1. Использовать крючки в используемых библиотеках JS
   # Using Jquery queue to get animation queue length.
    animationQueueIs = """
    return $.queue( $("#%s")[0], "fx").length;
    """ % element_id
    wait_until(lambda: self.driver.execute_script(animationQueueIs)==0)
  1. Перемещение ваших действий в JavaScript-инъекцию
 self.driver.execute_script("$(\"li:contains('Narendra')\").click()");
  1. Проактивно ждать, пока элемент станет устаревшим
  # Wait till the element goes stale, this means the list has updated
  wait_until(lambda: is_element_stale(old_link_reference))

Это решение, которое работало для меня