Testing Ansible Web App Roles with Selenium

Michael Lynch

使用 Selenium 測試 Ansible 網頁應用程式角色

原文由 Michael Lynch 發布,訂閱此部落格

概覽

Ansible 是部署網頁應用程式的絕佳工具。透過 Ansible,我們可以將網頁應用程式定義為組成它的不同「角色(roles)」(例如網頁伺服器、資料庫伺服器、應用程式伺服器)。隨著我們的角色以及它們之間的互動變得愈來愈複雜,我們需要更強大的測試方式來驗證是否正確部署了網頁應用程式。

上一篇貼文中,我們使用 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)

這是一個有用的測試,因為它驗證了我們網頁應用程式的幾個重要特性,也就是:

  • 網頁伺服器正在監聽 port 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 無錯誤畫面

所有安裝任務都順利完成,而且我們也看到了 ClipBucket 的首頁。那麼問題在哪呢?

來試著上傳一部影片。一直到我們嘗試觀看影片之前,一切都很正常:

影片錯誤截圖

因為我們刪掉了 playbook 中建立 ffmpeg 符號連結的那個任務,ClipBucket 無法將影片轉碼成可串流的格式。

我們想為這個情況建立一個自動化測試,但要用簡單的命令列工具來撰寫上傳影片的腳本相當困難。使用者必須先登入(這代表腳本需要在多次請求之間管理 cookies),然後還要在網頁介面上操作以上傳影片。要用一連串的 curl 指令來做到這些會非常困難。

幸好,我們可以使用Selenium。Selenium 是一套網頁測試工具,讓我們能以程式化的方式執行網頁瀏覽器的操作。

設定 Selenium

要在 Ansible 控制機上開始使用 Selenium,我們需要先安裝幾個元件:

  • Selenium Python 套件 - 我們會使用 Python API,這個套件提供了 Selenium 框架與 Python 綁定。
  • Firefox - 我們需要一個瀏覽器讓 Selenium 來驅動。雖然 Selenium 支援大多數主流瀏覽器,但它原生就支援 Firefox。
  • xvfb - 因為我們會在一台沒有實體顯示器的 VM 上執行這個測試,所以會使用 xvfb 作為虛擬顯示器,讓 Firefox 以為自己是在有螢幕的環境中執行。

我們可以為此建立一個相當簡單的 playbook。唯一比較麻煩的地方是,xvfb 需要一個不太直觀的 init script。幸好,部落客 Cory Klein 去年寫了一篇文章,裡面提供了 xvfb init script 的範例,參考他的範例,我們得以為我們的需求建立一個

選擇要測試的網頁流程

現在已經安裝好 Selenium,是時候來建立一個 Selenium 腳本來測試我們的網頁應用程式了。我們可能會想驗證的網頁流程有很多種,例如:

  • 登入
  • 上傳影片並播放
  • 在影片下方留言並確認留言有顯示
  • 建立新的使用者帳號

對 ClipBucket 來說,我特別想確保影片能正確上傳,所以我們需要測試登入和影片上傳。可惜的是,ClipBucket 使用了一套很重的 JavaScript 套件來處理上傳,因此一般用來上傳檔案的 Selenium API 無法運作。

作為替代方案,我們會改用 ClipBucket 的模組診斷頁面。這個頁面會顯示 ClipBucket 已安裝的模組,當有任何模組沒有正確安裝時,就會顯示錯誤訊息:

ClipBucket 模組檢視

我們可以用這個頁面來取代影片上傳流程,以驗證所有模組是否都已正確安裝。

既然已經知道想測試哪些功能,我們就可以大致規劃出想要自動化的網頁流程。它大概會長這樣:

  1. 載入 ClipBucket 網址
  2. 以管理員身分登入
  3. 前往模組診斷頁面
  4. 驗證所有模組皆已安裝

這會讓我們自動驗證一些在基礎測試中沒有涵蓋到的額外功能:

  • 使用者登入功能正常(這代表 ClipBucket 能成功存取資料庫)
  • ClipBucket 能存取其相依的工具

自動化網頁流程

要在 Selenium 中自動化瀏覽器操作,我們需要告訴 Selenium 要在瀏覽器中載入哪個 URL,然後讓 Selenium 知道如何在頁面上尋找並與元件互動。接下來我們來看看如何為所需的網頁流程自動化這些操作。

自動化登入

要登入,我們需要在登入頁面上找到帳號密碼欄位,輸入使用者名稱和密碼,然後按下「Login」按鈕。幸好,使用者名稱和密碼欄位都有 id 屬性,讓我們在頁面上很容易就能找到它們。「Login」按鈕雖然沒有 id 屬性,但它有一個值為 loginname 屬性,而且在頁面上是唯一的,因此我們可以用它來當作識別依據:

ClipBucket 登入欄位

我們在底下這個程式碼片段中定位這些欄位並輸入登入資訊:

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" 屬性的元件,所以如果方塊內沒有任何帶有這個 class 的子元件,我們就可以認定它是成功安裝的。

ClipBucket 模組錯誤

我們可以用底下這個程式碼片段來做到這件事。

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 腳本加入到建置檔中。

接著,我們用先前那個有問題的 commit 來建立一個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 driver

Selenium 腳本正確判斷出所有必要的模組都已正確安裝。如果未來我們做了任何更動,導致登入功能失效或模組安裝不正確,自動化測試就會幫我們抓出來。

結論

在這篇文章中,我們結合了 Ansible 與 Selenium,來驗證 Ansible 角色是否正確部署了網頁應用程式,以及部署結果是否支援預期的使用者行為。Selenium 提供了高度的彈性,因此可以針對各種網頁應用程式測試許多不同的網頁 UI 流程。

本文章由 muse-spark-1.2-contributor 進行翻譯

留言