問題現象
如截圖所示,異常原因: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循環),但是處理方案就是保證執行操作前,這個元素是最新的並且可進行操作
。