Web 应用 Selenium 测试

作者:Alexander Sirotkin

从财务角度来看,Web 2.0 可能无法与互联网泡沫时代和 Web 1.0 相提并论,但从技术角度来看,它已经领先数光年。作为一名 Web 开发人员,您会发现自己设计的 Web 应用程序比 1.0 泡沫时代的前辈们敢于梦想的更加复杂和更苛刻。这是有趣的部分。不太有趣的部分是尝试测试这些功能丰富的应用程序。手动测试的前景不会让任何开发人员感到兴奋,而且您需要测试的大量浏览器使其成为更大的噩梦。图 1 基于 Net Applications 在撰写本文时提供的浏览器市场份额统计数据,清楚地说明了浏览器战争 2.0 的状态。

Web Application Testing with Selenium

图 1. 浏览器市场份额

细分如下

  • Microsoft Internet Explorer: 66%

  • Firefox: 24%

  • Safari: 4%

  • Chrome: 3%

  • Opera: 2%

  • 其他: 1%

尽管不同公司监测 Web 使用流量的估计值有所不同,但结论显而易见:您不能再仅仅使用单个浏览器进行测试。并且,请注意,占流量约 66% 的 Microsoft IE 实际上是三个非常不同的浏览器:IE6、IE7 和 IE8,众所周知它们呈现 Web 网站的方式不同。

随着移动宽带互联网变得更便宜、更普及,这种情况预计会变得更糟,这将增加您必须测试的更多浏览器,有时这些浏览器的功能有限且分辨率不标准。

好消息是您并非孤身一人面临这个问题,并且有一些先进的自动 Web 应用程序测试框架可以显着减轻负担。

Selenium 来救援

Selenium 不仅仅是一个普通的 Web 站点单元测试应用程序。它实际上是一组工具,包括以下内容

  1. Selenium Core

  2. Selenium Remote Control (RC)

  3. Selenium Integrated Development Environment (IDE)

  4. Selenium Grid

Selenium Core 提供基本测试功能。它以 JavaScript 实现,可以独立部署(在这种情况下,它必须安装在 Web 服务器上),或者更常见的是,作为 Selenium RC、IDE 或 Grid 的一部分部署,它们都使用 Selenium Core 引擎。图 2 显示了 Core 和其他 Selenium 项目之间的关系。

Web Application Testing with Selenium

图 2. Selenium 架构

Selenium RC 是一个基于 Java 的命令行服务器,它启动浏览器并将测试命令发送到 Selenium Core。您可以使用多种编程语言(如 Perl、Java、C# 等)编写测试,这些测试作为 Selenium RC 客户端实现。如果您不害怕一些基本的编程,那么这是使用 Selenium 最强大的方法。

Selenium IDE 是一个 Firefox 插件,允许您使用图形工具创建测试。这些测试可以直接从 IDE 执行,也可以导出为多种编程语言,并作为 Selenium RC 客户端自动执行。

Selenium Grid 通过协调多个 Selenium RC 实例解决了可伸缩性问题,允许您在不同的机器上并行运行多个测试。

Selenium RC

我通常使用 Selenium RC,即使对于只有一点编程背景的人,我也推荐它。API 很简单,它提供的灵活性非常值得学习曲线。RC 由服务器和客户端部分组成,这有点令人困惑,因为服务器与您正在测试的 Web 服务器无关,而是驱动用于测试的 Web 浏览器。服务器是一个 Java 命令行应用程序,应按如下方式执行

java -jar selenium-server.jar

命令--help命令行开关将为您提供支持选项的完整列表,但通常默认值就足够了。您也可以从 Java 以编程方式调用它。服务器默认会在端口 4444 上等待客户端连接。客户端可以使用以下语言之一编写:Perl、Python、PHP、Ruby、Java、C# 和 Erlang。列表非常令人印象深刻。事实上,在我发现 Selenium 之前,我并不知道爱立信以外的任何人都在使用 Erlang。以下 Perl 代码示例展示了一个相当基本的 Selenium 测试,它打开一个 Firefox 浏览器,并使用 Google 搜索词语 Selenium

use strict;
use warnings;
use Test::WWW::Selenium;

my $sel = Test::WWW::Selenium->new(
                host          => "localhost",
                port          => 4444,
                browser       => "*firefox",
                browser_url   => "http://www.google.com",
                default_names => 1);
$sel->open("/");
$sel->type("q", "selenium");
$sel->click("btnI");
$sel->wait_for_page_to_load(60000);

print "$sel->get_title()\n";

$sel->stop();

上面的代码打开一个新的 Selenium 会话,连接到与客户端(列表中的代码)在同一台机器上运行的端口 4444 上的 Selenium 服务器,打开一个 Firefox 浏览器,转到 http://www.google.com URL,在查询字段中键入单词 selenium,然后单击 Google 的“I'm Feeling Lucky”按钮。查询和搜索元素都通过其 HTML 元素 ID 或名称 q 和 btnI 分别标识(检查 Google 主页的源代码)。脚本等待页面加载,打印页面标题并关闭 Selenium 会话。

Selenium Core

Selenium Core 是 Selenium 的核心。它是所有 Selenium 项目使用的 JavaScript 函数集合,但很少单独使用。如果您查看被测 Web 站点的 HTML 源代码(在 Firefox 中使用“查看”→“页面源代码”,或使用您告诉 Selenium 执行的任何浏览器),您会注意到一些额外的 JavaScript 代码,如下所示

<script
  type="text/javascript"
  src="/selenium-server/core/scripts/selenium-browserbot.js">
</script>

这实际上是 Selenium Core 代码。Selenium 将自身“注入”到 Web 页面中的方式可能会有所不同(稍后会详细介绍);但是,当直接使用 Selenium Core 时,必须手动将此代码添加到您的 HTML 中。当然,这需要访问提供页面的 Web 服务器,但这并非总是可用的。此模式的最大优点是它可以可靠地与所有浏览器一起使用——这是其他使用 Selenium 的方法无法保证的。

然而,独立的 Selenium Core 现在很少使用,将来可能会被弃用,因此在本文的其余部分,我将重点介绍 Selenium RC 和 IDE,它们是使用 Selenium 的首选方法。

Selenium IDE

与 Selenium RC 不同,您可以使用某种编程语言编写测试用例,而 Selenium IDE 允许您通过简单地与被测 Web 应用程序交互(就像您的用户一样)在图形环境中开发 Selenium 测试用例。这可能是开发测试用例最简单的方法。Selenium IDE 以 Firefox 插件的形式实现。IDE 不仅记录与您与浏览器的交互相对应的 Selenium 命令,而且还允许您修改其参数并将更多命令添加到记录的测试用例中。该插件有一个上下文菜单,允许您从浏览器当前显示的页面中选择一个 UI(用户界面)元素,然后从下拉列表中选择一个 Selenium 命令,根据所选 UI 元素的上下文自动添加相关参数。

安装后,可以通过 Firefox 菜单中的“工具”→“Selenium IDE”访问 IDE。为了说明 IDE 的功能,让我们使用 IDE 重新创建上面的 Google 搜索测试用例。为此,您只需运行 IDE,检查录制按钮是否已打开,打开 http://www.google.com 页面,键入 selenium 并单击“Google 搜索”。当您键入时,Selenium IDE 会捕获您的操作(图 3)。

Web Application Testing with Selenium

图 3. Selenium IDE 屏幕截图

Selenium 记录了手动输入的命令。现在可以使用 IDE 回放此测试用例,另存为 HTML 或导出为各种格式,包括 Selenium RC 的即用型代码片段。导出的测试用例与上面手写的代码非常相似,尽管在这种情况下,我将其导出为 Python,以演示 Selenium 的一些功能

from selenium import selenium
import unittest, time, re

class googletest(unittest.TestCase):
    def setUp(self):
        self.verificationErrors = []
        self.selenium = selenium(
              "localhost", 4444,
              "*chrome", "http://www.google.com/")
        self.selenium.start()

    def test_googletest(self):
        sel = self.selenium
        sel.open("/")
        sel.type("q", "selenium")
        sel.click("btnG")

    def tearDown(self):
        self.selenium.stop()
        self.assertEqual([], self.verificationErrors)

if __name__ == "__main__":
    unittest.main()

请注意,除了使用单元测试框架外,上面的代码与 Perl 示例的不同之处在于使用了"*chrome"浏览器字符串而不是"*firefox"。这是 Selenium 最令人困惑的问题之一,值得在此处单独讨论。

浏览器字符串参数

在深入了解此参数真正令人困惑的细节之前,重要的是要理解,即使 Selenium 不是一个新项目,它仍然在积极开发中,并且正如有时开源项目发生的那样,投入到引入新功能中的一些开发工作可能更好地用于调试旧功能并使其更稳定。因此,在某些配置中,某些 Selenium 功能可能无法按预期工作,而另一些功能可能根本无法工作。

浏览器字符串参数不仅指定 Selenium 将使用的浏览器,还指定 Selenium 用于控制浏览器的方法以及 Selenium 服务器和浏览器内运行的 Selenium Core 之间的通信模式。而且,是的,某些浏览器有多种模式。并非所有模式都为每个浏览器实现,并且某些 Selenium 命令在某些模式下无法工作。更令人困惑的是,默认模式有时会在不同的 Selenium 版本之间更改。在 Selenium 1.0 版本中"*firefox""*chrome""*firefoxproxy""*iexplore"的别名,而"*iehta".

则是"*chrome""*firefoxproxy""*iehta"的别名。这解释了为什么自动生成的 Python 代码为浏览器字符串生成了与手动生成的 Perl 代码不同的值,即使这两个测试都使用了 Firefox。这两个

浏览器配置文件都实现了一种“原生”方法,其中 Selenium 使用特定于浏览器的方法来“注入”Selenium Core JavaScript 代码并控制浏览器,而不是代理注入 (PI) 模式,后者是通用的,并且至少在理论上应该适用于所有浏览器。

启用代理注入模式后,Selenium RC(具有内置 HTTP 代理服务器)配置被测浏览器的自定义配置文件以使用其本地代理。代理服务器返回的每个 HTTP 响应都已将 Selenium Core JavaScript 代码“注入”到 <head> HTML 元素中。

按照设计,代理注入模式适用于所有浏览器,即使是那些未经 Selenium 测试的浏览器,只要浏览器支持 JavaScript 和 HTTP 代理即可。不仅如此,它还允许规避“同源策略”——也就是说,它允许您在同一 Selenium 会话期间使用“原生”模式不完整的浏览器(例如 Opera)测试不同域上的不同站点。此时,您可能会认为这是您应该用于测试的模式,但不幸的是,在 Selenium 的开发过程中,开发人员发现某些重要功能很难在 PI 模式下实现。因此,大多数开发人员逐渐切换到所谓的原生模式,即使它需要为每个浏览器单独实现。因此,PI 模式维护不善且错误百出。

正如您所看到的,即使 Selenium 因其多浏览器测试功能而经常受到赞扬,但实际上,除 Firefox 和 Internet Explorer 之外的浏览器支持不佳。归根结底,如果您必须确保您的 Web 站点在所有重要的浏览器下看起来和工作都正确,那么您最好的选择可能是浏览器屏幕截图工具,例如 BrowserShots 或 BrowserSeal。BrowserShots 是一种基于 Web 的屏幕截图服务,支持各种浏览器,但不幸的是响应时间很长。另一方面,BrowserSeal 是一个在您的 PC 上运行的独立应用程序,针对 Web 站点屏幕截图进行了优化。与 BrowserShots 的几分钟相比,它在几秒钟内生成屏幕截图;但是,在撰写本文时,只有 Windows 版本可用。

Selenium Grid

乍一看,Selenium RC 看起来支持足够的并行性,任何额外的分布式处理能力都是不需要的。毕竟,单个 Selenium RC 服务器允许您打开多个并行会话(即,同时驱动多个浏览器)和一个 Selenium RC 客户端。除了能够处理一个服务器的多个并发会话外,它还可以同时与多个服务器通信。

但是,在实践中,由于性能问题,不建议在同一 Selenium RC 服务器上运行超过六个浏览器。此外,管理大量 Selenium RC 服务器是一个主要的难题,并且不能很好地扩展。这就是 Selenium Grid 可以提供帮助的地方。

  • Selenium Grid 向 Selenium 架构引入了另一个组件——Selenium Hub,它管理可用的 Selenium Remote Control 实体池,并负责以下事项

  • 透明地为特定测试分配 Selenium RC 实体。

  • 限制每个 Remote Control 上的并发测试运行次数。

使测试免受实际网格基础设施的影响。"*firefox"就您的 RC 客户端编程而言,从 Selenium RC 迁移到 Grid 需要最少的代码更改。您所要做的就是更改臭名昭著的浏览器字符串参数。例如,将"Firefox on Windows"更改为类似"Safari on Mac".

hub:
   port: 4444
   environments:
       - name:    "Firefox on Linux"
         browser: "*firefox"
       - name:    "IE on Windows"
         browser: "*iexplore"

Selenium Hub 的配置稍微复杂一些。首先,您必须修改 grid_configuration.yml 文件。假设您想使用两个 RC 实例——一个在 Linux 上使用 Firefox,另一个在 Windows 上使用 Internet Explorer。在这种情况下,您的配置文件将如下所示之后,您应该使用ant通过运行ant launch-hub

在 hub 机器上启动 Selenium Hub。RC 实例是通过运行以下命令创建的,一个在 Linux 机器上,另一个在 Windows 机器上。

ant -Denvironment="Firefox on Linux" \
    -DhubURL=http://<hub-IP-address>:4444 \
        launch-remote-control

在 Linux 机器上

ant -Denvironment="IE on Windows" \
    -DhubURL=http://<hub-IP-address>:4444 \
        launch-remote-control

在 Windows 机器上

之后,您可以编写 RC 客户端代码,通过 hub 使用上述任何 RC 服务器实例。

Selenium API

  • Selenium API 的全面描述超出了本文的范围,但以下列表演示了该框架的功能$sel->click($locator)

  • — 单击链接、按钮、复选框或单选按钮。$sel->context_menu($locator)

  • — 模拟打开指定元素的上下文菜单(就像用户右键单击元素时可能发生的情况一样)。$sel->focus($locator)

  • — 将焦点移动到指定元素。$sel->key_press($locator, $key_sequence)

  • — 模拟用户按下并释放一个键。$sel->mouse_over($locator)

  • — 模拟用户将鼠标悬停在指定元素上方。$sel->type($locator, $value)

  • — 设置输入字段的值,就像您在其中键入一样。$sel->check($locator)

  • — 选中切换按钮(复选框/单选按钮)。$sel->select($select_locator, $option_locator)

  • — 使用选项定位器从下拉菜单中选择一个选项。$sel->submit($form_locator)

  • — 提交指定的表单。$sel->open($url)

  • — 在测试框架中打开一个 URL。$sel->open_window($url, $window_id)

  • — 打开一个弹出窗口。$sel->go_back()

  • — 模拟用户单击浏览器中的后退按钮。$sel->get_location()

  • — 获取当前页面的绝对 URL。$sel->get_body_text()

  • — 获取页面的全部文本。$sel->get_text($locator)

  • — 获取元素的文本。$sel->get_selected_indexes($select_locator)

  • — 获取指定选择或多选元素中选定选项的所有选项索引。$sel->get_all_links()

  • — 返回页面上所有链接的 ID。$sel->wait_for_condition($script, $timeout)

  • — 重复运行指定的 JavaScript 代码片段,直到其评估结果为“true”。$sel->get_cookie()

  • — 返回被测当前页面的所有 Cookie。$sel->wait_for_text_present($text, $timeout)

— 等待直到 $text 出现在 HTML 源代码中。有关更多详细信息,请查看文章末尾的 Perl API 链接。其他语言的 API 文档也可用。如果您发现 API 在某些方面有所欠缺,您始终可以使用以下命令执行您自己的 JavaScript 函数来扩展它.

$sel->get_eval($script)

总结

正如您所看到的,Selenium 是一个强大的 Web 应用程序测试工具,它支持多种不同的语言,并且围绕 Selenium Core 构建了许多不同的框架。它是一个开源项目,拥有活跃的社区,在撰写本文时,该社区正在全力开发 2.0 版本,该版本将包含许多令人兴奋的新功能。

资源

Selenium Web 站点: seleniumhq.org

Selenium Perl API 文档: release.seleniumhq.org/selenium-remote-control/1.0-beta-2/doc/perl/WWW-Selenium.html

BrowserShots 跨浏览器屏幕截图服务: browsershots.org

BrowserSeal 快速跨浏览器屏幕截图应用程序: www.browserseal.com

Alexander (Sasha) Sirotkin 在软件、操作系统和网络方面拥有超过十年的经验。他目前在 Comsys Mobile 的 LTE(长期演进)项目工作,与妻子和孩子住在以色列特拉维夫。可以通过电子邮件 sasha.sirotkin [AT] gmail.com 联系 Alexander。