2011-04-18 65 views
122

我希望这只是我,但硒Webdriver似乎是一个完整的噩梦。 Chrome浏览器驱动程序目前无法使用,其他驱动程序相当不可靠,或者看起来如此。我正在解决许多问题,但这里是一个问题。随机“元素不再附加到DOM”StaleElementReferenceException

随机,我的测试将失败,

"org.openqa.selenium.StaleElementReferenceException: Element is no longer attached 
to the DOM  
System info: os.name: 'Windows 7', os.arch: 'amd64', 
os.version: '6.1', java.version: '1.6.0_23'" 

我使用webdriver的版本2.0b3。我已经看到FF和IE驱动程序发生这种情况。我可以防止的唯一方法是在发生异常之前添加对Thread.sleep的实际调用。尽管这是一个糟糕的解决方法,所以我希望有人能指出我的错误,这会让这一切变得更好。

+20

希望17k的意见表明,它不只是你;)这已成为最令人沮丧的Selenium例外。 – 2013-06-12 04:45:49

+3

48k吧!我有同样的问题... – Gal 2015-06-25 04:22:09

+3

我发现硒是纯粹的,完整的垃圾.... – 2015-10-29 20:44:05

回答

111

是的,如果你遇到StaleElementReferenceExceptions问题,那是因为你的测试写得不好。这是一个竞赛条件。考虑以下情况:

WebElement element = driver.findElement(By.id("foo")); 
// DOM changes - page is refreshed, or element is removed and re-added 
element.click(); 

现在,在点击元素的位置,元素引用不再有效。 WebDriver几乎不可能对所有可能发生的情况进行猜测 - 所以它会抛出双手并给予您控制权,因为测试/应用程序作者应该知道可能会发生什么或不会发生什么。你想要做的是明确地等待,直到DOM处于你知道事情不会改变的状态。例如,使用WebDriverWait等待存在一个特定的元素:

// times out after 5 seconds 
WebDriverWait wait = new WebDriverWait(driver, 5); 

// while the following loop runs, the DOM changes - 
// page is refreshed, or element is removed and re-added 
wait.until(presenceOfElementLocated(By.id("container-element")));   

// now we're good - let's click the element 
driver.findElement(By.id("foo")).click(); 

的presenceOfElementLocated()方法会是这个样子:

private static Function<WebDriver,WebElement> presenceOfElementLocated(final By locator) { 
    return new Function<WebDriver, WebElement>() { 
     @Override 
     public WebElement apply(WebDriver driver) { 
      return driver.findElement(locator); 
     } 
    }; 
} 

你说得对目前的Chrome驱动程序非常不稳定,你会很高兴听到Selenium主干有一个重写的Chrome驱动程序,其中大部分实现都是由Chromium开发人员完成的,作为其树的一部分。

PS。另一方面,不是在上面的例子中明确等一样,可以使隐性等待 - 这样的webdriver会一直循环,直到指定的超时等待元素成为本:

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

以我的经验,虽然,明确等待总是更可靠。

+2

我是否正确地说,不再可能将元素读入变量并重新使用它们?因为我有一个巨大的干式和动态WATiR DSL,它依赖于传递元素,我试图移植到webdriver,但我遇到了同样的问题。本质上,我将不得不添加代码来重新读取模块中的所有元素,以改变DOM的每个测试步骤... – kinofrost 2011-06-07 14:53:45

+0

嗨。请问在这个例子中函数是什么类型?我似乎无法找到它......谢谢! – Hannibal 2011-09-07 13:27:52

+1

@Hannibal:'com.google.common.base.Function ',由[Guava]提供(http://code.google.com/p/guava-libraries/)。 – Stephan202 2011-10-08 21:50:52

-5
FirefoxDriver _driver = new FirefoxDriver(); 

// create webdriverwait 
WebDriverWait wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(10)); 

// create flag/checker 
bool result = false; 

// wait for the element. 
IWebElement elem = wait.Until(x => x.FindElement(By.Id("Element_ID"))); 

do 
{ 
    try 
    { 
     // let the driver look for the element again. 
     elem = _driver.FindElement(By.Id("Element_ID")); 

     // do your actions. 
     elem.SendKeys("text"); 

     // it will throw an exception if the element is not in the dom or not 
     // found but if it didn't, our result will be changed to true. 
     result = !result; 
    } 
    catch (Exception) { } 
} while (result != true); // this will continue to look for the element until 
          // it ends throwing exception. 
+0

我刚才在判断出它后添加了它。对不起,这是我第一次发布。只是想帮助。如果您觉得它有用,请将它分享给其他人:) – 2012-06-22 02:55:23

+0

欢迎使用stackoverflow!最好为示例代码提供简短说明以提高发布准确性:) – 2012-10-21 12:54:19

+0

运行上面的代码可能会永久停留在循环中,例如,如果该页面上存在服务器错误。 – munch 2012-11-12 15:37:43

9

我已经能够使用的方法类似这样的一些成功:

WebElement getStaleElemById(String id) { 
    try { 
     return driver.findElement(By.id(id)); 
    } catch (StaleElementReferenceException e) { 
     System.out.println("Attempting to recover from StaleElementReferenceException ..."); 
     return getStaleElemById(id); 
    } 
} 

是的,它只是不断轮询的元素,直到它不再被认为是过时的(新鲜?)。并没有真正解决问题的根源,但我发现WebDriver对于抛出这个异常可能会比较挑剔 - 有时我会得到它,有时我不会。或者可能是DOM真的在改变。

所以我不完全同意上面的答案,这必然表明一个写得不好的测试。我已经把它放在了我没有任何交互的新页面上。我认为DOM代表的方式或者WebDriver认为陈旧的方式都会有一些薄弱。

+6

你在这段代码中有一个错误,你不应该在没有某种帽子的情况下递归地调用这个方法,否则你会打击你的堆栈。 – Harry 2014-08-21 00:33:55

+2

我认为最好添加一个计数器或其他东西,所以当我们重复出现错误时,我们可以抛出错误。否则,如果实际上有错误,最终会出现在循环中 – Sudara 2015-03-18 07:33:57

0

我今天面对同样的问题,并组成一个包装类,它在每个方法之前检查元素引用是否仍然有效。我的解决方案来回顾元素非常简单,所以我想我只是分享它。

private void setElementLocator() 
{ 
    this.locatorVariable = "selenium_" + DateTimeMethods.GetTime().ToString(); 
    ((IJavaScriptExecutor)this.driver).ExecuteScript(locatorVariable + " = arguments[0];", this.element); 
} 

private void RetrieveElement() 
{ 
    this.element = (IWebElement)((IJavaScriptExecutor)this.driver).ExecuteScript("return " + locatorVariable); 
} 

你看我“定位”或者说保存元素在全局变量的JS,如果需要检索的元素。如果页面重新加载,则此引用将不再起作用。但只要只是改变参考保留的厄运。这应该在大多数情况下完成这项工作。

此外它避免了重新搜索元素。

约翰

6

有时,当AJAX更新中途出现此错误。水豚看起来在等待DOM变化方面非常聪明(见Why wait_until was removed from Capybara ),但在我的情况下,缺省的2秒等待时间是不够的。在_spec_helper.rb_中用例如

Capybara.default_wait_time = 5 
+1

这也解决了我的问题:我得到一个StaleElementReferenceError并增加了Capybara.default_max_wait_time解决了这个问题。 – brendan 2015-09-04 18:13:34

-1

在Java 8可以使用非常simple method为:

private Object retryUntilAttached(Supplier<Object> callable) { 
    try { 
     return callable.get(); 
    } catch (StaleElementReferenceException e) { 
     log.warn("\tTrying once again"); 
     return retryUntilAttached(callable); 
    } 
} 
0

试图send_keys一个搜索输入框的时候就发生在我身上 - 这取决于你在键入具有自动更新为。由Eero提到,如果您的元素在输入元素内输入文本时更新了某些Ajax,则会发生这种情况。解决方案是一次发送一个字符,然后再次搜索输入元素。 (红宝石如下例)

def send_keys_eachchar(webdriver, elem_locator, text_to_send) 
    text_to_send.each_char do |char| 
    input_elem = webdriver.find_element(elem_locator) 
    input_elem.send_keys(char) 
    end 
end 
0

要添加到@雅立的回答,我已经提出了一些扩展方法有助于消除竞争条件。

这是我的设置:

我有一个叫做“Driver.cs”的类。它包含一个充满驱动程序扩展方法和其他有用静态函数的静态类。

对于元素我通常需要检索,我创建像一个扩展方法如下:

public static IWebElement SpecificElementToGet(this IWebDriver driver) { 
    return driver.FindElement(By.SomeSelector("SelectorText")); 
} 

这可以让你从任何测试类检索元素与代码:

driver.SpecificElementToGet(); 

现在,如果这导致StaleElementReferenceException,我在我的驱动程序类中有以下静态方法:

public static void WaitForDisplayed(Func<IWebElement> getWebElement, int timeOut) 
{ 
    for (int second = 0; ; second++) 
    { 
     if (second >= timeOut) Assert.Fail("timeout"); 
     try 
     { 
      if (getWebElement().Displayed) break; 
     } 
     catch (Exception) 
     { } 
     Thread.Sleep(1000); 
    } 
} 

该函数的第一个参数是返回IWebElement对象的任何函数。第二个参数是以秒为单位的超时(超时代码是从Selenium IDE for FireFox中复制的)。该代码可以用于避免陈旧元件例外以下方式:

MyTestDriver.WaitForDisplayed(driver.SpecificElementToGet,5); 

上面的代码将调用driver.SpecificElementToGet().Displayed直到driver.SpecificElementToGet()没有抛出异常和.Displayed评估为true 5秒没有通过。 5秒后,测试将失败。

在另一面,等待一个元素不存在,你可以使用下面的函数一样:

public static void WaitForNotPresent(Func<IWebElement> getWebElement, int timeOut) { 
    for (int second = 0;; second++) { 
     if (second >= timeOut) Assert.Fail("timeout"); 
      try 
      { 
       if (!getWebElement().Displayed) break; 
      } 
      catch (ElementNotVisibleException) { break; } 
      catch (NoSuchElementException) { break; } 
      catch (StaleElementReferenceException) { break; } 
      catch (Exception) 
      { } 
      Thread.Sleep(1000); 
     } 
} 
1

我有同样的问题,而我的是一个古老的硒版本引起的。由于开发环境,我无法更新到新版本。该问题是由HTMLUnitWebElement.switchFocusToThisIfNeeded()引起的。当您导航到新页面时,可能会发生在旧页面上单击的元素为oldActiveElement(请参见下文)。硒试图从旧元素中获取上下文并失败。这就是为什么他们在未来的版本中建立了一个try catch。从硒的HtmlUnit驱动器版本< 2.23.0

代码:从硒的HtmlUnit驱动器版本

private void switchFocusToThisIfNeeded() { 
    HtmlUnitWebElement oldActiveElement = 
     ((HtmlUnitWebElement)parent.switchTo().activeElement()); 

    boolean jsEnabled = parent.isJavascriptEnabled(); 
    boolean oldActiveEqualsCurrent = oldActiveElement.equals(this); 
    boolean isBody = oldActiveElement.getTagName().toLowerCase().equals("body"); 
    if (jsEnabled && 
     !oldActiveEqualsCurrent && 
     !isBody) { 
     oldActiveElement.element.blur(); 
     element.focus(); 
    } 
} 

代码> = 2.23.0:

private void switchFocusToThisIfNeeded() { 
    HtmlUnitWebElement oldActiveElement = 
     ((HtmlUnitWebElement)parent.switchTo().activeElement()); 

    boolean jsEnabled = parent.isJavascriptEnabled(); 
    boolean oldActiveEqualsCurrent = oldActiveElement.equals(this); 
    try { 
     boolean isBody = oldActiveElement.getTagName().toLowerCase().equals("body"); 
     if (jsEnabled && 
      !oldActiveEqualsCurrent && 
      !isBody) { 
     oldActiveElement.element.blur(); 
     } 
    } catch (StaleElementReferenceException ex) { 
     // old element has gone, do nothing 
    } 
    element.focus(); 
} 

而不更新至2.23。 0或更新,你可以给页面焦点上的任何元素。例如,我刚刚使用了element.click()

+1

哇...这是一个非常晦涩的发现,不错的工作..我现在想知道如果其他驱动程序(如chromedriver)也有类似的问题 – kevlarr 2018-01-22 22:14:31

相关问题