Seleniumで「StaleElementReferenceException」を回避するにはどうすればよいですか?


89

私はJavaを使用して多くのSeleniumテストを実装しています。時々、私のテストはStaleElementReferenceException。のために失敗します。テストをより安定させるためのいくつかのアプローチを提案できますか?

回答:


91

これは、ページで発生しているDOM操作が一時的に要素にアクセスできなくなっている場合に発生する可能性があります。これらのケースを考慮して、最終的に例外をスローする前に、ループ内で要素に数回アクセスを試みることができます。

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

1
うわー!これはまさに私が必要としていたものでした。ありがとう!
SpartaSixZero 2014年

1
要素の異なる参照を使用することによっても修正できます。
リポンアルワシム2015

@jspcal、これは私にとって魅力のように機能しました!どうもありがとう!
AnthonyOkoth19年

上記で問題が解決しない場合は、最新のchromedriverに更新することで解決できます。
Vdex

5
これは非常に多くの点で恐ろしいです。それは私の現在の問題を解決しますので、ありがとう。
ソフトウェアエンジニア

68

私は断続的にこの問題を抱えていました。私の知らないうちに、BackboneJSがページ上で実行され、クリックしようとした要素を置き換えていました。私のコードは次のようになりました。

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

もちろんこれは機能的にはこれと同じです。

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

たまに起こることは、javascriptがそれを見つけてクリックする間にcheckoutLink要素を置き換えることでした。

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

リンクをクリックしようとすると、当然、StaleElementReferenceExceptionが発生しました。javascriptの実行が終了するまで待機するようにWebDriverに指示する信頼できる方法が見つからなかったため、最終的に解決した方法は次のとおりです。

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

このコードは、クリックが成功するかタイムアウトに達するまで、StaleElementReferenceExceptionsを無視して、リンクのクリックを継続的に試みます。このソリューションが気に入っているのは、再試行ロジックを作成する手間が省け、WebDriverの組み込み構造のみを使用するためです。


この回答は非推奨になりました。
vaibhavcool20 2018

20

通常、これはDOMが更新されており、更新された/新しい要素にアクセスしようとしているためですが、DOMが更新されているため、無効な参照になっています。

これを回避するには、最初に要素を明示的に待機して更新が完了したことを確認してから、要素への新しい参照を再度取得します。

説明するためのいくつかの擬似コードを次に示します(私がこの問題に正確に使用するいくつかのC#コードから適応):

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.

お役に立てれば!


17

ケニーの解決策は良いですが、よりエレガントな方法で書くことができます

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

またはまた:

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

しかしとにかく、最善の解決策はセレニドライブラリに依存することです。それはこの種のことなどを処理します。(要素参照の代わりにプロキシを処理するため、非常に困難な古い要素を処理する必要はありません)。セレニド


免責事項:私はただ幸せな
セレニド

2番目の解決策は、要素を見つけたときではなく、クリックしたときに要素が古くなるため、機能します。
ラジャゴパラン

セレニドを使用すると、この問題をはるかに簡単に回避できます。セレンは、この問題が原因でシンプルなユーザーのための低レベルAPIである事実を単独で使用することを意図していない
cocorossello

私はそれを完全に承知しています。Ruby Selenium BindingのラッパーであるWATIRを使用していますが、WATIRはこれらすべての問題を自動的に処理します(インスタンスの古い要素の場合)。Javaバインディングで同等のものを探しています。セレニドを見つけましたが、セレニドの暗黙的な待機と明示的な待機を変更する方法がわかりません。その方法を教えていただけますか?それとも、私が参照できる場所で私に提供できる資料はありますか?FluentLeniumについてどう思いますか?
ラジャゴパラン

1
OPによって選択された回答は2012年にさかのぼることを知っておく必要があります。過去7年間で多くのことが変化しました。この答えは2019年の方が正しいです。
hfontanez19年

11

StaleElementReferenceException発生する理由はすでに説明されています。要素を見つけてから何かを実行するまでのDOMの更新です。

クリックの場合-問題私は最近、次のようなソリューションを使用しました。

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

重要な部分は、ExpectedConditionsを介したSelenium自身の「チェーン」ですExpectedConditions.refreshed()。これは実際に待機し、指定されたタイムアウト中に問題の要素が更新されたかどうかを確認し、さらに要素がクリック可能になるのを待機します。

見ていリフレッシュ方法のマニュアルを


4

私のプロジェクトでは、StableWebElementの概念を導入しました。これは、要素が古いかどうかを検出し、元の要素への新しい参照を見つけることができるWebElementのラッパーです。WebElementの代わりにStableWebElementを返す要素を見つけるためのヘルパーメソッドを追加しましたが、StaleElementReferenceの問題は解消されました。

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

C#のコードは私のプロジェクトのページで入手できますが、Javahttps ://github.com/cezarypiatek/Tellurium/blob/master/Src/MvcPages/SeleniumUtils/StableWebElement.csに簡単に移植できます


1

C#での解決策は次のとおりです。

ヘルパークラス:

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

呼び出し:

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

同様に、Javaにも使用できます。


1

Kennyのソリューションは非推奨です。これを使用してください。actionsクラスを使用してダブルクリックしていますが、何でもできます。

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優雅に扱えるクリーンな方法StaleElementReference

これはjspcalの回答に大きく基づいていますが、セットアップ正常に機能するようにその回答を変更する必要があったため、他の人に役立つ場合に備えて、ここに追加したいと思いました。この回答が役に立った場合は、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.
    }
  }
}

0

これは、C#を使用して私のために機能します(100%機能しています)

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

問題は、要素をJavascriptからJavaに戻し、Javascriptに戻すまでに、DOMを離れることができることです。
Javascriptですべてをやってみてください:

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

0

これを試して

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

私はここで解決策を見つけまし。私の場合、現在のウィンドウ、タブ、またはページを離れて再び戻ってくると、要素にアクセスできなくなります。

.ignoring(StaleElement ...)、. refreshed(...)、elementToBeClicable(...)は役に立たず、act.doubleClick(element).build().perform();文字列で例外が発生していました。

メインのテストクラスで関数を使用する:

openForm(someXpath);

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

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

0

(アクションに関して)これまで誰も言及しなかったStaleElementReferenceExceptionにつながる潜在的な問題が存在する可能性があります。

Javascriptで説明しますが、Javaでも同じです。

これは機能しません:

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

しかし、アクションを再度インスタンス化すると、それが解決されます。

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

通常、アクセスしようとする要素が表示されたときにStaleElementReferenceExceptionが発生しますが、他の要素が対象の要素の位置に影響を与える可能性があるため、クリックまたはgetTextを実行しようとしたり、WebElementで何らかのアクションを実行しようとすると、通常、要素がDOMにアタッチされていないという例外が発生します。 。

私が試した解決策は次のとおりです。

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

上記のコードでは、最初に待機してから、例外が発生した場合は要素をクリックしてから、それをキャッチしてループしようとします。これは、まだすべての要素が読み込まれず、再び例外が発生する可能性があるためです。


-4

最近追加された可能性がありますが、他の回答では、上記のすべてを実行し、Seleniumに組み込まれているSeleniumの暗黙的な待機機能については言及されていません。

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

これによりfindElement()、要素が見つかるまで、または10秒間呼び出しが再試行されます。

ソース-http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp


2
そのソリューションはStaleElementReferenceExceptionを防止しません
MrSpock

1
Seleniumの最新バージョンであっても、バージョンの混乱をなくすために、暗黙的にWait()はStaleElementReferenceExceptionを防止しません。成功または固定カウントまで、スリープを使用してループを呼び出すメソッドを使用します。
angsuman Chakraborty 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.