量角器 – 快速指南
量角器 – 快速指南
量角器 – 介绍
本章向您介绍了量角器,您将在其中了解这个测试框架的起源,以及为什么要选择它,该工具的工作原理和局限性。
什么是量角器?
Protractor 是用于 Angular 和 AngularJS 应用程序的开源端到端测试框架。它是由 Google 在 WebDriver 之上构建的。它还可以替代现有的名为“Angular Scenario Runner”的 AngularJS E2E 测试框架。
它还作为解决方案集成器,结合了 NodeJS、Selenium、Jasmine、WebDriver、Cucumber、Mocha 等强大的技术。在测试 AngularJS 应用程序的同时,它还为普通 Web 应用程序编写了自动化回归测试。它允许我们像真实用户一样测试我们的应用程序,因为它使用实际浏览器运行测试。
下图将简要概述量角器 –
观察到,在上图中,我们有 –
-
Protractor – 如前所述,它是 WebDriver JS 的包装器,专为 Angular 应用程序设计。
-
Jasmine – 它基本上是一个用于测试 JavaScript 代码的行为驱动开发框架。我们可以使用 Jasmine 轻松编写测试。
-
WebDriver JS – 它是 selenium 2.0/WebDriver 的 Node JS 绑定实现。
-
Selenium – 它只是使浏览器自动化。
起源
如前所述,Protractor 是现有 AngularJS E2E 测试框架的替代品,称为“Angular Scenario Runner”。基本上,Protractor 的起源始于Scenario Runner 的终结。这里出现的一个问题是为什么我们需要构建 Protractor?为了理解这一点,我们首先需要了解它的前身——Scenario Runner。
量角器的诞生
Julie Ralph 是 Protractor 开发的主要贡献者,在 Google 的其他项目中使用 Angular Scenario Runner 有以下经验。这进一步成为构建 Protractor 的动机,特别是为了填补空白 –
“我们尝试使用 Scenario Runner,但我们发现它确实无法完成我们需要测试的事情。我们需要测试诸如登录之类的事情。您的登录页面不是 Angular 页面,Scenario Runner 无法处理。它无法处理诸如弹出窗口和多个窗口、浏览浏览器历史记录之类的事情。”
Protractor 的最大优势是 Selenium 项目的成熟度,它封装了它的方法,以便它可以轻松地用于 Angular 项目。Protractor 的设计方式是测试应用程序的所有层,例如 Web UI、后端服务、持久层等。
为什么是量角器?
众所周知,几乎所有的应用程序都使用 JavaScript 进行开发。当 JavaScript 的大小增加并且由于应用程序本身的数量增加而对应用程序变得复杂时,测试人员的任务变得困难。大多数时候,通过使用 JUnit 或 Selenium WebDriver,在 AngularJS 应用程序中捕获 Web 元素变得非常困难,使用扩展的 HTML 语法来表达 Web 应用程序组件。
这里的问题是,为什么 Selenium Web Driver 无法找到 AngularJS Web 元素?原因是因为 AngularJS 应用程序有一些扩展的 HTML 属性,如 ng-repeater、ng-controller 和 ng-model 等,这些属性没有包含在 Selenium 定位器中。
在这里,Protractor 的重要性就体现出来了,因为在 Selenium 之上的 Protractor 可以处理和控制 AngularJS Web 应用程序中那些扩展的 HTML 元素。这就是为什么我们可以说大多数框架都专注于对 AngularJS 应用程序进行单元测试,Protractor 用于测试应用程序的实际功能。
量角器的工作
测试框架 Protractor 与 Selenium 协同工作,提供自动化测试基础设施,用于模拟用户与在浏览器或移动设备中运行的 AngularJS 应用程序的交互。
在以下步骤的帮助下可以理解量角器的工作 –
-
步骤 1 – 在第一步中,我们需要编写测试。它可以在茉莉花或摩卡或黄瓜的帮助下完成。
-
第 2 步– 现在,我们需要运行可以在量角器的帮助下完成的测试。它也被称为测试运行器。
-
第 3 步– 在这一步中,Selenium 服务器将帮助管理浏览器。
-
第 4 步– 最后,在 Selenium WebDriver 的帮助下调用浏览器 API。
好处
这个开源的端到端测试框架具有以下优势 –
-
作为一个开源工具,Protractor 非常易于安装和设置。
-
与 Jasmine 框架配合使用以创建测试。
-
支持测试驱动开发(TDD)。
-
包含自动等待,这意味着我们不需要在测试中显式添加等待和睡眠。
-
提供 Selenium WebDriver 的所有优点。
-
支持通过多个浏览器进行并行测试。
-
提供自动同步的好处。
-
具有出色的测试速度。
限制
这个开源的端到端测试框架具有以下限制 –
-
不会发现浏览器自动化中的任何垂直领域,因为它是 WebDriver JS 的包装器。
-
JavaScript 的知识对用户来说是必不可少的,因为它仅适用于 JavaScript。
-
只提供前端测试,因为它是一个 UI 驱动的测试工具。
量角器 – Javascript 测试的概念
由于 JavaScript 的知识对于使用 Protractor 是必不可少的,因此在本章中,让我们详细了解 JavaScript 测试的概念。
JavaScript 测试和自动化
JavaScript 是最流行的动态类型和解释性脚本语言,但最具挑战性的任务是测试代码。这是因为与 JAVA 和 C++ 等其他编译语言不同,JavaScript 中没有编译步骤可以帮助测试人员找出错误。此外,基于浏览器的测试非常耗时;因此,需要支持 JavaScript 自动化测试的工具。
自动化测试的概念
编写测试始终是一个好习惯,因为它可以使代码更好;手动测试的问题在于它有点耗时且容易出错。手动测试的过程对于程序员来说也很无聊,因为他们需要重复这个过程,编写测试规范,更改代码并多次刷新浏览器。此外,手动测试也会减慢开发过程。
由于上述原因,拥有一些可以自动化这些测试并帮助程序员摆脱这些重复和无聊的步骤的工具总是有用的。开发人员应该怎么做才能使测试过程自动化?
基本上,开发人员可以在 CLI(命令行解释器)或开发 IDE(集成开发环境)中实现该工具集。然后,即使没有开发人员的输入,这些测试也将在单独的过程中连续运行。JavaScript 的自动化测试也不是新事物,已经开发了许多工具,如 Karma、Protractor、CasperJS 等。
JavaScript 的测试类型
可以针对不同的目的进行不同的测试。例如,一些测试用于检查程序中函数的行为,而另一些则用于测试模块或功能的流程。因此,我们有以下两种类型的测试 –
单元测试
测试是在称为单元的程序的最小可测试部分上完成的。该单元基本上是独立测试的,该单元对其他部分没有任何依赖性。在 JavaScript 的情况下,具有特定行为的单个方法或函数可以是一个代码单元,这些代码单元必须以隔离的方式进行测试。
单元测试的优点之一是单元的测试可以按任何顺序进行,因为这些单元是相互独立的。真正重要的单元测试的另一个优点是它可以随时运行测试,如下所示 –
- 从开发过程的一开始。
- 在完成任何模块/功能的开发之后。
- 修改任何模块/功能后。
- 在现有应用程序中添加任何新功能后。
对于 JavaScript 应用程序的自动化单元测试,我们可以从许多测试工具和框架中进行选择,例如 Mocha、Jasmine 和 QUnit。
端到端测试
它可以定义为用于测试应用程序从开始到结束(从一端到另一端)的流程是否按照设计正常工作的测试方法。
端到端测试也称为功能/流程测试。与单元测试不同,端到端测试测试各个组件作为应用程序如何协同工作。这是单元测试和端到端测试之间的主要区别。
例如,假设我们有一个注册模块,用户需要提供一些有效信息来完成注册,那么该特定模块的 E2E 测试将按照以下步骤完成测试 –
- 首先,它将加载/编译表单或模块。
- 现在,它将获取表单元素的 DOM(文档对象模型)。
- 接下来,触发提交按钮的点击事件来检查它是否工作。
- 现在,出于验证目的,从输入字段中收集值。
- 接下来,应验证输入字段。
- 出于测试目的,调用一个伪造的 API 来存储数据。
每一步都会给出自己的结果,这些结果将与预期的结果集进行比较。
现在,出现的问题是,虽然这种 E2E 或功能测试也可以手动执行,但为什么我们需要自动化?主要原因是自动化将使这个测试过程变得容易。一些可以轻松与任何应用程序集成的可用工具是 Selenium、PhantomJS 和 Protractor。
测试工具和框架
我们有各种用于 Angular 测试的测试工具和框架。以下是一些著名的工具和框架 –
业力
由 Vojta Jina 创建的 Karma 是一个测试运行器。最初这个项目被称为 Testacular。它不是一个测试框架,这意味着它使我们能够在真实浏览器上轻松自动地运行 JavaScript 单元测试。Karma 是为 AngularJS 构建的,因为在 Karma 之前,没有针对基于 Web 的 JavaScript 开发人员的自动化测试工具。另一方面,借助 Karma 提供的自动化,开发人员可以运行简单的单个命令并确定整个测试套件是通过还是失败。
使用 Karma 的优点
与手动过程相比,以下是使用 Karma 的一些优点 –
- 在多个浏览器和设备中自动化测试。
- 监视文件中的错误并修复它们。
- 提供在线支持和文档。
- 简化与持续集成服务器的集成。
使用 Karma 的缺点
以下是使用 Karma 的一些缺点 –
使用 Karma 的主要缺点是它需要额外的工具来配置和维护。
如果您将 Karma 测试运行器与 Jasmine 一起使用,那么在一个元素具有多个 id 的情况下,可用于查找有关设置 CSS 的信息的文档较少。
茉莉花
Jasmine 是一种用于测试 JavaScript 代码的行为驱动开发框架,由 Pivotal Labs 开发。在 Jasmine 框架积极开发之前,Pivotal Labs 也开发了一个名为 JsUnit 的类似单元测试框架,它有一个内置的测试运行器。浏览器测试可以通过包含 SpecRunner.html 文件或将其用作命令行测试运行器来通过 Jasmine 测试运行。它也可以在有或没有 Karma 的情况下使用。
使用茉莉花的优点
以下是使用 Jasmine 的一些优点 –
-
一个独立于浏览器、平台和语言的框架。
-
支持测试驱动开发 (TDD) 以及行为驱动开发。
-
默认与 Karma 集成。
-
易于理解的语法。
-
提供测试间谍、假货和直通功能,这些功能有助于作为附加功能进行测试。
使用茉莉花的缺点
以下是使用茉莉花的骗局 –
-
测试必须由用户在更改时返回,因为在运行测试时 Jasmine 中没有可用的文件监视功能。
摩卡
Mocha 是为 Node.js 应用程序编写的,是一个测试框架,但它也支持浏览器测试。它很像 Jasmine,但它们之间的主要区别在于 Mocha 需要一些插件和库,因为它不能作为测试框架独立运行。另一方面,Jasmine 是独立的。但是,Mocha 使用起来比 Jasmine 更灵活。
使用摩卡的优点
以下是使用 Mocha 的一些优点 –
- Mocha 非常易于安装和配置。
- 用户友好和简单的文档。
- 包含具有多个节点项目的插件。
使用摩卡的缺点
以下是使用 Mocha 的一些缺点 –
- 它需要用于断言、间谍等的单独模块。
- 它还需要额外的配置才能与 Karma 一起使用。
QUnit
QUint 最初由 John Resig 于 2008 年作为 jQuery 的一部分开发,是一个功能强大且易于使用的 JavaScript 单元测试套件。它可用于测试任何通用 JavaScript 代码。虽然它侧重于在浏览器中测试 JavaScript,但它对于开发人员的使用非常方便。
使用 QUnit 的优点
以下是使用 QUnit 的一些优点 –
- 易于安装和配置。
- 用户友好和简单的文档。
使用 QUnit 的缺点
以下是使用 QUnit 的缺点 –
- 它主要是为 jQuery 开发的,因此不太适合与其他框架一起使用。
硒
Selenium 最初由 Jason Huggins 于 2004 年作为 ThoughtWorks 的内部工具开发,是一种开源测试自动化工具。Selenium 将自己定义为“Selenium 自动化浏览器。就是这样!”。浏览器的自动化意味着开发人员可以非常轻松地与浏览器交互。
使用硒的优点
以下是使用 Selenium 的一些优点 –
- 包含大型功能集。
- 支持分布式测试。
- 通过 Sauce Labs 等服务获得 SaaS 支持。
- 易于使用,提供简单的文档和丰富的资源。
使用硒的缺点
以下是使用 Selenium 的一些缺点 –
- 使用 Selenium 的一个主要缺点是它必须作为一个单独的进程运行。
- 配置有点麻烦,因为开发人员需要遵循几个步骤。
量角器 – 入门
在前面的章节中,我们已经学习了量角器的基础知识。在本章中,让我们学习如何安装和配置它。
先决条件
在您的计算机上安装 Protractor 之前,我们需要满足以下先决条件 –
节点.js
Protractor 是一个 Node.js 模块,因此非常重要的先决条件是我们必须在我们的计算机上安装 Node.js。我们将使用 Node.js 附带的 npm(一个 JavaScript 包管理器)安装 Protractor 包。
要安装 Node.js,请按照官方链接 – https://nodejs.org/en/download/。安装 Node.js 后,您可以通过在命令提示符中编写命令node –version和npm –version来检查 Node.js 和 npm 的版本,如下所示 –
铬合金
Google Chrome 是由 Google 构建的网络浏览器,将用于在 Protractor 中运行端到端测试,而无需 Selenium 服务器。您可以通过点击链接下载 chrome – https://www.google.com/chrome/。
适用于 Chrome 的 Selenium WebDriver
该工具与 Protractor npm 模块一起提供,允许我们与 Web 应用程序进行交互。
安装量角器
在我们的计算机上安装 Node.js 后,我们可以在以下命令的帮助下安装 Protractor –
npm install -g protractor
成功安装 protractor 后,我们可以通过在命令提示符中编写protractor –version命令来检查其版本,如下所示 –
为 Chrome 安装 WebDriver
安装 Protractor 后,我们需要为 Chrome 安装 Selenium WebDriver。它可以在以下命令的帮助下安装 –
webdriver-manager update
上述命令将创建一个 Selenium 目录,其中包含项目中使用的所需 Chrome 驱动程序。
确认安装和配置
安装Protractor后,我们可以稍微修改一下例子中提供的conf.js,来确认Protractor的安装和配置。你可以在根目录node_modules/Protractor/example 中找到这个 conf.js 文件。
为此,首先在同一目录中创建一个名为testingconfig.js的新文件,即node_modules/Protractor/example。
现在,在 conf.js 文件中,在源文件声明参数下,编写 testingconfig.js。
接下来,保存并关闭所有文件并打开命令提示符。运行 conf.js 文件,如下面的屏幕截图所示。
如果您得到如下所示的输出,则 Protractor 的配置和安装成功 –
上面的输出显示没有规范,因为我们在 conf.js 文件中的源文件声明参数中提供了空文件。但是从上面的输出中,我们可以看到 protractor 和 WebDriver 都运行成功。
安装和配置问题
在安装和配置 Protractor 和 WebDriver 时,我们可能会遇到以下常见问题 –
硒未正确安装
这是安装 WebDriver 时最常见的问题。如果您不更新 WebDriver,则会出现此问题。请注意,我们必须更新 WebDriver,否则我们将无法将其引用到量角器安装。
找不到测试
另一个常见的问题是,在运行 Protractor 后,它显示无法找到测试。为此,我们必须确保相对路径、文件名或扩展名是正确的。我们还需要非常小心地编写 conf.js 文件,因为它是从配置文件本身开始的。
量角器 – 量角器和硒服务器
如前所述,Protractor 是一个开源的端到端测试框架,用于 Angular 和 AngularJS 应用程序。它是 Node.js 程序。另一方面,Selenium 是一个浏览器自动化框架,包括 Selenium 服务器、WebDriver API 和 WebDriver 浏览器驱动程序。
带硒的量角器
如果我们谈论 Protractor 和 Selenium 的结合,Protractor 可以与 Selenium 服务器一起提供自动化测试基础设施。该基础设施可以模拟用户与运行在浏览器或移动设备上的 angular 应用程序的交互。Protractor 和 Selenium 的结合可以分为三个部分,即测试、服务器和浏览器,如下图所示 –
Selenium WebDriver 进程
正如我们在上图中看到的,使用 Selenium WebDriver 的测试涉及以下三个过程 –
- 测试脚本
- 服务器
- 浏览器
在本节中,让我们讨论这三个进程之间的通信。
测试脚本和服务器之间的通信
前两个进程 – 测试脚本和服务器之间的通信取决于 Selenium Server 的工作。换句话说,我们可以说 Selenium 服务器的运行方式将塑造测试脚本和服务器之间的通信过程。
Selenium 服务器可以作为独立的 Selenium 服务器(selenium-server-standalone.jar)在我们的机器上本地运行,也可以通过服务(Sauce Labs)远程运行。在独立的 Selenium 服务器的情况下,Node.js 和 selenium 服务器之间会有一个 http 通信。
服务器和浏览器之间的通信
正如我们所知,服务器负责在从测试脚本中解释相同的命令后将命令转发到浏览器。这就是为什么服务器和浏览器也需要通信介质,这里的通信是在JSON WebDriver Wire Protocol的帮助下完成的。浏览器扩展了用于解释命令的浏览器驱动程序。
借助下图可以理解上述有关 Selenium WebDriver 进程及其通信的概念 –
在使用 Protractor 时,第一个过程,即测试脚本使用 Node.js 运行,但在浏览器上执行任何操作之前,它将发送一个额外的命令,以确保被测试的应用程序稳定。
设置硒服务器
Selenium Server 在我们的测试脚本和浏览器驱动程序之间充当代理服务器。它基本上将命令从我们的测试脚本转发到 WebDriver,并将来自 WebDriver 的响应返回到我们的测试脚本。有以下设置 Selenium 服务器的选项,这些选项包含在测试脚本的conf.js文件中 –
独立硒服务器
如果我们想在本地机器上运行服务器,我们需要安装独立的 selenium 服务器。安装独立 selenium 服务器的先决条件是 JDK(Java 开发工具包)。我们必须在本地机器上安装 JDK。我们可以通过从命令行运行以下命令来检查它 –
java -version
现在,我们可以选择手动或从测试脚本安装和启动 Selenium Server。
手动安装和启动 Selenium 服务器
为了手动安装和启动 Selenium 服务器,我们需要使用 Protractor 自带的 WebDriver-Manager 命令行工具。安装和启动 Selenium 服务器的步骤如下 –
步骤 1 – 第一步是安装 Selenium 服务器和 ChromeDriver。可以在运行以下命令的帮助下完成 –
webdriver-manager update
第 2 步– 接下来,我们需要启动服务器。可以在运行以下命令的帮助下完成 –
webdriver-manager start
步骤 3 – 最后,我们需要在配置文件中将 seleniumAddress 设置为正在运行的服务器的地址。默认地址是http://localhost:4444/wd/hub。
从测试脚本启动 Selenium 服务器
为了从测试脚本启动 Selenium 服务器,我们需要在我们的配置文件中设置以下选项 –
-
jar 文件的位置– 我们需要通过设置 seleniumServerJar 在配置文件中为独立 Selenium 服务器设置 jar 文件的位置。
-
指定端口– 我们还需要指定用于启动独立 Selenium 服务器的端口。它可以通过设置 seleniumPort 在配置文件中指定。默认端口为 4444。
-
命令行选项数组– 我们还需要设置要传递给服务器的命令行选项数组。它可以通过设置 seleniumArgs 在配置文件中指定。如果您需要完整的命令数组列表,请使用-help标志启动服务器。
使用远程 Selenium 服务器
运行我们的测试的另一个选择是远程使用 Selenium 服务器。远程使用服务器的先决条件是我们必须拥有托管服务器的服务的帐户。在使用 Protractor 时,我们内置支持以下托管服务器的服务 –
测试对象
为了使用 TestObject 作为远程 Selenium Server,我们需要设置 testobjectUser,我们 TestObject 帐户的用户名和 testobjectKey,我们 TestObject 帐户的 API 密钥。
浏览器堆栈
为了使用 BrowserStack 作为远程 Selenium 服务器,我们需要设置 browserstackUser,我们的 BrowserStack 帐户的用户名和 browserstackKey,我们的 BrowserStack 帐户的 API 密钥。
酱料实验室
为了使用 Sauce Labs 作为远程 Selenium 服务器,我们需要设置sauceUser,我们Sauce Labs 帐户的用户名和SauceKey,我们Sauce Labs 帐户的API 密钥。
科比通
为了使用 Kobiton 作为远程 Selenium 服务器,我们需要设置 kobitonUser,我们 Kobiton 帐户的用户名和 kobitonKey,我们 Kobiton 帐户的 API 密钥。
不使用Selenium Server直接连接浏览器驱动
运行我们的测试的另一种选择是直接连接到浏览器驱动程序,而不使用 Selenium 服务器。通过在配置文件中设置 directConnect: true,Protractor 可以直接针对 Chrome 和 Firefox 进行测试,而无需使用 Selenium 服务器。
设置浏览器
在配置和设置浏览器之前,我们需要知道 Protractor 支持哪些浏览器。以下是 Protractor 支持的浏览器列表 –
- 浏览器驱动程序
- 火狐驱动程序
- Safari驱动程序
- 驱动程序
- Appium-iOS/Safari
- Appium-Android/Chrome
- 仙人球
- PhantomJS
为了设置和配置浏览器,我们需要移动到量角器的配置文件,因为浏览器设置是在配置文件的功能对象中完成的。
设置 Chrome
为了设置 Chrome 浏览器,我们需要如下设置功能对象
capabilities: { 'browserName': 'chrome' }
我们还可以添加嵌套在 chromeOptions 中的 Chrome 特定选项,其完整列表可以在https://sites.google.com/a/chromium.org/chromedriver/capabilities 中看到。
例如,如果您想在右上角添加 FPS-counter,则可以在配置文件中按如下方式完成 –
capabilities: { 'browserName': 'chrome', 'chromeOptions': { 'args': ['show-fps-counter=true'] } },
设置火狐
为了设置 Firefox 浏览器,我们需要如下设置功能对象 –
capabilities: { 'browserName': 'firefox' }
我们还可以添加嵌套在 moz:firefoxOptions 对象中的 Firefox 特定选项,其完整列表可以在https://github.com/mozilla/geckodriver#firefox-capabilities 中看到。
例如,如果您想以安全模式在 Firefox 上运行测试,则可以在配置文件中按如下方式完成 –
capabilities: { 'browserName': 'firefox', 'moz:firefoxOptions': { 'args': ['—safe-mode'] } },
设置其他浏览器
要设置除 Chrome 或 Firefox 之外的任何其他浏览器,我们需要从https://docs.seleniumhq.org/download/安装一个单独的二进制文件。
设置 PhantonJS
实际上,由于其崩溃问题,不再支持 PhantomJS。相反,建议使用无头 Chrome 或无头 Firefox。它们可以设置如下 –
要设置无头 Chrome,我们需要使用 –headless 标志启动 Chrome,如下所示 –
capabilities: { 'browserName': 'chrome', 'chromeOptions': { 'args': [“--headless”, “--disable-gpu”, “--window-size=800,600”] } },
要设置无头 Firefox,我们需要使用–headless标志启动 Firefox,如下所示 –
capabilities: { 'browserName': 'firefox', 'moz:firefoxOptions': { 'args': [“--headless”] } },
设置多个浏览器进行测试
我们还可以针对多个浏览器进行测试。为此,我们需要使用 multiCapabilities 配置选项如下 –
multiCapabilities: [{ 'browserName': 'chrome' },{ 'browserName': 'firefox' }]
哪个框架?
Protractor 支持两个 BDD(行为驱动开发)测试框架 Jasmine 和 Mocha。这两个框架都基于 JavaScript 和 Node.js。这些框架提供了编写和管理测试所需的语法、报告和脚手架。
接下来,我们看看如何安装各种框架 –
茉莉花框架
它是 Protractor 的默认测试框架。当您安装 Protractor 时,您将获得 Jasmine 2.x 版本。我们不需要单独安装它。
摩卡框架
Mocha 是另一个基本运行在 Node.js 上的 JavaScript 测试框架。为了使用 Mocha 作为我们的测试框架,我们需要使用 BDD(行为驱动开发)接口和 Chai As Promised 的断言。可以在以下命令的帮助下完成安装 –
npm install -g mocha npm install chai npm install chai-as-promised
如您所见,安装 mocha 时使用了 -g 选项,这是因为我们使用 -g 选项全局安装了 Protractor。安装后,我们需要在我们的测试文件中要求并设置 Chai。可以按如下方式完成 –
var chai = require('chai'); var chaiAsPromised = require('chai-as-promised'); chai.use(chaiAsPromised); var expect = chai.expect;
在此之后,我们可以像这样使用 Chai As Promised –
expect(myElement.getText()).to.eventually.equal('some text');
现在,我们需要通过添加 framework: ‘mocha’ 将框架属性设置为配置文件的 mocha。可以在配置文件中添加像 mocha 的“reporter”和“slow”这样的选项,如下所示 –
mochaOpts: { reporter: "spec", slow: 3000 }
黄瓜框架
为了使用 Cucumber 作为我们的测试框架,我们需要将它与具有框架选项custom 的Protractor 集成。安装可以在以下命令的帮助下完成
npm install -g cucumber npm install --save-dev protractor-cucumber-framework
如您所见,在安装 Cucumber 时使用了 -g 选项,这是因为我们全局安装了 Protractor,即使用 -g 选项。接下来,我们需要设置框架属性来定制,加入框架配置文件的:“自定义”和frameworkPath:“量角器黄瓜框架”的配置文件命名cucumberConf.js。
下面显示的示例代码是一个基本的cucumberConf.js 文件,可用于使用Protractor 运行cucumber 特征文件 –
exports.config = { seleniumAddress: 'http://localhost:4444/wd/hub', baseUrl: 'https://angularjs.org/', capabilities: { browserName:'Firefox' }, framework: 'custom', frameworkPath: require.resolve('protractor-cucumber-framework'), specs: [ './cucumber/*.feature' ], // cucumber command line options cucumberOpts: { require: ['./cucumber/*.js'], tags: [], strict: true, format: ["pretty"], 'dry-run': false, compiler: [] }, onPrepare: function () { browser.manage().window().maximize(); } };
量角器 – 编写第一个测试
在本章中,让我们了解如何在 Protractor 中编写第一个测试。
量角器所需的文件
量角器需要以下两个文件才能运行 –
规范或测试文件
它是运行 Protractor 的重要文件之一。在这个文件中,我们将编写我们的实际测试代码。测试代码是使用我们的测试框架的语法编写的。
例如,如果我们使用Jasmine框架,那么测试代码将使用Jasmine的语法编写。该文件将包含测试的所有功能流和断言。
简单来说,我们可以说这个文件包含与应用程序交互的逻辑和定位器。
例子
以下是一个简单的脚本 TestSpecification.js,它让测试用例导航到一个 URL 并检查页面标题 –
//TestSpecification.js describe('Protractor Demo', function() { it('to check the page title', function() { browser.ignoreSynchronization = true; browser.get('https://www.tutorialspoint.com/tutorialslibrary.htm'); browser.driver.getTitle().then(function(pageTitle) { expect(pageTitle).toEqual('Free Online Tutorials and Courses'); }); }); });
代码说明
上述规范文件的代码可以解释如下 –
浏览器
它是 Protractor 创建的全局变量,用于处理所有浏览器级别的命令。它基本上是一个 WebDriver 实例的包装器。browser.get() 是一个简单的 Selenium 方法,它会告诉 Protractor 加载特定页面。
-
describe and it – 两者都是 Jasmine 测试框架的语法。该“说明”用于包含年底我们的测试案例的上端流,而“它”包含了一些测试场景。我们的测试用例程序中可以有多个“it”块。
-
Expect – 这是一个断言,我们将网页标题与一些预定义的数据进行比较。
-
ignoreSynchronization – 它是浏览器的标签,当我们尝试测试非角度网站时使用。Protractor 只希望与有角度的网站一起工作,但如果我们想与非有角度的网站一起工作,那么这个标签必须设置为“true”。
配置文件
顾名思义,该文件提供了所有量角器配置选项的说明。它基本上告诉量角器以下内容 –
- 在哪里可以找到测试或规格文件
- 选择哪个浏览器
- 使用哪种测试框架
- 在哪里与 Selenium 服务器交谈
例子
下面是简单的脚本,config.js,有测试
// config.js exports.config = { directConnect: true, // Capabilities to be passed to the webdriver instance. capabilities: { 'browserName': 'chrome' }, // Framework to use. Jasmine is recommended. framework: 'jasmine', // Spec patterns are relative to the current working directory when // protractor is called. specs: ['TestSpecification.js'],
代码说明
上述配置文件的代码具有三个基本参数,可以解释如下 –
能力参数
该参数用于指定浏览器的名称。它可以在 conf.js 文件的以下代码块中看到 –
exports.config = { directConnect: true, // Capabilities to be passed to the webdriver instance. capabilities: { 'browserName': 'chrome' },
如上所示,此处给出的浏览器名称是“chrome”,这是 Protractor 的默认浏览器。我们还可以更改浏览器的名称。
框架参数
该参数用于指定测试框架的名称。它可以在 config.js 文件的以下代码块中看到 –
exports.config = { directConnect: true, // Framework to use. Jasmine is recommended. framework: 'jasmine',
这里我们使用“jasmine”测试框架。
源文件声明参数
此参数用于指定源文件声明的名称。它可以在 conf.js 文件的以下代码块中看到 –
exports.config = { directConnect: true, // Spec patterns are relative to the current working directory when protractor is called. specs: ['TsetSpecification.js'],
如上所示,此处给出的源文件声明的名称是‘TestSpecification.js’。这是因为,对于此示例,我们创建了名为TestSpecification.js的规范文件。
执行代码
由于我们对运行 Protractor 所需的文件及其编码有了基本的了解,让我们尝试运行该示例。我们可以按照以下步骤来执行这个例子 –
-
步骤 1 – 首先,打开命令提示符。
-
步骤 2 – 接下来,我们需要转到保存文件的目录,即 config.js 和TestSpecification.js。
-
第 3 步– 现在,通过运行 Protrcator config.js 命令来执行 config.js 文件。
下面显示的屏幕截图将解释执行示例的上述步骤 –
从屏幕截图中可以看出,测试已通过。
现在,假设我们正在测试非 Angular 网站并且没有将 ignoreSynchronization 标记设置为 true,那么在执行代码后我们将收到错误“在页面上找不到 Angular”。
可以在以下屏幕截图中看到 –
报告生成
到目前为止,我们已经讨论了运行测试用例所需的文件及其编码。Protractor 还能够为测试用例生成报告。为此,它支持 Jasmine。JunitXMLReporter 可用于自动生成测试执行报告。
但在此之前,我们需要在以下命令的帮助下安装 Jasmine 记者 –
npm install -g jasmine-reporters
如您所见,安装 Jasmine Reporters 时使用了 -g 选项,这是因为我们在全局安装了 Protractor,并带有 -g 选项。
成功安装 jasmine-reporters 后,我们需要将以下代码添加到我们之前使用的 config.js 文件中 –
onPrepare: function(){ //configure junit xml report var jasmineReporters = require('jasmine-reporters'); jasmine.getEnv().addReporter(new jasmineReporters.JUnitXmlReporter({ consolidateAll: true, filePrefix: 'guitest-xmloutput', savePath: 'test/reports' }));
现在,我们的新 config.js 文件如下 –
// An example configuration file. exports.config = { directConnect: true, // Capabilities to be passed to the webdriver instance. capabilities: { 'browserName': 'chrome' }, // Framework to use. Jasmine is recommended. framework: 'jasmine', // Spec patterns are relative to the current working directory when // protractor is called. specs: ['TestSpecification.js'], //framework: "jasmine2", //must set it if you use JUnitXmlReporter onPrepare: function(){ //configure junit xml report var jasmineReporters = require('jasmine-reporters'); jasmine.getEnv().addReporter(new jasmineReporters.JUnitXmlReporter({ consolidateAll: true, filePrefix: 'guitest-xmloutput', savePath: 'reports' })); }, };
以同样的方式运行上述配置文件后,我们之前已经运行过,它将在报告文件夹的根目录下生成一个包含报告的 XML 文件。如果测试成功,报告将如下所示 –
但是,如果测试失败,报告将如下所示 –
量角器 – 核心 APIS
本章让您了解对量角器功能至关重要的各种核心 API。
量角器 API 的重要性
Protractor 为我们提供了广泛的 API,这些 API 非常重要,以便执行以下操作以获取网站的当前状态 –
- 获取我们要测试的网页的 DOM 元素。
- 与 DOM 元素交互。
- 为他们分配行动。
- 向他们分享信息。
要执行上述任务,了解 Protractor API 非常重要。
各种量角器 API
正如我们所知,Protractor 是 Selenium-WebDriver 的包装器,它是 Node.js 的 WebDriver 绑定。量角器具有以下 API –
浏览器
它是 WebDriver 实例的包装器,用于处理浏览器级别的命令,例如导航、页面范围的信息等。例如,browser.get 方法加载页面。
元素
它用于搜索我们正在测试的页面上的 DOM 元素并与之交互。为此,它需要一个参数来定位元素。
定位器(由)
它是元素定位器策略的集合。例如,元素可以通过 CSS 选择器、ID 或它们与 ng-model 绑定的任何其他属性找到。
接下来,我们将详细讨论这些 API 及其功能。
浏览器API
如上所述,它是 WebDriver 实例的包装器,用于处理浏览器级别的命令。它执行以下各种功能 –
功能及其说明
ProtractorBrowser API 的功能如下:
browser.angularAppRoot
浏览器 API 的这个函数为我们将要在其上找到 Angular 的元素设置 CSS 选择器。通常,这个函数在’body’中,但如果是我们的ng-app,它在页面的一个子部分;它也可能是一个子元素。
browser.waitForAngularEnabled
Browser API 的这个功能可以设置为true 或false。顾名思义,如果此函数设置为 false,则 Protractor 将不会等待 Angular $http 和 $timeout 任务完成后再与浏览器交互。我们还可以通过调用 waitForAngularEnabled() 而不传递值来读取当前状态而不更改它。
browser.getProcessedConfig
借助此浏览器 API 功能,我们可以获得当前正在运行的已处理配置对象,包括规范和功能。
browser.forkNewDriverInstance
顾名思义,此函数将派生另一个用于交互式测试的浏览器实例。它可以在启用和禁用控制流的情况下运行。下面给出了两种情况的示例 –
示例 1
在启用控制流的情况下运行browser.forkNewDriverInstance() –
var fork = browser.forkNewDriverInstance(); fork.get(‘page1’);
示例 2
在禁用控制流的情况下运行browser.forkNewDriverInstance() –
var fork = await browser.forkNewDriverInstance().ready; await forked.get(‘page1’);
浏览器重启
顾名思义,它将通过关闭浏览器实例并创建新实例来重新启动浏览器。它还可以在启用和禁用控制流的情况下运行。下面给出了两种情况的示例 –
示例 1 – 在启用控制流的情况下运行browser.restart() –
browser.get(‘page1’); browser.restart(); browser.get(‘page2’);
示例 2 – 在禁用控制流的情况下运行browser.forkNewDriverInstance() –
await browser.get(‘page1’); await browser.restart(); await browser.get(‘page2’);
browser.restartSync
它类似于 browser.restart() 函数。唯一的区别是它直接返回新的浏览器实例,而不是返回解析新浏览器实例的承诺。它只能在启用控制流时运行。
示例– 在启用控制流的情况下运行browser.restartSync() –
browser.get(‘page1’); browser.restartSync(); browser.get(‘page2’);
browser.useAllAngular2AppRoots
顾名思义,它仅与 Angular2 兼容。它将搜索页面上所有可用的 Angular 应用程序,同时查找元素或等待稳定性。
browser.waitForAngular
这个浏览器 API 函数指示 WebDriver 等待,直到 Angular 完成渲染并且在继续之前没有未完成的 $http 或 $timeout 调用。
browser.findElement
顾名思义,这个浏览器 API 函数在搜索元素之前等待 Angular 完成渲染。
browser.isElementPresent
顾名思义,这个浏览器 API 函数将测试元素是否出现在页面上。
browser.addMockModule
每次调用 Protractor.get 方法时,它都会在 Angular 之前添加一个要加载的模块。
例子
browser.addMockModule('modName', function() { angular.module('modName', []).value('foo', 'bar'); });
browser.clearMockModules
与 browser.addMockModule 不同,它将清除已注册的模拟模块列表。
browser.removeMockModule
顾名思义,它将删除一个注册模拟模块。示例: browser.removeMockModule(‘modName’);
browser.getRegisteredMockModules
与 browser.clearMockModule 相对,它将获取已注册的模拟模块列表。
浏览器.get
我们可以使用 browser.get() 将浏览器导航到特定的网址,并在 Angular 加载之前加载该页面的模拟模块。
例子
browser.get(url); browser.get('http://localhost:3000'); // This will navigate to the localhost:3000 and will load mock module if needed
浏览器刷新
顾名思义,这将重新加载当前页面并在 Angular 之前加载模拟模块。
浏览器导航
顾名思义,它用于将导航方法混合回导航对象,以便像以前一样调用它们。示例:driver.navigate().refresh()。
browser.setLocation
它用于使用页面内导航浏览到另一个页面。
例子
browser.get('url/ABC'); browser.setLocation('DEF'); expect(browser.getCurrentUrl()) .toBe('url/DEF');
它将从 ABC 导航到 DEF 页面。
浏览器调试器
顾名思义,这必须与量角器调试一起使用。这个函数基本上是在控制流中添加一个任务来暂停测试并将辅助函数注入浏览器,以便可以在浏览器控制台中完成调试。
浏览器暂停
它用于调试 WebDriver 测试。我们可以在测试中使用browser.pause()从控制流中的那个点进入量角器调试器。
例子
element(by.id('foo')).click(); browser.pause(); // Execution will stop before the next click action. element(by.id('bar')).click();
browser.controlFlowEnabled
它用于确定是否启用控制流。
量角器 – 核心 APIS(CONTD…)
在本章中,让我们学习更多 Protractor 的核心 API。
元素 API
元素是量角器公开的全局函数之一。此函数采用定位器并返回以下内容 –
- ElementFinder,根据定位器查找单个元素。
- ElementArrayFinder,根据定位器查找元素数组。
上述两种方法都支持下面讨论的链接方法。
ElementArrayFinder 的链式函数及其说明
以下是 ElementArrayFinder 的功能 –
element.all(locator).clone
顾名思义,此函数将创建元素数组的浅表副本,即 ElementArrayFinder。
element.all(定位器).all(定位器)
这个函数基本上返回一个新的 ElementArrayFinder ,它可以是空的或包含子元素。它可以用于选择多个元素作为数组,如下所示
例子
element.all(locator).all(locator) elementArr.all(by.css(‘.childselector’)); // it will return another ElementFindArray as child element based on child locator.
element.all(locator).filter(filterFn)
顾名思义,在对 ElementArrayFinder 中的每个元素应用过滤函数后,它会返回一个新的 ElementArrayFinder,其中包含所有通过过滤函数的元素。它基本上有两个参数,第一个是 ElementFinder,第二个是索引。它也可以用于页面对象。
例子
看法
<ul class = "items"> <li class = "one">First</li> <li class = "two">Second</li> <li class = "three">Third</li> </ul>
代码
element.all(by.css('.items li')).filter(function(elem, index) { return elem.getText().then(function(text) { return text === 'Third'; }); }).first().click();
element.all(locator).get(index)
借助这个,我们可以通过索引获取 ElementArrayFinder 中的元素。请注意,索引从 0 开始,负索引被包裹。
例子
看法
<ul class = "items"> <li>First</li> <li>Second</li> <li>Third</li> </ul>
代码
let list = element.all(by.css('.items li')); expect(list.get(0).getText()).toBe('First'); expect(list.get(1).getText()).toBe('Second');
element.all(locator).first()
顾名思义,这将获得 ElementArrayFinder 的第一个元素。它不会检索底层元素。
例子
看法
<ul class = "items"> <li>First</li> <li>Second</li> <li>Third</li> </ul>
代码
let first = element.all(by.css('.items li')).first(); expect(first.getText()).toBe('First');
element.all(locator).last()
顾名思义,这将获得 ElementArrayFinder 的最后一个元素。它不会检索底层元素。
例子
看法
<ul class = "items"> <li>First</li> <li>Second</li> <li>Third</li> </ul>
代码
let first = element.all(by.css('.items li')).last(); expect(last.getText()).toBe('Third');
element.all(定位器).all(选择器)
当对 $$ 的调用可能被链接时,它用于在父级中查找元素数组。
例子
看法
<div class = "parent"> <ul> <li class = "one">First</li> <li class = "two">Second</li> <li class = "three">Third</li> </ul> </div>
代码
let items = element(by.css('.parent')).$$('li');
element.all(locator).count()
顾名思义,这将计算 ElementArrayFinder 表示的元素数量。它不会检索底层元素。
例子
看法
<ul class = "items"> <li>First</li> <li>Second</li> <li>Third</li> </ul>
代码
let list = element.all(by.css('.items li')); expect(list.count()).toBe(3);
element.all(locator).isPresent()
它将与查找器匹配元素。它可以返回真或假。True,如果存在与查找器匹配的任何元素,否则为 False。
例子
expect($('.item').isPresent()).toBeTruthy();
element.all(定位器).定位器
顾名思义,它将返回最相关的定位器。
例子
$('#ID1').locator(); // returns by.css('#ID1') $('#ID1').$('#ID2').locator(); // returns by.css('#ID2') $$('#ID1').filter(filterFn).get(0).click().locator(); // returns by.css('#ID1')
element.all(locator).then(thenFunction)
它将检索由 ElementArrayFinder 表示的元素。
例子
看法
<ul class = "items"> <li>First</li> <li>Second</li> <li>Third</li> </ul>
代码
element.all(by.css('.items li')).then(function(arr) { expect(arr.length).toEqual(3); });
element.all(locator).each(eachFunction)
顾名思义,它会在每个由 ElementArrayFinder 表示的 ElementFinder 上调用输入函数。
例子
看法
<ul class = "items"> <li>First</li> <li>Second</li> <li>Third</li> </ul>
代码
element.all(by.css('.items li')).each(function(element, index) { // It will print First 0, Second 1 and Third 2. element.getText().then(function (text) { console.log(index, text); }); });
element.all(locator).map(mapFunction)
顾名思义,它将在 ElementArrayFinder 中的每个元素上应用映射函数。它有两个论点。第一个是 ElementFinder,第二个是索引。
例子
看法
<ul class = "items"> <li>First</li> <li>Second</li> <li>Third</li> </ul>
代码
let items = element.all(by.css('.items li')).map(function(elm, index) { return { index: index, text: elm.getText(), class: elm.getAttribute('class') }; }); expect(items).toEqual([ {index: 0, text: 'First', class: 'one'}, {index: 1, text: 'Second', class: 'two'}, {index: 2, text: 'Third', class: 'three'} ]);
element.all(locator).reduce(reduceFn)
顾名思义,它将对累加器和使用定位器找到的每个元素应用 reduce 函数。此函数会将每个元素减少为一个值。
例子
看法
<ul class = "items"> <li>First</li> <li>Second</li> <li>Third</li> </ul>
代码
let value = element.all(by.css('.items li')).reduce(function(acc, elem) { return elem.getText().then(function(text) { return acc + text + ' '; }); }, ''); expect(value).toEqual('First Second Third ');
element.all(locator).evaluate
顾名思义,它会评估输入是否在当前底层元素的范围内。
例子
看法
<span class = "foo">{{letiableInScope}}</span>
代码
let value = element.all(by.css('.foo')).evaluate('letiableInScope');
element.all(locator).allowAnimations
顾名思义,它将确定当前底层元素是否允许动画。
例子
element(by.css('body')).allowAnimations(false);
ElementFinder 的链接函数及其说明
ElementFinder 的链接功能及其描述 –
元素(定位器)。克隆
顾名思义,此函数将创建 ElementFinder 的浅表副本。
元素(定位器)。getWebElement()
它将返回由此 ElementFinder 表示的 WebElement,如果该元素不存在,则会引发 WebDriver 错误。
例子
看法
<div class="parent"> some text </div>
代码
// All the four following expressions are equivalent. $('.parent').getWebElement(); element(by.css('.parent')).getWebElement(); browser.driver.findElement(by.css('.parent')); browser.findElement(by.css('.parent'));
元素(定位器)。所有(定位器)
它将在父级中找到一组元素。
例子
看法
<div class = "parent"> <ul> <li class = "one">First</li> <li class = "two">Second</li> <li class = "three">Third</li> </ul> </div>
代码
let items = element(by.css('.parent')).all(by.tagName('li'));
元素(定位器)。元素(定位器)
它将在父级中查找元素。
例子
看法
<div class = "parent"> <div class = "child"> Child text <div>{{person.phone}}</div> </div> </div>
代码
// Calls Chain 2 element. let child = element(by.css('.parent')). element(by.css('.child')); expect(child.getText()).toBe('Child text\n981-000-568'); // Calls Chain 3 element. let triple = element(by.css('.parent')). element(by.css('.child')). element(by.binding('person.phone')); expect(triple.getText()).toBe('981-000-568');
元素(定位器)。所有(选择器)
当对 $$ 的调用可能被链接时,它将在父级中找到一组元素。
例子
看法
<div class = "parent"> <ul> <li class = "one">First</li> <li class = "two">Second</li> <li class = "three">Third</li> </ul> </div>
代码
let items = element(by.css('.parent')).$$('li'));
元素(定位器)。$(定位器)
当对 $ 的调用可能被链接时,它将在父级中查找元素。
例子
看法
<div class = "parent"> <div class = "child"> Child text <div>{{person.phone}}</div> </div> </div>
代码
// Calls Chain 2 element. let child = element(by.css('.parent')). $('.child')); expect(child.getText()).toBe('Child text\n981-000-568'); // Calls Chain 3 element. let triple = element(by.css('.parent')). $('.child')). element(by.binding('person.phone')); expect(triple.getText()).toBe('981-000-568');
元素(定位器)。 isPresent()
它将确定元素是否出现在页面上。
例子
看法
<span>{{person.name}}</span>
代码
expect(element(by.binding('person.name')).isPresent()).toBe(true); // will check for the existence of element expect(element(by.binding('notPresent')).isPresent()).toBe(false); // will check for the non-existence of element
元素(定位器)。isElementPresent()
它与 element(locator).isPresent() 相同。唯一的区别是它将检查由 sublocator 标识的元素是否存在,而不是当前元素查找器。
element.all(locator).evaluate
顾名思义,它会评估输入是否在当前底层元素的范围内。
例子
看法
<span id = "foo">{{letiableInScope}}</span>
代码
let value = element(by.id('.foo')).evaluate('letiableInScope');
元素(定位器)。允许动画
顾名思义,它将确定当前底层元素是否允许动画。
例子
element(by.css('body')).allowAnimations(false);
元素(定位器)。等于
顾名思义,它将比较元素是否相等。
定位器(按)API
它基本上是元素定位器策略的集合,提供了通过绑定、模型等在 Angular 应用程序中查找元素的方法。
功能及其说明
ProtractorLocators API 的功能如下 –
by.addLocator(locatorName,fuctionOrScript)
它将向 ProtrcatorBy 的这个实例添加一个定位器,它可以进一步与 element(by.locatorName(args)) 一起使用。
例子
看法
<button ng-click = "doAddition()">Go!</button>
代码
// Adding the custom locator. by.addLocator('buttonTextSimple', function(buttonText, opt_parentElement, opt_rootSelector) { var using = opt_parentElement || document, buttons = using.querySelectorAll('button'); return Array.prototype.filter.call(buttons, function(button) { return button.textContent === buttonText; }); }); element(by.buttonTextSimple('Go!')).click();// Using the custom locator.
通过绑定
顾名思义,它会通过文本绑定来查找元素。将完成部分匹配,以便返回绑定到包含输入字符串的变量的任何元素。
例子
看法
<span>{{person.name}}</span> <span ng-bind = "person.email"></span>
代码
var span1 = element(by.binding('person.name')); expect(span1.getText()).toBe('Foo'); var span2 = element(by.binding('person.email')); expect(span2.getText()).toBe('[email protected]');
通过精确绑定
顾名思义,它将通过精确绑定来查找元素。
例子
看法
<spangt;{{ person.name }}</spangt; <span ng-bind = "person-email"gt;</spangt; <spangt;{{person_phone|uppercase}}</span>
代码
expect(element(by.exactBinding('person.name')).isPresent()).toBe(true); expect(element(by.exactBinding('person-email')).isPresent()).toBe(true); expect(element(by.exactBinding('person')).isPresent()).toBe(false); expect(element(by.exactBinding('person_phone')).isPresent()).toBe(true); expect(element(by.exactBinding('person_phone|uppercase')).isPresent()).toBe(true); expect(element(by.exactBinding('phone')).isPresent()).toBe(false);
by.model(modelName)
顾名思义,它会通过 ng-model 表达式查找元素。
例子
看法
<input type = "text" ng-model = "person.name">
代码
var input = element(by.model('person.name')); input.sendKeys('123'); expect(input.getAttribute('value')).toBe('Foo123');
by.buttonText
顾名思义,它会通过文本找到一个按钮。
例子
看法
<button>Save</button>
代码
element(by.buttonText('Save'));
by.partialButtonText
顾名思义,它会通过部分文本找到一个按钮。
例子
看法
<button>Save my file</button>
代码
element(by.partialButtonText('Save'));
中继器
顾名思义,它会在 ng-repeat 中找到一个元素。
例子
看法
<div ng-repeat = "cat in pets"> <span>{{cat.name}}</span> <span>{{cat.age}}</span> <</div> <div class = "book-img" ng-repeat-start="book in library"> <span>{{$index}}</span> </div> <div class = "book-info" ng-repeat-end> <h4>{{book.name}}</h4> <p>{{book.blurb}}</p> </div>
代码
var secondCat = element(by.repeater('cat in pets').row(1)); // It will return the DIV for the second cat. var firstCatName = element(by.repeater('cat in pets'). row(0).column('cat.name')); // It will return the SPAN for the first cat's name.
by.exactRepeater
顾名思义,它将通过精确的中继器查找元素。
例子
看法
<li ng-repeat = "person in peopleWithRedHair"></li> <li ng-repeat = "car in cars | orderBy:year"></li>
代码
expect(element(by.exactRepeater('person in peopleWithRedHair')).isPresent()) .toBe(true); expect(element(by.exactRepeater('person in people')).isPresent()).toBe(false); expect(element(by.exactRepeater('car in cars')).isPresent()).toBe(true);
by.cssContainingText
顾名思义,它将通过 CSS 查找包含精确字符串的元素
例子
看法
<ul> <li class = "pet">Dog</li> <li class = "pet">Cat</li> </ul>
代码
var dog = element(by.cssContainingText('.pet', 'Dog')); // It will return the li for the dog, but not for the cat.
by.options(optionsDescriptor)
顾名思义,它会通过 ng-options 表达式查找元素。
例子
看法
<select ng-model = "color" ng-options = "c for c in colors"> <option value = "0" selected = "selected">red</option> <option value = "1">green</option> </select>
代码
var allOptions = element.all(by.options('c for c in colors')); expect(allOptions.count()).toEqual(2); var firstOption = allOptions.first(); expect(firstOption.getText()).toEqual('red');
by.deepCSS(选择器)
顾名思义,它会在 shadow DOM 中通过 CSS 选择器找到一个元素。
例子
看法
<div> <span id = "outerspan"> <"shadow tree"> <span id = "span1"></span> <"shadow tree"> <span id = "span2"></span> </> </> </div>
代码
var spans = element.all(by.deepCss('span')); expect(spans.count()).toEqual(3);
量角器 – 对象
本章详细讨论了量角器中的对象。
什么是页面对象?
页面对象是一种设计模式,在编写 e2e 测试以增强测试维护和减少代码重复方面变得流行。它可以定义为一个面向对象的类,用作 AUT(被测应用程序)页面的接口。但是,在深入研究页面对象之前,我们必须了解自动化 UI 测试的挑战以及处理它们的方法。
自动化 UI 测试的挑战
以下是自动化 UI 测试的一些常见挑战 –
用户界面更改
使用 UI 测试时非常常见的问题是 UI 中发生的变化。例如,大多数情况下,按钮或文本框等通常会发生变化并为 UI 测试带来问题。
缺乏 DSL(领域特定语言)支持
UI 测试的另一个问题是缺乏 DSL 支持。有了这个问题,就很难理解正在测试的内容。
大量重复/代码重复
UI 测试中的下一个常见问题是存在大量重复或代码重复。可以在以下代码行的帮助下理解 –
element(by.model(‘event.name’)).sendKeys(‘An Event’); element(by.model(‘event.name’)).sendKeys(‘Module 3’); element(by.model(‘event.name’));
艰难的维护
由于上述挑战,维护成为头疼的问题。这是因为我们必须找到所有实例,替换为新名称、选择器和其他代码。我们还需要花费大量时间使测试与重构保持一致。
损坏的测试
UI 测试中的另一个挑战是在测试中发生了很多失败。
应对挑战的方法
我们已经看到了 UI 测试的一些常见挑战。处理此类挑战的一些方法如下 –
手动更新参考
处理上述挑战的第一个选项是手动更新引用。此选项的问题在于我们必须在代码和测试中进行手动更改。这可以在您有一个或两个测试文件时完成,但是如果您在一个项目中有数百个测试文件怎么办?
使用页面对象
处理上述挑战的另一种选择是使用页面对象。页面对象基本上是一个简单的 JavaScript,它封装了 Angular 模板的属性。例如,以下规范文件是在没有和有页面对象的情况下编写的,以了解差异 –
没有页面对象
describe('angularjs homepage', function() { it('should greet the named user', function() { browser.get('http://www.angularjs.org'); element(by.model('yourName')).sendKeys('Julie'); var greeting = element(by.binding('yourName')); expect(greeting.getText()).toEqual('Hello Julie!'); }); });
使用页面对象
为了使用页面对象编写代码,我们需要做的第一件事是创建一个页面对象。因此,上述示例的页面对象可能如下所示 –
var AngularHomepage = function() { var nameInput = element(by.model('yourName')); var greeting = element(by.binding('yourName')); this.get = function() { browser.get('http://www.angularjs.org'); }; this.setName = function(name) { nameInput.sendKeys(name); }; this.getGreetingText = function() { return greeting.getText(); }; }; module.exports = new AngularHomepage();
使用页面对象组织测试
我们已经在上面的例子中看到了使用页面对象来处理 UI 测试的挑战。接下来,我们将讨论如何使用它们来组织测试。为此,我们需要修改测试脚本而不修改测试脚本的功能。
例子
为了理解这个概念,我们将上面的配置文件与页面对象一起使用。我们需要修改测试脚本如下 –
var angularHomepage = require('./AngularHomepage'); describe('angularjs homepage', function() { it('should greet the named user', function() { angularHomepage.get(); angularHomepage.setName('Julie'); expect(angularHomepage.getGreetingText()).toEqual ('Hello Julie!'); }); });
在这里,请注意页面对象的路径将与您的规范相关。
同样,我们也可以将我们的测试套件分成各种测试套件。然后可以更改配置文件如下
exports.config = { // The address of a running selenium server. seleniumAddress: 'http://localhost:4444/wd/hub', // Capabilities to be passed to the webdriver instance. capabilities: { 'browserName': 'chrome' }, // Spec patterns are relative to the location of the spec file. They may // include glob patterns. suites: { homepage: 'tests/e2e/homepage/**/*Spec.js', search: ['tests/e2e/contact_search/**/*Spec.js', 'tests/e2e/venue_search/**/*Spec.js'] }, // Options to be passed to Jasmine-node. jasmineNodeOpts: { showColors: true, // Use colors in the command line report. } };
现在,我们可以轻松地在运行一个或另一组测试之间切换。以下命令将仅运行测试的主页部分 –
protractor protractor.conf.js --suite homepage
同样,我们可以使用以下命令运行特定的测试套件 –
protractor protractor.conf.js --suite homepage,search
量角器 – 调试
现在我们已经在前面的章节中看到了 Protractor 的所有概念,让我们详细了解本章中的调试概念。
介绍
端到端 (e2e) 测试非常难以调试,因为它们依赖于该应用程序的整个生态系统。我们已经看到它们依赖于各种操作,或者特别是我们可以说依赖于先前的操作,例如登录,有时它们依赖于许可。调试 e2e 测试的另一个困难是它对 WebDriver 的依赖,因为它在不同的操作系统和浏览器中的作用不同。最后,调试 e2e 测试还会生成很长的错误消息,并且很难将浏览器相关问题和测试过程错误分开。
故障类型
测试套件失败可能有多种原因,以下是一些众所周知的失败类型 –
网络驱动程序故障
当命令无法完成时,WebDriver 会抛出错误。例如,浏览器无法获取定义的地址,或者未按预期找到元素。
WebDriver 意外失败
当无法更新 Web 驱动程序管理器时,会发生意外的浏览器和与操作系统相关的故障。
Angular 的量角器故障
当 Protractor 没有按预期在库中找到 Angular 时,就会发生 Protractor for Angular 的失败。
量角器Angular2失败
在这种失败的情况下,如果配置中没有找到 useAllAngular2AppRoots 参数,Protractor 就会失败。发生这种情况是因为,如果没有这个,测试过程将查看一个单一的根元素,同时期望过程中有多个元素。
超时的量角器失败
当测试规范遇到循环或长池并且未能及时返回数据时,就会发生这种失败。
预期失败
最常见的测试失败之一,显示了正常的预期失败是什么样的。
为什么在量角器中调试很重要?
假设,如果您编写了测试用例并且它们失败了,那么知道如何调试这些测试用例非常重要,因为很难找到发生错误的确切位置。在使用 Protractor 时,您会在命令行中出现一些红色字体的长错误。
暂停和调试测试
在量角器中调试的方法在这里解释&miuns
暂停方法
在 Protractor 中使用 pause 方法调试测试用例是最简单的方法之一。我们可以在要暂停测试代码的地方输入以下命令 &miuns
browser.pause();
当运行代码命中上述命令时,它会在该点暂停正在运行的程序。之后,我们可以根据自己的喜好给出以下命令 –
前进的C型
每当命令用完时,我们必须键入 C 才能前进。如果你不输入 C,测试将不会运行完整的代码,并且会由于 Jasmine 超时错误而失败。
输入 repl 以进入交互模式
交互模式的好处是我们可以将 WebDriver 命令发送到我们的浏览器。如果我们想进入交互模式,那么输入repl。
键入 Ctrl-C 退出并继续测试
为了从暂停状态退出测试并从停止的地方继续测试,我们需要输入 Ctrl-C。
例子
在这个例子中,我们有一个名为example_debug.js 的规范文件,量角器尝试通过 locator by.binding (‘mmmm’)来识别一个元素,但是 URL( https://angularjs.org/页面没有指定的元素定位器。
describe('Suite for protractor debugger',function(){ it('Failing spec',function(){ browser.get("http://angularjs.org"); element(by.model('yourName')).sendKeys('Vijay'); //Element doesn't exist var welcomeText = element(by.binding('mmmm')).getText(); expect('Hello '+welcomeText+'!').toEqual('Hello Ram!') }); });
现在,为了执行上面的测试,我们需要在上面的规范文件中添加 browser.pause() 代码,在你想要暂停测试的地方。它看起来如下 –
describe('Suite for protractor debugger',function(){ it('Failing spec',function(){ browser.get("http://angularjs.org"); browser.pause(); element(by.model('yourName')).sendKeys('Vijay'); //Element doesn't exist var welcomeText = element(by.binding('mmmm')).getText(); expect('Hello '+welcomeText+'!').toEqual('Hello Ram!') }); });
但是在执行之前,我们还需要对配置文件进行一些更改。我们正在对之前使用的配置文件进行以下更改,在上一章中命名为example_configuration.js –
// An example configuration file. exports.config = { directConnect: true, // Capabilities to be passed to the webdriver instance. capabilities: { 'browserName': 'chrome' }, // Framework to use. Jasmine is recommended. framework: 'jasmine', // Spec patterns are relative to the current working directory when // protractor is called. specs: ['example_debug.js'], allScriptsTimeout: 999999, jasmineNodeOpts: { defaultTimeoutInterval: 999999 }, onPrepare: function () { browser.manage().window().maximize(); browser.manage().timeouts().implicitlyWait(5000); } };
现在,运行以下命令 –
protractor example_configuration.js
调试器将在上述命令后启动。
调试器方法
在 Protractor 中使用 pause 方法调试测试用例是一种有点高级的方法。我们可以在想要破坏测试代码的地方输入以下命令 –
browser.debugger();
它使用节点调试器来调试测试代码。要运行上述命令,我们必须在从测试项目位置打开的单独命令提示符中键入以下命令 –
protractor debug protractor.conf.js
在这种方法中,我们还需要在终端中键入 C 才能继续测试代码。但与暂停方法相反,在此方法中只输入一次。
例子
在这个例子中,我们使用了上面使用的名为 b example_debug.js的相同规范文件。唯一的区别是,我们需要在要破坏测试代码的地方使用browser.debugger()而不是browser.pause()。它看起来如下 –
describe('Suite for protractor debugger',function(){ it('Failing spec',function(){ browser.get("http://angularjs.org"); browser.debugger(); element(by.model('yourName')).sendKeys('Vijay'); //Element doesn't exist var welcomeText = element(by.binding('mmmm')).getText(); expect('Hello '+welcomeText+'!').toEqual('Hello Ram!') }); });
我们使用与上面示例中相同的配置文件example_configuration.js。
现在,使用以下调试命令行选项运行量角器测试
protractor debug example_configuration.js
调试器将在上述命令后启动。
量角器 – 量角器风格指南
在本章中,让我们详细了解量角器的样式指南。
介绍
风格指南由两位软件工程师创建,分别是 ING 的前端工程师Carmen Popoviciu和谷歌的软件工程师Andres Dominguez。因此,这个风格指南也被称为 Carmen Popoviciu 和 Google 的量角器风格指南。
这个风格指南可以分为以下五个关键点 –
- 通用规则
- 项目结构
- 定位器策略
- 页面对象
- 测试套件
通用规则
以下是使用量角器进行测试时必须注意的一些通用规则 –
不要端到端测试已经过单元测试的内容
这是 Carmen 和 Andres 给出的第一个通用规则。他们建议我们一定不要对已经过单元测试的代码进行 e2e 测试。其背后的主要原因是单元测试比 e2e 测试快得多。另一个原因是我们必须避免重复测试(不要同时执行单元和 e2e 测试)以节省我们的时间。
只使用一个配置文件
另一个重要的建议是我们必须只使用一个配置文件。不要为您正在测试的每个环境创建配置文件。您可以使用grunt-protractor-coverage来设置不同的环境。
避免在测试中使用逻辑
我们必须避免在我们的测试用例中使用 IF 语句或 FOR 循环,因为如果我们这样做,那么测试可能会通过而不测试任何东西,或者它可能会运行得很慢。
使测试在文件级别独立
启用共享后,Protractor 可以并行运行测试。当这些文件可用时,它们会在不同的浏览器上执行。Carmen 和 Andres 建议至少在文件级别使测试独立,因为量角器运行它们的顺序是不确定的,而且单独运行测试很容易。
项目结构
Protractor 风格指南的另一个重要要点是项目的结构。以下是关于项目结构的建议 –
在合理的结构中摸索 e2e 测试
Carmen 和 Andres 建议我们必须将 e2e 测试分组到一个对您的项目结构有意义的结构中。此建议背后的原因是文件的查找将变得容易并且文件夹结构将更具可读性。此步骤还将 e2e 测试与单元测试分开。他们建议应避免以下类型的结构 –
|-- project-folder |-- app |-- css |-- img |-- partials home.html profile.html contacts.html |-- js |-- controllers |-- directives |-- services app.js ... index.html |-- test |-- unit |-- e2e home-page.js home-spec.js profile-page.js profile-spec.js contacts-page.js contacts-spec.js
另一方面,他们推荐了以下类型的结构 –
|-- project-folder |-- app |-- css |-- img |-- partials home.html profile.html contacts.html |-- js |-- controllers |-- directives |-- services app.js ... index.html |-- test |-- unit |-- e2e |-- page-objects home-page.js profile-page.js contacts-page.js home-spec.js profile-spec.js contacts-spec.js
定位器策略
以下是使用量角器进行测试时必须注意的一些定位器策略 –
从不使用 XPATH
这是量角器样式指南中推荐的第一个定位器策略。其背后的原因是 XPath 需要大量维护,因为标记很容易更改。此外,XPath 表达式是最慢的并且很难调试。
总是喜欢特定于量角器的定位器,例如 by.model 和 by.binding
特定于量角器的定位符,例如 by.model 和 by.binding,简短、具体且易于阅读。在他们的帮助下,编写我们的定位器也很容易。
例子
看法
<ul class = "red"> <li>{{color.name}}</li> <li>{{color.shade}}</li> <li>{{color.code}}</li> </ul> <div class = "details"> <div class = "personal"> <input ng-model = "person.name"> </div> </div>
对于上述代码,建议避免以下情况 –
var nameElement = element.all(by.css('.red li')).get(0); var personName = element(by.css('.details .personal input'));
另一方面,建议使用以下内容 –
var nameElement = element.all(by.css('.red li')).get(0); var personName = element(by.css('.details .personal input'));
var nameElement = element(by.binding('color.name')); var personName = element(by.model('person.name'));
当没有可用的量角器定位器时,建议首选 by.id 和 by.css。
始终避免使用文本定位器频繁更改文本
我们必须避免使用基于文本的定位器,例如 by.linkText、by.buttonText 和 by.cssContaningText,因为按钮、链接和标签的文本会随着时间的推移而频繁变化。
页面对象
如前所述,页面对象封装了有关我们应用程序页面上元素的信息,因此有助于我们编写更清晰的测试用例。页面对象的一个非常有用的优点是它们可以在多个测试中重用,如果我们的应用程序的模板发生了变化,我们只需要更新页面对象。以下是使用量角器进行测试时必须注意的页面对象的一些建议 –
要与被测页面交互,请使用页面对象
推荐使用页面对象与被测页面交互,因为它们可以封装被测页面上元素的信息,也可以重复使用。
始终为每个文件声明一页对象
我们应该在自己的文件中定义每个页面对象,因为这样可以保持代码干净并且查找内容变得容易。
在页面目标文件的末尾总是使用单个 module.exports
建议每个页面对象都声明一个类,这样我们只需要导出一个类。例如,应避免以下使用目标文件 –
var UserProfilePage = function() {}; var UserSettingsPage = function() {}; module.exports = UserPropertiesPage; module.exports = UserSettingsPage;
但另一方面,建议使用以下方法 –
/** @constructor */ var UserPropertiesPage = function() {}; module.exports = UserPropertiesPage;
在顶部声明所有必需的模块
我们应该在页面对象的顶部声明所有必需的模块,因为它使模块依赖关系清晰且易于查找。
在测试套件开始时实例化所有页面对象
建议在测试套件开始时实例化所有页面对象,因为这会将依赖项与测试代码分开,并使依赖项可用于套件的所有规范。
不要在页面对象中使用 expect()
我们不应该在页面对象中使用 expect() ,即我们不应该在我们的页面对象中做任何断言,因为所有的断言都必须在测试用例中完成。
另一个原因是测试的读者应该能够通过阅读测试用例来理解应用程序的行为。