Rasgele “Öğe artık DOM'a bağlı değil” StaleElementReferenceException


143

Ben sadece umuyorum, ama Selenium Webdriver tam bir kabus gibi görünüyor. Chrome web sürücüsü şu anda kullanılamıyor ve diğer sürücüler oldukça güvenilmez veya öyle görünüyor. Ben birçok sorunla mücadele ediyorum, ama işte bir tane.

Rastgele, testlerim bir

"org.openqa.selenium.StaleElementReferenceException: Element is no longer attached 
to the DOM    
System info: os.name: 'Windows 7', os.arch: 'amd64',
 os.version: '6.1', java.version: '1.6.0_23'"

Webdriver 2.0b3 sürümlerini kullanıyorum. Bunun FF ve IE sürücüleri ile olduğunu gördüm. Bunu önleyebilmemin tek yolu Thread.sleep, istisna oluşmadan önce gerçek bir çağrı eklemektir . Bu kötü bir çözüm olsa da, bu yüzden birinin benim tarafımdaki tüm bunları daha iyi hale getirecek bir hataya işaret edebileceğini umuyorum.


26
Umarım 17 bin görüş sadece bunun olmadığını gösterir;) Bu, en sinir bozucu Selenyum istisnası olmalı.
Mark Mayo

4
48k şimdi! Aynı problemim var ...
Gal

3
Selenyum'un saf ve tam bir çöp olduğunu görüyorum ....
C Johnson

4
60k, hala bir sorun :)
Pieter De Bie

benim durumumdan from selenium.common.exceptions import NoSuchElementException
dolayıydı

Yanıtlar:


119

Evet, StaleElementReferenceExceptions ile ilgili sorun yaşıyorsanız, bunun nedeni testlerinizin kötü yazılmış olmasıdır. Bu bir yarış koşulu. Aşağıdaki senaryoyu düşünün:

WebElement element = driver.findElement(By.id("foo"));
// DOM changes - page is refreshed, or element is removed and re-added
element.click();

Şimdi öğeyi tıkladığınız noktada, öğe referansı artık geçerli değil. WebDriver'ın bunun olabileceği tüm durumlar hakkında iyi bir tahmin yapması imkansızdır - bu yüzden testlerini / uygulama yazarı olarak tam olarak ne olacağını veya olmayacağını kimin bilmesi gerektiğini kontrol eder. Yapmak istediğiniz şey, DOM'nin bir şeylerin değişmeyeceğini bildiğiniz bir duruma gelene kadar açıkça beklemektir. Örneğin, belirli bir öğenin var olmasını beklemek için bir WebDriverWait kullanma:

// times out after 5 seconds
WebDriverWait wait = new WebDriverWait(driver, 5);

// while the following loop runs, the DOM changes - 
// page is refreshed, or element is removed and re-added
wait.until(presenceOfElementLocated(By.id("container-element")));        

// now we're good - let's click the element
driver.findElement(By.id("foo")).click();

PresenceOfElementLocated () yöntemi şuna benzer:

private static Function<WebDriver,WebElement> presenceOfElementLocated(final By locator) {
    return new Function<WebDriver, WebElement>() {
        @Override
        public WebElement apply(WebDriver driver) {
            return driver.findElement(locator);
        }
    };
}

Mevcut Chrome sürücüsünün oldukça kararsız olduğu konusunda oldukça haklısınız ve Selenium bagajının, uygulamanın çoğunun ağaçlarının bir parçası olarak Chromium geliştiricileri tarafından yapıldığı, yeniden yazılmış bir Chrome sürücüsüne sahip olduğunu duymaktan memnuniyet duyacaksınız.

PS. Alternatif olarak, yukarıdaki örnekte olduğu gibi açıkça beklemek yerine, örtülü beklemeleri etkinleştirebilirsiniz - bu şekilde WebDriver, öğenin mevcut olmasını bekleyen belirtilen zaman aşımına kadar her zaman döngü yapar:

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

Deneyimlerime göre, açıkça beklemek her zaman daha güvenilirdir.


2
Elemanları değişkenlere okumanın ve tekrar kullanmanın artık mümkün olmadığını söylemek doğru mudur? Çünkü öğeleri geçirmeye dayanan büyük bir kuru ve dinamik WATiR DSL'im var ve web sürücüsüne bağlanmaya çalışıyorum, ama aynı sorunu yaşıyorum. Esasen DOM'u değiştiren her test adımı için modüldeki tüm öğeleri yeniden okumak için kod eklemeliyim ...
kinofrost

