Javaを使用して多くのSeleniumテストを実装しています。が原因でテストが失敗することがありStaleElementReferenceException
ます。テストをより安定させるためのいくつかのアプローチを提案できますか?
16 に答える
これは、ページで発生している DOM 操作によって一時的に要素にアクセスできなくなった場合に発生する可能性があります。このような場合に備えて、ループ内で要素に数回アクセスしてから、最終的に例外をスローすることができます。
darrelgrainer.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;
}
私は断続的にこの問題を抱えていました。知らないうちに、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 の組み込み構造のみを使用するため、このソリューションが気に入っています。
ケニーの解決策は優れていますが、よりエレガントな方法で書くことができます
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 ライブラリに依存することです。これは、この種のものなどを処理します。(要素参照の代わりにプロキシを処理するため、非常に困難な古い要素を処理する必要がありません)。セレン化物
通常、これは 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.
お役に立てれば!
発生する理由は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
、ExpectedConditions.refreshed()
. これは実際に待機して、指定されたタイムアウト中に問題の要素が更新されたかどうかを確認し、さらに要素がクリック可能になるまで待機します。
更新されたメソッドのドキュメントをご覧ください。
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 にも使用できます。
ケニーのソリューションは非推奨です。これを使用します。アクション クラスを使用してダブルクリックしていますが、何でもできます。
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;
}
});
を適切に処理するクリーンな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.
}
}
}
ここで解決策を見つけました。私の場合、現在のウィンドウ、タブ、またはページを離れて再び戻ってくると、要素にアクセスできなくなります。
.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))));
}
通常、アクセスしようとする要素が表示されたときに 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));
}
上記のコードでは、最初に待機してから、例外が発生した場合は要素をクリックしてから、すべての要素がロードされず、再び例外が発生する可能性があるため、それをキャッチしてループしようとします。
最近追加されたのかもしれませんが、他の回答では、上記のすべてを実行し、Selenium に組み込まれている Selenium の暗黙の待機機能について言及していません。
driver.manage().timeouts().implicitlyWait(10,TimeUnit.SECONDS);
findElement()
これにより、要素が見つかるまで、または 10 秒間、呼び出しが再試行されます。
ソース - http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp