Jak uniknąć „StaleElementReferenceException” w Selenium?

85

Wdrażam wiele testów Selenium w Javie. Czasami moje testy kończą się niepowodzeniem z powodu pliku StaleElementReferenceException. Czy mógłbyś zasugerować jakieś podejście do uczynienia testów bardziej stabilnymi?

hoang nguyen
źródło

Odpowiedzi:

89

Może się tak zdarzyć, jeśli operacja DOM na stronie tymczasowo powoduje niedostępność elementu. Aby pozwolić na takie przypadki, możesz spróbować uzyskać dostęp do elementu kilka razy w pętli, zanim ostatecznie wyrzucisz wyjątek.

Wypróbuj to doskonałe rozwiązanie ze strony 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;
}
jspcal
źródło
1
Łał! Właśnie tego potrzebowałem. Dzięki!
SpartaSixZero
1
Można to naprawić również za pomocą innego odniesienia elementu.
Ripon Al Wasim
@jspcal, to zadziałało dla mnie jak urok! Wielkie dzięki!
Anthony Okoth
Jeśli powyższe nie rozwiązuje problemu, aktualizacja do najnowszego sterownika chromedriver jest tym, co dla nas rozwiązało.
Vdex
5
To jest okropne z wielu powodów. To rozwiązuje mój obecny problem, więc dziękuję.
Inżynier oprogramowania
66

Miałem ten problem sporadycznie. Bez mojej wiedzy BackboneJS działał na stronie i zastępował element, który próbowałem kliknąć. Mój kod wyglądał tak.

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

Co oczywiście jest funkcjonalnie takie samo.

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

Czasami zdarzało się, że javascript zastępował element checkoutLink między znalezieniem a kliknięciem, tj.

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

Co słusznie doprowadziło do wyjątku StaleElementReferenceException podczas próby kliknięcia łącza. Nie mogłem znaleźć żadnego niezawodnego sposobu, aby powiedzieć WebDriverowi, aby zaczekał, aż JavaScript się zakończy, więc oto, jak ostatecznie to rozwiązałem.

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

Ten kod będzie nieustannie próbował kliknąć łącze, ignorując StaleElementReferenceExceptions, dopóki kliknięcie się nie powiedzie lub nie zostanie osiągnięty limit czasu. Podoba mi się to rozwiązanie, ponieważ oszczędza konieczności pisania logiki ponawiania i wykorzystuje tylko wbudowane konstrukcje WebDriver.

Kenny
źródło
ta odpowiedź została wycofana.
vaibhavcool20
19

Zwykle jest to spowodowane tym, że DOM jest aktualizowany i próbujesz uzyskać dostęp do zaktualizowanego / nowego elementu - ale DOM został odświeżony, więc jest to nieprawidłowe odniesienie, które masz ...

Aby obejść ten problem, najpierw użyj jawnego czekania na element, aby upewnić się, że aktualizacja jest zakończona, a następnie ponownie pobierz nowe odwołanie do elementu.

Oto kod pseudo do zilustrowania (zaadaptowany z kodu C #, którego używam DOKŁADNIE w tym problemie):

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.

Mam nadzieję że to pomoże!

Jim Holmes
źródło
14

Rozwiązanie Kenny'ego jest dobre, ale można je zapisać w bardziej elegancki sposób

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

Lub też:

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

W każdym razie najlepszym rozwiązaniem jest skorzystanie z biblioteki Selenide, która obsługuje tego typu rzeczy i nie tylko. (zamiast odwołań do elementów obsługuje serwery proxy, więc nigdy nie musisz zajmować się przestarzałymi elementami, co może być dość trudne). Selenid

cocorossello
źródło
Zastrzeżenie: jestem po prostu szczęśliwym użytkownikiem selenku, nie mam nic wspólnego z jego rozwojem
cocorossello
Twoje drugie rozwiązanie zadziałałoby, ponieważ element staje się nieaktualny po kliknięciu, a nie po znalezieniu.
Rajagopalan
Użyj selenku, aby uniknąć tego problemu, o wiele łatwiej. Selen nie jest przeznaczony do samodzielnego stosowania z powodu tego problemu i faktu, że jest to interfejs API niskiego poziomu dla prostego użytkownika
cocorossello
Jestem tego doskonale świadomy. Używam WATIR, który jest opakowaniem wokół Ruby Selenium Binding, WATIR automatycznie rozwiązuje wszystkie te problemy (na przykład nieaktualny element). Szukam czegoś równoważnego w wiązaniu Java, znalazłem Selenide, ale nie wiem, jak zmienić niejawne oczekiwanie i jawne oczekiwanie w selenku. Czy możesz mi powiedzieć, jak to zrobić? Czy jest jakiś materiał, który możesz mi zaoferować, gdzie mogę polecić? a jaka jest Twoja opinia na temat FluentLenium?
Rajagopalan
1
Ludzie powinni wiedzieć, że wybrana przez PO odpowiedź pochodzi z 2012 roku. WIELE rzeczy zmieniło się w ciągu ostatnich 7 lat. Ta odpowiedź jest bardziej poprawna na rok 2019.
hfontanez
10

Powód, dla którego StaleElementReferenceExceptionwystępuje, został już określony: aktualizacje DOM-u pomiędzy znalezieniem a zrobieniem czegoś z elementem.

W przypadku problemu z kliknięciem niedawno zastosowałem takie rozwiązanie:

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

Kluczową częścią jest „powiązanie” własnego Selenu ExpectedConditionsza pomocą ExpectedConditions.refreshed(). To faktycznie czeka i sprawdza, czy dany element został odświeżony w określonym czasie i dodatkowo czeka, aż element stanie się klikalny.

Zajrzyj do dokumentacji odświeżonej metody .

Christian D.
źródło
3

W moim projekcie wprowadziłem pojęcie StableWebElement. Jest to opakowanie dla WebElement, które jest w stanie wykryć, czy element jest nieaktualny i znaleźć nowe odniesienie do oryginalnego elementu. Dodałem metody pomocnika do lokalizowania elementów, które zwracają StableWebElement zamiast WebElement i problem ze StaleElementReference zniknął.

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

Kod w C # jest dostępny na stronie mojego projektu, ale można go łatwo przenieść do java https://github.com/cezarypiatek/Tellurium/blob/master/Src/MvcPages/SeleniumUtils/StableWebElement.cs

cezarypiatek
źródło
1

Rozwiązaniem w C # byłoby:

Klasa pomocnika:

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

Wezwanie:

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

Podobnie może być używane w przypadku języka Java.

George Kargakis
źródło
1

Rozwiązanie Kenny'ego jest przestarzałe. Użyj tego, używam klasy akcji do dwukrotnego klikania, ale możesz zrobić wszystko.

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;
                    }
                });
vaibhavcool20
źródło
1

Czysta findByAndroidIdmetoda, która z wdziękiem radzi sobie StaleElementReference.

Jest to w dużej mierze oparte na odpowiedzi jspcal, ale musiałem zmodyfikować tę odpowiedź, aby działała bezproblemowo z naszą konfiguracją, więc chciałem dodać ją tutaj, na wypadek, gdyby była pomocna dla innych. Jeśli ta odpowiedź ci pomogła, przejdź do odpowiedzi upvote 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.
    }
  }
}
Joshua Pinter
źródło
0

To działa dla mnie (100% działa) przy użyciu C #

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;
    }
Shivam Bharadwaj
źródło
0

Problem polega na tym, że zanim przekażesz element z JavaScript do Java z powrotem do Javascript, może on opuścić DOM.
Spróbuj zrobić wszystko w Javascript:

driver.executeScript("document.querySelector('#my_id').click()") 
pguardiario
źródło
0

Spróbuj tego

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
    }
}
CodingGirl1
źródło
0

Znalazłem tutaj rozwiązanie . W moim przypadku element staje się niedostępny w przypadku opuszczenia bieżącego okna, zakładki lub strony i ponownego powrotu.

.ignoring (StaleElement ...), .refreshed (...) i elementToBeClicable (...) nie pomogły i otrzymałem wyjątek na act.doubleClick(element).build().perform();łańcuchu.

Używanie funkcji w mojej głównej klasie testowej:

openForm(someXpath);

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

Funkcja My 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))));
    }
Gryu
źródło
0

Może istnieć potencjalny problem prowadzący do wyjątku StaleElementReferenceException, o którym nikt do tej pory nie wspomniał (w odniesieniu do działań).

Wyjaśniam to w Javascript, ale tak samo jest w Javie.

To nie zadziała:

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

Ale ponowne utworzenie instancji akcji rozwiąże problem:

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()
ndsvw
źródło
0

Zwykle StaleElementReferenceException pojawia się, gdy element, do którego próbujemy uzyskać dostęp, ale inne elementy mogą wpływać na pozycję elementu, który nas interesuje, dlatego gdy próbujemy kliknąć lub pobraćText lub spróbować wykonać jakąś akcję na WebElement, otrzymujemy wyjątek, który zwykle mówi, że element nie jest dołączony do DOM .

Rozwiązanie, które wypróbowałem, jest następujące:

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

W powyższym kodzie najpierw próbuję poczekać, a następnie klikam element, jeśli wystąpi wyjątek, a następnie łapię go i próbuję zapętlić, ponieważ istnieje możliwość, że nadal wszystkie elementy mogą nie zostać załadowane i ponownie może wystąpić wyjątek.

Rakesh Singh Chouhan
źródło
-4

Być może został dodany niedawno, ale inne odpowiedzi nie wspominają o ukrytej funkcji oczekiwania Selenium, która wykonuje wszystkie powyższe czynności za Ciebie i jest wbudowana w Selenium.

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

Spowoduje to ponowienie findElement()wywołań do momentu znalezienia elementu lub przez 10 sekund.

Źródło - http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp

Lockedan
źródło
2
To rozwiązanie nie zapobiega StaleElementReferenceException
MrSpock
1
Aby wyeliminować wszelkie nieporozumienia dotyczące wersji, nawet w najnowszej wersji Selenium implicitlyWait () NIE zapobiega StaleElementReferenceException. Używam metody, która wywołuje pętlę ze snem, aż do sukcesu lub stałej liczby.
Angsuman Chakraborty,