Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/377.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java “随机”;元素不再附加到DOM;StaleElementReferenceException_Java_Selenium Webdriver_Webdriver_Automated Tests - Fatal编程技术网

Java “随机”;元素不再附加到DOM;StaleElementReferenceException

Java “随机”;元素不再附加到DOM;StaleElementReferenceException,java,selenium-webdriver,webdriver,automated-tests,Java,Selenium Webdriver,Webdriver,Automated Tests,我希望只有我一个人,但SeleniumWebDriver似乎是一场彻头彻尾的噩梦。ChromeWebDriver目前无法使用,而其他驱动程序相当不可靠,或者看起来是这样。我正在与许多问题作斗争,但这里有一个问题 随机地,我的测试会以一个错误而失败 "org.openqa.selenium.StaleElementReferenceException: Element is no longer attached to the DOM System info: os.name: 'Wind

我希望只有我一个人,但SeleniumWebDriver似乎是一场彻头彻尾的噩梦。ChromeWebDriver目前无法使用,而其他驱动程序相当不可靠,或者看起来是这样。我正在与许多问题作斗争,但这里有一个问题

随机地,我的测试会以一个错误而失败

"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
的实际调用。不过,这是一个糟糕的解决方法,因此我希望有人能指出我的一个错误,这将使情况变得更好。

是的,如果您遇到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);
        }
    };
}

但根据我的经验,显式等待总是更可靠。

我已经成功地使用了这样一种方法:

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.
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认为过时的方面都存在一些缺陷。

我今天也面临同样的问题,并创建了一个包装类,在每个方法之前检查元素引用是否仍然有效。我检索元素的解决方案非常简单,所以我想我应该分享一下

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变量中,并在需要时检索元素。如果重新加载页面,此引用将不再有效。但只要只是对厄运进行了修改,参考就不会改变。在大多数情况下,这应该能起到作用

它还避免了重新搜索元素


John

我有时在AJAX更新进行到一半时会遇到这个错误。在等待DOM更改方面,Capybara似乎非常聪明(请参阅),但在我的例子中,默认的2秒等待时间根本不够。在_spec_helper.rb中更改,例如

Capybara.default_max_wait_time = 5

在Java 8中,您可以使用非常简单的方法:

private Object retryUntilAttached(Supplier<Object> callable) {
    try {
        return callable.get();
    } catch (StaleElementReferenceException e) {
        log.warn("\tTrying once again");
        return retryUntilAttached(callable);
    }
}
private Object retryuntilatached(供应商可调用){
试一试{
返回callable.get();
}捕获(StaleElementReferenceException e){
log.warn(“\t再次尝试”);
return retryuntilatached(可调用);
}
}

当我试图将搜索键发送到搜索输入框时,我碰巧遇到了这种情况,搜索输入框具有自动更新功能,具体取决于您键入的内容。正如Eero所提到的,如果在输入元素中键入文本时,元素执行了一些Ajax更新,则可能会发生这种情况。解决方案是一次发送一个字符,然后再次搜索输入元素。(如下图所示的红宝石)


为了补充@jarib的答案,我做了几个扩展方法,帮助消除竞争条件

以下是我的设置:

我有一个叫做“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);
    }
}
上述代码将调用
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);
        }
}
public static void WaitForNotPresent(Func getWebElement,int timeOut){
for(int second=0;second++){
if(秒>=超时)Assert.Fail(“超时”);
尝试
{
如果(!getWebElement().显示)中断;
}
catch(ElementNotVisibleException){break;}
catch(NoSuchElementException){break;}
catch(StaleElementReferenceException){break;}
捕获(例外)
{ }
睡眠(1000);
}
}

我也有同样的问题,我的问题是由旧的selenium版本引起的。由于开发环境的原因,我无法更新到较新的版本。该问题是由HTMLUnitWebElement.SwitchFocusToThisFneed()引起的。当您导航到新页面时,您在旧页面上单击的元素可能是
oldActiveElement
(见下文)。Selenium尝试从旧元素获取上下文,但失败。这就是为什么他们在未来的版本中构建了一个try-catch

来自selenium htmlunit驱动程序版本<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驱动程序版本>=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或更新版本的情况下,您只需给出页面焦点上的任何元素即可。我刚刚使用了
元素。例如,单击()

我想我找到了co
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);
        }
}
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();
    }
}
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();
}
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")));
}
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;
    }
}
<aspectj>
    <aspects>
        <aspect name="path.to.your.aspects.WebElementAspect"/>
    </aspects>
</aspectj>
<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>
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
}
new WebDriverWait(driver,10).until(ExpectedConditions.presenceOfAllElementsLocatedBy("Your object property"));
driver.findElement("Your object property").click();//or anyother action