Selam. Bu örnekte ne tür bir İşlev olduğunu sorabilir miyim? Onu bulamıyorum .... TEŞEKKÜRLER!
Hannibal

1
@Hannibal:, Guavacom.google.common.base.Function<F, T> tarafından sağlanmıştır .
Stephan202

@jarib, çözümünüzden bir yıl sonra aynı sorunla karşı karşıyayım. Sorun şu ki, scriptlerimi ruby ​​ile yazıyorum ve 'presenceOfElementLocated' adında bir işlev veya benzeri bir şey yok. Herhangi bir tavsiye?
Amey

56
@jarib Bunun hepsinin kötü tasarlanmış testten kaynaklandığına katılmıyorum. Öğe bir AJAX çağrısından sonra göründükten sonra bile StaleElementReferenceException özelliğine neden olabilecek hala jQuery kodu çalışıyor olabilir. Ve çok hoş görünmeyen açık bekleme eklemek dışında yapabileceğiniz bir şey yok. Bunun WebDriver'daki bir tasarım kusuru olduğunu düşünüyorum
munch

10

Böyle bir yöntemi biraz başarı ile kullanabildim:

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

Evet, artık eski (taze?) Olarak değerlendirilene kadar öğeyi sorgulamaya devam ediyor. Sorunun köküne gerçekten ulaşmıyor, ancak WebDriver'ın bu istisnayı atmak konusunda oldukça seçici olabileceğini buldum - bazen anlıyorum ve bazen de anlayamıyorum. Veya DOM gerçekten değişiyor olabilir.

Dolayısıyla, yukarıdaki cevaba tam olarak katılmıyorum, bunun mutlaka kötü yazılmış bir test olduğunu gösterir. Hiçbir şekilde etkileşimde bulunmadığım yeni sayfalarda buldum. Ben DOM nasıl temsil veya WebDriver bayat olarak kabul ne bazı lapa lapa olduğunu düşünüyorum.


7
Bu kodda bir hata var, bir tür kapak olmadan yöntemi tekrar tekrar çağırmaya devam etmemelisiniz yoksa yığınızı patlatacaksınız.
Harry

2
Ben bir sayaç ya da bir şey eklemek daha iyi olduğunu düşünüyorum, bu yüzden hatayı tekrar tekrar aldığımızda, aslında hatayı atabiliriz. Aksi takdirde gerçekten bir hata varsa, bir döngü ile
sonuçlanacaksınız

Bunun kötü yazılmış testlerin sonucu olmadığını kabul ediyorum. Selenium'un bunu en iyi yazılı testler için bile modern web sitelerinde yapma eğilimi vardır - muhtemelen web siteleri, herhangi bir değişiklik yapılmasa bile reaktif web uygulaması çerçevelerinde yaygın olan iki yönlü bağlamalar yoluyla öğelerini sürekli olarak yenilediği için bu unsurların yapılması gerekir. Bunun gibi bir yöntem, modern bir web uygulamasını test eden her Selenyum çerçevesinin bir parçası olmalıdır.
emery

10

