Как избежать исключения «StaleElementReferenceException» в Selenium?

88

Я реализую множество тестов Selenium с использованием Java. Иногда мои тесты терпят неудачу из-за файла StaleElementReferenceException. Не могли бы вы предложить какие-нибудь подходы к повышению стабильности тестов?

Hoang Nguyen
источник

Ответы:

90

Это может произойти, если операция DOM, происходящая на странице, временно делает элемент недоступным. Чтобы учесть эти случаи, вы можете попытаться получить доступ к элементу несколько раз в цикле, прежде чем окончательно выбросить исключение.

Попробуйте это отличное решение от darrelgrainger.blogspot.com :

public boolean retryingFindClick(By by) {
    boolean result = false;
    int attempts = 0;
    while(attempts < 2) {
        try {
            driver.findElement(by).click();
            result = true;
            break;
        } catch(StaleElementException e) {
        }
        attempts++;
    }
    return result;
}
jspcal
источник
1
Вот Это Да! Это было именно то, что мне было нужно. Благодаря!
SpartaSixZero
1
Это также можно исправить, используя другую ссылку на элемент.
Рипон Аль Васим,
@jspcal, это сработало для меня! Большое спасибо!
Энтони
Если вышеперечисленное не решает проблему, обновление до последней версии Chrome - это то, что решило ее для нас.
Vdex
5
Это ужасно во многих отношениях. Это действительно решает мою текущую проблему, так что спасибо.
Software Engineer
68

У меня периодически возникала эта проблема. Без моего ведома BackboneJS работал на странице и заменял элемент, который я пытался щелкнуть. Мой код выглядел так.

driver.findElement(By.id("checkoutLink")).click();

Что, конечно, функционально то же самое.

WebElement checkoutLink = driver.findElement(By.id("checkoutLink"));
checkoutLink.click();

Иногда происходило то, что javascript заменял элемент checkoutLink между поиском и щелчком по нему, т.е.

WebElement checkoutLink = driver.findElement(By.id("checkoutLink"));
// javascript replaces checkoutLink
checkoutLink.click();

Что по праву привело к исключению StaleElementReferenceException при попытке щелкнуть ссылку. Я не мог найти надежного способа сказать WebDriver, чтобы он дождался завершения работы javascript, поэтому вот как я в конечном итоге решил это.

new WebDriverWait(driver, timeout)
    .ignoring(StaleElementReferenceException.class)
    .until(new Predicate<WebDriver>() {
        @Override
        public boolean apply(@Nullable WebDriver driver) {
            driver.findElement(By.id("checkoutLink")).click();
            return true;
        }
    });

Этот код будет постоянно пытаться щелкнуть ссылку, игнорируя StaleElementReferenceExceptions, пока либо щелчок не будет успешным, либо не истечет время ожидания. Мне нравится это решение, потому что оно избавляет вас от необходимости писать логику повтора и использует только встроенные конструкции WebDriver.

Кенни
источник
этот ответ устарел.
vaibhavcool20
20

Обычно это происходит из-за того, что DOM обновляется, и вы пытаетесь получить доступ к обновленному / новому элементу, но DOM обновился, поэтому у вас есть недопустимая ссылка.

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

Вот некоторый псевдо-код для иллюстрации (адаптированный из некоторого кода C #, который я использую ТОЧНО для этой проблемы):

WebDriverWait wait = new WebDriverWait(browser, TimeSpan.FromSeconds(10));
IWebElement aRow = browser.FindElement(By.XPath(SOME XPATH HERE);
IWebElement editLink = aRow.FindElement(By.LinkText("Edit"));

//this Click causes an AJAX call
editLink.Click();

//must first wait for the call to complete
wait.Until(ExpectedConditions.ElementExists(By.XPath(SOME XPATH HERE));

//you've lost the reference to the row; you must grab it again.
aRow = browser.FindElement(By.XPath(SOME XPATH HERE);

//now proceed with asserts or other actions.

Надеюсь это поможет!

Джим Холмс
источник
17

Решение Кенни хорошее, но его можно написать более элегантно

new WebDriverWait(driver, timeout)
        .ignoring(StaleElementReferenceException.class)
        .until((WebDriver d) -> {
            d.findElement(By.id("checkoutLink")).click();
            return true;
        });

Или также:

new WebDriverWait(driver, timeout).ignoring(StaleElementReferenceException.class).until(ExpectedConditions.elementToBeClickable(By.id("checkoutLink")));
driver.findElement(By.id("checkoutLink")).click();

Но в любом случае лучшее решение - полагаться на библиотеку Selenide, она справляется с подобными и другими вещами. (вместо ссылок на элементы он обрабатывает прокси, поэтому вам никогда не придется иметь дело с устаревшими элементами, что может быть довольно сложно). Селенид

кокоросселло
источник
Отказ от ответственности: я просто счастливый пользователь селенида, не
имею
Ваше второе решение будет работать, потому что элемент становится устаревшим, когда вы щелкаете по нему, а не когда находите его.
Rajagopalan
Используйте селенид, чтобы избежать этой проблемы, гораздо проще. Selenium не предназначен для использования в одиночку из-за этой проблемы и того факта, что это API низкого уровня для простого пользователя
cocorossello
Я прекрасно это понимаю. Я использую WATIR, который является оболочкой вокруг Ruby Selenium Binding, WATIR автоматически решает все эти проблемы (для экземпляра устаревшего элемента). Я ищу что-то эквивалентное в привязке Java, я нашел Selenide, но не знаю, как изменить неявное ожидание и явное ожидание в selenide. Подскажите, как это сделать? Или есть какие-то материалы, которые вы можете мне предложить, куда я могу порекомендовать? а что вы думаете о FluentLenium?
Rajagopalan
1
Люди должны знать, что выбранный ответ ОП датируется 2012 годом. МНОЖЕСТВО вещей изменилось за последние 7 лет. Этот ответ более верен для 2019 года.
hfontanez
11

Причина, по которой это StaleElementReferenceExceptionпроисходит, уже изложена: обновления DOM между поиском и выполнением каких-либо действий с элементом.

Для проблемы с щелчком я недавно использовал такое решение:

public void clickOn(By locator, WebDriver driver, int timeout)
{
    final WebDriverWait wait = new WebDriverWait(driver, timeout);
    wait.until(ExpectedConditions.refreshed(
        ExpectedConditions.elementToBeClickable(locator)));
    driver.findElement(locator).click();
}

Важнейшей частью является «цепочка» собственного Selenium ExpectedConditionsчерез ExpectedConditions.refreshed(). Это фактически ожидает и проверяет, обновился ли рассматриваемый элемент в течение указанного тайм-аута, а также ожидает, когда элемент станет интерактивным.

Посмотрите документацию по обновленному методу .

Christian.D
источник
4

В своем проекте я ввел понятие StableWebElement. Это оболочка для WebElement, которая может определять, является ли элемент устаревшим, и находить новую ссылку на исходный элемент. Я добавил вспомогательные методы для поиска элементов, которые возвращают StableWebElement вместо WebElement, и проблема со StaleElementReference исчезла.

public static IStableWebElement FindStableElement(this ISearchContext context, By by)
{
    var element = context.FindElement(by);
    return new StableWebElement(context, element, by, SearchApproachType.First);
} 

Код на C # доступен на странице моего проекта, но его можно легко перенести на java https://github.com/cezarypiatek/Tellurium/blob/master/Src/MvcPages/SeleniumUtils/StableWebElement.cs

цезарипятек
источник
1

Решение на C #:

Класс помощника:

internal class DriverHelper
{

    private IWebDriver Driver { get; set; }
    private WebDriverWait Wait { get; set; }

    public DriverHelper(string driverUrl, int timeoutInSeconds)
    {
        Driver = new ChromeDriver();
        Driver.Url = driverUrl;
        Wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(timeoutInSeconds));
    }

    internal bool ClickElement(string cssSelector)
    {
        //Find the element
        IWebElement element = Wait.Until(d=>ExpectedConditions.ElementIsVisible(By.CssSelector(cssSelector)))(Driver);
        return Wait.Until(c => ClickElement(element, cssSelector));
    }

    private bool ClickElement(IWebElement element, string cssSelector)
    {
        try
        {
            //Check if element is still included in the dom
            //If the element has changed a the OpenQA.Selenium.StaleElementReferenceException is thrown.
            bool isDisplayed = element.Displayed;

            element.Click();
            return true;
        }
        catch (StaleElementReferenceException)
        {
            //wait until the element is visible again
            element = Wait.Until(d => ExpectedConditions.ElementIsVisible(By.CssSelector(cssSelector)))(Driver);
            return ClickElement(element, cssSelector);
        }
        catch (Exception)
        {
            return false;
        }
    }
}

Призыв:

        DriverHelper driverHelper = new DriverHelper("http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp", 10);
        driverHelper.ClickElement("input[value='csharp']:first-child");

Аналогично можно использовать для Java.

Георгий Каргакис
источник
1

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

new FluentWait<>(driver).withTimeout(30, TimeUnit.SECONDS).pollingEvery(5, TimeUnit.SECONDS)
                    .ignoring(StaleElementReferenceException.class)
                    .until(new Function() {

                    @Override
                    public Object apply(Object arg0) {
                        WebElement e = driver.findelement(By.xpath(locatorKey));
                        Actions action = new Actions(driver);
                        action.moveToElement(e).doubleClick().perform();
                        return true;
                    }
                });
vaibhavcool20
источник
1

Чистый findByAndroidIdметод, который изящно обрабатывает StaleElementReference.

Это в значительной степени основано на ответе jspcal, но мне пришлось изменить этот ответ, чтобы он работал чисто с нашей настройкой, и поэтому я хотел добавить его здесь, если он будет полезен другим. Если этот ответ вам помог, пожалуйста, проголосуйте за ответ jspcal .

// This loops gracefully handles StateElementReference errors and retries up to 10 times. These can occur when an element, like a modal or notification, is no longer available.
export async function findByAndroidId( id, { assert = wd.asserters.isDisplayed, timeout = 10000, interval = 100 } = {} ) {
  MAX_ATTEMPTS = 10;
  let attempt = 0;

  while( attempt < MAX_ATTEMPTS ) {
    try {
      return await this.waitForElementById( `android:id/${ id }`, assert, timeout, interval );
    }
    catch ( error ) {
      if ( error.message.includes( "StaleElementReference" ) )
        attempt++;
      else
        throw error; // Re-throws the error so the test fails as normal if the assertion fails.
    }
  }
}
Джошуа Пинтер
источник
0

Это работает для меня (100% работает) с использованием С #

public Boolean RetryingFindClick(IWebElement webElement)
    {
        Boolean result = false;
        int attempts = 0;
        while (attempts < 2)
        {
            try
            {
                webElement.Click();
                result = true;
                break;
            }
            catch (StaleElementReferenceException e)
            {
                Logging.Text(e.Message);
            }
            attempts++;
        }
        return result;
    }
Шивам Бхарадвадж
источник
0

Проблема в том, что к тому времени, когда вы передадите элемент из Javascript в Java обратно в Javascript, он может покинуть DOM.
Попробуйте сделать все на Javascript:

driver.executeScript("document.querySelector('#my_id').click()") 
pguardiario
источник
0

Попробуй это

while (true) { // loops forever until break
    try { // checks code for exceptions
        WebElement ele=
        (WebElement)wait.until(ExpectedConditions.elementToBeClickable((By.xpath(Xpath))));  
        break; // if no exceptions breaks out of loop
    } 
    catch (org.openqa.selenium.StaleElementReferenceException e1) { 
        Thread.sleep(3000); // you can set your value here maybe 2 secs
        continue; // continues to loop if exception is found
    }
}
CodingGirl1
источник
0

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

.ignoring (StaleElement ...), .refreshed (...) и elementToBeClicable (...) не помогли, и я получал исключение в act.doubleClick(element).build().perform();строке.

Использование функции в моем основном тестовом классе:

openForm(someXpath);

Моя функция BaseTest:

int defaultTime = 15;

boolean openForm(String myXpath) throws Exception {
    int count = 0;
    boolean clicked = false;
    while (count < 4 || !clicked) {
        try {
            WebElement element = getWebElClickable(myXpath,defaultTime);
            act.doubleClick(element).build().perform();
            clicked = true;
            print("Element have been clicked!");
            break;
        } catch (StaleElementReferenceException sere) {
            sere.toString();
            print("Trying to recover from: "+sere.getMessage());
            count=count+1;
        }
    }

Моя функция BaseClass:

protected WebElement getWebElClickable(String xpath, int waitSeconds) {
        wait = new WebDriverWait(driver, waitSeconds);
        return wait.ignoring(StaleElementReferenceException.class).until(
                ExpectedConditions.refreshed(ExpectedConditions.elementToBeClickable(By.xpath(xpath))));
    }
Грю
источник
0

Возможна потенциальная проблема, которая приводит к исключению StaleElementReferenceException, о котором пока никто не упоминал (в отношении действий).

Я объясняю это на Javascript, но то же самое и на Java.

Это не сработает:

let actions = driver.actions({ bridge: true })
let a = await driver.findElement(By.css('#a'))
await actions.click(a).perform() // this leads to a DOM change, #b will be removed and added again to the DOM.
let b = await driver.findElement(By.css('#b'))
await actions.click(b).perform()

Но повторное создание экземпляров действий решит эту проблему:

let actions = driver.actions({ bridge: true })
let a = await driver.findElement(By.css('#a'))
await actions.click(a).perform()  // this leads to a DOM change, #b will be removed and added again to the DOM.
actions = driver.actions({ bridge: true }) // new
let b = await driver.findElement(By.css('#b'))
await actions.click(b).perform()
ndsvw
источник
0

Обычно StaleElementReferenceException, когда элемент, к которому мы пытаемся получить доступ, появился, но другие элементы могут повлиять на положение интересующего нас элемента, поэтому, когда мы пытаемся щелкнуть или получить текст, или попытаться выполнить какое-либо действие с WebElement, мы получаем исключение, которое обычно говорит, что элемент не прикреплен к DOM .

Решение, которое я пробовал, выглядит следующим образом:

 protected void clickOnElement(By by) {
        try {
            waitForElementToBeClickableBy(by).click();
        } catch (StaleElementReferenceException e) {
            for (int attempts = 1; attempts < 100; attempts++) {
                try {
                    waitFor(500);
                    logger.info("Stale element found retrying:" + attempts);
                    waitForElementToBeClickableBy(by).click();
                    break;
                } catch (StaleElementReferenceException e1) {
                    logger.info("Stale element found retrying:" + attempts);
                }
            }
        }

protected WebElement waitForElementToBeClickableBy(By by) {
        WebDriverWait wait = new WebDriverWait(getDriver(), 10);
        return wait.until(ExpectedConditions.elementToBeClickable(by));
    }

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

Ракеш Сингх Чохан
источник
-4

Возможно, он был добавлен совсем недавно, но в других ответах не упоминается функция неявного ожидания Selenium, которая выполняет все вышеперечисленное за вас и встроена в Selenium.

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

Это повторит попытку findElement() вызовы, пока элемент не будет найден, или в течение 10 секунд.

Источник - http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp

Lockan
источник
2
Это решение не предотвращает StaleElementReferenceException
MrSpock
1
Чтобы избежать путаницы в версиях, даже в последней версии Selenium implicitlyWait () НЕ предотвращает StaleElementReferenceException. Я использую метод, который вызывает цикл со сном до успеха или фиксированного счета.
Ангсуман Чакраборти