Sto implementando molti test sul selenio usando Java. A volte, i miei test falliscono a causa di un file StaleElementReferenceException
. Potresti suggerire alcuni approcci per rendere i test più stabili?
Sto implementando molti test sul selenio usando Java. A volte, i miei test falliscono a causa di un file StaleElementReferenceException
. Potresti suggerire alcuni approcci per rendere i test più stabili?
Risposte:
Ciò può accadere se un'operazione DOM in corso sulla pagina rende temporaneamente inaccessibile l'elemento. Per consentire questi casi, puoi provare ad accedere all'elemento più volte in un ciclo prima di lanciare finalmente un'eccezione.
Prova questa eccellente soluzione da 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;
}
Ho riscontrato questo problema a intermittenza. A mia insaputa, BackboneJS era in esecuzione sulla pagina e stava sostituendo l'elemento su cui stavo cercando di fare clic. Il mio codice sembrava così.
driver.findElement(By.id("checkoutLink")).click();
Il che ovviamente è funzionalmente lo stesso di questo.
WebElement checkoutLink = driver.findElement(By.id("checkoutLink"));
checkoutLink.click();
Quello che a volte accadeva era che javascript sostituisse l'elemento checkoutLink tra la ricerca e il clic, ad es.
WebElement checkoutLink = driver.findElement(By.id("checkoutLink"));
// javascript replaces checkoutLink
checkoutLink.click();
Che giustamente ha portato a un'eccezione StaleElementReferenceException quando si tenta di fare clic sul collegamento. Non sono riuscito a trovare alcun modo affidabile per dire a WebDriver di attendere fino al termine dell'esecuzione di javascript, quindi ecco come alla fine l'ho risolto.
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;
}
});
Questo codice proverà continuamente a fare clic sul collegamento, ignorando StaleElementReferenceExceptions fino a quando il clic non riesce o il timeout viene raggiunto. Mi piace questa soluzione perché ti evita di dover scrivere qualsiasi logica di ripetizione e utilizza solo i costrutti incorporati di WebDriver.
In genere ciò è dovuto all'aggiornamento del DOM e al tentativo di accedere a un elemento nuovo / aggiornato, ma il DOM è stato aggiornato quindi è un riferimento non valido che hai ..
Aggira questo problema usando prima un'attesa esplicita sull'elemento per assicurarti che l'aggiornamento sia completo, quindi prendi di nuovo un nuovo riferimento all'elemento.
Ecco un po 'di codice psuedo per illustrare (adattato da un codice C # che uso ESATTAMENTE per questo problema):
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.
Spero che sia di aiuto!
La soluzione di Kenny è buona, tuttavia può essere scritta in modo più elegante
new WebDriverWait(driver, timeout)
.ignoring(StaleElementReferenceException.class)
.until((WebDriver d) -> {
d.findElement(By.id("checkoutLink")).click();
return true;
});
O anche:
new WebDriverWait(driver, timeout).ignoring(StaleElementReferenceException.class).until(ExpectedConditions.elementToBeClickable(By.id("checkoutLink")));
driver.findElement(By.id("checkoutLink")).click();
Ma comunque, la soluzione migliore è affidarsi alla libreria Selenide, gestisce questo genere di cose e altro ancora. (invece dei riferimenti agli elementi gestisce i proxy, quindi non devi mai avere a che fare con elementi obsoleti, il che può essere piuttosto difficile). Selenide
Il motivo per cui si StaleElementReferenceException
verifica è già stato spiegato : aggiornamenti al DOM tra la ricerca e l'esecuzione di qualcosa con l'elemento.
Per il problema del clic ho usato di recente una soluzione come questa:
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 parte cruciale è il "concatenamento" del selenio ExpectedConditions
tramite il ExpectedConditions.refreshed()
. Questo in realtà attende e controlla se l'elemento in questione è stato aggiornato durante il timeout specificato e attende inoltre che l'elemento diventi cliccabile.
Dai un'occhiata alla documentazione per il metodo aggiornato .
Nel mio progetto ho introdotto una nozione di StableWebElement. È un wrapper per WebElement che è in grado di rilevare se l'elemento è obsoleto e trovare un nuovo riferimento all'elemento originale. Ho aggiunto un metodo di supporto per individuare gli elementi che restituiscono StableWebElement invece di WebElement e il problema con StaleElementReference è scomparso.
public static IStableWebElement FindStableElement(this ISearchContext context, By by)
{
var element = context.FindElement(by);
return new StableWebElement(context, element, by, SearchApproachType.First);
}
Il codice in C # è disponibile sulla pagina del mio progetto ma potrebbe essere facilmente portato su java https://github.com/cezarypiatek/Tellurium/blob/master/Src/MvcPages/SeleniumUtils/StableWebElement.cs
Una soluzione in C # sarebbe:
Classe di aiuto:
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;
}
}
}
Invocazione:
DriverHelper driverHelper = new DriverHelper("http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp", 10);
driverHelper.ClickElement("input[value='csharp']:first-child");
Allo stesso modo può essere utilizzato per Java.
La soluzione di Kenny è deprecata, usa questa, sto usando la classe delle azioni per fare doppio clic ma puoi fare qualsiasi cosa.
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;
}
});
findByAndroidId
Metodo pulito che gestisce con grazia StaleElementReference
.Questo è fortemente basato sulla risposta di jspcal, ma ho dovuto modificare quella risposta per farlo funzionare in modo pulito con la nostra configurazione e quindi volevo aggiungerla qui nel caso fosse utile ad altri. Se questa risposta ti ha aiutato, per favore vai a votare la risposta di jspcal .
// 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.
}
}
}
Questo funziona per me (funziona al 100%) usando 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;
}
Il problema è che quando passi l'elemento da Javascript a Java di nuovo a Javascript, può aver lasciato il DOM.
Prova a fare tutto in Javascript:
driver.executeScript("document.querySelector('#my_id').click()")
Prova questo
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
}
}
Ho trovato la soluzione qui . Nel mio caso l'elemento diventa inaccessibile in caso di uscita dalla finestra, scheda o pagina corrente e ritorno di nuovo.
.ignoring (StaleElement ...), .refreshed (...) e elementToBeClicable (...) non hanno aiutato e stavo ottenendo un'eccezione sulla act.doubleClick(element).build().perform();
stringa.
Utilizzo della funzione nella mia classe di test principale:
openForm(someXpath);
La mia funzione 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;
}
}
La mia funzione 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))));
}
Potrebbe esserci un potenziale problema che porta all'eccezione StaleElementReferenceException che nessuno ha menzionato finora (in merito alle azioni).
Lo spiego in Javascript, ma è lo stesso in Java.
Questo non funzionerà:
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()
Ma istanziare nuovamente le azioni risolverà il problema:
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()
Di solito StaleElementReferenceException quando l'elemento a cui tentiamo di accedere è apparso ma altri elementi possono influenzare la posizione dell'elemento a cui siamo interessati, quindi quando proviamo a fare clic o getText o proviamo a fare qualche azione su WebElement otteniamo un'eccezione che di solito dice che l'elemento non è collegato a DOM .
La soluzione che ho provato è la seguente:
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));
}
Nel codice sopra provo prima ad aspettare e quindi a fare clic sull'elemento se si verifica un'eccezione, quindi lo prendo e provo a riprodurlo in loop poiché esiste la possibilità che tutti gli elementi non vengano caricati e che si verifichi nuovamente un'eccezione.
Forse è stato aggiunto più di recente, ma altre risposte non menzionano la funzione di attesa implicita di Selenium, che fa tutto quanto sopra per te ed è integrata in Selenium.
driver.manage().timeouts().implicitlyWait(10,TimeUnit.SECONDS);
Questo riproverà findElement()
chiamate fino a quando l'elemento non è stato trovato o per 10 secondi.
Fonte: http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp