從selenium說起
雖然我們的主題是cdp(chrome debug protocol)的應用晒衩,但在介紹cdp之前嗤瞎,不得不先從selenium說起,因為這兩者有密不可分的關系听系。
我們知道贝奇,在最新的selenium里,當你去執(zhí)行一個測試動作靠胜,例如打開瀏覽器弃秆,然后輸入網(wǎng)址,找到一個搜索框填入文本并點擊搜索髓帽,這背后所依賴的技術菠赚,其實是webdriver,而當你的動作執(zhí)行在chrome瀏覽器上郑藏,更為細化的說衡查,依賴的是chromewebdriver。
我們詳細的來分析這一流程必盖,你會更清楚的知道cdp與此有何關系拌牲。
首先我們來寫一個示例代碼:
from selenium import webdriver
driver = webdriver.Chrome()
driver.get("https://www.baidu.com/")
執(zhí)行這段代碼,會看到系統(tǒng)啟動了chrome瀏覽器歌粥,并跳轉到了百度首頁塌忽。
看driver = webdriver.Chrome()這一句,以下是這段代碼的流程圖解失驶。
可以看到在這個流程中土居,chromedriver起到的是橋梁的作用,他接受客戶端的請求嬉探,然后轉化為瀏覽器的標準指令操作瀏覽器擦耀,而在后半部分,也就是指令如何讓瀏覽器工作中涩堤,就涉及到了我們的主題cdp眷蜓,因為這部分的標準其實就是cdp。
什么是cdp
chrome debug protocol胎围,簡稱cdp吁系。
大家應該都用過chrome瀏覽器的F12,也就是devtools白魂,其實這是一個web應用汽纤,當你使用devtools的時候,瀏覽器本身會作為一個服務端碧聪,而你看到的瀏覽器調試工具界面冒版,其實只是一個前端應用,在這中間通信的逞姿,就是cdp辞嗡,他是基于websocket的捆等,一個讓devtools和瀏覽器內核交換數(shù)據(jù)的通道。
cdp本身是可開放的续室,換句話說栋烤,你用devtools能做什么(例如操作瀏覽器,獲取網(wǎng)絡信息挺狰,獲取js覆蓋數(shù)據(jù)明郭,獲取性能數(shù)據(jù)等等),你就能用cdp做什么丰泊。
cdp的官方文檔地址薯定,可以點擊查閱,這里再簡單的介紹一下瞳购。
cdp把不同的操作劃分為了不同的域(domain)话侄,每個域負責不同的功能模塊,例如学赛,Page域可以獲取當前頁面數(shù)據(jù)年堆,或者操作頁面跳轉等等;Profiler域可以獲取當前的頁面的js覆蓋率數(shù)據(jù)等等盏浇;
直接引用FEX的一篇文章來解釋:
該協(xié)議把操作劃分為不同的域(domain)变丧,比如 DOM、Debugger绢掰、Network痒蓬、Console 和 Timeline 等,可以理解為 DevTools 中的不同功能模塊曼月。
每個域(domain)定義了它所支持的 command 和它所產生的 event谊却。
每個 command 包含 request 和 response 兩部分,request 部分指定所要進行的操作以及操作說要的參數(shù)哑芹,response 部分表明操作狀態(tài),成功或失敗捕透。
command 和 event 中可能涉及到非基本數(shù)據(jù)類型聪姿,在 domain 中被歸為 Type,比如:’frameId’: <FrameId>乙嘀,其中 FrameId 為非基本數(shù)據(jù)類型
至此末购,不難理解:
domain = command + event + type
使用cdp的方式
最原始的使用cdp的方式可以參照google的cdp文檔來:
1.使用附加參數(shù)打開chrome的遠程調試協(xié)議開關(普通模式下的chrome瀏覽器是無法直接使用cdp通信的,另外虎谢,請注意盟榴,在不同的操作系統(tǒng)下指令細節(jié)會有所不同),
chrome.exe --remote-debugging-port = 9222
此時一個打開的遠程調試協(xié)議的瀏覽器實例被啟動婴噩。
2.為做演示擎场,在打開的瀏覽器中羽德,輸入百度的網(wǎng)址并進入,新開一個tab迅办,進入網(wǎng)址http://localhost:9222宅静,此時應該如截圖所示:
3.點擊百度這個標簽,進入他的devtools界面站欺,看一下地址欄姨夹,記錄page/后面的通信標識值,然后在console里輸入以下代碼:
var ws = new WebSocket('ws://localhost:9222/devtools/page/這里填剛才記錄的標識值');
ws.send('{"id": 1, "method": "Page.navigate", "params": {"url": "http://www.soso.com"}}')
執(zhí)行完會發(fā)現(xiàn)矾策,剛才的百度頁面磷账,跳轉到了soso的頁面,其實這段代碼就是新開了一個websocket連接到剛才的百度頁面的調試地址贾虽,然后通過page域的navigate方法讓該頁面重新跳轉到了指定地址逃糟。
需要注意的一點是,在這里榄鉴,每個tab(頁面)都只有一個單獨的通信地址履磨,且每個地址只能與對應的tab通信。
以上就是比較原始的使用方法庆尘,實際上剃诅,cdp有很多封裝好的庫可以使用,例如python的PyChromeDevTools庫驶忌,nodejs的chrome-remote-interface庫等等矛辕,更多上層封裝庫請參見官方文檔。
cdp在自動化中的應用和實踐
看了以上內容付魔,可能你會得出一個結論聊品,selenium依賴webdriver,而在chrome瀏覽器中几苍,webdriver又依賴chromedriver翻屈,chromedriver又是依賴cdp的;那么妻坝,我使用selenium和我直接使用cdp伸眶,有什么區(qū)別呢?
實際上真要較真(不怕麻煩)的話刽宪,是沒有區(qū)別的厘贼,但二者還是有一些差異的,selenium的封裝更為上層圣拄,使得你不用去關心原始的cdp到底如何使用嘴秸,而且也集成了聚焦測試所需要的一些功能,例如分布式執(zhí)行,docker image等等岳掐,使得在測試這個需求上凭疮,更為方便;而直接使用cdp的話岩四,會讓整個結構更為簡潔哭尝,而且,有些操作由于webdriver沒有封裝(例如獲取性能數(shù)據(jù)剖煌,獲取js覆蓋率等等)材鹦,所以直接使用cdp會更為精準。那么有沒有辦法讓二者的優(yōu)點結合呢耕姊?
在這里我發(fā)現(xiàn)了兩種方案可以做到桶唐,
1.通過命令行啟動開啟了調試協(xié)議的chrome瀏覽器,然后在selenium里茉兰,初始化webdriver時指定ChromeOption的__debugger_address的值為之前的遠程調試地址尤泽,然后使用selenium操作webdriver,使用PyChromeDevTools操作cdp规脸,示例代碼如下:
import os
import PyChromeDevTools
from selenium import webdriver
cmd = "chrome.exe --remote-debugging-port=9222"
os.popen(cmd) #此時chrome瀏覽器打開
time.sleep(3)
chrome = PyChromeDevTools.ChromeInterface()#使用chrome操作cdp
options = webdriver.ChromeOptions()
options._debugger_address = "localhost:9222"
driver = webdriver.Chrome(chrome_options=self.options)
2.可以直接使用selenium的預留cdp通信方法execute_cdp_cmd坯约,示例代碼如下:
from selenium import webdriver
driver = webdriver.Chrome()
driver.get("https://www.baidu.com/")
driver.execute_cdp_cmd('Page.navigate',{"url": "http://www.soso.com"})
利用cdp獲取頁面網(wǎng)絡數(shù)據(jù)
有時候當腳本出錯了,我們會希望獲得更多的信息去排查莫鸭,如果這時候能重現(xiàn)當時的網(wǎng)絡請求闹丐,那么排查會容易的多,下面是一個獲取頁面網(wǎng)絡數(shù)據(jù)(response值)的例子被因,這里只拿了請求的response值卿拴,但實際上稍加改動就可以把請求信息拿全(request+response),為了方便演示上面兩種方法梨与,這里混用了上面的兩個方案堕花。
from selenium import webdriver
import time
import os
import PyChromeDevTools
os.chdir(r"C:\Users\zyj\AppData\Local\Google\Chrome SxS\Application") #這里是改變了當前環(huán)境變量
cmd = "chrome.exe --remote-debugging-port=9222"
os.popen(cmd)#啟動chrome瀏覽器
time.sleep(3)
chrome = PyChromeDevTools.ChromeInterface()
options = webdriver.ChromeOptions()
options._debugger_address = "localhost:9222"
driver = webdriver.Chrome(chrome_options=options)
chrome.Network.enable()#開啟頁面的網(wǎng)絡信息收集模式
time.sleep(2)
driver.execute_cdp_cmd('Page.navigate',{"url": "http://www.mycaigou.com"})#跳轉到mycaigou,這里用的selenium的execute_cdp_cmd方法做到的
responseReceived = chrome.wait_event("Network.responseReceived", timeout=60)#等待response收集事件結束粥鞋,獲取收集信息缘挽,這里的信息不包含詳細的response內容,需要用到方法getResponseBody
resquest_id = responseReceived[0]['params']['requestId']#這個id是指你想要收集哪個請求的信息呻粹,他是請求的唯一標示到踏,這里隨便拿了一個,沒做遍歷
res = chrome.Network.getResponseBody(requestId=resquest_id)#傳入id尚猿,拿到請求的返回值
print(res)
利用cdp獲取頁面加載時間
這是PyChromeDevTools的官方例子,演示如何獲取頁面加載時間:
import PyChromeDevTools
import time
import os
os.chdir(r"C:\Users\zyj\AppData\Local\Google\Chrome SxS\Application") #這里是改變了當前環(huán)境變量
cmd = "chrome.exe --remote-debugging-port=9222"
os.popen(cmd)#啟動chrome瀏覽器
chrome = PyChromeDevTools.ChromeInterface()
chrome.Network.enable()
chrome.Page.enable()
start_time=time.time()
chrome.Page.navigate(url="http://www.baidu.com/")
chrome.wait_event("Page.loadEventFired", timeout=60)#loadEventFired是頁面全部加載完畢的時間楣富,實際上這里還可以用reload方法凿掂,選擇去除緩存加載,這樣的時間會更加精確
end_time=time.time()
print ("Page Loading Time:", end_time-start_time)
利用cdp拿到自動化測試后的js覆蓋率數(shù)據(jù)并展示
在cdp中,是無法直接得到覆蓋率的數(shù)據(jù)的庄萎,有關js代碼執(zhí)行情況的統(tǒng)計踪少,在Profiler域,我們可以使用takePreciseCoverage方法來拿到js執(zhí)行數(shù)據(jù)糠涛,這個數(shù)據(jù)的數(shù)據(jù)結構是這樣的:
'result': {
'result': [{
'scriptId': '17',
'url':'https://www.xxxxxxxxx.com/browser/guide.js',
'functions': [{
'functionName': 'get',
'ranges': [{
'startOffset': 0,
'endOffset': 4273,
'count': 1
}],
'isBlockCoverage': False
},
}],
}],
}
......
一個result包含多個js的統(tǒng)計情況援奢,每個url基本就是js的請求地址;在每個js的統(tǒng)計情況里忍捡,又有多個function的統(tǒng)計情況集漾,每個function里的startOffset和endOffset指的是這個方法的被統(tǒng)計語句按字節(jié)位置來算的開始位置和結束位置,count代表這段語句是否被執(zhí)行到砸脊,1代表是具篇,0代表否。
因此凌埂,思路就是驱显,拿到測試完成后的js統(tǒng)計數(shù)據(jù),然后通過每個js統(tǒng)計數(shù)據(jù)里的每個function的統(tǒng)計坐標值和統(tǒng)計狀態(tài)瞳抓,和原始js數(shù)據(jù)比對埃疫,從而實現(xiàn)對js覆蓋狀況的總覽。
這個實現(xiàn)比較復雜孩哑,我直接做成了一個模塊栓霜,只需要接受takePreciseCoverage的數(shù)據(jù),就可以計算出覆蓋情況并直觀的展示臭笆,具體的代碼在github上叙淌,這里就不放出了。
最終的效果圖演示: