임의의 "요소가 더 이상 DOM에 첨부되지 않습니다"StaleElementReferenceException


143

나는 그것이 단지 나이기를 바라고 있지만 Selenium Webdriver는 완전한 악몽처럼 보입니다. Chrome 웹 드라이버는 현재 사용할 수 없으며 다른 드라이버는 상당히 신뢰할 수 없습니다. 나는 많은 문제와 싸우고 있지만 여기에 하나가 있습니다.

무작위로, 내 테스트는

"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'"

웹 드라이버 버전 2.0b3을 사용하고 있습니다. 나는 이것이 FF 및 IE 드라이버에서 발생하는 것을 보았습니다. 내가 이것을 막을 수있는 유일한 방법 Thread.sleep은 예외가 발생하기 전에 실제 호출을 추가하는 것 입니다. 그것은 좋지 않은 해결 방법이므로 누군가가 내 부분 에서이 모든 것을 더 좋게 만드는 오류를 지적 할 수 있기를 바랍니다.


26
바라건대 17k 뷰는 당신 만이 아니라는 것을 나타냅니다.) 이것은 가장 실망스러운 셀레늄 예외였습니다.
Mark Mayo

4
지금 48k! 나는 같은 문제가 있습니다 ...
Gal

3
나는 셀레늄이 순수하고 완전한 쓰레기라는 것을 알았습니다.
C Johnson

4
60k, 여전히 문제 :)
Pieter De Bie

내 경우에는from selenium.common.exceptions import NoSuchElementException
Cpt. Senkfuss

답변:


119

예, 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();

presentOfElementLocated () 메소드는 다음과 같습니다.

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 개발자가 트리의 일부로 수행 한 것을 알게되어 기쁩니다.

추신. 또는 위의 예와 같이 명시 적으로 대기하는 대신 암시 적 대기를 활성화 할 수 있습니다. 이렇게하면 WebDriver는 요소가 나타날 때까지 지정된 제한 시간이 초과 될 때까지 항상 루프됩니다.

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

내 경험상 명시 적으로 기다리는 것이 항상 더 안정적입니다.


2
더 이상 변수로 요소를 읽고 재사용 할 수 없다고 말하는 것이 맞습니까? 요소 전달에 의존하는 거대하고 역동적 인 WATiR DSL이 있고 웹 드라이버로 이식하려고하는데 동일한 문제가 발생하기 때문에. 본질적으로 DOM을 변경하는 모든 테스트 단계마다 모듈의 모든 요소를 ​​다시 읽는 코드를 추가해야합니다.
kinofrost

안녕하세요. 이 예제에서 어떤 유형의 함수인지 물어볼 수 있습니까? 나는 그것을 찾을 수없는 것 같습니다 .... 감사합니다!
한니발

1
@Hannibal : com.google.common.base.Function<F, T>, 구아바 제공 .
Stephan202

@ jarib, 귀하의 솔루션 이후 1 년 동안이 같은 문제에 직면하고 있습니다. 문제는 루비로 스크립트를 작성하고 있으며 'presenceOfElementLocated'라는 이름이나 그와 비슷한 기능이 없다는 것입니다. 추천이 있습니까?
Amey

56
@ jarib 나는 이것이 모두 잘못 설계 된 테스트로 인한 것이라고 동의하지 않습니다. AJAX 호출 후에 요소가 나타난 후에도 StaleElementReferenceException을 일으킬 수있는 jQuery 코드가 여전히 실행 중일 수 있습니다. 그리고 아주 멋진 것처럼 보이지 않는 명시 적 대기를 추가하는 것 외에는 할 수있는 것이 없습니다. 차라리이 WebDriver의 설계 결함 생각
뭉크

10

나는 약간의 성공으로 이와 같은 방법을 사용할 수있었습니다.

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가 부실하다고 생각하는 부분에는 약간의 결함이 있다고 생각합니다.


7
이 코드에 버그가 있으므로 어떤 종류의 캡없이 메소드를 재귀 적으로 호출하지 않아야합니다. 그렇지 않으면 스택이 날려 버릴 것입니다.
Harry

2
카운터 또는 무언가를 추가하는 것이 낫다고 생각하므로 오류가 반복적으로 발생하면 실제로 오류를 던질 수 있습니다. 그렇지 않으면 실제로 오류가 발생하면 루프 상태가됩니다
Sudara

나는 시험이 잘못 작성된 결과가 아니라는 데 동의합니다. Selenium이 최신 웹 사이트, 심지어 가장 잘 작성된 테스트를 위해이 작업을 수행하는 경향이 있습니다. 아마도 웹 사이트는 반응성 웹 응용 프로그램 프레임 워크에서 공통적 인 양방향 바인딩을 통해 요소를 지속적으로 새로 고치기 때문입니다. 그러한 요소들이 만들어 져야합니다. 이와 같은 방법은 최신 웹 앱을 테스트하는 모든 Selenium 프레임 워크의 일부 여야합니다.
emery

10

AJAX 업데이트가 중간에있을 때 가끔이 오류가 발생합니다. Capybara는 DOM 변경을 기다리는 것에 대해 꽤 똑똑한 것 같지만 ( wait_until이 Capybara에서 제거 된 이유 참조 ) 기본 대기 시간 2 초는 충분하지 않습니다. _spec_helper.rb_에서 다음과 같이 변경되었습니다.

Capybara.default_max_wait_time = 5

2
이것은 또한 내 문제를 해결했습니다 : StaleElementReferenceError가 발생하고 Capybara.default_max_wait_time을 늘리면 문제가 해결되었습니다.
brendan

1

나는 오늘 같은 문제에 직면하고 있으며 요소 참조가 여전히 유효한지 모든 메소드 전에 확인하는 래퍼 클래스를 구성했습니다. 요소를 검색하는 내 솔루션은 매우 간단하므로 공유 할 것이라고 생각했습니다.

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 변수에 요소를 "위치"하거나 저장하고 필요한 경우 요소를 검색하십시오. 페이지가 다시로드되면이 참조는 더 이상 작동하지 않습니다. 그러나 변경이 끝날 때까지만 참조가 유지됩니다. 그리고 대부분의 경우 그 일을해야합니다.

또한 요소를 다시 검색하지 않습니다.

남자


1

나는 같은 문제가 있었고 내 셀레늄 버전으로 인해 발생했습니다. 개발 환경으로 인해 최신 버전으로 업데이트 할 수 없습니다. HTMLUnitWebElement.switchFocusToThisIfNeeded ()로 인해 문제가 발생합니다. 새 페이지로 이동하면 이전 페이지에서 클릭 한 요소가 아래에 나타날 수 있습니다 oldActiveElement. Selenium은 이전 요소에서 컨텍스트를 가져 오려고하지만 실패합니다. 이것이 향후 릴리스에서 시험판을 구축 한 이유입니다.

셀레늄 -html 단위 드라이버 버전 <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 version> = 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

0

검색 입력 상자에 _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

@ 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);
    }
}

이 함수의 첫 번째 매개 변수는 IWebElement 객체를 반환하는 모든 함수입니다. 두 번째 파라미터는 초 단위의 타임 아웃입니다 (타임 아웃 코드는 Selenium IDE for FireFox에서 복사되었습니다). 이 코드는 다음과 같은 방식으로 오래된 요소 예외를 피하는 데 사용될 수 있습니다.

MyTestDriver.WaitForDisplayed(driver.SpecificElementToGet,5);

위의 코드는 예외가 발생하지 않고 평가 되고 5 초가 지날 driver.SpecificElementToGet().Displayed때까지 호출 합니다. 5 초 후에 테스트가 실패합니다.driver.SpecificElementToGet().Displayedtrue

반대로, 요소가 존재하지 않을 때까지 다음 기능을 같은 방식으로 사용할 수 있습니다.

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);
        }
}

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>

그리고 그게 전부입니다. 도움이 되길 바랍니다.


0

명시 적 대기를 사용하여이 문제를 해결할 수 있으므로 강제 대기를 사용할 필요가 없습니다.

하나의 속성으로 모든 요소를 ​​가져오고 각 루프마다 반복하여 사용하면 다음과 같이 루프 내부에서 대기를 사용할 수 있습니다.

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 

-1

Java 8에서는 매우 간단한 방법 을 사용할 수 있습니다 .

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

-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.

나는 그것을 알아 낸 직후에 그것을 추가했다. 처음 게시 한 형식으로 죄송합니다. 그냥 도와 주려고 유용하다고 생각되면 다른 사람들과 공유하십시오. :
Alvin Vera

stackoverflow에 오신 것을 환영합니다! 샘플 코드는 포스트 정확도 : 개선하는 것은 간단한 설명을 제공하기 위해 항상 더 나은
Picrofo 소프트웨어

예를 들어 해당 페이지에 서버 오류가있는 경우 위의 코드를 실행하면 루프에 영원히 멈출 수 있습니다.
munch
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.