Protractor - 快速指南


Protractor - 简介

本章向您介绍 Protractor,您将了解这个测试框架的起源以及为什么必须选择它、该工具的工作原理和局限性。

什么是Protractor?

Protractor 是一个用于 Angular 和 AngularJS 应用程序的开源端到端测试框架。它是由 Google 在 WebDriver 之上构建的。它还可以替代现有的 AngularJS E2E 测试框架“Angular Scenario Runner”。

它还作为一个解决方案集成商,结合了 NodeJS、Selenium、Jasmine、WebDriver、Cucumber、Mocha 等强大技术。除了测试 AngularJS 应用程序外,它还为普通 Web 应用程序编写自动回归测试。它允许我们像真实用户一样测试我们的应用程序,因为它使用实际的浏览器运行测试。

下图将简要概述Protractor -

Protractor概述

请注意,在上图中,我们有 -

  • Protractor - 如前所述,它是 WebDriver JS 的包装器,专门为角度应用程序设计。

  • Jasmine - 它基本上是一个用于测试 JavaScript 代码的Behave驱动开发框架。我们可以使用 Jasmine 轻松编写测试。

  • WebDriver JS - 它是 selenium 2.0/WebDriver 的 Node JS 绑定实现。

  • Selenium - 它只是自动化浏览器。

起源

如前所述,Protractor 是现有 AngularJS E2E 测试框架“Angular Scenario Runner”的替代品。基本上,Protractor 的起源是从 Scenario Runner 的结束开始的。这里出现的一个问题是我们为什么需要构建 Protractor?为了理解这一点,我们首先需要了解一下它的前身——Scenario Runner。

Protractor的成立

Julie Ralph 是 Protractor 开发的主要贡献者,她在 Google 内部的其他项目中拥有以下 Angular Scenario Runner 的经验。这进一步成为构建 Protractor 的动机,特别是为了填补空白 -

“我们尝试使用 Scenario Runner,但发现它确实无法完成我们需要测试的事情。我们需要测试诸如登录之类的事情。您的登录页面不是 Angular 页面,并且 Scenario Runner 无法处理这一点。它无法处理诸如弹出窗口和多个窗口、浏览浏览器历史记录之类的事情。”

Protractor 最大的优势是 Selenium 项目的成熟性,它封装了它的方法,因此可以轻松地用于 Angular 项目。Protractor 的设计方式是测试应用程序的所有层,例如 Web UI、后端服务、持久层等。

为什么是Protractor?

众所周知,几乎所有的应用程序都是使用 JavaScript 进行开发。当 JavaScript 的大小增加并且由于应用程序本身数量的增加而变得复杂时,测试人员的任务变得困难。大多数时候,在 AngularJS 应用程序中捕获 Web 元素变得非常困难,通过使用 JUnit 或 Selenium WebDriver 使用扩展的 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的工作

Protractor 是一个测试框架,它与 Selenium 结合使用,提供自动化测试基础设施,用于模拟用户与在浏览器或移动设备中运行的 AngularJS 应用程序的交互。

Protractor的工作原理可以通过以下步骤来理解 -

  • 步骤 1 - 在第一步中,我们需要编写测试。这可以在茉莉花、摩卡咖啡或Cucumber的帮助下完成。

  • 步骤 2 - 现在,我们需要运行测试,这可以在 Protractor 的帮助下完成。它也称为测试运行程序。

  • 步骤 3 - 在此步骤中,Selenium 服务器将帮助管理浏览器。

  • 步骤 4 - 最后,在 Selenium WebDriver 的帮助下调用浏览器 API。

Protractor的工作

优点

这个开源端到端测试框架具有以下优点 -

  • Protractor 是一款开源工具,非常易于安装和设置。

  • 与 Jasmine 框架配合良好,可以创建测试。

  • 支持测试驱动开发(TDD)。

  • 包含自动等待,这意味着我们不需要在测试中显式添加等待和睡眠。

  • 提供 Selenium WebDriver 的所有优点。

  • 支持通过多个浏览器并行测试。

  • 提供自动同步的好处。

  • 具有优异的测试速度。

