ランダムな「要素はDOMにアタッチされていません」StaleElementReferenceException


143

それが私だけであることを願っていますが、Selenium Webdriverは完全な悪夢のようです。現在、Chromeウェブドライバは使用できず、他のドライバは非常に信頼性が低い、またはそのようです。私は多くの問題と戦っていますが、ここに1つあります。

ランダムに、私のテストは失敗します

"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'"

私はバージョン2.0b3のwebdriverを使用しています。私はこれがFFとIEドライバーで起こるのを見ました。これを防ぐ唯一の方法はThread.sleep、例外が発生する前に実際の呼び出しを追加することです。ただし、これは不十分な回避策であるため、誰かが私の側でこれをすべて改善するエラーを指摘できることを願っています。


26
うまくいけば、17kビューは、それがあなただけではないことを示しています;)これは、最も苛立たしいSeleniumの例外です。
Mark Mayo

4
今48k!私も同じ問題を抱えています...
Gal

3
私はセレンが純粋で完全なゴミであることを発見しています...
Cジョンソン

4
60k、まだ問題:)
Pieter De Bie

私の場合、それはそうするためでしたfrom selenium.common.exceptions import NoSuchElementException
Cpt。Senkfuss

回答:


119

はい、StaleElementReferenceExceptionsに問題がある場合は、テストの記述が不十分なためです。競合状態です。次のシナリオを検討してください。

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

要素をクリックした時点で、要素参照は無効になります。WebDriverがこれが発生する可能性のあるすべてのケースを適切に推測することはほぼ不可能です。そのため、テスト/アプリの作成者は、何が発生するか、何が発生しないかを正確に把握しているはずです。やりたいことは、DOMが変更されないことがわかる状態になるまで明示的に待機することです。たとえば、WebDriverWaitを使用して特定の要素が存在するのを待機します。

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

presentOfElementLocated()メソッドは次のようになります。

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

現在のChromeドライバーが非常に不安定であることについてはあなたはまったく正しいと思います。Seleniumトランクには書き換えられたChromeドライバーがあり、ほとんどの実装はChromium開発者がツリーの一部として行ったと聞いてうれしいです。

PS。または、上記の例のように明示的に待機する代わりに、暗黙的な待機を有効にすることもできます。これにより、要素が存在するのを待機する指定されたタイムアウトまでWebDriverは常にループします。

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

しかし、私の経験では、明示的に待機する方が常に信頼性が高くなります。


2
要素を変数に読み込んで再利用することはもはや不可能だと私は言っていますか?要素を渡すことに依存する巨大でドライでダイナミックなWATiR DSLがあり、webdriverに移植しようとしているのですが、同じ問題が発生しています。基本的に、DOMを変更するすべてのテストステップでモジュールのすべての要素を再度読み取るコードを追加する必要があります...
kinofrost

こんにちは。この例では、関数のタイプを尋ねてもいいですか?見つけられないようです。
ハンニバル

1
@ハンニバル:com.google.common.base.Function<F, T>グアバが提供。
Stephan202、2011年

@jarib、私はあなたの解決策から1年、これと同じ問題に直面しています。問題は、スクリプトをルビで記述していて、「presenceOfElementLocated」などの名前による機能がないことです。推奨事項はありますか?
Amey

56
@jarib私はこれはすべて、不十分に設計されたテストが原因であることに同意しません。要素がAJAX呼び出しの後に表示された後でも、StaleElementReferenceExceptionを引き起こす可能性があるjQueryコードがまだ実行されている可能性があるためです。そして、非常に良くないと思われる明示的な待機を追加する以外にあなたができることは何もありません。私はむしろ、これはwebdriverをにおける設計上の欠陥だと思う
ムンク

10

私はこのような方法をいくつかの成功で使用することができました:

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

はい、それはもはや古くなった(新鮮ですか?)と見なされなくなるまで要素をポーリングし続けます。実際には問題の根本に到達していませんが、WebDriverはこの例外をスローすることにかなりうるさい場合があることを発見しました。または、DOMが実際に変化している可能性もあります。

したがって、これは必ずしも不十分な記述のテストを示しているという上記の答えにはまったく同意しません。私は、まったくやり取りしていない新しいページにそれを持っています。DOMがどのように表現されているか、またはWebDriverが古くなっていると見なしているものに多少の不安定さが存在すると思います。


7
このコードにはバグがあり、何らかのキャップなしでメソッドを再帰的に呼び出し続けないでください。そうしないと、スタックが破壊されます。
ハリー

2
カウンターなどを追加した方がいいと思うので、繰り返しエラーが発生する場合は、実際にエラーをスローすることができます。それ以外の場合、実際にエラーがあると、ループに
陥り

私はそれが不十分に書かれたテストの結果ではないことに同意します。Seleniumは、最もよく書かれたテストであっても、最新のWebサイトでこれを行う傾向があります-おそらく、Webサイトが、リアクティブWebアプリフレームワークで一般的な双方向バインディングを介して継続的に要素を更新しているためです。これらの要素を作成する必要があります。このようなメソッドは、最新のWebアプリをテストするすべてのSeleniumフレームワークの一部である必要があります。
エメリー

10

このエラーは、AJAXの更新が途中で発生することがあります。CapybaraはDOMの変更を待つことについてはかなり賢いように見えますが(Wait_untilがCapybaraから削除された理由を 参照)、デフォルトの待機時間である2秒では、私の場合は十分ではありませんでした。例:_spec_helper.rb_で変更

Capybara.default_max_wait_time = 5

2
これによって私の問題も修正されました。StaleElementReferenceErrorが発生し、Capybara.default_max_wait_timeを増やすと問題が解決しました。
ブレンダン2015

1

私は今日同じ問題に直面し、ラッパークラスを作成しました。これは、すべてのメソッドの前に、要素参照がまだ有効かどうかをチェックします。要素を取得するための私のソリューションは非常に単純なので、共有するだけだと思いました。

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

要素をグローバルなjs変数に「検索」するか、むしろ保存し、必要に応じて要素を取得します。ページがリロードされると、この参照は機能しなくなります。しかし、運命に変更が加えられている限り、参照は残ります。そして、ほとんどの場合それでうまくいくはずです。

また、要素の再検索も回避されます。

ジョン


1

私も同じ問題を抱えており、私の原因は古いセレンバージョンでした。開発環境のため、新しいバージョンに更新できません。この問題は、HTMLUnitWebElement.switchFocusToThisIfNeeded()が原因で発生します。新しいページに移動すると、古いページでクリックした要素がoldActiveElement(以下を参照)である場合があります。Seleniumは古い要素からコンテキストを取得しようとして失敗します。これが、将来のリリースでtry catchを構築した理由です。

selenium-htmlunit-driverバージョン<2.23.0のコード:

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

selenium-htmlunit-driverバージョン> = 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以降に更新しなくても、ページ上の任意の要素にフォーカスを与えることができます。element.click()たとえば、私はちょうど使用しました。


1
うわぁ...これは本当にあいまいな検索でした。すばらしい作業でした。他のドライバー(例:chromedriver)にも同様の問題があるかどうか疑問に思っています
kevlarr

0

検索入力ボックスにsend_keysを送信しようとしたときにちょうど起こった-入力内容に応じて自動更新される。解決策は、一度に1文字送信し、入力要素を再度検索することです。(例:以下のルビの場合)

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の答えに追加するために、競合状態を排除するのに役立ついくつかの拡張メソッドを作成しました。

これが私のセットアップです:

「Driver.cs」というクラスがあります。これには、ドライバーの拡張メソッドとその他の便利な静的関数でいっぱいの静的クラスが含まれています。

通常取得する必要がある要素については、次のような拡張メソッドを作成します。

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

これにより、コードを使用して任意のテストクラスからその要素を取得できます。

driver.SpecificElementToGet();

これでが発生した場合StaleElementReferenceException、ドライバクラスに次の静的メソッドがあります。

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

この関数の最初のパラメーターは、IWebElementオブジェクトを返す関数です。2番目のパラメーターは秒単位のタイムアウトです(タイムアウトのコードはFireFoxのSelenium IDEからコピーされました)。このコードを使用すると、次の方法で古い要素の例外を回避できます。

MyTestDriver.WaitForDisplayed(driver.SpecificElementToGet,5);

上記のコードは、例外がスローされずに評価され、5秒が経過するdriver.SpecificElementToGet().Displayedまで呼び出されます。5秒後、テストは失敗します。driver.SpecificElementToGet().Displayedtrue

反対に、要素が存在しなくなるのを待つには、次の関数を同じ方法で使用できます。

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

私はStaleElementReferenceExceptionを処理する便利なアプローチを見つけたと思います。通常、アクションを再試行するには、すべてのWebElementメソッドのラッパーを作成する必要があります。

このコードを追加する

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

すべてのWebElementアクションがテストの安定性を向上させる前に、StaleElementReferenceExceptionが時々発生する可能性がある前に。

これが私が思いついたものです(AspectJを使用して):

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

このアスペクトを有効にするには、ファイルsrc\main\resources\META-INF\aop-ajc.xml を作成 して書き込みます

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

これをあなたに追加 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>

これですべてです。それが役に立てば幸い。


0

ハードウェイトを使用する必要がないように、明示的なウェイトを使用してこれを解決できます。

1つのプロパティですべての要素をフェッチし、ループごとに繰り返し使用する場合、次のようにループ内で待機を使用できます。

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
}

または、以下のコードを使用できる単一の要素の場合、

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

-1

Java 8では、そのための非常に単純な方法を使用できます。

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.

考え出した直後に追加しました。フォーマットを申し訳ありませんが、これは私の初めての投稿です。ただ助けようとしています。役に立ったら、他の人と共有してください:)
Alvin Vera

Stackoverflowへようこそ!投稿の精度を向上させるために、サンプルコードの短い説明を提供することは常に優れています:)
Picrofoソフトウェア

上記のコードを実行すると、たとえばそのページでサーバーエラーが発生した場合に、ループに永久にスタックする可能性があります。
2012年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.