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.