局限性

这个开源端到端测试框架具有以下限制 -

  • 不会发现浏览器自动化中的任何垂直领域,因为它是 WebDriver JS 的包装器。

  • JavaScript 知识对于用户来说至关重要,因为它仅适用于 JavaScript。

  • 只提供前端测试,因为它是UI驱动的测试工具。

Protractor - Javascript 测试的概念

由于 JavaScript 知识对于使用 Protractor 至关重要,因此在本章中,让我们详细了解 JavaScript 测试的概念。

JavaScript 测试和自动化

JavaScript 是最流行的动态类型和解释性脚本语言,但最具挑战性的任务是测试代码。这是因为,与 JAVA 和 C++ 等其他编译语言不同,JavaScript 中没有可以帮助测试人员找出错误的编译步骤。此外,基于浏览器的测试非常耗时;因此,需要支持 JavaScript 自动化测试的工具。

自动化测试的概念

编写测试始终是一个很好的做法,因为它可以使代码变得更好;手动测试的问题是它有点耗时并且容易出错。手动测试的过程对于程序员来说也很无聊,因为他们需要重复这个过程,编写测试规范,更改代码并刷新浏览器多次。此外,手动测试也会减慢开发过程。

由于上述原因,拥有一些可以自动化这些测试并帮助程序员摆脱这些重复且无聊的步骤的工具总是有用的。开发人员应该怎样做才能使测试过程自动化?

基本上,开发人员可以在 CLI(命令行解释器)或开发 IDE(集成开发环境)中实现工具集。然后,即使没有开发人员的输入,这些测试也将在单独的进程中连续运行。JavaScript 的自动化测试也并不新鲜,并且已经开发了许多工具,例如 Karma、Protractor、CasperJS 等。

JavaScript 测试类型

可以针对不同目的进行不同的测试。例如,编写一些测试是为了检查程序中函数的Behave,而编写其他一些测试是为了测试模块或功能的流程。因此,我们有以下两种类型的测试 -

单元测试

测试是在称为单元的程序的最小可测试部分上完成的。该单元基本上是单独测试的,该单元对其他部分没有任何依赖性。对于 JavaScript,具有特定Behave的单个方法或函数可以是一个代码单元,并且这些代码单元必须以隔离的方式进行测试。

单元测试的优点之一是可以按任意顺序进行单元测试,因为单元之间是相互独立的。单元测试的另一个真正重要的优点是它可以随时运行测试,如下所示 -

  • 从开发过程的一开始。
  • 完成任何模块/功能的开发后。
  • 修改任何模块/功能后。
  • 在现有应用程序中添加任何新功能后。

对于 JavaScript 应用程序的自动化单元测试,我们可以选择许多测试工具和框架,例如 Mocha、Jasmine 和 QUnit。

端到端测试

它可以被定义为一种测试方法,用于测试应用程序从开始到结束(从一端到另一端)的流程是否按照设计正常工作。

端到端测试也称为功能/流程测试。与单元测试不同,端到端测试测试各个组件如何作为应用程序一起工作。这是单元测试和端到端测试之间的主要区别。

例如,假设我们有一个注册模块,用户需要提供一些有效信息才能完成注册,那么该特定模块的端到端测试将按照以下步骤完成测试 -

  • 首先,它将加载/编译表单或模块。
  • 现在,它将获取表单元素的 DOM(文档对象模型)。
  • 接下来,触发提交按钮的点击事件来检查它是否有效。
  • 现在,出于验证目的,从输入字段收集值。
  • 接下来,应验证输入字段。
  • 出于测试目的,调用一个假 API 来存储数据。

每个步骤都会给出自己的结果,该结果将与预期结果集进行比较。

现在出现的问题是,虽然这种 E2E 或功能测试也可以手动执行,但为什么我们需要自动化?主要原因是自动化将使这个测试过程变得容易。为此,可以轻松与任何应用程序集成的一些可用工具包括 Selenium、PhantomJS 和 Protractor。

