Selenium 工做原理

Selenium 的發展經歷了三個階段:Selenium Core、Selenium RC 和 Selenium WebDriver。本文將依次介紹每一個階段的工做原理,若有錯誤,請及時指正。html

提示:Selenium Core 用戶不直接接觸,而 Selenium RC 已通過時,不感興趣的同窗能夠直接看第三節 Selenium WebDriver。

術語列表:java

術語 全稱 中文全稱/簡介
AUT Application Under Test 被測應用
Selenium Selenium 一款跨瀏覽器自動化工具
Selenium Core Selenium Core Selenium 第一代版本,簡稱 Selenium 1.0,於 2004 年發佈
Selenium RC Selenium Remote Control 基於 Selenium Core 的改進版,屬於 Selenium 1.0
WebDriver WebDriver 和 Selenium Core 相似的跨瀏覽器自動化工具,於 2007 年首次發佈源碼
w3c WebDriver w3c WebDriver 由 WebDriver 發展而來的瀏覽器自動化協議,有時簡稱爲 WebDriver 協議/規範/API
Selenium WebDriver Selenium WebDriver 2009年8月由 Selenium 1.0 和 WebDriver 項目合併而成,遵循 w3c WebDriver 協議,早期又稱做 Selenium 2.0
Driver Driver/Browser Driver 瀏覽器驅動,實現了 w3c WebDriver 接口,每一個瀏覽器都有本身的驅動程序
DriverService DriverService 驅動服務,運行驅動程序後所起的 HTTP 服務,接收 WebDriver 接口調用後操做瀏覽器

Selenium Core

在 2004 年 Internet Explorer 有 93.25% 的市場佔有率,當時的開源測試工具要麼關注單個瀏覽器的測試(如 IE),要麼是瀏覽器模擬工具(如 HttpUnit )。沒有開源自動化測試工具能支持多瀏覽器的測試,手工測試執行須要耗費大量的時間和精力。nginx

提示:HttpUnit 是瀏覽器模擬工具,測試腳本與 HttpUnit 交互,HttpUnit 和真實服務端交互,不通過真實的瀏覽器,即不是模擬用戶操做真實的瀏覽器。

初步想法

不過全部瀏覽器都支持 JavaScript,這爲開發支持多瀏覽器的自動化工具提供了可能。受 Fit: Framework for Integrated Test 啓發,ThoughtWorks 的 Jason Huggins 和他的團隊想到了一個方案,使用基於表格的關鍵字驅動語法來編寫測試用例。相比原生的 JavaScript 腳本,用戶只須要有限的編程能力,另外用例也更容易理解和方便維護。git

基於表格的關鍵字語法(一個典型的登陸操做):github

Command Target Value
open http://example.com/page1.html
type username testUser
type password testPasword
clickAndWait submitButton
verifyTextPresent Welcome, testUser!
  • 第一列:命令的名稱,如 open、type、click、submit 等等。
  • 第二列:目標,如元素定位符、URL 等。
  • 第三列:可選的值(如 click 命令不須要值,type 命令可能須要輸入一些值)。

如何實現

開發團隊裏有幾位成員對 Closure Library 很是熟悉,並且 Closure Library 編譯的惟一輸出語言是 JavaScript 。因此使用 Closure Library 來開發 Selenium Core 是個合適的選擇。web

與不少大型項目同樣,Selenium Core 採用了分層構建的架構。
最底層是 Google 開源的 Closure Library,它是一個模塊化的、跨瀏覽器的底層 JavaScript 庫,模塊化使每一個源文件能夠聚焦某個功能而且儘量小,跨瀏覽器能夠幫其屏蔽掉不少瀏覽器兼容性問題。
中間層是一個包含大量函數的工具層,提供了從簡單到複雜的操做,如:獲取元素屬性值、判斷一個元素對用戶是否可見、使用合成事件模擬用戶點擊。該層能夠被看做是瀏覽器自動化的最小操做單元,所以也被稱爲瀏覽器自動化原子 Atomsatoms
最上層是由 Atoms 組成的適配層,用於實現 Selenium Core 約定的 API,即前面表格裏的命令。chrome

至此,只要將表格內容解析爲 Selenium Core 的 API,一個完整的跨瀏覽器自動化工具就實現了。編程

用於實戰

Selenium Core 使用純 JavaScript 編寫,而 JavaScript 存在同源策略的問題,所以使用時須要將 Core 和測試用例部署在與被測應用相同的服務器上(只要被測應用和測試腳本同源就能夠)。這也意味着,你沒法測試別人的網站,好比 https://www.baidu.comsegmentfault

同源策略:跨域

Selenium Core 的使用步驟:

  1. 下載 Selenium Core 的壓縮文件並解壓,如:selenium-core-1.0.1.zip
  2. 複製 core 文件夾到應用服務器的目錄。
  3. 訪問 http://<webservername>:<port>/[path/]core/TestRunner.html 頁面運行測試。被測應用有兩種運行方式,單窗口模式:在 TestRunner 頁面下方使用 iframe 嵌入被測應用,多窗口模式:在新窗口中打開被測應用。訪問 https://www.qadoc.org/edu/cat... 查看示例。
擴展:使用 HTA 模式(將 TestRunner.html 重命名爲 TestRunner.hta )能夠測試其餘網站,但 HTA 僅支持 Windows 上的 IE 瀏覽器。

最後總結一下:

  1. 在與 core 文件夾同級的 tests 文件夾下,使用 HTML 編寫基於表格的測試用例頁面,並加入測試套件頁面。
  2. 部署 coretests 文件夾到應用服務器的目錄,避開同源策略限制,也所以沒法測試其餘非同源網站,包括頁面跳轉後的非同源網站。
  3. 訪問 http://<webservername>:<port>/[path/]core/TestRunner.html 頁面準備運行測試。
  4. 選擇一個測試套件或測試用例,點擊 Run All testsRun the Selected test 運行測試用例。
  5. Selenium Core 獲取測試套件的全部測試用例或當前測試用例,解析表格的每一行(固定三列),按行依次調用 Selenium Core API 執行。
  6. 每一個 API 調用 Atoms 層函數,Atoms 層調用 Closure Library 函數,由於 Closure LibraryJavaScript 都是跨瀏覽器的,因此 Selenium Core 支持多個瀏覽器。
擴展:雖然 Selenium Core 自己受限於同源策略,沒法測試其餘非同源網站,但幸運的是,瀏覽器插件的 JavaScript 代碼並無這個限制,因此基於 Selenium Core 的 Selenium IDE 支持任何網站的測試。

Selenium RC

上節咱們瞭解到,Selenium Core 雖然知足了跨瀏覽器自動化的基本需求,但仍然有一些不足:

  • 使用 HTML 編寫測試用例,因爲沒有專門針對 HTML 的用例編寫工具,當用例規模較大時,編寫和維護都是一件頭疼的事。
  • 測試工具和測試用例須要部署在應用服務器上,這對被測應用有必定的侵入性,另外不是全部狀況下都能在應用服務器上部署測試代碼。
  • 沒法測試與被測應用非同源的網站,這個缺點在今天,顯得更爲明顯,現今很多網站都流行使用子域名,這致使頁面跳轉後將沒法繼續執行測試。

淘寶部分子域名示例:

爲了解決這些問題,Selenium RC 隨之誕生。

如何避開同源策略

爲了解決上面的問題,開發團隊爲 Selenium 寫了一個 HTTP 代理服務器,這樣 Selenium 能夠攔截一切 HTTP 請求。

HTTP 代理: HTTP 代理原理及實現(一)

關於 Selenium RC 架構,官網有詳細的描述,這裏偷下懶,作下翻譯和補充。

代理注入

下載解壓後,啓動 Selenium RC Server:

