Selenium 中的 StaleElementReferenceException
一、概述
StaleElementReferenceException是我們在使用 Selenium 測試 Web 應用程序時遇到的常見錯誤。當我們引用過時的元素時,Selenium 會拋出StaleElementReferenceException 。由於頁面刷新或 DOM 更新,元素變得陳舊。
在本教程中,我們將了解 Selenium 中的StaleElementReferenceException是什麼以及它發生的原因。然後,我們將看看如何在我們的 Selenium 測試中避免異常。
2. 避免StaleElementReferenceException策略
為避免StaleElementReferenceException ,必須確保元素被動態定位並與之交互,而不是存儲對它們的引用。這意味著我們應該在每次需要時找到元素,而不是將它們保存在變量中。
在某些情況下,這種方法是行不通的,我們需要在再次與元素交互之前刷新元素。因此,我們的解決方案是捕獲StaleElementReferenceException並在刷新元素後執行重試。我們可以直接在我們的測試中執行此操作,也可以在所有測試中全局執行此操作。
對於我們的測試,我們將為我們的定位器定義幾個常量:
By LOCATOR_REFRESH = By.xpath("//a[.='click here']");
By LOCATOR_DYNAMIC_CONTENT = By.xpath("(//div[@id='content']//div[@class='large-10 columns'])[1]");
對於設置,我們使用使用 WebDriverManager 的自動化方法。
2.1.生成StaleElementReferenceException
首先,我們將看一下以StaleElementReferenceException結束的測試:
void givenDynamicPage_whenRefreshingAndAccessingSavedElement_thenSERE() {
driver.navigate().to("https://the-internet.herokuapp.com/dynamic_content?with_content=static");
final WebElement element = driver.findElement(LOCATOR_DYNAMIC_CONTENT);
driver.findElement(LOCATOR_REFRESH).click();
Assertions.assertThrows(StaleElementReferenceException.class, element::getText);
}
該測試通過單擊該頁面上的鏈接來存儲元素並更新 DOM。當重新訪問不再存在的元素時,將拋出StaleElementReferenceException 。
2.2.刷新元素
讓我們使用在重新訪問之前刷新元素的重試邏輯:
boolean retryingFindClick(By locator) {
boolean result = false;
int attempts = 0;
while (attempts < 5) {
try {
driver.findElement(locator).click();
result = true;
break;
} catch (StaleElementReferenceException ex) {
System.out.println(ex.getMessage());
}
attempts++;
}
return result;
}
每當發生StaleElementReferenceException時,我們將使用存儲的元素定位器在再次執行單擊之前再次定位該元素。
現在,讓我們更新測試以使用新的重試邏輯:
void givenDynamicPage_whenRefreshingAndAccessingSavedElement_thenHandleSERE() {
driver.navigate().to("https://the-internet.herokuapp.com/dynamic_content?with_content=static");
final WebElement element = driver.findElement(LOCATOR_DYNAMIC_CONTENT);
if (!retryingFindClick(LOCATOR_REFRESH)) {
Assertions.fail("Element is still stale after 5 attempts");
}
Assertions.assertDoesNotThrow(() -> retryingFindGetText(LOCATOR_DYNAMIC_CONTENT));
}
我們看到我們需要更新測試,如果我們需要對許多測試執行此操作,這會很麻煩。幸運的是,我們可以在中央位置使用此邏輯,而無需更新所有測試。
3. 避免StaleElementReferenceException通用策略
我們將為通用解決方案創建兩個新類: RobustWebDriver和RobustWebElement 。
3.1. RobustWebDriver
首先,我們需要創建一個實現WebDriver實例的新類。我們將把它寫成WebDriver的包裝器。它調用WebDriver方法,方法findElement和findElements將返回RobustWebElement :
class RobustWebDriver implements WebDriver {
WebDriver originalWebDriver;
RobustWebDriver(WebDriver webDriver) {
this.originalWebDriver = webDriver;
}
...
@Override
public List<WebElement> findElements(By by) {
return originalWebDriver.findElements(by)
.stream().map(e -> new RobustWebElement(e, by, this))
.collect(Collectors.toList());
}
@Override
public WebElement findElement(By by) {
return new RobustWebElement(originalWebDriver.findElement(by), by, this);
}
...
}
3.2. RobustWebElement
RobustWebElement是WebElement的包裝器。該類實現WebElement接口並包含重試邏輯:
class RobustWebElement implements WebElement {
WebElement originalElement;
RobustWebDriver driver;
By by;
int MAX_RETRIES = 10;
String SERE = "Element is no longer attached to the DOM";
RobustWebElement(WebElement element, By by, RobustWebDriver driver) {
originalElement = element;
by = by;
driver = driver;
}
...
}
我們必須實現WebElement接口的每個方法,以便在拋出StaleElementReferenceException時執行元素的刷新。為此,讓我們介紹一些包含刷新邏輯的輔助方法。我們將從這些重寫的方法中調用它們。
我們可以利用功能接口並創建一個幫助類來調用WebElement的各種方法:
class WebElementUtils {
private WebElementUtils(){
}
static void callMethod(WebElement element, Consumer<WebElement> method) {
method.accept(element);
}
static <U> void callMethod(WebElement element, BiConsumer<WebElement, U> method, U parameter) {
method.accept(element, parameter);
}
static <T> T callMethodWithReturn(WebElement element, Function<WebElement, T> method) {
return method.apply(element);
}
static <T, U> T callMethodWithReturn(WebElement element, BiFunction<WebElement, U, T> method, U parameter) {
return method.apply(element, parameter);
}
}
在WebElement中,我們實現了四個包含刷新邏輯的方法,並調用了之前介紹的WebElementUtils :
void executeMethodWithRetries(Consumer<WebElement> method) { ... }
<T> T executeMethodWithRetries(Function<WebElement, T> method) { ... }
<U> void executeMethodWithRetriesVoid(BiConsumer<WebElement, U> method, U parameter) { ... }
<T, U> T executeMethodWithRetries(BiFunction<WebElement, U, T> method, U parameter) { ... }
click方法將如下所示:
@Override
public void click() {
executeMethodWithRetries(WebElement::click);
}
我們現在需要為我們的測試更改的是WebDriver實例:
driver = new RobustWebDriver(new ChromeDriver(options));
其他一切都可以保持不變, StaleElementReferenceException不應再發生。
4。結論
在本教程中,我們了解到在 DOM 已更改且元素尚未刷新後訪問元素時可能會發生StaleElementReferenceException 。我們在測試中引入了重試邏輯,以便在發生StaleElementReferenceException時刷新元素。
與往常一樣,所有這些示例的實現都可以在 GitHub 上找到。