【Selenium】stale element reference 问题解决方案

问题现象

如截图所示,异常原因:stale element reference: element is not attached to the page document
在这里插入图片描述

用例执行环境

java 1.8
selenium 3.141.0
docker(chromedriver 2.43 + chromebrowser 70.0.3538.77)

出现该问题的场景

autoOperate.verify_element_exists(driver, driverOperBean);  //判断元素是否存在
List<WebElement> webElements = driver.findElements(By.cssSelector(elements));
if(webElements == null || webElements.isEmpty()){
    throw new CheckDriverException(String.format("要编历的元素列表为空; case信息: %s;", driverOperBean.toString()));
}
if(start> webElements.size()){
    throw new CheckDriverException(String.format("编历的元素列表长度不足; 元素列表长度:[%s], 要编历的起始位置:[%s]; 用例行数: %s;",webElements.size(),start,driverOperBean.getNum()));
}
int forLen = webElements.size();
if(end>0 && end<=forLen){
    forLen=end;
}
for(int i=start;i<=forLen;i++){
    String param = driverOperBean.getCustomizename();
    List<DriverOperBean> driverOperBeans = driverOperBean.getDriverOpers();
    if(StringUtils.isNotEmpty(param)){ 
        autoOperate.setParamElMap(driver.hashCode(), param, webElements .get(i-1));
    }
    goAutoCase(driver, autoCaseinfoProcess.copyDriverOperBeanlist(driverOperBeans)); 
}

逻辑是:根据一个定位获取一个元素list,然后遍历,遍历会涉及点击类操作;

分析问题

这里看到整个获取元素和遍历逻辑是没有问题,但是在执行遍历的过程中有的case能执行通过,有的会在遍历点击的时候报stale element reference: element is not attached to the page document错误(任何操作元素的方法都会报这个错误);这个错误是什么意思呢?从翻译的字面意思是,这个元素没有挂载到dom树上(这里就不说dom树的概念了)。

没有在dom树上?
通过case直接的对比,发现一个问题,能够通过的case是因为这个case中的元素是写死在html文件里的,不通过的是因为遍历的元素是通过js渲染的。

写死在html和js渲染的有什么区别呢?
url进行网络请求的时候拿到的最原始的内容其实是html源码,可以直接看到内容;还有一种就是直接通过js渲染,呈现在浏览器网页上;这个应该容易理解,对于我们遍历的元素,代码举例如下(示例意在即可):

  • 可点击的
<div>
	<div><a></div>
	<div><a></div>
	<div><a></div>
</div>

for循环点击a标签的时候,是能够拿到当前浏览器已经渲染好的完整dom树结构下的标签元素(这个获取的元素是唯一的),在循环点击的a标签的时候,其他a标签是不会变的,所以可以顺利执行,因为在for之前的getElementsBy那个list内容,在循环的click或其他api操作下不会改变的。

  • 不可点击的
<div id='aa'></div>   //页面源码

$("#aa").append([<div><a></div>, <div><a></div>, <div><a></div>]) //用js渲染

$('a').on('click', functino(){				//为每个a标签加个点击事件
	$("#aa").append([<div><a></div>, <div><a></div>, <div><a></div>])
})

这里想说明点就是,出现报错的原因,当循环开始的时候,第一个元素点击是正常的,因为list里的对象在点击前是没有变化的,点击后呢?dom树内容已经更新了,虽然是以同名同姓的方式填入,但还是变化了,所以还是按原来getElementsBy获取到的那个list遍历,肯定不对的 ,现实页面中对应的同名元素对象已经更新了,这个官网有详细的解释,所以面对改变,我们要做的是拿着那个定位方式,再来一遍。

解决后的代码

List<WebElement> webElements = driver.findElements(By.cssSelector(elements));
...省略同上
for(int i=start;i<=forLen;i++){
    String param = driverOperBean.getCustomizename();
    List<DriverOperBean> driverOperBeans = driverOperBean.getDriverOpers();
    if(StringUtils.isNotEmpty(param)){  
        // **** 再次获取需要循环的elements,防止dom刷新,即使没有刷新也等同webElements
        List<WebElement> wes = driver.findElements(By.cssSelector(elements));
        if(wes.size() > webElements.size() && i > webElements.size()){
            i = webElements.size();     // **** 如果操作后的目标list发生变化,还按原来的list大小处理
        }
        autoOperate.setParamElMap(driver.hashCode(), param, wes.get(i-1));   // **** 取元素按最新wes的内容取
    }
    goAutoCase(driver, autoCaseinfoProcess.copyDriverOperBeanlist(driverOperBeans));  //执行selenium api方法
}

备注 :这个问题的引发原因应该会出现在很多场景里(不限于文中的for循环),但是处理方案就是保证执行操作前,这个元素是最新的并且可进行操作

参考文献

Stale Element Reference Exception

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章