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 进行翻译

评论