Selenium'da "StaleElementReferenceException" nasıl önlenir?


89

Java kullanarak birçok Selenium testi uyguluyorum. Bazen testlerim a StaleElementReferenceException. Testleri daha kararlı hale getirmek için bazı yaklaşımlar önerebilir misiniz?

Yanıtlar:


91

Bu, sayfada gerçekleşen bir DOM işlemi öğenin geçici olarak erişilemez olmasına neden oluyorsa olabilir. Bu tür durumlara izin vermek için, nihayet bir istisna atmadan önce, bir döngüde öğeye birkaç kez erişmeyi deneyebilirsiniz.

Darrelgrainger.blogspot.com adresinden bu mükemmel çözümü deneyin :

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

1
Vaov! Bu tam da ihtiyacım olan şeydi. Teşekkürler!
SpartaSixZero

1
Farklı eleman referansı kullanılarak da düzeltilebilir.
Ripon Al Wasim

@jspcal, bu benim için bir cazibe gibi çalıştı! Çok teşekkürler!
Anthony Okoth

Yukarıdakiler sorunu çözmezse, en son Chromedriver'a güncelleme bizim için çözen şeydi.
Vdex

5
Bu pek çok yönden korkunç. Şu anki sorunumu çözüyor, bu yüzden teşekkürler.
Software Engineer

68

Bu sorunu ara sıra yaşıyordum. Bence BackboneJS sayfada çalışıyordu ve tıklamaya çalıştığım öğeyi değiştiriyordu. Kodum şöyle görünüyordu.

driver.findElement(By.id("checkoutLink")).click();

Tabii ki işlevsel olarak bununla aynı.

WebElement checkoutLink = driver.findElement(By.id("checkoutLink"));
checkoutLink.click();

Javascript, bulup tıklamak arasında checkoutLink öğesinin yerini alacaktı, yani.

WebElement checkoutLink = driver.findElement(By.id("checkoutLink"));
// javascript replaces checkoutLink
checkoutLink.click();

Bağlantıya tıklamaya çalışırken haklı olarak StaleElementReferenceException'a yol açtı. WebDriver'a javascript'in çalışması bitene kadar beklemesini söylemenin güvenilir bir yolunu bulamadım, bu yüzden sonunda bunu nasıl çözdüm.

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

Bu kod, tıklama başarılı olana veya zaman aşımına ulaşılana kadar StaleElementReferenceExceptions'ı yok sayarak sürekli olarak bağlantıyı tıklatmaya çalışacaktır. Bu çözümü beğendim çünkü sizi herhangi bir yeniden deneme mantığı yazmak zorunda bırakmaz ve yalnızca WebDriver'ın yerleşik yapılarını kullanır.


bu cevap kullanımdan kaldırılmıştır.
vaibhavcool20

20

Genellikle bunun nedeni DOM'nin güncellenmesi ve güncellenmiş / yeni bir öğeye erişmeye çalışmanızdır - ancak DOM yenilendi, bu nedenle sahip olduğunuz geçersiz bir referans ..

Güncellemenin tamamlandığından emin olmak için önce öğede açık bir bekleme kullanarak bunu aşın, ardından öğeye yeniden yeni bir referans alın.

