Случайный "Элемент больше не привязан к DOM" StaleElementReferenceException

Я надеюсь, что это только я, но Selenium Webdriver кажется полным кошмаром. В настоящее время веб-репозиторий Chrome недоступен, а другие драйверы довольно ненадежны или, как кажется. Я борюсь со многими проблемами, но вот один.

Случайно, мои тесты не сработают с

"org.openqa.selenium.StaleElementReferenceException: Element is no longer attached 
to the DOM    
System info: os.name: 'Windows 7', os.arch: 'amd64',
 os.version: '6.1', java.version: '1.6.0_23'"

Я использую webdriver версии 2.0b3. Я видел это с драйверами FF и IE. Единственный способ предотвратить это - добавить фактический вызов Thread.sleep до возникновения исключения. Однако это плохой способ, поэтому я надеюсь, что кто-то может указать на ошибку с моей стороны, которая сделает все это лучше.

4b9b3361

Да, если у вас возникли проблемы с StaleElementReferenceExceptions, потому что ваши тесты плохо написаны. Это состояние гонки. Рассмотрим следующий сценарий:

WebElement element = driver.findElement(By.id("foo"));
// DOM changes - page is refreshed, or element is removed and re-added
element.click();

Теперь, когда вы нажимаете на элемент, ссылка на элемент больше недействительна. Для WebDriver практически невозможно догадаться обо всех случаях, когда это может произойти, поэтому оно подбрасывает руки и дает вам контроль, кто, как автор теста/приложения, должен точно знать, что может или не может произойти. То, что вы хотите сделать, - это явно ждать, пока DOM находится в состоянии, когда вы знаете, что вещи не изменятся. Например, использование WebDriverWait для ожидания существования определенного элемента:

// times out after 5 seconds
WebDriverWait wait = new WebDriverWait(driver, 5);

// while the following loop runs, the DOM changes - 
// page is refreshed, or element is removed and re-added
wait.until(presenceOfElementLocated(By.id("container-element")));        

// now we're good - let click the element
driver.findElement(By.id("foo")).click();

Метод presenceOfElementLocated() будет выглядеть примерно так:

private static Function<WebDriver,WebElement> presenceOfElementLocated(final By locator) {
    return new Function<WebDriver, WebElement>() {
        @Override
        public WebElement apply(WebDriver driver) {
            return driver.findElement(locator);
        }
    };
}

Вы совершенно правы в том, что текущий драйвер Chrome довольно нестабилен, и вы с удовольствием услышите, что в Selenium trunk есть перезаписанный драйвер Chrome, где большая часть реализации была выполнена разработчиками Chromium как часть их дерево.

PS. В качестве альтернативы вместо ожидания явно, как в приведенном выше примере, вы можете включить неявные ожидания - таким образом, WebDriver всегда будет петлиться до тех пор, пока указанный тайм-аут не ожидает появления элемента:

driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS)

По моему опыту, явное ожидание всегда более надежное.

109
ответ дан 19 апр. '11 в 1:58
источник

Я смог использовать такой метод с некоторым успехом:

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

Да, он просто проводит опрос элемента до тех пор, пока он перестанет считаться устаревшим (свежим?). На самом деле это не доходит до корня проблемы, но я обнаружил, что WebDriver может быть довольно разборчивым в том, чтобы выбрасывать это исключение - иногда я получаю его, а иногда и нет. Или может быть, что DOM действительно меняется.

Поэтому я не совсем согласен с вышеприведенным ответом, что это обязательно указывает на плохо написанный тест. У меня есть свежие страницы, с которыми я никак не взаимодействовал. Я думаю, что есть некоторая шелушатость как в том, как представлен DOM, так и в том, что WebDriver считает устаревшим.

9
ответ дан 15 марта '13 в 23:29
источник

Иногда я получаю эту ошибку, когда обновления AJAX находятся на полпути. Capybara, похоже, довольно умна в ожидании изменений DOM (см. Почему wait_until был удален из Capybara), но время ожидания по умолчанию в 2 секунды было просто недостаточно в моем случае. Изменено в _spec_helper.rb_, например,

Capybara.default_wait_time = 5
6
ответ дан 06 авг. '13 в 15:57
источник

У меня была та же проблема, и моя была вызвана старой версией селена. Я не могу обновить новую версию из-за среды разработки. Проблема вызвана HTMLUnitWebElement.switchFocusToThisIfNeeded(). Когда вы переходите на новую страницу, может случиться, что элемент, который вы нажали на старой странице, - это oldActiveElement (см. Ниже). Selenium пытается получить контекст от старого элемента и терпит неудачу. Вот почему они построили попытку catch в будущих выпусках.

Код из версии selenium-htmlunit-driver < 2.23.0:

private void switchFocusToThisIfNeeded() {
    HtmlUnitWebElement oldActiveElement =
        ((HtmlUnitWebElement)parent.switchTo().activeElement());

    boolean jsEnabled = parent.isJavascriptEnabled();
    boolean oldActiveEqualsCurrent = oldActiveElement.equals(this);
    boolean isBody = oldActiveElement.getTagName().toLowerCase().equals("body");
    if (jsEnabled &&
        !oldActiveEqualsCurrent &&
        !isBody) {
      oldActiveElement.element.blur();
      element.focus();
    }
}

Код из версии selenium-htmlunit-driver >= 2.23.0:

