Selenide翻页采集商品名时出现StaleElementReferenceException报错如何解决
错误原因
StaleElementReferenceException 触发的核心原因是操作的DOM元素已经被页面销毁,缓存的旧引用失效,你的代码具体存在三个问题:
- 点击下一页按钮后没有等待页面跳转、新内容渲染完成,就立即执行元素采集逻辑,此时读取到的还是上一页已经失效的元素引用。
- 采集商品名称时没有先校验元素是否已完成加载、挂载到DOM树,直接遍历调用
getText(),遍历过程中如果页面DOM发生变更就会报错。 - 下一页按钮点击逻辑没有做可用性校验,也没有等待跳转完成的判断,页面还在加载中就进入下一轮采集逻辑。
修复代码
你可以按照如下逻辑修改代码即可解决报错:
import com.codeborne.selenide.CollectionCondition; import com.codeborne.selenide.ElementsCollection; import com.codeborne.selenide.Selenide; import com.codeborne.selenide.WebDriverRunner; import org.junit.jupiter.api.Assertions; import org.openqa.selenium.By; import java.time.Duration; import java.util.List; import java.util.stream.Collectors; public class AfterPhonesCategory { // 商品标题选择器统一管理,避免重复编写 private static final By GOODS_TITLE_SELECTOR = By.xpath("//article//h3[@data-zone-name = 'title']//span"); public List<String> collectResultsFromPage() { // 滚动到底部触发懒加载,确保本页所有商品都加载完成 Selenide.executeJavaScript("window.scrollTo(0, document.body.scrollHeight)"); // 最长等待10秒,直到商品标题元素全部加载完成、可正常显示 ElementsCollection resultsNameWebElement = Selenide.$$(GOODS_TITLE_SELECTOR) .shouldBe(CollectionCondition.sizeGreaterThan(0), Duration.ofSeconds(10)) .shouldBe(CollectionCondition.allMatch("所有元素都可见", elem -> elem.isDisplayed())); Assertions.assertTrue(resultsNameWebElement.stream().anyMatch(x->x.getText().contains("Apple")), "the snippet does not contain the name of the apple"); // 立即提取所有文本,避免后续元素引用失效 return resultsNameWebElement.stream() .map(elem -> elem.exists() && elem.isDisplayed() ? elem.getText() : "") .filter(text -> !text.isBlank()) .collect(Collectors.toList()); } private boolean initNextPageButton() { ElementsCollection goNextPageButton = Selenide.$$x("//a[contains(@class, '_3OFYT')]"); // 先判断下一页按钮存在且可点击 if(goNextPageButton.size() > 0 && goNextPageButton.get(0).isEnabled() && goNextPageButton.get(0).isDisplayed()){ // 记录点击前的URL,用来判断是否跳转完成 String beforePageUrl = WebDriverRunner.getWebDriver().getCurrentUrl(); goNextPageButton.get(0).click(); // 等待页面跳转完成 Selenide.Wait().until(driver -> !driver.getCurrentUrl().equals(beforePageUrl)); // 等待新页面的商品元素加载完成 Selenide.$$(GOODS_TITLE_SELECTOR).shouldBe(CollectionCondition.sizeGreaterThan(0), Duration.ofSeconds(10)); return true; } return false; } private List<String> findResults; public AfterPhonesCategory collectResults() { findResults = collectResultsFromPage(); while (initNextPageButton()) { findResults.addAll(collectResultsFromPage()); } return this; } }
内容的提问来源于stack exchange,提问作者Otto Rahn
相关产品推荐
相关产品推荐

