我希望它只是我,但Selenium Webdriver似乎是一场彻头彻尾的噩梦。 Chrome webdriver目前无法使用,而其他驱动程序则非常不可靠,或者看起来如此。我正在与许多问题作斗争,但这里有一个问题。
随机地,我的测试将以
失败"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
添加实际调用。这是一个糟糕的解决方法,所以我希望有人可以指出我的错误,这将使这一切变得更好。
答案 0 :(得分:115)
是的,如果您遇到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)
根据我的经验,明确等待总是更可靠。
答案 1 :(得分:8)
我已经能够使用这样的方法取得了一些成功:
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认为过时的方式都存在一些瑕疵。
答案 2 :(得分:8)
当AJAX更新处于中途时,我有时会收到此错误。 Capybara似乎非常聪明地等待DOM更改(参见Why wait_until was removed from Capybara ),但在我的情况下,默认等待时间2秒是不够的。改为_spec_helper.rb_,例如
Capybara.default_wait_time = 5
答案 3 :(得分:1)
我有同样的问题,我的是由旧的硒版本引起的。由于开发环境,我无法更新到更新的版本。问题是由HTMLUnitWebElement.switchFocusToThisIfNeeded()引起的。导航到新页面时,您在旧页面上单击的元素可能是oldActiveElement
(见下文)。 Selenium试图从旧元素获取上下文并失败。这就是他们在未来版本中构建try catch的原因。
来自selenium-htmlunit-driver版本的代码&lt; 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版本的代码&gt; = 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()
。
答案 4 :(得分: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变量中,并在需要时检索元素。如果页面重新加载,则此引用将不再起作用。但只要只对厄运做出改变,参考就会停留。在大多数情况下,这应该可以胜任。
它也避免了重新搜索元素。
约翰
答案 5 :(得分:0)
尝试将send_keys发送到搜索输入框时发生了这种情况 - 根据您输入的内容自动更新。如Eero所述,如果您的元素在您输入文本时更新了一些Ajax,则会发生这种情况输入元素。解决方案是一次发送一个字符并再次搜索输入元素。 (例如下面显示的红宝石)
ValueError: could not broadcast input array from shape (294,3) into shape (294)
答案 6 :(得分:0)
要添加@ jarib的答案,我已经制作了几种扩展方法,有助于消除竞争条件。
这是我的设置:
我有一个名为&#34; Driver.cs&#34;的课程。它包含一个充满驱动程序扩展方法的静态类和其他有用的静态函数。
对于我通常需要检索的元素,我创建了一个扩展方法,如下所示:
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);
}
}
答案 7 :(得分:0)
我认为我找到了处理StaleElementReferenceException的便捷方法。 通常,您必须为每个WebElement方法编写包装程序以重试操作,这令人沮丧并且浪费大量时间。
添加此代码
webDriverWait.until((webDriver1) -> (((JavascriptExecutor) webDriver).executeScript("return document.readyState").equals("complete")));
if ((Boolean) ((JavascriptExecutor) webDriver).executeScript("return window.jQuery != undefined")) {
webDriverWait.until((webDriver1) -> (((JavascriptExecutor) webDriver).executeScript("return jQuery.active == 0")));
}
在每个WebElement操作可以提高测试的稳定性之前,但您仍然可以不时获得StaleElementReferenceException。
这就是我想出的(使用AspectJ):
package path.to.your.aspects;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.remote.RemoteWebElement;
import org.openqa.selenium.support.pagefactory.DefaultElementLocator;
import org.openqa.selenium.support.pagefactory.internal.LocatingElementHandler;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
@Aspect
public class WebElementAspect {
private static final Logger LOG = LogManager.getLogger(WebElementAspect.class);
/**
* Get your WebDriver instance from some kind of manager
*/
private WebDriver webDriver = DriverManager.getWebDriver();
private WebDriverWait webDriverWait = new WebDriverWait(webDriver, 10);
/**
* This will intercept execution of all methods from WebElement interface
*/
@Pointcut("execution(* org.openqa.selenium.WebElement.*(..))")
public void webElementMethods() {}
/**
* @Around annotation means that you can insert additional logic
* before and after execution of the method
*/
@Around("webElementMethods()")
public Object webElementHandler(ProceedingJoinPoint joinPoint) throws Throwable {
/**
* Waiting until JavaScript and jQuery complete their stuff
*/
waitUntilPageIsLoaded();
/**
* Getting WebElement instance, method, arguments
*/
WebElement webElement = (WebElement) joinPoint.getThis();
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
Object[] args = joinPoint.getArgs();
/**
* Do some logging if you feel like it
*/
String methodName = method.getName();
if (methodName.contains("click")) {
LOG.info("Clicking on " + getBy(webElement));
} else if (methodName.contains("select")) {
LOG.info("Selecting from " + getBy(webElement));
} else if (methodName.contains("sendKeys")) {
LOG.info("Entering " + args[0].toString() + " into " + getBy(webElement));
}
try {
/**
* Executing WebElement method
*/
return joinPoint.proceed();
} catch (StaleElementReferenceException ex) {
LOG.debug("Intercepted StaleElementReferenceException");
/**
* Refreshing WebElement
* You can use implementation from this blog
* http://www.sahajamit.com/post/mystery-of-stale-element-reference-exception/
* but remove staleness check in the beginning (if(!isElementStale(elem))), because we already caught exception
* and it will result in an endless loop
*/
webElement = StaleElementUtil.refreshElement(webElement);
/**
* Executing method once again on the refreshed WebElement and returning result
*/
return method.invoke(webElement, args);
}
}
private void waitUntilPageIsLoaded() {
webDriverWait.until((webDriver1) -> (((JavascriptExecutor) webDriver).executeScript("return document.readyState").equals("complete")));
if ((Boolean) ((JavascriptExecutor) webDriver).executeScript("return window.jQuery != undefined")) {
webDriverWait.until((webDriver1) -> (((JavascriptExecutor) webDriver).executeScript("return jQuery.active == 0")));
}
}
private static String getBy(WebElement webElement) {
try {
if (webElement instanceof RemoteWebElement) {
try {
Field foundBy = webElement.getClass().getDeclaredField("foundBy");
foundBy.setAccessible(true);
return (String) foundBy.get(webElement);
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
} else {
LocatingElementHandler handler = (LocatingElementHandler) Proxy.getInvocationHandler(webElement);
Field locatorField = handler.getClass().getDeclaredField("locator");
locatorField.setAccessible(true);
DefaultElementLocator locator = (DefaultElementLocator) locatorField.get(handler);
Field byField = locator.getClass().getDeclaredField("by");
byField.setAccessible(true);
return byField.get(locator).toString();
}
} catch (IllegalAccessException | NoSuchFieldException e) {
e.printStackTrace();
}
return null;
}
}
要启用此方面,请创建文件
src\main\resources\META-INF\aop-ajc.xml
并写
<aspectj>
<aspects>
<aspect name="path.to.your.aspects.WebElementAspect"/>
</aspects>
</aspectj>
将此添加到您的pom.xml
<properties>
<aspectj.version>1.9.1</aspectj.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.0</version>
<configuration>
<argLine>
-javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
</argLine>
</configuration>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
</dependency>
</dependencies>
</plugin>
</build>
仅此而已。希望对您有所帮助。
答案 8 :(得分:0)
您可以通过使用显式等待来解决此问题,从而不必使用硬等待。
如果您使用一个属性获取所有元素并使用每个循环对其进行迭代,则可以像这样在循环内部使用wait
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:title="@string/general"
android:layout="@layout/fragment_settings">
<Preference
android:title="@string/about"
android:icon="@drawable/ic_information"
android:key="report_a_bug"
android:summary="Find out more about Brainixx"/>
</PreferenceCategory>
<PreferenceCategory
android:title="@string/brainixxapp"
android:layout="@layout/fragment_settings">
<Preference
android:title="@string/bug"
android:icon="@drawable/ic_bug"
android:key="report_a_bug"
android:summary="Hallo"/>
</PreferenceCategory>
或者对于单个元素,您可以使用以下代码
List<WebElement> elements = driver.findElements("Object property");
for(WebElement element:elements)
{
new WebDriverWait(driver,10).until(ExpectedConditions.presenceOfAllElementsLocatedBy("Object property"));
element.click();//or any other action
}
答案 9 :(得分:-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);
}
}
答案 10 :(得分:-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.