IT TIP

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

itqueen 2020. 11. 19. 22:44
반응형

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


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


페이지에서 발생하는 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;
}

이 문제가 간헐적으로 발생했습니다. 나에게 알려지지 않은 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의 기본 제공 구조 만 사용하므로이 솔루션을 좋아합니다.


일반적으로 이는 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.

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


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 라이브러리에 의존하는 것입니다. 이런 종류의 것들을 처리합니다. (요소 참조 대신 프록시를 처리하므로 오래된 요소를 처리 할 필요가 없습니다. 매우 어려울 수 있습니다). 셀레 나이드


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을 ExpectedConditions통한 Selenium 자체의 "체인"입니다 ExpectedConditions.refreshed(). 이것은 실제로 해당 요소가 지정된 제한 시간 동안 새로 고쳐 졌는지 확인하고 추가로 요소를 클릭 할 수있을 때까지 기다립니다.

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


내 프로젝트에서 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 로 쉽게 이식 할 수 있습니다.


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에도 사용할 수 있습니다.


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

이것은 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;
    }

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

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

이 시도

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

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

.ignoring(StaleElement...), .refreshed(...) and elementToBeClicable(...) did not help and I was getting exception on act.doubleClick(element).build().perform(); string.

Using function in my main test class:

openForm(someXpath);

My BaseTest function:

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

My BaseClass function:

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

There could be a potential problem that leads to the StaleElementReferenceException that no one mentioned so far (in regard to actions).

I explain it in Javascript, but it's the same in Java.

This won't work:

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()

But instantiating the actions again will solve it:

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()

Maybe it was added more recently, but other answers fail to mention Selenium's implicit wait feature, which does all the above for you, and is built into Selenium.

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

This will retry findElement() calls until the element has been found, or for 10 seconds.

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

참고URL : https://stackoverflow.com/questions/12967541/how-to-avoid-staleelementreferenceexception-in-selenium

반응형