java -jar selenium-server.jar -interactive -browserSideLog -proxyInjectionMode -debug -log seleniumrc.log

當咱們執行測試用例(使用編程語言編寫的測試腳本)時,下面的步驟將發生:

  1. 客戶端驅動(即 Selenium 的某個編程語言的客戶端庫)與 Selenium RC 服務器創建鏈接。
  2. Selenium RC 服務器使用特定的 URL 啓動瀏覽器(或重複使用以前的瀏覽器實例),該頁面引入了 Selenium-Core 的全部文件(JS、CSS 文件)。
  3. 客戶端驅動經過 HTTP 發送一條 Selenium 命令給 RC 服務器。
  4. 服務器解釋命令,並觸發相應的 JavaScript 在瀏覽器中執行命令。第一條命令,一般是 open AUT 的一個頁面。
  5. 瀏覽器收到 open 請求後,向 RC 服務器(做爲瀏覽器的代理)請求頁面內容。
  6. RC 服務器向 Web 服務器(AUT 的服務器)請求頁面,請求完成後,RC 服務器將 Selenium Core 注入該頁面併發送給瀏覽器(這讓 Selenium Core 看起來和該頁面同源)。
  7. 瀏覽器接收該頁面並在 frame 或 window 中渲染頁面。

補充幾點官網沒有解釋的內容:

Selenium RC 如何啓動瀏覽器

使用命令行方式啓動瀏覽器,啓動命令的格式以下:

# 格式:瀏覽器程序文件路徑 + 命令參數

# 示例
"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe"
--disable-hang-monitor
--disable-metrics
--disable-popup-blocking
--disable-prompt-on-repost
--proxy-server="localhost:4444"
--start-maximized
--user-data-dir="C:\Users\Think\AppData\Local\Temp\customProfileDir7a0b6e794a9445338159f582e1b8e3aa"
http://oktools.xyz/selenium-server/core/RemoteRunner.html?sessionId=7a0b6e794a9445338159f582e1b8e3aa&multiWindow=false&baseUrl=http%3A%2F%2Foktools.xyz%2F&debugMode=true

更多細節能夠翻閱 org.openqa.selenium.server.browserlaunchers.AbstractBrowserLauncher#launch 實現類對應的方法。

Selenium RC 如何和瀏覽器通訊

這個問題乍看起來好像不是問題,但仔細想,瀏覽器並非服務端,即沒有處理請求的能力,因此 RC 沒法通知瀏覽器。有人可能會想到使用 websocket,但 websocket 2011 年才成爲瀏覽器標準,而在 2004 年的時候,Selenium RC 顯然還沒法使用它。

當時有一種叫 Comet 的技術,Selenium RC 最終選用的是 XMLHttpRequest 長輪詢,用於保持和瀏覽器的鏈接。下圖是一個簡單的示例,客戶端(圖示中使用的是 Selenium Sever 的交互模式,功能和客戶端相同)前後發起了兩個命令:getNewBrowserSessionopen

服務器收到 getNewBrowserSession 命令後,啓動瀏覽器,瀏覽器訪問 RemoteRunner.html,該頁面經過 script 等標籤引入了 Selenium Core。

  1. 當瀏覽器加載 RemoteRunner.html 頁面時觸發 onload 事件, Selenium Core 發起一個 seleniumStart 的 HTTP 請求,服務器返回一個 getTitle 命令。
  2. Selenium Core 收到響應後,執行響應中的命令,繼續發出請求(攜帶剛纔的命令執行結果),服務器返回 setContext 命令,通知 Selenium Core 設置上下文。
  3. Selenium Core 收到響應後,執行響應中的命令,發出重試請求。服務器若是 10001ms 內沒有收到客戶端的命令,便結束這次請求,返回 retryLast 命令,即繼續重試。
  4. Selenium Core 收到響應後,重複第三步,直到客戶端給服務器發送新命令。

整個 XMLHttpRequest 長輪詢的過程至關因而一個 Response/Request 模式,服務器將要通知瀏覽器的內容放入 Response Body 中,瀏覽器將本次 Response 的處理結果放入下次的 Request URL 和 Body中,所以對於服務器來講,每次 HTTP 響應至關於服務器的請求,每次 HTTP 請求至關於服務器的響應。

因爲文章篇幅有限,open 命令的執行過程就不介紹了。上圖的 uniqueId 是什麼用途呢?每當頁面跳轉或刷新後,意味着上個頁面會關閉,新頁面須要繼續保持和服務器的鏈接,這裏的 uniqueId 能夠理解成頁面的標識,每當上個頁面關閉後,下個頁面就會使用新的 uniqueId 發起請求,同時 sequenceNumber 被重置爲 0。

提高瀏覽器權限

該模式和代理注入很是相似,主要區別在於瀏覽器以一個被稱爲 Heightened Privileges 的特殊模式啓動,它容許網站作一些一般不被容許的事(好比 XSS、填充文件上傳輸入框以及對 Selenium 頗有用的東西)。該模式下,Selenium Core 能夠直接打開 AUT 並讀取它的內容或與它的內容交互,而沒必要經過 Selenium RC 服務器訪問整個 AUT。

Selenium WebDriver

早期的 WebDriver

早期的 WebDriver 是和 Selenium Core 幾乎同時期的來自 ThoughtWorks 的瀏覽器自動化工具,也是基於 Atoms 構建,這和後來咱們所熟悉的 w3c WebDriver 協議有些不一樣。

Selenium CoreWebDriver 的設計理念有很大不一樣。WebDriver 的開發人員傾向於向用戶隱藏其並不關心的不少細節,提供儘量簡單的 API,好讓用戶聚焦在用例設計和發現 Bug 上,正如他們擅長的那樣,而 Selenium Core 則是給用戶提供低級別的 API 。如下是 Selenium Core 用於設置 input 元素值的方法:

  • type
  • typeKeys
  • keydown
  • keypress
  • keyup
  • keydownNative
  • keypressNative
  • keyupNative
  • attachFile

它們等價於 WebDriversendKeys API,正如前面說的同樣,WebDriver 努力模仿訪問被測應用的用戶。xxx 和 xxxNative 的區別在於,前者使用合成事件,後者經過 java.awt.Robot 模擬鍵盤輸入來模擬用戶操做。

w3c WebDriver 協議

講述 Selenium WebDriver 原理以前,咱們先來看下 w3c WebDriver 協議,這裏以 ChromeDriver 爲例。

術語解釋:

  • W3C WebDriver 是一個瀏覽器協議,又稱 WebDriver 協議 / WebDriver 規範 / WebDriver API
  • Driver 是 WebDriver API 的特定實現,好比 Chrome 瀏覽器的 ChromeDriver。
  • ChromeDriver 是一個能夠獨立運行的服務器程序,它實現了 WebDriver 協議。
  • Selenium WebDriver 是一個基於 WebDriver 協議的 Web 自動化框架。

啓動 ChromeDriver

在命令行窗口執行如下命令啓動 ChromeDriver。

# 爲了方便,建議先切換到 exe 文件所在目錄
chromedriver_80.exe -port=12345

ChromeDriver 啓動成功後,將獲得一個服務器訪問地址 http://localhost:12345。打開任務管理器,咱們能夠看到 chromedriver_80.exe 進程。

訪問 ChromeDriver

一、調用 New Session 接口,建立一個 Session,返回 sessionId=be92a2485bcdb5ca0d3bc44bd0b00a8d。與此同時將打開一個 Chrome 瀏覽器窗口。

endpoint_webdriver= http://localhost:12345

思考題:若是再次調用該接口會發生什麼呢?

二、調用 Navigate To 接口,訪問百度網站,等價於 Java 客戶端的 driver.get("https://www.baidu.com")

執行結果:

其餘接口相似,再也不一一舉例。這些步驟是否是和使用 Java/Python 編寫測試步驟時似曾相識?總結起來就是兩步:

  • 第一步:經過 WebDriver 驅動啓動一個服務端(每一個驅動操做與之對應的瀏覽器,如 Chrome 驅動操做 Chrome 瀏覽器)。
  • 第二步:客戶端調用服務端 HTTP 接口,服務端收到請求後解析請求,執行對應的瀏覽器操做。

瀏覽器驅動大部分是各瀏覽器廠商根據 WebDriver 規範實現的,因此 Selenium 再也不須要直接操做瀏覽器,而是經過 HTTP 接口向驅動發出符合 WebDriver 規範的指令。

有一點須要注意,雖然低版本的 Firefox(47及如下) 和 Safari 不須要單獨下載驅動程序,但這不是說 Firefox 和 Safari 不須要 WebDriver 驅動,而是由於在 Selenium 客戶端中內置了瀏覽器的 webdriver 擴展,這些瀏覽器擴展起到了和驅動相同的做用,一樣遵循 W3C WebDriver 協議。

從 Selenium 的源碼中能夠發現這些特殊狀況:

  • Selenium2和3中低版本 Firefox 使用了 webdriver.xpi,經過 HTTP 通訊。
  • Selenium2 中 Safari 使用了 client.js,經過 WebSocket 通訊。
  • Selenium3 中 Safari 使用蘋果系統自帶的 safaridriver,經過 HTTP 通訊。

    • Safari 發佈版驅動路徑:/usr/bin/safaridriver
    • Safari 技術預覽版驅動路徑:/Applications/Safari Technology Preview.app/Contents/MacOS/safaridriver

Selenium WebDriver

瞭解了 WebDriver 協議後,咱們回過頭來看 Selenium WebDriver 的工做原理就簡單不少了。

若是咱們有一個客戶端庫,負責瀏覽器驅動的啓動、管理,HTTP 請求的組裝、響應的處理等諸多和測試腳本自己無關的事,測試用例的編寫將變得更加容易。而 Selenium WebDriver 就提供了一系列這樣的客戶端庫,除此以外還提供了 Selenium Grid 用於分佈式執行。

從上圖中能夠看出,Java 編寫的測試腳本中的命令由 Java 客戶端轉爲 HTTP 請求並訪問 WebDriver 服務器(驅動服務),瀏覽器驅動收到 HTTP 請求後,操做瀏覽器,就用網站用戶那樣。

若是你讀過上面 Selenium RC 的部分,你就會發現,Selenium WebDriver 相比 Selenium RC,從架構上來講要簡單不少,API 也更加簡潔。這對用戶和 Selenium 的開發者來講,都是一個質的飛躍。若是你考慮到 Selenium 要支持 X 個操做系統、Y 種瀏覽器和 Z 種編程語言,你就能理解 Selenium 的開發工做量有多大了,在 Selenium RC 時代,大量代碼和編程語言綁定(好比 XHR 的長輪詢、SSL 證書管理),這讓每次迭代更新都很痛苦。

上面的 Selenium WebDriver 原理圖,等價於官網的這張圖。

除此以外,還能夠有不一樣的部署方式,使用 Remote WebDriver,操做遠程主機上的瀏覽器。

經過 Selenium Server 或 Selenium Grid 與驅動通訊。

Selenium firefox xpi

由於對 Firefox 的支持,已經從內置的 firefox xpi 變爲和其餘驅動相似的 geckodriver,因此再也不過多介紹。若是你對 Selenium 客戶端中內置的 firefox xpi 插件的工做機制感興趣,能夠閱讀參考資料 [1] 中的 16.6. The Remote Driver, and the Firefox Driver in Particular 部分。

參考資料:

  1. The Architecture of Open Source Applications: Selenium WebDriver
  2. Understanding the components
相關文章
相關標籤/搜索