web-dev-qa-db-fra.com

Comment résoudre, exception d'élément obsolète? si l'élément n'est plus attaché au DOM?

J'ai une question concernant "L'élément n'est plus rattaché au DOM".

J'ai essayé différentes solutions mais elles fonctionnent de manière intermittente. Veuillez suggérer une solution qui pourrait être permanente.

WebElement getStaleElemById(String id, WebDriver driver) {
    try {
        return driver.findElement(By.id(id));
    } catch (StaleElementReferenceException e) {
        System.out.println("Attempting to recover from StaleElementReferenceException ...");
        return getStaleElemById(id, driver);
    }
}

WebElement getStaleElemByCss(String css, WebDriver driver) {
    try {
        return driver.findElement(By.cssSelector(css));
    } catch (StaleElementReferenceException e) {
        System.out.println("Attempting to recover from StaleElementReferenceException ...");
        return getStaleElemByCss(css, driver);
    } catch (NoSuchElementException ele) {
         System.out.println("Attempting to recover from NoSuchElementException ...");
         return getStaleElemByCss(css, driver);
    }
}

Merci, Anu

14
user2498024

Le problème

Le problème que vous rencontrez probablement est que la méthode retourne le bon (et valide!) Élément, mais lorsque vous essayez d'y accéder une seconde plus tard, il est périmé et jette.

Cela se produit généralement lorsque:

  1. Vous cliquez sur quelque chose qui charge une nouvelle page de manière asynchrone ou du moins la change.
  2. Vous devez immédiatement (avant la fin du chargement de la page) rechercher un élément ... et vous le trouvez!
  3. La page se décharge enfin et la nouvelle se charge.
  4. Vous essayez d'accéder à votre élément précédemment trouvé, mais il est maintenant obsolète, même si la nouvelle page le contient également.

Les solutions

Je connais quatre façons de le résoudre:

  1. Utilisez les temps d'attente appropriés

    Utilisez les temps d'attente appropriés après chaque chargement de page prévu lorsque vous faites face à des pages asynchrones. Insérez une attente explicite après le clic initial et attendez le chargement de la nouvelle page/du nouveau contenu. Ce n’est qu’après cela que vous pourrez essayer de rechercher l’élément souhaité. Cela devrait être la première chose que vous ferez. Cela augmentera considérablement la robustesse de vos tests.

  2. La façon dont vous l'avez fait

    J'utilise une variante de votre méthode depuis deux ans (avec la technique ci-dessus dans la solution 1) et cela fonctionne absolument la plupart du temps et n'échoue que sur des bugs étranges de WebDriver. Essayez d'accéder à l'élément trouvé juste après sa découverte (avant de revenir de la méthode) via une méthode .isDisplayed() ou quelque chose de ce genre. Si cela se produit, vous savez déjà comment effectuer une nouvelle recherche. Si cela réussit, vous avez une autre (fausse) assurance.

  3. Utilisez un WebElement qui se retrouve quand il est périmé

    Ecrivez un décorateur WebElement qui se souvient de la façon dont il a été trouvé et retrouvez-le lorsque vous y accédez et le lance. Cela vous oblige évidemment à utiliser des méthodes personnalisées findElement() qui renverraient des instances de votre décorateur (ou, mieux encore, une WebDriver décorée qui renverrait vos instances à partir des méthodes habituelles findElement() et findElemens()). Fais-le comme ça:

    public class NeverStaleWebElement implements WebElement {
        private WebElement element;
        private final WebDriver driver;
        private final By foundBy;
    
        public NeverStaleWebElement(WebElement element, WebDriver driver, By foundBy) {
            this.element = element;
            this.driver = driver;
            this.foundBy = foundBy;
        }
    
        @Override
        public void click() {
            try {
                element.click();
            } catch (StaleElementReferenceException e) {
                // log exception
    
                // assumes implicit wait, use custom findElement() methods for custom behaviour
                element = driver.findElement(foundBy);
    
                // recursion, consider a conditioned loop instead
                click();
            }
        }
    
        // ... similar for other methods, too
    
    }
    

    Notez que, même si je pense que les informations foundBy devraient être accessibles à partir des WebElements génériques, les développeurs de Selenium considèrent qu’il est erroné d’essayer quelque chose de ce genre et ont choisi de ne pas rendre cette information publique . Retrouver des éléments périmés est sans doute une mauvaise pratique, car vous redécouvrez implicitement des éléments sans aucun mécanisme permettant de vérifier si cela est justifié. Le mécanisme de repérage pourrait potentiellement trouver un élément complètement différent et non le même. De plus, il échoue horriblement avec findElements() quand il y a beaucoup d'éléments trouvés (vous devez soit interdire la recherche sur les éléments trouvés par findElements(), soit vous rappeler le nombre de fois que votre élément était issu de la List renvoyée).

    Je pense que cela peut être utile parfois, mais il est vrai que personne n’utilisera jamais les options 1 et 2, qui sont évidemment de bien meilleures solutions pour la robustesse de vos tests. Utilisez-les et ne vous en faites que si vous en avez besoin.

  4. Utiliser une file d'attente de tâches (pouvant réexécuter des tâches antérieures)

    Mettez en œuvre votre flux de travail entier d'une nouvelle manière!

    • Créez une file d'attente centrale de travaux à exécuter. Faites en sorte que cette file d'attente se souvienne des travaux passés.
    • Implémentez toutes les tâches nécessaires ("rechercher un élément et cliquer dessus", "rechercher un élément et lui envoyer des clés", etc.) via le modèle de commande. Une fois appelé, ajoutez la tâche à la file d'attente centrale qui l'exécutera ensuite (de manière synchrone ou asynchrone, cela n'a pas d'importance).
    • Annotez chaque tâche avec @LoadsNewPage, @Reversible etc. au besoin.
    • La plupart de vos tâches gèrent leurs exceptions par elles-mêmes, elles doivent donc être autonomes.
    • Lorsque la file d'attente rencontrait une exception d'élément périmé, elle récupérait la dernière tâche de l'historique des tâches et la réexécutait pour réessayer.

    Cela demanderait évidemment beaucoup d'efforts et, si on n'y réfléchissait pas très bien, on pourrait se retourner bientôt. J'ai utilisé une variante (beaucoup plus complexe et puissante) de ceci pour reprendre des tests ayant échoué après avoir corrigé manuellement la page sur laquelle ils se trouvaient. Sous certaines conditions (par exemple, sur une StaleElementException), un échec ne met pas fin au test immédiatement, mais attend (avant l'expiration du délai d'attente au bout de 15 secondes), ce qui ouvre une fenêtre d'informations et donne à l'utilisateur la possibilité d'actualiser manuellement la page/cliquez sur le bouton droit/corrigez le formulaire/peu importe. Il relancerait ensuite la tâche ayant échoué ou donnerait même la possibilité de revenir en arrière dans l’historique (par exemple, jusqu’au dernier travail @LoadsNewPage).


