Wie vermeide ich "StaleElementReferenceException" in Selen?

85

Ich implementiere viele Selenium-Tests mit Java. Manchmal schlagen meine Tests aufgrund von a fehl StaleElementReferenceException. Könnten Sie einige Ansätze vorschlagen, um die Tests stabiler zu machen?

hoang nguyen
quelle

Antworten:

89

Dies kann passieren, wenn eine DOM-Operation auf der Seite vorübergehend dazu führt, dass auf das Element nicht zugegriffen werden kann. Um diese Fälle zu berücksichtigen, können Sie versuchen, mehrmals in einer Schleife auf das Element zuzugreifen, bevor Sie schließlich eine Ausnahme auslösen.

Probieren Sie diese hervorragende Lösung von darrelgrainger.blogspot.com aus :

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
quelle
1
Beeindruckend! Das war genau das, was ich brauchte. Vielen Dank!
SpartaSixZero
1
Es könnte auch durch Verwendung einer anderen Referenz des Elements behoben werden.
Ripon Al Wasim
@jspcal, das hat wie ein Zauber für mich funktioniert! Vielen Dank!
Anthony Okoth
Wenn das oben genannte Problem nicht behoben werden kann, ist die Aktualisierung auf den neuesten Chromedriver das Problem für uns.
Vdex
5
Das ist in vielerlei Hinsicht schrecklich. Es löst mein aktuelles Problem durch, also danke.
Software Engineer
66

Ich hatte dieses Problem zeitweise. Ohne mein Wissen lief BackboneJS auf der Seite und ersetzte das Element, auf das ich klicken wollte. Mein Code sah so aus.

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

Welches ist natürlich funktional das gleiche wie dieses.

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

Was gelegentlich passieren würde, war, dass das Javascript das checkoutLink-Element zwischen dem Suchen und Klicken ersetzt, d. H.

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

Was zu Recht zu einer StaleElementReferenceException führte, wenn versucht wurde, auf den Link zu klicken. Ich konnte keine zuverlässige Möglichkeit finden, WebDriver anzuweisen, zu warten, bis das Javascript ausgeführt wurde. Hier ist, wie ich es schließlich gelöst habe.

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

Dieser Code versucht ständig, auf den Link zu klicken, wobei StaleElementReferenceExceptions ignoriert werden, bis entweder der Klick erfolgreich ist oder das Zeitlimit erreicht ist. Ich mag diese Lösung, weil Sie keine Wiederholungslogik schreiben müssen und nur die integrierten Konstrukte von WebDriver verwenden.

Kenny
quelle
Diese Antwort ist veraltet.
Vaibhavcool20
19

Im Allgemeinen liegt dies daran, dass das DOM aktualisiert wird und Sie versuchen, auf ein aktualisiertes / neues Element zuzugreifen. Das DOM wurde jedoch aktualisiert, sodass es sich um eine ungültige Referenz handelt.

Umgehen Sie dies, indem Sie zunächst explizit auf das Element warten, um sicherzustellen, dass die Aktualisierung abgeschlossen ist, und dann erneut einen neuen Verweis auf das Element abrufen.

Hier ist ein Pseudo-Code zur Veranschaulichung (angepasst an einen C # -Code, den ich genau für dieses Problem verwende):

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.

Hoffe das hilft!

Jim Holmes
quelle
15

Kennys Lösung ist gut, kann aber eleganter geschrieben werden

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

Oder auch:

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

Die beste Lösung ist jedoch, sich auf die Selenide-Bibliothek zu verlassen, die diese Art von Dingen und mehr erledigt. (Anstelle von Elementreferenzen werden Proxys behandelt, sodass Sie sich nie mit veralteten Elementen befassen müssen, was sehr schwierig sein kann.) Selenid

Cocorossello
quelle
Haftungsausschluss: Ich bin nur ein glücklicher Selenid-Benutzer, der nichts mit seiner Entwicklung zu tun hat
Cocorossello
Ihre zweite Lösung würde funktionieren, da das Element veraltet ist, wenn Sie darauf klicken, und nicht, wenn Sie es finden.
Rajagopalan
Verwenden Sie Selenid, um dieses Problem zu vermeiden, viel einfacher. Selen ist aufgrund dieses Problems und der Tatsache, dass es sich um eine Low-Level-API für einen einfachen Benutzer handelt, nicht dazu gedacht, allein verwendet zu werden
cocorossello
Ich bin mir dessen vollkommen bewusst. Ich verwende WATIR, einen Wrapper um Ruby Selenium Binding. WATIR kümmert sich automatisch um all diese Probleme (zum Beispiel um ein veraltetes Element). Ich suche nach etwas Äquivalentem in der Java-Bindung. Ich habe Selenide gefunden, weiß aber nicht, wie ich das implizite Warten und das explizite Warten in Selenide ändern soll. Kannst du mir sagen, wie das geht? Oder gibt es Material, das Sie mir anbieten können, auf das ich mich beziehen kann? und wie ist deine Meinung zu FluentLenium?
Rajagopalan
1
Die Menschen sollten wissen, dass die vom OP ausgewählte Antwort auf das Jahr 2012 zurückgeht. Viele Dinge haben sich in den letzten 7 Jahren geändert. Diese Antwort ist korrekter für 2019.
hfontanez
10

Der Grund, warum das StaleElementReferenceExceptionauftritt, wurde bereits dargelegt: Aktualisierungen des DOM zwischen dem Finden und Ausführen von etwas mit dem Element.

Für das Klick-Problem habe ich kürzlich eine Lösung wie diese verwendet:

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

Der entscheidende Teil ist die "Verkettung" von Selenium ExpectedConditionsüber die ExpectedConditions.refreshed(). Dies wartet tatsächlich und prüft, ob das betreffende Element während des angegebenen Zeitlimits aktualisiert wurde, und wartet zusätzlich darauf, dass das Element anklickbar wird.

Schauen Sie sich die Dokumentation für die aktualisierte Methode an .

Christian.D
quelle
4

In meinem Projekt habe ich einen Begriff von StableWebElement eingeführt. Es ist ein Wrapper für WebElement, der erkennen kann, ob das Element veraltet ist, und einen neuen Verweis auf das ursprüngliche Element findet. Ich habe eine Hilfemethode zum Suchen von Elementen hinzugefügt, die StableWebElement anstelle von WebElement zurückgeben, und das Problem mit StaleElementReference ist verschwunden.

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

Der Code in C # ist auf der Seite meines Projekts verfügbar, kann jedoch problemlos auf Java https://github.com/cezarypiatek/Tellurium/blob/master/Src/MvcPages/SeleniumUtils/StableWebElement.cs portiert werden

Cezarypiatek
quelle
1

Eine Lösung in C # wäre:

Hilfsklasse:

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

Aufruf:

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

Ebenso kann für Java verwendet werden.

George Kargakis
quelle
1

Kennys Lösung ist veraltet. Verwenden Sie diese Option. Ich verwende die Aktionsklasse zum Doppelklicken, aber Sie können alles tun.

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
quelle
1

Saubere findByAndroidIdMethode, die elegant funktioniert StaleElementReference.

Dies basiert stark auf der Antwort von jspcal, aber ich musste diese Antwort ändern, damit sie mit unserem Setup sauber funktioniert, und wollte sie hier hinzufügen, falls sie für andere hilfreich ist. Wenn diese Antwort Ihnen geholfen hat, stimmen Sie bitte der Antwort von jspcal zu .

// 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
quelle
0

Dies funktioniert bei mir (zu 100%) mit 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
quelle
0

Das Problem besteht darin, dass das Element, wenn Sie es von Javascript an Java zurück an Javascript übergeben, möglicherweise das DOM verlassen hat.
Versuchen Sie, das Ganze in Javascript zu machen:

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

Versuche dies

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
quelle
0

Ich habe hier eine Lösung gefunden . In meinem Fall kann auf das Element nicht mehr zugegriffen werden, wenn das aktuelle Fenster, die Registerkarte oder die Seite verlassen und wieder angezeigt wird.

.ignoring (StaleElement ...), .refreshed (...) und elementToBeClicable (...) haben nicht geholfen, und ich habe eine Ausnahme für den act.doubleClick(element).build().perform();String erhalten.

Verwenden der Funktion in meiner Haupttestklasse:

openForm(someXpath);

Meine BaseTest-Funktion:

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

Meine BaseClass-Funktion:

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
quelle
0

Möglicherweise liegt ein potenzielles Problem vor, das zu der StaleElementReferenceException führt, die bisher niemand erwähnt hat (in Bezug auf Aktionen).

Ich erkläre es in Javascript, aber es ist das gleiche in Java.

Das wird nicht funktionieren:

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

Aber das erneute Instanziieren der Aktionen wird es lösen:

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
quelle
0

Normalerweise tritt eine StaleElementReferenceException auf, wenn ein Element angezeigt wird, auf das wir zugreifen möchten. Andere Elemente können jedoch die Position des Elements beeinflussen, an dem wir interessiert sind. Wenn wir also versuchen, auf oder auf Text zu klicken oder eine Aktion für WebElement auszuführen, erhalten wir eine Ausnahme, die normalerweise besagt, dass das Element nicht mit DOM verbunden ist .

Die Lösung, die ich versucht habe, ist wie folgt:

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

Im obigen Code versuche ich zuerst zu warten und dann auf das Element zu klicken, wenn eine Ausnahme auftritt. Dann fange ich es ab und versuche, es zu schleifen, da die Möglichkeit besteht, dass immer noch nicht alle Elemente geladen werden und erneut eine Ausnahme auftreten kann.

Rakesh Singh Chouhan
quelle
-4

Vielleicht wurde es kürzlich hinzugefügt, aber in anderen Antworten wird die implizite Wartefunktion von Selenium nicht erwähnt, die alle oben genannten Aufgaben für Sie erledigt und in Selenium integriert ist.

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

Dadurch werden findElement()Aufrufe wiederholt , bis das Element gefunden wurde, oder für 10 Sekunden.

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

Lockedan
quelle
2
Diese Lösung verhindert nicht StaleElementReferenceException
MrSpock
1
Nur um jegliche Verwirrung bei den Versionen zu beseitigen, verhindert implizit Wait () auch in der neuesten Version von Selenium NICHT die StaleElementReferenceException. Ich benutze eine Methode, die in einer Schleife mit Schlaf aufruft, bis Erfolg oder feste Anzahl.
Angsuman Chakraborty