Come evitare "StaleElementReferenceException" in Selenium?


89

Sto implementando molti test sul selenio usando Java. A volte, i miei test falliscono a causa di un file StaleElementReferenceException. Potresti suggerire alcuni approcci per rendere i test più stabili?

Risposte:


91

Ciò può accadere se un'operazione DOM in corso sulla pagina rende temporaneamente inaccessibile l'elemento. Per consentire questi casi, puoi provare ad accedere all'elemento più volte in un ciclo prima di lanciare finalmente un'eccezione.

Prova questa eccellente soluzione da 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
Wow! Questo era proprio quello di cui avevo bisogno. Grazie!
SpartaSixZero

1
Potrebbe essere risolto anche utilizzando diversi riferimenti di elemento.
Ripon Al Wasim,

@jspcal, questo ha funzionato come un fascino per me! Molte grazie!
Anthony Okoth

Se quanto sopra non lo risolve, l'aggiornamento all'ultima versione del chromeedriver è ciò che lo ha risolto per noi.
Vdex

5
Questo è orribile in tanti modi. Risolve il mio problema attuale, quindi grazie.
Software Engineer

68

Ho riscontrato questo problema a intermittenza. A mia insaputa, BackboneJS era in esecuzione sulla pagina e stava sostituendo l'elemento su cui stavo cercando di fare clic. Il mio codice sembrava così.

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

Il che ovviamente è funzionalmente lo stesso di questo.

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

Quello che a volte accadeva era che javascript sostituisse l'elemento checkoutLink tra la ricerca e il clic, ad es.

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

Che giustamente ha portato a un'eccezione StaleElementReferenceException quando si tenta di fare clic sul collegamento. Non sono riuscito a trovare alcun modo affidabile per dire a WebDriver di attendere fino al termine dell'esecuzione di javascript, quindi ecco come alla fine l'ho risolto.

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

Questo codice proverà continuamente a fare clic sul collegamento, ignorando StaleElementReferenceExceptions fino a quando il clic non riesce o il timeout viene raggiunto. Mi piace questa soluzione perché ti evita di dover scrivere qualsiasi logica di ripetizione e utilizza solo i costrutti incorporati di WebDriver.


questa risposta è stata deprecata.
vaibhavcool20

20

In genere ciò è dovuto all'aggiornamento del DOM e al tentativo di accedere a un elemento nuovo / aggiornato, ma il DOM è stato aggiornato quindi è un riferimento non valido che hai ..

Aggira questo problema usando prima un'attesa esplicita sull'elemento per assicurarti che l'aggiornamento sia completo, quindi prendi di nuovo un nuovo riferimento all'elemento.

Ecco un po 'di codice psuedo per illustrare (adattato da un codice C # che uso ESATTAMENTE per questo problema):

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.

Spero che sia di aiuto!


17

La soluzione di Kenny è buona, tuttavia può essere scritta in modo più elegante

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

O anche:

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

Ma comunque, la soluzione migliore è affidarsi alla libreria Selenide, gestisce questo genere di cose e altro ancora. (invece dei riferimenti agli elementi gestisce i proxy, quindi non devi mai avere a che fare con elementi obsoleti, il che può essere piuttosto difficile). Selenide


Disclaimer: sono solo un felice utente di selenide, niente a che vedere con il suo sviluppo
cocorossello

La tua seconda soluzione funzionerebbe perché l'elemento diventa obsoleto quando fai clic su di esso non quando lo trovi.
Rajagopalan

Usa selenide per evitare questo problema, molto più facilmente. Il selenio non è pensato per essere utilizzato da solo a causa di questo problema e del fatto che è un'API di basso livello per un semplice utente
cocorossello

