您好,登錄后才能下訂單哦!
引入:
其實熟悉selenium的人肯定都對wire協議不陌生,因為我們知道,當我們在代碼中使用WebDriver API 做一些操作的時候,它最終會轉為一個基于wire協議的命令(Command)發送到瀏覽器,并且請求的內容都封裝在json對象中,通過WebService調用瀏覽器,從而所有WebDriver API的調用都最后轉為對瀏覽器的Web Service調用。
我們這里就通過最簡單的輸入文本內容(WebElement.sendKeys(String))來研究下wire協議。以及上述所有細節。
Wire 協議的參考規范如下:
http://code.google.com/p/selenium/wiki/JsonWireProtocol
粗略看了下,這個wire協議可強大了,幾乎可以操作自然人對瀏覽器能做的任何事情,比如打開,關閉,點擊,關閉,定位,上傳文件,最大最小化等等。
它是一套基于RESTful風格的web service.
調試實戰:
比如說頁面上有個輸入框id叫那么叫 policy-name ,然后我們要輸入的值在dataProvider對象中,那么自動化測試代碼是:
sendKeys()方法如下:
當調用sendKeys方法的時候,它會吧我們要輸入的內容值轉為一個字符數組,這個很好理解,因為任何字符串都是一組鍵盤的輸入的集合。所以我們的policyName的值被轉為:
然后,它去在87行調用execute()方法,并且使用了Command設計模式,把我們的調用sendKeys(String) 方法名轉為了一個命令DriverCommand.SEND_KEYS_TO_ELEMENT(也就是字符串 "sendKeysToElement"),然后把我們要發送的內容keysToSend變量通過ImmutableMap進行打包, 這樣做的目的是為了讓我們的輸入的內容不可改變。
因為在我們例子中,我們使用的是Linux操作系統上的Firefox所以,它會調用FirefoxWebElement上的execute()方法,并且我們的輸入內容中被ImmutableMap包裝后加上了WebElement的id。
然后它接著會調用父類的execute()方法來完成這個操作:
這是最重要的方法,我們仔細分析:
從宏觀上看,首先,在第436行會吧當前WebDriver到它啟動的瀏覽器的所創建的session對應的sessionId,以及命令字符串(也就是上文中傳遞過來的DriverCommand.SEND_KEYS_TO_ELEMENT字符串常量),還有發送的字符串內容的包裝體,都封裝在一個Command對象中,封裝后這個Command對象如下:
值得一提的是:這個sessionId是WebDriver每次啟動瀏覽器時候分配的唯一的會話id,從而保證多線程并行運行時候不會出現問題,而總是吧請求發送到正確的瀏覽器所包含的webservice中。(關于這一點,我們在精華分析1中會講到)
然后,它會在446行用CommandExecutor的execute()方法來執行Command從而吧命令發送到sessionId指定的瀏覽器內含的web service服務中,最終它使用HttpCommandExecutor來完成這個任務
(執行命令的細節,是我們所探索的最主要目的,它反映了基于wire協議的web service調用,這點我們在精華分析2中講到)
最后,在第455-456行對于執行結果的返回進行一些后處理,于是你就可以在頁面上看到自動化測試的動畫了。
精華分析1:sessionId是如何產生的?
因為我們使用的是Firefox瀏覽器做的測試(其他瀏覽器也一樣),當WebDriver啟動瀏覽器的時候,它會調用webDriver = new FirefoxDriver(firefoxBinary,firefoxProfile)方法,
因為FirefoxDriver繼承自RemoteWebDriver,所以調用FirefoxDriver構造器時候會調用RemoteWebDriver的構造器,其最后一行會調用startSession()方法如下:
而startSession會在開始就用Command模式,調用execute()方法創建一個新的session:
而這個execute方法,最終被HttpCommandExecutor來執行,和前面敘述一樣,它會發送一個Http請求,并且所有請求細節都在Command對象中。可以從下面調試信息看到,Command是如下的信息:
它的命令name是newSession,而sessionId為空,因為還沒有創建嘛。
然后info對象中包含了要發送的請求url,這里可以看出,它發送到的請求url是/session
最后從httpMethod對象中,可以看出httpMethod用的是HttpPost
所以聯系以上的信息就知道,在RemoteWebDriver中,其實它是以HttpPost方法發送了一個請求對象到/session中,并且請求對象中包含了命令"newSession"還有一些desiredCapabilities信息。
我們對比wire 協議:
正如協議中描述的,這個請求是用來創建一個新的session的,我們檢查參數,請求類型,請求payload完全一致。
所以最后會發送此請求,發送完的response中會包含新創建的sessionId.
然后這個sessionId就可以作為每次發送請求到的目標瀏覽器的標識,從而保證每次請求的都是正確的瀏覽器了,當然,這個sessionId就必須被包含在每次請求中。
精華分析2:HttpCommandExecutor執行命令的細節.
我們看下HttpCommandExecutor.execute()方法:
首先,它會在第279行吧command的名字(命令名,也就是我們的DriverCommand.SEND_KEYS_TO_ELEMENT)轉為一個url形式的命令。回想,我們用的是REST ,所以命令也要用路徑表達式的方式表現出來,轉換后,CommandInfo如下:
所以sendKeysToElement 命令被轉為POST /session/:sessionId/element/:id/value的url形式。
我們對比Wire協議的說明:
所以,這里我們轉換對了,的確我們sendKeysToELement的最終目的是發送一組鍵盤敲擊動作序列到指定元素。
然后,它在第281行從剛才的CommandInfo對象中分離出Http動作:
并且這個getMethod方法內部還會吧我們的url中由名字參數(:sessionId),(:id)表示的url全部替換為真實值,并且前面拼接上由remoteServer實例變量指定的服務器請求url
因為從調試信息上看,info中的動詞(verb)的名字叫”POST”,所以它最終會被轉為httpMethod為HttpPost。而這個uri被變量具體化后被轉為:
這里可以看出(:sessionId),(:id)都被替換了,其中sessionId來自于瀏覽器的sessionId,具體可參見精華分析1.
然后在283行吧HttpPost設置為Http Accept頭。
接著根據不同的HttpMethod進行不同的處理,因為我們的請求是httpPost 請求,所以它會在第286行利用BeanToJsonConverter()吧我們封裝在Command對象中的內容轉成json格式的payload ,并且接下來設置payload的編碼格式以及Content-Type內容。
轉換之后的json變為:
最后,在297行通過調用fallbackExecute來發送瀏覽器中,從這里可以看出,這個的確是一個RESTful的Web Service調用。
當處理完之后,其結果封裝在HttpResponse對象中,我們要對它進行后處理,從調試信息看,這個Response是一個標準的HttpResonse
我們發現了一個很有趣的東西,這里發現這個server是httpd.js,這就說明,其實真正消費我們Http請求的是瀏覽器內置的一段httpd.js的腳本,這也和我們理論模型(瀏覽器包含了一段js來專門處理基于wire協議的請求)完全符合,可以猜想這段js就是模擬輸入值到輸入框中的動畫。
我在selenium官網找到了這個js文件,其內容在:http://code.google.com/p/selenium/source/browse/firefox/src/extension/components/httpd.js?spec=svn004f447f8b359859da694f79569d7e5b03470dd7&r=004f447f8b359859da694f79569d7e5b03470dd7
當我們拿到Response對象后,我們要進行后處理,我們后處理不感興趣,就不分析了。
總結:
從這里我們可以獲取許多有用的信息
(1)從架構的角度來看,當我們用WebDriver API 調用來書寫自動化測試的代碼時候,最終這些方法調用都會被selenium框架內部轉為一個基于wire協議的web service調用。采用的設計模式是Command模式,這個web service的服務端是在任何瀏覽器中都包含的,并且用于服務的其實是httpd.js這段代碼。
(2)wire協議幾乎可以模擬自然人對瀏覽器能做的任何事情,比如打開,關閉,點擊,關閉,定位,上傳文件,最大最小化等等。它是一套基于RESTful風格的web service.
(3)每次web service調用的時候,都必須有一個sessionId作為請求url一部分,這個sessionId用于唯一標識請求要送到的瀏覽器,并且是唯一的uuid,從而保證在多線程環境中工作的正確性。它的產生在于初始化瀏覽器的WebDriver時候,會發送一個Command為newSession的web service到瀏覽器中,這個請求路徑是/session,并且payload中包含了目標瀏覽器的desireCapability信息,這樣,這個web service的調用就會返回一個sessionId,然后包含在后續的所有操作中。
(4)在具體執行某個API 調用,比如sendKeys,它會轉為web service的調用,調用的url會包含sessionId和其他一些相關信息,以名字參數的形式,而調用過程開始時,這些名字參數會被代替為實際參數。然后Command中封裝的所有參數信息會被轉為一個json對象作為web service的payload , 請求類型也會根據你的實際請求動作的需要指定,最后請求調用的過程就是一個web service調用的過程。它的服務端被各種瀏覽器實現,并且包含在一段叫httpd.js的代碼中。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。