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