Как избежать "StaleElementReferenceException" в селене?


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

9   51  

9 ответов:

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

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

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

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

вот некоторый код psuedo для иллюстрации (адаптированный из некоторого кода 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.

Надежда это помогает!

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

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

Почему 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();
}

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

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

решение в 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.

в моем проекте я ввел понятие 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

это работает для меня (100% работает) с помощью C#

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

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

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

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

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