之前在團隊內做了一次關於 Headless Browser 的分享,趁著週末,梳理成文字版本,主要內容涉及到 Selenium、PhantomJS、Puppeteer、Headeless Chrome。
內容有誤的地方,歡迎指正
“木偶”瀏覽器
受 Puppeteer 啟發,我在這裡造了一個詞,“木偶”瀏覽器,指通過呼叫 API 來操控瀏覽器行為,我們可以在此基礎上構建一套自動化系統,以此解放開發人員的雙手,目前主要應用在下面幾個場景中:
- 頁面自動化測試,產品上線前,進行一些重要路徑的自動化測試
- Javascript 庫自動化自測,提供 JS 執行環境
- 網頁截圖,在一些常見的前端監控系統中,網頁出錯了,通過這個功能進行網頁截圖記錄
- 爬蟲,面對一些反爬蟲系統,無頭瀏覽器可以模擬使用者訪問進行爬去資料
實現木偶瀏覽器,目前有兩種方案,一種是 Selenium,一種是以 PhantomJS 為代表的 Headles Browser(無頭瀏覽器),兩個方案,在使用方式和應用的方向都有所區別,Selenium 多應用在產品自動化測試,而無頭瀏覽器則更多被用於應對反爬蟲系統和網頁截圖。
兩種木偶
Selenium
Jason Huggins 在 2014 年開發了 Selenium,作為 ThoughtWorks 的內部工具,之後 Paul Hammant 加入開發團隊,領導開發了新一版操作邏輯,即經典的的 “Selenium Remote Control”(Selenium-RC),並選擇在當年開源。
Selenium 出現前,市面上流行著 Mercury 公司的自動化功能測試軟體 QTP,Selenium 的命名源於 Huggins 在郵件裡開的一個玩笑,“you can cure mercury poisoning by taking selenium supplements”,你可以通過服用硒(selenium)補充劑來治療汞(mercury)中毒。
2007 年 Huggins 加入 Google,繼續 Selenium-RC 的開發維護,與此同時,還在 ThoughtWorks 的 Simon Stewart 開發出了大名鼎鼎的 WebDriver,接著就到了 2009 Selenium 與 WebDriver 的合併,開啟 Selenium 2.0 時代,合併之後,Selenium-RC 和 WebDriver 共存,直到 2016 年,跳票 3 年之久的 Selenium 3.0 釋出,Selenium-RC 被拋棄,專案完全投向 WebDriver API。
還有一個重要的時間點,2018 年 Philippe Hanrigou 開發了 “Selenium Grid”,使 Selenium 支援分散式執行測試,可以控制多臺機器多個瀏覽器執行測試用例。
以 Selenium 2.0 為例,完整的組成結構應該是
Selenium = Selenium IDE + Selenium WebDriver + Selenium Remote Control + Selenium Grid
這裡簡要解釋下這幾個名詞,配合下圖(途中略去 RC 部分)食用效果更佳:
Selenium IDE
Selenium IDE 是 firefox/Chrome 瀏覽器的外掛,提供簡單的指令碼錄製、編輯與回放功能。
Selenium Client API
Selenese 是 Selenium 的指令集,除了使用 Selenese,Selenium 還開放了程式語言呼叫介面,通過呼叫 Selenium Client API 中的方法與 WebDriver 進行通訊,目前支援 JAVA
、C#
、Javascript
、Python
。
2.0 版本之後,引入了新的 Client API(以 WebDriver 為中心元件),不過仍向下相容。
Selenium Grid
Grid 上文已經介紹了,用於對測試指令碼進行分散式處理,目前已經整合到 Selenium Server 中。
WebDriver & Selenium-RC
RC 通過 Javascript 驅動網頁,這使得整個過程與網頁的內容高度耦合,得益於此,Selenium 也是第一批支援 Ajax 和一些高動態網頁的自動化測試工具之一,同時,一個繞不過的問題,自動化程式碼執行在 Javascript 沙箱內,這就要求 Selenium-RC 服務必須跟對應網頁保持同源。
而 WebDriver 則是通過瀏覽器原生介面協議驅動瀏覽器,並且開放了不同語言對應的 API,雖然瀏覽器、不同程式語言的適配會耗費很長的人力、時間成本,但帶來的是 RC 無可替代的使用體驗。
WebDriver 和 Selenium 合併,WebDriver 解決了 RC 繞不過 JS 沙箱問題,帶來了更友好的 API,同時,WebDriver 也得支援更多的瀏覽器。
Headles Browser
關於無頭瀏覽器,這裡分別介紹下 PhantomJS 和 Headless Chrome。
PhantomJS
2011 年 1 月 23 號,Ariya Hidayat 釋出了 PhantomJS,這是真正意義上第一個無頭瀏覽器。
PhantomJS 基於 webkit 核心打造,並且提供一系列的 Javascript API 供開發者操控瀏覽器行為,專案釋出後,一石激起千層浪,到目前為止,已經被上千家組織或公司使用,並在原有的基礎上衍生出了 CasperJS 和 Yslow 兩個專案
這樣的場景直到 2017 年被打破,Chrome 59 宣佈支援在 headless 環境下執行 Chrome,同時因為長期缺乏程式碼提交,2018 年 5 月 4 號,Ariya 宣佈了暫停專案的開發維護,版本號最終停在 2.1.1
。
Headless Chrome
上面也提到,2017 年 Chrome 開始支援 headless 環境,緊接著,用於控制 Chrome 的 Node 庫開源專案 Chromeless 出現,不過隨著 Chrome 團隊釋出了官方庫 Puppeteer,Chromeless 作者宣佈專案停止開發,並建議使用者遷移 Puppeteer。
讀到這裡,關於這兩種無頭瀏覽器的關係,你可能有些疑惑,主要的區別是 PhantomJS 整合了 瀏覽器(webkit)和 API,而 Google 的做法則是將瀏覽器(Chrome)和 Node API(pupteer-core) 獨立,可以用下面這個等式表示:
Chrome + Puppeteer-core/Chromeless = PhantomJS
Puppeteer、Puppeteer-core、Chrome 和 PhantomJS 的關係如下:
Puppeteer = Puppeteer-core + Chromium = PhantomJS
木偶提線
PhantomJS
舉個例子,下面是使用 PhantomJS 訪問掘金首頁並截圖的程式碼:
// 為了方便演示,我們引入 npm phantom 包
const phantom = require(`phantom`)
const log = console.log
const imgPath = `./capture.jpg`
const targetUrl = `https://juejin.im/`
const picSet = {
format: `jpg`,
qualiity: `80`
}
const getTime = () => {
return (new Date()).getTime()
}
const genPic = async () => {
let start = getTime()
const instance = await phantom.create()
const page = await instance.createPage()
await page.on(`onResourceRequested`, function(requestData) {
log(`Requesting =>> `, requestData.url)
})
const status = await page.open(targetUrl)
log(`page download: `, (getTime() - start))
page.property(`viewportSize`, {width: 375*2, height: 627*2})
page.property(`zoomFactor`, 2)
log(status)
// const content = await page.property(`content`)
// console.log(content)
await page.render(imgPath, picSet)
log(`pic rendered: `, (getTime() - start))
await instance.exit()
}
genPic()
複製程式碼
Puppeteer
Puppeteer 基於 DevTools Protocol 控制 Chrome 或 Chromium,安裝時,預設下載最新版本的 Chromium,不過從 1.7
版本開始,官方提供了 puppeteer-core
,不會下載 Chromium 包的版本。
網頁截圖 Puppeteer 版本:
const puppeteer = require(`puppeteer`)
const shotSet = {
path: `./screenshot.png`,
fullPage: true
}
const viewPortSet = {
width: 375,
height: 627,
isMobile: true,
deviceScaleFactor: 1
}
const genPic = async () => {
const browser = await puppeteer.launch()
const page = await browser.newPage()
await page.setViewport(viewPortSet)
await page.goto(`https://juejin.im/`)
console.log(await page.content())
await page.screenshot(shotSet)
await browser.close()
}
genPic()
複製程式碼
Puppeteer 操控 GUI Chrome 訪問 juejin.im
:
const search = async () => {
const browser = await puppeteer.launch({
headless: false,
timeout: 30000
})
const page = await browser.newPage()
// viewPortSet 參見上一段截圖程式碼
await page.setViewport(viewPortSet)
await page.goto(`https://juejin.im/`)
let menu = `.main-nav-list`
await page.click(menu)
let pin = `.pin`
await page.waitForSelector(pin)
await page.click(pin)
let login = `.nav-item.auth`
await page.waitForSelector(login)
await page.click(login)
await page.type(`[name="loginPhoneOrEmail"]`, `xxxxxxxxxx@xx.com`)
await page.type(`[name="loginPassword"]`, `xxxxxxxxxx`)
let loginBtn = `.panel .btn`
await page.waitForSelector(loginBtn)
await page.click(loginBtn)
await browser.close()
}
search()
複製程式碼
因為指令碼控制,速度很快,我在關鍵操作後增加了延時,錄了個 gif,效果如下,有興趣的同學可以直接拷貝程式碼,安裝依賴之後即可執行:
Selenium WebDriver VS Puppeteer
上文已經對 WebDriver、Puppeteer 的概念做了闡述,那麼兩者的區別在哪?
實際上,WebDriver 是 Selenium 根據不同的瀏覽器(Chrome、Safari、IE、Firefox)的介面定製的規範統稱,面對不同瀏覽器,使用的 Driver 不同,官網目前提供了 IE Driver
、Safari Driver
、Chrome Driver
、Firefox Driver
,可以通過下圖加深理解,以 Chrome 為例,Puppeteer-core 和 ChromeDriver 都是通過 devtools-protocol 控制瀏覽器,區別是 Puppeteer-core 是開發者直接可用的 Node 庫,ChromeDriver 則需要使用者通過 Selenium Client API 進行呼叫
原文地址:“木偶”瀏覽器-Leeon
參考: