Selenium页面回退后提示stale element reference错误

前言
● 需求
做 WEB 的 UI 自动化练习,其需求为:
访问 Testerhome 的精华帖页面,获取精华帖页面中的用户名称,并逐个点击进入用户详情后,关闭页面详情,直到最后一个。
● 环境
○ Java 8
○ Maven
○ Selenium 4.0
○ Junit 5
初步代码
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;

import java.time.Duration;
import java.util.List;

public class imoocTest {
private static WebDriver driver;

@BeforeAll
static void setUP() {
    // 获取当前文件下所在的目录
    String currentDirectory = System.getProperty("user.dir");
    // 设置chromeDriver
    System.setProperty("webdriver.chrome.driver", currentDirectory + "/src/test/resources/Driver/Chrome/chromedriver");
    ChromeOptions options = new ChromeOptions();
    //浏览器模拟手机模式
    options.addArguments("--disable-web-security");
    // 最大化窗口
    options.addArguments("--start-maximized");
    //解决报 403 问题
    options.addArguments("--remote-allow-origins=*");
    driver = new ChromeDriver(options);
    // 隐式调用
    driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
}

@AfterAll
static void tearDown() {
    //关闭浏览器
    driver.quit();
}

@Test
public void test1()  throws InterruptedException {
    // 打开Testerhome精华帖页面
    driver.get("https://testerhome.com/topics/excellent");
    // 获取页面中所有username的元素信息
    List<WebElement> userNameListElement = driver.findElements(By.xpath("//div[@class='info']/a"));
    for (WebElement userNameElement : userNameListElement) {
        String doctorName = userNameElement.getText();
        System.out.println(doctorName);
        // 点击进入用户详情页面
        userNameElement.click();
        Thread.sleep(1000);
        // 页面回退
        driver.navigate().back();
        Thread.sleep(1000);
    }
}

}
问题现象
执行上述代码,在循环遍历到第二个元素,程序报错,错误信息如下
org.openqa.selenium.StaleElementReferenceException: stale element reference: stale element not found
(Session info: chrome=119.0.6045.199)
For documentation on this error, please visit: https://www.selenium.dev/documentation/webdriver/troubleshooting/errors#stale-element-reference-exception
Build info: version: ‘4.15.0’, revision: ‘1d14b5521b’
System info: os.name: ‘Mac OS X’, os.arch: ‘x86_64’, os.version: ‘13.6’, java.version: ‘11.0.21’
Driver info: org.openqa.selenium.chrome.ChromeDriver
Command: [b57b931d2229e257dbdc3498963c1d9b, getElementText {id=1D2B358D7CCB6367AF1A65523245310C_element_49}]
Capabilities {acceptInsecureCerts: false, browserName: chrome, browserVersion: 119.0.6045.199, chrome: {chromedriverVersion: 119.0.6045.105 (38c72552c5e…, userDataDir: /var/folders/9l/slrbyxhs46l…}, fedcm:accounts: true, goog:chromeOptions: {debuggerAddress: localhost:58122}, networkConnectionEnabled: false, pageLoadStrategy: normal, platformName: mac, proxy: Proxy(), se:cdp: ws://localhost:58122/devtoo…, se:cdpVersion: 119.0.6045.199, setWindowRect: true, strictFileInteractability: false, timeouts: {implicit: 0, pageLoad: 300000, script: 30000}, unhandledPromptBehavior: dismiss and notify, webauthn:extension:credBlob: true, webauthn:extension:largeBlob: true, webauthn:extension:minPinLength: true, webauthn:extension:prf: true, webauthn:virtualAuthenticators: true}
Element: [[ChromeDriver: chrome on mac (b57b931d2229e257dbdc3498963c1d9b)] → css selector: .user-name]
Session ID: b57b931d2229e257dbdc3498963c1d9b
at org.openqa.selenium.remote.RemoteWebElement.execute(RemoteWebElement.java:224)
at org.openqa.selenium.remote.RemoteWebElement.getText(RemoteWebElement.java:192)
at imoocTest.test1(imoocTest.java:48)
问题分析
org.openqa.selenium.StaleElementReferenceException异常表示Selenium尝试操作的元素在DOM中已经不存在或者已经被更新,通常发生在页面内容动态变化(如页面刷新、元素移除或重新渲染)之后。
要解决这个问题,可以采取以下策略:
● 重新定位元素
在执行任何对元素的操作之前,先重新查找该元素。例如,如果你在一个循环里操作列表项,并且在每次迭代时页面内容可能会发生变化,那么你应该在每次迭代时都重新找到那个元素。
● 使用ExpectedConditions
在进行元素操作前确保元素存在于DOM中并且可见。WebDriverWait类可以与ExpectedConditions结合使用,来等待特定条件满足后再进行操作。
● 避免直接处理集合中的元素
如果你正在遍历一个元素集合并逐个操作它们,考虑先将所有元素的引用存储到一个List中,然后遍历这个List而不是原始集合。
● 合理使用显式等待
根据页面加载和元素出现的实际情况设置合理的显式等待时间,这可以帮助防止因页面加载不完全而导致的StaleElementReferenceException。
问题解决
根据问题分析,导致出现问题是由于页面回退之后,页面刷新导致driver 已经被更新了。
解决方案有两种:
a. 回退之后,将driver 再次进行赋值即可
b. 避免直接处理集合中的元素,将所有元素的引用存储到一个List中
代码调整
○ 由于页面刷新导致driver 已经被更新了,那简单的方法就是回退之后,将driver 再次进行赋值即可
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;

import java.time.Duration;
import java.util.List;

public class imoocTest {
private static WebDriver driver;

@BeforeAll
static void setUP() {
    // 获取当前文件下所在的目录
    String currentDirectory = System.getProperty("user.dir");
    // 设置chromeDriver
    System.setProperty("webdriver.chrome.driver", currentDirectory + "/src/test/resources/Driver/Chrome/chromedriver");
    ChromeOptions options = new ChromeOptions();
    //浏览器模拟手机模式
    options.addArguments("--disable-web-security");
    // 最大化窗口
    options.addArguments("--start-maximized");
    //解决报 403 问题
    options.addArguments("--remote-allow-origins=*");
    driver = new ChromeDriver(options);
    // 隐式调用
    driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
}

@AfterAll
static void tearDown() {
    //关闭浏览器
    driver.quit();
}

@Test
public void test1()  throws InterruptedException {
    // 打开Testerhome精华帖页面
    driver.get("https://testerhome.com/topics/excellent");
    // 获取页面中所有username的元素信息
    List<WebElement> userNameListElement = driver.findElements(By.xpath("//div[@class='info']/a"));
    for(int i=0;i<userNameListElement.size();i++){
        String doctorName = userNameListElement.get(i).getText();
        System.out.println(doctorName);
        // 点击进入用户详情页面
        userNameListElement.get(i).click();
        Thread.sleep(1000);
        // 页面回退
        driver.navigate().back();
        // 重新赋值
        userNameListElement = driver.findElements(By.xpath("//div[@class='info']/a"));
        Thread.sleep(1000);
    }
}

}
○ 避免直接处理集合中的元素,将所有元素的引用存储到一个List中
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;

import java.time.Duration;
import java.util.List;

public class imoocTest {
private static WebDriver driver;

@BeforeAll
static void setUP() {
    // 获取当前文件下所在的目录
    String currentDirectory = System.getProperty("user.dir");
    // 设置chromeDriver
    System.setProperty("webdriver.chrome.driver", currentDirectory + "/src/test/resources/Driver/Chrome/chromedriver");
    ChromeOptions options = new ChromeOptions();
    //浏览器模拟手机模式
    options.addArguments("--disable-web-security");
    // 最大化窗口
    options.addArguments("--start-maximized");
    //解决报 403 问题
    options.addArguments("--remote-allow-origins=*");
    driver = new ChromeDriver(options);
    // 隐式调用
    driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
}

@AfterAll
static void tearDown() {
    //关闭浏览器
    driver.quit();
}

@Test
public void test1()  throws InterruptedException {
    // 打开Testerhome精华帖页面
    driver.get("https://testerhome.com/topics/excellent");
    // 获取页面中所有username的元素信息
    List<WebElement> userNameListElement = driver.findElements(By.xpath("//div[@class='info']/a"));
    // 将用户信息存入到ArrayList中
    List<String> userNameList =new ArrayList<>();
    for(WebElement userNameElement: userNameListElement){
        userNameList.add(userNameElement.getText());
    }
    for(int i=0;i<userNameList.size();i++){
        String doctorName = userNameList.get(i);
        System.out.println(doctorName);
        // 点击进入用户详情页面
        driver.findElement(By.xpath("//a[@data-name='"+doctorName+"']")).click();
        Thread.sleep(1000);
        // 页面回退
        driver.navigate().back();
        Thread.sleep(1000);
    }
}

}