WebDriver的工作原理

今天QQ羣有個朋友說,他現在對webdriver的使用已經比較熟悉了,但是對於webdriver的工作原理還不是太瞭解。我覺得他的這些話說出一些的人的心聲。大家想下如果在你面試的時候面試官問你這個問題的話,你會怎麼回答呢?希望大家看了我下面寫的文章會有所理解,並且會很順利的回答出來。

 

下面詳細的闡述下WebDriver的工作原理:

 

在我們new一個WebDriver的過程中,Selenium首先會確認瀏覽器的native component是否存在可用而且版本匹配。接着就在目標瀏覽器裏啓動一整套Web Service(實際上就是瀏覽器廠商提供的driver, 比如IEDriver, ChromeDriver,它們都實現了WebDriver's wire protocol.),這套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我們希望瀏覽器接下來做什麼事情。

 

可以更通俗的理解:由於客戶端腳本(java, python, ruby)不能直接與瀏覽器通信,這時候可以把WebService當做一個翻譯器,它可以把客戶端代碼翻譯成瀏覽器可以識別的代碼(比如js).客戶端(也就是測試腳本)創建1個session,在該session中通過http請求向WebService發送restful的請求,WebService翻譯成瀏覽器懂得腳本傳給瀏覽器,瀏覽器把執行的結果返回給WebService,WebService把返回的結果做了一些封裝(一般都是json格式),然後返回給client,根據返回值就能判斷對瀏覽器的操作是不是執行成功

摘自官網對於chrome driver的描述:

The ChromeDriver consists of three separate pieces. There is the browser itself ("chrome"), the language bindings provided by the Selenium project ("the driver") and an executable downloaded from the Chromium project which acts as a bridge between "chrome" and the "driver". This executable is called "chromedriver", but we'll try and refer to it as the "server" in this page to reduce confusion.

大概意思就是我們下載的chrome可執行文件(.exe)是爲作爲瀏覽器與client(language binding)橋樑的作用,也更印證了對於Web Service(driver)的理解。

 

 

舉個實際的例子:

WebDriver diver = new FirefoxDriver();

driver.get("http://google.com");

 

在執行 driver.get("http://google.com");  這句代碼時,client也就是我們的測試代碼向Web Service(remote server)發送瞭如下的請求:

POST session/285b12e4-2b8a-4fe6-90e1-c35cba245956/url

post_data {"url":"http://google.com"} 

 

通過post的方式請求localhost:port/hub/session/session_id/url地址,請求瀏覽器完成跳轉url的操作。

 

如果上述請求是可接受的,或者說Web Service是實現了這個接口,那麼Web Service會跳轉到該post data包含的url,並返回如下的response

{"name":"get","sessionId":"285b12e4-2b8a-4fe6-90e1-c35cba245956","status":0,"value":""}

 

該response中包含如下信息

name:Web Service端的實現的方法的名稱,這裏是get,表示跳轉到指定url;

sessionId:當前session的id;

status:請求執行的狀態碼,非0表示未正確執行,這裏是0,表示一切ok不必擔心;

value:請求的返回值,這裏返回值爲空,如果client調用title接口,則該值應該是當前頁面的title;

 

如果client發送的請求是定位某個特定的頁面元素,則response的返回值可能是這樣的:

{"name":"findElement","sessionId":"285b12e4-2b8a-4fe6-90e1-c35cba245956","status":0,"value":{"ELEMENT":"{2192893e-f260-44c4-bdf6-7aad3c919739}"}}

 

name,sessionId,status跟上面的例子是差不多的,區別是該請求的返回值是ELEMENT:{2192893e-f260-44c4-bdf6-7aad3c919739},表示定位到元素的id,通過該id,client可以發送如click之類的請求與server端進行交互。

 

 

 

下圖表示了各種WebDriver的工作原理

 

 

從上圖中我們可以看出,不同瀏覽器的WebDriver子類,都需要依賴特定的瀏覽器原生組件,例如運行Firefox就需要一個add-on名字叫webdriver.xpi。而IE的話就需要用到一個dll文件來轉化Web Service的命令爲瀏覽器native的調用。另外,圖中還標明瞭WebDriver Wire協議是一套基於RESTful的web service

 

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

複製代碼

 1 .put(NEW_SESSION, post("/session"))  
 2         .put(QUIT, delete("/session/:sessionId"))  
 3         .put(GET_CURRENT_WINDOW_HANDLE, get("/session/:sessionId/window_handle"))  
 4         .put(GET_WINDOW_HANDLES, get("/session/:sessionId/window_handles"))  
 5         .put(GET, post("/session/:sessionId/url"))  
 6    7             // The Alert API is still experimental and should not be used.   8         .put(GET_ALERT, get("/session/:sessionId/alert"))  
 9         .put(DISMISS_ALERT, post("/session/:sessionId/dismiss_alert"))  
10         .put(ACCEPT_ALERT, post("/session/:sessionId/accept_alert"))  
11         .put(GET_ALERT_TEXT, get("/session/:sessionId/alert_text"))  
12         .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       } //...

複製代碼

 

 PS:如果想更深入的瞭解WebDriver的架構,可以參考該文章http://www.aosabook.org/en/selenium.html

 

來源:http://www.cnblogs.com/testermark/p/3546287.html 

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