J'implémente de nombreux tests Selenium avec Java. Parfois, mes tests échouent à cause d'une StaleElementReferenceException
. Pouvez-vous suggérer quelques approches pour rendre les tests plus stables?
Cela peut se produire si une opération DOM sur la page rend temporairement l'élément inaccessible. Pour permettre ces cas, vous pouvez essayer d'accéder à l'élément plusieurs fois dans une boucle avant de finalement 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 s'exécutait 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 identique à celui-ci.
WebElement checkoutLink = driver.findElement(By.id("checkoutLink"));
checkoutLink.click();
Ce qui arrivait parfois était que le javascript remplaçait l'élément checkoutLink entre trouver et cliquer dessus, c'est-à-dire.
WebElement checkoutLink = driver.findElement(By.id("checkoutLink"));
// javascript replaces checkoutLink
checkoutLink.click();
Ce qui, à juste titre, a conduit à une exception StaleElementReferenceException lorsque vous essayez de cliquer sur le lien. Je ne trouvais aucun moyen fiable de dire à WebDriver d'attendre que le javascript soit terminé. Voici comment j'ai finalement résolu le problème.
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;
}
});
Ce code essaiera en permanence de cliquer sur le lien, en ignorant StaleElementReferenceExceptions jusqu'à ce que le clic soit réussi ou que le délai soit atteint. J'aime cette solution car elle vous évite d'écrire toute logique de nouvelle tentative et utilise uniquement les constructions intégrées de WebDriver.
Généralement, cela est dû au fait que le DOM est mis à jour et que vous essayez d'accéder à un élément mis à jour/nouveau - mais le DOM est actualisé, ce qui en fait une référence non valide.
Contournez ceci 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 quelques exemples de code psuedo (Adapté du code C # que j’utilise pourEXACTEMENTce 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();
Quoi qu'il en soit, la meilleure solution consiste à faire confiance à la bibliothèque Selenide, qui gère ce genre de choses et plus encore. (au lieu de références d’éléments, il gère les mandataires pour que vous n’ayez jamais à traiter des éléments périmés, ce qui peut être assez difficile) Selenide
La raison pour laquelle la variable StaleElementReferenceException
se produit a déjà été définie: mises à jour du DOM entre la recherche et l'utilisation de l'élément.
Pour le problème du clic, 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" de la ExpectedConditions
propre à Selenium via la ExpectedConditions.refreshed()
. En fait, cela attend et vérifie si l'élément en question a été actualisé pendant le délai spécifié et attend également que l'élément devienne cliquable.
Consultez la documentation pour la méthode actualisée .
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 à la localisation des é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 facilement être porté en Java https://github.com/cezarypiatek/Tellurium/blob/master/Src/MvcPages/SeleniumUtils/StableWebElement.cs
Une solution en C # serait:
Classe d'assistance:
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;
}
}
}
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.
La solution de Kenny est obsolète, utilisez-la. J'utilise une classe d'actions pour double-cliquer, mais vous pouvez tout faire.
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;
}
});
Le problème est qu'au moment où vous passez l'élément de Javascript à Java, il peut avoir quitté le DOM.
Essayez de faire le tout en Javascript:
driver.executeScript("document.querySelector('#my_id').click()")
Essaye ça
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
}
}
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;
}
J'ai trouvé la solution ici . Dans mon cas, l'élément devient inaccessible en cas de sortie de la fenêtre, de l'onglet ou de la page en cours.
.ignoring (StaleElement ...), .refresh (...) et elementToBeClicable (...) n'ont pas aidé et j'ai eu une exception sur act.doubleClick(element).build().perform();
chaîne.
Utilisation de la fonction dans ma classe de test principale:
openForm(someXpath);
Fonction My 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;
}
}
Fonction 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))));
}