Come risolvere, eccezione dell’elemento Stanti? se l’elemento non è più collegato al DOM?

Ho una domanda riguardante “L’elemento non è più collegato al DOM”.

Ho provato diverse soluzioni ma funzionano a intermittenza. Si prega di suggerire una soluzione che potrebbe essere 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); } } 

Grazie, Anu

Il problema

Il problema che probabilmente stai affrontando è che il metodo restituisce l’elemento giusto (e valido!), Ma quando stai provando ad accedervi un secondo dopo, è vecchio e getta.

Questo di solito si verifica quando:

  1. Fai clic su qualcosa che carica una nuova pagina in modo asincrono o almeno lo modifica.
  2. Immediatamente (prima che il caricamento della pagina possa finire) cerca un elemento … e lo trovi!
  3. Alla fine la pagina si scarica e il nuovo si carica.
  4. Cerchi di accedere al tuo elemento precedentemente trovato, ma ora è scaduto, anche se la nuova pagina lo contiene.

Le soluzioni

Ci sono quattro modi per risolverlo che conosco:

  1. Usa le giuste attese

    Utilizzare le attese appropriate dopo ogni carico di pagina anticipato quando si affrontano pagine asincrone. Inserisci un’attesa esplicita dopo il clic iniziale e attendi il caricamento della nuova pagina o del nuovo contenuto. Solo dopo puoi provare a cercare l’elemento che desideri. Questa dovrebbe essere la prima cosa che farai. Aumenterà notevolmente la robustezza dei test.

  2. Il modo in cui l’hai fatto

    Ho usato una variante del tuo metodo per due anni (insieme con la tecnica di cui sopra in soluzione 1) e funziona assolutamente la maggior parte del tempo e fallisce solo su strani bug di WebDriver. Prova ad accedere all’elemento trovato subito dopo averlo trovato (prima di tornare dal metodo) tramite un metodo .isDisplayed() o qualcosa del genere. Se lancia, sai già come cercare di nuovo. Se passa, hai un’altra assicurazione (falsa).

  3. Utilizzare un WebElement che si ritrova da solo quando non è aggiornato

    Scrivi un decoratore WebElement che ricordi come è stato trovato e ri-trova quando viene visitato e getta. Questo ovviamente ti costringe a usare metodi findElement() che restituiscono istanze del decoratore (o, meglio ancora, un WebDriver decorato che restituisce le tue istanze dai normali findElement() e findElemens() ). Fai cosi:

     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 } 

    Si noti che mentre penso che le informazioni foundBy dovrebbero essere accessibili dai WebElements generici per semplificare questo compito, gli sviluppatori Selenium considerano un errore provare qualcosa di simile e hanno scelto di non rendere pubbliche queste informazioni . Probabilmente è una ctriggers pratica trovare nuovamente elementi obsoleti, perché stai ri-trovare gli elementi implicitamente senza alcun meccanismo per verificare se sia giustificato. Il meccanismo di ritrovamento potrebbe potenzialmente trovare un elemento completamente diverso e non lo stesso di nuovo. Inoltre, fallisce orribilmente con findElements() quando ci sono molti elementi trovati (o non è ansible trovare nuovamente gli elementi trovati da findElements() , o ricordare quanti l’elemento è stato dall’elenco restituito).

    Penso che a volte sarebbe utile, ma è vero che nessuno userebbe mai le opzioni 1 e 2 che sono ovviamente soluzioni molto migliori per la robustezza dei test. Usali e solo dopo che sei sicuro di averne bisogno, vai a prenderlo.

  4. Utilizzare una coda di attività (in grado di rieseguire le attività precedenti)

    Implementa tutto il tuo stream di lavoro in un modo nuovo!

    • Crea una coda centrale di lavori da eseguire. Fai in modo che questa coda ricordi i lavori passati.
    • Implementa ogni operazione necessaria (“trova un elemento e fai clic su di esso”, “trova un elemento e invia le chiavi ad esso”, ecc.) Tramite il comando Modello di comando. Quando viene chiamato, aggiungi l’attività alla coda centrale che verrà eseguita (in modo sincrono o asincrono, non importa).
    • Annotare ogni attività con @LoadsNewPage , @Reversible ecc. @Reversible necessità.
    • La maggior parte delle attività gestirà le eccezioni da sole, dovrebbero essere indipendenti.
    • Quando la coda incontra un’eccezione di elemento obsoleto, prenderebbe l’ultima attività dalla cronologia dell’attività e la eseguirà nuovamente per riprovare.

    Questo ovviamente richiederebbe un grande sforzo e se non pensasse molto bene, potrebbe presto ritorcersi contro. Ho usato una (molto più complessa e potente) variante di questo per riprendere i test falliti dopo che ho riparato manualmente la pagina su cui si trovavano. In alcune condizioni (ad esempio, su una StaleElementException ), un errore non avrebbe terminato il test subito, ma avrebbe atteso (prima di uscire definitivamente dopo 15 secondi), facendo apparire una finestra informativa e dando all’utente un’opzione per l’aggiornamento manuale la pagina / fai clic con il pulsante destro / correggi il modulo / qualunque cosa. Quindi eseguirà nuovamente l’attività non riuscita o darà anche la possibilità di tornare indietro nella cronologia (ad es. @LoadsNewPage lavoro @LoadsNewPage ).


Nitpicks finali

Detto questo, la tua soluzione originale potrebbe usare un po ‘di lucidatura. Potresti combinare i due metodi in uno, più generale (o almeno renderli delegati a questo per ridurre la ripetizione del codice):

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

Con Java 7, anche un singolo blocco multicatch sarebbe sufficiente:

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

In questo modo, puoi ridurre notevolmente la quantità di codice che devi mantenere.

Risolvi questo problema 1. mantenendo l’elemento stantio e eseguendo il polling fino a quando non genera un’eccezione, e quindi 2. attendi finché l’elemento non è di nuovo visibile.

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

Se stai tentando di fare clic su link, questo ti porterà in una nuova pagina. Dopo di ciò, torna indietro e fai clic su altri collegamenti. Sotto il codice potrebbero aiutarti.

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

Soluzioni per risolverli:

  1. Memorizzare locatori per i tuoi elementi invece di riferimenti
 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. Sfrutta i ganci nelle librerie JS utilizzate
  # 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. Spostando le tue azioni in JavaScript injection
  self.driver.execute_script("$(\"li:contains('Narendra')\").click()"); 
  1. Attendere in modo proattivo che l’elemento diventi obsoleto
  # Wait till the element goes stale, this means the list has updated wait_until(lambda: is_element_stale(old_link_reference)) 

Questa soluzione, che ha funzionato per me

Quando si verifica un’eccezione di elemento obsoleto !!

L’eccezione dell’elemento stante può accadere quando le librerie che supportano tali caselle di testo / pulsanti / collegamenti sono cambiate, il che significa che gli elementi sono uguali ma il riferimento è ora cambiato nel sito Web senza influenzare i locatori. Quindi il riferimento che abbiamo memorizzato nella nostra cache incluso il riferimento alla libreria ora è diventato vecchio o obsoleto perché la pagina è stata aggiornata con le librerie aggiornate.

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

Per Fitnesse puoi usare:

| start | Smart Web Driver | selenium.properties |

@Fixture (name = “Smart Web Driver”) SmartWebDriver di class pubblica estende 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; } 

}

https://www.swtestacademy.com/selenium-wait-javascript-angular-ajax/ Ecco un buon articolo sulle strategie dinamiche dei camerieri. Il tuo problema non sta aspettando correttamente tutte le chiamate ajax, jquery o angolari. Quindi si finisce con StaleElementException.

Se il tuo approccio è utilizzare il meccanismo Try-Catch, immagino che abbia un difetto. Non dovresti fare affidamento su quella struttura perché non saprai mai che funzionerà nella clausola catch.

Il selenium ti dà l’opportunità di effettuare chiamate javascript. Puoi eseguire

  • “return jQuery.active == 0”
  • return angular.element (document) .injector (). get (‘$ http’). pendingRequests.length === 0 ”
  • “return document.readyState”
  • “return angular.element (document) .injector () === undefined”

comandi solo per verificare l’esistenza e gli stati di quelle chiamate.

Puoi farlo prima di qualsiasi operazione di findBy in modo che lavori sempre con la pagina più recente