「木偶」瀏覽器

以前在團隊內作了一次關於 Headless Browser 的分享,趁着週末,梳理成文字版本,主要內容涉及到 Selenium、PhantomJS、Puppeteer、Headeless Chrome。html

內容有誤的地方,歡迎指正前端

「木偶」瀏覽器

受 Puppeteer 啓發,我在這裏造了一個詞,「木偶」瀏覽器,指經過調用 API 來操控瀏覽器行爲,咱們能夠在此基礎上構建一套自動化系統,以此解放開發人員的雙手,目前主要應用在下面幾個場景中:git

  • 頁面自動化測試,產品上線前,進行一些重要路徑的自動化測試
  • Javascript 庫自動化自測,提供 JS 運行環境
  • 網頁截圖,在一些常見的前端監控系統中,網頁出錯了,經過這個功能進行網頁截圖記錄
  • 爬蟲,面對一些反爬蟲系統,無頭瀏覽器能夠模擬用戶訪問進行爬去數據

實現木偶瀏覽器,目前有兩種方案,一種是 Selenium,一種是以 PhantomJS 爲表明的 Headles Browser(無頭瀏覽器),兩個方案,在使用方式和應用的方向都有所區別,Selenium 多應用在產品自動化測試,而無頭瀏覽器則更多被用於應對反爬蟲系統和網頁截圖。github

兩種木偶

Selenium

Jason Huggins 在 2014 年開發了 Selenium,做爲 ThoughtWorks 的內部工具,以後 Paul Hammant 加入開發團隊,領導開發了新一版操做邏輯,即經典的的 "Selenium Remote Control"(Selenium-RC),並選擇在當年開源。web

Selenium 出現前,市面上流行着 Mercury 公司的自動化功能測試軟件 QTP,Selenium 的命名源於 Huggins 在郵件裏開的一個玩笑,「you can cure mercury poisoning by taking selenium supplements」,你能夠經過服用硒(selenium)補充劑來治療汞(mercury)中毒。chrome

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。npm

還有一個重要的時間點,2018 年 Philippe Hanrigou 開發了 「Selenium Grid」,使 Selenium 支持分佈式執行測試,能夠控制多臺機器多個瀏覽器執行測試用例。編程

以 Selenium 2.0 爲例,完整的組成結構應該是瀏覽器

Selenium = Selenium IDE + Selenium WebDriver + Selenium Remote Control + Selenium Gridless

這裏簡要解釋下這幾個名詞,配合下圖(途中略去 RC 部分)食用效果更佳:

Selenium IDE

Selenium IDE 是 firefox/Chrome 瀏覽器的插件,提供簡單的腳本錄製、編輯與回放功能。

Selenium Client API

Selenese 是 Selenium 的指令集,除了使用 Selenese,Selenium 還開放了編程語言調用接口,經過調用 Selenium Client API 中的方法與 WebDriver 進行通訊,目前支持 JAVAC#JavascriptPython

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 供開發者操控瀏覽器行爲,項目發佈後,一石激起千層浪,到目前爲止,已經被上千家組織或公司使用,並在原有的基礎上衍生出了 CasperJSYslow 兩個項目

這樣的場景直到 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 DriverSafari DriverChrome DriverFirefox Driver,能夠經過下圖加深理解,以 Chrome 爲例,Puppeteer-core 和 ChromeDriver 都是經過 devtools-protocol 控制瀏覽器,區別是 Puppeteer-core 是開發者直接可用的 Node 庫,ChromeDriver 則須要用戶經過 Selenium Client API 進行調用

原文地址:「木偶」瀏覽器-Leeon

參考:

相關文章
相關標籤/搜索