web-dev-qa-db-ja.com

Seleniumで「StaleElementReferenceException」を回避する方法は?

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

62
hoang nguyen

これは、ページで発生している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;
}
65
jspcal

この問題は断続的に発生していました。私には知られていないが、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のビルトインコンストラクトのみを使用するため、このソリューションが気に入っています。

58
Kenny

一般的に、これは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.

お役に立てれば!

13
Jim Holmes

ケニーのソリューションは優れていますが、よりエレガントな方法で書くことができます

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

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

11
cocorossello

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.refreshed()を介したSelenium独自のExpectedConditionsの「連鎖」です。これは実際に待機し、問題の要素が指定されたタイムアウト中に更新されたかどうかをチェックし、さらに要素がクリック可能になるのを待ちます。

更新されたメソッドのドキュメント をご覧ください。

7
Christian.D

私のプロジェクトでは、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#のコードはプロジェクトのページで利用できますが、Javaに簡単に移植できます https://github.com/cezarypiatek/Tellurium/blob/master/Src/MvcPages/SeleniumUtils/ StableWebElement.cs

3
cezarypiatek

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
George Kargakis

ケニーのソリューションはこれを使用することは推奨されません。私はアクションクラスを使用してダブルクリックしますが、何でもできます。

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
vaibhavcool20

問題は、JavascriptからJavaに要素を渡してJavascriptに戻すときまでに、DOMから離れている可能性があることです。
Javascriptですべてを試してください:

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

これを試して

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
CodingGirl1

これは私のために動作します(100%動作)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;
    }
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
Gryu