Comment éviter «StaleElementReferenceException» dans Selenium?

J’implémente beaucoup de tests Selenium en Java. Parfois, mes tests échouent en raison d’une StaleElementReferenceException . Pourriez-vous suggérer quelques approches pour rendre les tests plus stables?

Cela peut se produire si une opération DOM se produisant sur la page provoque temporairement l’access à l’élément. Pour permettre ces cas, vous pouvez essayer d’accéder à l’élément plusieurs fois dans une boucle avant de lancer une exception.

Essayez cette excellente solution de 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; } 

J’avais ce problème par intermittence. À mon insu, BackboneJS fonctionnait sur la page et remplaçait l’élément sur lequel je tentais de cliquer. Mon code ressemblait à ceci.

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

Ce qui est bien sûr fonctionnellement le même que celui-ci.

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

Ce qui arriverait occasionnellement était que le javascript remplacera l’élément checkoutLink entre la recherche et le clic, c.-à-d.

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

Ce qui a conduit à une exception StaleElementReferenceException lorsqu’on a tenté de cliquer sur le lien. Je n’ai pas trouvé de moyen fiable de dire à WebDriver d’attendre que le javascript soit terminé, voici comment je l’ai finalement résolu.

 new WebDriverWait(driver, timeout) .ignoring(StaleElementReferenceException.class) .until(new Predicate() { @Override public boolean apply(@Nullable WebDriver driver) { driver.findElement(By.id("checkoutLink")).click(); return true; } }); 

Ce code essaiera continuellement de cliquer sur le lien, en ignorant StaleElementReferenceExceptions jusqu’à ce que le clic réussisse ou que le délai d’attente soit atteint. J’aime cette solution car elle vous évite d’écrire une logique de nouvelle tentative et n’utilise que les constructions intégrées de WebDriver.

En général, cela est dû à la mise à jour du DOM et à l’access à un nouvel élément / mis à jour – mais le DOM a été rafraîchi, ce qui en fait une référence invalide.

Contournez cela en utilisant d’abord une attente explicite sur l’élément pour vous assurer que la mise à jour est terminée, puis récupérez une nouvelle référence à l’élément.

Voici un code psuedo à illustrer (Adapté à partir d’un code C # que j’utilise EXACTEMENT pour ce problème):

 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. 

J’espère que cela t’aides!

La solution de Kenny est bonne, mais elle peut être écrite de manière plus élégante

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

Ou aussi:

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

La raison pour laquelle l’ StaleElementReferenceException se produit a déjà été définie: les mises à jour du DOM entre rechercher et faire quelque chose avec l’élément.

Pour le clic problème, j’ai récemment utilisé une solution comme celle-ci:

 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 partie cruciale est le “chaînage” des propres ExpectedConditions de Selenium via le ExpectedConditions.refreshed() . Cela attend en fait et vérifie si l’élément en question a été actualisé pendant le délai spécifié et attend en outre que l’élément devienne cliquable.

Consultez la documentation de la méthode actualisée .

Une solution en C # serait:

Classe d’assistance:

 internal class DriverHelper { private IWebDriver Driver { get; set; } private WebDriverWait Wait { get; set; } public DriverHelper(ssortingng driverUrl, int timeoutInSeconds) { Driver = new ChromeDriver(); Driver.Url = driverUrl; Wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(timeoutInSeconds)); } internal bool ClickElement(ssortingng 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, ssortingng 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; } } } 

Invocation:

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

De même peut être utilisé pour Java.

Dans mon projet, j’ai introduit une notion de StableWebElement. C’est un wrapper pour WebElement qui est capable de détecter si l’élément est Stale et de trouver une nouvelle référence à l’élément d’origine. J’ai ajouté une méthode d’assistance pour localiser les éléments qui renvoient StableWebElement au lieu de WebElement et le problème avec StaleElementReference a disparu.

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

Le code en C # est disponible sur la page de mon projet mais il pourrait être facilement porté sur Java https://github.com/cezarypiatek/Tellurium/blob/master/Src/MvcPages/SeleniumUtils/StableWebElement.cs

Cela fonctionne pour moi (100% de travail) en utilisant 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; } 

Peut-être que cela a été ajouté plus récemment, mais d’autres réponses ne mentionnent pas la fonction d’attente implicite de Selenium, qui fait tout ce qui précède pour vous et qui est intégrée à Selenium.

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

Cela réessayera les findElement() jusqu’à ce que l’élément ait été trouvé ou pendant 10 secondes.

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