Ne sono perfettamente consapevole. Sto usando WATIR che è un wrapper attorno a Ruby Selenium Binding, WATIR si occupa automaticamente di tutti questi problemi (per un elemento non aggiornato dell'istanza). Sto cercando qualcosa di equivalente nel binding Java, ho trovato Selenide ma non so come cambiare l'attesa implicita e l'attesa esplicita in selenide. Puoi dirmi come farlo? O c'è del materiale che puoi offrirmi a cui posso fare riferimento? e qual è la tua opinione su FluentLenium?
Rajagopalan

1
La gente dovrebbe sapere che la risposta selezionata dal PO risale al 2012. MOLTE cose sono cambiate negli ultimi 7 anni. Questa risposta è più corretta per il 2019.
hfontanez

11

Il motivo per cui si StaleElementReferenceExceptionverifica è già stato spiegato : aggiornamenti al DOM tra la ricerca e l'esecuzione di qualcosa con l'elemento.

Per il problema del clic ho usato di recente una soluzione come questa:

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

La parte cruciale è il "concatenamento" del selenio ExpectedConditionstramite il ExpectedConditions.refreshed(). Questo in realtà attende e controlla se l'elemento in questione è stato aggiornato durante il timeout specificato e attende inoltre che l'elemento diventi cliccabile.

Dai un'occhiata alla documentazione per il metodo aggiornato .


4

Nel mio progetto ho introdotto una nozione di StableWebElement. È un wrapper per WebElement che è in grado di rilevare se l'elemento è obsoleto e trovare un nuovo riferimento all'elemento originale. Ho aggiunto un metodo di supporto per individuare gli elementi che restituiscono StableWebElement invece di WebElement e il problema con StaleElementReference è scomparso.

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

Il codice in C # è disponibile sulla pagina del mio progetto ma potrebbe essere facilmente portato su java https://github.com/cezarypiatek/Tellurium/blob/master/Src/MvcPages/SeleniumUtils/StableWebElement.cs


1

Una soluzione in C # sarebbe:

Classe di aiuto:

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

Invocazione:

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

Allo stesso modo può essere utilizzato per Java.


1

La soluzione di Kenny è deprecata, usa questa, sto usando la classe delle azioni per fare doppio clic ma puoi fare qualsiasi cosa.

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

findByAndroidIdMetodo pulito che gestisce con grazia StaleElementReference.

Questo è fortemente basato sulla risposta di jspcal, ma ho dovuto modificare quella risposta per farlo funzionare in modo pulito con la nostra configurazione e quindi volevo aggiungerla qui nel caso fosse utile ad altri. Se questa risposta ti ha aiutato, per favore vai a votare la risposta di 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

Questo funziona per me (funziona al 100%) usando 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;
    }

0

Il problema è che quando passi l'elemento da Javascript a Java di nuovo a Javascript, può aver lasciato il DOM.
Prova a fare tutto in Javascript:

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

0

Prova questo

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

Ho trovato la soluzione qui . Nel mio caso l'elemento diventa inaccessibile in caso di uscita dalla finestra, scheda o pagina corrente e ritorno di nuovo.

.ignoring (StaleElement ...), .refreshed (...) e elementToBeClicable (...) non hanno aiutato e stavo ottenendo un'eccezione sulla act.doubleClick(element).build().perform();stringa.

Utilizzo della funzione nella mia classe di test principale:

openForm(someXpath);

La mia funzione 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;
        }
    }

La mia funzione 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

Potrebbe esserci un potenziale problema che porta all'eccezione StaleElementReferenceException che nessuno ha menzionato finora (in merito alle azioni).

Lo spiego in Javascript, ma è lo stesso in Java.

Questo non funzionerà:

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

Ma istanziare nuovamente le azioni risolverà il problema:

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

Di solito StaleElementReferenceException quando l'elemento a cui tentiamo di accedere è apparso ma altri elementi possono influenzare la posizione dell'elemento a cui siamo interessati, quindi quando proviamo a fare clic o getText o proviamo a fare qualche azione su WebElement otteniamo un'eccezione che di solito dice che l'elemento non è collegato a DOM .

La soluzione che ho provato è la seguente:

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

Nel codice sopra provo prima ad aspettare e quindi a fare clic sull'elemento se si verifica un'eccezione, quindi lo prendo e provo a riprodurlo in loop poiché esiste la possibilità che tutti gli elementi non vengano caricati e che si verifichi nuovamente un'eccezione.


-4

Forse è stato aggiunto più di recente, ma altre risposte non menzionano la funzione di attesa implicita di Selenium, che fa tutto quanto sopra per te ed è integrata in Selenium.

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

Questo riproverà findElement() chiamate fino a quando l'elemento non è stato trovato o per 10 secondi.

Fonte: http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp


2
Questa soluzione non impedisce StaleElementReferenceException
MrSpock

1
Solo per eliminare qualsiasi confusione sulle versioni, anche nell'ultima versione di Selenium implicitlyWait () NON impedisce StaleElementReferenceException. Uso un metodo che chiama in un ciclo con sleep, fino al successo o al conteggio fisso.
Angsuman Chakraborty
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.