【问题标题】:How to avoid "StaleElementReferenceException" in Selenium?如何避免 Selenium 中的“StaleElementReferenceException”?
【发布时间】:2012-10-19 04:45:30
【问题描述】:

我正在使用 Java 实现很多 Selenium 测试 - 有时,我的测试由于 StaleElementReferenceException 而失败。

您能否提出一些使测试更稳定的方法?

【问题讨论】:

  • 我推荐使用这个库,它解决了间歇性的 StaleElementReference 异常,并为 Selenium 页面对象模型和页面工厂功能带来了一些增强:github.com/fslev/selenium-jutils#retry-on-error
  • @SlevFlorin 该库使用 PageFactory,编写它的 Selenium 创建者说不要使用。

标签: java selenium-webdriver testing


【解决方案1】:

如果页面上发生的 DOM 操作暂时导致元素不可访问,则可能会发生这种情况。为了应对这些情况,您可以尝试在循环中多次访问该元素,然后最终引发异常。

试试this excellent solution from 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;
}

【讨论】:

  • 也可以通过使用不同的元素引用来修复。
  • 如果上面的方法都没有解决,更新到最新的chromedriver就可以解决了。
  • @Vdex ChromeDriver的版本与此问题无关。
【解决方案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】:

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

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 库,它可以处理这类事情等等。 (它处理代理而不是元素引用,因此您永远不必处理陈旧的元素,这可能非常困难)。 Selenide

【讨论】:

  • 免责声明:我只是一个快乐的硒用户,与它的开发无关
  • 你的第二个解决方案会起作用,因为当你点击它而不是找到它时元素会过时。
  • 使用硒化物来避免这个问题,容易得多。 Selenium 不适合单独使用,因为这个问题以及对于简单用户来说是一个低级 API 的事实
  • 我非常清楚这一点。我正在使用 WATIR,它是 Ruby Selenium Binding 的包装器,WATIR 会自动处理所有这些问题(对于实例陈旧元素)。我正在寻找 Java 绑定中的等价物,我找到了 Selenide,但我不知道如何更改 selenide 中的隐式等待和显式等待。你能告诉我该怎么做吗?或者有什么材料可以提供给我参考吗?您对 FluentLenium 有何看法?
  • 人们应该知道,OP 选择的答案可以追溯到 2012 年。在过去的 7 年里,很多事情都发生了变化。这个答案在 2019 年更正确。
【解决方案4】:

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

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

这里有一些伪代码来说明(改编自我用于EXACTLY这个问题的一些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.

希望这会有所帮助!

【讨论】:

    【解决方案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() 的“链接”。这实际上会等待并检查相关元素是否在指定的超时期间被刷新,另外还等待元素变为可点击。

    看看documentation for the refreshed method

    【讨论】:

      【解决方案6】:

      在我的项目中,我引入了 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

      【讨论】:

        【解决方案7】:

        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。

        【讨论】:

          【解决方案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;
                              }
                          });
          

          【讨论】:

          • 这不能回答问题。
          • 我虽然做到了。
          • 忽略StaleElementReferenceException 不会让它消失。这只会超时,因为一旦你得到那个异常,它不会在没有一些新的动作(比如重新获取元素等)的情况下消失。
          • 如何重新获取 selenium 中的元素?
          • 重新获取我的意思是再次driver.findElement(),存储该元素,然后再做需要做的事情。您可能想花一些时间阅读一些文章,了解为什么会抛出 StaleElementReferenceException、什么是陈旧元素以及如何避免它。
          【解决方案9】:

          我找到了解决方案here。在我的情况下,如果离开当前窗口、选项卡或页面并再次返回,元素将变得不可访问。

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

          【讨论】:

            【解决方案10】:

            干净的findByAndroidId 方法可以优雅地处理StaleElementReference

            这在很大程度上基于jspcal's answer,但我必须修改该答案以使其与我们的设置完美配合,因此我想在此处添加它以防对其他人有所帮助。如果这个答案对你有帮助,请点赞jspcal's answer

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

            【讨论】:

              【解决方案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
                  }
              }
              

              【讨论】:

              • 这段代码很容易陷入无限循环。您应该只实现一个计数器或超时来避免这种情况。此外,您不需要硬编码 3 秒睡眠......这是浪费的,因为页面没有重新加载,所以您可以立即重试。
              【解决方案12】:

              可能存在导致 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()
              

              【讨论】:

              • 如果在Java里也一样,那就用Java写吧。这就是 OP 所要求的。
              【解决方案13】:

              通常当我们尝试访问的元素出现时 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));
                  }
              

              在上面的代码中,我首先尝试等待,然后在发生异常时单击元素,然后我捕获它并尝试循环它,因为可能仍然无法加载所有元素,并且可能再次发生异常。

              【讨论】:

              • 您对陈旧元素的定义不正确。您可能应该花几分钟时间搜索一下定义。
              【解决方案14】:

              这适用于我使用 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;
                  }
              

              【讨论】:

              • 它与上述解决方案有何不同?什么是 100% 的计算机科学?
              • @cruisepandey 我猜这是相同的实现,但在 C# 而不是 Java 中 - 虽然几乎相同?
              【解决方案15】:

              问题在于,当您将元素从 Javascript 传递到 Java 时,它可能已经离开了 DOM。
              尝试用 Javascript 做所有事情:

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

              【讨论】:

              • 这与使用 Java 方法并没有什么不同,除了你没有用 JS 代码获得任何智能感知,你通过在 Java 中使用 JS 带来了一个全新的问题等。
              • 其实完全不同。它通过立即获取该 dom 元素来避免过时的元素问题。如果你错过了智能感知,你应该切换到更有意义的 Javascript。
              • 不,它实际上并没有变得更快。如果不出意外,它可能会稍微慢一些,因为必须解释 JS 而不是仅使用 driver.findElement(By.id()).click();。最终结果完全一样。
              • 不,恰恰相反。浏览器使用 Javascript,而不是 Java。
              • 这个问题是关于 Java 的。您是在建议加载 Java 库,调用一个 Java 方法,该方法反过来解释 JS 代码,对其进行评估,然后一路返回,这比仅仅调用 Java 方法要快吗?我不这么认为。
              【解决方案16】:

              也许它是最近添加的,但其他答案没有提到 Selenium 的隐式等待功能,它为您完成上述所有操作,并且内置于 Selenium 中。

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

              这将重试 findElement() 调用,直到找到该元素,或持续 10 秒。

              来源 - http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp

              【讨论】:

              • 该解决方案不能防止 StaleElementReferenceException
              • 只是为了消除版本上的任何混淆,即使在最新版本的 Selenium 中,implicitlyWait() 也不会阻止 StaleElementReferenceException。我使用一种方法,它在睡眠中循环调用,直到成功或固定计数。
              猜你喜欢
              • 2017-02-23
              • 1970-01-01
              • 2016-02-14
              • 2019-04-02
              • 1970-01-01
              • 2021-01-02
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多