使用 Selenium 測試 Ansible 網頁應用程式角色
總覽
Ansible 是部署網頁應用程式的絕佳工具。Ansible 讓我們能以組成網頁應用程式的不同「角色」來定義應用程式(例如:網頁伺服器、資料庫伺服器、應用程式伺服器)。隨著角色及其之間的互動變得越來越複雜,我們需要更強大的方式來測試角色,以驗證我們是否正確部署了網頁應用程式。
在上一篇貼文中,我們使用 Ansible 部署了一個名為 ClipBucket 的網頁應用程式,這是一個影片託管網頁應用程式。在該篇貼文中,我們加入了自動化測試來驗證網頁應用程式是否正確安裝,但這些測試並未非常嚴謹地檢驗應用程式的功能。
在本文中,我們將展示更強大的自動化測試,能更深入地檢驗應用程式的功能。為此,我們將使用名為 Selenium 的網頁瀏覽器自動化工具。我們在此仍會沿用 ClipBucket 角色,但這些概念應可普遍適用於任何使用 Ansible 部署的網頁應用程式。
使用 curl 進行基本測試
在我們原本的建置指令碼中,最後的測試如下所示:
curl -s "${container_ip}" \
| grep "Login" && \
(echo 'Landing page test: pass' && exit 0) || \
(echo 'Landing page test: fail' && exit 1)這是一個有用的測試,因為它驗證了我們網頁應用程式的幾個重要特性,包含:
- 網頁伺服器正在監聽連接埠 80
- 網頁伺服器正在回應使用者請求
- 網頁伺服器正在提供 ClipBucket 首頁
為什麼我們需要更好的測試
雖然原本的測試提供了一些重要的檢查,但它們幾乎沒有檢驗到網頁應用程式的實際功能。我們有可能在 Ansible 角色中引入會破壞網頁應用程式的錯誤,卻無法被基本測試偵測到。
例如,想像我們不小心在 ClipBucket playbook 中刪除了某個關鍵任務:
diff --git a/tasks/main.yml b/tasks/main.yml
index 8542ffc..e9d42c0 100644
--- a/tasks/main.yml
+++ b/tasks/main.yml
@@ -29,15 +29,6 @@
name: flvtool2
state: present
-- name: create a symlink for ClipBucket to find ffmpeg and MP4Box
- file:
- path: "/usr/local/bin/{{ item }}"
- src: "/usr/bin/{{ item }}"
- state: link
- with_items:
- - ffmpeg
- - MP4Box
-
- name: install the Python MySQLB module
pip: name=MySQL-python如果我們使用這個修改過的 playbook 進行部署,然後瀏覽目標伺服器,一切看起來都正常:
所有安裝任務都成功執行,我們也看到了 ClipBucket 首頁。那問題在哪裡呢?
我們來試著上傳一部影片。在嘗試觀看之前,一切都運作正常:
因為我們刪除了 playbook 中用來為 ffmpeg 建立符號連結(symlink)的任務,ClipBucket 無法將影片轉碼為可串流的格式。
我們想為此建立自動化測試,但使用簡單的命令列工具來編寫上傳影片的指令碼相當困難。使用者必須先登入(這表示指令碼需要在多個請求之間管理 Cookie),然後還要在網頁使用者介面中導覽以上傳影片。要用一系列的 curl 指令來完成這些操作會非常困難。
幸好,我們可以使用 Selenium。Selenium 是一個網頁測試工具,可讓我們以程式化的方式執行網頁瀏覽器操作。
設定 Selenium
要在 Ansible 控制機器上開始使用 Selenium,我們需要安裝幾個元件:
- Selenium Python 套件 - 我們將使用 Python API,這個套件提供了 Selenium 框架與 Python 綁定。
- Firefox - 我們需要一個瀏覽器供 Selenium 驅動。雖然 Selenium 可支援大多數主流瀏覽器,但它原生支援 Firefox。
- xvfb - 因為我們將在沒有實體顯示器的 VM 上執行此測試,所以我們會使用 xvfb 作為虛擬顯示器,讓 Firefox 以為自己是在顯示器上執行。
我們可以為此建立一個相當簡單的 playbook。唯一有點困難的部分是 xvfb 需要一個不太直觀的 init 指令碼。幸運的是,部落客 Cory Klein(柯瑞·克萊恩)去年寫了一篇文章,提供了一個 xvfb init 指令碼的範例,參考他的範例,我們得以為我們的需求建立一個。
選擇要測試的網頁流程
既然已經安裝好 Selenium,現在是時候建立 Selenium 指令碼來檢驗我們的網頁應用程式了。我們可能想驗證的網頁流程有許多種,例如:
- 登入
- 上傳影片並播放
- 對影片留言並確認留言有顯示
- 建立新的使用者帳號
對於 ClipBucket,我特別想確保影片能正確上傳,因此我們需要測試登入與影片上傳。不幸的是,ClipBucket 使用了一套厚重的 JavaScript 套件來管理上傳,因此用於上傳檔案的一般 Selenium API 無法運作。
作為替代方案,我們將使用 ClipBucket 的模組診斷頁面。它會顯示 ClipBucket 已安裝的模組,並在任何模組未正確安裝時顯示錯誤訊息:
我們可以用這個頁面來取代影片上傳流程,以驗證所有模組是否已正確安裝。
既然已經知道要檢驗哪些功能,我們就可以規劃想要自動化的網頁流程。其內容大致如下:
- 載入 ClipBucket 網址
- 以管理員身分登入
- 前往模組診斷頁面
- 驗證所有模組皆已安裝
這將讓我們能自動化驗證一些在基本測試中未涵蓋的額外功能:
- 使用者登入功能正常運作(這表示 ClipBucket 能成功存取資料庫)
- ClipBucket 能存取其工具相依套件
自動化網頁流程
要在 Selenium 中自動化瀏覽器操作,我們需要告訴 Selenium 要在瀏覽器中載入哪個 URL,然後再告訴 Selenium 如何在頁面上尋找並與元素互動。讓我們來看看如何為所需的網頁流程自動化這些操作。
自動化登入
要登入,我們需要在登入頁面上找到憑證欄位,輸入使用者名稱和密碼,然後按下「Login」按鈕。幸好,使用者名稱和密碼欄位都有 id 屬性,因此很容易在頁面上識別它們。「Login」按鈕雖然沒有 id 屬性,但它有一個在頁面上唯一的 name 屬性值 login,讓我們可以將其作為唯一識別碼:
我們在下方的程式碼片段中定位這些欄位並輸入登入憑證:
self.get('/admin_area/login.php')
username_element = self._driver.find_element_by_id('username')
ui.WebDriverWait(
self._driver, TIMEOUT).until(
expected_conditions.visibility_of(username_element))
password_element = self._driver.find_element_by_id('password')
username_element.send_keys(username)
password_element.send_keys(password)
self._driver.find_element_by_name('login').click()擷取模組清單畫面
檢查已安裝模組頁面的方式有些不同。我們完全不需要與頁面互動,只需要尋找並解讀某些頁面元素。我們想確保所有模組都已正確安裝,這表示我們需要識別出標示各個模組的方塊,然後以程式化的方式判斷它們是否顯示錯誤訊息。
這有點棘手,因為我們感興趣的元素(或它們在 DOM 中的父元素)都沒有 id 屬性。它們全都是帶有 class="well" 的 <div>,所以這是我們用來尋找各個模組資訊方塊的最佳選擇。
找到這些方塊後,我們需要判斷方塊顯示的是模組安裝成功還是發生問題。我們可以透過尋找成功指標,或是驗證元素是否缺少失敗指標來做到這一點。前者較為嚴謹,但後者在程式碼上更簡單。帶有錯誤訊息的方塊必定包含一個帶有 class="alert" 屬性的元素,因此如果方塊沒有任何帶有此類別的子元素,我們就能判斷該方塊代表成功。
我們可以用下方的程式碼片段來達成此目的。
self.get('/admin_area/cb_mod_check.php')
for module_element in self._driver.find_elements_by_class_name('well'):
try:
ui.WebDriverWait(
self._driver, TIMEOUT).until(
expected_conditions.visibility_of(module_element))
alert_element = module_element.find_element_by_class_name(
'alert')
if alert_element:
raise ClipBucketModuleError(alert_element.text)
except exceptions.NoSuchElementException:
# Lack of alert is good: the module is installed correctly.
continue在 Travis 中整合網頁流程測試
綜合以上,我們可以將 Selenium 安裝 playbook 和 Selenium 指令碼加入到我們的建置檔案中。
接著,我們使用先前那個有問題的提交來建立一個 GitHub pull request。當 Travis 建置執行時,我們可以看到它失敗了:
2016-09-21 00:17:06,229 clipbucket_driver INFO Logging in with username=testadmin
2016-09-21 00:17:06,229 clipbucket_driver INFO Loading url: http://172.17.0.2/admin_area/login.php
2016-09-21 00:17:07,525 clipbucket_driver INFO Login complete
2016-09-21 00:17:07,526 clipbucket_driver INFO Checking ClipBucket modules
2016-09-21 00:17:07,526 clipbucket_driver INFO Loading url: http://172.17.0.2/admin_area/cb_mod_check.php
Traceback (most recent call last):
File "tests/clipbucket_driver/main.py", line 36, in <module>
main(parser.parse_args())
File "tests/clipbucket_driver/main.py", line 24, in main
driver.do_check_modules()
File "/home/travis/build/mtlynch/ansible-role-clipbucket/tests/clipbucket_driver/clipbucket_driver.py", line 64, in do_check_modules
raise ClipBucketModuleError(alert_element.text)
clipbucket_driver.ClipBucketModuleError: ffmpeg is not found太好了!我們的新測試正確地找出了 Ansible 角色中的問題。
接著,讓我們確認執行正常的 playbook 時一切運作正常:
2016-09-21 01:58:21,992 clipbucket_driver INFO Logging in with username=testadmin
2016-09-21 01:58:21,992 clipbucket_driver INFO Loading url: http://172.17.0.2/admin_area/login.php
2016-09-21 01:58:23,036 clipbucket_driver INFO Login complete
2016-09-21 01:58:23,039 clipbucket_driver INFO Checking ClipBucket modules
2016-09-21 01:58:23,040 clipbucket_driver INFO Loading url: http://172.17.0.2/admin_area/cb_mod_check.php
2016-09-21 01:58:23,724 clipbucket_driver INFO Module check complete
2016-09-21 01:58:23,724 clipbucket_driver INFO Exiting ClipBucket driverSelenium 指令碼正確地判斷出所有必要的模組皆已正確安裝。如果未來我們所做的任何變更導致登入功能失效或模組安裝不正確,我們的自動化測試就會將其攔截下來。
結論
在本文中,我們結合了 Ansible 與 Selenium,以驗證 Ansible 角色是否正確部署了網頁應用程式,且部署結果是否支援預期的使用者行為。Selenium 提供了高度的彈性,因此能夠在各種網頁應用程式中測試多種不同的網頁 UI 流程。
隨機一篇部落格




