Java 如何避免;StaleElementReferenceException“;硒?

Java 如何避免;StaleElementReferenceException“;硒?,java,selenium-webdriver,Java,Selenium Webdriver,我正在使用Java实现许多Selenium测试。有时,我的测试会由于StaleElementReferenceException而失败。您能建议一些方法使测试更稳定吗?如果页面上发生的DOM操作暂时导致元素无法访问,则可能会发生这种情况。考虑到这些情况,您可以尝试在循环中多次访问该元素,然后最终引发异常 尝试: public boolean retryingFindClick(By){ 布尔结果=假; int=0; 而(尝试次数ExpectedConditions.ElementIsVisibl

我正在使用Java实现许多Selenium测试。有时,我的测试会由于
StaleElementReferenceException
而失败。您能建议一些方法使测试更稳定吗?

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

尝试:

public boolean retryingFindClick(By){
布尔结果=假;
int=0;
而(尝试次数<2次){
试一试{
driver.findElement(by).click();
结果=真;
打破
}捕获(StaleElementException e){
}
尝试++;
}
返回结果;
}

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

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

下面是一些psuedo代码来说明(改编自一些我用于的C#代码,确切地说这个问题):


希望这有帮助

我间歇性地有这个问题。在我不知道的情况下,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;
        }
    });
newwebdriverwait(驱动程序,超时)
.忽略(StaleElementReferenceException.class)
.until(新谓词(){
@凌驾
公共布尔应用(@Nullable WebDriver){
driver.findElement(By.id(“checkoutLink”))。单击();
返回true;
}
});

此代码将持续尝试单击链接,忽略StaleElementReferenceExceptions,直到单击成功或达到超时。我喜欢这个解决方案,因为它省去了您编写任何重试逻辑的麻烦,并且只使用WebDriver的内置结构。

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

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

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

来源-

C中的解决方案是:

助手类:

内部类驱动程序
{
专用IWebDriver驱动程序{get;set;}
私有WebDriverWait等待{get;set;}
公共驱动器LPER(字符串驱动器URL,int timeoutin秒)
{
驱动程序=新的ChromeDriver();
Driver.Url=driverUrl;
Wait=newwebdriverwait(驱动程序,TimeSpan.FromSeconds(timeoutInSeconds));
}
内部布尔ClickElement(字符串CSS选择器)
{
//找到元素
IWebElement=Wait.Until(d=>ExpectedConditions.ElementIsVisible(By.CssSelector(CssSelector)))(驱动程序);
返回Wait.Until(c=>ClickElement(element,cssSelector));
}
私有布尔ClickElement(IWebElement元素、字符串CSS选择器)
{
尝试
{
//检查元素是否仍包含在dom中
//如果元素更改了一个值,则会引发OpenQA.Selenium.StaleElementReferenceException。
bool isDisplayed=元素。已显示;
元素。单击();
返回true;
}
捕获(StaleElementReferenceException)
{
//等待元素再次可见
element=Wait.Until(d=>ExpectedConditions.ElementIsVisible(By.CssSelector(CssSelector)))(驱动程序);
返回ClickElement(element,CSS选择器);
}
捕获(例外)
{
返回false;
}
}
}
调用:

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

类似地,也可用于Java。

发生
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.refresh()
链接Selenium自己的
ExpectedConditions
。这实际上会等待并检查相关元素是否已在指定的超时期间刷新,并额外等待该元素成为可单击的


看看。

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

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

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

这对我使用C很有效#

public Boolean RetryingFindClick(IWebElement webElement)
{
布尔结果=假;
int=0;
而(尝试次数<2次)
{
尝试
{
new WebDriverWait(driver, timeout).ignoring(StaleElementReferenceException.class).until(ExpectedConditions.elementToBeClickable(By.id("checkoutLink")));
driver.findElement(By.id("checkoutLink")).click();
public static IStableWebElement FindStableElement(this ISearchContext context, By by)
{
    var element = context.FindElement(by);
    return new StableWebElement(context, element, by, SearchApproachType.First);
} 
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;
    }
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;
                    }
                });
driver.executeScript("document.querySelector('#my_id').click()") 
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
    }
}
openForm(someXpath);
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;
        }
    }
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))));
    }
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()
// 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.
    }
  }
}
 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));
    }