İşte gösterilecek bazı psuedo kodları ( TAM bu sorun için kullandığım bazı C # kodlarından uyarlanmıştır ):

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.

Bu yardımcı olur umarım!


17

Kenny'nin çözümü iyi, ancak daha zarif bir şekilde yazılabilir

new WebDriverWait(driver, timeout)
        .ignoring(StaleElementReferenceException.class)
        .until((WebDriver d) -> {
            d.findElement(By.id("checkoutLink")).click();
            return true;
        });

Veya ayrıca:

new WebDriverWait(driver, timeout).ignoring(StaleElementReferenceException.class).until(ExpectedConditions.elementToBeClickable(By.id("checkoutLink")));
driver.findElement(By.id("checkoutLink")).click();

Her neyse, en iyi çözüm Selenide kitaplığına güvenmektir, bu tür şeyleri ve daha fazlasını halleder. (öğe referansları yerine vekilleri yönetir, böylece asla eski öğelerle uğraşmak zorunda kalmazsınız, bu oldukça zor olabilir). Selenide


Yasal Uyarı: Ben sadece mutlu selenide kullanıcıyım, hiçbir şey onun gelişimi ile yapılacak
cocorossello

İkinci çözümünüz işe yarayacaktır çünkü öğeyi bulduğunuzda değil, tıklattığınızda eskimiş olacaktır.
Rajagopalan

Bu sorunu önlemek için selenide kullanın, çok daha kolay. Selenium, bu sorun ve basit bir kullanıcı için düşük seviyeli bir API olması nedeniyle tek başına kullanılması amaçlanmamıştır
cocorossello

Bunun tamamen farkındayım. Ruby Selenium Binding etrafında bir sarmalayıcı olan WATIR kullanıyorum, WATIR tüm bu sorunları otomatik olarak halleder (örneğin eski bir öğe). Java bağlamada eşdeğer bir şey arıyorum, Selenide'yi buldum ama selenide'de örtük bekleme ve açık beklemeyi nasıl değiştireceğimi bilmiyorum. Bunu nasıl yapacağımı bana söyleyebilir misin? Ya da bana önerebileceğim herhangi bir materyal var mı? ve FluentLenium hakkındaki düşüncelerin neler?
Rajagopalan

1
İnsanlar OP tarafından seçilen cevabın 2012 yılına dayandığını bilmelidir. Son 7 yılda birçok şey değişti. Bu cevap 2019 için daha doğru.
hfontanez

11

Bunun StaleElementReferenceExceptionmeydana gelmesinin nedeni zaten açıklanmıştır: öğe ile bir şeyler bulma ve yapma arasında DOM'da yapılan güncellemeler.

Tıklama Sorunu için son zamanlarda şöyle bir çözüm kullandım:

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

Önemli bir parçası Selenyum kendi içinde "zincirleme" dir ExpectedConditionsaracılığıyla ExpectedConditions.refreshed(). Bu, söz konusu öğenin belirtilen zaman aşımı sırasında yenilenip yenilenmediğini kontrol eder ve ayrıca öğenin tıklanabilir hale gelmesini bekler.

Yenilenmiş yöntem için belgelere bir göz atın .


4

Projemde StableWebElement kavramını tanıttım. Öğenin Eski olup olmadığını algılayabilen ve orijinal öğeye yeni bir referans bulabilen WebElement için bir sarmalayıcıdır. WebElement yerine StableWebElement döndüren öğeleri bulmak için yardımcı yöntemler ekledim ve StaleElementReference ile ilgili sorun ortadan kalktı.

public static IStableWebElement FindStableElement(this ISearchContext context, By by)
{
    var element = context.FindElement(by);
    return new StableWebElement(context, element, by, SearchApproachType.First);
} 

C # kodundaki kod projemin sayfasında mevcuttur, ancak kolayca java'ya https://github.com/cezarypiatek/Tellurium/blob/master/Src/MvcPages/SeleniumUtils/StableWebElement.cs aktarılabilir.


1

C # 'da bir çözüm şöyle olacaktır:

Yardımcı sınıf:

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

Çağrı:

        DriverHelper driverHelper = new DriverHelper("http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp", 10);
        driverHelper.ClickElement("input[value='csharp']:first-child");

Benzer şekilde Java için de kullanılabilir.


1

Kenny'nin çözümü kullanımdan kaldırıldı bunu kullanın, çift tıklamak için eylemler sınıfını kullanıyorum ama her şeyi yapabilirsiniz.

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

1

findByAndroidIdİncelikle işleyen temiz yöntem StaleElementReference.

Bu, ağırlıklı olarak jspcal'ın cevabına dayanmaktadır, ancak bu cevabı, kurulumumuzla temiz bir şekilde çalışmasını sağlamak için değiştirmek zorunda kaldım ve bu yüzden başkalarına yardımcı olması durumunda buraya eklemek istedim. Bu cevap size yardımcı olduysa, lütfen jspcal'ın cevabını yükseltin .

// 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.
    }
  }
}

0

Bu benim için çalışıyor (% 100 çalışıyor) C # kullanarak

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

0

Sorun, öğeyi Javascript'ten Java'ya geri Javascript'e geçirdiğinizde DOM'dan ayrılmış olabilmesidir.
Her şeyi Javascript'te yapmayı deneyin:

driver.executeScript("document.querySelector('#my_id').click()") 

0

Bunu dene

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

0

Burada çözüm buldum . Benim durumumda mevcut pencere, sekme veya sayfadan çıkıp tekrar geri gelme durumunda eleman erişilemez hale geliyor.

.ignoring (StaleElement ...), .refreshed (...) ve elementToBeClicable (...) yardımcı olmadı ve act.doubleClick(element).build().perform();string üzerinde istisna alıyordum .

Ana test sınıfımda işlevi kullanma:

openForm(someXpath);

BaseTest işlevim:

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

Benim BaseClass işlevi:

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

0

Şimdiye kadar kimsenin bahsetmediği (eylemlerle ilgili olarak) StaleElementReferenceException'a yol açan potansiyel bir sorun olabilir.

Javascript'te açıklıyorum ama Java'da da aynı.

Bu işe yaramaz:

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()

Ancak eylemleri tekrar örneklemek sorunu çözecektir:

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()

0

Genellikle StaleElementReferenceException, erişmeye çalıştığımız öğe göründüğünde, ancak diğer öğeler ilgilendiğimiz öğenin konumunu etkileyebilir, bu nedenle tıklamaya veya getText'i tıklamaya veya WebElement üzerinde bazı eylemler yapmaya çalıştığımızda genellikle DOM ile öğe eklenmediğini söyleyen istisna alırız .

Denediğim çözüm aşağıdaki gibidir:

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

Yukarıdaki kodda, önce beklemeye ve sonra istisna oluşursa öğeyi tıklamaya çalışıyorum, sonra onu yakalıyorum ve yine de tüm öğelerin yüklenemeyebileceği ve yine istisna oluşma olasılığı olduğu için döngüye girmeye çalışıyorum.


-4

Belki daha yakın zamanda eklenmiştir, ancak diğer yanıtlar, Selenium'un yukarıda belirtilenlerin hepsini sizin için yapan ve Selenium'da yerleşik olan örtük bekleme özelliğinden bahsetmiyor.

driver.manage().timeouts().implicitlyWait(10,TimeUnit.SECONDS);

Bu yeniden deneyecek findElement() , eleman bulunana kadar veya 10 saniye boyunca aramaları .

Kaynak - http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp


2
Bu çözüm StaleElementReferenceException'ı engellemez
MrSpock

1
Selenium'un en son sürümünde bile olsa, sürümlerdeki herhangi bir karışıklığı ortadan kaldırmak için örtük olarak Wait () StaleElementReferenceException'ı ÖNLEMEZ. Başarıya veya sabit sayıma kadar uyku ile döngü içinde çağıran bir yöntem kullanıyorum.
Angsuman Chakraborty
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.