Selenium에서 "StaleElementReferenceException"을 피하는 방법은 무엇입니까?


85

Java를 사용하여 많은 Selenium 테스트를 구현하고 있습니다. 때때로 내 테스트는 StaleElementReferenceException. 테스트를보다 안정적으로 만드는 방법을 제안 해 주시겠습니까?

답변:


89

페이지에서 발생하는 DOM 작업으로 인해 일시적으로 요소에 액세스 할 수없는 경우 이러한 상황이 발생할 수 있습니다. 이러한 경우를 허용하기 위해 최종적으로 예외를 발생시키기 전에 루프에서 요소에 여러 번 액세스 할 수 있습니다.

darrelgrainger.blogspot.com에서이 훌륭한 솔루션을 사용해보십시오 .

public boolean retryingFindClick(By by) {
    boolean result = false;
    int attempts = 0;
    while(attempts < 2) {
        try {
            driver.findElement(by).click();
            result = true;
            break;
        } catch(StaleElementException e) {
        }
        attempts++;
    }
    return result;
}

1
와! 이것은 내가 필요한 것입니다. 감사!
— SpartaSixZero

1
요소의 다른 참조를 사용하여 수정할 수도 있습니다.
— Ripon Al Wasim 2015 년

@jspcal, 이것은 나를위한 매력처럼 작동했습니다! 감사합니다!
— Anthony Okoth

위의 방법으로 문제가 해결되지 않으면 최신 chromedriver로 업데이트하여 문제를 해결했습니다.
— Vdex

5
이것은 여러면에서 끔찍합니다. 현재 문제를 해결해 주니 감사합니다.
— 소프트웨어 엔지니어

66

이 문제가 간헐적으로 발생했습니다. 나에게 알려지지 않은 BackboneJS가 페이지에서 실행 중이며 클릭하려는 요소를 대체했습니다. 내 코드는 다음과 같습니다.

driver.findElement(By.id("checkoutLink")).click();

물론 기능적으로 이것과 동일합니다.

WebElement checkoutLink = driver.findElement(By.id("checkoutLink"));
checkoutLink.click();

가끔 일어나는 일은 자바 스크립트가 그것을 찾아 클릭하는 사이에 checkoutLink 요소를 대체하는 것입니다.

WebElement checkoutLink = driver.findElement(By.id("checkoutLink"));
// javascript replaces checkoutLink
checkoutLink.click();

링크를 클릭하려고 할 때 StaleElementReferenceException이 정당하게 발생했습니다. WebDriver에게 자바 스크립트 실행이 끝날 때까지 기다리라고하는 신뢰할 수있는 방법을 찾을 수 없었기 때문에 결국 해결 방법은 다음과 같습니다.

new WebDriverWait(driver, timeout)
    .ignoring(StaleElementReferenceException.class)
    .until(new Predicate<WebDriver>() {
        @Override
        public boolean apply(@Nullable WebDriver driver) {
            driver.findElement(By.id("checkoutLink")).click();
            return true;
        }
    });

이 코드는 클릭이 성공하거나 시간 초과에 도달 할 때까지 StaleElementReferenceExceptions를 무시하고 링크를 계속 클릭하려고합니다. 이 솔루션을 사용하면 재시도 논리를 작성하지 않아도되고 WebDriver의 기본 제공 구조 만 사용하므로이 솔루션을 좋아합니다.


이 답변은 더 이상 사용되지 않습니다.
— vaibhavcool20

19

일반적으로 이는 DOM이 업데이트되고 업데이트 된 / 새 요소에 액세스하려고 시도하기 때문입니다.하지만 DOM이 새로 고쳐 져서 잘못된 참조입니다.

먼저 요소에 대한 명시 적 대기를 사용하여 업데이트가 완료되었는지 확인한 다음 요소에 대한 새로운 참조를 다시 가져와이 문제를 해결하십시오.

여기에 (I가 사용하는 몇 가지 C # 코드에서 적응 설명하기 위해 일부 사이비 코드입니다 정확히 이 문제는) :

WebDriverWait wait = new WebDriverWait(browser, TimeSpan.FromSeconds(10));
IWebElement aRow = browser.FindElement(By.XPath(SOME XPATH HERE);
IWebElement editLink = aRow.FindElement(By.LinkText("Edit"));

//this Click causes an AJAX call
editLink.Click();

//must first wait for the call to complete
wait.Until(ExpectedConditions.ElementExists(By.XPath(SOME XPATH HERE));

//you've lost the reference to the row; you must grab it again.
aRow = browser.FindElement(By.XPath(SOME XPATH HERE);

//now proceed with asserts or other actions.

도움이 되었기를 바랍니다!


14

Kenny의 솔루션은 좋지만 더 우아한 방식으로 작성할 수 있습니다.

new WebDriverWait(driver, timeout)
        .ignoring(StaleElementReferenceException.class)
        .until((WebDriver d) -> {
            d.findElement(By.id("checkoutLink")).click();
            return true;
        });

또는 :

new WebDriverWait(driver, timeout).ignoring(StaleElementReferenceException.class).until(ExpectedConditions.elementToBeClickable(By.id("checkoutLink")));
driver.findElement(By.id("checkoutLink")).click();

그러나 어쨌든 최선의 해결책은 Selenide 라이브러리에 의존하는 것입니다.이 라이브러리는 이런 종류의 것들을 처리합니다. (요소 참조 대신 프록시를 처리하므로 오래된 요소를 처리 할 필요가 없습니다. 이는 매우 어려울 수 있습니다). 셀레 나이드


면책 조항 : 난 그냥 행복 셀렌 사용자를 해요, 아무것도의 발전과 함께 할 수 없습니다
— cocorossello

두 번째 솔루션은 요소를 찾을 때가 아니라 클릭하면 부실하기 때문에 작동합니다.
— Rajagopalan

셀레 나이드를 사용하면이 문제를 훨씬 쉽게 피할 수 있습니다. 셀레늄 때문에이 문제를 간단한 사용자에 대한 낮은 수준의 API 인 사실을 단독으로 사용하는 것은 아닙니다
— cocorossello

나는 그것을 완벽하게 알고 있습니다. Ruby Selenium Binding을 둘러싼 래퍼 인 WATIR을 사용하고 있습니다. WATIR은 이러한 모든 문제를 자동으로 처리합니다 (인스턴스 부실 요소). Java 바인딩에서 동등한 것을 찾고 있는데 Selenide를 찾았지만 암시 적 대기 및 명시 적 대기를 selenide에서 변경하는 방법을 모르겠습니다. 어떻게하는지 말해 줄 수 있나요? 아니면 제가 참조 할 수있는 자료가 있습니까? FluentLenium에 대한 귀하의 의견은 무엇입니까?
— Rajagopalan

1
사람들은 OP에 의해 선택된 답변이 2012 년으로 거슬러 올라간다는 것을 알아야합니다. 지난 7 년 동안 많은 것이 변경되었습니다. 이 답변은 2019
— hfontanez

10

StaleElementReferenceException발생 하는 이유 는 이미 설명되어 있습니다. 요소를 사용하여 무언가를 찾고 수행하는 사이의 DOM 업데이트입니다.

클릭 문제의 경우 최근 다음과 같은 솔루션을 사용했습니다.

public void clickOn(By locator, WebDriver driver, int timeout)
{
    final WebDriverWait wait = new WebDriverWait(driver, timeout);
    wait.until(ExpectedConditions.refreshed(
        ExpectedConditions.elementToBeClickable(locator)));
    driver.findElement(locator).click();
}

중요한 부분은 .NET Framework ExpectedConditions를 통한 셀레늄 자체의 "체인"입니다 ExpectedConditions.refreshed(). 이것은 실제로 해당 요소가 지정된 시간 제한 동안 새로 고쳐 졌는지 확인하고 추가로 요소를 클릭 할 수있을 때까지 기다립니다.

새로 고침 방법 에 대한 설명서를 살펴보십시오 .


3

내 프로젝트에서 StableWebElement 개념을 도입했습니다. 요소가 Stale인지 감지하고 원래 요소에 대한 새 참조를 찾을 수있는 WebElement 용 래퍼입니다. WebElement 대신 StableWebElement를 반환하는 요소를 찾는 데 도우미 메서드를 추가했으며 StaleElementReference의 문제가 사라졌습니다.

public static IStableWebElement FindStableElement(this ISearchContext context, By by)
{
    var element = context.FindElement(by);
    return new StableWebElement(context, element, by, SearchApproachType.First);
} 

C #의 코드는 내 프로젝트 페이지에서 사용할 수 있지만 java https://github.com/cezarypiatek/Tellurium/blob/master/Src/MvcPages/SeleniumUtils/StableWebElement.cs 로 쉽게 이식 할 수 있습니다.


1

C #의 솔루션은 다음과 같습니다.

도우미 클래스 :

internal class DriverHelper
{

    private IWebDriver Driver { get; set; }
    private WebDriverWait Wait { get; set; }

    public DriverHelper(string driverUrl, int timeoutInSeconds)
    {
        Driver = new ChromeDriver();
        Driver.Url = driverUrl;
        Wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(timeoutInSeconds));
    }

    internal bool ClickElement(string cssSelector)
    {
        //Find the element
        IWebElement element = Wait.Until(d=>ExpectedConditions.ElementIsVisible(By.CssSelector(cssSelector)))(Driver);
        return Wait.Until(c => ClickElement(element, cssSelector));
    }

    private bool ClickElement(IWebElement element, string cssSelector)
    {
        try
        {
            //Check if element is still included in the dom
            //If the element has changed a the OpenQA.Selenium.StaleElementReferenceException is thrown.
            bool isDisplayed = element.Displayed;

            element.Click();
            return true;
        }
        catch (StaleElementReferenceException)
        {
            //wait until the element is visible again
            element = Wait.Until(d => ExpectedConditions.ElementIsVisible(By.CssSelector(cssSelector)))(Driver);
            return ClickElement(element, cssSelector);
        }
        catch (Exception)
        {
            return false;
        }
    }
}

기도:

        DriverHelper driverHelper = new DriverHelper("http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp", 10);
        driverHelper.ClickElement("input[value='csharp']:first-child");

마찬가지로 Java에도 사용할 수 있습니다.


1

Kenny의 솔루션은 더 이상 사용되지 않습니다. 이것을 사용하면 작업 클래스를 사용하여 두 번 클릭하지만 무엇이든 할 수 있습니다.

new FluentWait<>(driver).withTimeout(30, TimeUnit.SECONDS).pollingEvery(5, TimeUnit.SECONDS)
                    .ignoring(StaleElementReferenceException.class)
                    .until(new Function() {

                    @Override
                    public Object apply(Object arg0) {
                        WebElement e = driver.findelement(By.xpath(locatorKey));
                        Actions action = new Actions(driver);
                        action.moveToElement(e).doubleClick().perform();
                        return true;
                    }
                });

1

클린 findByAndroidId우아의 처리하는 방법 StaleElementReference.

이것은 jspcal의 답변을 기반으로 하지만 우리 설정에서 깔끔하게 작동하도록 답변을 수정해야했기 때문에 다른 사람들에게 도움이되는 경우 여기에 추가하고 싶었습니다. 이 답변이 도움이 되었다면 jspcal의 답변을 올려 주세요 .

// This loops gracefully handles StateElementReference errors and retries up to 10 times. These can occur when an element, like a modal or notification, is no longer available.
export async function findByAndroidId( id, { assert = wd.asserters.isDisplayed, timeout = 10000, interval = 100 } = {} ) {
  MAX_ATTEMPTS = 10;
  let attempt = 0;

  while( attempt < MAX_ATTEMPTS ) {
    try {
      return await this.waitForElementById( `android:id/${ id }`, assert, timeout, interval );
    }
    catch ( error ) {
      if ( error.message.includes( "StaleElementReference" ) )
        attempt++;
      else
        throw error; // Re-throws the error so the test fails as normal if the assertion fails.
    }
  }
}

0

이것은 C #을 사용하여 나를 위해 작동합니다 (100 % 작동).

public Boolean RetryingFindClick(IWebElement webElement)
    {
        Boolean result = false;
        int attempts = 0;
        while (attempts < 2)
        {
            try
            {
                webElement.Click();
                result = true;
                break;
            }
            catch (StaleElementReferenceException e)
            {
                Logging.Text(e.Message);
            }
            attempts++;
        }
        return result;
    }

0

문제는 Javascript에서 Java로 요소를 다시 Javascript로 전달할 때 DOM을 떠날 수 있다는 것입니다.
Javascript에서 모든 것을 시도하십시오.

driver.executeScript("document.querySelector('#my_id').click()") 

0

이 시도

while (true) { // loops forever until break
    try { // checks code for exceptions
        WebElement ele=
        (WebElement)wait.until(ExpectedConditions.elementToBeClickable((By.xpath(Xpath))));  
        break; // if no exceptions breaks out of loop
    } 
    catch (org.openqa.selenium.StaleElementReferenceException e1) { 
        Thread.sleep(3000); // you can set your value here maybe 2 secs
        continue; // continues to loop if exception is found
    }
}

0

여기 에서 해결책을 찾았 습니다 . 제 경우에는 현재 창, 탭 또는 페이지를 떠나 다시 돌아 오는 경우 요소에 액세스 할 수 없게됩니다.

.ignoring (StaleElement ...), .refreshed (...) 및 elementToBeClicable (...)은 도움이되지 않았고 act.doubleClick(element).build().perform();문자열에 예외가 발생했습니다 .

내 주요 테스트 클래스에서 기능 사용 :

openForm(someXpath);

내 BaseTest 기능 :

int defaultTime = 15;

boolean openForm(String myXpath) throws Exception {
    int count = 0;
    boolean clicked = false;
    while (count < 4 || !clicked) {
        try {
            WebElement element = getWebElClickable(myXpath,defaultTime);
            act.doubleClick(element).build().perform();
            clicked = true;
            print("Element have been clicked!");
            break;
        } catch (StaleElementReferenceException sere) {
            sere.toString();
            print("Trying to recover from: "+sere.getMessage());
            count=count+1;
        }
    }

내 BaseClass 함수 :

protected WebElement getWebElClickable(String xpath, int waitSeconds) {
        wait = new WebDriverWait(driver, waitSeconds);
        return wait.ignoring(StaleElementReferenceException.class).until(
                ExpectedConditions.refreshed(ExpectedConditions.elementToBeClickable(By.xpath(xpath))));
    }

0

(작업과 관련하여) 지금까지 아무도 언급하지 않은 StaleElementReferenceException으로 이어지는 잠재적 인 문제가있을 수 있습니다.

Javascript로 설명하지만 Java에서도 동일합니다.

작동하지 않습니다.

let actions = driver.actions({ bridge: true })
let a = await driver.findElement(By.css('#a'))
await actions.click(a).perform() // this leads to a DOM change, #b will be removed and added again to the DOM.
let b = await driver.findElement(By.css('#b'))
await actions.click(b).perform()

그러나 작업을 다시 인스턴스화하면 해결됩니다.

let actions = driver.actions({ bridge: true })
let a = await driver.findElement(By.css('#a'))
await actions.click(a).perform()  // this leads to a DOM change, #b will be removed and added again to the DOM.
actions = driver.actions({ bridge: true }) // new
let b = await driver.findElement(By.css('#b'))
await actions.click(b).perform()

0

일반적으로 StaleElementReferenceException 우리가 접근하려는 요소가 나타 났지만 다른 요소가 우리가 포함 된 요소의 위치에 영향을 미칠 수 있으므로 클릭 또는 getText를 시도하거나 WebElement에서 일부 작업을 시도 할 때 일반적으로 DOM에 첨부되지 않은 요소를 나타내는 예외가 발생합니다. .

내가 시도한 해결책은 다음과 같습니다.

 protected void clickOnElement(By by) {
        try {
            waitForElementToBeClickableBy(by).click();
        } catch (StaleElementReferenceException e) {
            for (int attempts = 1; attempts < 100; attempts++) {
                try {
                    waitFor(500);
                    logger.info("Stale element found retrying:" + attempts);
                    waitForElementToBeClickableBy(by).click();
                    break;
                } catch (StaleElementReferenceException e1) {
                    logger.info("Stale element found retrying:" + attempts);
                }
            }
        }

protected WebElement waitForElementToBeClickableBy(By by) {
        WebDriverWait wait = new WebDriverWait(getDriver(), 10);
        return wait.until(ExpectedConditions.elementToBeClickable(by));
    }

위의 코드에서 나는 먼저 기다렸다가 예외가 발생하면 요소를 클릭 한 다음, 여전히 모든 요소가로드되지 않고 다시 예외가 발생할 가능성이 있으므로이를 포착하고 루프를 시도합니다.


-4

아마도 더 최근에 추가되었지만 다른 답변은 위의 모든 작업을 수행하고 Selenium에 내장 된 Selenium의 암시 적 대기 기능을 언급하지 않습니다.

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

findElement()요소를 찾을 때까지 또는 10 초 동안 호출 을 재 시도 합니다.

출처-http: //www.seleniumhq.org/docs/04_webdriver_advanced.jsp


2
이 솔루션은 StaleElementReferenceException을 방지하지 않습니다
— MrSpock

1
버전에 대한 혼란을 없애기 위해 최신 버전의 Selenium에서도 암시 적으로 Wait ()는 StaleElementReferenceException을 방지하지 않습니다. 성공 또는 고정 카운트까지 수면 루프에서 호출하는 메서드를 사용합니다.
— Angsuman Chakraborty
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.