Ich implementiere viele Selenium-Tests mit Java. Manchmal schlagen meine Tests aufgrund von a fehl StaleElementReferenceException
. Könnten Sie einige Ansätze vorschlagen, um die Tests stabiler zu machen?
Ich implementiere viele Selenium-Tests mit Java. Manchmal schlagen meine Tests aufgrund von a fehl StaleElementReferenceException
. Könnten Sie einige Ansätze vorschlagen, um die Tests stabiler zu machen?
Antworten:
Dies kann passieren, wenn eine DOM-Operation auf der Seite vorübergehend dazu führt, dass auf das Element nicht zugegriffen werden kann. Um diese Fälle zu berücksichtigen, können Sie versuchen, mehrmals in einer Schleife auf das Element zuzugreifen, bevor Sie schließlich eine Ausnahme auslösen.
Probieren Sie diese hervorragende Lösung von darrelgrainger.blogspot.com aus :
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;
}
Ich hatte dieses Problem zeitweise. Ohne mein Wissen lief BackboneJS auf der Seite und ersetzte das Element, auf das ich klicken wollte. Mein Code sah so aus.
driver.findElement(By.id("checkoutLink")).click();
Welches ist natürlich funktional das gleiche wie dieses.
WebElement checkoutLink = driver.findElement(By.id("checkoutLink"));
checkoutLink.click();
Was gelegentlich passieren würde, war, dass das Javascript das checkoutLink-Element zwischen dem Suchen und Klicken ersetzt, d. H.
WebElement checkoutLink = driver.findElement(By.id("checkoutLink"));
// javascript replaces checkoutLink
checkoutLink.click();
Was zu Recht zu einer StaleElementReferenceException führte, wenn versucht wurde, auf den Link zu klicken. Ich konnte keine zuverlässige Möglichkeit finden, WebDriver anzuweisen, zu warten, bis das Javascript ausgeführt wurde. Hier ist, wie ich es schließlich gelöst habe.
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;
}
});
Dieser Code versucht ständig, auf den Link zu klicken, wobei StaleElementReferenceExceptions ignoriert werden, bis entweder der Klick erfolgreich ist oder das Zeitlimit erreicht ist. Ich mag diese Lösung, weil Sie keine Wiederholungslogik schreiben müssen und nur die integrierten Konstrukte von WebDriver verwenden.
Im Allgemeinen liegt dies daran, dass das DOM aktualisiert wird und Sie versuchen, auf ein aktualisiertes / neues Element zuzugreifen. Das DOM wurde jedoch aktualisiert, sodass es sich um eine ungültige Referenz handelt.
Umgehen Sie dies, indem Sie zunächst explizit auf das Element warten, um sicherzustellen, dass die Aktualisierung abgeschlossen ist, und dann erneut einen neuen Verweis auf das Element abrufen.
Hier ist ein Pseudo-Code zur Veranschaulichung (angepasst an einen C # -Code, den ich genau für dieses Problem verwende):
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.
Hoffe das hilft!
Kennys Lösung ist gut, kann aber eleganter geschrieben werden
new WebDriverWait(driver, timeout)
.ignoring(StaleElementReferenceException.class)
.until((WebDriver d) -> {
d.findElement(By.id("checkoutLink")).click();
return true;
});
Oder auch:
new WebDriverWait(driver, timeout).ignoring(StaleElementReferenceException.class).until(ExpectedConditions.elementToBeClickable(By.id("checkoutLink")));
driver.findElement(By.id("checkoutLink")).click();
Die beste Lösung ist jedoch, sich auf die Selenide-Bibliothek zu verlassen, die diese Art von Dingen und mehr erledigt. (Anstelle von Elementreferenzen werden Proxys behandelt, sodass Sie sich nie mit veralteten Elementen befassen müssen, was sehr schwierig sein kann.) Selenid
Der Grund, warum das StaleElementReferenceException
auftritt, wurde bereits dargelegt: Aktualisierungen des DOM zwischen dem Finden und Ausführen von etwas mit dem Element.
Für das Klick-Problem habe ich kürzlich eine Lösung wie diese verwendet:
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();
}
Der entscheidende Teil ist die "Verkettung" von Selenium ExpectedConditions
über die ExpectedConditions.refreshed()
. Dies wartet tatsächlich und prüft, ob das betreffende Element während des angegebenen Zeitlimits aktualisiert wurde, und wartet zusätzlich darauf, dass das Element anklickbar wird.
Schauen Sie sich die Dokumentation für die aktualisierte Methode an .
In meinem Projekt habe ich einen Begriff von StableWebElement eingeführt. Es ist ein Wrapper für WebElement, der erkennen kann, ob das Element veraltet ist, und einen neuen Verweis auf das ursprüngliche Element findet. Ich habe eine Hilfemethode zum Suchen von Elementen hinzugefügt, die StableWebElement anstelle von WebElement zurückgeben, und das Problem mit StaleElementReference ist verschwunden.
public static IStableWebElement FindStableElement(this ISearchContext context, By by)
{
var element = context.FindElement(by);
return new StableWebElement(context, element, by, SearchApproachType.First);
}
Der Code in C # ist auf der Seite meines Projekts verfügbar, kann jedoch problemlos auf Java https://github.com/cezarypiatek/Tellurium/blob/master/Src/MvcPages/SeleniumUtils/StableWebElement.cs portiert werden
Eine Lösung in C # wäre:
Hilfsklasse:
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;
}
}
}
Aufruf:
DriverHelper driverHelper = new DriverHelper("http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp", 10);
driverHelper.ClickElement("input[value='csharp']:first-child");
Ebenso kann für Java verwendet werden.
Kennys Lösung ist veraltet. Verwenden Sie diese Option. Ich verwende die Aktionsklasse zum Doppelklicken, aber Sie können alles tun.
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
Methode, die elegant funktioniert StaleElementReference
.Dies basiert stark auf der Antwort von jspcal, aber ich musste diese Antwort ändern, damit sie mit unserem Setup sauber funktioniert, und wollte sie hier hinzufügen, falls sie für andere hilfreich ist. Wenn diese Antwort Ihnen geholfen hat, stimmen Sie bitte der Antwort von jspcal zu .
// 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.
}
}
}
Dies funktioniert bei mir (zu 100%) mit 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;
}
Das Problem besteht darin, dass das Element, wenn Sie es von Javascript an Java zurück an Javascript übergeben, möglicherweise das DOM verlassen hat.
Versuchen Sie, das Ganze in Javascript zu machen:
driver.executeScript("document.querySelector('#my_id').click()")
Versuche dies
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
}
}
Ich habe hier eine Lösung gefunden . In meinem Fall kann auf das Element nicht mehr zugegriffen werden, wenn das aktuelle Fenster, die Registerkarte oder die Seite verlassen und wieder angezeigt wird.
.ignoring (StaleElement ...), .refreshed (...) und elementToBeClicable (...) haben nicht geholfen, und ich habe eine Ausnahme für den act.doubleClick(element).build().perform();
String erhalten.
Verwenden der Funktion in meiner Haupttestklasse:
openForm(someXpath);
Meine BaseTest-Funktion:
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;
}
}
Meine BaseClass-Funktion:
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))));
}
Möglicherweise liegt ein potenzielles Problem vor, das zu der StaleElementReferenceException führt, die bisher niemand erwähnt hat (in Bezug auf Aktionen).
Ich erkläre es in Javascript, aber es ist das gleiche in Java.
Das wird nicht funktionieren:
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()
Aber das erneute Instanziieren der Aktionen wird es lösen:
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()
Normalerweise tritt eine StaleElementReferenceException auf, wenn ein Element angezeigt wird, auf das wir zugreifen möchten. Andere Elemente können jedoch die Position des Elements beeinflussen, an dem wir interessiert sind. Wenn wir also versuchen, auf oder auf Text zu klicken oder eine Aktion für WebElement auszuführen, erhalten wir eine Ausnahme, die normalerweise besagt, dass das Element nicht mit DOM verbunden ist .
Die Lösung, die ich versucht habe, ist wie folgt:
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));
}
Im obigen Code versuche ich zuerst zu warten und dann auf das Element zu klicken, wenn eine Ausnahme auftritt. Dann fange ich es ab und versuche, es zu schleifen, da die Möglichkeit besteht, dass immer noch nicht alle Elemente geladen werden und erneut eine Ausnahme auftreten kann.
Vielleicht wurde es kürzlich hinzugefügt, aber in anderen Antworten wird die implizite Wartefunktion von Selenium nicht erwähnt, die alle oben genannten Aufgaben für Sie erledigt und in Selenium integriert ist.
driver.manage().timeouts().implicitlyWait(10,TimeUnit.SECONDS);
Dadurch werden findElement()
Aufrufe wiederholt , bis das Element gefunden wurde, oder für 10 Sekunden.
Quelle - http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp