在自動化元素定位操作中經常使用智能等待來加強定位的強壯性,主要就是因為WebDriver沒有提供頁面加載場景的方法;在使用JavaScript知識的突然心生靈感,可以使用JavaScript來配合驗證頁面加載,結果發現我真是井底之蛙。
一、domcument.readyState
首先定位從Document對象出發,而Document對象是在html文檔加載完成便可操作使用,所以判斷文件裝載完成即可;Document對象的readyState屬性返回當前文檔裝載狀態:
uninitialized 表示未開始載入
loading 載入中,下載了html並且開始加載
interactive 已加載完html內容
complete 已加載完html內容和下載解析了子資源(js、css、image),不包含ajax異步。
public static void pageDOMLoadComplete(){ JavaScriptExecutor js=(JavaScriptExecutor) SeleniumMethod.ThreadDriver.get(); try { while(!"complete".equals(js.executeScript("return document.readyState"))){ Thread.sleep(2000); } logger.info("頁面元素加載完成"); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); logger.info("判斷頁面元素加載異常"); } }
如上代碼,如果結果返回的complete表示加載完成,這時候就可以開始元素定位操作。返回不是complete則使用線程循環等待,直到加載完成才跳出循環。
詳細了解的建議去腦補一下瀏覽器對頁面加載的過程。通過瀏覽器發送請求到服務器響應先得到html形成Document對象,瀏覽器解析html形成dom樹開始加載,當加載dom樹遇到外部資源時去請求外部資源,但加載dom樹並不會等待會繼續加載,當資源如JavaScript、css、圖片等等加載完成這時候document.readyState的值變成complete。
二、Selenium加載策略
因為Selenium從頭到位都沒有任何判斷頁面加載完成的關鍵字;而結合document.readyState的返回來確定頁面加載成功,然后再去執行定位操作,這樣來說是個合理的思想。根據想法編寫下面的代碼:
WebDriver driver = new ChromeDriver(); driver.get("https://www.sina.com.cn/"); try { while(!"complete".equals(((JavaScriptExecutor) driver).executeScript("return document.readyState"))) { Thread.sleep(1000); } } catch(Exception e) { // TODO: handle exception e.printStackTrace(); } driver.findElement(By.name("SerchKey")).sendKeys("軟件測試"); driver.findElement(By.name("SearchSubButton")).click(); driver.quit();
上面代碼利用JavaScript執行一段腳本document.readyState的值返回不是complete則一直循環並線程等待1S,直到返回的值是complete退出循環。
但是在代碼運行后發現,從來都沒有進入過循環。於是猜測,是get()方法在打開瀏覽器並訪問URL時,是不是就已經判斷過頁面加載完成了?驗證方法很簡單,把循環去掉,代碼直接打印document.readyState返回的值
System.out.print(((JavaScriptExecutor)driver).executeScript("return document.readyState"));
得到的結果每次都是complete,驗證確實get()方法后就已經頁面加載成功了。在使用WebDriver時,每次打開瀏覽器都知道它並不會帶有任何緩存數據,而是一個全新初始化的瀏覽器,這點只要用過Selenium的同學,並做過登錄功能都應該知道,每次使用都需要重新登錄,瀏覽器並不會保存任何cookie、緩存信息。就是因為這個原因,WebDriver每次調起瀏覽器並訪問URL的時候因為沒有緩存所以頁面加載會非常慢(當然不僅僅是緩存,WebDriver還會去遍歷一些其他東西,如虛擬網卡、代理配置等);這個慢的現象大多數是頁面已經展示內容了,然后瀏覽器還在轉圈加載,然而使用document.readyState來判斷好像不現實了,因為WebDriver的get()方法默認就會等待頁面加載完。所以思路就轉化為,get()方法的探究。
通過官方資料查詢,發現WebDriver有pageLoadStrategy加載策略這一說,地址如下:https://www.w3.org/TR/webdriver/#dfn-table-of-page-load-strategies,大家可以自己去瞧瞧。加載策略有如下三種:
none:沒有說明對應關系
eager:對應ducument readiness state為”interactive”
normal:對應ducument readiness state為”complete”
從上面三種加載策略可以看到,官方除了none這種沒有具體說明外,另外兩種對應的正式document.readyState返回的狀態。而默認的加載策略是normal,所以再次驗證為什么使用document.readyState得到的值是complete。驗證代碼如下:
DesiredCapabilities capabilities = DesiredCapabilities.chrome(); capabilities.setCapability("pageLoadStrategy", "none"); WebDriver driver = new ChromeDriver(capabilities); driver.get("https://www.sina.com.cn/"); System.out.println(((JavaScriptExecutor) driver).executeScript("return document.readyState")); driver.findElement(By.name("SerchKey")).sendKeys("軟件測試"); driver.findElement(By.name("SearchSubButton")).click(); System.out.println(((JavaScriptExecutor) driver).executeScript("return document.readyState")); driver.quit();
執行代碼,打印document.readyState返回的結果是loading;在使用findElement查找元素時報錯“Unable to locate element”。當加載策略為none時,對應document.readyState結果是loading的時候,頁面還在加載中,進行元素查找會報錯,這很好理解,Dom樹還在加載過程中,是無法解析任何元素的查找的,所以報錯。
接下來分別嘗試了eager加載策略,需要注意一點的是Chrome瀏覽器並不支持這種加載策略,所以改為了Firefox驗證。
DesiredCapabilities capabilities = DesiredCapabilities.firefox(); capabilities.setCapability("pageLoadStrategy", "eager"); WebDriver driver = new FirefoxDriver(capabilities);
其他代碼不變,執行代碼打印document.readyState返回的結果是interactive,但是不同的是使用findElement查找元素沒有報錯,並且全部執行成功。當加載策略為eager,頁面加載結果interactive時,整個DOM樹加載完成,這時候就已經可以定位元素並執行操作了;根據document.readyState與WebDriver的加載策略得出以下結論:
(1)loading與none
DOM樹 載入中,也就是說已經下載了html並且開始加載;這時候是無法查找元素的,get()方法的執行時間非常短,可以馬上執行get()方法后面的代碼。所以這時候就可以使用下面代碼再次進行判斷后再執行。
while(!"complete".equals(((JavascriptExecutor)driver).executeScript("return document.readyState"))){}
這樣的作法好像與加載策略的默認方法沒什么不同,但是WebDriver的get()方法可不僅僅是等待頁面為complete而已,它還會去判斷一些瀏覽器其他的配置。
這種情況還有一種不建議使用的方式,就是使用隱式等待,每次查找元素找不到時,等待一定時間。隱式等待在當前driver的整個生命周期中都生效,是非常適合配合none這種加載策略的。
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
新片場https://www.wode007.com/sites/73286.html 傲視網https://www.wode007.com/sites/73285.html
(2)interactive與eager
已加載完html內容,但是沒有解析子資源;這時候DOM樹加載完成,並且去請求子資源了,是可以查找元素並正確返回了。有個弊端就是如果一些數據是通過JavaScript去賦值的,這時候查找元素某些屬性數據可能得到空。
(3)complete與normal
已加載完html內容和下載解析了子資源(JavaScript、css、image),不包含ajax異步。前面應該都沒疑問,重要的是ajax異步請求的加載。如果頁面上一些數據是通過ajax異步去請求並加載的,同樣在ajax請求中數據響應沒有及時回來的情況下,拿到的也是空。當然這並不需要擔心,只要不是太大數據請求響應還是非常快速的。