AJAX güncellemeleri yarıya indiğinde bazen bu hatayı alıyorum. Capybara, DOM değişikliklerini beklemede oldukça akıllı görünüyor (bkz. Wait_until, Capybara'dan neden kaldırıldı ), ancak benim durumumda varsayılan 2 saniye bekleme süresi yeterli değildi. _Spec_helper.rb_ biçiminde ör.

Capybara.default_max_wait_time = 5

2
Bu da benim sorunumu çözdü: Bir StaleElementReferenceError alıyordum ve Capybara.default_max_wait_time artırmak sorunu çözdü.
brendan

1

Bugün aynı sorunla karşı karşıya kaldım ve öğe başvurusunun hala geçerli olup olmadığını her yöntemden önce kontrol eden bir sarıcı sınıfı oluşturdum. Öğeyi kurtarmak için benim çözüm oldukça basit, bu yüzden sadece paylaşmak düşündüm.

private void setElementLocator()
{
    this.locatorVariable = "selenium_" + DateTimeMethods.GetTime().ToString();
    ((IJavaScriptExecutor)this.driver).ExecuteScript(locatorVariable + " = arguments[0];", this.element);
}

private void RetrieveElement()
{
    this.element = (IWebElement)((IJavaScriptExecutor)this.driver).ExecuteScript("return " + locatorVariable);
}

Görüyorum i "bulmak" veya daha doğrusu genel bir js değişkeni öğeyi kaydetmek ve gerekirse öğeyi almak. Sayfa yeniden yüklenirse bu başvuru artık çalışmaz. Ancak, kıyamete sadece değişiklikler yapıldığı sürece, referans kalır. Ve bu çoğu durumda işi yapmalıdır.

Ayrıca elemanın tekrar aranmasını önler.

John


1

Aynı problemi yaşadım ve benimki eski bir selenyum versiyonundan kaynaklanıyordu. Geliştirme ortamı nedeniyle daha yeni bir sürüme güncelleme yapamıyorum. Soruna HTMLUnitWebElement.switchFocusToThisIfNeeded () neden olmaktadır. Yeni bir sayfaya gittiğinizde, eski sayfada tıkladığınız öğeninoldActiveElement (aşağıya bakınız) görülebilir. Selenyum eski öğeden bağlam almaya çalışır ve başarısız olur. Bu yüzden gelecekteki sürümlerde bir deneme yakalama yaptılar.

Selenyum-htmlunit-sürücü sürümü <2.23.0 kodu:

private void switchFocusToThisIfNeeded() {
    HtmlUnitWebElement oldActiveElement =
        ((HtmlUnitWebElement)parent.switchTo().activeElement());

    boolean jsEnabled = parent.isJavascriptEnabled();
    boolean oldActiveEqualsCurrent = oldActiveElement.equals(this);
    boolean isBody = oldActiveElement.getTagName().toLowerCase().equals("body");
    if (jsEnabled &&
        !oldActiveEqualsCurrent &&
        !isBody) {
      oldActiveElement.element.blur();
      element.focus();
    }
}

Selenyum-htmlunit-driver sürümünün kodu> = 2.23.0:

private void switchFocusToThisIfNeeded() {
    HtmlUnitWebElement oldActiveElement =
        ((HtmlUnitWebElement)parent.switchTo().activeElement());

    boolean jsEnabled = parent.isJavascriptEnabled();
    boolean oldActiveEqualsCurrent = oldActiveElement.equals(this);
    try {
        boolean isBody = oldActiveElement.getTagName().toLowerCase().equals("body");
        if (jsEnabled &&
            !oldActiveEqualsCurrent &&
            !isBody) {
        oldActiveElement.element.blur();
        }
    } catch (StaleElementReferenceException ex) {
      // old element has gone, do nothing
    }
    element.focus();
}

2.23.0 veya daha yeni bir sürüme güncelleme yapmadan, sayfa odağındaki herhangi bir öğeyi verebilirsiniz. element.click()Mesela sadece kullandım .


1
Vay be ... Bu gerçekten belirsiz bir keşifti, güzel bir iş .. Şimdi diğer sürücülerin (örn. Chromedriver) da benzer sorunlara sahip olup olmadığını merak ediyorum
kevlarr

0

Ne yazdığınıza bağlı olarak otomatik güncelleme içeren bir arama giriş kutusuna send_keys göndermeye çalıştığımda bana oldu. Eero tarafından belirtildiği gibi, giriş öğesinin içine metninizi yazarken bazı Ajax güncellendiğinde bu olabilir . Çözüm, her seferinde bir karakter göndermek ve giriş öğesini tekrar aramaktır . (Örn. Aşağıda gösterilen yakut olarak)

def send_keys_eachchar(webdriver, elem_locator, text_to_send)
  text_to_send.each_char do |char|
    input_elem = webdriver.find_element(elem_locator)
    input_elem.send_keys(char)
  end
end

0

@ Jarib'in cevabına eklemek için, yarış koşulunu ortadan kaldırmaya yardımcı olan birkaç uzatma yöntemi yaptım.

İşte benim kurulum:

"Driver.cs" adlı bir sınıfım var. Sürücü ve diğer kullanışlı statik işlevler için genişletme yöntemleriyle dolu statik bir sınıf içerir.

Genelde almam gereken öğeler için aşağıdaki gibi bir uzantı yöntemi oluşturuyorum:

public static IWebElement SpecificElementToGet(this IWebDriver driver) {
    return driver.FindElement(By.SomeSelector("SelectorText"));
}

Bu, o öğeyi kodla herhangi bir test sınıfından almanızı sağlar:

driver.SpecificElementToGet();

Şimdi, bu sonuçlanırsa, StaleElementReferenceExceptionsürücü sınıfımda aşağıdaki statik yöntem var:

public static void WaitForDisplayed(Func<IWebElement> getWebElement, int timeOut)
{
    for (int second = 0; ; second++)
    {
        if (second >= timeOut) Assert.Fail("timeout");
        try
        {
            if (getWebElement().Displayed) break;
        }
        catch (Exception)
        { }
        Thread.Sleep(1000);
    }
}

Bu işlevin ilk parametresi, IWebElement nesnesini döndüren herhangi bir işlevdir. İkinci parametre saniye cinsinden bir zaman aşımıdır (zaman aşımı kodu, FireFox için Selenyum IDE'den kopyalanmıştır). Kod, eski öğe istisnasını aşağıdaki şekilde önlemek için kullanılabilir:

MyTestDriver.WaitForDisplayed(driver.SpecificElementToGet,5);

Arayacak Yukarıdaki kod driver.SpecificElementToGet().Displayedkadar driver.SpecificElementToGet()hiçbir istisna ve atar .Displayeddeğerlendirir içintrue ve 5 saniye geçmemiş. 5 saniye sonra test başarısız olur.

Kapak tarafında, bir öğenin bulunmamasını beklemek için aşağıdaki işlevi aynı şekilde kullanabilirsiniz:

public static void WaitForNotPresent(Func<IWebElement> getWebElement, int timeOut) {
    for (int second = 0;; second++) {
        if (second >= timeOut) Assert.Fail("timeout");
            try
            {
                if (!getWebElement().Displayed) break;
            }
            catch (ElementNotVisibleException) { break; }
            catch (NoSuchElementException) { break; }
            catch (StaleElementReferenceException) { break; }
            catch (Exception)
            { }
            Thread.Sleep(1000);
        }
}

0

Ben StaleElementReferenceException işlemek için uygun bir yaklaşım buldum düşünüyorum. Genellikle, eylemleri yeniden denemek için her WebElement yöntemi için sarmalayıcılar yazmanız gerekir, bu da sinir bozucu ve çok zaman harcar.

Bu kodu ekleme

webDriverWait.until((webDriver1) -> (((JavascriptExecutor) webDriver).executeScript("return document.readyState").equals("complete")));

if ((Boolean) ((JavascriptExecutor) webDriver).executeScript("return window.jQuery != undefined")) {
    webDriverWait.until((webDriver1) -> (((JavascriptExecutor) webDriver).executeScript("return jQuery.active == 0")));
}

önce her WebElement eylemi testlerinizin kararlılığını artırabilir ancak zaman zaman StaleElementReferenceException alabilirsiniz.

Bu yüzden (AspectJ kullanarak) bu ile geldi:

package path.to.your.aspects;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.remote.RemoteWebElement;
import org.openqa.selenium.support.pagefactory.DefaultElementLocator;
import org.openqa.selenium.support.pagefactory.internal.LocatingElementHandler;
import org.openqa.selenium.support.ui.WebDriverWait;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

@Aspect
public class WebElementAspect {
    private static final Logger LOG = LogManager.getLogger(WebElementAspect.class);
    /**
     * Get your WebDriver instance from some kind of manager
     */
    private WebDriver webDriver = DriverManager.getWebDriver();
    private WebDriverWait webDriverWait = new WebDriverWait(webDriver, 10);

    /**
     * This will intercept execution of all methods from WebElement interface
     */
    @Pointcut("execution(* org.openqa.selenium.WebElement.*(..))")
    public void webElementMethods() {}

    /**
     * @Around annotation means that you can insert additional logic
     * before and after execution of the method
     */
    @Around("webElementMethods()")
    public Object webElementHandler(ProceedingJoinPoint joinPoint) throws Throwable {
        /**
         * Waiting until JavaScript and jQuery complete their stuff
         */
        waitUntilPageIsLoaded();

        /**
         * Getting WebElement instance, method, arguments
         */
        WebElement webElement = (WebElement) joinPoint.getThis();
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        Object[] args = joinPoint.getArgs();

        /**
         * Do some logging if you feel like it
         */
        String methodName = method.getName();

        if (methodName.contains("click")) {
            LOG.info("Clicking on " + getBy(webElement));
        } else if (methodName.contains("select")) {
            LOG.info("Selecting from " + getBy(webElement));
        } else if (methodName.contains("sendKeys")) {
            LOG.info("Entering " + args[0].toString() + " into " + getBy(webElement));
        }

        try {
            /**
             * Executing WebElement method
             */
            return joinPoint.proceed();
        } catch (StaleElementReferenceException ex) {
            LOG.debug("Intercepted StaleElementReferenceException");

            /**
             * Refreshing WebElement
             * You can use implementation from this blog
             * http://www.sahajamit.com/post/mystery-of-stale-element-reference-exception/
             * but remove staleness check in the beginning (if(!isElementStale(elem))), because we already caught exception
             * and it will result in an endless loop
             */
            webElement = StaleElementUtil.refreshElement(webElement);

            /**
             * Executing method once again on the refreshed WebElement and returning result
             */
            return method.invoke(webElement, args);
        }
    }

    private void waitUntilPageIsLoaded() {
        webDriverWait.until((webDriver1) -> (((JavascriptExecutor) webDriver).executeScript("return document.readyState").equals("complete")));

        if ((Boolean) ((JavascriptExecutor) webDriver).executeScript("return window.jQuery != undefined")) {
            webDriverWait.until((webDriver1) -> (((JavascriptExecutor) webDriver).executeScript("return jQuery.active == 0")));
        }
    }

    private static String getBy(WebElement webElement) {
        try {
            if (webElement instanceof RemoteWebElement) {
                try {
                    Field foundBy = webElement.getClass().getDeclaredField("foundBy");
                    foundBy.setAccessible(true);
                    return (String) foundBy.get(webElement);
                } catch (NoSuchFieldException e) {
                    e.printStackTrace();
                }
            } else {
                LocatingElementHandler handler = (LocatingElementHandler) Proxy.getInvocationHandler(webElement);

                Field locatorField = handler.getClass().getDeclaredField("locator");
                locatorField.setAccessible(true);

                DefaultElementLocator locator = (DefaultElementLocator) locatorField.get(handler);

                Field byField = locator.getClass().getDeclaredField("by");
                byField.setAccessible(true);

                return byField.get(locator).toString();
            }
        } catch (IllegalAccessException | NoSuchFieldException e) {
            e.printStackTrace();
        }

        return null;
    }
}

Bu yönü etkinleştirmek için dosya oluştur src\main\resources\META-INF\aop-ajc.xml ve yaz

<aspectj>
    <aspects>
        <aspect name="path.to.your.aspects.WebElementAspect"/>
    </aspects>
</aspectj>

Bunu pom.xml

<properties>
    <aspectj.version>1.9.1</aspectj.version>
</properties>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.22.0</version>
            <configuration>
                <argLine>
                    -javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
                </argLine>
            </configuration>
            <dependencies>
                <dependency>
                    <groupId>org.aspectj</groupId>
                    <artifactId>aspectjweaver</artifactId>
                    <version>${aspectj.version}</version>
                </dependency>
            </dependencies>
        </plugin>
</build>

Ve hepsi bu. Umarım yardımcı olur.


0

Zor beklemeyi kullanmak zorunda kalmamak için açık beklemeyi kullanarak bunu çözebilirsiniz.

Bir özelliğe sahip tüm öğeleri alır ve her döngü için kullanarak yineleme yaparsanız, döngü içinde bu şekilde beklemeyi kullanabilirsiniz,

List<WebElement> elements = driver.findElements("Object property");
for(WebElement element:elements)
{
    new WebDriverWait(driver,10).until(ExpectedConditions.presenceOfAllElementsLocatedBy("Object property"));
    element.click();//or any other action
}

veya tek eleman için aşağıdaki kodu kullanabilirsiniz,

new WebDriverWait(driver,10).until(ExpectedConditions.presenceOfAllElementsLocatedBy("Your object property"));
driver.findElement("Your object property").click();//or anyother action 

-1

Java 8'de bunun için çok basit bir yöntem kullanabilirsiniz :

private Object retryUntilAttached(Supplier<Object> callable) {
    try {
        return callable.get();
    } catch (StaleElementReferenceException e) {
        log.warn("\tTrying once again");
        return retryUntilAttached(callable);
    }
}

-5
FirefoxDriver _driver = new FirefoxDriver();

// create webdriverwait
WebDriverWait wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(10));

// create flag/checker
bool result = false;

// wait for the element.
IWebElement elem = wait.Until(x => x.FindElement(By.Id("Element_ID")));

do
{
    try
    {
        // let the driver look for the element again.
        elem = _driver.FindElement(By.Id("Element_ID"));

        // do your actions.
        elem.SendKeys("text");

        // it will throw an exception if the element is not in the dom or not
        // found but if it didn't, our result will be changed to true.
        result = !result;
    }
    catch (Exception) { }
} while (result != true); // this will continue to look for the element until
                          // it ends throwing exception.

Anladıktan hemen sonra ekledim. biçim için özür dilerim bu benim ilk gönderi. Sadece yardım etmeye çalışıyorum. Yararlı bulursanız, lütfen başkalarıyla paylaşın :)
Alvin Vera

Stackoverflow'a hoş geldiniz!
Gönderi

Yukarıdaki kodu çalıştırmak, örneğin o sayfada bir sunucu hatası varsa, döngüde sonsuza kadar sıkışmış olabilirsiniz.
munch
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.