private void switchFocusToThisIfNeeded() {
    HtmlUnitWebElement oldActiveElement =
        ((HtmlUnitWebElement)parent.switchTo().activeElement());

    boolean jsEnabled = parent.isJavascriptEnabled();
    boolean oldActiveEqualsCurrent = oldActiveElement.equals(this);
    try {
        boolean isBody = oldActiveElement.getTagName().toLowerCase().equals("body");
        if (jsEnabled &&
            !oldActiveEqualsCurrent &&
            !isBody) {
        oldActiveElement.element.blur();
        }
    } catch (StaleElementReferenceException ex) {
      // old element has gone, do nothing
    }
    element.focus();
}

Без обновления до версии 2.23.0 или новее вы можете просто указать любой элемент в фокусе страницы. Я просто использовал element.click(), например.

0
ответ дан 17 февр. '16 в 15:57
источник

Чтобы добавить к ответу @jarib, я сделал несколько методов расширения, которые помогают устранить условие гонки.

Вот моя настройка:

У меня есть класс Called "Driver.cs". Он содержит статический класс, полный методов расширения для драйвера и других полезных статических функций.

Для элементов, которые мне обычно нужно получить, я создаю метод расширения, например:

public static IWebElement SpecificElementToGet(this IWebDriver driver) {
    return driver.FindElement(By.SomeSelector("SelectorText"));
}

Это позволяет вам извлечь этот элемент из любого тестового класса с кодом:

driver.SpecificElementToGet();

Теперь, если это приводит к StaleElementReferenceException, у меня есть следующий статический метод в моем классе драйвера:

public static void WaitForDisplayed(Func<IWebElement> getWebElement, int timeOut)
{
    for (int second = 0; ; second++)
    {
        if (second >= timeOut) Assert.Fail("timeout");
        try
        {
            if (getWebElement().Displayed) break;
        }
        catch (Exception)
        { }
        Thread.Sleep(1000);
    }
}

Этот первый параметр функции - это любая функция, которая возвращает объект IWebElement. Второй параметр - это тайм-аут в секундах (код для таймаута был скопирован из Selenium IDE для FireFox). Код можно использовать для исключения исключения устаревшего элемента следующим образом:

MyTestDriver.WaitForDisplayed(driver.SpecificElementToGet,5);

Вышеприведенный код вызовет driver.SpecificElementToGet().Displayed, пока driver.SpecificElementToGet() не выдаст исключений, а .Displayed примет значение true и не пройдет 5 секунд. Через 5 секунд тест завершится с ошибкой.

С другой стороны, чтобы ждать, пока элемент не будет присутствовать, вы можете использовать следующую функцию следующим образом:

public static void WaitForNotPresent(Func<IWebElement> getWebElement, int timeOut) {
    for (int second = 0;; second++) {
        if (second >= timeOut) Assert.Fail("timeout");
            try
            {
                if (!getWebElement().Displayed) break;
            }
            catch (ElementNotVisibleException) { break; }
            catch (NoSuchElementException) { break; }
            catch (StaleElementReferenceException) { break; }
            catch (Exception)
            { }
            Thread.Sleep(1000);
        }
}
0
ответ дан 12 авг. '15 в 17:37
источник

Только что случилось со мной при попытке send_keys в поле ввода поиска, которое имеет autoupdate в зависимости от того, что вы вводите. Как упоминалось Eero, это может произойти, если ваш элемент обновляет Ajax, когда вы вводите текст внутри входной элемент. Решение - отправить один символ за раз и снова выполнить поиск для элемента ввода. (Пример в рубине, показанный ниже)

def send_keys_eachchar(webdriver, elem_locator, text_to_send)
  text_to_send.each_char do |char|
    input_elem = webdriver.find_element(elem_locator)
    input_elem.send_keys(char)
  end
end
0
ответ дан 30 апр. '15 в 19:14
источник

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

private void setElementLocator()
{
    this.locatorVariable = "selenium_" + DateTimeMethods.GetTime().ToString();
    ((IJavaScriptExecutor)this.driver).ExecuteScript(locatorVariable + " = arguments[0];", this.element);
}

private void RetrieveElement()
{
    this.element = (IWebElement)((IJavaScriptExecutor)this.driver).ExecuteScript("return " + locatorVariable);
}

Вы видите, что я "локализую" или скорее сохраняю элемент в глобальной переменной js и извлекаю элемент, если это необходимо. Если страница будет перезагружена, эта ссылка больше не будет работать. Но до тех пор, пока будут сделаны только изменения, обременительная ссылка остается. И это должно делать работу в большинстве случаев.

Также он избегает повторного поиска элемента.

Джон

0
ответ дан 10 июля '13 в 23:32
источник

В Java 8 вы можете использовать очень простой метод для этого:

private Object retryUntilAttached(Supplier<Object> callable) {
    try {
        return callable.get();
    } catch (StaleElementReferenceException e) {
        log.warn("\tTrying once again");
        return retryUntilAttached(callable);
    }
}
-1
ответ дан 16 авг. '14 в 13:08
источник
FirefoxDriver _driver = new FirefoxDriver();

// create webdriverwait
WebDriverWait wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(10));

// create flag/checker
bool result = false;

// wait for the element.
IWebElement elem = wait.Until(x => x.FindElement(By.Id("Element_ID")));

do
{
    try
    {
        // let the driver look for the element again.
        elem = _driver.FindElement(By.Id("Element_ID"));

        // do your actions.
        elem.SendKeys("text");

        // it will throw an exception if the element is not in the dom or not
        // found but if it didn't, our result will be changed to true.
        result = !result;
    }
    catch (Exception) { }
} while (result != true); // this will continue to look for the element until
                          // it ends throwing exception.
-5
ответ дан 22 июня '12 в 5:54
источник