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

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

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

4b9b3361

Ответ 1

Это может произойти, если операция 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;
}

Ответ 2

У меня возникала эта проблема с перерывами. Без ведома меня 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.

Ответ 3

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

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

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

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.

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

Ответ 4

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

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, которая обрабатывает подобные вещи и многое другое. (вместо ссылок на элементы он обрабатывает прокси, поэтому вам никогда не придется иметь дело с устаревшими элементами, что может быть довольно сложно). селенида

Ответ 5

Причина возникновения 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(). Это фактически ожидает и проверяет, был ли соответствующий элемент обновлен в течение указанного времени ожидания, и дополнительно ожидает, что элемент станет активным.

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

Ответ 6

В моем проекте я представил понятие StableWebElement. Это оболочка для WebElement, которая способна определить, является ли элемент Stale и найти новую ссылку на исходный элемент. Я добавил вспомогательные методы для поиска элементов, которые возвращают 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);
} 

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

Ответ 7

Решение в С# было бы:

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

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.

Ответ 8

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

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;
                    }
                });

Ответ 9

Это работает для меня (работает на 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;
    }

Ответ 10

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

driver.executeScript("document.querySelector('#my_id').click()") 

Ответ 11

Попробуйте это

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
    }
}

Ответ 12

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

.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))));
    }

Ответ 13

Возможна потенциальная проблема, которая приводит к исключению 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()

Ответ 14

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

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

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

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