测试工具和框架

我们有各种用于 Angular 测试的测试工具和框架。以下是一些著名的工具和框架 -

业力

Karma 由 Vojta Jina 创建,是一个测试运行程序。最初这个项目被称为睾丸。它不是一个测试框架,这意味着它使我们能够在真实浏览器上轻松自动地运行 JavaScript 单元测试。Karma 是为 AngularJS 构建的,因为在 Karma 之前,还没有针对基于 Web 的 JavaScript 开发人员的自动化测试工具。另一方面,借助 Karma 提供的自动化功能,开发人员可以运行简单的单个命令并确定整个测试套件是否通过或失败。

使用 Karma 的优点

与手动过程相比,以下是使用 Karma 的一些优点 -

  • 在多个浏览器和设备中自动进行测试。
  • 监视文件中的错误并修复它们。
  • 提供在线支持和文档。
  • 简化与持续集成服务器的集成。

使用 Karma 的缺点

以下是使用 Karma 的一些缺点 -

使用 Karma 的主要缺点是它需要额外的工具来配置和维护。

如果您将 Karma 测试运行程序与 Jasmine 一起使用,那么在一个元素具有多个 id 的情况下,可用于查找有关设置 CSS 的信息的文档会较少。

茉莉花

Jasmine 是一个用于测试 JavaScript 代码的Behave驱动开发框架,由 Pivotal Labs 开发。在积极开发 Jasmine 框架之前,Pivotal Labs 也开发了一个名为 JsUnit 的类似单元测试框架,它有一个内置的测试运行器。浏览器测试可以通过 Jasmine 测试运行,方法是包含 SpecRunner.html 文件或将其用作命令行测试运行程序。它也可以在有或没有 Karma 的情况下使用。

使用茉莉花的优点

以下是使用 Jasmine 的一些优点 -

  • 独立于浏览器、平台和语言的框架。

  • 支持测试驱动开发 (TDD) 以及Behave驱动开发。

  • 与 Karma 默认集成。

  • 易于理解的语法。

  • 提供测试间谍、伪造和传递功能,作为附加功能协助测试。

使用茉莉花的缺点

以下是使用 Jasmine 的缺点 -

  • 用户必须在更改时返回测试,因为运行测试时 Jasmine 中没有可用的文件监视功能。

摩卡

Mocha 是为 Node.js 应用程序编写的一个测试框架,但它也支持浏览器测试。它很像 Jasmine,但它们之间的主要区别是 Mocha 需要一些插件和库,因为它不能作为测试框架独立运行。另一方面,茉莉花是独立的。不过,Mocha 使用起来比 Jasmine 更灵活。

使用摩卡的优点

以下是使用摩卡的一些优点 -

  • Mocha 非常容易安装和配置。
  • 用户友好且简单的文档。
  • 包含带有多个节点项目的插件。

使用摩卡的缺点

以下是使用 Mocha 的一些缺点 -

  • 它需要单独的模块来进行断言、间谍等。
  • 它还需要额外的配置才能与 Karma 一起使用。

质量单位

QUint 最初由 John Resig 于 2008 年作为 jQuery 的一部分开发,是一个功能强大且易于使用的 JavaScript 单元测试套件。它可用于测试任何通用 JavaScript 代码。虽然它专注于在浏览器中测试JavaScript,但开发人员使用起来非常方便。

使用 QUnit 的优点

以下是使用 QUnit 的一些优点 -

  • 易于安装和配置。
  • 用户友好且简单的文档。

使用 QUnit 的缺点

以下是使用 QUnit 的缺点 -

  • 它主要是为 jQuery 开发的,因此不太适合与其他框架一起使用。

Selenium

Selenium 最初由 Jason Huggins 于 2004 年开发,作为 ThoughtWorks 的内部工具,是一个开源测试自动化工具。Selenium 将自己定义为“Selenium 自动化浏览器。就是这样!”。浏览器的自动化意味着开发人员可以非常轻松地与浏览器交互。

使用 Selenium 的优点

以下是使用 Selenium 的一些优点 -

  • 包含大量功能集。
  • 支持分布式测试。
  • 通过 Sauce Labs 等服务获得 SaaS 支持。
  • 易于使用,提供简单的文档和丰富的资源。

使用 Selenium 的缺点

以下是使用 Selenium 的一些缺点 -

  • 使用 Selenium 的一个主要缺点是它必须作为单独的进程运行。
  • 配置有点麻烦,因为开发者需要遵循几个步骤。

Protractor - 入门

在前面的章节中,我们学习了 Protractor 的基础知识。在本章中,让我们学习如何安装和配置它。

先决条件

在您的计算机上安装 Protractor 之前,我们需要满足以下先决条件 -

Node.js

Protractor 是一个 Node.js 模块,因此非常重要的前提是我们的计算机上必须安装 Node.js。我们将使用 Node.js 附带的 npm(一个 JavaScript 包管理器)来安装 Protractor 包。

要安装 Node.js,请点击官方链接 - https://nodejs.org/en/download/。安装 Node.js 后,您可以通过在命令提示符中写入命令node --versionnpm --version来检查 Node.js 和 npm 的版本,如下所示 -

先决条件

铬合金

Google Chrome 是 Google 构建的网络浏览器,将用于在 Protractor 中运行端到端测试,而无需 Selenium 服务器。您可以通过点击链接下载 Chrome - https://www.google.com/chrome/

适用于 Chrome 的 Selenium WebDriver

该工具随 Protractor npm 模块提供,允许我们与 Web 应用程序交互。

安装Protractor

在我们的计算机上安装 Node.js 后,我们可以借助以下命令安装 Protractor -

npm install -g protractor

成功安装 Protractor 后,我们可以通过在命令提示符中编写protractor --version命令来检查其版本,如下所示 -

安装Protractor

安装适用于 Chrome 的 WebDriver

安装Protractor后,我们需要安装Selenium WebDriver for Chrome。可以借助以下命令进行安装 -

webdriver-manager update

上面的命令将创建一个 Selenium 目录,其中包含项目中使用的所需 Chrome 驱动程序。

确认安装和配置

安装Protractor后,我们可以通过稍微更改示例中提供的conf.js来确认Protractor的安装和配置。您可以在根目录node_modules/Protractor/example中找到此 conf.js 文件。

为此,首先在同一目录(即node_modules/Protractor/example)中创建一个名为testingconfig.js的新文件。

现在,在conf.js 文件中的源文件声明参数下,编写testingconfig.js。

接下来,保存并关闭所有文件并打开命令提示符。运行conf.js 文件,如下面的屏幕截图所示。

如果得到如下所示的输出,则 Protractor 的配置和安装成功 -

配置Protractor

上面的输出显示没有规范,因为我们在conf.js文件中的源文件声明参数中提供了空文件。但从上面的输出中,我们可以看到 Protractor 和 WebDriver 都运行成功。

安装和配置问题

在安装和配置 Protractor 和 WebDriver 时,我们可能会遇到以下常见问题 -

Selenium未正确安装

这是安装 WebDriver 时最常见的问题。如果您不更新 WebDriver,就会出现此问题。请注意,我们必须更新 WebDriver,否则我们将无法将其引用到 Protractor 安装。

无法找到测试

另一个常见问题是运行 Protractor 后,显示无法找到测试。为此,我们必须确保相对路径、文件名或扩展名正确。我们还需要非常仔细地编写conf.js文件,因为它是从配置文件本身开始的。

Protractor - Protractor和 Selenium 服务器

如前所述,Protractor 是一个用于 Angular 和 AngularJS 应用程序的开源端到端测试框架。它是 Node.js 程序。另一方面,Selenium 是一个浏览器自动化框架,包括 Selenium Server、WebDriver API 和 WebDriver 浏览器驱动程序。

