web-dev-qa-db-ja.com

解決方法、古い要素の例外?要素がDOMにアタッチされなくなった場合

「要素がDOMにアタッチされなくなった」という質問があります。

別の解決策を試しましたが、断続的に機能しています。永続的な解決策を提案してください。

WebElement getStaleElemById(String id, WebDriver driver) {
    try {
        return driver.findElement(By.id(id));
    } catch (StaleElementReferenceException e) {
        System.out.println("Attempting to recover from StaleElementReferenceException ...");
        return getStaleElemById(id, driver);
    }
}

WebElement getStaleElemByCss(String css, WebDriver driver) {
    try {
        return driver.findElement(By.cssSelector(css));
    } catch (StaleElementReferenceException e) {
        System.out.println("Attempting to recover from StaleElementReferenceException ...");
        return getStaleElemByCss(css, driver);
    } catch (NoSuchElementException ele) {
         System.out.println("Attempting to recover from NoSuchElementException ...");
         return getStaleElemByCss(css, driver);
    }
}

ありがとう、アヌ

14
user2498024

問題

おそらく直面している問題は、メソッドが正しい(かつ有効な)要素を返すことですが、1秒後にアクセスしようとすると、古くなってスローされます。

これは通常、次の場合に発生します。

  1. 新しいページを非同期で読み込むか、少なくともそれを変更する何かをクリックします。
  2. すぐに(ページの読み込みが完了する前に)要素を検索すると...見つかります!
  3. 最後にページがアンロードされ、新しいページがロードされます。
  4. 以前に見つかった要素にアクセスしようとしましたが、新しいページにも含まれているにもかかわらず、現在は古くなっています。

ソリューション

私が知っているそれを解決する4つの方法があります。

  1. 適切な待機を使用する

    非同期ページに直面するときは、予想されるすべてのページロードの後に​​適切な待機を使用してください。最初のクリックの後に明示的な待機を挿入し、新しいページ/新しいコンテンツが読み込まれるのを待ちます。その後のみ、目的の要素を検索できます。これは、最初に行う必要があります。テストの堅牢性が大幅に向上します。

  2. あなたのやり方

    私は2年前からあなたの方法の変種を使用しており(上記の解決策1の手法と一緒に)、絶対に機能しほとんど、奇妙なWebDriverバグでのみ失敗します。見つかった直後に(メソッドから戻る前に).isDisplayed()メソッドなどを使用して、見つかった要素にアクセスしてみます。それがスローされた場合は、もう一度検索する方法をすでに知っています。合格した場合は、もう1つ(偽)の保証があります。

  3. 古くなったときに再発見するWebElementを使用する

    WebElementデコレーターを作成して、それがどのように見つけられたかを記憶し、アクセスおよびスローされたときにそれを再発見します。これは明らかに、デコレータのインスタンスを返すカスタムfindElement()メソッドを使用することを強制します(または、さらに良いことに、通常のfindElement()からインスタンスを返す装飾されたWebDriverを使用します)およびfindElemens()メソッド)。次のようにします。

    _public class NeverStaleWebElement implements WebElement {
        private WebElement element;
        private final WebDriver driver;
        private final By foundBy;
    
        public NeverStaleWebElement(WebElement element, WebDriver driver, By foundBy) {
            this.element = element;
            this.driver = driver;
            this.foundBy = foundBy;
        }
    
        @Override
        public void click() {
            try {
                element.click();
            } catch (StaleElementReferenceException e) {
                // log exception
    
                // assumes implicit wait, use custom findElement() methods for custom behaviour
                element = driver.findElement(foundBy);
    
                // recursion, consider a conditioned loop instead
                click();
            }
        }
    
        // ... similar for other methods, too
    
    }
    _

    foundBy情報は、これを簡単にするために一般的なWebElementsからアクセスできる必要があると思いますが、Selenium開発者は、このようなものを試すのは間違いであると考えて この情報を公開しないでください) 。古くなった要素を再検索することは間違いなく悪い習慣です。なぜなら、正当化されているかどうかを確認するためのメカニズムなしに暗黙的に要素を再検索しているためです。再検索メカニズムは、完全に異なる要素を見つける可能性があり、同じ要素を再び見つけることはできません。また、見つかった要素が多い場合は、findElements()でひどく失敗します(findElements()で見つかった要素の再検索を禁止するか、要素の元の数を覚えておく必要があります返されたList)。

    私はそれがときどき役立つと思いますが、テストの堅牢性のために明らかにはるかに優れたソリューションであるオプション1と2を誰も使用しないことは事実です。それらを使用して、これが必要であると確信した後でのみ、それを実行してください。

  4. タスクキューを使用する(過去のタスクを再実行できる)

    ワークフロー全体を新しい方法で実装してください!

    • 実行するジョブの中央キューを作成します。このキューに過去のジョブを記憶させます。
    • コマンドパターンの方法で、必要なすべてのタスク(「要素を見つけてクリックする」、「要素を見つけてそれにキーを送信する」など)を実装します。呼び出されたら、タスクを中央のキューに追加し、中央のキューで(同期または非同期のどちらでもかまいません)実行します。
    • 必要に応じて、すべてのタスクに_@LoadsNewPage_、_@Reversible_などの注釈を付けます。
    • ほとんどのタスクは自分で例外を処理しますが、スタンドアロンである必要があります。
    • キューが古い要素の例外を検出すると、タスク履歴から最後のタスクを取得して再実行し、再試行します。

    これは明らかに多くの労力を要し、十分に検討しなければ、すぐに逆効果になる可能性があります。テストのページを手動で修正した後、失敗したテストを再開するために、この(かなり複雑で強力な)バリアントを使用しました。一部の条件(たとえば、StaleElementException)では、失敗してもテストはすぐには終了しませんが、(最終的に15秒後にタイムアウトする前に)待機し、情報ウィンドウをポップアップしてユーザーに表示します。手動でページを更新するオプション/右ボタンをクリックする/フォームを修正する/何でも。その後、失敗したタスクを再実行するか、履歴内のいくつかのステップに戻る可能性があります(たとえば、最後の_@LoadsNewPage_ジョブまで)。


最終ニピック

そうは言っても、元のソリューションでは、いくらか磨くことができます。 2つの方法を1つのより一般的な方法に組み合わせることができます(または少なくともコードの繰り返しを減らすために、これらをこの方法に委任することができます)。

_WebElement getStaleElem(By by, WebDriver driver) {
    try {
        return driver.findElement(by);
    } catch (StaleElementReferenceException e) {
        System.out.println("Attempting to recover from StaleElementReferenceException ...");
        return getStaleElem(by, driver);
    } catch (NoSuchElementException ele) {
        System.out.println("Attempting to recover from NoSuchElementException ...");
        return getStaleElem(by, driver);
    }
}
_

Java 7の場合、単一のマルチキャッチブロックでも十分です。

_WebElement getStaleElem(By by, WebDriver driver) {
    try {
        return driver.findElement(by);
    } catch (StaleElementReferenceException | NoSuchElementException e) {
        System.out.println("Attempting to recover from " + e.getClass().getSimpleName() + "...");
        return getStaleElem(by, driver);
    }
}
_

このようにして、維持する必要のあるコードの量を大幅に減らすことができます。

41
Petr Janeček

これを解決するには、1。古くなった要素を保持し、例外がスローされるまでポーリングします。次に、要素が再び表示されるまで待ちます。

    boolean isStillOnOldPage = true;
    while (isStillOnOldPage) {
        try {
            theElement.getAttribute("whatever");
        } catch (StaleElementReferenceException e) {
            isStillOnOldPage = false;
        }
    }
    WebDriverWait wait = new WebDriverWait(driver, 15);
    wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("theElementId")));
1
Felix Dobslaw

解決策:

  1. 参照の代わりに要素にロケーターを格納する
driver = webdriver.Firefox();
driver.get("http://www.github.com");
search_input = lambda: driver.find_element_by_name('q');
search_input().send_keys('hello world\n'); 
time.sleep(5);


