如何避免硒中的“ StaleElementReferenceException”?


85

我正在使用Java实现许多Selenium测试。有时,我的测试由于导致失败StaleElementReferenceException。您能否提出一些使测试更稳定的方法?

Answers:


89

如果页面上发生的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;
}

1
哇!这正是我所需要的。谢谢!
SpartaSixZero 2014年

1
也可以通过使用不同的元素引用来固定它。
Ripon Al Wasim 2015年

@jspcal,这对我来说就像是一种魅力!非常感谢!
Anthony Okoth,

如果以上方法不能解决问题,那么更新为最新的chromedriver就是解决问题的方法。
Vdex

5
这在很多方面都是可怕的。它确实解决了我当前的问题,所以谢谢。
软件工程师

66

我间歇性地遇到这个问题。我不知道,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

19

通常,这是由于DOM正在更新,并且您尝试访问已更新/新元素-但DOM已刷新,因此它是无效的引用。

通过首先对元素进行显式等待以确保更新完成,然后再次获取对元素的新引用来解决此问题。

这是一些伪代码来说明(改编自我用于 自我此问题的):

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.

希望这可以帮助!


14

肯尼的解决方案很好,但是可以用更优雅的方式编写

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库,它可以处理这类事情以及更多。(代替元素引用,它处理代理,因此您不必处理陈旧的元素,这可能非常困难)。硒化物


免责声明:我只是硒硒化物的使用者,与它的发展无关
cocorossello

您的第二个解决方案将起作用,因为单击元素时该元素会过时,而不是找到时就会失效。
Rajagopalan

使用硒化物可以避免此问题,非常容易。由于这个问题以及对于简单用户来说是低级API的事实,硒并不打算单独使用
cocorossello

我完全意识到这一点。我使用的是WATIR,它是Ruby Selenium Binding的包装,WATIR自动解决所有这些问题(对于实例陈旧元素)。我正在寻找与Java绑定等效的东西,我找到了Selenide,但是我不知道如何更改selenide中的隐式等待和显式等待。你能告诉我怎么做吗?还是您可以提供给我参考的任何材料?您对FluentLenium有何看法?
Rajagopalan

1
人们应该知道,OP选择的答案可以追溯到2012年。在过去7年中,许多事情已经发生了变化。这个答案对于2019
。– hfontanez '19

10

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()。实际上,这会等待并检查相关元素在指定的超时时间内是否已刷新,并另外等待该元素变为可单击状态。

请查看刷新方法文档


3

在我的项目中,我引入了StableWebElement的概念。它是WebElement的包装,它可以检测element是否为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);
} 

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

肯尼(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;
                    }
                });

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

使用C#对我有效(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时,它可能已经离开了DOM。
尝试使用Javascript完成整个操作:

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

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

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

0

当我们尝试访问的元素出现时,通常会出现StaleElementReferenceException,但是其他元素可能会影响我们感兴趣的元素的位置,因此,当我们尝试单击或getText或尝试对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


2
该解决方案不会阻止StaleElementReferenceException
MrSpock 2016年

1
只是为了消除版本上的任何混乱,即使在最新版本的Selenium中,implicitlyWait()也不会阻止StaleElementReferenceException。我使用一种在睡眠中循环调用的方法,直到成功或固定计数为止。
Angsuman Chakraborty
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.