SeleniumProtractor

如果我们谈论 Protractor 和 Selenium 的结合,Protractor 可以与 Selenium 服务器一起提供自动化测试基础设施。该基础设施可以模拟用户与在浏览器或移动设备上运行的角度应用程序的交互。Protractor 和 Selenium 的结合可以分为三个部分,即测试、服务器和浏览器,如下图所示 -

SeleniumProtractor

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 进程及其通信的概念可以借助下图来理解 -

Web 驱动程序进程

使用 Protractor 时,第一个过程(即测试脚本)是使用 Node.js 运行的,但在浏览器上执行任何操作之前,它将发送一个额外的命令以确保正在测试的应用程序稳定。

设置 Selenium 服务器

Selenium Server 充当我们的测试脚本和浏览器驱动程序之间的代理服务器。它基本上将命令从我们的测试脚本转发到 WebDriver,并将响应从 WebDriver 返回到我们的测试脚本。有以下用于设置 Selenium 服务器的选项,这些选项包含在测试脚本的conf.js文件中 -

独立的 Selenium 服务器

如果我们想在本地计算机上运行服务器,我们需要安装独立的 selenium 服务器。安装独立selenium服务器的先决条件是JDK(Java Development Kit)。我们必须在本地计算机上安装 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 服务器,我们需要设置 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 Server。

设置浏览器

在配置和设置浏览器之前,我们需要知道Protractor支持哪些浏览器。以下是 Protractor 支持的浏览器列表 -

  • Chrome驱动程序
  • Firefox驱动程序
  • Safari驱动程序
  • IE驱动程序
  • Appium-iOS/Safari
  • Appium-Android/Chrome
  • 塞兰德机器人
  • PhantomJS

为了设置和配置浏览器,我们需要移动到 Protractor 的配置文件,因为浏览器设置是在配置文件的功能对象内完成的。

设置 Chrome

为了设置 Chrome 浏览器,我们需要设置功能对象,如下所示

capabilities: {
   'browserName': 'chrome'
}

我们还可以添加嵌套在 chromeOptions 中的特定于 Chrome 的选项,其完整列表可以在https://sites.google.com/a/chromium.org/chromedriver/capability中查看。

例如,如果您想在右上角添加 FPS-counter,则可以在配置文件中按如下方式完成 -

capabilities: {
   'browserName': 'chrome',
   'chromeOptions': {
      'args': ['show-fps-counter=true']
   }
},

设置火狐浏览器

为了设置 Firefox 浏览器,我们需要设置功能对象,如下所示 -

capabilities: {
   'browserName': 'firefox'
}

我们还可以添加嵌套在 moz:firefoxOptions 对象中的 Firefox 特定选项,其完整列表可以在https://github.com/mozilla/geckodriver#firefox-capability中查看。

例如,如果您想在 Firefox 上以安全模式运行测试,则可以在配置文件中按如下方式完成 -

capabilities: {
   'browserName': 'firefox',
   'moz:firefoxOptions': {
     'args': ['—safe-mode']
   }
},

设置其他浏览器

要设置除 Chrome 或 Firefox 之外的任何其他浏览器,我们需要从https://docs.seleniumhq.org/download/安装单独的二进制文件。

设置 PhantonJS

实际上,PhantomJS 由于崩溃问题而不再受支持。相反,建议使用无头 Chrome 或无头 Firefox。它们可以设置如下 -

为了设置 headless Chrome,我们需要使用 –headless 标志启动 Chrome,如下所示 -

capabilities: {
   'browserName': 'chrome',
   'chromeOptions': {
      'args': [“--headless”, “--disable-gpu”, “--window-size=800,600”]
   }
},

为了设置 headless Firefox,我们需要使用–headless标志启动 Firefox,如下所示 -

capabilities: {
   'browserName': 'firefox',
   'moz:firefoxOptions': {
      'args': [“--headless”]
   }
},

设置多个浏览器进行测试

我们还可以针对多个浏览器进行测试。为此,我们需要使用 multiCapability 配置选项,如下所示 -