search_input().send_keys('hello frank\n') // no stale element exception
  1. 使用するJSライブラリのフックを利用
   # Using Jquery queue to get animation queue length.
    animationQueueIs = """
    return $.queue( $("#%s")[0], "fx").length;
    """ % element_id
    wait_until(lambda: self.driver.execute_script(animationQueueIs)==0)
  1. アクションをJavaScriptインジェクションに移動する
 self.driver.execute_script("$(\"li:contains('Narendra')\").click()");
  1. 要素が古くなるのを積極的に待つ
  # Wait till the element goes stale, this means the list has updated
  wait_until(lambda: is_element_stale(old_link_reference))

私のために働いたこの解決策

0
NarendraC

リンクをクリックしようとすると、新しいページに移動します。その後、戻って他のリンクをクリックします。コードの下のそれらはあなたを助けるかもしれません。

public int getNumberOfElementsFound(By by) {
    return  driver.findElements(by).size();
  }

public WebElement getElementWithIndex(By by, int pos) {
    return driver.findElements(by).get(pos);
  }

/**click on each link */
public void getLinks()throws Exception{
try {
List<WebElement> componentList = driver.findElements(By.tagName("a"));
System.out.println(componentList.size()); 

    for (WebElement component : componentList)
    {
        //click1();
        System.out.println(component.getAttribute("href"));
    }
 int numberOfElementsFound = getNumberOfElementsFound(By.tagName("a"));
for (int pos = 0; pos < numberOfElementsFound; pos++) {
     if (getElementWithIndex(By.tagName("a"), pos).isDisplayed()){

  getElementWithIndex(By.tagName("a"), pos).click();
  Thread.sleep(200);
  driver.navigate().back();
  Thread.sleep(200);                                                       
}
  }
    }catch (Exception e){
        System.out.println("error in getLinks "+e);
    }
}
0
Shammi

Fitnesseの場合、以下を使用できます。

|開始|スマートWebドライバー| Selenium.properties |

@Fixture(name = "Smart Web Driver")public class SmartWebDriver extends SlimWebDriver {

private final static Logger LOG = LoggerFactory.getLogger(SmartWebDriver.class);

/**
 * Constructs a new SmartWebDriver.
 */
@Start(name = "Start Smart Web Driver", arguments = {"configuration"}, example = "|start |Smart Web Driver| Selenium.properties|")
public SmartWebDriver(String configuration) {
    super(configuration);
}

/**
 * Waits for an element to become invisible (meaning visible and width and height != 0).
 *
 * @param locator the locator to use to find the element.
 */
@Command(name = "smartWaitForNotVisible", arguments = {"locator"}, example = "|smartWaitForNotVisible; |//path/to/input (of css=, id=, name=, classname=, link=, partiallink=)|")
public boolean smartWaitForNotVisible(String locator) {
    try {
        waitForNotVisible(locator);
    } catch (StaleElementReferenceException sere) {
        LOG.info("Element with locator '%s' did not become invisible (visible but also width and height != 0), a StaleElementReferenceException occurred, trying to continue...", locator);
    } catch (NoSuchElementException ele) {
        LOG.info("Element with locator '%s' did not become invisible (visible but also width and height != 0), a NoSuchElementException occurred, trying to continue...", locator);
    } catch (AssertionError ae) {
        if (ae.getMessage().contains("No element found")) {
            LOG.info("Element with locator '%s' did not become invisible (visible but also width and height != 0), a AssertionError occurred, trying to continue...", locator);
        } else {
            throw ae;
        }
    }
    return true;
}

}

0
Koenraad Appelo

古い要素の例外が発生したとき!!

これらのテキストボックス/ボタン/リンクをサポートするライブラリが変更された場合、古い要素の例外が発生する可能性があります。つまり、要素は同じですが、ロケーターに影響を与えずにWebサイトで参照が変更されています。したがって、ページが更新されたライブラリで更新されたため、ライブラリ参照を含むキャッシュに保存した参照が古くなっているか、古くなっています。

for(int j=0; j<5;j++)
try {
    WebElement elementName=driver.findElement(By.xpath(“somexpath”));
    break;
} catch(StaleElementReferenceException e){
e.toString();
System.out.println(“Stale element error, trying ::  ” + e.getMessage());
}
elementName.sendKeys(“xyz”);
0
Nag Raj