2011-04-18 67 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

相關問題