随机“元素不再附加到DOM”StaleElementReferenceException
我希望这只是我,但seleniumWebdriver似乎是一个完整的噩梦。 Chrome浏览器驱动程序目前无法使用,其他驱动程序是相当不可靠的,或者看起来如此。 我正在和许多问题作斗争,但是这里是一个。
随机地,我的testing将失败与一个
"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驱动程序发生这种情况。 我可以防止这种情况的唯一方法是在发生exception之前将实际的调用添加到Thread.sleep
。 虽然这是一个糟糕的解决方法,所以我希望有人可以指出我的错误,这将使这一切更好。
是的,如果你遇到StaleElementReferenceExceptions问题,那是因为你的testing写得不好。 这是一个竞争条件。 考虑以下情况:
WebElement element = driver.findElement(By.id("foo")); // DOM changes - page is refreshed, or element is removed and re-added element.click();
现在,在点击元素时,元素引用不再有效。 WebDriver几乎不可能对所有可能发生这种情况的情况进行猜测 – 所以它会抛出双手并给予您控制权,作为testing/应用程序作者应该确切地知道可能会发生什么或不会发生什么。 你想要做的是明确地等待,直到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)
根据我的经验,明确的等待总是比较可靠的。
我已经能够使用这样的方法取得一些成功:
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对于抛出这个exception可能会比较挑剔 – 有时我会得到它,有时候我不会。 或者可能是DOM真的在改变。
所以我不完全同意上面的答案,这必然表明一个不好的testing。 我已经把它放在了我没有任何交互的新页面上。 我认为,DOM是如何performance出来的,或者WebDriver认为是陈旧的。
有时AJAX更新中途出现此错误。 水豚似乎是非常聪明的等待DOM的变化(请参阅为什么wait_until从水豚中删除 ),但默认等待2秒的时间是不够的,在我的情况下。 在_spec_helper.rb_改变例如
Capybara.default_wait_time = 5
我今天面对同样的问题,并组成一个包装类,它在每个方法之前检查元素引用是否仍然有效。 我的解决scheme来回顾元素是非常简单的,所以我想我只是分享它。
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); }
你看到我“定位”,或者更确切地说,将元素保存在全局jsvariables中,并在需要时检索元素。 如果页面重新加载,则此引用将不再起作用。 但只要只是改变了厄运的参考依然存在。 在大多数情况下,这应该做好这项工作。
也避免了重新search元素。
约翰
当我尝试将send_keys发送到一个searchinput框时,发生了一些事情 – 根据input的内容,它有自动更新。正如Eero所提到的,如果您的元素在input元素中键入文本时更新了某些Ajax,则会发生这种情况。 解决方法是一次发送一个字符,然后再次searchinput元素 。 (如下所示的ruby)
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
添加到@ jarib的答案,我已经做了几个扩展方法,帮助消除竞争条件。
这是我的设置:
我有一个类叫“Driver.cs”。 它包含一个完整的驱动程序扩展方法和其他有用的静态函数的静态类。
对于我通常需要检索的元素,我创build了如下所示的扩展方法:
public static IWebElement SpecificElementToGet(this IWebDriver driver) { return driver.FindElement(By.SomeSelector("SelectorText")); }
这允许你从代码中检索任何testing类的元素:
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中复制的)。 该代码可以用来避免陈旧的元素exception,如下所示:
MyTestDriver.WaitForDisplayed(driver.SpecificElementToGet,5);
上面的代码将调用driver.SpecificElementToGet().Displayed
直到driver.SpecificElementToGet()
抛出exception,并且.Displayed
计算结果为true
,5秒没有通过。 5秒后,testing将失败。
另一方面,等待一个元素不存在,你可以用同样的方法使用下面的函数:
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); } }
我有同样的问题,我的是由一个旧的selenium版本造成的。 由于开发环境,我无法更新到新版本。 问题是由HTMLUnitWebElement.switchFocusToThisIfNeeded()引起的。 当您导航到新页面时,可能会发生您在旧页面上单击的元素是oldActiveElement
(请参见下文)。 selenium试图从旧元素中获取上下文,并失败。 这就是为什么他们在未来的版本中build立了一个try catch。
来自selenium-htmlunit-driver version <2.23.0的代码:
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(); } }
来自selenium-htmlunit-driver版本的代码> = 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()
作为例子。
在Java 8中,您可以使用非常简单的方法 :
private Object retryUntilAttached(Supplier<Object> callable) { try { return callable.get(); } catch (StaleElementReferenceException e) { log.warn("\tTrying once again"); return retryUntilAttached(callable); } }
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.