Testing Ansible Web App Roles with Selenium

Michael Lynch

SeleniumでAnsibleのWebアプリロールをテストする

原文は Michael Lynch により に公開されました。 このブログを購読する

概要

AnsibleはWebアプリのデプロイに優れたツールです。Ansibleでは、Webアプリを構成するさまざまな「ロール」(例:Webサーバー、データベースサーバー、アプリケーションサーバー)という単位でWebアプリを定義できます。ロールやロール間の相互作用が複雑になるにつれて、Webアプリが正しくデプロイされていることを検証するために、それに見合ったより強力なロールテストの手法が必要になります。

前回の記事では、ClipBucketという動画共有WebアプリをAnsibleでデプロイしました。その記事ではWebアプリが正しくインストールされたことを確認するための自動テストも含めましたが、これらのテストはアプリケーションの機能をそれほど厳密には検証していませんでした。

今回は、アプリの機能をより深く検証する、より強力な自動テストを紹介します。そのために、SeleniumというWebブラウザ自動化ツールを使用します。ここでも引き続きClipBucketのロールを例にしますが、ここで紹介する考え方はAnsibleでデプロイするあらゆるWebアプリに一般的に応用できます。

curlによる基本的なテスト

元々のビルドスクリプトでは、最後のテストは次のようになっていました。

curl -s "${container_ip}"  \
  | grep "Login" && \
  (echo 'Landing page test: pass' && exit 0) || \
  (echo 'Landing page test: fail' && exit 1)

これは有用なテストで、Webアプリのいくつかの重要な特性を検証しています。具体的には次のとおりです。

  • Webサーバーがポート80でリッスンしていること
  • Webサーバーがユーザーのリクエストに応答していること
  • WebサーバーがClipBucketのランディングページを配信していること

なぜより良いテストが必要なのか

元のテストでもいくつかの重要なチェックはできましたが、Webアプリ本来の機能はほとんど検証できていませんでした。そのため、AnsibleロールにWebアプリを壊すようなバグを混入させてしまっても、基本的なテストでは検出できない可能性があります。

例えば、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は動画をストリーミング可能な形式にトランスコードできなくなっています。

このケースを自動テストしたいところですが、単純なコマンドラインツールで動画アップロードをスクリプト化するのは困難です。まずユーザーがログインする必要があり(つまりスクリプトはリクエスト間でCookieを管理しなければなりません)、さらにWeb UIを操作して動画をアップロードしなければなりません。これを一連のcurlコマンドで実現するのは非常に困難です。

幸い、Seleniumを利用できます。Seleniumは、Webブラウザの操作をプログラムで実行できるWebテストツールです。

Seleniumのセットアップ

Seleniumを使い始めるには、Ansibleのコントロールマシンにいくつかのコンポーネントをインストールする必要があります。

  • Selenium Pythonパッケージ - Python APIを使用するので、このパッケージでSeleniumフレームワークとPythonバインディングを導入します。
  • Firefox - Seleniumに操作させるブラウザが必要です。Seleniumは主要なブラウザのほとんどで動作しますが、Firefoxをネイティブにサポートしています。
  • xvfb - このテストはディスプレイのないVM上で実行するため、仮想ディスプレイとしてxvfbを使用し、Firefoxがモニタ上で動作していると認識させます。

このために比較的シンプルなPlaybookを作成できます。少し難しかったのは、xvfbが少々分かりにくいinitスクリプトを必要とする点でした。幸い、ブロガーのCory Klein氏が昨年、xvfbのinitスクリプトの例を紹介する記事を書いており、その例を参考にして私たちの用途に合ったものを作成できました

テストするWebフローの選択

Seleniumのインストールができたので、Webアプリを検証するためのSeleniumスクリプトを作成しましょう。検証したいWebフローには、例えば次のようなものが考えられます。

  • ログイン
  • 動画をアップロードして再生する
  • 動画にコメントを投稿して表示されることを確認する
  • 新しいユーザーアカウントを作成する

ClipBucketでは特に動画が正しくアップロードされることを確認したいので、ログインと動画アップロードをテストする必要があります。残念ながら、ClipBucketはアップロード管理に重量級のJavaScriptパッケージを使用しているため、通常のSelenium APIではファイルアップロードがうまく動作しません。

代わりに、ClipBucketのモジュール診断ページを利用します。このページにはClipBucketにインストールされているモジュールが表示され、いずれかのモジュールが正しくインストールされていない場合はエラーメッセージが表示されます。

ClipBucketのモジュール表示

このページを動画アップロードフローの代わりに使うことで、すべてのモジュールが正しくインストールされていることを検証できます。

検証したい機能が決まったので、自動化したいWebフローを概略化してみましょう。次のようになります。

  1. ClipBucketのURLを読み込む
  2. 管理者としてログインする
  3. モジュール診断ページに移動する
  4. すべてのモジュールがインストールされていることを確認する

これにより、基本的なテストでは検証していなかった追加の機能を自動的に検証できるようになります。

  • ユーザーログインが機能していること(つまりClipBucketがデータベースに正常にアクセスできること)
  • ClipBucketが依存ツールにアクセスできること

Webフローの自動化

Seleniumでブラウザ操作を自動化するには、まずブラウザで読み込むURLをSeleniumに指示し、次にページ上の要素をどのように見つけて操作するかをSeleniumに教える必要があります。目的のWebフローに必要な操作をどのように自動化するか見ていきましょう。

ログインの自動化

ログインするには、ログインページで認証情報の入力欄を見つけ、ユーザー名とパスワードを入力してから「Login」ボタンを押す必要があります。幸い、ユーザー名とパスワードの欄にはid属性があるため、ページ上で簡単に特定できます。「Login」ボタンにはid属性がありませんが、ページ内で一意なname属性loginがあるため、それを一意の識別子として利用できます。

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"属性を持つ要素が含まれるため、このクラスを持つ子要素がなければ成功したボックスと判断できます。

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でのWebフローテストの統合

以上をまとめると、Seleniumのインストール用PlaybookとSeleniumスクリプトをビルドファイルに追加できます。

次に、先ほどの壊れたコミットを含むGitHubプルリクエストを作成します。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ロールがWebアプリを正しくデプロイし、デプロイ結果が期待されるユーザー操作をサポートしていることを検証しました。Seleniumは非常に柔軟性が高いため、さまざまなWebアプリケーションで多様なWeb UIフローをテストすることが可能です。

この記事は「muse-spark-1.2-contributor」を使用して翻訳されました。

コメント