Testing Ansible Web App Roles with Selenium

Michael Lynch

使用 Selenium 测试 Ansible Web 应用角色

原文由 Michael Lynch 于 发布,订阅该博客

概述

Ansible 是部署 Web 应用的优秀工具。通过 Ansible,我们可以将 Web 应用定义为组成它的不同“角色”(例如 Web 服务器、数据库服务器、应用服务器)。随着角色及其交互变得越来越复杂,我们需要更强大的测试手段来验证 Web 应用是否被正确部署。

在上一篇文章中,我们使用 Ansible 部署了一个名为 ClipBucket 的 Web 应用,这是一个视频托管应用。在那篇文章中,我们加入了自动化测试来验证 Web 应用是否正确安装,但这些测试并没有对应用功能进行严格的检验。

在本文中,我们将展示更强的自动化测试,能够更深入地检验应用功能。为此,我们将使用一款名为 Selenium 的浏览器自动化工具。本文仍将以 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 界面来上传视频。用一系列 curl 命令来完成这些操作会非常困难。

好在我们可以使用 Selenium。Selenium 是一款 Web 测试工具,可以让我们以编程方式执行浏览器操作。

搭建 Selenium 环境

要在 Ansible 控制机上开始使用 Selenium,我们需要安装几个组件:

  • Selenium Python Package - 我们将使用 Python API,这个包提供了 Selenium 框架和 Python 绑定。
  • Firefox - 我们需要一个供 Selenium 驱动的浏览器。虽然 Selenium 支持大多数主流浏览器,但它对 Firefox 提供了原生支持。
  • xvfb - 由于我们将在没有真实显示器的虚拟机上运行测试,我们会使用 xvfb 作为虚拟显示,让 Firefox 以为自己运行在有显示器的环境中。

我们可以为此创建一个相当简单的 playbook。唯一有点棘手的是 xvfb 需要一个不太直观的 init 脚本。好在博主 Cory Klein 去年写过一篇文章,其中给出了 xvfb init 脚本的示例,参考他的示例,我们得以为自己的需求创建一个。

选择要测试的 Web 流程

现在已经装好了 Selenium,是时候编写一个 Selenium 脚本来检验我们的 Web 应用了。我们可以验证的 Web 流程有很多,例如:

  • 登录
  • 上传视频并播放
  • 在视频下发表评论并检查是否正常显示
  • 创建新用户账号

对于 ClipBucket,我尤其关心视频能否正确上传,因此我们需要测试登录和视频上传。不幸的是,ClipBucket 使用了一个笨重的 JavaScript 组件来管理上传,因此用于上传文件的常规 Selenium API 无法生效。

作为替代方案,我们将使用 ClipBucket 的模块诊断页面。该页面会显示 ClipBucket 已安装的模块,并在某个模块未正确安装时显示错误信息:

ClipBucket 模块视图

我们可以用这个页面来代替视频上传流程,以验证所有模块是否都已正确安装。

明确了想要检验的功能后,我们可以梳理出要自动化的 Web 流程,大致如下:

  1. 加载 ClipBucket 的 URL
  2. 以管理员身份登录
  3. 进入模块诊断页面
  4. 验证所有模块均已安装

这将让我们能够自动验证一些在基础测试中未覆盖的额外功能:

  • 用户登录功能正常(这意味着 ClipBucket 能够成功访问数据库)
  • ClipBucket 能够访问其依赖的工具

自动化 Web 流程

要在 Selenium 中自动化浏览器操作,我们需要告诉 Selenium 在浏览器中加载哪个 URL,然后指明如何查找页面元素并与之交互。下面我们来看一下如何为所需的 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 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 角色是否正确部署了 Web 应用,以及部署结果是否支持预期的用户行为。Selenium 提供了极大的灵活性,因此可以用来在各种 Web 应用中测试多种不同的 Web 界面流程。

本文章由 muse-spark-1.2-contributor 进行翻译

评论