私はJavaを使用して多くのSeleniumテストを実装しています。時々、私のテストはStaleElementReferenceException
。のために失敗します。テストをより安定させるためのいくつかのアプローチを提案できますか?
回答:
これは、ページで発生している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;
}
私は断続的にこの問題を抱えていました。私の知らないうちに、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の組み込み構造のみを使用するためです。
通常、これは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.
お役に立てれば!
ケニーの解決策は良いですが、よりエレガントな方法で書くことができます
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();
しかしとにかく、最善の解決策はセレニドライブラリに依存することです。それはこの種のことなどを処理します。(要素参照の代わりにプロキシを処理するため、非常に困難な古い要素を処理する必要はありません)。セレニド
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()
。これは実際に待機し、指定されたタイムアウト中に問題の要素が更新されたかどうかを確認し、さらに要素がクリック可能になるのを待機します。
見ていリフレッシュ方法のマニュアルを。
私のプロジェクトでは、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に簡単に移植できます。
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にも使用できます。
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;
}
});
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.
}
}
}
これは、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;
}
問題は、要素をJavascriptからJavaに戻し、Javascriptに戻すまでに、DOMを離れることができることです。
Javascriptですべてをやってみてください:
driver.executeScript("document.querySelector('#my_id').click()")
これを試して
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
}
}
私はここで解決策を見つけました。私の場合、現在のウィンドウ、タブ、またはページを離れて再び戻ってくると、要素にアクセスできなくなります。
.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につながる潜在的な問題が存在する可能性があります。
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()
通常、アクセスしようとする要素が表示されたときに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