Nitpicks finales

Cela dit, votre solution initiale pourrait nécessiter un peu de polissage. Vous pouvez combiner les deux méthodes en une seule, plus générale (ou au moins leur faire déléguer à celle-ci pour réduire la répétition de code):

WebElement getStaleElem(By by, WebDriver driver) {
    try {
        return driver.findElement(by);
    } catch (StaleElementReferenceException e) {
        System.out.println("Attempting to recover from StaleElementReferenceException ...");
        return getStaleElem(by, driver);
    } catch (NoSuchElementException ele) {
        System.out.println("Attempting to recover from NoSuchElementException ...");
        return getStaleElem(by, driver);
    }
}

Avec Java 7, même un seul bloc multicatch suffirait:

WebElement getStaleElem(By by, WebDriver driver) {
    try {
        return driver.findElement(by);
    } catch (StaleElementReferenceException | NoSuchElementException e) {
        System.out.println("Attempting to recover from " + e.getClass().getSimpleName() + "...");
        return getStaleElem(by, driver);
    }
}

De cette façon, vous pouvez réduire considérablement la quantité de code que vous devez gérer.

41
Petr Janeček

Je résous cela en 1. gardant l'élément périmé et l'interroge jusqu'à ce qu'il lève une exception, puis 2. attendons que l'élément soit à nouveau visible.

    boolean isStillOnOldPage = true;
    while (isStillOnOldPage) {
        try {
            theElement.getAttribute("whatever");
        } catch (StaleElementReferenceException e) {
            isStillOnOldPage = false;
        }
    }
    WebDriverWait wait = new WebDriverWait(driver, 15);
    wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("theElementId")));
1
Felix Dobslaw

Solutions pour les résoudre:

  1. Stocker des localisateurs sur vos éléments au lieu de références
driver = webdriver.Firefox();
driver.get("http://www.github.com");
search_input = lambda: driver.find_element_by_name('q');
search_input().send_keys('hello world\n'); 
time.sleep(5);


search_input().send_keys('hello frank\n') // no stale element exception
  1. Tirer parti des points d'ancrage dans les bibliothèques JS utilisées
   # Using Jquery queue to get animation queue length.
    animationQueueIs = """
    return $.queue( $("#%s")[0], "fx").length;
    """ % element_id
    wait_until(lambda: self.driver.execute_script(animationQueueIs)==0)
  1. Transférer vos actions dans l'injection JavaScript
 self.driver.execute_script("$(\"li:contains('Narendra')\").click()");
  1. Attendre de manière proactive que l'élément soit périmé
  # Wait till the element goes stale, this means the list has updated
  wait_until(lambda: is_element_stale(old_link_reference))

Cette solution qui a fonctionné pour moi

0
NarendraC

Si vous essayez de cliquer sur le lien, cela vous mènera à une nouvelle page. Après cela, revenir en arrière et en cliquant sur d'autres liens. Ils ci-dessous le code peut vous aider.

public int getNumberOfElementsFound(By by) {
    return  driver.findElements(by).size();
  }

public WebElement getElementWithIndex(By by, int pos) {
    return driver.findElements(by).get(pos);
  }

/**click on each link */
public void getLinks()throws Exception{
try {
List<WebElement> componentList = driver.findElements(By.tagName("a"));
System.out.println(componentList.size()); 

    for (WebElement component : componentList)
    {
        //click1();
        System.out.println(component.getAttribute("href"));
    }
 int numberOfElementsFound = getNumberOfElementsFound(By.tagName("a"));
for (int pos = 0; pos < numberOfElementsFound; pos++) {
     if (getElementWithIndex(By.tagName("a"), pos).isDisplayed()){

  getElementWithIndex(By.tagName("a"), pos).click();
  Thread.sleep(200);
  driver.navigate().back();
  Thread.sleep(200);                                                       
}
  }
    }catch (Exception e){
        System.out.println("error in getLinks "+e);
    }
}
0
Shammi

Pour Fitnesse, vous pouvez utiliser:

| démarrer | Smart Web Driver | Selenium.properties |

@Fixture (name = "Smart Web Driver") Public class SmartWebDriver étend SlimWebDriver {

private final static Logger LOG = LoggerFactory.getLogger(SmartWebDriver.class);

/**
 * Constructs a new SmartWebDriver.
 */
@Start(name = "Start Smart Web Driver", arguments = {"configuration"}, example = "|start |Smart Web Driver| Selenium.properties|")
public SmartWebDriver(String configuration) {
    super(configuration);
}

/**
 * Waits for an element to become invisible (meaning visible and width and height != 0).
 *
 * @param locator the locator to use to find the element.
 */
@Command(name = "smartWaitForNotVisible", arguments = {"locator"}, example = "|smartWaitForNotVisible; |//path/to/input (of css=, id=, name=, classname=, link=, partiallink=)|")
public boolean smartWaitForNotVisible(String locator) {
    try {
        waitForNotVisible(locator);
    } catch (StaleElementReferenceException sere) {
        LOG.info("Element with locator '%s' did not become invisible (visible but also width and height != 0), a StaleElementReferenceException occurred, trying to continue...", locator);
    } catch (NoSuchElementException ele) {
        LOG.info("Element with locator '%s' did not become invisible (visible but also width and height != 0), a NoSuchElementException occurred, trying to continue...", locator);
    } catch (AssertionError ae) {
        if (ae.getMessage().contains("No element found")) {
            LOG.info("Element with locator '%s' did not become invisible (visible but also width and height != 0), a AssertionError occurred, trying to continue...", locator);
        } else {
            throw ae;
        }
    }
    return true;
}

}

0
Koenraad Appelo

Quand une exception d'élément obsolète se produit !!

Une exception d'élément obsolète peut se produire lorsque les bibliothèques prenant en charge ces zones de texte/boutons/liens ont changé, ce qui signifie que les éléments sont identiques mais que la référence a maintenant été modifiée sur le site Web sans affecter les localisateurs. Ainsi, la référence que nous avons stockée dans notre cache, y compris la référence de bibliothèque, est devenue ancienne ou obsolète car la page a été actualisée avec des bibliothèques mises à jour.

for(int j=0; j<5;j++)
try {
    WebElement elementName=driver.findElement(By.xpath(“somexpath”));
    break;
} catch(StaleElementReferenceException e){
e.toString();
System.out.println(“Stale element error, trying ::  ” + e.getMessage());
}
elementName.sendKeys(“xyz”);
0
Nag Raj