Selenium源碼分析之WebDriver

文章來源:http://blog.csdn.net/ant_yan/article/details/7970793

最近比較空閒就仔細看了一下Selenium的源碼,因爲主要是使用WebDriver所以重點關注了一下WebDriver的工作原理。在前一篇blog裏已經解釋過了WebDriver與之前Selenium的JS注入實現不同,直接利用了瀏覽器native support來操作瀏覽器。所以對於不同平臺,不同的瀏覽器,必須依賴一個特定的瀏覽器的native component來實現把WebDriver API的調用轉化爲瀏覽器的native invoke。


在我們new一個WebDriver的過程中,Selenium首先會確認瀏覽器的native component是否存在可用而且版本匹配。接着就在目標瀏覽器裏啓動一整套Web Service,這套Web Service使用了Selenium自己設計定義的協議,名字叫做The WebDriver Wire Protocol。這套協議非常之強大,幾乎可以操作瀏覽器做任何事情,包括打開、關閉、最大化、最小化、元素定位、元素點擊、上傳文件等等等等。


WebDriver Wire協議是通用的,也就是說不管是FirefoxDriver還是ChromeDriver,啓動之後都會在某一個端口啓動基於這套協議的Web Service。例如FirefoxDriver初始化成功之後,默認會從http://localhost:7055開始,而ChromeDriver則大概是http://localhost:46350之類的。接下來,我們調用WebDriver的任何API,都需要藉助一個ComandExecutor發送一個命令,實際上是一個HTTP request給監聽端口上的Web Service。在我們的HTTP request的body中,會以WebDriver Wire協議規定的JSON格式的字符串來告訴Selenium我們希望瀏覽器接下來做社麼事情。


這裏筆者初步畫了一個圖來表示各種WebDriver的工作原理:




從上圖中我們可以看出,不同瀏覽器的WebDriver子類,都需要依賴特定的瀏覽器原生組件,例如Firefox就需要一個add-on名字叫webdriver.xpi。而IE的話就需要用到一個dll文件來轉化Web Service的命令爲瀏覽器native的調用。另外,圖中還標明瞭WebDriver Wire協議是一套基於RESTful的web service。如果不明白什麼是RESTful的,可以參見筆者之前另外一篇介紹REST的blog(http://blog.csdn.net/ant_yan/article/details/7963517)


關於WebDriver Wire協議的細節,比如希望瞭解這套Web Service能夠做哪些事情,可以閱讀Selenium官方的協議文檔, 在Selenium的源碼中,我們可以找到一個HttpCommandExecutor這個類,裏面維護了一個Map<String, CommandInfo>,它負責將一個個代表命令的簡單字符串key,轉化爲相應的URL,因爲REST的理念是將所有的操作視作一個個狀態,每一個狀態對應一個URI。所以當我們以特定的URL發送HTTP request給這個RESTful web service之後,它就能解析出需要執行的操作。截取一段源碼如下:

  1. nameToUrl = ImmutableMap.<String, CommandInfo>builder()  
  2.         .put(NEW_SESSION, post("/session"))  
  3.         .put(QUIT, delete("/session/:sessionId"))  
  4.         .put(GET_CURRENT_WINDOW_HANDLE, get("/session/:sessionId/window_handle"))  
  5.         .put(GET_WINDOW_HANDLES, get("/session/:sessionId/window_handles"))  
  6.         .put(GET, post("/session/:sessionId/url"))  
  7.   
  8.             // The Alert API is still experimental and should not be used.  
  9.         .put(GET_ALERT, get("/session/:sessionId/alert"))  
  10.         .put(DISMISS_ALERT, post("/session/:sessionId/dismiss_alert"))  
  11.         .put(ACCEPT_ALERT, post("/session/:sessionId/accept_alert"))  
  12.         .put(GET_ALERT_TEXT, get("/session/:sessionId/alert_text"))  
  13.         .put(SET_ALERT_VALUE, post("/session/:sessionId/alert_text"))  

可以看到實際發送的URL都是相對路徑,後綴多以/session/:sessionId開頭,這也意味着WebDriver每次啓動瀏覽器都會分配一個獨立的sessionId,多線程並行的時候彼此之間不會有衝突和干擾。例如我們最常用的一個WebDriver的API,getWebElement在這裏就會轉化爲/session/:sessionId/element這個URL,然後在發出的HTTP request body內再附上具體的參數比如by ID還是CSS還是Xpath,各自的值又是什麼。收到並執行了這個操作之後,也會回覆一個HTTP response。內容也是JSON,會返回找到的WebElement的各種細節,比如text、CSS selector、tag name、class name等等。以下是解析我們說的HTTP response的代碼片段:


  1. try {  
  2.         response = new JsonToBeanConverter().convert(Response.class, responseAsText);  
  3.       } catch (ClassCastException e) {  
  4.         if (responseAsText != null && "".equals(responseAsText)) {  
  5.           // The remote server has died, but has already set some headers.  
  6.           // Normally this occurs when the final window of the firefox driver  
  7.           // is closed on OS X. Return null, as the return value _should_ be  
  8.           // being ignored. This is not an elegant solution.  
  9.           return null;  
  10.         }  
  11.         throw new WebDriverException("Cannot convert text to response: " + responseAsText, e);  
  12.       } //...  

相信總結道這裏,應該對WebDriver的運行原理應該清楚了!其實挺佩服這一套RESTful web service的設計。感覺封裝WebDriver暴露出來的public API還可以更加友好跟強大一點,這次就先總結道這裏,會繼續分析Selenium源碼,繼續分享的!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章