multiCapabilities: [{
   'browserName': 'chrome'
},{
   'browserName': 'firefox'
}]

哪个框架?

Protractor 支持两个 BDD(Behave驱动开发)测试框架 Jasmine 和 Mocha。这两个框架都基于 JavaScript 和 Node.js。这些框架提供了编写和管理测试所需的语法、报告和支架。

接下来,我们看看如何安装各种框架 -

茉莉花框架

它是 Protractor 的默认测试框架。当您安装 Protractor 时,您将获得 Jasmine 2.x 版本。我们不需要单独安装它。

摩卡框架

Mocha 是另一个基本上运行在 Node.js 上的 JavaScript 测试框架。为了使用 Mocha 作为我们的测试框架,我们需要使用 BDD(Behave驱动开发)接口和 Chai 断言以及 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'来将配置文件的framework属性设置为mocha。摩卡的“reporter”和“slow”等选项可以添加到配置文件中,如下所示 -

mochaOpts: {
   reporter: "spec", slow: 3000
}

Cucumber框架

为了使用 Cucumber 作为我们的测试框架,我们需要将其与 Protractor 与框架选项custom集成。可以借助以下命令完成安装

npm install -g cucumber
npm install --save-dev protractor-cucumber-framework

正如你所看到的,在安装 Cucumber 时使用了 -g 选项,这是因为我们已经全局安装了 Protractor,即使用 -g 选项。接下来,我们需要将框架属性设置为配置文件的自定义,方法是将框架:'custom'和frameworkPath:'Protractor-cucumber-framework'添加到名为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 中编写第一个测试。

Protractor所需的文件

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 加载特定页面。

  • describeit - 两者都是 Jasmine 测试框架的语法。“描述用于包含测试用例的端到端流程,而“它”包含一些测试场景。我们的测试用例程序中可以有多个“it”块。

  • Expect - 这是一个断言,我们将网页标题与一些预定义的数据进行比较。

  • ignoreSynchronization - 这是我们尝试测试非角度网站时使用的浏览器标签。Protractor 期望仅适用于角度网站,但如果我们想使用非角度网站,则必须将此标签设置为“true”

配置文件

顾名思义,该文件提供了所有 Protractor 配置选项的说明。它基本上告诉Protractor以下内容 -

  • 在哪里可以找到测试或规格文件
  • 选择哪个浏览器
  • 使用哪个测试框架
  • 在哪里与 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 文件。

下面显示的屏幕截图将解释执行示例的上述步骤 -

执行代码

从屏幕截图中可以看出,测试已经通过。

现在,假设如果我们正在测试非角度网站并且没有将ignoreSynchronization标记设置为true,那么在执行代码后我们将收到错误“在页面上找不到Angular”。

在下面的屏幕截图中可以看到 -

忽略同步

报告生成

到目前为止,我们已经讨论了运行测试用例所需的文件及其编码。Protractor 还能够生成测试用例的报告。为此,它支持 Jasmine。JunitXMLReporter可用于自动生成测试执行报告。

但在此之前,我们需要借助以下命令安装 Jasmine 报告器 -

npm install -g jasmine-reporters

正如你所看到的,在安装Jasmine Reporters时使用了-g选项,这是因为我们已经使用-g选项全局安装了Protractor。

成功安装 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'
      }));
   },
};

以同样的方式运行上面的配置文件后,我们之前已经运行过,它将在reports文件夹的根目录下生成一个包含报告的XML文件。如果测试成功,报告将如下所示 -

报告生成

但是,如果测试失败,报告将如下所示 -

报告生成失败

Protractor - 核心 APIS

本章让您了解对于 Protractor 功能至关重要的各种核心 API。

Protractor API 的重要性

Protractor 为我们提供了广泛的 API,这些 API 对于执行以下操作以获取网站的当前状态非常重要 -

  • 获取我们要测试的网页的 DOM 元素。
  • 与 DOM 元素交互。
  • 为他们分配操作。
  • 向他们分享信息。

