Python-Selenium-测试工具学习指南-全-
Python Selenium 测试工具学习指南(全)
原文:
zh.annas-archive.org/md5/ec9c269b5bb6049f13670061a906eb22译者:飞龙
前言
Selenium 是一套用于自动化浏览器的工具。它主要用于测试应用程序,但其用途并不仅限于测试。它还可以用于屏幕抓取以及在浏览器窗口中自动化重复性任务。Selenium 支持在所有主要浏览器上自动化,包括 Firefox、Internet Explorer、Google Chrome、Safari 和 Opera。Selenium WebDriver 现在已成为 W3C 标准,并得到主要浏览器厂商的支持。
Selenium 提供了一套用于自动化与浏览器交互的工具:
-
Selenium IDE:这是一个用于记录和回放 Firefox 中 Selenium 脚本的 Firefox 插件。它提供了一个图形用户界面来记录使用 Firefox 的用户操作。这是一个开始学习和使用 Selenium 的绝佳工具,但它只能与 Firefox 一起使用,不支持其他浏览器。然而,它可以将记录的脚本转换为 Selenium WebDriver 支持的多种编程语言,这支持在除 Firefox 之外的其他浏览器上运行脚本。
-
Selenium WebDriver:这是一个用于使用编程语言开发高级 Selenium 脚本的编程接口。我们还可以在多个操作系统(包括 Linux、Windows 和 Mac OS X)上支持 Selenium 的多个浏览器上运行测试。这使得 Selenium 成为一个真正的跨浏览器测试工具。Selenium WebDriver 提供了各种语言的客户端库,包括 Java、C#、Python、Ruby、PHP 和 JavaScript,并更倾向于编写测试脚本。
-
Selenium 独立服务器:这也被称为 Selenium Grid,允许远程和分布式执行使用 WebDriver 创建的 Selenium 脚本。我们还可以使用独立服务器的网格功能并行运行测试,包括在移动平台(如 Android 或 Apple iOS 的 iPhone 和 iPad)上的测试。
如标题所示,本书将向您介绍 Python 的 Selenium WebDriver 客户端库。您将学习如何使用 Python 中的 Selenium WebDriver 自动化浏览器以测试 Web 应用程序。本书包含从设置 Selenium 到使用 Selenium 的基本和高级功能创建和运行自动化脚本的测试 Web 应用程序的课程。本书假设您对使用 Python 进行编程有基本了解。
本书涵盖的内容
第一章, 使用 Selenium WebDriver 和 Python 入门,从安装 Python 和 Selenium WebDriver 客户端库开始。我们将选择用于 Selenium 脚本开发的 Python 编辑器或 IDE。然后,我们将从测试的应用程序中创建我们的第一个自动化脚本,用于简单的搜索工作流程。在本章结束时,我们将在 Selenium 支持的各种浏览器上运行 Selenium 脚本。
第二章, 使用 unittest 编写测试,向您展示了如何使用 Selenium 和 unittest 库来测试 Web 应用程序。我们将把脚本转换为 unittest 测试用例。我们将使用 Selenium 和 unittest 创建更多测试。我们将为一系列测试创建一个 TestSuite。我们将运行这些测试并分析结果。在本章末尾,您将学习如何生成 HTML 格式的测试报告,您可以将其分发给项目的各个利益相关者。
第三章, 查找元素,向您介绍了定位器,它们是自动化浏览器窗口中显示的不同类型用户界面(UI)元素的钥匙。Selenium 使用定位器在页面上查找元素,然后执行操作或检索它们的属性以进行测试。您将学习各种定位元素的方法,包括 XPath 和 CSS。我们将通过在测试的应用程序上使用示例来展示如何使用这些方法。
第四章, 使用 Selenium Python API 进行元素交互,向您展示了如何使用 Selenium WebDriver 客户端库在 Python 中与不同类型的元素、JavaScript 警报、框架和窗口进行交互。您将学习如何执行诸如向元素发送值、执行点击和从下拉列表中选择选项等操作。您还将看到如何处理框架、不同类型的 JavaScript 警报以及在不同子浏览器窗口之间切换。
第五章, 同步测试,为您介绍了 Selenium 提供的各种等待方法,以实现测试的可靠和稳健执行。您将学习如何在 Selenium 测试中使用隐式和显式等待来实现同步。您还将学习在我们的测试脚本中实现显式等待的各种方法。
第六章, 跨浏览器测试,深入探讨了使用 RemoteWebDriver 在远程机器或通过 Selenium Grid 运行跨浏览器测试。您将学习如何使用 RemoteWebDriver 在远程机器上运行测试。我们还将设置一个 Selenium Grid,以在多种浏览器和操作系统组合上运行测试。您还将看到如何在无头浏览器(如 PhantomJS)上执行测试。在本章末尾,我们将看到如何使用 Sauce Labs 和 BrowserStack 等云测试工具,通过 RemoteWebDriver 在云中运行测试。
第七章, 移动设备测试,展示了如何使用 Selenium WebDriver 和 Appium 在移动设备上测试应用程序。我们将设置 Appium 来测试我们的示例应用程序在 iOS 和 Android 模拟器及设备上。您还将学习如何使用 Appium 运行原生移动应用程序。
第八章, 页面对象和数据驱动测试,介绍了两个重要的设计模式,以实现可维护和高效的测试框架。你将学习如何使用页面对象来隐藏定位器的技术细节,并将页面上的操作划分为单独的类,创建更易读且易于维护的测试用例。然后,你将学习如何使用 unittest 库创建数据驱动测试。
第九章, Selenium WebDriver 的高级技术,深入探讨了使用 Selenium 自动化浏览器进行测试的一些高级技术。你将学习如何使用各种动作方法来模拟复杂的鼠标和键盘操作。你将了解如何处理会话 cookie,在测试运行期间捕获屏幕截图,以及创建整个测试运行的影片。
第十章, 与其他工具和框架的集成,展示了如何将 Selenium WebDriver 与自动化验收测试框架(如 Behave)和持续集成工具结合使用。你将首先学习如何将 Selenium 与 Behave 集成以创建自动化验收测试。我们将使用 Selenium WebDriver 在 UI 上实现一个示例功能和验收测试。在第章的结尾,我们将设置使用 Jenkins 运行我们创建的测试,作为持续集成的一部分。我们将设置一个日程表,以每天一次的频率运行测试。
在阅读完本书之后,你将学会 Selenium WebDriver 的所有基本功能,以便在 Python 中创建自己的 Web 测试框架。
阅读本书所需的条件
要开始阅读本书,你需要具备 Python 的基本编程技能,以及 HTML、JavaScript、CSS 和 XML 等 Web 技术的知识。如果你能够编写简单的 Python 脚本,使用循环和条件语句,定义类,那么你应该能够跟上本书中的每一个示例。我们将花时间解释本书中编写的每一行代码,以便你能够在任何情况下都能创建所需的结果。有一些软件先决条件是必需的,这些将在第一章中解释。你需要能够访问命令行界面终端、Python 解释器和机器上的 Firefox 和 Google Chrome 等 Web 浏览器。你可以从www.mozilla.org/en-US/firefox/下载并安装 Firefox,从www.google.com/chrome/下载并安装 Google Chrome。如果你是 Windows 用户,你可能对测试默认安装的 Internet Explorer 感兴趣。
本书面向的对象
如果你是一名质量保证/测试专业人士、软件开发人员或使用 Python 的 Web 应用程序开发人员,并想学习 Selenium WebDriver 来自动化浏览器以测试你的 Web 应用程序,那么这本指南是你开始学习的完美选择!作为先决条件,这本书假设你具备 Python 编程语言的基本理解,尽管不需要任何先前的 Selenium WebDriver 知识。到本书结束时,你将获得 Selenium WebDriver 的全面知识,这将有助于你编写自动化测试。
惯例
在这本书中,你会找到许多不同风格的文本,以区分不同类型的信息。以下是一些这些风格的示例及其含义的解释。
文本中的代码词汇、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称将如下所示:“pip工具将下载 Selenium 软件包的最新版本并在你的机器上安装。”
代码块如下设置:
# create a new Firefox session
driver = webdriver.Firefox()
driver.implicitly_wait(30)
driver.maximize_window()
当我们希望将你的注意力引到代码块的一个特定部分时,相关的行或项目会以粗体显示:
# run the suite
xmlrunner.XMLTestRunner(verbosity=2, output='test-reports').
run(smoke_tests)
任何命令行输入或输出都如下所示:
pip install -U selenium
新术语和重要词汇以粗体显示。你在屏幕上看到的,例如在菜单或对话框中的文字,在文本中会这样显示:“从工具菜单中选择Internet Options。”
注意
警告或重要注意事项如下所示。
小贴士
小贴士和技巧如下所示。
读者反馈
我们读者的反馈总是受欢迎的。告诉我们你对这本书的看法——你喜欢什么或不喜欢什么。读者反馈对我们很重要,因为它帮助我们开发出你真正能从中获得最大收益的标题。
要向我们发送一般反馈,只需发送电子邮件至<feedback@packtpub.com>,并在邮件主题中提及书籍的标题。
如果你在一个领域有专业知识,并且你对撰写或为书籍做出贡献感兴趣,请参阅我们的作者指南www.packtpub.com/authors。
客户支持
现在你已经是 Packt 图书的骄傲拥有者,我们有许多事情可以帮助你从购买中获得最大收益。
下载示例代码
你可以从www.packtpub.com下载你购买的所有 Packt 出版物的示例代码文件。如果你在其他地方购买了这本书,你可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给你。
错误清单
尽管我们已经尽一切努力确保我们内容的准确性,但错误仍然会发生。如果您在我们的某本书中发现错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。通过这样做,您可以节省其他读者的挫败感,并帮助我们改进本书的后续版本。如果您发现任何勘误,请通过访问 www.packtpub.com/submit-errata,选择您的书籍,点击勘误提交表单链接,并输入您的勘误详情来报告它们。一旦您的勘误得到验证,您的提交将被接受,勘误将被上传到我们的网站或添加到该标题的勘误部分下的现有勘误列表中。
要查看之前提交的勘误表,请访问 www.packtpub.com/books/content/support,并在搜索字段中输入书籍名称。所需信息将出现在勘误部分。
盗版
在互联网上对版权材料的盗版是一个跨所有媒体的持续问题。在 Packt,我们非常重视保护我们的版权和许可证。如果您在互联网上发现我们作品的任何非法副本,请立即提供位置地址或网站名称,以便我们可以寻求补救措施。
请通过 <copyright@packtpub.com> 联系我们,并提供涉嫌盗版材料的链接。
我们感谢您在保护我们作者和我们为您提供有价值内容的能力方面的帮助。
问题
如果您在这本书的任何方面有问题,您可以通过 <questions@packtpub.com> 联系我们,我们将尽力解决问题。
第一章:Selenium WebDriver 和 Python 入门
Selenium 自动化浏览器。它自动化我们在浏览器窗口中进行的交互,例如导航到网站、点击链接、填写表单、提交表单、浏览页面等。它适用于所有主要浏览器。
为了使用 Selenium WebDriver,我们需要一种编程语言来编写自动化脚本。我们选择的编程语言也应该有可用的 Selenium 客户端库。
在本书中,我们将使用 Python 和 Selenium WebDriver 客户端库来创建自动化脚本。Python 是一种广泛使用的通用、高级编程语言。它易于使用,其语法允许我们在更少的代码行中表达概念。它强调代码可读性,并提供使我们能够在小型和大型规模上编写程序的构造。它还提供了一系列内置和用户编写的库,使我们能够轻松地完成复杂任务。
Selenium WebDriver Python 客户端库提供了对所有 Selenium WebDriver 功能和 Selenium 独立服务器的访问,用于基于浏览器的应用程序的远程和分布式测试。Selenium Python 语言绑定由 David Burns、Adam Goucher、Maik Röder、Jason Huggins、Luke Semerau、Miki Tebeka 和 Eric Allenin 开发和维护。
Selenium WebDriver 客户端库支持 Python 版本 2.6、2.7、3.2 和 3.3。
本章将通过演示其安装、基本功能和整体结构,向您介绍 Selenium WebDriver Python 客户端库。
在本章中,我们将介绍以下主题:
-
安装 Python 和 Selenium 包
-
选择和设置 Python 编辑器
-
使用 Selenium WebDriver Python 客户端库实现示例脚本
-
使用 Internet Explorer 和 Google Chrome 实现跨浏览器支持
准备您的机器
使用 Python 与 Selenium 的第一步,我们需要以尽可能少的最低要求在我们的计算机上安装它。让我们按照以下章节中解释的步骤设置基本环境。
安装 Python
你会发现大多数 Linux 发行版、Mac OS X 和其他 Unix 系统默认已安装 Python。在 Windows 上,你需要单独安装它。不同平台的安装程序可在 python.org/download/ 找到。
注意
本书中的所有示例都是在 Windows 8 操作系统上的 Python 2.7 和 Python 3.0 上编写和测试的。
安装 Selenium 包
Selenium WebDriver Python 客户端库包含在 Selenium 包中。要简单安装 Selenium 包,请使用可在 pip.pypa.io/en/latest/ 找到的 pip 安装程序工具。
使用 pip,您可以使用以下命令简单地安装或升级 Selenium 包:
pip install -U selenium
这是一个相当简单的过程。此命令将在您的机器上设置 Selenium WebDriver 客户端库,包括我们将需要用于创建自动化脚本的 Python 模块和类。pip工具将下载 Selenium 包的最新版本并在您的机器上安装它。可选的–U标志将升级已安装包的现有版本到最新版本。
您还可以从pypi.python.org/pypi/selenium下载 Selenium 包的最新版本源代码。只需点击页面右上角的下载按钮,解压下载的文件,然后使用以下命令安装:
python setup.py install
浏览 Selenium WebDriver Python 文档
如下截图所示,Selenium WebDriver Python 客户端库文档可在selenium.googlecode.com/git/docs/api/py/api.html找到:

它提供了关于 Selenium WebDriver 所有核心类和函数的详细信息。同时请注意以下 Selenium 文档链接:
-
在
docs.seleniumhq.org/docs/的官方文档提供了所有 Selenium 组件的文档,并附有支持语言的示例 -
Selenium Wiki 在
code.google.com/p/selenium/w/list列出了我们将在本书后面探索的一些有用主题
选择 IDE
现在我们已经设置了 Python 和 Selenium WebDriver,我们需要一个编辑器或集成开发环境(IDE)来编写自动化脚本。一个好的编辑器或 IDE 可以提高生产力,并帮助完成许多使编码体验简单易行的事情。虽然我们可以在 Emacs、Vim 或记事本等简单编辑器中编写 Python 代码,但使用 IDE 会让生活变得更加容易。有许多 IDE 可供选择。通常,IDE 提供以下功能以加速您的开发和编码时间:
-
带有代码完成和 IntelliSense 的图形代码编辑器
-
函数和类的代码探索器
-
语法高亮
-
项目管理
-
代码模板
-
单元测试和调试工具
-
源代码控制支持
如果您是 Python 的新手,或者您是第一次在 Python 中工作的测试人员,您的开发团队将帮助您设置正确的 IDE。
然而,如果您是第一次使用 Python 并且不知道要选择哪个 IDE,这里有一些您可能想要考虑的选择。
PyCharm
PyCharm 由 JetBrains 开发,是一家领先的提供专业开发工具和 IDE(如 IntelliJ IDEA、RubyMine、PhpStorm 和 TeamCity)的供应商。
PyCharm 是一个精致、强大且多功能的 IDE,工作表现相当出色。它将 JetBrains 在构建功能强大的 IDE 方面的最佳经验与许多其他功能相结合,以提供高度高效的使用体验。
PyCharm 支持 Windows、Linux 和 Mac。要了解更多关于 PyCharm 及其功能,请访问www.jetbrains.com/pycharm/。
PyCharm 有两种版本——社区版和专业版。社区版是免费的,而专业版则需要付费。以下截图展示了正在运行示例 Selenium 脚本的 PyCharm 社区版:

社区版非常适合使用其出色的调试支持构建和运行 Selenium 脚本。本书的其余部分我们将使用 PyCharm。在本章的后面部分,我们将设置 PyCharm 并创建我们的第一个 Selenium 脚本。
注意
本书中的所有示例都是使用 PyCharm 构建的;然而,你可以轻松地将这些示例用于你选择的编辑器或 IDE。
PyDev Eclipse 插件
PyDev Eclipse 插件是 Python 开发者中广泛使用的另一个编辑器。Eclipse 是一个著名的开源 IDE,主要用于 Java 开发;然而,它也通过其强大的插件架构支持各种其他编程语言和工具。
Eclipse 是一个跨平台 IDE,支持 Windows、Linux 和 Mac。你可以在www.eclipse.org/downloads/获取 Eclipse 的最新版本。
在设置 Eclipse 之后,你需要单独安装 PyDev 插件。使用Lars Vogel的教程在www.vogella.com/tutorials/Python/article.html安装 PyDev。安装说明也可在pydev.org/找到。
以下是 Eclipse PyDev 插件运行示例 Selenium 脚本的截图,如下所示:

PyScripter
对于 Windows 用户,PyScripter 也可以是一个很好的选择。它是开源的、轻量级的,并提供现代 IDE 所提供的所有功能,如 IntelliSense 和代码补全、测试和调试支持。你可以在code.google.com/p/pyscripter/找到更多关于 PyScripter 及其下载信息。
以下是 PyScripter 运行示例 Selenium 脚本的截图,如下所示:

设置 PyCharm
既然我们已经看到了 IDE 的选择,让我们来设置 PyCharm。本书中的所有示例都是使用 PyCharm 创建的。然而,你可以设置任何你选择的 IDE,并直接使用示例。我们将按照以下步骤设置 PyCharm,以开始使用 Selenium Python:
-
从 JetBrains 网站
www.jetbrains.com/pycharm/download/index.html下载并安装 PyCharm Community Edition。 -
启动 PyCharm Community Edition。在如图所示的 PyCharm Community Edition 对话框中点击 创建新项目 选项:
![设置 PyCharm]()
-
在如图所示的 创建新项目 对话框中,在 项目名称 字段中指定您的项目名称。在此示例中,使用
setests作为项目名称。我们需要首次配置解释器。点击
按钮来设置解释器,如图所示:![设置 PyCharm]()
-
在 Python 解释器 对话框中,点击加号图标。PyCharm 将建议类似以下截图所示的已安装解释器。从 选择解释器路径 中选择解释器。
![设置 PyCharm]()
-
PyCharm 将配置所选解释器,如图所示。它将显示与 Python 一起安装的包列表。点击 应用 按钮,然后点击 确定 按钮:
![设置 PyCharm]()
-
在 创建新项目 对话框中,点击 确定 按钮以创建项目:
![设置 PyCharm]()
使用 Selenium 和 Python 的第一步
现在我们已经准备好开始使用 Python 创建和运行自动化脚本。让我们从 Selenium WebDriver 开始,创建一个使用 Selenium WebDriver 类和函数来自动化浏览器交互的 Python 脚本。
在本书的大部分示例中,我们将使用一个示例 Web 应用程序。此示例应用程序建立在著名的电子商务框架——Magento 上。您可以在demo.magentocommerce.com/找到该应用程序。
小贴士
下载示例代码
您可以从www.packtpub.com下载您购买的所有 Packt 出版物的示例代码文件。如果您在其他地方购买了这本书,您可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。
示例代码也托管在github.com/upgundecha/learnsewithpython。
在本示例脚本中,我们将导航到应用程序的演示版本,搜索产品,并按照以下步骤列出搜索结果页面上的产品名称:
-
让我们使用在设置 PyCharm 时创建的项目。创建一个简单的 Python 脚本,该脚本将使用 Selenium WebDriver 客户端库。在项目资源管理器中,右键单击
setests,然后从弹出菜单中选择新建 | Python 文件:![使用 Selenium 和 Python 迈出第一步]()
-
在新建 Python 文件对话框中,在名称字段中输入
searchproducts,然后单击确定按钮:![使用 Selenium 和 Python 迈出第一步]()
-
PyCharm 将在代码编辑区域添加一个新的标签searchproducts.py。在searchproduct.py标签中复制以下代码:
from selenium import webdriver # create a new Firefox session driver = webdriver.Firefox() driver.implicitly_wait(30) driver.maximize_window() # navigate to the application home page driver.get("http://demo.magentocommerce.com/") # get the search textbox search_field = driver.find_element_by_name("q") search_field.clear() # enter search keyword and submit search_field.send_keys("phones") search_field.submit() # get all the anchor elements which have product names displayed # currently on result page using find_elements_by_xpath method products = driver.find_elements_by_xpath("//h2[@class='product-name']/a") # get the number of anchor elements found print "Found " + str(len(products)) + " products:" # iterate through each anchor element and print the text that is # name of the product for product in products: print product.text # close the browser window driver.quit()注意
如果你使用的是任何其他 IDE 或你选择的编辑器,创建一个新文件,将代码复制到新文件中,并将文件保存为
searchproducts.py。 -
要运行脚本,请在 PyCharm 代码窗口中按Ctrl + Shift + F10组合键,或者从运行菜单中选择运行 'searchproducts'。这将启动执行,你将看到一个新窗口的 Firefox 导航到演示网站,并在 Firefox 窗口中执行 Selenium 命令。如果一切顺利,脚本将在最后关闭 Firefox 窗口。脚本将在 PyCharm 控制台打印出产品列表,如下面的截图所示:
![使用 Selenium 和 Python 迈出第一步]()
注意
我们也可以使用以下命令通过命令行运行此脚本。打开命令行,然后打开
setests目录,并运行以下命令:python searchproducts.py在本书的其余部分,我们将使用命令行作为首选方法来执行测试。
我们将花一些时间查看我们刚才创建的脚本。我们将逐句分析,简要了解 Selenium WebDriver。本书的其余部分还有很多内容要介绍。
selenium.webdriver模块实现了 Selenium 支持的浏览器驱动类,包括 Firefox、Chrome、Internet Explorer、Safari 以及各种其他浏览器,以及RemoteWebDriver,用于在远程机器上托管的浏览器上进行测试。
我们需要从 Selenium 包中导入webdriver以使用 Selenium WebDriver 方法:
from selenium import webdriver
接下来,我们需要一个我们想要使用的浏览器实例。这将提供一个程序接口,使用 Selenium 命令与浏览器交互。在这个例子中,我们使用 Firefox。我们可以创建一个 Firefox 实例,如下面的代码所示:
driver = webdriver.Firefox()
在运行过程中,这将启动一个新的 Firefox 窗口。我们还在驱动程序上设置了一些选项:
driver.implicitly_wait(30)
driver.maximize_window()
我们为 Selenium 设置了一个超时,使用 30 秒的隐式等待来执行驱动程序步骤,并通过 Selenium API 最大化 Firefox 窗口。我们将在第五章同步测试中学习更多关于隐式等待的内容。
接下来,我们将通过调用 driver.get() 方法使用其 URL 导航到应用程序的演示版本。在调用 get() 方法后,WebDriver 等待页面在 Firefox 窗口中完全加载,并将控制权返回给脚本。
在页面加载完成后,Selenium 将像人类用户一样与页面上的各种元素进行交互。例如,在应用程序的首页,我们需要在一个文本框中输入一个搜索词并点击搜索按钮。这些元素被实现为 HTML 输入元素,Selenium 需要找到这些元素来模拟用户操作。Selenium WebDriver 提供了多种方法来查找这些元素并与它们交互,以执行发送值、点击按钮、在下拉列表中选择项目等操作。我们将在第三章查找元素中了解更多关于这方面的内容。
在这个例子中,我们使用 find_element_by_name 方法查找搜索文本框。这将返回与 find 方法中指定的名称属性匹配的第一个元素。HTML 元素由标签和属性定义。我们可以使用这些信息通过以下步骤找到元素:
-
在这个例子中,搜索文本框的名称属性定义为
q,我们可以像以下代码示例中那样使用这个属性:search_field = driver.find_element_by_name("q") -
一旦找到搜索文本框,我们将通过使用
clear()方法清除之前输入的值(如果有的话)以及使用send_keys()方法输入指定的新值来与这个元素进行交互。接下来,我们将通过调用submit()方法提交搜索请求:search_field.clear() search_field.send_keys("phones") search_field.submit() -
在提交搜索请求后,Firefox 将加载应用程序返回的结果页面。结果页面包含与搜索词匹配的产品列表,即
phones。我们可以读取结果列表,特别是使用find_elements_by_xpath()方法读取渲染在锚<a>元素中的所有产品的名称。这将返回一个包含多个匹配元素的列表:products = driver.find_elements_by_xpath("//h2[@class='product-name']/a") -
接下来,我们将使用所有锚
<a>元素的.text属性打印出页面上找到的产品数量(即锚<a>元素的数目)和产品名称:print "Found " + str(len(products)) + " products:" for product in products: print product.text -
在脚本末尾,我们将使用
driver.quit()方法关闭 Firefox 浏览器:driver.quit()
此示例脚本为我们提供了一个简洁的示例,说明如何将 Selenium WebDriver 和 Python 结合起来创建一个简单的自动化脚本。在这个脚本中,我们还没有进行任何测试。在本书的后面部分,我们将扩展这个简单的脚本成为一个测试集,并使用 Python 的各种其他库和功能。
跨浏览器支持
到目前为止,我们已经使用 Firefox 构建并运行了我们的脚本。Selenium 广泛支持跨浏览器测试,您可以在包括 Internet Explorer、Google Chrome、Safari、Opera 以及无头浏览器(如 PhantomJS)在内的所有主要浏览器上自动化。在本节中,我们将设置并运行上一节中创建的脚本,以使用 Internet Explorer 和 Google Chrome 查看 Selenium WebDriver 的跨浏览器功能。
设置 Internet Explorer
在 Internet Explorer 上运行脚本还有一些其他注意事项。要在 Internet Explorer 上运行测试,我们需要下载并设置InternetExplorerDriver服务器。InternetExplorerDriver服务器是一个独立的可执行服务器,它实现了 WebDriver 的线协议,作为测试脚本和 Internet Explorer 之间的粘合剂。它支持 Windows XP、Vista、Windows 7 和 Windows 8 操作系统上的主要 IE 版本。让我们按照以下步骤设置InternetExplorerDriver服务器:
-
从
www.seleniumhq.org/download/下载InternetExplorerDriver服务器。您可以根据所使用的系统配置下载 32 位或 64 位版本。 -
下载
InternetExplorerDriver服务器后,解压缩并将文件复制到存储脚本的同一目录中。 -
在 IE 7 或更高版本中,每个区域的保护模式设置必须具有相同的值。保护模式可以是开启或关闭,只要对所有区域都一样。要设置保护模式设置:
-
从工具菜单中选择Internet 选项。
-
在Internet 选项对话框中,单击安全选项卡。
-
在选择一个区域以查看或更改安全设置中,选择列出的每个区域,并确保所有区域都勾选或取消勾选启用保护模式(需要重新启动 Internet Explorer)。所有区域应与以下屏幕截图所示的设置相同:
![设置 Internet Explorer]()
注意
在使用
InternetExplorerDriver服务器时,保持浏览器缩放级别设置为 100%也很重要,这样可以将原生鼠标事件设置为正确的坐标。 -
-
最后,修改脚本以使用 Internet Explorer。我们不会创建 Firefox 类的实例,而是以下方式使用
IE类:import os from selenium import webdriver # get the path of IEDriverServer dir = os.path.dirname(__file__) ie_driver_path = dir + "\IEDriverServer.exe" # create a new Internet Explorer session driver = webdriver.Ie(ie_driver_path) driver.implicitly_wait(30) driver.maximize_window() # navigate to the application home page driver.get("http://demo.magentocommerce.com/") # get the search textbox search_field = driver.find_element_by_name("q") search_field.clear() # enter search keyword and submit search_field.send_keys("phones") search_field.submit() # get all the anchor elements which have product names displayed # currently on result page using find_elements_by_xpath method products = driver.find_elements_by_xpath("//h2[@class='product-name']/a") # get the number of anchor elements found print "Found " + str(len(products)) + " products:" # iterate through each anchor element and print the text that is # name of the product for product in products: print product.text # close the browser window driver.quit()在此脚本中,我们在创建
IE浏览器类实例时传递了InternetExplorerDriver服务器的路径。 -
运行脚本,Selenium 将首先启动
InternetExplorerDriver服务器,该服务器启动浏览器并执行步骤。InternetExplorerDriver服务器在 Selenium 脚本和浏览器之间充当中间件。实际步骤的执行与我们观察到的 Firefox 非常相似。
小贴士
在 code.google.com/p/selenium/wiki/InternetExplorerDriver 了解有关 Internet Explorer 的重要配置选项,以及在 code.google.com/p/selenium/wiki/DesiredCapabilities 了解 DesiredCapabilities 文章的更多信息。
设置 Google Chrome
在 Google Chrome 上设置和运行 Selenium 脚本与 Internet Explorer 类似。我们需要下载与 InternetExplorerDriver 类似的 ChromeDriver 服务器。ChromeDriver 服务器是由 Chromium 团队开发和维护的独立服务器。它实现了 WebDriver 的线协议以自动化 Google Chrome。它支持 Windows、Linux 和 Mac 操作系统。按照以下步骤设置 ChromeDriver 服务器:
-
从
chromedriver.storage.googleapis.com/index.html下载ChromeDriver服务器。 -
下载
ChromeDriver服务器后,解压并将文件复制到存储脚本的同一目录中。 -
最后,修改示例脚本以使用 Chrome。我们不会创建 Firefox 类的实例,而是以下方式使用
Chrome类:import os from selenium import webdriver # get the path of chromedriver dir = os.path.dirname(__file__) chrome_driver_path = dir + "\chromedriver.exe" #remove the .exe extension on linux or mac platform # create a new Chrome session driver = webdriver.Chrome(chrome_driver_path) driver.implicitly_wait(30) driver.maximize_window() # navigate to the application home page driver.get("http://demo.magentocommerce.com/") # get the search textbox search_field = driver.find_element_by_name("q") search_field.clear() # enter search keyword and submit search_field.send_keys("phones") search_field.submit() # get all the anchor elements which have product names displayed # currently on result page using find_elements_by_xpath method products = driver.find_elements_by_xpath("//h2[@class='product-name']/a") # get the number of anchor elements found print "Found " + str(len(products)) + " products:" # iterate through each anchor element and print the text that is # name of the product for product in products: print product.text # close the browser window driver.quit()在此脚本中,我们在创建 Chrome 浏览器类实例时传递了
ChromeDriver服务器的路径。 -
运行脚本。Selenium 首先启动
Chromedriver服务器,该服务器启动 Chrome 浏览器并执行步骤。实际步骤的执行与我们观察到的 Firefox 非常相似。
小贴士
在 code.google.com/p/selenium/wiki/ChromeDriver 和 sites.google.com/a/chromium.org/chromedriver/home 了解有关 ChromeDriver 的更多信息。
摘要
在本章中,我们向您介绍了 Selenium 及其组件。我们使用 pip 工具安装了 selenium 包。然后我们查看各种编辑器和 IDE 以简化我们的 Selenium 和 Python 编码体验,并设置了 PyCharm。然后我们在示例应用程序上构建了一个简单的脚本,涵盖了 Selenium WebDriver Python 客户端库的一些高级概念。我们运行了脚本并分析了结果。最后,我们通过配置和运行脚本使用 Internet Explorer 和 Google Chrome 探索了 Selenium WebDriver 的跨浏览器测试支持。
在下一章中,我们将学习如何使用 unittest 库通过 Selenium WebDriver 创建自动化测试。我们还将学习如何创建测试套件并在组内运行测试。
第二章:使用 unittest 编写测试
Selenium WebDriver 是一个浏览器自动化 API。它提供了自动化浏览器交互的功能,这个 API 主要用于测试网络应用程序。我们不能使用 Selenium WebDriver 设置测试的前置条件和后置条件,检查预期的和实际的输出,检查应用程序的状态,报告测试结果,创建数据驱动测试等等。我们可以使用单元测试框架或单元测试中使用的测试运行器与 Selenium 结合,创建一个测试框架。在本章中,我们将学习如何使用 unittest 库在 Python 中创建 Selenium WebDriver 测试。
在本章中,我们将涵盖以下主题:
-
什么是
unittest? -
使用
unittest库编写 Selenium WebDriver 测试 -
使用
TestCase类实现测试 -
理解
unittest库提供的各种assert方法的类型 -
为一组测试创建
TestSuite -
使用
unittest扩展生成 HTML 格式的测试报告
unittest 库
unittest 库(最初命名为 PyUnit)灵感来源于在 Java 应用程序开发中广泛使用的 JUnit 库。我们可以使用 unittest 为任何项目创建一个全面的测试套件。unittest 模块在 Python 项目中使用,用于测试各种标准库模块,包括 unittest 本身。您可以在 docs.python.org/2/library/unittest.html 找到 unittest 的文档。
unittest 库为我们提供了创建测试用例、测试套件和测试设置的能力。让我们根据以下图表了解每个组件:

-
测试设置:通过使用测试设置,我们可以定义执行一个或多个测试所需的准备工作以及任何相关的清理操作。
-
测试用例:在
unittest中,测试用例是测试的最小单元。它使用unittest库提供的各种assert方法检查对特定动作和输入的特定响应。unittest库提供了一个名为TestCase的基类,可以用来创建新的测试用例。 -
测试套件:测试套件是多个测试或测试用例的集合,用于创建代表特定功能或测试应用程序的模块的测试组,这些测试将一起执行。
-
测试运行器:测试运行器协调测试的执行并向用户提供结果。运行器可能使用图形界面、文本界面或返回一个特殊值来指示测试执行的结果。
-
测试报告:测试报告显示测试结果的摘要,包括已执行测试用例的通过或失败状态,失败的步骤的预期与实际结果,以及整体运行和计时信息的摘要。
使用xUnit框架(如unittest)创建的测试分为三个部分,也称为 3A,如下所示:
-
安排:这部分为测试设置前提条件,包括需要测试的对象、相关配置和依赖项。
-
行动:这部分执行功能。
-
断言:这部分检查结果是否符合预期。
我们将在本章的其余部分使用这种方法,通过unittest库来创建测试。
注意
我们将在本书的其余部分使用unittest库来创建和运行 Selenium WebDriver 测试。然而,Python 中还有其他具有额外功能的测试框架,如下所示:
-
Nose:
nose框架扩展了unittest库,并提供了自动搜索和运行测试的能力。它还提供了各种插件来创建更高级的测试。你可以在nose.readthedocs.org/en/latest/了解更多关于nose的信息。 -
Pytest:
pytest框架是另一个提供许多高级功能以在 Python 中编写和运行单元测试的测试框架。你可以在pytest.org/latest/了解更多关于pytest的信息。
TestCase类
我们可以通过继承TestCase类并将每个测试作为方法添加到这个类中来创建一个测试或一组测试。要创建一个测试,我们需要使用assert或TestCase类中包含的许多assert变体之一。每个测试最重要的任务是调用assertEqual()来检查预期的结果,assertTrue()来验证条件,或assertRaises()来验证预期的异常被抛出。
除了添加测试外,我们还可以添加测试固定装置:即setUp()和tearDown()方法来处理创建和处置测试所需的任何对象或条件。
让我们开始使用unittest库,首先通过继承TestCase类编写一个简单的测试,然后为我们在第一章中创建的示例脚本添加一个测试方法,使用 Selenium WebDriver 和 Python 入门。
我们需要导入unittest模块并定义一个继承TestCase类的类,如下所示:
import unittest
from selenium import webdriver
class SearchTest(unittest.TestCase):
setUp()方法
测试用例的起点是setUp()方法,我们可以用它来在每个测试的开始或定义在类中的所有测试之前执行一些任务。这些可以是测试准备任务,例如创建浏览器驱动程序的实例、导航到基本 URL、加载测试数据、打开日志文件等等。
此方法不接受任何参数,也不返回任何内容。当定义了setUp()方法时,测试运行器将在每个测试方法之前运行该方法。在我们的例子中,我们将使用setUp()方法创建 Firefox 的实例,设置属性,并在测试执行之前导航到应用程序的主页,如下面的示例所示:
import unittest
from selenium import webdriver
class SearchTests(unittest.TestCase):
def setUp(self):
# create a new Firefox session
self.driver = webdriver.Firefox()
self.driver.implicitly_wait(30)
self.driver.maximize_window()
# navigate to the application home page
self.driver.get("http://demo.magentocommerce.com/")
编写测试
在设置方法到位后,我们现在可以编写一些测试来验证我们想要测试的应用程序的功能。在这个例子中,我们将搜索一个产品并检查结果是否返回了项目数量。与setUp()方法类似,测试方法是在TestCase类中实现的。我们命名这些方法时以单词test开头是很重要的。这种命名约定通知测试运行器哪些方法代表一个测试。
对于测试运行器找到的每个测试方法,它会在执行test方法之前执行setUp()方法。这有助于确保每个test方法都可以依赖于一个一致的环境,无论类中定义了多少个测试。我们将使用简单的assertEqual()方法来检查给定搜索词的预期结果是否与应用程序返回的结果匹配。我们将在本章后面讨论断言。
添加一个新的测试方法,test_search_by_category(),该方法通过类别搜索产品并检查搜索返回的产品数量,如下面的示例所示:
import unittest
from selenium import webdriver
class SearchTests(unittest.TestCase):
def setUp(self):
# create a new Firefox session
self.driver = webdriver.Firefox()
self.driver.implicitly_wait(30)
self.driver.maximize_window()
# navigate to the application home page
self.driver.get("http://demo.magentocommerce.com/")
def test_search_by_category(self):
# get the search textbox
self.search_field = self.driver.find_element_by_name("q")
self.search_field.clear()
# enter search keyword and submit
self.search_field.send_keys("phones")
self.search_field.submit()
# get all the anchor elements which have product names # displayed currently on result page using # find_elements_by_xpath method
products = self.driver.find_elements_by_xpath ("//h2[@class='product-name']/a")
self.assertEqual(2, len(products))
清理代码
与在每次测试方法之前调用的setUp()方法类似,TestCase类也会调用一个tearDown()方法来清理测试执行后的任何初始化值。一旦测试执行完毕,setUp()方法中定义的值就不再需要;因此,在测试完成后清理setUp()方法初始化的值是一个好习惯。在我们的例子中,测试执行后,我们不再需要 Firefox 的实例。我们将在tearDown()方法中关闭为测试创建的 Firefox 实例,如下面的代码所示:
import unittest
from selenium import webdriver
class SearchTests(unittest.TestCase):
def setUp(self):
# create a new Firefox session
self.driver = webdriver.Firefox()
self.driver.implicitly_wait(30)
self.driver.maximize_window()
# navigate to the application home page
self.driver.get("http://demo.magentocommerce.com/")
def test_search_by_category(self):
# get the search textbox
self.search_field = self.driver.find_element_by_name("q")
self.search_field.clear()
# enter search keyword and submit
self.search_field.send_keys("phones")
self.search_field.submit()
# get all the anchor elements which have product names # displayed currently on result page using # find_elements_by_xpath method
products = self.driver.find_elements_by_xpath ("//h2[@class='product-name']/a")
self.assertEqual(2, len(products))
def tearDown(self):
# close the browser window
self.driver.quit()
运行测试
要从命令行运行测试,我们可以向测试用例的main方法添加一个调用。我们将传递verbosity参数,该参数用于在控制台上显示测试结果细节的数量:
if __name__ == '__main__':
unittest.main(verbosity=2)
我们可以将测试存储为正常的 Python 脚本。对于这个例子,将示例测试保存为searchtests.py。保存文件后,我们可以通过使用以下命令在命令行中执行它:
python searchtests.py
运行测试后,unittest会在控制台上显示结果以及测试摘要,如下面的截图所示:

除了结果摘要之外,当测试用例失败时,对于每次失败,摘要将生成一段文本来描述出错的原因。查看以下截图,了解当我们更改预期值时会发生什么:

如你所见,它显示了哪个测试方法生成了失败,并带有跟踪信息以追踪导致失败代码流。此外,失败本身被显示为AssertionError,显示了预期输出与实际输出的不匹配。
添加另一个测试
我们可以将多个测试组合成一个测试类的一部分。这有助于创建属于特定功能的逻辑测试组。让我们向测试类中添加另一个测试。规则很简单;为新方法命名时以单词test开头,如下面的代码所示:
def test_search_by_name(self):
# get the search textbox
self.search_field = self.driver.find_element_by_name("q")
self.search_field.clear()
# enter search keyword and submit
self.search_field.send_keys("salt shaker")
self.search_field.submit()
# get all the anchor elements which have
# product names displayed
# currently on result page using
# find_elements_by_xpath method
products = self.driver.find_elements_by_xpath ("//h2[@class='product-name']/a")
self.assertEqual(1, len(products))
运行测试,你会看到两个 Firefox 实例打开和关闭。这就是setUp()和tearDown()方法对每个测试方法的工作方式。你将看到如下截图所示的结果:

类级别的setUp()和tearDown()方法
在上一个示例中,我们在每个测试方法执行之前使用setUp()方法创建一个新的 Firefox 实例,并在测试方法执行后关闭该实例。那么,我们是否可以考虑在每次创建新实例之前,在方法之间共享单个 Firefox 实例呢?这可以通过使用setUpClass()和tearDownClass()方法以及使用@classmethod装饰器来实现。这些方法允许我们在类级别而不是方法级别初始化值,然后在这些测试方法之间共享这些值。在下面的示例中,代码被修改为使用@classmethod装饰器调用setUpClass()和tearDownClass()方法:
import unittest
from selenium import webdriver
class SearchTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
# create a new Firefox session
cls.driver = webdriver.Firefox()
cls.driver.implicitly_wait(30)
cls.driver.maximize_window()
# navigate to the application home page
cls.driver.get("http://demo.magentocommerce.com/")
cls.driver.title
def test_search_by_category(self):
# get the search textbox
self.search_field = self.driver.find_element_by_name("q")
self.search_field.clear()
# enter search keyword and submit
self.search_field.send_keys("phones")
self.search_field.submit()
# get all the anchor elements which have product names
# displayed currently on result page using
# find_elements_by_xpath method
products = self.driver.find_elements_by_xpath("//h2[@class='product-name']/a")
self.assertEqual(2, len(products))
def test_search_by_name(self):
# get the search textbox
self.search_field = self.driver.find_element_by_name("q")
self.search_field.clear()
# enter search keyword and submit
self.search_field.send_keys("salt shaker")
self.search_field.submit()
# get all the anchor elements which have product names
# displayed currently on result page using
# find_elements_by_xpath method
products = self.driver.find_elements_by_xpath("//h2[@class='product-name']/a")
self.assertEqual(1, len(products))
@classmethod
def tearDownClass(cls):
# close the browser window
cls.driver.quit()
if __name__ == '__main__':
unittest.main()
运行测试,你会看到一个 Firefox 实例被创建;两个测试都将使用这个实例。
注意
关于@classmethod装饰器的更多信息,请参阅docs.python.org/2/library/functions.html#classmethod。
断言
unittest库的TestCase类提供了一些实用方法,用于检查应用程序返回的实际值与预期值之间的差异。这些方法以这种方式实现,即它们代表必须为真才能继续执行测试的条件。大致有三种这样的方法,每种方法覆盖特定类型的条件,例如检查等价性、逻辑比较和异常。如果给定的断言通过,测试将继续到下一行代码;否则,测试将立即停止,并生成一个失败消息。
unittest库提供了所有标准的 xUnit asserts方法。以下表格列出了我们将在本书其余部分使用的一些重要方法:
| 方法 | 检查的条件 | 示例使用 |
|---|---|---|
assertEqual(a, b [,msg]) |
a == b | 这些方法检查 a 和 b 是否相等。msg 对象是一个解释失败(如果有的话)的消息。这有助于检查元素、属性等值。例如:assertEqual(element.text,"10") |
assertNotEqual(a, b[,msg]) |
a != b | |
assertTrue(x[,msg])) |
bool(x) 是 True | 这些方法检查给定的表达式是否评估为 True 或 False。例如,为了检查元素是否在页面上显示,我们可以使用以下方法:assertTrue(element.is_dispalyed()) |
assertFalse(x[,msg])) |
bool(x) 是 False | |
assertIsNot(a, b[,msg])) |
a 不是 b | |
assertRaises(exc, fun, *args, **kwds) |
fun(*args, **kwds) 抛出 exc | 这些方法检查测试步骤是否引发了特定的异常。此方法的可能用途是检查 NoSuchElementFoundexception。 |
assertRaisesRegexp(exc, r, fun, *args, **kwds) |
fun(*args, **kwds) 抛出 exc 并且消息与正则表达式 r 匹配 | |
assertAlmostEqual(a, b) |
round(a-b, 7) 等于 0 | 这些方法专门检查数值,并在检查相等性之前将值四舍五入到指定的十进制位数。这有助于解决舍入误差和其他由于浮点运算引起的问题。 |
assertNotAlmostEqual(a, b) |
round(a-b, 7) 不等于 0 | |
assertGreater(a, b) |
a > b | 这些方法与 assertEqual() 方法类似,都是基于逻辑条件设计的。 |
assertGreaterEqual(a, b) |
a >= b | |
assertLess(a, b) |
a < b | |
assertLessEqual(a, b) |
a <= b | |
assertRegexpMatches(s, r) |
r.search(s) | 这些方法检查 regexp 搜索是否与文本匹配。 |
assertNotRegexpMatches(s, r) |
not r.search(s) | |
assertMultiLineEqual(a, b) |
字符串 | 此方法是 assertEqual() 的一个特殊形式,专为多行字符串设计。相等性工作方式与其他字符串相同,但默认的失败消息已优化以显示值之间的差异。 |
assertListEqual(a, b) |
列表 | 此方法检查列表 a 和 b 是否匹配。这对于匹配下拉字段中的选项很有用。 |
fail() |
此方法无条件地使测试失败。这也可以用来创建其他 assert 方法难以轻松工作的自定义条件块。 |
测试套件
使用 unittest 的 TestSuites 功能,我们可以将各种测试收集到逻辑组中,然后将其合并到一个统一的测试套件中,该套件可以通过单个命令运行。这是通过使用 TestSuite、TestLoader 和 TestRunner 类来实现的。
在我们深入 TestSuite 的细节之前,让我们添加一个新的测试来检查示例应用的首页。我们将把这个测试与之前的搜索测试一起汇总到一个单独的测试套件中,如下面的代码所示:
import unittest
from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.by import By
from __builtin__ import classmethod
class HomePageTest(unittest.TestCase):
@classmethod
def setUp(cls):
# create a new Firefox session """
cls.driver = webdriver.Firefox()
cls.driver.implicitly_wait(30)
cls.driver.maximize_window()
# navigate to the application home page """
cls.driver.get("http://demo.magentocommerce.com/")
def test_search_field(self):
# check search field exists on Home page
self.assertTrue(self.is_element_present(By.NAME,"q"))
def test_language_option(self):
# check language options dropdown on Home page
self.assertTrue(self.is_element_present(By.ID,"select-language"))
def test_shopping_cart_empty_message(self):
# check content of My Shopping Cart block on Home page
shopping_cart_icon = \
self.driver.find_element_by_css_selector("div.header-minicart span.icon")
shopping_cart_icon.click()
shopping_cart_status = \
self.driver.find_element_by_css_selector("p.empty").text
self.assertEqual("You have no items in your shopping cart.", shopping_cart_status)
close_button = self.driver.find_element_by_css_selector("div.minicart-wrapper a.close")
close_button.click()
@classmethod
def tearDown(cls):
# close the browser window
cls.driver.quit()
def is_element_present(self, how, what):
"""
Utility method to check presence of an element on page
:params how: By locator type
:params what: locator value
"""
try: self.driver.find_element(by=how, value=what)
except NoSuchElementException, e: return False
return True
if __name__ == '__main__':
unittest.main(verbosity=2)
我们将使用TestSuite类来定义和运行测试套件。我们可以向测试套件添加多个测试用例。除了TestSuite类,我们还需要使用TestLoader和TextTestRunner来创建和运行测试套件,如下面的代码所示:
import unittest
from searchtests import SearchTests
from homepagetests import HomePageTest
# get all tests from SearchProductTest and HomePageTest class
search_tests = unittest.TestLoader().loadTestsFromTestCase(SearchTests)
home_page_tests = unittest.TestLoader().loadTestsFromTestCase(HomePageTest)
# create a test suite combining search_test and home_page_test
smoke_tests = unittest.TestSuite([home_page_tests, search_tests])
# run the suite
unittest.TextTestRunner(verbosity=2).run(smoke_tests)
使用TestLoader类,我们将从指定的测试文件中获取所有测试方法,这些方法将用于创建测试套件。TestRunner类将接受测试套件并运行这些文件中的所有测试。
我们可以使用以下命令运行新的测试套件文件:
python smoketests.py
这将运行SearchProductTest和HomePageTest类中的所有测试,并在控制台生成以下输出:

生成 HTML 测试报告
unittest库在控制台窗口生成测试输出。你可能想生成所有测试执行的报告作为证据,或者将测试结果分发给不同的利益相关者。将控制台日志发送给利益相关者可能不是一个好主意。利益相关者需要格式良好、可以深入细节的总结报告。unittest库没有内置的生成格式良好报告的方式。我们可以使用由 Wai Yip Tung 编写的unittest的HTMLTestRunner扩展。你可以在pypi.python.org/pypi/HTMLTestRunner上找到更多关于HTMLTestRunner的信息,以及下载说明。
注意
HTMLTestRunner扩展包含在本书的源代码中。
我们将在测试中使用HTMLTestRunner生成一个看起来很棒的报告。让我们修改本章中创建的测试套件文件,并添加HTMLTestRunner支持。我们需要创建一个包含实际报告的输出文件,配置HTMLTestRunner选项,并按以下方式运行测试:
import unittest
import HTMLTestRunner
import os
from searchtests import SearchTests
from homepagetests import HomePageTest
# get the directory path to output report file
dir = os.getcwd()
# get all tests from SearchProductTest and HomePageTest class
search_tests = unittest.TestLoader().loadTestsFromTestCase(SearchTests)
home_page_tests = unittest.TestLoader().loadTestsFromTestCase(HomePageTest)
# create a test suite combining search_test and home_page_test
smoke_tests = unittest.TestSuite([home_page_tests, search_tests])
# open the report file
outfile = open(dir + "\SmokeTestReport.html", "w")
# configure HTMLTestRunner options
runner = HTMLTestRunner.HTMLTestRunner(
stream=outfile,
title='Test Report',
description='Smoke Tests'
)
# run the suite using HTMLTestRunner
runner.run(smoke_tests)
运行测试套件;HTMLTestRunner执行所有测试,类似于unittest库的默认测试运行器。执行结束时,它将生成如下截图所示的报告文件:

摘要
在本章中,我们学习了如何使用unittest测试库和 Selenium WebDriver 编写和运行测试。我们使用具有setUp()和tearDown()方法的TestClass类创建了一个测试,并添加了一个断言来检查预期的输出与实际输出。
我们还学习了如何使用unittest库支持的不同类型的断言。我们实现了提供在逻辑组中聚合测试能力的测试套件。最后,我们使用HTMLTestRunner生成 HTML 格式的测试报告,这些报告展示了格式良好的测试结果。
在下一章中,我们将学习如何使用和定义定位器来与页面上显示的各种 HTML 元素进行交互。
第三章:查找元素
网络应用程序以及这些应用程序中的网页是用超文本标记语言(HTML)、层叠样式表(CSS)和 JavaScript 代码混合编写的。基于用户操作,如导航到网站统一资源定位符(URL)或点击提交按钮,浏览器向网络服务器发送请求。网络服务器处理此请求,并将带有 HTML 和相关资源(如 JavaScript、CSS 和图像等)的响应发送回浏览器。从服务器接收到的信息被浏览器用于在页面上渲染带有各种视觉元素(如文本框、按钮、标签、表格、表单、复选框、单选按钮、列表、图像等)的网页。在此过程中,浏览器隐藏了 HTML 代码和相关资源。用户在浏览器窗口中看到一个图形用户界面。页面上使用的各种视觉元素或控件在 Selenium 中被称为WebElements。
在本章中,我们将涵盖以下主题:
-
更深入地了解使用 Selenium WebDriver 查找元素
-
理解如何使用各种浏览器中可用的开发者工具选项来调查和定义定位器,以找到元素
-
探索各种查找元素的方法,包括
ID、Name和Class属性值,并使用 XPath 和 CSS 选择器定义更动态的定位器 -
实现各种
find_element_by方法来找到元素,以便我们可以使用 Selenium WebDriver 自动化这些元素的交互
当我们想要使用 Selenium 自动化浏览器交互时,我们需要告诉 Selenium 如何以编程方式在网页上找到特定元素或一组元素,并模拟对这些元素的用户操作。Selenium 提供了各种选择器或定位器方法,根据我们提供的属性/值标准或脚本中的选择器值来查找元素。
我们如何找到选择器或定位信息?网页是用 HTML、CSS 和 JavaScript 混合编写的。我们可以通过查看网页的 HTML 源代码来获取这些信息。我们需要找到有关我们想要与之交互的元素使用的 HTML 标签、定义的属性以及属性值和页面结构的信息。让我们看看我们正在测试的应用程序中的一个示例表单。以下截图显示了示例应用程序中的搜索字段和搜索(放大镜)图标:

让我们看看为搜索表单编写的 HTML 代码:
<form id="search_mini_form" action="http://demo.magentocommerce.com/catalogsearch/result/" method="get">
<div class="form-search">
<label for="search">Search:</label>
<input id="search" type="text" name="q" value="" class="input-text" maxlength="128" />
<button type="submit" title="Search" class="button"><span><span>Search</span></span></button>
<div id="search_autocomplete" class="search-autocomplete"></div>
<script type="text/javascript">
//<![CDATA[
var searchForm = new Varien.searchForm('search_mini_form', 'search', 'Search entire store here...');
searchForm.initAutocomplete('http://demo.magentocommerce.com/catalogsearch/ajax/suggest/', 'search_autocomplete');
//]]>
</script>
</div>
</form>
每个元素,如搜索文本框和搜索按钮,都是使用<form>标签内的<input>标签实现的,标签则是使用<label>标签实现的。在<script>标签中还有一些 JavaScript 代码。
表示为<input>标签的搜索文本框定义了id、type、name、value、class和maxlength属性:
<input id="search" type="text" name="q" value="" class="input-text" maxlength="128" />
我们可以通过在浏览器窗口上右键单击并从弹出菜单中选择查看页面源代码选项来查看为页面编写的代码。它将在单独的窗口中显示页面的 HTML 和客户端 JavaScript 代码。
小贴士
如果您对 HTML、CSS 和 JavaScript 不太熟悉,那么查看一些有用的教程可能值得,这些教程位于www.w3schools.com/。这些教程将帮助您使用 Selenium WebDriver 支持的不同方式来识别定位器。
使用开发者工具查找定位器
在编写 Selenium 测试时,我们通常会需要查看网页代码,可能需要特殊工具以结构化和易于理解的方式显示信息。好消息是,大多数浏览器都有内置功能或插件来帮助我们。这些工具为我们提供了一种整洁且清晰的方式来理解页面上的元素及其属性定义、DOM 结构、JavaScript 块、CSS 样式属性等。让我们更详细地探索这些工具,看看我们如何使用它们。
使用 Firefox 和 Firebug 插件检查页面和元素
更新版本的 Firefox 提供了内置的方式来分析和页面元素;然而,我们将遵循以下步骤使用功能更强大的 Firebug 插件:
-
您需要在 Firefox 中下载并安装 Firebug 插件,可从
addons.mozilla.org/en-us/firefox/addon/firebug/获取。 -
要使用 Firebug 检查页面,将鼠标移至所需元素上,然后右键单击以打开弹出菜单。
-
从弹出菜单中选择使用 Firebug 检查元素选项。
这将显示 Firebug 部分以及关于页面和所选元素的详细信息,包括以树形格式显示的 HTML 代码,如下面的截图所示:
![使用 Firefox 和 Firebug 插件检查页面和元素]()
-
使用 Firebug,我们还可以通过 Firebug 部分显示的搜索框验证 XPath 或 CSS 选择器。只需输入 XPath 或 CSS 选择器,Firebug 就会突出显示与表达式匹配的元素(s),如下面的截图所示:
![使用 Firefox 和 Firebug 插件检查页面和元素]()
使用 Google Chrome 检查页面和元素
Google Chrome 提供了一个内置功能来分析元素或页面。您可以通过遵循以下步骤检查页面和元素:
-
将鼠标移至页面上的所需元素上,然后右键单击以打开弹出菜单;然后选择检查元素选项。
这将在浏览器中打开开发者工具,显示与 Firebug 类似的信息,如下面的截图所示:
![使用 Google Chrome 检查页面和元素]()
-
与 Firefox 中的 Firebug 类似,我们还可以在 Google Chrome 开发者工具中测试 XPath 和 CSS 选择器。在元素选项卡中按Ctrl + F。这将显示一个搜索框。只需输入 XPath 或 CSS 选择器,Firebug 就会突出显示与表达式匹配的元素(s),如下面的截图所示:
![使用 Google Chrome 检查页面和元素]()
使用 Internet Explorer 检查页面和元素
Microsoft Internet Explorer 也提供了内置功能来分析元素或页面。你可以按照以下步骤检查页面和元素:
-
要打开开发者工具,按F12键。开发者工具部分将在浏览器底部显示。
-
要检查一个元素,点击指针图标,并将鼠标悬停在页面上的目标元素上。开发者工具将以蓝色轮廓突出显示该元素,并在以下截图所示的树中显示 HTML 代码:
![使用 Internet Explorer 检查页面和元素]()
在编写测试时,你会发现这些工具非常有用。其中一些工具还提供了运行 JavaScript 代码进行调试和测试的能力。
使用 Selenium WebDriver 查找元素
我们需要告诉 Selenium 如何查找一个元素,以便它可以模拟一个期望的用户操作,或者查看一个元素的属性或状态,以便我们可以执行检查。例如,如果我们想搜索一个产品,我们需要通过视觉上找到搜索文本框和搜索按钮。我们通过按键盘上的各种键来输入搜索词,然后点击搜索按钮来提交我们的搜索请求。
我们可以使用 Selenium 自动化相同的操作。然而,Selenium 并不像我们那样理解这些字段或按钮的视觉信息。它需要找到搜索文本框和搜索按钮,以模拟程序化的键盘输入和鼠标点击。
Selenium 提供了各种find_element_by方法来在网页上查找元素。这些方法根据提供给它们的准则搜索元素。如果找到匹配的元素,则返回WebElement实例;如果 Selenium 无法找到任何匹配搜索准则的元素,则抛出NoSuchElementException异常。
Selenium 还提供了各种find_elements_by方法来定位多个元素。这些方法根据提供的值搜索并返回匹配的元素列表。
使用查找方法
Selenium 提供了八个find_element_by方法来定位元素。在本节中,我们将详细查看每一个。以下表格列出了find_element_by方法:
| 方法 | 描述 | 参数 | 示例 |
|---|---|---|---|
find_element_by_id(id) |
通过 ID 属性值查找元素 | id:要查找的元素的 ID |
driver.find_element_by_id('search') |
find_element_by_name(name) |
通过名称属性值查找元素的方法 | name: 要查找的元素名称 |
driver.find_element_by_name('q') |
find_element_by_class_name(name) |
通过类属性值查找元素的方法 | name: 要查找的元素类名 |
driver.find_element_by_class_name('input-text') |
find_element_by_tag_name(name) |
通过标签名查找元素的方法 | name: 要查找的元素标签名 |
driver.find_element_by_tag_name('input') |
find_element_by_xpath(xpath) |
使用 XPath 查找元素的方法 | xpath: 要查找的元素 XPath |
driver.find_element_by_xpath('//form[0]/div[0]/input[0]') |
find_element_by_css_selector(css_selector) |
通过 CSS 选择器查找元素的方法 | css_selector: 要查找的元素 CSS 选择器 |
driver.find_element_by_css_selector('#search') |
find_element_by_link_text(link_text) |
通过链接文本查找元素的方法 | link_text: 要查找的元素文本 |
driver.find_element_by_link_text('Log In') |
find_element_by_partial_link_text(link_text) |
通过部分匹配链接文本查找元素的方法 | link_text: 匹配元素部分文本的文本 |
driver.find_element_by_partial_link_text('Log') |
以下表格列出了返回匹配指定条件的元素列表的find_elements_by方法:
| 方法 | 描述 | 参数 | 示例 |
|---|---|---|---|
find_elements_by_id(id_) |
使用 ID 查找多个元素的方法 | id_: 要查找的元素 ID |
driver.find_element_by_id('product') |
find_elements_by_name(name) |
使用名称查找元素的方法 | name: 要查找的元素名称 |
driver.find_elements_by_name('products') |
find_elements_by_class_name(name) |
使用类名查找元素的方法 | name: 要查找的元素类名 |
driver.find_elements_by_class_name('foo') |
find_elements_by_tag_name(name) |
使用标签名查找元素的方法 | name: 要查找的元素标签名 |
driver.find_elements_by_tag_name('a') |
find_elements_by_xpath(xpath) |
通过 XPath 查找多个元素的方法 | xpath: 要查找的元素 XPath |
driver.find_elements_by_xpath("//div[contains(@class, 'lists')]") |
find_elements_by_css_selector(css_selector) |
使用 CSS 选择器查找元素的方法 | css_selector: 要查找的元素 CSS 选择器 |
driver.find_element_by_css_selector('.input-class') |
find_elements_by_link_text(text) |
使用链接文本查找元素的方法 | text: 要查找的元素文本 |
driver.find_elements_by_link_text('Log In') |
find_elements_by_partial_link_text(link_text) |
此方法通过元素链接文本的部分匹配来查找元素 | link_text: 匹配元素文本的一部分的文本 |
driver.find_element_by_partial_link_text('Add to,') |
使用 ID 属性查找元素
使用 ID 查找页面上的元素是最受欢迎的方法。find_element_by_id()和find_elements_by_id()方法返回具有匹配 ID 属性值的元素或一组元素。
find_element_by_id()方法返回第一个具有匹配 ID 属性值的元素。如果没有找到具有匹配 ID 属性值的元素,将引发NoSuchElementException。
让我们尝试从以下截图所示的示例应用程序中找到搜索文本框:

下面是具有 ID 属性值定义为search的搜索文本框的 HTML 代码:
<input id="search" type="text" name="q" value="" class="input-text" maxlength="128" autocomplete="off">
下面是一个使用find_element_by_id()方法查找搜索文本框并检查其maxlength属性的测试示例。我们将传递 ID 属性值search到find_element_by_id()方法:
def test_search_text_field_max_length(self):
# get the search textbox
search_field = self.driver.find_element_by_id("search")
# check maxlength attribute is set to 128
self.assertEqual("128", search_field.get_attribute("maxlength"))
find_elements_by_id()方法返回所有具有相同 ID 属性值的元素。
使用名称属性查找元素
通过其名称属性值查找元素是另一种受欢迎的方法。find_element_by_name()和find_elements_by_name()方法返回具有匹配名称属性值的元素(s)。如果没有找到具有匹配名称属性值的元素,将引发NoSuchElementException。
在前面的示例中,我们可以使用名称属性值而不是 ID 属性值以以下方式找到搜索文本框:
# get the search textbox
self.search_field = self.driver.find_element_by_name("q")
find_elements_by_name()方法返回所有具有相同名称属性值的元素。
使用类名查找元素
除了使用 ID 和名称属性外,我们还可以使用类属性来查找元素。类属性用于将 CSS 应用于元素。find_element_by_class_name()和find_elements_by_class_name()方法返回具有匹配类属性值的元素(s)。如果没有找到具有匹配名称属性值的元素,将引发NoSuchElementException。
小贴士
使用 ID、名称或类属性查找元素是查找元素最受欢迎和最快的方法。Selenium WebDriver 还提供了一套方法,当这些方法不足以查找元素时。我们将在本章后面看到这些方法。
让我们使用find_element_by_class_name()方法通过其类属性值找到以下截图显示的搜索按钮:

搜索按钮(放大镜图标)是通过具有类型、标题和类属性值的<button>元素实现的,如下面的 HTML 代码所示:
<button type="submit" title="Search" class="button"><span><span>Search</span></span></button>
让我们创建一个测试,使用其类属性值查找搜索按钮元素,并检查它是否启用,如下面的代码所示:
def test_search_button_enabled(self):
# get Search button
search_button = self.driver.find_element_by_class_name("button")
# check Search button is enabled
self.assertTrue(search_button.is_enabled())
find_elements_by_class_name() 方法返回所有具有相同类名属性值的元素。
使用标签名查找元素
find_element_by_tag_name() 和 find_elements_by_tag_name() 方法通过元素的 HTML 标签名来查找元素。这些方法与 JavaScript 中的 getElementsByTagName() DOM 方法类似。如果找不到匹配的标签名的元素,将抛出 NoSuchElementException 异常。
当我们想要通过标签名查找元素时,这些方法很有用。例如,为了找到表格中的所有 <tr> 标签以确定行数。
示例应用的首页显示了促销横幅图像,如下面的截图所示:

这些横幅是通过在 <ul> 或无序列表标签内使用 <img> 或图像标签实现的,如下面的 HTML 代码所示:
<ul class="promos">
<li>
<a href="http://demo.magentocommerce.com/home-decor.html">
<img src="img/homepage-three-column-promo-01B.png" alt="Physical & Virtual Gift Cards">
</a>
</li>
<li>
<a href="http://demo.magentocommerce.com/vip.html">
<img src="img/homepage-three-column-promo-02.png" alt="Shop Private Sales - Members Only">
</a>
</li>
<li>
<a href="http://demo.magentocommerce.com/accessories/bags-luggage.html">
<img src="img/homepage-three-column-promo-03.png" alt="Travel Gear for Every Occasion">
</a>
</li>
</ul>
我们将使用 find_elements_by_tag_name() 方法获取所有图像。在这个例子中,我们将首先使用 find_element_by_class_name() 方法找到实现为 <ul> 或无序列表的横幅列表,然后通过在横幅列表上调用 find_elements_by_tag_name() 方法来获取所有 <img> 或图像元素。
def test_count_of_promo_banners_images(self):
# get promo banner list
banner_list = self.driver.find_element_by_class_name("promos")
# get images from the banner_list
banners = banner_list.find_elements_by_tag_name("img")
# check there are 20 tags displayed on the page
self.assertEqual(2, len(banners))
使用 XPath 查找元素
XPath 是一种查询语言,用于在 XML 文档中搜索和定位节点。所有主要的网络浏览器都支持 XPath。Selenium 可以利用并使用强大的 XPath 查询来查找网页上的元素。
使用 XPath 的一个优点是,当我们无法为元素找到合适的 ID、name 或 class 属性值时。我们可以使用 XPath 来在绝对术语中查找元素,或者相对于具有 ID 或 name 属性的元素查找。我们还可以使用 XPath 查询中定义的除 ID、name 或 class 之外的属性。我们还可以使用 XPath 函数如 starts-with()、contains() 和 ends-with() 来对属性值进行部分检查以查找元素。
小贴士
要了解更多关于 XPath 的信息,请访问 www.w3schools.com/Xpath/ 和 www.zvon.org/comp/r/tut-XPath_1.html。
你可以在 Selenium Testing Tools Cookbook,Packt Publishing 一书中了解更多关于 XPath 定位符的信息。
find_element_by_xpath() 和 find_elements_by_xpath() 方法返回通过指定的 XPath 查询找到的元素。例如,我们可以检查主页上显示的促销横幅是否按预期工作,并且我们可以使用这些图像打开促销页面,如下面的截图所示:

以下是 Shop Private Sales 标签定义为 <img> 标签的方式。该图像没有定义 ID、name 或 class 属性。此外,我们无法使用 find_by_tag_name() 方法,因为页面上定义了多个 <img> 标签。然而,通过查看以下 HTML 代码,我们可以使用 alt 属性获取 <img> 标签:
<ul class="promos">
...
<li>
<a href="http://demo.magentocommerce.com/vip.html">
<img src="img/homepage-three-column-promo-02.png" alt="Shop Private Sales - Members Only">
</a>
</li>
...
</ul>
让我们创建一个使用 find_element_by_xpath() 方法的测试。我们使用相对 XPath 查询,通过其 alt 属性来查找这个 <img> 标签(这就是我们如何在 XPath 查询中使用 ID、name 和 class 属性以及其他属性,如 title、type、value、alt 等):
def test_vip_promo(self):
# get vip promo image
vip_promo = self.driver.\
find_element_by_xpath("//img[@alt='Shop Private Sales - Members Only']")
# check vip promo logo is displayed on home page
self.assertTrue(vip_promo.is_displayed())
# click on vip promo images to open the page
vip_promo.click()
# check page title
self.assertEqual("VIP", self.driver.title)
find_elements_by_xpath() 方法返回所有与 XPath 查询匹配的元素。
使用 CSS 选择器查找元素
CSS 是一种样式表语言,由网页设计师用来描述 HTML 文档的外观和感觉。CSS 用于定义可以应用于元素的样式类,以便进行格式化。CSS 选择器用于根据元素的属性(如 ID、类、类型、属性或值等)查找 HTML 元素,以便应用定义的 CSS 规则。
与 XPath 类似,Selenium 可以利用并使用 CSS 选择器来查找网页上的元素。要了解更多关于 CSS 选择器的信息,请访问 www.w3schools.com/css/css_selectors.asp 和 www.w3.org/TR/CSS2/selector.html。
find_element_by_css_selector() 和 find_elements_by_css_selector() 方法返回由指定的 CSS 选择器找到的元素。
在示例应用的首页上,我们可以看到购物车图标。当我们点击图标时,我们可以看到购物车。当购物车中没有添加任何项目时,应该显示一条消息说 您的购物车中没有商品,如图所示:

这在以下 HTML 代码中实现:
<div class="minicart-wrapper">
<p class="block-subtitle">
Recently added item(s)
<a class="close skip-link-close" href="#" title="Close">×</a>
</p>
<p class="empty">You have no items in your shopping cart.</p>
</div>
让我们创建一个测试来验证这个消息。我们将使用 CSS 选择器来查找购物车图标,点击它,然后找到 <p> 或段落元素中实现的购物车消息:
def test_shopping_cart_status(self):
# check content of My Shopping Cart block on Home page
# get the Shopping cart icon and click to open the # Shopping Cart section
shopping_cart_icon = self.driver.\
find_element_by_css_selector("div.header-minicart span.icon")
shopping_cart_icon.click()
# get the shopping cart status
shopping_cart_status = self.driver.\
find_element_by_css_selector("p.empty").text
self.assertEqual("You have no items in your shopping cart.", shopping_cart_status)
# close the shopping cart section
close_button = self.driver.\
find_element_by_css_selector("div.minicart-wrapper a.close")
close_button.click()
在这个例子中,我们使用了元素标签和类名。例如,要获取购物车图标,我们使用了以下选择器:
shopping_cart_icon = self.driver.\
find_element_by_css_selector("div.header-minicart span.icon")
这将首先找到一个具有 header_minicart 类名的 <div> 元素,然后找到该 div 下的 <span> 元素,其类名为 icon。
你可以在 Selenium 测试工具手册,Packt 出版 中找到更多关于 CSS 选择器的信息。
查找链接
find_element_by_link_text() 和 find_elements_by_link_text() 方法通过链接显示的文本来查找链接。例如:
-
要获取主页上显示的 Account 链接,如图所示,我们可以使用
find_element_by_link_text()方法:![查找链接]()
-
下面是账户链接的 HTML 代码,它被实现为
<a>(或锚标签)和<span>标签,并带有文本:<a href="#header-account" class="skip-link skip-account"> <span class="icon"></span> <span class="label">Account</span> </a> -
让我们创建一个测试,使用文本定位账户链接,并检查它是否显示:
def test_my_account_link_is_displayed(self): # get the Account link account_link = self.driver.find_element_by_link_text("ACCOUNT") # check My Account link is displayed/visible in # the Home page footer self.assertTrue(account_link.is_displayed())
find_elements_by_link_text() 方法获取所有具有匹配链接文本的链接元素。
使用部分文本查找链接
find_element_by_partial_link_text() 和 find_elements_by_partial_link_text() 方法通过部分文本查找链接。这些方法在需要使用部分文本值查找链接时非常有用。以下步骤可作为示例:
-
在应用程序的首页上,显示两个链接以打开账户页面:一个在页眉部分,文本为
Account,另一个在页脚部分,文本为My Account。 -
让我们使用
find_elements_by_partial_link_text()方法使用Account文本查找这些链接,并检查页面上是否有两个这样的链接可用:def test_account_links(self): # get the all the links with Account text in it account_links = self.driver.\ find_elements_by_partial_link_text("ACCOUNT") # check Account and My Account link is displayed/visible in the Home page footer self.assertTrue(2, len(account_links))
使用查找方法汇总所有测试
在前面的章节中,我们看到了各种 find_element_by 方法及其示例。现在让我们将这些示例汇总到一个测试中。
-
创建一个新的
homepagetest.py文件,并将我们之前创建的所有测试复制进去,如下代码所示:import unittest from selenium import webdriver class HomePageTest(unittest.TestCase): @classmethod def setUpClass(cls): # create a new Firefox session cls.driver = webdriver.Firefox() cls.driver.implicitly_wait(30) cls.driver.maximize_window() # navigate to the application home page cls.driver.get('http://demo.magentocommerce.com/') def test_search_text_field_max_length(self): # get the search textbox search_field = self.driver.find_element_by_id("search") # check maxlength attribute is set to 128 self.assertEqual("128", search_field.get_attribute ("maxlength")) def test_search_button_enabled(self): # get Search button search_button = self.driver.find_element_by_class_name("button") # check Search button is enabled self.assertTrue(search_button.is_enabled()) def test_my_account_link_is_displayed(self): # get the Account link account_link = self.driver.find_element_by_link_text("ACCOUNT") # check My Account link is displayed/visible in # the Home page footer self.assertTrue(account_link.is_displayed()) def test_account_links(self): # get the all the links with Account text in it account_links = self.driver.\ find_elements_by_partial_link_text("ACCOUNT") # check Account and My Account link is # displayed/visible in the Home page footer self.assertTrue(2, len(account_links)) def test_count_of_promo_banners_images(self): # get promo banner list banner_list = self.driver.find_element_by_class_name("promos") # get images from the banner_list banners = banner_list.find_elements_by_tag_name("img") # check there are 3 banners displayed on the page self.assertEqual(2, len(banners)) def test_vip_promo(self): # get vip promo image vip_promo = self.driver.\ find_element_by_xpath("//img[@alt='Shop Private Sales - Members Only']") # check vip promo logo is displayed on home page self.assertTrue(vip_promo.is_displayed()) # click on vip promo images to open the page vip_promo.click() # check page title self.assertEqual("VIP", self.driver.title) def test_shopping_cart_status(self): # check content of My Shopping Cart block # on Home page # get the Shopping cart icon and click to # open the Shopping Cart section shopping_cart_icon = self.driver.\ find_element_by_css_selector("div.header-minicart span.icon") shopping_cart_icon.click() # get the shopping cart status shopping_cart_status = self.driver.\ find_element_by_css_selector("p.empty").text self.assertEqual("You have no items in your shopping cart.", shopping_cart_status) # close the shopping cart section close_button = self.driver.\ find_element_by_css_selector("div.minicart-wrapper a.close") close_button.click() @classmethod def tearDownClass(cls): # close the browser window cls.driver.quit() if __name__ == '__main__': unittest.main(verbosity=2) -
让我们使用以下命令通过命令行执行所有测试:
python homepagetest.py -
运行测试后,
unittest显示运行了七个测试,并且所有测试都通过了,状态为 OK,如下截图所示:![使用查找方法汇总所有测试]()
概述
在本章中,你学习了 Selenium 最重要的功能之一,即如何在网页上查找元素以模拟用户操作。
我们研究了各种 find_element_by_ 方法,用于通过 ID、name、class name 属性、标签名、XPath、CSS 选择器查找元素,以及使用链接文本和部分链接文本查找链接。
我们使用各种 find_element_by 方法实现了测试,以了解我们可以使用的各种查找元素策略。
本章将是后续章节的基础,这些章节将深入探讨使用 Selenium API 进行用户交互。
在下一章中,你将学习如何使用 Selenium WebDriver 函数与各种 HTML 元素交互,并执行诸如在文本框中输入值、点击按钮、选择下拉选项、处理 JavaScript 警告以及与框架和窗口一起工作等操作。
第四章:使用 Selenium Python API 进行元素交互
网络应用程序使用 HTML 表单将数据发送到服务器。HTML 表单包含输入元素,如文本字段、复选框、单选按钮、提交按钮等。表单还可以包含选择列表、文本区域、字段集、图例和标签元素。
一个典型的网络应用程序需要你填写许多表单,从注册用户或搜索产品开始。表单被 HTML <form> 标签包围。此标签指定提交数据的方法,是使用 GET 还是 POST 方法,以及表单中输入的数据应在服务器上的哪个地址提交。
在本章中,我们将介绍以下主题:
-
深入了解
WebDriver和WebElement类 -
实现使用
WebDriver和WebElement类的各种方法和属性与应用程序交互的测试 -
使用
Select类自动化下拉列表和列表 -
自动化 JavaScript 警报和浏览器导航
HTML 表单的元素
HTML 表单由不同类型的元素组成,包括 <form>、<input>、<button> 和 <label>,如下所示。网络开发人员使用这些元素来设计网页以显示数据或从用户那里接收数据。开发人员编写网页的 HTML 代码以定义这些元素。然而,作为最终用户,我们把这些元素看作是 图形用户界面 (GUI) 控件,如文本框、标签、按钮、复选框和单选按钮。HTML 代码对最终用户是隐藏的。

Selenium WebDriver 提供了广泛的支持,用于自动化与这些元素的交互以及检查应用程序的功能。
理解 WebDriver 类
WebDriver 类提供了一系列用于浏览器交互的属性或属性。我们可以使用 WebDriver 类的属性和方法与浏览器窗口、警报、框架和弹出窗口进行交互。它还提供了自动化浏览器导航、访问 cookies、捕获屏幕截图等功能。在本章中,我们将探讨 WebDriver 类的一些最重要的功能。以下表格涵盖了本书其余部分将使用的一些最重要的属性和方法。
注意
要查看属性和方法的完整列表,请访问 selenium.googlecode.com/git/docs/api/py/webdriver_remote/selenium.webdriver.remote.webdriver.html#module-selenium.webdriver.remote.webdriver.
WebDriver 类的属性
WebDriver 类实现了以下属性以访问浏览器:
| 属性/属性 | 描述 | 示例 |
|---|---|---|
current_url |
获取浏览器中当前显示的页面的 URL | driver.current_url |
current_window_handle |
获取当前窗口的句柄 | driver.current_window_handle |
name |
获取此实例的底层浏览器的名称 | driver.name |
orientation |
获取设备的当前方向 | driver.orientation |
page_source |
获取当前页面的源代码 | driver.page_source |
title |
获取当前页面的标题 | driver.title |
window_handles |
获取当前会话中所有窗口的句柄 | driver.window_handles |
WebDriver 类的方法
WebDriver 类实现了各种方法来与浏览器窗口、网页以及这些页面上的元素进行交互。以下是重要方法的列表:
| 方法 | 描述 | 参数 | 示例 |
|---|---|---|---|
back() |
在当前会话的浏览器历史记录中后退一步。 | driver.back() |
|
close() |
关闭当前浏览器窗口。 | driver.close() |
|
forward() |
在当前会话的浏览器历史记录中向前一步。 | driver.forward() |
|
get(url) |
在当前浏览器会话中导航并加载一个网页。 | url 是要导航的网站或网页的地址 |
driver.get("http://www.google.com") |
maximize_window() |
最大化当前浏览器窗口。 | driver.maximize_window() |
|
quit() |
退出驱动程序并关闭所有相关窗口。 | driver.quit() |
|
refresh() |
刷新浏览器中显示的当前页面。 | driver.refresh() |
|
switch_to.active_element() |
返回具有焦点的元素或如果没有其他元素具有焦点,则为 body。 | driver.switch_to_active_element() |
|
Switch.to_alert() |
将焦点切换到页面上的一个警告框。 | driver.switch_to_alert() |
|
switch_to.default_content() |
将焦点切换到默认框架。 | driver.switch_to_default_content() |
|
switch_to.frame(frame_reference) |
通过索引、名称或网页元素将焦点切换到指定的框架。此方法也适用于 IFRAMES。 |
frame_reference:这是要切换到的窗口的名称,一个表示索引的整数,或一个要切换到的框架的网页元素 |
driver.switch_to_frame('frame_name') |
switch_to.window(window_name) |
将焦点切换到指定的窗口。 | window_name 是要切换到的窗口的名称或窗口句柄。 |
driver.switch_to_window('main') |
implicitly_wait(time_to_wait) |
这将设置一个粘性超时,隐式等待元素被找到或命令完成。此方法在每个会话中只需要调用一次。要设置对 execute_async_script 的调用超时,请参阅 set_script_timeout。 |
time_to_wait 是等待时长(以秒为单位)。 |
|
set_page_load_timeout(time_to_wait) |
这将设置等待页面加载完成的时长。 | time_to_wait 是等待时长(以秒为单位)。 |
driver.set_page_load_timeout(30) |
set_script_timeout(time_to_wait) |
这将在抛出错误之前设置脚本在 execute_async_script 调用期间应等待的时长。 |
time_to_wait 是等待时长(以秒为单位)。 |
driver.set_script_timeout(30) |
理解 WebElement 类
我们可以使用 WebElement 类与网页上的元素进行交互。我们可以使用 WebElement 类与文本框、文本区域、按钮、单选按钮、复选框、表格、表格行、表格单元格、div 等元素进行交互。
WebElemet 类提供了一系列属性、方法来与元素交互。下表涵盖了本书其余部分将使用的一些最重要的属性和方法。要获取属性和方法的完整列表,请访问 selenium.googlecode.com/git/docs/api/py/webdriver_remote/selenium.webdriver.remote.webelement.html#module-selenium.webdriver.remote.webelement。
WebElement 类的属性
WebElement 类实现了以下属性:
| 属性/属性 | 描述 | 示例 |
| --- | --- | --- | --- |
| size | 这将获取元素的大小 | element.size |
| tag_name | 这将获取此元素的 HTML 标签名 | element.tag_name |
| text | 这将获取元素的文本 | element.text |
WebElement 类的方法
WebElement 类实现了以下方法:
| 方法 | 描述 | 参数 | 示例 |
|---|---|---|---|
clear() |
这将清除文本框或文本区域元素的内容。 | element.clear() |
|
click() |
这将点击元素。 | element.click() |
|
get_attribute(name) |
这将从元素中获取属性值。 | name 是属性名。 |
element.get_attribute("value") 或 element.get_attribute("maxlength") |
is_displayed() |
这将检查元素是否对用户可见。 | element.is_displayed() |
|
is_enabled() |
这将检查元素是否启用。 | element.is_enabled() |
|
is_selected() |
这将检查元素是否被选中。此方法用于检查单选按钮或复选框的选择。 | element.is_selected() |
|
send_keys(*value) |
这将模拟在元素中输入。 | Value 是用于输入或设置表单字段的字符串。 |
element.send_keys("foo") |
submit() |
这将提交一个表单。如果你在元素上调用此方法,它将提交父表单。 | element.submit() |
|
value_of_css_property(property_name) |
这将获取 CSS 属性的值。 | property_name 是 CSS 属性的名称。 |
element.value_of_css_property("background-color") |
与表单、文本框、复选框和单选按钮一起工作
我们可以使用 WebElement 类来自动化各种 HTML 控件的交互,例如在文本框中输入文本、点击按钮、在复选框或单选按钮中选择选项、从元素获取文本和属性值等。
我们在本章前面看到了 WebElement 类提供的属性和方法。在本节中,我们将使用 WebElement 类及其属性和方法来自动化示例应用程序的创建账户功能。因此,让我们创建一个测试来验证在示例应用程序中创建新用户账户。我们将填写以下截图所示的表单并提交我们的请求。然后系统应该创建一个新的用户账户:

如前述截图所示,我们需要填写五个文本框并选择一个复选框来订阅通讯。
-
首先,根据以下代码创建一个新的测试类
RegisterNewUser:from selenium import webdriver import unittest class RegisterNewUser(unittest.TestCase): def setUp(self): self.driver = webdriver.Firefox self.driver.implicitly_wait(30) self.driver.maximize_window() # navigate to the application home page self.driver.get("http://demo.magentocommerce.com/") -
向
RegisterNewUser类添加一个测试,test_register_new_user(self)。 -
要打开登录页面,我们需要点击主页上的“登录”链接。以下是对“登录”按钮的代码:
def test_register_new_user(self): driver = self.driver # click on Log In link to open Login page driver.find_element_by_link_text("Log In").click()
检查元素是否显示和启用
is_displayed() 方法返回 TRUE 如果元素在屏幕上可见(可见属性设置为 TRUE),否则它将返回 FALSE。同样,is_enabled() 方法返回 TRUE 如果元素被启用,即用户可以执行点击、输入文本等操作。如果元素被禁用,此方法返回 FALSE。
客户登录页面有选项让注册用户登录系统或为新用户创建账户。我们可以使用 WebElement 类的 is_displayed() 和 is_enabled() 方法检查用户是否可以看到并启用“创建账户”按钮。将以下代码中给出的步骤添加到测试中:
# get the Create Account button
create_account_button = driver.find_element_by_xpath("//button[@title='Create an Account']")
# check Create Account button is displayed and enabled
self.assertTrue(create_account_button.is_displayed() and
create_account_button.is_enabled())
我们想测试“创建账户”功能,因此让我们点击“创建账户”按钮。这将显示“创建新客户账户”页面。我们可以使用 WebDriver 类的 title 属性来检查页面标题是否与我们预期的相符,如下面的代码所示:
# click on Create Account button. This will display # new account
create_account_button.click()
# check title
self.assertEquals("Create New Customer Account - Magento Commerce Demo Store", driver.title)
在“创建新客户账户”页面上,使用以下方式使用 find_element_by_* 方法定位所有元素:
# get all the fields from Create an Account form
first_name = driver.find_element_by_id("firstname")
last_name = driver.find_element_by_id("lastname")
email_address = driver.find_element_by_id("email_address")
news_letter_subscription = driver.find_element_by_id("is_subscribed")
password = driver.find_element_by_id("password")
confirm_password = driver.find_element_by_id("confirmation")
submit_button = driver.find_element_by_xpath("//button[@title='Submit']")
查找元素属性值
可以使用get_attribute()方法获取为元素定义的属性值。例如,有一个测试说,firstname和lastname文本框的最大长度应该定义为 255 个字符。以下是firstname文本框的 HTML 代码,其中定义了maxlength属性,其值为255,如下面的代码所示:
<input type="text" id="firstname" name="firstname" value="" title="First Name" maxlength="255" class="input-text required-entry">
我们可以使用WebElement类的get_attribute()方法通过以下步骤断言maxlength属性:
-
我们需要将属性的名称作为参数传递给
get_attribute()方法:# check maxlength of first name and last name textbox self.assertEqual("255", first_name.get_attribute("maxlength")) self.assertEqual("255", last_name.get_attribute("maxlength")) -
将以下步骤添加到测试中,以确保所有字段都显示并启用供用户使用:
# check all fields are enabled self.assertTrue(first_name.is_enabled() and last_name.is_enabled() and email_address.is_enabled() and news_letter_subscription.is_enabled() and password.is_enabled() and confirm_password.is_enabled() and submit_button.is_enabled())
使用is_selected()方法
is_selected()方法与复选框和单选按钮一起工作。我们可以使用此方法来了解复选框或单选按钮是否被选中。
通过使用WebElement类的click()方法执行点击操作来选择复选框或单选按钮。在这个例子中,检查默认情况下注册新闻通讯复选框是否未选中,如下面的代码所示:
# check Sign Up for Newsletter is unchecked
self.assertFalse(news_letter_subscription.is_selected())
使用clear()和send_keys()方法
clear()和send_keys()方法是WebElement类对文本框或文本区域适用的,它们用于清除元素的文本内容,并像真实用户在键盘上输入一样发送文本值。send_keys()方法将要在元素中输入的文本作为参数。让我们考虑以下步骤:
-
让我们将给定的代码添加到使用
send_keys()方法填充字段:# fill out all the fields first_name.send_keys("Test") last_name.send_keys("User1") news_letter_subscription.click() email_address.send_keys("TestUser_150214_2200@example.com") password.send_keys("tester") confirm_password.send_keys("tester") -
最后,通过检查欢迎信息来确认用户是否已创建。我们可以使用
WebElement类的text属性从元素中获取文本:# check new user is registered self.assertEqual("Hello, Test User1!", driver.find_element_by_css_selector("p.hello > strong").text) self.assertTrue(driver.find_element_by_link_text("Log Out").is_displayed()) -
这里是
创建账户功能的完整测试。运行此测试,你将看到创建账户页面上所有的操作:from selenium import webdriver from time import gmtime, strftime import unittest class RegisterNewUser(unittest.TestCase): def setUp(self): self.driver = webdriver.Firefox() self.driver.implicitly_wait(30) self.driver.maximize_window() # navigate to the application home page self.driver.get("http://demo.magentocommerce.com/") def test_register_new_user(self): driver = self.driver # click on Log In link to open Login page driver.find_element_by_link_text("ACCOUNT").click() driver.find_element_by_link_text("My Account").click() # get the Create Account button create_account_button = \ driver.find_element_by_link_text("CREATE AN ACCOUNT") # check Create Account button is displayed # and enabled self.assertTrue(create_account_button.is_displayed() and create_account_button.is_enabled()) # click on Create Account button. This will # display new account create_account_button.click() # check title self.assertEquals("Create New Customer Account", driver.title) # get all the fields from Create an Account form first_name = driver.find_element_by_id("firstname") last_name = driver.find_element_by_id("lastname") email_address = driver.find_element_by_id("email_address") password = driver.find_element_by_id("password") confirm_password = driver.find_element_by_id("confirmation") news_letter_subscription = driver.find_element_by_id("is_subscribed") submit_button = driver.\ find_element_by_xpath("//button[@title='Register']") # check maxlength of first name and # last name textbox self.assertEqual("255", first_name.get_attribute("maxlength")) self.assertEqual("255", last_name.get_attribute("maxlength")) # check all fields are enabled self.assertTrue(first_name.is_enabled() and last_name.is_enabled() and email_address.is_enabled() and news_letter_subscription.is_enabled() and password.is_enabled() and confirm_password.is_enabled() and submit_button.is_enabled()) # check Sign Up for Newsletter is unchecked self.assertFalse(news_letter_subscription.is_selected()) user_name = "user_" + strftime("%Y%m%d%H%M%S", gmtime()) # fill out all the fields first_name.send_keys("Test") last_name.send_keys(user_name) news_letter_subscription.click() email_address.send_keys(user_name + "@example.com") password.send_keys("tester") confirm_password.send_keys("tester") # click Submit button to submit the form submit_button.click() # check new user is registered self.assertEqual("Hello, Test " + user_name + "!", driver.find_element_by_css_selector("p.hello > strong").text) driver.find_element_by_link_text("ACCOUNT").click() self.assertTrue(driver.find_element_by_link_text("Log Out").is_displayed()) def tearDown(self): self.driver.quit() if __name__ == "__main__": unittest.main(verbosity=2)
与下拉列表和列表一起工作
Selenium WebDriver 提供了一个特殊的Select类,用于与网页上的列表和下拉列表进行交互。例如,在演示应用程序中,你可以看到一个下拉列表来选择商店的语言。你可以选择并设置商店的语言,如下面的截图所示:

下拉列表或列表在 HTML 中通过<select>元素实现。选项或选择通过<select>元素内的<options>元素实现,如下面的 HTML 代码所示:
<select id="select-language" title="Your Language" onchange="window.location.href=this.value">
<option value="http://demo.magentocommerce.com/?___store=default&___from_store=default" selected="selected">English</option>
<option value="http://demo.magentocommerce.com/?___store=french&___from_store=default">French</option>
<option value="http://demo.magentocommerce.com/?___store=german&___from_store=default">German</option>
</select>
每个<option>元素都有其属性值定义和用户将看到的文本。例如,在以下代码中,<option>值被设置为商店的 URL,文本被设置为语言,即法语:
<option value="http://demo.magentocommerce.com/customer/account/create/?___store=french&___from_store=default">French</option>
理解Select类
Select类是 Selenium 中的一个特殊类,用于与下拉列表或列表进行交互。它提供了各种方法和属性以供用户交互。
以下表格列出了 Select 类的所有属性和方法。您可以在selenium.googlecode.com/git/docs/api/py/webdriver_support/selenium.webdriver.support.select.html#module-selenium.webdriver.support.select 找到类似的信息。
Select 类的属性
Select 类实现了以下属性:
| 属性/属性 | 描述 | 示例 |
|---|---|---|
all_selected_options |
这将获取属于下拉列表或列表的所有已选择选项的列表 | select_element.all_selected_options |
first_selected_option |
这将获取下拉列表或列表中第一个已选择/当前选中的选项 | select_element.first_selected_option |
options |
这将获取下拉列表或列表中所有选项的列表 | select_element.options |
Select 类的方法
Select 类实现了以下方法:
| 方法 | 描述 | 参数 | 示例 |
|---|---|---|---|
deselect_all() |
这将清除多选下拉列表或列表中的所有已选择条目 | select_element.deselect_all() |
|
deselect_by_index(index) |
这将取消选择下拉列表或列表中给定索引的选项 | index 是要取消选择的选项的索引 |
select_element.deselect_by_index(1) |
deselect_by_value(value) |
这将取消选择所有值与参数匹配的选项,从下拉列表或列表中 | value 是要取消选择的选项的值属性 |
select_element.deselect_by_value("foo") |
deselect_by_visible_text(text) |
这将取消选择所有显示文本与参数匹配的选项,从下拉列表或列表中 | text 是要取消选择的选项的文本值 |
select_element.deselect_by_visible_text("bar") |
select_by_index(index) |
这将选择下拉列表或列表中给定索引的选项 | index 是要选择的选项的索引 |
select_element.select_by_index(1) |
select_by_value(value) |
这将选择所有值与参数匹配的选项,从下拉列表或列表中 | value 是要选择的选项的值属性 |
select_element.select_by_value("foo") |
select_by_visible_text(text) |
这将选择所有显示与参数匹配的文本的选项,从下拉列表或列表中 | text 是要选择的选项的文本值 |
select_element.select_by_visible_text("bar") |
让我们探索这些属性和方法,以测试演示应用程序的语言功能。我们将向之前章节中构建的主页测试添加一个新的测试。此测试检查用户是否有八种语言可供选择。我们将使用 options 属性首先检查选项数量,然后获取列表中每个选项的文本,并检查该列表与预期的选项列表,如下面的代码所示:
def test_language_options(self):
# list of expected values in Language dropdown
exp_options = ["ENGLISH", "FRENCH", "GERMAN"]
# empty list for capturing actual options displayed # in the dropdown
act_options = []
# get the Your language dropdown as instance of Select class
select_language = \
Select(self.driver.find_element_by_id("select-language"))
# check number of options in dropdown
self.assertEqual(2, len(select_language.options))
# get options in a list
for option in select_language.options:
act_options.append(option.text)
# check expected options list with actual options list
self.assertListEqual(exp_options, act_options)
# check default selected option is English
self.assertEqual("ENGLISH", select_language.first_selected_option.text)
# select an option using select_by_visible text
select_language.select_by_visible_text("German")
# check store is now German
self.assertTrue("store=german" in self.driver.current_url)
# changing language will refresh the page,
# we need to get find language dropdown once again
select_language = \
Select(self.driver.find_element_by_id("select-language"))
select_language.select_by_index(0)
options 属性返回为下拉列表或列表定义的所有 <option> 元素。选项列表中的每个项目都是 WebElement 类的实例。
我们还可以使用 first_selected_option 属性来检查默认/当前选定的选项。
注意
all_selected_options 属性用于测试多选下拉列表或列表。
最后,选择一个项目,并使用以下代码检查是否根据语言选择更改了存储 URL:
# select an option using select_by_visible text
select_language.select_by_visible_text("German")
# check store is now German
self.assertTrue("store=german" in self.driver.current_url)
可以通过索引(即列表中的位置)、值属性或可见文本来选择选项。Select 类提供了各种 select_ 方法来选择选项。在这个例子中,我们使用了 select_by_visible_text() 方法来选择一个选项。我们还可以使用各种 deselect_ 方法来取消选择选项。
与警告和弹出窗口一起工作
开发者使用 JavaScript 警告或模式对话框来通知用户关于验证错误、警告、对操作做出响应、接受输入值等。在本节中,我们将了解如何使用 Selenium 处理警告和弹出窗口。
理解 Alert 类
Selenium WebDriver 提供了 Alert 类来处理 JavaScript 警告。Alert 类包含接受、关闭、输入和从警告中获取文本的方法。
Alert 类的属性
Alert 类实现了以下属性:
| 属性/属性 | 描述 | 示例 |
|---|---|---|
text |
从警告窗口获取文本 | alert.text |
Alert 类的方法
Alert 类实现了以下方法:
| 方法 | 描述 | 参数 | 示例 |
|---|---|---|---|
accept() |
这将接受点击 OK 按钮的 JavaScript 警告框 | alert.accept() |
|
dismiss() |
这将关闭点击 Cancel 按钮的 JavaScript 警告框 | alert.dismiss() |
|
send_keys(*value) |
模拟在元素中输入 | value 是用于输入或设置表单字段的字符串 |
alert.send_keys("foo") |
在演示应用程序中,您可以找到使用警告来通知或警告用户的情况。例如,当您添加产品进行比较,然后删除其中一个产品或所有产品时,应用程序会显示一个类似于以下截图的警告:

我们将实现一个测试,检查清除所有选项在比较产品功能中是否向用户显示一个询问是否确定删除产品的警告窗口。
创建一个新的测试类,CompareProducts,并添加以下代码片段中所示的搜索和添加产品以进行比较的步骤:
from selenium import webdriver
import unittest
class CompareProducts(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Firefox()
self.driver.implicitly_wait(30)
self.driver.maximize_window()
self.driver.get("http://demo.magentocommerce.com/")
def test_compare_products_removal_alert(self):
# get the search textbox
search_field = self.driver.find_element_by_name("q")
search_field.clear()
# enter search keyword and submit
search_field.send_keys("phones")
search_field.submit()
# click the Add to compare link
self.driver.\
find_element_by_link_text("Add to Compare").click()
一旦通过点击添加到比较链接添加了产品进行比较,你将看到产品被添加到比较产品部分。你也可以添加另一个产品进行比较。如果你想从比较中删除所有产品,你可以通过点击比较产品部分的清除所有链接来实现。你将收到一个询问你是否要清除所有产品的警告。我们可以使用Alert类来处理这个警告。WebDriver类的switch_to_alert()方法返回Alert实例。我们可以使用这个实例来读取警告上显示的消息,并通过点击确定按钮或通过点击取消按钮来接受该警告,从而关闭警告。将以下代码添加到测试中。这部分读取并检查警告消息,然后通过调用accept()方法来接受警告:
# click on Remove this item link, this will display # an alert to the user
self.driver.find_element_by_link_text("Clear All").click()
# switch to the alert
alert = self.driver.switch_to_alert()
# get the text from alert
alert_text = alert.text
# check alert text
self.assertEqual("Are you sure you would like to remove all products from your comparison?", alert_text)
# click on Ok button
alert.accept()
def tearDown(self):
self.driver.quit()
if __name__ == "__main__":
unittest.main()
自动化浏览器导航
浏览器提供了各种导航方法,可以通过浏览器历史记录或通过刷新当前页面等方式访问网页,例如使用浏览器窗口工具栏上的后退、前进、刷新/重新加载按钮。Selenium WebDriver API 提供了各种导航方法来访问这些按钮。我们可以测试当使用这些方法时应用程序的行为。WebDriver类提供了以下方法来执行浏览器导航,如后退、前进和刷新:
| 方法 | 描述 | 参数 | 示例 |
|---|---|---|---|
back() |
这将在当前会话的浏览器历史记录中后退一步 | 无 | driver.back() |
forward() |
这将在当前会话的浏览器历史记录中前进一步 | 无 | driver.forward() |
refresh() |
这将刷新浏览器中显示的当前页面 | 无 | driver.refresh() |
以下是一个使用浏览器导航 API 导航历史记录并验证应用程序状态的示例:
import unittest
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
class NavigationTest(unittest.TestCase):
def setUp(self):
# create a new Firefox session
self.driver = webdriver.Chrome()
self.driver.implicitly_wait(30)
self.driver.maximize_window()
# navigate to the application home page
self.driver.get("http://www.google.com")
def testBrowserNavigation(self):
driver = self.driver
# get the search textbox
search_field = driver.find_element_by_name("q")
search_field.clear()
# enter search keyword and submit
search_field.send_keys("selenium webdriver")
search_field.submit()
se_wd_link = driver.find_element_by_link_text("Selenium WebDriver")
se_wd_link.click()
self.assertEqual("Selenium WebDriver", driver.title)
driver.back()
self.assertTrue(WebDriverWait(self.driver, 10)
.until(expected_conditions.title_is("selenium webdriver - Google Search")))
driver.forward()
self.assertTrue(WebDriverWait(self.driver, 10)
.until(expected_conditions.title_is("Selenium WebDriver")))
driver.refresh()
self.assertTrue(WebDriverWait(self.driver, 10)
.until(expected_conditions.title_is("Selenium WebDriver")))
def tearDown(self):
# close the browser window
self.driver.quit()
if __name__ == '__main__':
unittest.main()
摘要
在本章中,你被介绍了 Selenium WebDriver API,用于与页面上的各种元素进行交互。Selenium WebDriver API 提供了各种类、属性和方法来模拟用户操作并检查应用程序状态。我们探讨了自动化文本框、按钮、复选框和下拉列表等元素的各种方法。
我们创建了一些处理警告的测试。我们还探讨了使用浏览器导航方法并测试了页面间的导航。
在下一章中,我们将探索 Selenium API 来处理同步测试。这将帮助我们使用 Selenium 构建可靠的测试。
第五章:同步测试
构建稳健和可靠的测试是自动化 UI 测试的关键成功因素之一。然而,你可能会遇到测试条件因测试而异的情况。当你的脚本搜索元素或应用程序的某种状态,并且由于突然的资源限制或网络延迟导致应用程序开始缓慢响应,无法再找到这些元素时,测试会报告假阴性结果。我们需要通过在测试脚本中引入延迟来匹配测试脚本的速率与应用程序的速率。换句话说,我们需要将脚本与应用程序的响应同步。WebDriver 提供了隐式和显式等待来同步测试。
在本章中,你将学习以下主题:
-
使用隐式和显式等待
-
何时使用隐式和显式等待
-
使用预期条件
-
创建自定义等待条件
使用隐式等待
隐式等待提供了一个通用的方法来同步 WebDriver 中的整个测试或一系列步骤。隐式等待在处理由于网络速度或使用 Ajax 调用动态渲染元素的应用程序响应时间不一致的情况时非常有用。
当我们在 WebDriver 上设置隐式等待时,它会搜索 DOM 一段时间以查找元素或元素,如果它们当时不可用。默认情况下,隐式等待超时设置为0。
一旦设置,隐式等待将应用于 WebDriver 实例的生命周期或整个测试期间,WebDriver 将为所有在页面上查找元素的步骤应用此隐式等待,除非我们将它重新设置为0。
webdriver类提供了implicitly_wait()方法来配置超时。我们在第二章中创建了一个SearchProductTest测试,使用 unittest 编写测试。我们将修改这个测试,并在setUp()方法中添加一个 10 秒的超时隐式等待,如下面的代码示例所示。当测试执行时,如果 WebDriver 找不到元素,它将等待最多 10 秒。当达到超时,即本例中的 10 秒时,它将抛出NoSuchElementException。
import unittest
from selenium import webdriver
class SearchProductTest(unittest.TestCase):
def setUp(self):
# create a new Firefox session
self.driver = webdriver.Firefox()
self.driver.implicitly_wait(30)
self.driver.maximize_window()
# navigate to the application home page
self.driver.get("http://demo.magentocommerce.com/")
def test_search_by_category(self):
# get the search textbox
self.search_field = self.driver.find_element_by_name("q")
self.search_field.clear()
# enter search keyword and submit
self.search_field.send_keys("phones")
self.search_field.submit()
# get all the anchor elements which have product names # displayed currently on result page using # find_elements_by_xpath method
products = self.driver\
.find_elements_by_xpath("//h2[@class='product-name']/a")
# check count of products shown in results
self.assertEqual(2, len(products))
def tearDown(self):
# close the browser window
self.driver.quit()
if __name__ == '__main__':
unittest.main(verbosity=2)
小贴士
在测试中最好避免使用隐式等待,并尝试使用显式等待来处理同步问题,与隐式等待相比,显式等待提供了更多的控制。
使用显式等待
显式等待是 WebDriver 中用于同步测试的另一种等待机制。与隐式等待相比,显式等待提供了更好的控制。与隐式等待不同,我们可以在脚本继续下一步之前使用一组预定义或自定义条件来等待。
显式等待只能在需要脚本同步的特定情况下实现。WebDriver 提供了WebDriverWait和expected_conditions类来实现显式等待。
expected_conditions类提供了一组预定义的条件,用于在代码中进一步执行之前等待。
让我们创建一个简单的测试,使用预期条件显式等待一个元素的可见性,如下面的代码所示:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
import unittest
class ExplicitWaitTests(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Firefox()
self.driver.get("http://demo.magentocommerce.com/")
def test_account_link(self):
WebDriverWait(self.driver, 10)\
.until(lambda s: s.find_element_by_id("select-language").get_attribute("length") == "3")
account = WebDriverWait(self.driver, 10)\
.until(expected_conditions.visibility_of_element_located((By.LINK_TEXT, "ACCOUNT")))
account.click()
def tearDown(self):
self.driver.quit()
if __name__ == "__main__":
unittest.main(verbosity=2)
在这个测试中,使用显式等待来等待直到登录链接在 DOM 中可见,使用预期的visibility_of_element_located条件。此条件需要我们想要等待的元素的定位策略和定位详情。脚本将等待最多 10 秒钟寻找可见的元素。一旦元素通过指定的定位器可见,预期的条件将返回定位到的元素回脚本。
如果在指定的超时时间内,元素通过指定的定位器不可见,将引发TimeoutException异常。
预期条件类
下表显示了我们在自动化由expected_conditions类支持的网页浏览器时经常遇到的常见条件及其示例:
| 预期条件 | 描述 | 参数 | 示例 |
|---|---|---|---|
element_to_be_clickable(locator) |
这将等待一个元素被定位、可见并且可点击,以便可以点击。此方法将返回定位到的元素到测试中。 | locator: 这是一个(by, locator)的元组。 |
WebDriverWait(self.driver, 10).until(expected_conditions.element_to_be_clickable((By.NAME,"is_subscribed"))) |
element_to_be_selected(element) |
这将等待直到指定的元素被选中。 | element: 这是一个 WebElement。 |
subscription = self.driver.find_element_by_name("is_subscribed")``WebDriverWait(self.driver, 10).until(expected_conditions.element_to_be_selected(subscription)) |
invisibility_of_element_located(locator) |
这将等待一个元素要么不可见,要么不在 DOM 上。 | locator: 这是一个(by, locator)的元组。 |
WebDriverWait(self.driver, 10).until(expected_conditions.invisibility_of_element_located((By.ID,"loading_banner"))) |
presence_of_all_elements_located(locator) |
这将等待直到至少一个匹配定位器的元素出现在网页上。此方法在元素被定位后返回 WebElements 列表。 | locator: 这是一个(by, locator)的元组。 |
WebDriverWait(self.driver, 10).until(expected_conditions.presence_of_all_elements_located((By.CLASS_NAME,"input-text"))) |
presence_of_element_located(locator) |
这将等待直到匹配定位器的元素出现在网页上或在 DOM 上可用。此方法在元素被定位后返回一个元素。 | locator: 这是一个(by, locator)的元组。 |
WebDriverWait(self.driver, 10).until(expected_conditions.presence_of_element_located((By.ID,"search"))) |
text_to_be_present_in_element(locator, text_) |
这将等待直到找到元素并且具有给定的文本。 | locator: 这是一个 (by, locator) 的元组。text: 这是需要检查的文本。 |
WebDriverWait(self.driver,10).until(expected_conditions.text_to_be_present_in_element((By.ID,"select-language"),"English")) |
title_contains(title) |
这将等待页面标题包含一个大小写敏感的子串。此方法如果标题匹配则返回 true,否则返回 false。 |
title: 这是需要检查的标题子串。 |
WebDriverWait(self.driver, 10).until(expected_conditions.title_contains("Create New Customer Account")) |
title_is(title) |
这将等待页面标题等于预期的标题。此方法如果标题匹配则返回 true,否则返回 false。 |
title: 这是页面的标题。 |
WebDriverWait(self.driver, 10).until(expected_conditions.title_is("Create New Customer Account - Magento Commerce Demo Store")) |
visibility_of(element) |
这将等待直到元素在 DOM 中存在,可见,并且其宽度和高度都大于零。此方法在元素变为可见时返回(相同的)WebElement。 | element: 这是 WebElement。 |
first_name = self.driver.find_element_by_id("firstname") WebDriverWait(self.driver, 10).until(expected_conditions.visibility_of(first_name)) |
visibility_of_element_located(locator) |
这将等待直到要定位的元素在 DOM 中存在,可见,并且其宽度和高度都大于零。此方法在元素变为可见时返回 WebElement。 | locator: 这是一个 (by, locator) 的元组。 |
WebDriverWait(self.driver, 10).until(expected_conditions.visibility_of_element_located((By.ID,"firstname"))) |
让我们在接下来的部分中探索更多预期条件的示例。
等待元素变为可用
如我们之前所见,expected_conditions 类提供了各种等待条件,我们可以在脚本中实现。在下面的示例中,我们将等待一个元素变为可用或可点击。我们可以在基于其他表单字段值或筛选器的表单字段启用或禁用的 Ajax 重量级应用程序中使用此条件。在这个例子中,我们点击 登录 链接,然后等待登录页面上的 创建账户 按钮变为可点击,该按钮在登录页面上显示。然后我们将点击 创建账户 按钮,并等待显示下一页。
def test_create_new_customer(self):
# click on Log In link to open Login page
self.driver.find_element_by_link_text("ACCOUNT").click()
# wait for My Account link in Menu
my_account = WebDriverWait(self.driver, 10)\
.until(expected_conditions.visibility_of_element_located((By.LINK_TEXT, "My Account")))
my_account.click()
# get the Create Account button
create_account_button = WebDriverWait(self.driver, 10)\
.until(expected_conditions.element_to_be_clickable((By.LINK_TEXT, "CREATE AN ACCOUNT")))
# click on Create Account button. This will displayed new account
create_account_button.click()
WebDriverWait(self.driver, 10)\
.until(expected_conditions.title_contains("Create New Customer Account"))
我们可以使用element_to_be_clickable条件等待并检查元素是否可点击。这需要定位策略和定位值。当该元素变为可点击或换句话说变为启用时,它将返回定位的元素到脚本中。
前面的测试还通过检查指定的文本标题来等待创建新客户账户页面加载。我们使用了title_contains条件,该条件确保子字符串与页面标题匹配。
等待警报
我们还可以在警报和框架上使用显式等待。复杂的 JavaScript 处理或后端请求可能需要时间来向用户显示警报。这可以通过以下方式处理预期的alert_is_present条件:
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions
import unittest
class CompareProducts(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Firefox()
self.driver.get("http://demo.magentocommerce.com/")
def test_compare_products_removal_alert(self):
# get the search textbox
search_field = self.driver.find_element_by_name("q")
search_field.clear()
# enter search keyword and submit
search_field.send_keys("phones")
search_field.submit()
# click the Add to compare link
self.driver.\
find_element_by_link_text("Add to Compare").click()
# wait for Clear All link to be visible
clear_all_link = WebDriverWait(self.driver, 10)\
.until(expected_conditions.visibility_of_element_located((By.LINK_TEXT, "Clear All")))
# click on Clear All link,
# this will display an alert to the user
clear_all_link.click()
# wait for the alert to present
alert = WebDriverWait(self.driver, 10)\
.until(expected_conditions.alert_is_present())
# get the text from alert
alert_text = alert.text
# check alert text
self.assertEqual("Are you sure you would like
to remove all products from your comparison?", alert_text)
# click on Ok button
alert.accept()
def tearDown(self):
self.driver.quit()
if __name__ == "__main__":
unittest.main(verbosity=2)
前面的测试验证了从应用程序的产品比较功能中删除产品。当用户从比较中删除产品时,会向用户发送确认警报。使用alert_is_present条件来检查警报是否显示给用户,并将其返回到脚本以进行后续操作。脚本将等待 10 秒钟检查警报的存在,否则将引发异常。
实现自定义等待条件
如我们之前所见,expected_conditions类提供了各种预定义的条件以供等待。我们还可以使用WebDriverWait构建自定义条件。当没有合适的预期条件可用时,这非常有用。
让我们修改本章早期创建的一个测试,并实现一个自定义等待条件来检查下拉列表项的数量:
def testLoginLink(self):
WebDriverWait(self.driver, 10).until(lambda s: s.find_element_by_id "select-language").get_attribute("length") == "3")
login_link = WebDriverWait(self.driver, 10).until(expected_conditions.visibility_of_element_located((By.LINK_TEXT,"Log In")))
login_link.click();
我们可以使用 Python lambda 表达式通过WebDriverWait实现自定义等待条件。在这个例子中,脚本将等待 10 秒钟,直到选择语言下拉列表有八个选项可供选择。这个条件在下拉列表通过 Ajax 调用填充,并且脚本需要等待直到所有选项都可供用户选择时非常有用。
摘要
在本章中,我们认识到同步的需要及其在构建高度可靠的测试中的重要性。我们探讨了隐式等待以及如何使用隐式等待作为一个通用的等待机制。然后我们探讨了提供更灵活同步测试方式的显式等待。expected_conditions类提供了各种内置的等待条件。我们已经实现了一些这些条件。
WebDriverWait类还提供了一种非常强大的方式来在expected_conditions之上实现自定义等待条件。我们在下拉列表上实现了一个自定义等待条件。
在下一章中,你将学习如何使用RemoteWebDriver和 Selenium Server 实现跨浏览器测试,在远程机器上运行测试,并使用 Selenium Grid 进行并行执行。
第六章。跨浏览器测试
Selenium 支持在多个浏览器和操作系统组合上进行跨浏览器测试。这对于在多种浏览器和操作系统组合上测试网络应用程序,以验证应用程序的跨浏览器兼容性以及确保用户在使用他们选择的浏览器或操作系统时不会遇到问题非常有用。Selenium WebDriver 提供了在远程机器上运行测试或在多个运行在远程机器或云端的操作系统和浏览器上分布测试的能力。到目前为止,你已经学习了如何创建和运行在本地机器上安装了各种浏览器驱动程序的测试,如下面的图所示:

在本章中,你将学习如何在远程机器上运行这些测试,然后如何在不同浏览器和操作系统组合的分布式架构中扩展和运行测试,以进行跨浏览器测试。这可以节省大量在跨浏览器测试中花费的时间和精力。在本章中,我们将涵盖以下方面:
-
下载和使用 Selenium 独立服务器
-
如何使用
Remote类在 Selenium 独立服务器上运行测试 -
在 Selenium 独立服务器上运行测试
-
向 Selenium 独立服务器添加节点以创建用于分布式执行的网格
-
在网格中针对多个浏览器和操作系统组合运行测试
-
在 Sauce Labs 和 BrowserStack 云上运行测试
Selenium 独立服务器
Selenium 独立服务器是 Selenium 的一个组件,它提供了在远程机器上运行测试的能力。我们需要使用 RemoteWebDriver 类来连接到 Selenium 独立服务器,以便在远程机器上运行测试。RemoteWebDriver 类通过在指定的端口上使用 RemoteWebDriver 类监听来自测试脚本中的 Selenium 命令。根据 RemoteWebDriver 类提供的配置,Selenium 服务器将启动指定的浏览器并将命令转发到浏览器。它支持几乎所有的浏览器和带有 Appium 的移动平台。以下图显示了配置了不同类型浏览器的远程机器上运行的 Selenium 服务器架构:

下载 Selenium 独立服务器
Selenium 独立服务器以捆绑的 JAR 格式提供下载,可在 docs.seleniumhq.org/download/ 的 Selenium 服务器(以前称为 Selenium RC 服务器) 部分找到。在编写本书时,Selenium 服务器版本 2.41.0 可供下载。你只需将 Selenium 独立服务器 JAR 文件复制到远程机器上并启动服务器即可。
注意
Selenium 独立服务器是一个用 Java 编写的自包含服务器。它需要在运行它的机器上安装 Java 运行时环境(JRE)。请确保您已在打算运行 Selenium 独立服务器的远程机器上安装了 JRE 6 或更高版本。
启动 Selenium 独立服务器
Selenium 独立服务器可以以各种模式或角色启动。在本节中,我们将以独立模式启动它。我们可以在保存服务器 JAR 文件的目录中,从远程机器的命令行使用以下命令启动服务器。在本例中,它通过以下命令行在 Windows 8 机器上启动:
java –jar selenium-server-standalone-2.41.0.jar
默认情况下,Selenium 服务器将在4444端口上监听http://<remote-machine-ip>:4444。在启动服务器时,您可以在命令行上看到以下输出:

Selenium 服务器将在远程机器上作为 HTTP 服务器启动,我们可以在浏览器窗口中启动并查看服务器。启动浏览器并导航到http://<remote-machine-ip>:4444/wd/hub/static/resource/hub.html。这将显示以下页面在浏览器窗口中:

现在我们已经启动并运行了 Selenium 服务器,是时候创建并运行一个可以在服务器上运行的测试了。
在 Selenium 独立服务器上运行测试
要在 Selenium 服务器上运行测试,我们需要使用RemoteWebDriver。Selenium Python 绑定中的Remote类充当客户端,与 Selenium 服务器通信,以在远程机器上运行测试。我们需要使用这个类来指示 Selenium 服务器需要哪些配置来在远程机器上运行测试,以及要在所选浏览器上运行的命令。
除了Remote类之外,我们还需要设置desired_capabilities,即浏览器、操作系统以及我们想要传达给 Selenium 独立服务器的任何其他配置。在本例中,我们将指定一个平台和浏览器名称作为运行测试所需的期望能力:
desired_caps = {}
desired_caps['platform'] = 'WINDOWS'
desired_caps['browserName'] = 'firefox'
接下来,我们将创建一个Remote类的实例并传递desired_capabilities。当脚本执行时,它将连接到 Selenium 服务器并请求服务器设置一个在 Windows 上运行的 Firefox 浏览器来运行测试:
self.driver = webdriver.Remote('http://192.168.1.103:4444/wd/hub', desired_caps)
让我们实现一个我们之前创建的搜索测试,并用以下方式使用Remote类代替 Firefox 驱动程序:
import unittest
from selenium import webdriver
class SearchProducts(unittest.TestCase):
def setUp(self):
desired_caps = {}
desired_caps['platform'] = 'WINDOWS'
desired_caps['browserName'] = 'firefox'
self.driver = \
webdriver.Remote('http://192.168.1.102:4444/wd/hub',desired_caps)
self.driver.get('http://demo.magentocommerce.com/')
self.driver.implicitly_wait(30)
self.driver.maximize_window()
def testSearchByCategory(self):
# get the search textbox
self.search_field = self.driver.find_element_by_name('q')
self.search_field.clear()
# enter search keyword and submit
self.search_field.send_keys('phones')
self.search_field.submit()
# get all the anchor elements which have product names # displayed currently on result page using # find_elements_by_xpath method
products = self.driver\
.find_elements_by_xpath('//h2[@class=\'product-name\']/a')
# check count of products shown in results
self.assertEqual(2, len(products))
def tearDown(self):
# close the browser window
self.driver.quit()
if __name__ == '__main__':
unittest.main()
当这个测试执行时,您可以在 Selenium 服务器的控制台中看到。它显示了测试与服务器之间的交互,如下面的截图所示。它显示了已执行的命令及其状态:

您还可以导航到http://<remote-machine-ip>:4444/wd/hub/static/resource/hub.html,该链接显示正在创建的新会话。如果您将鼠标悬停在“能力”链接上,它将显示用于运行测试的能力,如图以下屏幕截图所示:

添加对 Internet Explorer 的支持
Firefox 支持捆绑在 Selenium 服务器中;然而,为了在Internet Explorer(IE)上运行测试,我们需要在启动 Selenium 服务器时指定 IE 驱动程序可执行文件的路径。这通过在命令行中指定wedriver.ie.driver选项的可执行路径来完成,如图所示:
java -Dwebdriver.ie.driver="C:\SeDrivers\IEDriverServer.exe" -jar selenium-server-standalone-2.41.0.jar
通过提供 IE 驱动程序的路径,Selenium 服务器现在将启动并支持在远程机器上测试 IE。
添加对 Chrome 的支持
与 IE 驱动程序可执行文件类似,我们需要在远程机器上提及 Chrome 驱动程序以支持 Chrome 的测试。这通过在以下命令行中指定webdriver.chrome.driver选项来完成:
java -Dwebdriver.ie.driver="C:\SeDrivers\IEDriverServer.exe" -Dwebdriver.chrome.driver="C:\SeDrivers\chromedriver.exe" -jar selenium-server-standalone-2.41.0.jar
Selenium 服务器现在将支持在远程机器上运行 Internet Explorer 和 Chrome 的测试。
Selenium Grid
Selenium Grid 允许我们将测试分布在多个物理或虚拟机器上,以便以分布式方式运行测试或并行运行它们。这有助于通过减少运行测试所需的时间并加快跨浏览器测试的速度来获得更快、更准确的反馈。我们可以使用我们现有的云虚拟机基础设施来设置网格。
Selenium Grid 使我们能够在异构环境中并行运行多个测试,在多个节点或客户端上,我们可以有浏览器和操作系统支持的混合。它使所有这些节点看起来像一个单一实例,并透明地将测试分配到以下图示的基础设施上:

以集线器方式启动 Selenium 服务器
我们需要设置 Selenium 服务器作为集线器以分布式方式运行测试。集线器将为测试提供所有可用的配置或功能。
从机,也称为节点,连接到集线器。测试将使用Remote类通过 JSON 线协议与集线器通信以执行 Selenium 命令。您可以在code.google.com/p/selenium/wiki/JsonWireProtocol上找到更多关于 JSON 线协议的信息。
集线器作为中心点,将接收来自测试的命令并将它们分配到适当的节点或与测试所需的节点匹配的节点。让我们设置一个 Selenium 服务器作为网格,然后添加具有不同浏览器和操作系统组合的节点。
我们可以通过向之前章节中启动服务器的命令添加额外的参数来以 hub(也称为网格服务器)的形式启动 Selenium 独立服务器。
创建一个新的命令/终端窗口,导航到 Selenium 服务器 JAR 文件所在的目录。通过输入以下命令以 hub 的形式启动服务器:
java -jar selenium-server-standalone-2.25.0.jar -port 4444 -role hub
我们需要使用 –role 参数并传递 hub 值来以 hub 或网格服务器的方式启动服务器。
在这个例子中,服务器在 Windows 机器上启动。它将在控制台上打印以下信息:

当我们以 hub 的形式启动 Selenium 服务器时,它将以网格服务器启动。我们可以在浏览器中看到以下截图所示的网格控制台:

添加节点
现在我们已经启动了 Selenium 服务器作为网格服务器,让我们将一些节点配置添加到服务器中。
添加 IE 节点
让我们从在 Windows 上运行的提供 Internet Explorer 功能的节点开始。打开一个新的命令提示符或终端窗口,导航到 Selenium 服务器 JAR 文件所在的目录。要启动一个节点并将其添加到网格中,请输入以下命令:
java -Dwebdriver.ie.driver="C:\SeDrivers\IEDriverServer.exe" -jar selenium-server-standalone-2.41.0.jar -role webdriver -browser "browserName=internet explorer,version=10,maxinstance=1,platform=WINDOWS" -hubHost 192.168.1.103 –port 5555
要将节点添加到网格中,我们需要使用 –role 参数并传递 webdriver 作为值。我们还需要传递节点的浏览器配置。这通过 –browser 参数传递。在这个例子中,我们传递了 browserName 为 internet explorer,version 为 10,maxinstance 为 1,以及 platform 为 WINDOWS。maxinstance 的值告诉网格节点将支持多少个浏览器的并发实例。
要将节点连接到 hub 或网格服务器,我们需要指定 –hubHost 参数,并使用网格服务器的主机名或 IP 地址。最后,我们需要指定节点将运行的端口。
当我们运行前面的命令并启动节点时,以下配置将出现在网格控制台中:

或者,可以通过创建一个 JSON 格式的配置文件来添加一个节点,然后使用以下代码:
{
"class": "org.openqa.grid.common.RegistrationRequest",
"capabilities": [
{
"seleniumProtocol": "WebDriver",
"browserName": "internet explorer",
"version": "10",
"maxInstances": 1,
"platform" : "WINDOWS"
}
],
"configuration": {
"port": 5555,
"register": true,
"host": "192.168.1.103",
"proxy": "org.openqa.grid.selenium.proxy.
DefaultRemoteProxy",
"maxSession": 2,
"hubHost": "192.168.1.100",
"role": "webdriver",
"registerCycle": 5000,
"hub": "http://192.168.1.100:4444/grid/register",
"hubPort": 4444,
"remoteHost": "http://192.168.1.102:5555"
}
}
我们现在可以通过以下方式将 selenium-node-win-ie10.cfg.json 配置文件作为命令行参数传递:
java -Dwebdriver.ie.driver="C:\SeDrivers\IEDriverServer.exe"-jar selenium-server-standalone-2.41.0.jar -role webdriver -nodeConfig selenium-node-win-ie10.cfg.json
添加 Firefox 节点
要添加一个 Firefox 节点,打开一个新的命令提示符或终端窗口,导航到 Selenium 服务器 JAR 文件所在的目录。要启动并添加节点到网格,请输入以下命令:
java -jar selenium-server-standalone-2.41.0.jar -role webdriver -browser "browserName=firefox,version=27,maxinstance=2,platform=WINDOWS" -hubHost localhost –port 6666
在这个例子中,我们将 maxinstance 设置为 2。这告诉网格该节点将支持两个 Firefox 实例。一旦节点启动,以下配置将出现在网格控制台中:

添加 Chrome 节点
要添加 Chrome 节点,打开一个新的命令提示符或终端窗口,导航到 Selenium 服务器 JAR 文件所在的位置。要启动并将节点添加到网格中,请输入以下命令:
java -Dwebdriver.chrome.driver="C:\SeDrivers\chromedriver.exe" -jar selenium-server-standalone-2.41.0.jar -role webdriver -browser "browserName=chrome,version=35,maxinstance=2,platform=WINDOWS" -hubHost localhost -port 7777
一旦节点启动,以下配置将在网格控制台中显示:

Mac OS X with Safari
我们从一台 Windows 机器添加了 IE、Firefox 和 Chrome 实例,现在让我们从 Mac OS 添加一个 Safari 节点。打开一个新的终端窗口,导航到 Selenium 服务器 JAR 文件所在的位置。要启动并将节点添加到网格中,请输入以下命令:
java -jar selenium-server-standalone-2.41.0.jar -role webdriver -browser "browserName=safari,version=7,maxinstance=1,platform=MAC" -hubHost 192.168.1.104 -port 8888
一旦节点启动,以下配置将在网格控制台中显示:

现在,我们已经设置了 Selenium 网格,让我们尝试在这个网格上运行测试。
在网格中运行测试
在网格中运行测试以及不同浏览器和操作系统的组合需要对我们之前创建的测试进行一些调整。我们在期望的能力中指定了硬编码的浏览器和平台名称。如果我们硬编码这些值,那么我们将为每种组合编写一个单独的脚本。为了避免这种情况并使用一个可以在所有组合上运行的单一测试,我们需要按照以下步骤参数化传递给期望能力类的浏览器和平台值:
-
我们将从命令行传递浏览器和平台到测试中。例如,如果我们想在 Windows 和 Chrome 的组合上运行测试,我们将通过以下方式在命令行中运行脚本:
python grid_test.py WINDOWS chrome -
如果我们想在 Mac 上的 Safari 上运行测试,我们可以使用以下命令:
python grid_test.py MAC safari -
要实现这一点,我们需要向测试类中添加两个全局属性,
PLATFORM和BROWSER,如下所示。如果没有从命令行提供值,我们将设置一个默认值:class SearchProducts(unittest.TestCase): PLATFORM = 'WINDOWS' BROWSER = 'firefox' -
接下来,我们需要在
setUp()方法中参数化期望能力,如下面的代码所示:desired_caps = {} desired_caps['platform'] = self.PLATFORM desired_caps['browserName'] = self.BROWSER -
最后,我们需要读取传递给脚本的参数,并按照以下方式将值分配给
PLATFORM和BROWSER属性:if __name__ == '__main__': if len(sys.argv) > 1: SearchProducts.BROWSER = sys.argv.pop() SearchProducts.PLATFORM = sys.argv.pop() unittest.main() -
就这样。我们的测试现在可以处理任何给定的组合。以下是包含之前更改的完整代码:
import sys import unittest from selenium import webdriver class SearchProducts(unittest.TestCase): PLATFORM = 'WINDOWS' BROWSER = 'firefox' def setUp(self): desired_caps = {} desired_caps['platform'] = self.PLATFORM desired_caps['browserName'] = self.BROWSER self.driver = \ webdriver.Remote('http://192.168.1.104:4444/wd/hub',desired_caps) self.driver.get('http://demo.magentocommerce.com/') self.driver.implicitly_wait(30) self.driver.maximize_window() def testSearchByCategory(self): # get the search textbox self.search_field = self.driver.find_element_by_name('q') self.search_field.clear() # enter search keyword and submit self.search_field.send_keys('phones') self.search_field.submit() # get all the anchor elements which have product names # displayed currently on result page using # find_elements_by_xpath method products = self.driver.\ find_elements_by_xpath('//h2[@class=\'product-name\']/a') # check count of products shown in results self.assertEqual(2, len(products)) def tearDown(self): # close the browser window self.driver.quit() if __name__ == '__main__': if len(sys.argv) > 1: SearchProducts.BROWSER = sys.argv.pop() SearchProducts.PLATFORM = sys.argv.pop() unittest.main(verbosity=2) -
要运行测试,打开一个新的命令提示符或终端窗口,导航到脚本的位置。输入以下命令,您将看到网格将连接与给定平台和浏览器匹配的节点,并在该节点上执行测试:
python grid_test.py MAC safari
在云中运行测试
在前面的步骤中,我们设置了一个本地网格以运行跨浏览器测试。这需要设置具有不同浏览器和操作系统的物理或虚拟机。需要投入成本和努力来获取所需的硬件、软件和支持以运行测试实验室。您还需要投入努力以保持该基础设施更新到最新版本和补丁等。并非每个人都能承担这些成本和努力。
您无需投资和设置跨浏览器测试实验室,可以轻松地将虚拟测试实验室外包给第三方云服务提供商。Sauce Labs 和 BrowserStack 是领先的基于云的跨浏览器测试云服务提供商。这两者都支持超过 400 种不同的浏览器和操作系统配置,包括移动和平板设备,并支持在他们的云中运行 Selenium WebDriver 测试。
在本节中,我们将设置并运行 Sauce Labs 云中的测试。如果您想使用 BrowserStack 运行测试,步骤类似。
使用 Sauce Labs
让我们按照以下步骤设置并使用 Sauce Labs 运行测试:
-
您需要先拥有一个免费的 Sauce Labs 账户。在 Sauce Labs 上注册一个免费账户:
saucelabs.com/,并获取用户名和访问密钥。Sauce Labs 提供所有必要的硬件和软件基础设施,以便在云中运行您的测试。 -
您可以在登录 Sauce Labs 仪表板后从 Sauce Labs 获取访问密钥,如下面的截图所示:
![使用 Sauce Labs]()
-
让我们修改之前创建的测试,使其与网格一起运行,并添加步骤以在 Sauce Labs 云上运行此测试。
-
我们需要将 Sauce 用户名和访问密钥添加到测试中,并将网格地址更改为 Sauce 的网格地址,传递用户名和访问密钥,如下面的代码所示:
import sys import unittest from selenium import webdriver class SearchProducts(unittest.TestCase): PLATFORM = 'WINDOWS' BROWSER = 'phantomjs' SAUCE_USERNAME = 'upgundecha' SUACE_KEY = 'c6e7132c-ae27-4217-b6fa-3cf7df0a7281' def setUp(self): desired_caps = {} desired_caps['platform'] = self.PLATFORM desired_caps['browserName'] = self.BROWSER sauce_string = self.SAUCE_USERNAME + ':' + self.SUACE_KEY self.driver = \ webdriver.Remote('http://' + sauce_string + '@ondemand.saucelabs.com:80/wd/hub', desired_caps) self.driver.get('http://demo.magentocommerce.com/') self.driver.implicitly_wait(30) self.driver.maximize_window() def testSearchByCategory(self): # get the search textbox self.search_field = self.driver.find_element_by_name('q') self.search_field.clear() # enter search keyword and submit self.search_field.send_keys('phones') self.search_field.submit() # get all the anchor elements which have product names # displayed currently on result page using # find_elements_by_xpath method products = self.driver.\ find_elements_by_xpath('//h2[@class=\'product-name\']/a') # check count of products shown in results self.assertEqual(2, len(products)) def tearDown(self): # close the browser window self.driver.quit() if __name__ == '__main__': if len(sys.argv) > 1: SearchProducts.BROWSER = sys.argv.pop() SearchProducts.PLATFORM = sys.argv.pop() unittest.main(verbosity=2) -
要运行测试,打开一个新的命令提示符或终端窗口,导航到脚本的位置。输入以下命令:
python sauce_test.py "OS X 10.9" "Safari"小贴士
您可以在 Sauce Labs 的网站上找到支持的平台列表:
saucelabs.com/platforms。在运行测试时,它将连接到 Sauce Lab 的网格服务器,并请求所需的操作系统和浏览器配置。Sauce 为我们的测试分配一个虚拟机,以便在给定的配置上运行。
-
我们可以在 Sauce 仪表板上监控这次运行,如下面的截图所示:
![使用 Sauce Labs]()
我们可以进一步深入 Sauce 会话,查看运行期间的确切情况。它提供了大量的细节,包括 Selenium 命令、截图、Selenium 日志以及执行的视频,如下面的截图所示:

小贴士
您还可以通过使用 Sauce Connect 工具安全地测试托管在内部服务器上的应用程序,该工具在您的机器和 Sauce 云之间创建一个安全隧道。
摘要
在本章中,你学习了如何使用 Selenium 独立服务器在远程机器上运行测试。Selenium 独立服务器使我们能够在远程机器上运行测试,以测试我们的应用程序针对浏览器和操作系统的组合进行跨浏览器测试。这增加了测试覆盖率,并确保应用程序在所需的组合上运行。
然后,我们探讨了如何设置 Selenium Grid 以在分布式架构中运行测试。Selenium Grid 通过提供针对多台机器的透明执行来简化执行跨浏览器测试的复杂性。它还缩短了运行测试的时间。
我们还考虑了使用基于云的、跨浏览器的测试服务提供商。我们在 Sauce Labs 上执行了测试。这提供了所有必要的测试基础设施,以最低的成本在数百种不同的组合上运行测试。
在下一章中,你将学习如何使用 Appium 和 Selenium WebDriver 测试移动应用程序,这些概念在本章中已经学习过。Appium 支持在 iOS 和 Android 上测试原生、混合和 Web 移动应用程序。我们将设置 Appium 并对示例应用程序的移动版本进行测试。
第七章。移动测试
随着全球移动用户数量的不断增长,智能手机和平板电脑的采用率显著提高。移动应用程序已经渗透到消费和企业市场,用智能设备取代了台式机和笔记本电脑。小型企业和大型企业有很大的潜力利用移动作为与用户连接的渠道。为了服务客户和员工,正在投入大量努力构建对移动友好的网站和原生应用程序。在市场上可用的各种移动平台上测试这些应用程序变得至关重要。本章将教你如何使用 Selenium WebDriver 以及更具体地使用 Appium 来测试移动应用程序。
在本章中,你将了解以下内容:
-
使用 Appium 测试移动应用程序
-
安装和设置 Appium
-
在 iPhone 模拟器上创建和运行 iOS 测试
-
在真实设备上创建和运行 Android 测试
介绍 Appium
Appium 是一个开源的测试自动化框架,用于使用 Selenium WebDriver 测试在 iOS、Android 和 Firefox OS 平台上运行的本地和混合移动应用程序,该框架使用与 Selenium WebDriver 测试通信的 JSON 线协议。Appium 将取代 Selenium 2 中用于测试移动网页应用程序的 iPhoneDriver 和 AndroidDriver API。
Appium 允许我们使用和扩展现有的 Selenium WebDriver 框架来构建移动测试。由于它使用 Selenium WebDriver 来驱动测试,我们可以使用任何存在 Selenium 客户端库的语言来创建测试。以下是 Appium 的覆盖图,展示了对不同平台和应用类型的支持:

Appium 支持以下类型的应用程序测试:
-
原生应用程序:原生应用程序是特定于平台的、使用平台支持的语言和框架构建的应用程序。例如,iPhone 和 iPad 的应用程序使用 Objective-C 和 iOS SDK 开发;同样,Android 应用程序使用 Java 和 Android SDK 开发。在性能方面,原生应用程序运行速度快且更可靠。它们使用原生框架进行用户界面。
-
移动网页应用程序:移动网页应用程序是服务器端应用程序,使用任何服务器端技术(如 PHP、Java 或 ASP.NET)构建,并使用 jQuery Mobile、Sencha Touch 等框架来渲染一个模仿原生用户界面的用户界面。
-
混合应用程序:与原生应用程序类似,混合应用程序在设备上运行,并使用 Web 技术(HTML5、CSS 和 JavaScript)编写。混合应用程序使用设备的浏览器引擎来渲染 HTML 并在本地使用 WebView 在原生容器中处理 JavaScript,这使应用程序能够访问在移动网页应用程序中不可访问的设备功能,例如相机、加速度计、传感器和本地存储。
Appium 的先决条件
在你开始学习更多关于 Appium 之前,你需要一些 iOS 和 Android 平台的工具。
注意
Appium 基于 Node.js 构建,同时提供 Node.js 包以及 Mac OS X 和 Windows 上的独立 GUI。我们将使用带有内置 Node.js 的 Mac OS X 上的 Appium 独立 GUI。
设置 iOS 的 Xcode
我们需要安装 Xcode 4.6.3 或更高版本,在 Mac OS X 上进行 iOS 平台应用的测试。在编写本书时,使用了 Xcode 5.1。您可以从 App Store 或开发者门户 developer.apple.com/xcode/ 获取 Xcode。
安装 Xcode 后,从 应用程序 菜单启动它,然后导航到 首选项 | 下载,并安装 命令行工具 以及用于在不同版本的 iOS 平台上测试应用的附加 iOS SDK,如图所示:

在真实设备上运行测试时,您需要在设备上安装配置文件,并启用设备的 USB 调试功能。
尝试启动 iPhone 模拟器并验证其是否正常工作。您可以通过导航到 Xcode | 打开开发者工具 | iOS 模拟器 来启动模拟器。在模拟器中启动 Safari 并打开示例应用的移动网页版本 demo.magentocommerce.com,如图所示:

设置 Android SDK
我们需要安装 Android SDK 以测试 Android 应用。Android SDK 可在 developer.android.com/sdk/ 下载。这将为我们提供 SDK 的最新版本。安装后,请确保已将 ANDROID_HOME 添加到路径中。完整的安装步骤可在 developer.android.com/sdk/installing/index.html?pkg=tools 查找。
注意
详细和最新的安装要求请访问 appium.io/getting-started.html#requirements。
设置 Appium Python 客户端包
在编写本书时,Appium Python 客户端完全符合 Selenium 3.0 规范草案。它提供了一些辅助工具,使使用 Appium 进行 Python 移动测试更加容易。您可以使用以下命令进行安装:
pip install Appium-Python-Client
注意
关于 Appium Python 客户端包的更多信息可在 pypi.python.org/pypi/Appium-Python-Client 找到。
安装 Appium
在我们开始使用 Appium 测试移动应用程序之前,我们需要下载和安装 Appium。我们将使用 Appium GUI 版本。如果您希望在 iPhone 或 iPad 上为 iOS 运行测试,那么您需要在 Mac OS X 机器上设置 Appium。对于测试 Android 应用程序,您可以在 Windows 或 Linux 机器上设置环境。使用 Mac OS X 的新 Appium 应用程序设置 Appium 相当简单。您可以从appium.io/下载最新的 Appium 二进制文件。按照以下步骤安装 Appium:
-
点击首页上的下载 Appium按钮,您将被引导到下载页面。
![安装 Appium]()
-
从以下屏幕截图所示的列表中选择适用于您所使用操作系统的特定版本:
![安装 Appium]()
注意
在以下示例中,我们将使用 Mac OS X 上的 Appium。
-
您可以通过启动安装程序并将 Appium 复制到“应用程序”文件夹中来在 Mac 上安装 Appium。
当您第一次从“应用程序”菜单启动 Appium 时,它将请求授权运行 iOS 模拟器。
小贴士
默认情况下,Appium 在
http://127.0.0.1:4723或本地主机上启动。这是您的测试应将测试命令指向的 URL。我们将测试我们在书中使用的示例应用程序的 iPhone Safari 浏览器上的移动版本。 -
在 Appium 的主窗口中,点击苹果图标以打开 iOS 设置:
![安装 Appium]()
-
在iOS 设置对话框中,选择强制设备复选框,并在 iOS 部分指定iPhone 4s。同时,选择使用移动 Safari复选框,如以下屏幕截图所示:
![安装 Appium]()
-
在 Appium 窗口中点击启动按钮以启动 Appium 服务器。
Appium Inspector
Appium 还附带一个名为Appium Inspector的间谍工具。我们可以通过点击 Appium 主窗口上的放大镜图标来启动 Appium Inspector。
检查器提供了许多选项来分析测试中的应用程序。它提供的主要功能之一是应用程序中 UI 元素的使用方式,元素的架构或层次结构,以及这些元素的属性,我们可以使用这些属性来定义定位字符串。
您还可以在应用程序上模拟各种手势并查看它们在模拟器上的效果。它还提供了一种记录您在应用程序上执行的操作的能力。
iOS 测试
Appium 通过使用各种原生自动化框架来驱动自动化,并提供基于 Selenium WebDriver JSON 线协议的 API。对于自动化 iOS 应用程序,它使用 Apple Instruments 提供的 UI Automation 功能。
Appium 作为一个 HTTP 服务器,通过 JSON 线协议接收来自测试脚本的命令。Appium 将这些命令发送到 Apple Instruments,以便在模拟器或真实设备上运行的 app 可以执行这些命令。在这个过程中,Appium 将 JSON 命令转换为 Instruments 理解的 UI Automation JavaScript 命令。Instruments 负责在模拟器或设备上启动和关闭 app。这个过程在下面的图中展示:

当在模拟器或设备上的 app 执行命令时,目标 app 会将响应发送回 Instruments,然后 Instruments 将其以 JavaScript 响应格式发送回 Appium。Appium 将 UI Automation JavaScript 响应转换为 Selenium WebDriver JSON 线协议响应,并将它们发送回测试脚本。
编写 iOS 测试
现在,我们已经启动了 Appium;让我们创建一个测试来检查 iPhone Safari 浏览器中的搜索功能。创建一个新的测试,SearchProductsOnIPhone,如下面的代码所示:
import unittest
from appium import webdriver
class SearchProductsOnIPhone(unittest.TestCase):
def setUp(self):
desired_caps = {}
# platform
desired_caps['device'] = 'iPhone Simulator'
# platform version
desired_caps['version'] = '7.1'
# mobile browser
desired_caps['app'] = 'safari'
# to connect to Appium server use RemoteWebDriver
# and pass desired capabilities
self.driver = \
webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_caps)
self.driver.get("http://demo.magentocommerce.com/")
self.driver.implicitly_wait(30)
self.driver.maximize_window()
def test_search_by_category(self):
# click on search icon
self.driver.find_element_by_xpath("//a[@href='#header-search']").click()
# get the search textbox
self.search_field = self.driver.find_element_by_name("q")
self.search_field.clear()
# enter search keyword and submit
self.search_field.send_keys("phones")
self.search_field.submit()
# get all the anchor elements which have product names # displayed currently on result page using # find_elements_by_xpath method
products = self.driver\
.find_elements_by_xpath("//div[@class='category-products']/ul/li")
# check count of products shown in results
self.assertEqual(2, len(products))
def tearDown(self):
# close the browser window
self.driver.quit()
if __name__ == '__main__':
unittest.main(verbosity=2)
我们需要使用RemoteWebDriver来运行 Appium 的测试。然而,为了 Appium 能够使用所需的平台,我们需要传递一组所需的配置能力,如下面的代码所示:
desired_caps = {}
# platform
desired_caps['device'] = 'iPhone Simulator'
# platform version
desired_caps['version'] = '7.1'
# mobile browser
desired_caps['app'] = 'safari'
desired_caps['device']配置能力被 Appium 用来决定测试脚本应该在哪个平台上执行。在这个例子中,我们使用了iPhone Simulator。对于在 iPad 上运行测试,我们可以指定 iPad 模拟器。
当在真实设备上运行测试时,我们需要指定设备能力值为iPhone或iPad。Appium 将选择通过 USB 连接到 Mac 的设备。
desired_caps['version']配置能力是我们想要使用的 iPhone/iPad 模拟器的版本。在这个例子中,使用了 iOS 7.1 模拟器,这是在撰写本书时的最新 iOS 版本。
我们最后使用的最后一个所需能力是desired_caps['app'],它被 Appium 用来启动目标 app。在这种情况下,它将启动 Safari 浏览器。
最后,我们需要使用RemoteWebDriver和所需的配置能力连接到 Appium 服务器。这通过创建一个Remote实例来完成,如下面的代码所示:
self.driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_caps)
测试的其余部分使用 Selenium API 与移动网络版应用程序交互。正常运行测试。你会看到 Appium 与测试脚本建立会话,并使用 Safari 应用启动 iPhone 模拟器。Appium 将通过在模拟器窗口中的 Safari 应用上运行命令来执行所有测试步骤。
在 Android 上测试
Appium 使用 Android SDK 捆绑的 UI Automator 来驱动 Android 应用程序的自动化。这个过程与 iOS 上的测试非常相似。
Appium 作为一个 HTTP 服务器,通过 JSON 线协议接收来自测试脚本的命令。Appium 将这些命令发送到 UI Automator,以便它们可以在模拟器或真实设备上启动的应用程序中执行。在此过程中,Appium 将 JSON 命令转换为 Android SDK 能理解的 UI Automator Java 命令。这个过程在以下图中展示:

当命令在模拟器或设备上的应用程序上执行时,目标应用程序将响应发送回 UI Automator,UI Automator 再将它发送回 Appium。它将 UI Automator 的响应转换为 Selenium WebDriver JSON 线协议响应,并将它们发送回测试脚本。
为 Android 编写测试
在 Android 上测试应用与我们在 iOS 上所做的大致相同。对于 Android,我们将使用真实设备而不是模拟器(在 Android 社区中,模拟器被称为模拟器)。我们将使用相同的应用程序在 Android 的 Chrome 上进行测试。
在这个例子中,我使用的是三星 Galaxy S III 手机。我们需要在设备上安装 Chrome 浏览器。你可以在 Play Store 上获取 Google Chrome。接下来,我们需要将设备连接到运行 Appium 服务器的机器。
现在,我们将专注于 Android。在这里,我们将尝试在我们的 Android 真实设备上执行测试脚本。我们需要确保我们在 Android 设备上安装了 Chrome,并将我们的设备连接到运行 Appium 服务器的机器。让我们运行以下命令以获取连接到机器的模拟器或设备列表:
./adb devices
Android 调试桥接器(adb)是 Android SDK 中可用的一种命令行工具,它允许你与模拟器实例或连接的真实设备进行通信。
之前的命令将显示连接到主机的所有 Android 设备的列表。在这个例子中,我们已经连接到一个真实设备,如下面的截图所示:

让我们使用为 iOS 创建的测试,并将其修改为适用于 Android。我们将创建一个新的测试,SearchProductsOnAndroid。将以下代码复制到新创建的测试中:
import unittest
from appium import webdriver
class SearchProductsOnAndroid(unittest.TestCase):
def setUp(self):
desired_caps = {}
# platform
desired_caps['device'] = 'Android'
# platform version
desired_caps['version'] = '4.3'
# mobile browser
desired_caps['app'] = 'Chrome'
# to connect to Appium server use RemoteWebDriver
# and pass desired capabilities
self.driver = \
webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_caps)
self.driver.get("http://demo.magentocommerce.com/")
self.driver.implicitly_wait(30)
def test_search_by_category(self):
# click on search icon
self.driver.find_element_by_xpath("//a[@href='#header-search']").click()
# get the search textbox
self.search_field = self.driver.find_element_by_name("q")
self.search_field.clear()
# enter search keyword and submit
self.search_field.send_keys("phones")
self.search_field.submit()
# get all the anchor elements which have product names # displayed currently on result page using # find_elements_by_xpath method
products = self.driver\
.find_elements_by_xpath("//div[@class='category-products']/ul/li")
# check count of products shown in results
self.assertEqual(2, len(products))
def tearDown(self):
# close the browser window
self.driver.quit()
if __name__ == '__main__':
unittest.main(verbosity=2)
在这个例子中,我们将desired_caps['device']能力值分配给了 Android,这将由 Appium 用于在 Android 上运行测试。
接下来,我们在desired_caps['version']能力中提到了 Android 版本 4.3(Jelly Bean)。因为我们想在 Android 的 Chrome 上运行测试,所以我们提到了 Chrome 在desired_caps['app']能力中。
Appium 将使用 adb 返回的设备列表中的第一个设备。它将使用我们提到的期望能力,在设备上启动 Chrome 浏览器,并开始执行测试脚本命令,如下面的截图所示:

下面是测试在真实设备上运行的截图:

使用 Sauce Labs
我们在 第六章 中探讨了 Sauce Labs 进行跨浏览器测试,跨浏览器测试。Sauce 还提供了使用 Appium 测试移动应用程序的支持。实际上,Appium 项目是由 Sauce Labs 开发和支持的。通过最小化对所需能力的修改,我们可以在 Sauce Labs 上使用以下代码运行移动测试:
import unittest
from appium import webdriver
class SearchProductsOnIPhone(unittest.TestCase):
SAUCE_USERNAME = 'upgundecha'
SUACE_KEY = 'c6e7132c-ae27-4217-b6fa-3cf7df0a7281'
def setUp(self):
desired_caps = {}
desired_caps['browserName'] = "Safari"
desired_caps['platformVersion'] = "7.1"
desired_caps['platformName'] = "iOS"
desired_caps['deviceName'] = "iPhone Simulator"
sauce_string = self.SAUCE_USERNAME + ':' + self.SUACE_KEY
self.driver = \
webdriver.Remote('http://' + sauce_string + '@ondemand.saucelabs.com:80/wd/hub', desired_caps)
self.driver.get('http://demo.magentocommerce.com/')
self.driver.implicitly_wait(30)
self.driver.maximize_window()
def test_search_by_category(self):
# click on search icon
self.driver.find_element_by_xpath("//a[@href='#header-search']").click()
# get the search textbox
self.search_field = self.driver.find_element_by_name("q")
self.search_field.clear()
# enter search keyword and submit
self.search_field.send_keys("phones")
self.search_field.submit()
# get all the anchor elements which have
# product names displayed
# currently on result page using
# find_elements_by_xpath method
products = self.driver\
.find_elements_by_xpath("//div[@class='category-products']/ul/li")
# check count of products shown in results
self.assertEqual(2, len(products))
def tearDown(self):
# close the browser window
self.driver.quit()
if __name__ == '__main__':
unittest.main(verbosity=2)
在运行移动测试后,我们可以在 Sauce Labs 控制台中查看结果和视频录制。这大大节省了在本地环境中设置 Appium 的时间和精力,因为 Sauce 提供了各种 SDK 和设置的组合。
摘要
在本章中,我们认识到在移动设备上测试应用程序的需求。我们探讨了 Appium,它正成为 Selenium 测试移动应用程序的核心功能。我们安装并设置了 Appium 以测试示例应用程序的移动版本。
我们在 iPhone 模拟器和 Android 设备上测试了移动网络应用程序。使用 Appium,我们可以测试各种类型的移动应用程序,并使用任何具有 WebDriver 客户端库的编程语言。
在下一章中,你将学习一些良好的实践,例如使用 PageObjects 和数据驱动的测试与 Selenium WebDriver 结合。
第八章。页面对象和数据驱动测试
本章介绍了两个在创建可扩展和可维护的测试自动化框架设计中有用的设计模式。我们将探讨如何使用数据驱动方法,通过使用 Python 库创建数据驱动 Selenium 测试。
在本章的第二部分,你将了解如何使用页面对象模式通过将定位器和其他低级调用从测试用例中分离到一个抽象层,从而创建高度可维护和健壮的测试,这个抽象层类似于应用程序的功能,类似于用户在浏览器窗口中体验到的功能。
在本章中,你将学习:
-
什么是数据驱动测试
-
如何使用数据驱动测试(ddt)库与
unittest库一起创建数据驱动测试 -
如何从外部源读取数据用于数据驱动测试
-
页面对象模式是什么以及它如何帮助创建可维护的测试套件
-
如何实现示例应用程序的页面对象模式
数据驱动测试
通过使用数据驱动测试方法,我们可以通过从外部数据源驱动测试,使用输入值和预期值,而不是每次运行测试时都使用硬编码的值,来使用单个测试验证不同的测试用例集或测试数据。
当我们有一些类似的测试,这些测试包含相同的步骤,但在输入数据、预期值或应用程序状态上有所不同时,这变得很有用。以下是一组具有不同组合的登录测试用例的示例:
| 描述 | 测试数据 | 预期输出 |
|---|---|---|
| 有效的用户名和密码 | 一对有效的用户名和密码 | 用户应使用成功消息登录到应用程序 |
| 无效的用户名和密码 | 无效的用户名和密码 | 应向用户显示登录错误 |
| 有效的用户名和无效的密码 | 有效的用户名和无效的密码 | 应向用户显示登录错误 |
我们可以创建一个可以处理测试数据和前面表格中条件的单个脚本。
通过使用数据驱动测试方法,我们将测试数据与测试逻辑分离,通过使用来自外部源(如 CSV 或电子表格文件)的数据替换硬编码的测试数据,从而使用变量。这也帮助创建可重用的测试,这些测试可以与不同的数据集一起运行,这些数据集可以保存在测试之外。
数据驱动测试还有助于提高测试覆盖率,因为我们可以在最小化需要编写和维护的测试代码量的同时处理多个测试条件。
在本节中,我们将使用 Python 中的ddt库实现数据驱动测试方法,应用于我们在前面章节中创建的一些测试。
使用 ddt 进行数据驱动测试
ddt 库提供了在 Python 中使用 unittest 库编写的测试用例参数化的能力。我们可以使用 ddt 向测试用例提供一组数据,以进行数据驱动测试。
ddt 库提供了一组类和方法装饰器,我们可以使用它们来创建数据驱动测试。
安装 ddt
我们可以使用以下命令行下载和安装 ddt:
pip install ddt
就这些!你可以在 pypi.python.org/pypi/ddt 上找到更多关于 ddt 的信息。
在 unittest 中使用 ddt 创建简单的数据驱动测试
我们将在示例应用程序上使用搜索测试用例,并通过删除用于搜索不同产品和类别的硬编码值将其转换为数据驱动测试。
要创建数据驱动测试,我们需要在测试类中使用 @ddt 装饰器,并在数据驱动测试方法中使用 @data 装饰器。
@data 装饰器接受与我们想要提供给测试的值一样多的参数。这些可以是单个值或列表、元组和字典。对于列表,我们需要使用 @unpack 装饰器,它将元组或列表解包成多个参数。
让我们实现搜索测试,它接受一对参数,用于不同的搜索词和预期结果计数,如下所示代码所示:
import unittest
from ddt import ddt, data, unpack
from selenium import webdriver
@ddt
class SearchDDT(unittest.TestCase):
def setUp(self):
# create a new Firefox session
self.driver = webdriver.Firefox()
self.driver.implicitly_wait(30)
self.driver.maximize_window()
# navigate to the application home page
self.driver.get("http://demo.magentocommerce.com/")
# specify test data using @data decorator
@data(("phones", 2), ("music", 5))
@unpack
def test_search(self, search_value, expected_count):
# get the search textbox
self.search_field = self.driver.find_element_by_name("q")
self.search_field.clear()
# enter search keyword and submit.
# use search_value argument to pass data
self.search_field.send_keys(search_value)
self.search_field.submit()
# get all the anchor elements which have
# product names displayed
# currently on result page using
# find_elements_by_xpath method
products = self.driver.find_elements_by_xpath("//h2[@class='product-name']/a")
# check count of products shown in results
self.assertEqual(expected_count, len(products))
def tearDown(self):
# close the browser window
self.driver.quit()
if __name__ == '__main__':
unittest.main(verbosity=2)
在这个测试中,我们使用 @data 装饰器传递一个元组列表。使用 @unpack 装饰器将这些元组解包成多个参数。test_search() 方法接受 search_value 和 expected_count 参数,这些参数将通过 ddt 映射到元组值,如下所示:
# specify test data using @data decorator
@data(("phones", 2), ("music", 5))
@unpack
def test_search(self, search_value, expected_count):
当我们运行测试时,ddt 将生成新的测试方法,并通过将数据值转换为有效的 Python 标识符来赋予它们有意义的名称。例如,对于前面的测试,ddt 将生成具有以下截图所示名称的新测试方法:

使用外部数据源进行数据驱动测试
在前面的例子中,我们在测试代码中提供了测试数据。然而,你可能会遇到已经在外部源中定义了测试数据的情况,例如文本文件、电子表格或数据库。将测试数据与代码分离,并将其放在外部源中也是一个好主意,这样可以方便维护,并避免每次更新值时都要更改测试代码。
让我们来探讨如何从 逗号分隔值 (CSV) 文件或电子表格中读取测试数据,并将其提供给 ddt。
从 CSV 读取值
我们将使用之前的测试用例,并将提供给 @data 装饰器的数据移动到一个单独的 CSV 文件中,称为 testdata.csv,而不是将其保留在脚本中。这些数据将以如下截图所示的表格格式存储:

接下来,我们将实现 get_data() 方法,该方法接受 CSV 文件的路径和名称。此方法使用 csv 库从文件中读取值,并返回这些值的列表。我们将在 @data 装饰器中使用 get_data() 方法,如以下代码所示:
import csv, unittest
from ddt import ddt, data, unpack
from selenium import webdriver
def get_data(file_name):
# create an empty list to store rows
rows = []
# open the CSV file
data_file = open(file_name, "rb")
# create a CSV Reader from CSV file
reader = csv.reader(data_file)
# skip the headers
next(reader, None)
# add rows from reader to list
for row in reader:
rows.append(row)
return rows
@ddt
class SearchCsvDDT(unittest.TestCase):
def setUp(self):
# create a new Firefox session
self.driver = webdriver.Firefox()
self.driver.implicitly_wait(30)
self.driver.maximize_window()
# navigate to the application home page
self.driver.get("http://demo.magentocommerce.com/")
# get the data from specified csv file by
# calling the get_data function
@data(*get_data("testdata.csv"))
@unpack
def test_search(self, search_value, expected_count):
self.search_field = self.driver.find_element_by_name("q")
self.search_field.clear()
# enter search keyword and submit.
self.search_field.send_keys(search_value)
self.search_field.submit()
# get all the anchor elements which have
# product names displayed
# currently on result page using
# find_elements_by_xpath method
products = self.driver.find_elements_by_xpath("//h2[@class='product-name']/a")
expected_count = int(expected_count)
if expected_count > 0:
# check count of products shown in results
self.assertEqual(expected_count, len(products))
else:
msg = self.driver.find_element_by_class_name
("note-msg")
self.assertEqual("Your search returns no results.", msg.text)
def tearDown(self):
# close the browser window
self.driver.quit()
if __name__ == '__main__':
unittest.main()
当执行此测试时,@data 将调用 get_data() 方法,该方法将读取提供的文件,并将从 @data 返回的值列表返回。这些值将被解包,并为每一行生成测试方法。
从 Excel 读取值
在 Excel 电子表格中维护测试数据是另一种常见做法。它还有助于非技术用户通过在电子表格中添加一行数据来简单地定义新的测试。以下截图是维护 Excel 电子表格中数据的示例:

从 Excel 电子表格中读取值需要另一个名为 xlrd 的库,可以使用以下命令进行安装:
pip install xlrd
注意
xlrd 库提供了对工作簿、工作表和单元格的读取访问权限,以便读取数据。它不会写入电子表格。对于写入数据,我们可以使用 xlwt 库。我们还可以使用 openpyxl 在电子表格中读取和写入数据。更多信息请访问 www.python-excel.org/。
让我们修改上一个示例中的 get_data() 方法,以便从电子表格中读取数据到列表中,并按以下代码修改测试:
import xlrd, unittest
from ddt import ddt, data, unpack
from selenium import webdriver
def get_data(file_name):
# create an empty list to store rows
rows = []
# open the specified Excel spreadsheet as workbook
book = xlrd.open_workbook(file_name)
# get the first sheet
sheet = book.sheet_by_index(0)
# iterate through the sheet and get data from rows in list
for row_idx in range(1, sheet.nrows):
rows.append(list(sheet.row_values(row_idx, 0, sheet.ncols)))
return rows
@ddt
class SearchExcelDDT(unittest.TestCase):
def setUp(self):
# create a new Firefox session
self.driver = webdriver.Firefox()
self.driver.implicitly_wait(30)
self.driver.maximize_window()
# navigate to the application home page
self.driver.get("http://demo.magentocommerce.com/")
# get the data from specified Excel spreadsheet
# by calling the get_data function
@data(*get_data("TestData.xlsx"))
@unpack
def test_search(self, search_value, expected_count):
self.search_field = self.driver.find_element_by_name("q")
self.search_field.clear()
# enter search keyword and submit.
self.search_field.send_keys(search_value)
self.search_field.submit()
# get all the anchor elements which have
# product names displayed
# currently on result page using
# find_elements_by_xpath method
products = self.driver.find_elements_by_xpath("//h2[@class='product-name']/a")
if expected_count > 0:
# check count of products shown in results
self.assertEqual(expected_count, len(products))
else:
msg = self.driver.find_element_by_class_name("note-msg")
self.assertEqual("Your search returns no results.", msg.text)
def tearDown(self):
# close the browser window
self.driver.quit()
if __name__ == '__main__':
unittest.main()
与之前 CSV 文件的示例类似,当执行此测试时,@data 将调用 get_data() 方法,该方法将读取提供的文件,并将从电子表格中返回的值列表返回给 @data。这些值将被解包,并为每一行生成测试方法。
小贴士
从数据库读取值
如果你需要从数据库中读取值,只需修改 get_data() 方法并使用适当的库连接到数据库,然后使用 SQL 查询将值读取回列表中。
页面对象模式
到目前为止,我们直接使用 unittest 将 Selenium WebDriver 测试写入 Python 类中。我们在这些类中指定定位器和测试用例步骤。这段代码是一个好的开始;然而,随着我们继续向测试套件中添加越来越多的测试,它将变得难以维护。这将使测试变得脆弱。
开发可维护和可重用的测试代码对于可持续的测试自动化至关重要,测试代码应被视为生产代码,并且在开发测试代码时应应用类似的标准和模式。
为了克服这些问题,我们可以在创建测试时使用各种设计模式和原则,例如不要重复自己(DRY)和代码重构技术。如果你是一名开发者,你可能已经在使用这些技术了。
页面对象模式是 Selenium 用户社区中高度使用的模式之一,用于构建测试,使它们与低级动作分离,并提供高级抽象。你可以将页面对象模式与外观模式进行比较,它允许为复杂代码创建简化的接口。
页面对象模式提供了从正在测试的应用程序创建代表每个网页的对象。我们可以为每个页面定义类,模拟该页面的所有属性和动作。这创建了一个测试代码与我们将要测试的页面和应用程序功能的技术实现之间的分离层,通过隐藏定位器、处理元素的低级方法和业务功能。相反,页面对象将为测试提供一个高级 API 来处理页面功能。
测试应使用这些页面对象在高级别,其中底层页面中属性或动作的任何更改都不应破坏测试。使用页面对象模式提供了以下好处:
-
创建一个高级抽象,有助于在底层页面被开发者修改时最小化更改。因此,你只需更改页面对象,而调用测试将不受影响。
-
创建可跨多个测试案例共享的可重用代码。
-
测试更易于阅读、灵活且易于维护。
让我们开始重构我们在早期章节中创建的测试,并实现为正在测试的应用程序提供高级抽象的页面对象。在这个例子中,我们将为示例应用程序中选定的页面创建以下结构。我们将开始实现一个基本页面对象,它将被所有其他页面用作模板。基本对象还将提供所有其他页面可用的功能区域;例如,搜索功能在应用程序的所有页面上都可用。我们将创建一个搜索区域对象,它将适用于从基本页面继承的所有页面。我们将实现一个代表应用程序主页的类,搜索结果页面,它显示与搜索标准匹配的产品列表;以及一个产品页面,它提供与产品相关的属性和动作。我们将创建如以下图表所示的结构:

组织测试
在我们开始为正在测试的示例应用程序实现页面对象之前,让我们实现一个BaseTestCase类,它将为我们提供setUp()和tearDown()方法,这样我们就不需要为每个创建的测试类编写这些方法。我们还可以将可重用代码放在这个类中。创建basetestcase.py并实现如以下代码所示的BaseTestCase类:
import unittest
from selenium import webdriver
class BaseTestCase(unittest.TestCase):
def setUp(self):
# create a new Firefox session
self.driver = webdriver.Firefox()
self.driver.implicitly_wait(30)
self.driver.maximize_window()
# navigate to the application home page
self.driver.get('http://demo.magentocommerce.com/')
def tearDown(self):
# close the browser window
self.driver.quit()
基础页面对象
BasePage对象将作为我们将在测试套件中创建的所有页面对象的父对象。基本页面提供了页面对象可以使用的基本代码。让我们创建base.py文件并实现BasePage,如下面的代码所示:
from abc import abstractmethod
class BasePage(object):
""" All page objects inherit from this """
def __init__(self, driver):
self._validate_page(driver)
self.driver = driver
@abstractmethod
def _validate_page(self, driver):
return
""" Regions define functionality available throughall page objects """
@property
def search(self):
from search import SearchRegion
return SearchRegion(self.driver)
class InvalidPageException(Exception):
""" Throw this exception when you don't find the correct page """
pass
我们添加了一个名为_validate_page()的抽象方法,该方法将由继承自BasePage的页面对象实现,以验证在测试使用属性或操作之前,它们所代表的页面是否已在浏览器中加载。
我们还创建了一个名为search的属性,它返回SearchRegion对象。这类似于页面对象。然而,SearchRegion代表应用程序所有页面显示的搜索框。因此,我们将从BasePage类中共享此内容添加到每个页面对象中。
我们还实现了InvalidPageException,该异常用于_validate_page()方法。如果验证页面失败,将引发InvalidPageExecption。
实现页面对象
现在,让我们开始实现我们将要处理的每个页面的页面对象。
-
首先,我们将定义
HomePage。创建homepage.py文件并实现HomePage类,如下面的代码所示:from base import BasePage from base import InvalidPageException class HomePage(BasePage): _home_page_slideshow_locator = 'div.slideshow-container' def __init__(self, driver): super(HomePage, self).__init__(driver) def _validate_page(self, driver): try: driver.find_element_by_class_name(self._home_page_slideshow_locator) except: raise InvalidPageException("Home Page not loaded")我们将遵循的一种做法是将定位器字符串与其使用的地方分开。我们将创建一个使用
_前缀的私有变量来存储定位器。例如,_home_page_slideshow_locator变量存储了显示在应用程序主页上的幻灯片组件的定位器。我们将使用它来验证浏览器是否确实显示了主页,如下所示:_home_page_slideshow_locator = 'div.slideshow-container'我们还在
HomePage类中实现了_validate_page()方法。此方法验证是否使用用于在主页上显示幻灯片的元素在浏览器中加载了主页。 -
接下来,我们将实现
SearchRegion类,该类处理应用程序的搜索功能。它提供了searchFor()方法,该方法返回表示搜索结果页面的SearchResult类。创建一个新的search.py文件并实现这两个类,如下面的代码所示:from base import BasePage from base import InvalidPageException from product import ProductPage class SearchRegion(BasePage): _search_box_locator = 'q' def __init__(self, driver): super(SearchRegion, self).__init__(driver) def searchFor(self, term): self.search_field = self.driver.find_element_by_name(self._search_box_locator) self.search_field.clear() self.search_field.send_keys(term) self.search_field.submit() return SearchResults(self.driver) class SearchResults(BasePage): _product_list_locator = 'ul.products-grid > li' _product_name_locator = 'h2.product-name a' _product_image_link = 'a.product-image' _page_title_locator = 'div.page-title' _products_count = 0 _products = {} def __init__(self, driver): super(SearchResults, self).__init__(driver) results = self.driver.find_elements_by_css_selector(self._product_list_locator) for product in results: name = product.find_element_by_css_selector(self._product_name_locator).text self._products[name] = product.find_element_by_css_selector(self._product_image_link) def _validate_page(self, driver): if 'Search results for' not in driver.title: raise InvalidPageException('Search results not loaded') @property def product_count(self): return len(self._products) def get_products(self): return self._products def open_product_page(self, product_name): self._products[product_name].click() return ProductPage(self.driver) -
最后,我们将实现
ProductPage类,该类包含一些与产品相关的属性。我们可以从SearchResults类中访问产品,该类有一个方法可以打开给定产品的产品详情页面。创建一个product.py文件并实现ProductPage类,如下面的代码所示:from base import BasePage from base import InvalidPageException class ProductPage(BasePage): _product_view_locator = 'div.product-view' _product_name_locator = 'div.product-name span' _product_description_locator = 'div.tab-content div.std' _product_stock_status_locator = 'p.availability span.value' _product_price_locator = 'span.price' def __init__(self, driver): super(ProductPage, self).__init__(driver) @property def name(self): return self.driver.\ find_element_by_css_selector(self._product_name_locator)\ .text.strip() @property def description(self): return self.driver.\ find_element_by_css_selector(self._product_description_locator)\ .text.strip() @property def stock_status(self): return self.driver.\ find_element_by_css_selector(self._product_stock_status_locator)\ .text.strip() @property def price(self): return self.driver.\ find_element_by_css_selector(self._product_price_locator)\ .text.strip() def _validate_page(self, driver): try: driver.find_element_by_css_selector(self._product_view_locator) except: raise InvalidPageException('Product page not loaded')
你还可以在产品页面上添加操作,将产品添加到购物车,或者用于产品比较。此外,将返回评分和其他与产品相关的信息的属性添加回测试中。
使用页面对象创建测试
让我们创建一个使用BaseTestCase并调用我们创建的页面对象来测试应用程序搜索功能的测试。此测试创建HomePage类的实例并调用searchFor()方法,该方法返回SearchResults类的实例。随后,测试调用SearchResults类的open_product_page()方法来打开结果中列出的指定产品的详细信息。测试检查样本产品的属性。创建一个searchtest.py文件并实现如以下代码所示的SearchProductTest测试:
import unittest
from homepage import HomePage
from BaseTestCase import BaseTestCase
class SearchProductTest(BaseTestCase):
def testSearchForProduct(self):
homepage = HomePage(self.driver)
search_results = homepage.search.searchFor('earphones')
self.assertEqual(2, search_results.product_count)
product = search_results.open_product_page('MADISON EARBUDS')
self.assertEqual('MADISON EARBUDS', product.name)
self.assertEqual('$35.00', product.price)
self.assertEqual('IN STOCK', product.stock_status)
if __name__ == '__main__':
unittest.main(verbosity=2)
注意,我们没有在这个测试中编写setUp()和tearDown()方法。我们从这个实现这些方法的BaseTestCase继承了这个测试类。如果我们想进行特定于测试的设置或清理,我们可以重载这些方法。
在这个例子中,我们实现了用于搜索工作流程导航的页面对象。您也可以实现类似的页面对象或区域,用于购物车、账户注册、登录等。
摘要
在本章中,我们认识到需要编写数据驱动测试,并使用页面对象模式来组织测试代码,以提高可重用性、可扩展性和可维护性。数据驱动模式使我们能够将测试数据与测试用例分离,因此我们可以重用测试代码来测试多个测试数据。我们还探讨了如何使用ddt库与unittest一起实现数据驱动测试,并从各种外部源读取数据。您学习了页面对象模式及其在通过为示例应用程序实现页面对象并创建使用页面对象的测试来构建可维护的测试套件方面的好处。
在下一章中,您将学习一些使用 Selenium WebDriver API 的高级技术,例如从测试运行中捕获屏幕截图和视频、执行鼠标和键盘操作、处理会话 cookie 等。
第九章:Selenium WebDriver 高级技术
到目前为止,本书中我们已看到如何设置 Selenium WebDriver 以测试 Web 应用程序,以及一些用于在浏览器中定位和与各种元素交互的重要特性和 API。
在本章中,我们将探讨 Selenium WebDriver 的一些高级 API。当测试相对复杂的应用程序时,这些功能非常有用。
在本章中,你将了解更多关于:
-
使用
Actions类创建模拟键盘或鼠标事件的测试 -
模拟鼠标操作,如拖放和双击
-
运行 JavaScript 代码
-
捕获测试运行的截图和视频
-
处理浏览器导航和 Cookie
执行键盘和鼠标操作的方法
Selenium WebDriver 的高级用户交互 API 允许我们执行从简单的键盘和鼠标事件到复杂的鼠标事件(如拖放、按下快捷键组合、按住键和执行鼠标操作)的操作。这是通过使用 Selenium WebDriver Python API 中的 ActionChains 类来实现的。
这里是 ActionChains 类支持执行键盘和鼠标事件的重要方法列表:
| 方法 | 描述 | 参数 | 示例 |
|---|---|---|---|
click(on_element=None) |
此方法执行点击操作。 | on_element: 这是点击的元素。如果为 None,则在当前鼠标位置点击。 |
click(main_link) |
click_and_hold(on_element=None) |
此方法在元素上按下鼠标左键。 | on_element: 这是点击并按下鼠标按钮的元素。如果为 None,则在当前鼠标位置点击。 |
click_and_hold(gmail_link) |
double_click(on_element=None) |
此方法在元素上执行双击操作。 | on_element: 这是双击的元素。如果为 None,则在当前鼠标位置点击。 |
double_click(info_box) |
drag_and_drop(source, target) |
此方法执行拖放操作。 | source: 这是鼠标按下时要拖动的元素。target: 鼠标释放的元素。 |
drag_and_drop(img, canvas) |
key_down(value, element=None) |
此方法仅发送按键,不释放它。这应仅与修饰键(如 Ctrl、Alt 和 Shift 键)一起使用。 | key: 这是要发送的修饰键。值在 Keys 类中定义。target: 发送键的元素。如果为 None,则发送到当前聚焦的元素。 |
key_down(Keys.SHIFT)\ send_keys('n')\ key_up(Keys.SHIFT) |
key_up(value, element=None) |
此方法释放一个修饰键。 | key: 这是要发送的修饰键。值在 Keys 类中定义。target: 这是发送键的元素。如果为 None,则发送到当前聚焦的元素。 |
|
move_to_element(to_element) |
这个方法将鼠标移动到元素的中间。 | to_element: 这是需要移动到的元素。 |
move_to_element(gmail_link) |
perform() |
这个方法执行所有存储的动作。 | perform() |
|
release(on_element=None) |
这个方法释放一个按住的鼠标按钮。 | on_element: 这是鼠标释放的元素。 |
release(banner_img) |
send_keys(keys_to_send) |
这个方法向具有当前焦点的元素发送按键。 | keys_to_send: 这是需要发送的按键 |
send_keys("hello") |
send_keys_to_element(element, keys_to_send) |
这个方法向一个元素发送按键。 | element: 这是需要发送按键的元素。keys_to_send: 需要发送的按键。 |
send_keys_to_element(firstName, "John") |
详细列表请访问 selenium.googlecode.com/git/docs/api/py/webdriver/selenium.webdriver.common.action_chains.html。
注意
在 Safari 上不支持 Interactions API。此外,在各个浏览器上对某些事件也有限制。更多详情请参阅 code.google.com/p/selenium/wiki/AdvancedUserInteractions。
键盘操作
让我们创建一个测试来演示如何使用键盘操作,例如按下快捷键组合。在示例应用中,当我们按下 Shift + N 键组合时,标签的颜色会改变,如下面的代码所示:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
import unittest
class HotkeyTest(unittest.TestCase):
URL = "https://rawgit.com/jeresig/jquery.hotkeys/master/test-static-05.html"
def setUp(self):
self.driver = webdriver.Chrome()
self.driver.get(self.URL)
self.driver.implicitly_wait(30)
self.driver.maximize_window()
def test_hotkey(self):
driver = self.driver
shift_n_label = WebDriverWait(self.driver, 10).\
until(expected_conditions.visibility_of_element_located((By.ID, "_shift_n")))
ActionChains(driver).\
key_down(Keys.SHIFT).\
send_keys('n').\
key_up(Keys.SHIFT).perform()
self.assertEqual("rgba(12, 162, 255, 1)",
shift_n_label.value_of_css_property("background-color"))
def tearDown(self):
self.driver.close()
if __name__ == "__main__":
unittest.main(verbosity=2)
我们可以使用 ActionChains 类执行快捷键按下操作。在这个例子中,我们使用 key_down()、send_key() 和 key_up() 方法的组合来执行 Shift + N 键的按下,就像真实用户按下这些键一样:
ActionChains(driver).\
key_down(Keys.SHIFT).\
send_keys('n').\
key_up(Keys.SHIFT).perform()
ActionChains 类需要传递 driver 实例。然后我们可以通过调用可用方法并执行调用 perform() 方法的动作来安排事件序列。
鼠标移动
这里是另一个示例,通过调用 ActionChains 类的 move_to_element() 方法来调用鼠标移动事件。这相当于 onMouseOver 事件。move_to_element() 方法会将鼠标光标从当前位置移动到指定的元素。
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.common.action_chains import ActionChains
import unittest
class ToolTipTest (unittest.TestCase):
def setUp(self):
self.driver = webdriver.Firefox()
self.driver.get("http://jqueryui.com/tooltip/")
self.driver.implicitly_wait(30)
self.driver.maximize_window()
def test_tool_tip(self):
driver = self.driver
frame_elm = driver.find_element_by_class_name("demo-frame")
driver.switch_to.frame(frame_elm)
age_field = driver.find_element_by_id("age")
ActionChains(self.driver).move_to_element(age_field).perform()
tool_tip_elm = WebDriverWait(self.driver, 10)\ .until(expected_conditions.visibility_of_element_located((By.CLASS_NAME, "ui-tooltip-content")))
# verify tooltip message
self.assertEqual("We ask for your age only for statistical purposes.", tool_tip_elm.text)
def tearDown(self):
self.driver.close()
if __name__ == "__main__":
unittest.main(verbosity=2)
双击方法
我们可以通过调用 ActionChains 类的 double_click() 方法来双击一个元素,如下所示:
from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
import unittest
class DoubleClickTest (unittest.TestCase):
URL = "http://api.jquery.com/dblclick/"
def setUp(self):
self.driver = webdriver.Chrome()
self.driver.get(self.URL)
self.driver.maximize_window()
def test_double_click(self):
driver = self.driver
frame = driver.find_element_by_tag_name("iframe")
driver.switch_to.frame(frame)
box = driver.find_element_by_tag_name("div")
# verify color is Blue
self.assertEqual("rgba(0, 0, 255, 1)",
box.value_of_css_property("background-color"))
ActionChains(driver).move_to_element(
driver.find_element_by_tag_name("span"))\
.perform()
ActionChains(driver).double_click(box).perform()
# verify Color is Yellow
self.assertEqual("rgba(255, 255, 0, 1)",
box.value_of_css_property("background-color"))
def tearDown(self):
self.driver.close()
if __name__ == "__main__":
unittest.main(verbosity=2)
拖放方法
在 Selenium WebDriver 中,我们可以通过调用 ActionChains 类的 drag_and_drop() 方法来执行拖放操作。这个方法需要指定将被拖动的源元素和目标元素,这里是一个 drag_and_drop 方法的示例:
from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
import unittest
class DragAndDropTest (unittest.TestCase):
URL = "http://jqueryui.com/resources/demos/droppable/default.html"
def setUp(self):
self.driver = webdriver.Firefox()
self.driver.get(self.URL)
self.driver.maximize_window(30)
self.driver.maximize_window()
def test_drag_and_drop(self):
driver = self.driver
source = driver.find_element_by_id("draggable")
target = driver.find_element_by_id("droppable")
ActionChains(self.driver).drag_and_drop(source, target).perform()
self.assertEqual("Dropped!", target.text)
def tearDown(self):
self.driver.close()
if __name__ == "__main__":
unittest.main(verbosity=2)
执行 JavaScript
我们可以通过使用WebDriver类中提供的方法来执行 JavaScript 代码。当我们无法使用 Selenium WebDriver API 执行某些操作或我们想要测试 JavaScript 代码时,这非常有用。
WebDriver类提供了以下方法来执行 JavaScript 代码:
| 方法 | 描述 | 参数 | 示例 |
|---|---|---|---|
execute_async_script(script, *args) |
此方法在当前窗口/框架中异步执行 JavaScript。 | script: 这是 JavaScript 代码args: 这是 JavaScript 代码的任何参数 |
driver.execute_async_script("return document.title") |
execute_script(script, *args) |
此方法在当前窗口/框架中同步执行 JavaScript。 | script: 这是 JavaScript 代码args: 这是 JavaScript 代码的任何参数 |
driver.execute_script("return document.title") |
让我们创建一个使用实用方法的测试,该方法在执行这些元素的操作之前,通过使用 JavaScript 方法突出显示这些元素:
from selenium import webdriver
import unittest
class ExecuteJavaScriptTest (unittest.TestCase):
def setUp(self):
# create a new Firefox session
self.driver = webdriver.Firefox()
self.driver.implicitly_wait(30)
self.driver.maximize_window()
# navigate to the application home page
self.driver.get("http://demo.magentocommerce.com/")
def test_search_by_category(self):
# get the search textbox
search_field = self.driver.find_element_by_name("q")
self.highlightElement(search_field)
search_field.clear()
# enter search keyword and submit
self.highlightElement(search_field)
search_field.send_keys("phones")
search_field.submit()
# get all the anchor elements which have product names # displayed currently on result page using # find_elements_by_xpath method
products = self.driver.find_elements_by_xpath("//h2[@class='product-name']/a")
# check count of products shown in results
self.assertEqual(2, len(products))
def tearDown(self):
# close the browser window
self.driver.quit()
def highlightElement(self, element):
self.driver.execute_script("arguments[0].setAttribute('style', arguments[1]);",
element, "color: green; border: 2px solid green;")
self.driver.execute_script("arguments[0].setAttribute('style', arguments[1]);",
element , "")
if __name__ == "__main__":
unittest.main(verbosity=2)
我们可以通过调用WebDriver类的execute_script方法来执行 JavaScript 代码,如下面的示例所示。我们还可以通过此方法将参数传递给 JavaScript 代码。在这个例子中,我们暂时修改边框样式,然后撤销这个更改。这将突出显示执行期间给定的元素,以绿色边框显示。了解屏幕上正在执行哪个步骤非常有用:
def highlightElement(self, element):
self.driver.execute_script("arguments[0].setAttribute('style', arguments[1]);",
element, "color: green; border: 2px solid green;")
self.driver.execute_script("arguments[0].setAttribute('style', arguments[1]);",
element , "")
捕获失败的截图
在测试运行期间捕获截图,当您想要将失败通知开发者时非常有用。它还有助于调试测试或创建测试运行的证据。Selenium WebDriver 自带在测试运行期间捕获截图的方法。WebDriver类提供了以下方法来捕获和保存截图:
| 方法 | 描述 | 参数 | 示例 |
|---|---|---|---|
Save_screenshot(filename) |
此方法获取当前窗口的截图,并将图像保存到指定的文件中。 | filename: 这是截图将保存到的文件路径/名称 |
Driver.save_screenshot("homepage.png") |
get_screenshot_as_base64() |
此方法获取当前窗口的截图,并以 base64 编码的字符串形式返回,这在将图像嵌入 HTML 中非常有用。 | driver.get_screenshot_as_base64() |
|
get_screenshot_as_file(filename) |
此方法获取当前窗口的截图。如果发生任何 IO 错误,则返回False,否则返回True。它使用完整的文件名路径。 |
filename: 这是截图将保存到的文件路径/名称 |
driver.get_screenshot_as_file('/results/screenshots/HomePage.png') |
get_screenshot_as_png() |
此方法获取当前窗口的截图作为二进制数据。 | driver.get_screenshot_as_png() |
让我们创建一个测试,当它导致失败时捕获屏幕截图。在这个例子中,我们将定位到应用程序主页上应该存在的元素。然而,如果测试找不到这个元素,它将抛出 NoSuchElementException 并捕获浏览器窗口中显示的页面截图,我们可以将其用于调试或作为证据发送给开发者。
from selenium import webdriver
import datetime, time, unittest
from selenium.common.exceptions import NoSuchElementException
class ScreenShotTest(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Firefox()
self.driver.get("http://demo.magentocommerce.com/")
def test_screen_shot(self):
driver = self.driver
try:
promo_banner_elem = driver.find_element_by_id("promo_banner")
self.assertEqual("Promotions", promo_banner_elem.text)
except NoSuchElementException:
st = datetime.datetime\
.fromtimestamp(time.time()).strftime('%Y%m%d_%H%M%S')
file_name = "main_page_missing_banner" + st + ".png"
driver.save_screenshot(file_name)
raise
def tearDown(self):
self.driver.close()
if __name__ == "__main__":
unittest.main(verbosity=2)
在这个例子中,当测试找不到促销横幅元素时,它将使用 save_screenshot() 方法进行截图。我们需要传递要保存结果的文件路径和名称,如下所示:
try:
promo_banner_elem = driver.find_element_by_id("promo_banner")
self.assertEqual("Promotions", promo_banner_elem.text)
except NoSuchElementException:
st = datetime.datetime.fromtimestamp(time.time()).strftime('%Y%m%d_%H%M%S')
file__name = "main_page_missing_banner" + st + ".png"
driver.save_screenshot(file__name)
raise
小贴士
在捕获和保存屏幕截图时,建议为图像文件使用独特的名称,例如包括时间戳,并使用 便携式网络 图形 (PNG) 格式以实现文件的最高压缩,这也会导致文件大小最小化。
录制测试运行的视频
与捕获屏幕截图类似,录制测试运行的视频有助于以可视化的方式记录完整的测试会话。我们可以观看录制的视频来了解测试运行期间发生了什么。这也可以作为其他项目利益相关者的证据,或者也可以用作演示。
Selenium WebDriver 没有内置的视频录制功能。可以通过使用名为 Castro 的 Python 库来单独录制测试运行的视频。它是由 Selenium 的创造者 Jason Huggin 创建的。
Castro 基于名为 Pyvnc2swf 的跨平台屏幕录制工具(参考 www.unixuser.org/~euske/vnc2swf/pyvnc2swf.html)。它使用 VNC 协议捕获测试运行的屏幕,并生成 Shockwave Flash (SWF) 电影文件。
Castro 还允许使用 VNC 协议从远程机器录制会话。需要在该机器上安装 VNC 程序来录制视频。在安装 Castro 之前,我们需要安装 PyGame 库。PyGame 包不能通过 pip 命令安装,我们需要从 www.pygame.org/download.shtml 获取 PyGame 安装程序。
我们可以使用以下命令行使用 pip 安装 Castro:
pip install Castro
我们还需要在将要执行测试的桌面上安装或启用 VNC。在 Windows 上,我们需要安装一个 VNC 程序。TightVNC (www.tightvnc.com/) 将是一个不错的选择。在 Windows 上安装 TightVNC 服务器和查看器。
在 Ubuntu 上,前往 设置 | 首选项 | 远程桌面 并勾选 允许其他用户查看您的 桌面 复选框。对于 Mac,我们可以从 www.testplant.com/products/vine/ 安装 Vine VNC 服务器或从 系统偏好设置 中启用 远程桌面。
让我们捕捉一下我们在前几章中创建的搜索测试用例的视频录制,如下面的代码所示:
import unittest
from selenium import webdriver
from castro import Castro
class SearchProductTest(unittest.TestCase):
def setUp(self):
# create an instance of Castro and provide name for the output # file
self.screenCapture = Castro(filename="testSearchByCategory.swf")
# start the recording of movie
self.screenCapture.start()
# create a new Firefox session
self.driver = webdriver.Firefox()
self.driver.implicitly_wait(30)
self.driver.maximize_window()
# navigate to the application home page
self.driver.get("http://demo.magentocommerce.com/")
def test_search_by_category(self):
# get the search textbox
search_field = self.driver.find_element_by_name("q")
search_field.clear()
# enter search keyword and submit
search_field.send_keys("phones")
search_field.submit()
# get all the anchor elements which have product names # displayed
# currently on result page using find_elements_by_xpath method
products = self.driver.find_elements_by_xpath("//h2[@class='product-name']/a")
# check count of products shown in results
self.assertEqual(2, len(products))
def tearDown(self):
# close the browser window
self.driver.quit()
# Stop the recording
self.screenCapture.stop()
if __name__ == '__main__':
unittest.main(verbosity=2)
要创建一个新的视频录制会话,我们需要创建一个 Castro 对象,并用构造函数的参数初始化实例,该参数是捕获文件的路径和名称。屏幕捕获通过start()方法开始,它将记录整个屏幕,直到调用stop方法。使用setUp()方法进行测试是初始化 Castro 实例并开始录制的最佳方式,如下面的示例所示:
def setUp(self):
#Create an instance of Castro and provide name for the output # file
self.screenCapture = Castro(filename="testSearchByCategory.swf")
# Start the recording of movie
self.screenCapture.start()
# create a new Firefox session
self.driver = webdriver.Firefox()
self.driver.implicitly_wait(30)
self.driver.maximize_window()
# navigate to the application home page
self.driver.get("http://demo.magentocommerce.com/")
要停止录制,请调用stop()方法。同样,tearDown()方法是一个调用此方法的好地方,这样我们就可以捕捉整个测试用例,如下面的代码所示:
def tearDown(self):
# close the browser window
self.driver.quit()
# Stop the recording
self.screenCapture.stop()
如果一个类中有多个测试,我们可以使用setUp()和tearDown()方法在类级别初始化和停止录制,而不是为每个测试创建一个新的文件。
处理弹出窗口
测试弹出窗口涉及通过其名称属性或窗口句柄来识别弹出窗口,切换驱动程序上下文到所需的弹出窗口,然后在该弹出窗口上执行步骤,最后切换回父窗口。
当我们从测试中创建浏览器实例时,它是一个父窗口,并且从父窗口创建的任何后续窗口都被称为子窗口或弹出窗口。只要它属于当前的 WebDriver 上下文,我们就可以与任何子窗口一起工作。
这里是一个弹出窗口的例子:

创建一个新的测试类PopupWindowTest,其中包含测试方法test_popup_window(),如下面的代码所示:
from selenium import webdriver
import unittest
class PopupWindowTest(unittest.TestCase):
URL = "https://rawgit.com/upgundecha/learnsewithpython/master/pages/Config.html"
def setUp(self) :
self.driver = webdriver.Firefox()
self.driver.get(self.URL)
self.driver.maximize_window()
def test_window_popup(self):
driver = self.driver
# save the WindowHandle of Parent Browser Window
parent_window_id = driver.current_window_handle
# clicking Help Button will open Help Page in a new Popup # Browser Window
help_button = driver.find_element_by_id("helpbutton")
help_button.click()
driver.switch_to.window("HelpWindow")
driver.close()
driver.switch_to.window(parent_window_id)
def tearDown(self):
self.driver.close()
if __name__ == "__main__":
unittest.main(verbosity=2)
在上下文移动到子窗口之前,我们可以使用current_window_handle属性保存父窗口的句柄。我们将在稍后使用此值从子窗口切换回父窗口。我们可以通过调用WebDriver类的switch_to.window()方法使用其名称或窗口句柄来切换到子窗口。在这个例子中,我们使用窗口的名称,如下所示:
driver.switch_to_window("HelpWindow")
在执行操作并检查帮助窗口后,我们可以通过调用close()方法来关闭它,并切换回父窗口,如下所示:
driver.close()
# switch back to Home page window using the handle
driver.switch_to_window(default_window)
管理 Cookie
Cookie 对于任何 Web 应用程序来说都很重要,因为它可以在用户的计算机上存储信息,从而提供更好的用户体验。Cookie 用于存储用户偏好、登录信息和客户端的详细信息。Selenium WebDriver API 提供了各种方法在测试期间管理这些 Cookie。我们可以在测试期间读取 Cookie 值、添加 Cookie 和删除 Cookie。这可以用来测试应用程序在 Cookie 被操作时的反应。WebDriver类提供了以下方法来管理 Cookie:
| 方法 | 描述 | 参数 | 示例 |
|---|---|---|---|
add_cookie(cookie_dict) |
此方法将 cookie 添加到当前会话 | cookie_dict: 这是一个包含 cookie 名称和值对的字典 |
driver.add_cookie({"foo","bar"}) |
delete_all_cookies() |
此方法删除当前会话中的所有 cookies | driver.delete_all_cookies() |
|
delete_cookie(name) |
此方法删除指定名称的单个 cookie | name: 这是要删除的 cookie 的名称 |
driver.delete_cookie("foo") |
get_cookie(name) |
此方法通过名称获取单个 cookie,如果找到则返回 cookie 的字典,如果没有找到则返回 none | name: 这是搜索 cookie 的名称 |
driver.get_cookie("foo") |
get_cookies() |
此方法获取与当前会话中 cookies 对应的一组字典 | driver.get_cookies() |
这里有一个示例,验证了用于存储用户在演示应用程序主页上选择的语言的 cookie:
import unittest
from selenium import webdriver
from selenium.webdriver.support.ui import Select
class CookiesTest(unittest.TestCase):
def setUp(self):
# create a new Firefox session
self.driver = webdriver.Firefox()
self.driver.implicitly_wait(30)
self.driver.maximize_window()
# navigate to the application home page
self.driver.get("http://demo.magentocommerce.com/")
def test_store_cookie(self):
driver = self.driver
# get the Your language dropdown as instance of Select class
select_language = \
Select(self.driver.find_element_by_id("select-language"))
# check default selected option is English
self.assertEqual("ENGLISH", select_language.first_selected_option.text)
# store cookies should be none
store_cookie = driver.get_cookie("store")
self.assertEqual(None, store_cookie)
# select an option using select_by_visible text
select_language.select_by_visible_text("French")
# store cookie should be populated with selected country
store_cookie = driver.get_cookie("store")['value']
self.assertEqual("french", store_cookie)
def tearDown(self):
# close the browser window
self.driver.quit()
if __name__ == '__main__':
unittest.main(verbosity=2)
我们可以使用 WebDriver 类的 get_cookie() 方法检索 cookie 的值。我们需要传递 cookie 的名称。此方法返回一个字典。
摘要
在本章中,你学习了 Selenium WebDriver API 的高级功能,用于处理键盘和鼠标事件、捕获截图、录制视频和处理 cookies。
我们使用了 ActionChains 类来执行各种键盘和鼠标操作。当处理大量使用键盘和鼠标动作的应用程序时,这些功能非常有用。
你看到了如何在测试中运行 JavaScript 代码。当处理使用 Ajax 的应用程序时,这是一个非常强大的功能,我们可以从脚本中使用底层的 JavaScript API。
你在测试运行期间捕获了错误截图,并记录了测试会话。这有助于调试测试,并为测试运行创建证据。
最后,你学习了关于浏览器导航方法和 cookies 的内容。
在下一章中,你将学习如何将我们的测试与其他工具集成,例如持续集成工具,以便将测试作为构建过程的一部分运行。
第十章. 与其他工具和框架的集成
Selenium WebDriver Python API 非常强大和灵活。到目前为止,我们已经学习了如何将 Selenium WebDriver 与 unittest 库集成并创建一个简单的测试框架。然而,这并不限制我们只使用 unittest 库。我们可以将 Selenium WebDriver 与许多其他工具和框架集成。Selenium WebDriver 旁边有许多现成的框架可供使用。
我们可以使用支持 BDD 的各种框架,在项目中使用 Selenium WebDriver 应用 行为驱动开发 (BDD)。
我们还可以将 Selenium Python API 与 持续集成 (CI) 和构建工具集成,允许我们在应用程序构建后立即运行测试。这为开发人员提供了关于应用程序质量和稳定性的早期反馈。
在本章中,您将学习一些主要的集成示例,包括:
-
下载和安装 Behave 用于 BDD
-
使用 Behave 编写特性
-
使用 Behave 和 Selenium WebDriver 自动化特性
-
下载和安装 Jenkins
-
设置 Jenkins 以运行 Selenium 测试
-
配置 Jenkins 以捕获测试运行的结果
行为驱动开发
BDD 是 Dan North 在其著名的论文 Introducing BDD (dannorth.net/introducing-bdd/) 中引入的一种敏捷软件开发方法。
BDD 也被称为 验收测试驱动开发 (ATDD)、故事测试或示例规格。BDD 鼓励开发人员、质量保证人员和非技术或业务用户在软件项目中协作,通过编写非程序员可读的自然语言测试用例来定义规格和决定验收标准。
注意事项
Python 中有几种工具可用于实现 BDD;两个主要工具是 Behave (pythonhosted.org/behave/) 和 Lettuce (lettuce.it/),后者受到非常著名的 BDD 工具 Cucumber (cukes.info/) 的启发,该工具在 Ruby 中可用。
在接下来的章节中,您将学习如何使用 Behave 为示例应用程序实现 BDD。
安装 Behave
安装 Behave 是一个简单的过程。我们可以使用以下命令行下载和安装 Behave:
pip install behave
这将下载并安装 Behave 及其依赖项。在 pythonhosted.org/behave/install.html 提供了 Behave 的附加安装选项。
在 Behave 中编写第一个特性
该过程从讨论和列出正在开发的应用程序的功能和用户故事开始。各种利益相关者聚集在一起,以通用语言创建功能、用户故事和验收标准的列表,这种语言被所有各方理解,包括开发者、测试人员、业务分析师和客户。Behave 支持以Given、When、Then(GWT)格式创建 Gherkin 语言的特性文件。有关 Gherkin 语言的更多信息,请参阅github.com/cucumber/cucumber/wiki/Gherkin。
让我们从样本应用程序中的搜索功能特性开始。搜索特性应该使用户能够从主页搜索产品。特性文件提供了一个简单的用户故事和验收标准的描述,以 GWT 格式的场景概述。这些也被称为场景步骤,如下所述:
-
Given:这为执行场景设置先决条件;在这个场景中导航到主页
-
当:这包含场景的动作;在这个例子中搜索一个术语
-
然后:这包含场景的结果;检查是否在这个例子中显示了匹配产品的列表
在一个场景中可以有多个When和Then步骤:
Feature: I want to search for products
Scenario Outline: Search
Given I am on home page
when I search for "phone"
then I should see list of matching products in search results
要使用 Behave 功能,我们需要将此存储在一个带有.feature扩展名的纯文本文件中。让我们创建一个名为bdd/feature的文件夹,并将此文件保存为search.feature。
实现特性步骤定义文件
一旦我们编写了特性文件,我们需要为场景概述中编写的步骤创建步骤定义。步骤定义是理解以纯文本格式编写的步骤的 Python 代码块,并包含调用 API 或 Selenium WebDriver 命令以执行步骤的代码。步骤定义文件应存储在存储特性文件的steps子文件夹中。让我们创建一个search_steps.py文件,包含以下步骤定义:
from behave import *
@given('I am on home page')
def step_i_am_on_home_page(context):
context.driver.get("http://demo.magentocommerce.com/")
@when('I search for {text}')
def step_i_search_for(context, text):
search_field = context.driver.find_element_by_name("q")
search_field.clear()
# enter search keyword and submit
search_field.send_keys(text)
search_field.submit()
@then('I should see list of matching products in search results')
def step_i_should_see_list(context):
products = context.driver.\
find_elements_by_xpath("//h2[@class='product-name']/a")
# check count of products shown in results
assert len(products) > 0
对于每个 GWT,我们需要创建一个匹配的步骤定义。例如,对于给定的我正在主页上步骤,我们创建了以下步骤定义。步骤是通过与特性文件中的谓词匹配的装饰器来识别的:@given、@when和@then。装饰器接受一个字符串,包含场景步骤中使用的其余短语,在这种情况下,我正在主页上。
@given('I am on home page')
def step_i_am_on_home_page(context):
context.driver.get("http://demo.magentocommerce.com/")
我们也可以将步骤中嵌入的参数传递给步骤定义。例如,对于@when,我们传递搜索短语作为当搜索"电话"。
我们可以使用{text}如以下代码示例中所示来读取值:
@when('I search for {text}')
def step_i_search_for(context, text):
search_field = context.driver.find_element_by_name("q")
search_field.clear()
# enter search keyword and submit
search_field.send_keys(text)
search_field.submit()
你可以看到传递给步骤定义的上下文变量。上下文变量由 Behave 用来存储要共享的信息。它在三个级别上运行,由 Behave 自动管理。我们还可以使用上下文变量在步骤之间存储和共享信息。
创建环境配置
在我们能够运行功能之前,我们需要创建一个环境文件,该文件用于设置常见的 Behave 设置以及将在步骤或步骤定义文件之间共享的任何代码。这是一个初始化 WebDriver 以启动 Firefox 的好地方,Firefox 将被用来运行使用 Selenium WebDriver 的步骤。在功能文件旁边创建一个environment.py文件,并添加before_all()和after_all()方法,这些方法将在功能执行前后执行,如下面的代码所示:
from selenium import webdriver
def before_all(context):
context.driver = webdriver.Chrome()
def after_all(context):
context.driver.quit()
运行功能
现在,是时候使用 Behave 运行功能了。这真的很简单。导航到我们在早期步骤中创建的bdd文件夹,并执行behave命令:
behave
Behave 将执行bdd文件夹中编写的所有功能。它将使用我们为运行场景所做的步骤定义和环境设置。执行结束时,你将看到执行摘要,如下面的截图所示:

Behave 在三个级别上生成摘要,即功能、场景和步骤的通过和失败。
使用场景概述
有时我们可能想要运行具有多个变量的一组场景,这些变量给出了一组已知状态、要采取的操作和预期结果,所有这些都使用相同的步骤,类似于数据驱动测试。我们可以为此使用场景概述。
让我们按照以下步骤重写search.feature文件,包括场景概述和示例。场景概述就像一个模板,例如在Example部分中给出的。
-
在这个例子中,我们创建了两个示例来检查分类或特定产品的搜索功能。
Example部分以表格格式包含搜索词和预期结果:Feature: I want to search for products Scenario Outline: Search Given I am on home page when I search for <term> then I should see results <search_count> in search results Examples: By category | term | search_count | | Phones | 2 | | Bags | 7 | Examples: By product name | term | search_count | | Madison earbuds | 3 | -
此外,修改
search_steps.py文件以匹配步骤中使用的文本:from behave import * @given('I am on home page') def step_i_am_on_home_page(context): context.driver.get("http://demo.magentocommerce.com/") @when('I search for {text}') def step_i_search_for(context, text): search_field = context.driver.find_element_by_name("q") search_field.clear() # enter search keyword and submit search_field.send_keys(text) search_field.submit() @then('I should see results {text} in search results') def step_i_should_see_results(context, text): products = context.driver.\ find_elements_by_xpath("//h2[@class='product-name']/a") # check count of products shown in results assert len(products) >= int(text)
当我们执行此功能时,Behave 会自动重复执行Example部分中用search.feature文件编写的场景概述。它会将示例数据中的数据传递给场景步骤并执行定义。在 Behave 运行修改后的功能后,你可以看到结果。Behave 会打印出它在功能上运行的所有组合,如下面的截图所示:

小贴士
Behave 还支持使用–junit开关以 JUnit 格式生成报告。
与 Jenkins 的 CI
Jenkins 是一个流行的 Java 编写的 CI 服务器。它源自 Hudson 项目。Jenkins 和 Hudson 都提供类似的功能。
Jenkins 支持各种版本控制工具,如 CVS、SVN、Git、Mercurial、Perforce 和 ClearCase,并且可以执行使用 Apache Ant 或 Maven 构建的 Java 项目。但是,它也可以使用插件、任意 shell 脚本和 Windows 的 批处理 命令为其他平台构建项目。
除了构建软件外,Jenkins 还可以部署以设置一个自动化的测试环境,其中 Selenium WebDriver 测试可以根据定义的计划或每次提交更改到版本控制系统时无人在场运行。
在接下来的章节中,您将学习如何使用免费式项目模板设置 Jenkins 来运行测试。
准备 Jenkins
在我们开始使用 Jenkins 运行我们的测试之前,我们需要进行一些更改,以便我们可以利用 Jenkins 的功能。我们将使用 Jenkins 在预定义的计划上运行我们的测试,并收集测试结果,以便 Jenkins 可以在仪表板上显示它们。我们将重用我们在 第二章 使用 unittest 编写测试 中创建的烟雾测试。
我们使用了 unittest 的 TestSuite 运行器来一起执行一系列测试。现在,我们将以 JUnit 报告格式输出这些测试的结果。为此,我们需要一个名为 xmlrunner 的 Python 库,它可以从 pypi.python.org/pypi/xmlrunner/1.7.4 获取。
使用以下命令行下载并安装 xmlrunner:
pip install xmlrunner
我们将使用 smoketests.py,它使用 TestSuite 运行器从 homepagetests.py 和 searchtest.py 运行测试。我们将使用 xmlrunner.XML TestRunner 来运行这些测试,并生成一个 JUnit 格式的测试报告。该报告将以 XML 格式生成并保存在 test-reports 子目录中。要使用 xmlrunner,请在 smoketest.py 中进行以下突出显示的更改,如下面的代码示例所示:
import unittest
from xmlrunner import xmlrunner
from searchtest import SearchProductTest
from homepagetests import HomePageTest
# get all tests from SearchProductTest and HomePageTest class
search_tests = unittest.TestLoader().loadTestsFromTestCase(SearchProductTest)
home_page_tests = unittest.TestLoader().loadTestsFromTestCase(HomePageTest)
# create a test suite combining search_test and home_page_test
smoke_tests = unittest.TestSuite([home_page_tests, search_tests])
# run the suite
xmlrunner.XMLTestRunner(verbosity=2, output='test-reports').run(smoke_tests)
设置 Jenkins
设置 Jenkins 相对直接。您可以使用适用于各种平台的安装程序下载并安装 Jenkins。在以下示例中,我们将设置 Jenkins 并创建一个新的构建作业来在示例应用程序上运行烟雾测试:
-
从
jenkins-ci.org/下载并安装 Jenkins CI 服务器。对于此配方,使用 Jenkins Windows 安装程序在 Windows 7 机器上设置 Jenkins。 -
在浏览器窗口中导航到 Jenkins 仪表板(默认为
http://localhost:8080)。 -
在 Jenkins 仪表板 上,点击 新建项目 或 创建新作业 链接来创建一个新的 Jenkins 作业,如图所示:
![设置 Jenkins]()
-
在 项目名称 文本框中输入
Demo_App_Smoke_Test并选择 构建一个免费式软件项目 单选按钮,如图所示:![设置 Jenkins]()
-
点击 确定 按钮。将创建一个具有指定名称的新作业。
注意
我们可以连接到各种版本控制或源代码管理(SCM)工具,如 SVN、GIT、Perforce 等,以存储源代码和测试代码。然后,我们可以获取代码的最新版本,在 Jenkins 工作区中作为构建步骤的一部分构建和测试软件。然而,为了简化,在这个例子中,我们将使用执行 Windows 批处理命令构建步骤将测试脚本从文件夹复制到 Jenkins 工作区,具体步骤如下。
-
在构建部分,点击添加构建步骤,并从下拉菜单中选择执行 Windows 批处理命令选项,如图所示:
![设置 Jenkins]()
-
在命令文本框中输入以下命令,如图所示。路径可能因你的情况而异。此命令将复制包含烟雾测试的 Python 文件到 Jenkins 工作区,并运行
smoketest.py,如下所示:copy c:\setests\chapter10\smoketests\*.py python smoketest.py![设置 Jenkins]()
-
我们已将
smoketest.py配置为以 JUnit 格式生成测试结果,以便 Jenkins 可以在其仪表板上显示测试结果。要将这些报告与 Jenkins 集成,请点击添加构建后操作,并从下拉菜单中选择发布 JUnit 测试结果报告选项,如图所示:![设置 Jenkins]()
-
在构建后操作部分,将
test-reports/*.xml添加到测试报告 XML文本框中,如图所示。每次 Jenkins 运行测试时,它将从test-report子文件夹中读取测试结果。![设置 Jenkins]()
-
要在构建触发器部分安排测试以自动执行,请选择定期构建,并在计划文本框中输入如图所示的数据。这将每天晚上 10 点触发构建过程,Jenkins 将在无人值守的情况下作为构建过程的一部分运行测试,以便你第二天早上到达办公室时可以看到结果。
![设置 Jenkins]()
-
点击保存按钮以保存作业配置。Jenkins 将显示新创建作业的项目页面。
-
我们可以检查一切是否设置正确,以查看测试是否执行。点击立即构建链接手动运行作业,如图所示:
![设置 Jenkins]()
-
你可以在构建历史部分看到构建的运行状态,如图所示:
![设置 Jenkins]()
-
点击构建历史部分的运行项目,这将打开以下页面:
![设置 Jenkins]()
-
除了 Jenkins 上的状态和进度条外,我们还可以通过打开控制台输出链接来查看幕后发生的事情。这将打开控制台输出页面,显示命令行输出,如图所示:
![设置 Jenkins]()
-
一旦 Jenkins 完成构建过程,我们就可以看到类似于下一张截图所示的构建页面。
-
Jenkins 通过读取由
unittest框架生成的结果文件来显示测试结果和各种其他指标。Jenkins 还存档测试结果。要查看测试结果,请点击构建页面上的测试结果链接。 -
我们配置了测试,使其以 JUnit 格式生成测试结果,当我们点击测试结果时,Jenkins 将显示以下截图所示的 JUnit 测试结果。它突出显示了失败的测试以及测试的摘要。
![设置 Jenkins]()
-
通过单击包名,我们可以深入挖掘并查看以下截图所示的各个测试的结果:
![设置 Jenkins]()
Jenkins 还在仪表板上显示作业的状态,以下列格式显示最后构建的状态:

摘要
在本章中,您学习了如何将 Selenium 与 Behave 集成以进行 BDD,以及如何将 Jenkins 集成以进行 CI。您看到了如何通过编写特性和步骤定义文件来将 Selenium WebDriver API 与 Behave 集成,以运行自动化验收测试。
您设置了 Jenkins 来运行 Selenium WebDriver 测试,这样您就可以在构建软件时运行这些测试,或者安排测试以便它们可以在夜间运行。Jenkins 提供了一个易于设置的模型来运行各种应用程序开发平台和环境的构建和测试作业。
这完成了您使用 Selenium WebDriver 和 Python 的学习之旅。您学习了如何使用 Selenium WebDriver 自动化浏览器交互来为 Web 应用程序创建自动化测试的一些基本课程。您可以使用这些知识来构建自己的测试框架。



按钮来设置解释器,如图所示:

































浙公网安备 33010602011771号