要执行上述任务,了解 Protractor API 非常重要。

各种Protractor API

我们知道 Protractor 是 Selenium-WebDriver 的包装器,Selenium-WebDriver 是 Node.js 的 WebDriver 绑定。Protractor有以下 API -

浏览器

它是 WebDriver 实例的包装器,用于处理浏览器级命令,例如导航、页面范围信息等。例如, browser.get 方法加载页面。

元素

它用于搜索我们正在测试的页面上的 DOM 元素并与之交互。为此,需要一个参数来定位元素。

定位器(作者)

它是元素定位器策略的集合。例如,可以通过 CSS 选择器、ID 或通过 ng-model 绑定到它们的任何其他属性来找到元素。

接下来,我们将详细讨论这些 API 及其功能。

浏览器API

如上所述,它是 WebDriver 实例的包装器,用于处理浏览器级命令。它执行以下各种功能 -

功能及其描述

ProtractorBrowser API 的功能如下:

browser.angularAppRoot

浏览器 API 的这个函数为我们要在其上查找 Angular 的元素设置 CSS 选择器。通常,这个函数位于“body”中,但如果是我们的 ng-app,它位于页面的一个子部分;它也可能是一个子元素。

browser.waitForAngularEnabled

浏览器 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 兼容。它将搜索页面上所有可用的角度应用程序,同时查找元素或等待稳定性。

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 相反,它将获取已注册的模拟模块的列表。

浏览器获取

我们可以使用 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 页面。

浏览器调试器

顾名思义,它必须与Protractor调试一起使用。该函数基本上向控制流添加了一个任务来暂停测试并将辅助函数注入到浏览器中,以便可以在浏览器控制台中进行调试。

浏览器.暂停

它用于调试 WebDriver 测试。我们可以在测试中使用browser.pause()从控制流中的该点进入Protractor调试器。

例子

element(by.id('foo')).click();
browser.pause();
// Execution will stop before the next click action.
element(by.id('bar')).click();

browser.controlFlowEnabled

用于判断控制流是否启用。

Protractor - 核心 APIS(续...)

在本章中,让我们学习Protractor的一些更核心的API。

元素API

Element 是 Protractor 公开的全局函数之一。该函数采用定位器并返回以下内容 -

  • ElementFinder,根据定位器查找单个元素。
  • ElementArrayFinder,根据定位器查找元素数组。

上述两种方法都支持链接方法,如下所述。

ElementArrayFinder 的链接函数及其描述

以下是 ElementArrayFinder 的功能 -

element.all(定位器).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(定位器).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(定位器).get(索引)

借助它,我们可以通过索引获取 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(定位器).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(定位器).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(定位器).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(定位器).isPresent()

它将与查找器匹配元素。它可以返回 true 或 false。如果存在任何与查找器匹配的元素,则为 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(定位器).then(then函数)

它将检索 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(定位器).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(定位器).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(定位器).reduce(reduceFn)

顾名思义,它将对累加器和使用定位器找到的每个元素应用归约函数。该函数会将每个元素减少为单个值。

例子

看法

<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(定位器).evaluate

顾名思义,它将评估输入是否在当前底层元素的范围内。

例子

看法

<span class = "foo">{{letiableInScope}}</span>

代码

let value = 
element.all(by.css('.foo')).evaluate('letiableInScope');

element.all(定位器).allowAnimations

顾名思义,它将确定当前底层元素是否允许动画。

例子

element(by.css('body')).allowAnimations(false);

ElementFinder 的链接函数及其描述

ElementFinder 的链接函数及其描述 -

元素(定位器).clone

顾名思义,此函数将创建 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'));

元素(定位器).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')).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');

元素(定位器).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'));

元素(定位器).$(定位器)

当对 $ 的调用可能被链接时,它将在父级中查找元素。

例子

看法

<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() 相同。唯一的区别是它会检查由子定位器标识的元素是否存在而不是当前元素查找器。

元素.all