- 测试
【免费下载链接】protractor
E2E test framework for Angular apps
Protractor 是面向 Angular(含 AngularJS)应用的端到端测试框架,基于 Node.js 运行,支持 Jasmine 与 Mocha。本篇快速入门指南围绕 Protractor 的"两个必需文件"模型展开——spec 测试文件与配置文件(config),从安装环境、编写第一条断言、操作页面元素、配置多浏览器,到理解控制流(Control Flow)与 Selenium 通信的底层机制,带领你完成从零到可运行的首个 E2E 测试。读完本文,你将掌握protractor与webdriver-manager两个命令行工具的使用、browser/element/by三大全局对象,以及如何用 Page Objects 组织可维护的测试代码。
本文内容以 docs/getting-started.md 为骨架,完整展开其指向的官方教程(docs/tutorial.md)、Spec 与 Config 文件详解(docs/api-overview.md)、WebDriver 控制流(docs/control-flow.md)、架构原理(docs/infrastructure.md)与 Page Objects(docs/page-objects.md),并结合作品仓库中的源码与真实示例进行佐证。
一、快速开始:先读教程再上手
Protractor 官方给出的最快上手路径是跟着教程(Tutorial)走一遍。docs/tutorial.md 提供了一步一步的指南:如何安装 Protractor、创建测试文件、配置配置文件并运行测试。教程中使用了一个公开的 AngularJS 示例应用 Super Calculator(一个计算器页面),非常适合完整演示"定位元素 → 输入 → 点击 → 断言结果"的典型 E2E 流程。
在动手写测试之前,有两点需要提前建立认知:
- Protractor 是 WebDriverJS 的封装。WebDriverJS 是 Selenium WebDriver API 的 JavaScript 绑定,因此 Protractor 的绝大多数能力都来自 WebDriverJS。
- WebDriverJS API 基于 Promise。所有命令都是异步的,它们被调度进一个"控制流"(Control Flow)队列,并返回 Promise 而非原始值。这一点直接影响你编写断言的方式,详见后文控制流与 Promise一节。
此外,官方推荐在熟悉 Protractor 之后,立刻开始使用Page Objects模式组织测试(见 docs/page-objects.md),这能显著提升测试的可维护性。
二、前置条件:Node.js、npm 与 JDK
Protractor 是一个 Node.js 程序,因此运行它需要:
- Node.js:从 npm 安装 Protractor 包,而 npm 随 Node.js 一同分发。先用
node --version检查你本机的 Node.js 版本,再对照 Protractor README 中的兼容性说明确认版本匹配。 - Jasmine:默认情况下,Protractor 使用 Jasmine 作为测试框架的接口。下文教程默认你已具备基本的 Jasmine 使用经验,并使用 Jasmine 2.x 语法(
describe/it/expect)。 - Java Development Kit (JDK):如果采用"本地独立 Selenium Server"方案(本教程的方案),需要用 JDK 运行 Selenium Server。用
java -version在命令行验证 JDK 是否已安装。
除了本地 Selenium Server,Protractor 还支持直连浏览器驱动的
directConnect、Sauce Labs、BrowserStack 等远程服务连接方式。本教程聚焦最通用的本地 Server 方案,完整的连接选项见下文配置文件详解。
三、环境搭建:npm 全局安装与 webdriver-manager
使用 npm 全局安装 Protractor:
npm install -g protractor安装完成后,你会获得两个命令行工具:
protractor:测试启动器。运行protractor --version确认它工作正常。webdriver-manager:Selenium Server 的辅助管理工具,用于下载并启动一个可用的 Selenium Server 实例。
用webdriver-manager下载必要的二进制文件:
webdriver-manager update然后启动服务器:
webdriver-manager start这会启动一个 Selenium Server,并输出大量信息日志。你的 Protractor 测试会向这个服务器发送请求,由它来控制本地的浏览器。整个教程期间请保持该服务器运行。服务器状态可在http://localhost:4444/wd/hub查看。
这一"测试脚本 → Selenium Server → 浏览器"的三进程协作模型是 Protractor 工作的基础,其通信细节参见架构原理一节。
四、Step 0:编写你的第一个 Protractor 测试
打开一个新的命令行终端,创建一个干净的测试文件夹。Protractor 运行需要两个文件:一个spec 文件(测试用例)和一个配置文件(运行配置)。
4.1 编写 spec 文件
先写一个最简单的测试:导航到一个示例 AngularJS 应用并检查其标题。把以下内容复制为spec.js:
// spec.js describe('Protractor Demo App', function() { it('should have a title', function() { browser.get('http://juliemr.github.io/protractor-demo/'); expect(browser.getTitle()).toEqual('Super Calculator'); }); });这里出现了三个关键元素:
describe与it:来自 Jasmine 框架的测试组织语法,Protractor 原生支持。browser:Protractor 创建的全局对象,用于浏览器级命令,如用browser.get进行导航。仓库中该对象的实现位于 lib/browser.ts,它是 WebDriver 实例的封装。browser.getTitle():读取页面标题,返回一个 Promise,Protractor 对expect做了适配使其能理解 Promise(详见控制流与 Promise)。
4.2 编写配置文件
把以下内容复制为conf.js:
// conf.js exports.config = { framework: 'jasmine', seleniumAddress: 'http://localhost:4444/wd/hub', specs: ['spec.js'] }这个配置告诉 Protractor 三件事:
specs:测试文件的位置。seleniumAddress:要连接的 Selenium Server 地址。framework:使用的测试框架为 Jasmine。
其余全部使用默认值——Chrome 是默认浏览器。这份极简配置的完整形态可参考仓库中的真实示例 example/conf.js,后者还演示了directConnect、capabilities与jasmineNodeOpts的用法。
4.3 运行测试
protractor conf.js你会看到一个 Chrome 浏览器窗口打开并导航到 Calculator 页面,然后自动关闭(整个过程非常快)。测试输出应为1 tests, 1 assertion, 0 failures。恭喜,你已成功运行了第一个 Protractor 测试!
五、配置文件详解:Spec & Config 两个文件模型
Protractor 运行时依赖且仅依赖两个文件——测试文件(spec)与配置文件,这一点在 docs/api-overview.md 中有系统说明。
5.1 Spec 文件与三大全局变量
Protractor 的测试使用你所选测试框架的语法(如 Jasmine)编写,并结合 Protractor 自身的 API(完整列表见 docs/api.md)。一个完整的示例 spec(仓库中的 example/example_spec.js)如下:
describe('angularjs homepage', () => { it('should greet the named user', async () => { await browser.get('http://www.angularjs.org'); await element(by.model('yourName')).sendKeys('Julie'); const greeting = element(by.binding('yourName')); expect(await greeting.getText()).toEqual('Hello Julie!'); }); });Protractor 会向你的 spec 文件导出以下全局变量:
| 全局变量 | 作用 | 关键方法 |
|---|---|---|
browser | WebDriver 实例的封装,用于导航和页面级信息 | browser.get(url)加载页面;browser.getTitle()获取标题 |
element | 查找并交互页面 DOM 元素的辅助函数,接收一个 Locator 作为唯一参数 | element(locator)返回 ElementFinder |
by | 元素定位策略(Locator)工厂的集合 | by.css()、by.id()、by.model()、by.binding() |
protractor | 包裹 WebDriver 命名空间的 Protractor 命名空间,包含静态变量与类 | protractor.Key枚举特殊键盘按键码 |
需要注意:browser.get会期待页面中存在 Angular 库。如果它尝试加载的页面不包含 Angular,会抛出错误;如需与非 Angular 页面交互,可通过browser.driver直接访问被包裹的 WebDriver 实例。
关于element的更多示例,可查看 Protractor 的 ElementFinder 测试套件 spec/basic/elements_spec.js。
5.2 Config 文件:核心配置选项
配置文件负责告诉 Protractor 如何设置 Selenium Server、运行哪些测试、如何配置浏览器以及使用哪个测试框架。仓库中的 example/conf.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_spec.js', 'angular_material/input_spec.js', 'angular_material/mat_paginator_spec.js' ], // Options to be passed to Jasmine. jasmineNodeOpts: { defaultTimeoutInterval: 30000 } };默认设置为:独立 Selenium Server、Chrome 浏览器、Jasmine 测试框架。完整配置项的解释见 lib/config.ts,各类配置选项的进一步信息对应以下文档:
- 设置 Selenium Server:docs/server-setup.md
- 设置浏览器:docs/browser-setup.md
- 选择测试框架:docs/frameworks.md
- 使用 Page Objects 组织测试:docs/page-objects.md
5.3 浏览器连接方式与 capabilities
从源码 lib/config.ts 可以看到,Protractor 提供了五种连接浏览器驱动的方案,配置时只需指定其中之一:
seleniumServerJar:在本地启动一个独立 Selenium Server(jar 文件相对 webdriver-manager 位置的路径)。seleniumAddress:连接一个已经运行的 Selenium Server(如'http://localhost:4444/wd/hub')。sauceUser/sauceKey:通过 Sauce Labs 使用远程 Selenium Server。browserstackUser/browserstackKey:通过 BrowserStack 使用远程 Selenium Server。directConnect:直接连接浏览器驱动,仅对 Firefox 和 Chrome 可用(无需单独启动 Selenium Server)。
教程采用的正是方案 2(seleniumAddress),而 example/conf.js 演示了方案 5(directConnect: true)。
此外,若通过本地独立 Server 运行,还可配置:
seleniumServerStartTimeout:等待本地独立 Selenium Server 启动的超时时间,默认 30000ms(见 lib/config.ts)。chromeDriver/geckoDriver:指定 chromedriver / geckodriver 二进制位置,未设置时使用 webdriver-manager 下载的默认值(见 lib/config.ts)。
六、从入门到实战:四个渐进步骤
教程(docs/tutorial.md)把完整的上手过程拆成四个渐进步骤,从"只读断言"逐步推进到"多场景组织与元素列表操作"。
6.1 Step 1:与页面元素交互
把spec.js修改为以下内容,测试输入两个数字并点击按钮:
// spec.js describe('Protractor Demo App', function() { it('should add one and two', function() { browser.get('http://juliemr.github.io/protractor-demo/'); element(by.model('first')).sendKeys(1); element(by.model('second')).sendKeys(2); element(by.id('gobutton')).click(); expect(element(by.binding('latest')).getText()). toEqual('5'); // This is wrong! }); });这里使用了另外两个全局对象:
element:用于在网页中查找 HTML 元素,返回ElementFinder对象。可调用sendKeys向<input>输入文本、click点击按钮、getText获取元素内容。by:element接收一个参数——描述如何查找元素的Locator,而by对象负责创建 Locator。本测试用了三种 Locator:by.model('first'):查找带ng-model="first"的元素(即页面源码中的<input type="text" ng-model="first">);by.id('gobutton'):按 id 查找元素(<button id="gobutton">);by.binding('latest'):查找绑定到变量latest的元素(即包含{{latest}}的 span)。
运行protractor conf.js,你会看到页面输入两个数字并等待结果显示。因为结果是 3 而不是 5,测试会失败——这正是 E2E 测试的价值:修改断言并重新运行,直到通过。Locator 与 ElementFinder 的完整讲解见 docs/locators.md。
6.2 Step 2:组织多个测试场景
把两个测试合并整理,并抽出公共逻辑:
// spec.js describe('Protractor Demo App', function() { var firstNumber = element(by.model('first')); var secondNumber = element(by.model('second')); var goButton = element(by.id('gobutton')); var latestResult = element(by.binding('latest')); beforeEach(function() { browser.get('http://juliemr.github.io/protractor-demo/'); }); it('should have a title', function() { expect(browser.getTitle()).toEqual('Super Calculator'); }); it('should add one and two', function() { firstNumber.sendKeys(1); secondNumber.sendKeys(2); goButton.click(); expect(latestResult.getText()).toEqual('3'); }); it('should add four and six', function() { // Fill this in. expect(latestResult.getText()).toEqual('10'); }); it('should read the value from an input', function() { firstNumber.sendKeys(1); expect(firstNumber.getAttribute('value')).toEqual('1'); }); });本步的关键重构动作:
- 把导航抽入
beforeEach函数,它在每个it块之前运行; - 把两个输入框的 ElementFinder 存入可复用的变量;
- 最后一个断言用
firstNumber.getAttribute('value')读取输入框当前值并与刚才设置的值比较。
你可以补全第三个测试(用上面两个变量做4 + 6的输入)再运行,确保全部通过。
6.3 Step 3:修改配置——切换与并行运行浏览器
配置文件可以改变使用的浏览器以及连接 Selenium Server 的方式。把浏览器从默认的 Chrome 切换为 Firefox:
// conf.js exports.config = { framework: 'jasmine', seleniumAddress: 'http://localhost:4444/wd/hub', specs: ['spec.js'], capabilities: { browserName: 'firefox' } }capabilities对象描述要测试的浏览器;全部选项见 lib/config.ts。
Protractor 还支持同时在多个浏览器上运行测试:
// conf.js exports.config = { framework: 'jasmine', seleniumAddress: 'http://localhost:4444/wd/hub', specs: ['spec.js'], multiCapabilities: [{ browserName: 'firefox' }, { browserName: 'chrome' }] }再次运行,你会看到测试同时在 Chrome 和 Firefox 上执行,命令行会分别报告两者的结果。
6.4 Step 4:处理元素列表(ElementArrayFinder)
有时你需要处理多个元素。element.all返回ElementArrayFinder。在 Calculator 应用中,每次运算都会记录到历史中(页面用带ng-repeat的表格实现),可以这样验证:
// spec.js describe('Protractor Demo App', function() { var firstNumber = element(by.model('first')); var secondNumber = element(by.model('second')); var goButton = element(by.id('gobutton')); var latestResult = element(by.binding('latest')); var history = element.all(by.repeater('result in memory')); function add(a, b) { firstNumber.sendKeys(a); secondNumber.sendKeys(b); goButton.click(); } beforeEach(function() { browser.get('http://juliemr.github.io/protractor-demo/'); }); it('should have a history', function() { add(1, 2); add(3, 4); expect(history.count()).toEqual(2); add(5, 6); expect(history.count()).toEqual(0); // This is wrong! }); });这里完成了两件事:一是抽出add辅助函数;二是用element.all+by.repeater得到 ElementArrayFinder,用count方法断言历史长度。修正第二个断言(应为 3),让测试通过。
ElementArrayFinder除count外还有很多方法。例如用last获取最后一个匹配元素:
it('should have a history', function() { add(1, 2); add(3, 4); expect(history.last().getText()).toContain('1 + 2'); expect(history.first().getText()).toContain('foo'); // This is wrong! });由于 Calculator 把最旧的结果放在底部,最旧的加法(1 + 2)正是历史的最后一项。这里用 Jasmine 的toContain匹配器断言元素文本包含 "1 + 2"(完整文本还含时间戳和结果)。请修正测试,使其正确期望第一条历史记录包含 "3 + 4"。
ElementArrayFinder还提供each、map、filter、reduce等方法,与 JavaScript 数组方法类似。完整的 API 见官方 API 参考。
七、控制流与 Promise:WebDriverJS 的异步模型
WebDriverJS(因而 Protractor)的 API完全异步,所有函数都返回 Promise。这一点在 docs/control-flow.md 中有详细说明。
7.1 Promise 队列:Control Flow
WebDriverJS 维护一个待处理 Promise 的队列,称为控制流(Control Flow),用来保持执行有序。考虑如下测试:
it('should find an element by text input model', function() { browser.get('app/index.html#/form'); var username = element(by.model('username')); username.clear(); username.sendKeys('Jane Doe'); var name = element(by.binding('username')); expect(name.getText()).toEqual('Jane Doe'); // Point A });在// Point A这一时刻,没有任何任务实际执行过:browser.get位于控制流队列最前,name.getText()位于队列最末,而name.getText()此刻的值是一个未解析的 Promise 对象。所有命令按书写顺序入队,随后依次执行。
7.2 Protractor 的适配
- Jasmine 适配:Protractor 让每个 spec自动等待控制流清空后才退出,因此你不必手动等待异步命令完成。
- expect 适配:Jasmine 的
expect被改造为能理解 Promise。下面这行代码能正常工作,是因为它实际往控制流里添加了一个"期望任务",该任务会在其他任务之后运行:
expect(name.getText()).toEqual('Jane Doe');7.3 禁用控制流:SELENIUM_PROMISE_MANAGER
控制流正在被逐步移除。要禁用控制流,可使用配置选项:
SELENIUM_PROMISE_MANAGER: false对应源码定义见 lib/config.ts。从源码注释可以看到:在未来的 webdriver 版本中,环境变量SELENIUM_PROMISE_MANAGER=1才会恢复启用控制流,之后控制流将被彻底移除;且配置文件中的该选项优先级高于同名环境变量。
禁用控制流后,你需要用 Promise 链或 ES7 的async/await来同步命令。仓库的 spec/ts/ 目录提供了禁用控制流下的 TypeScript 测试示例(如 spec/ts/basic/element_spec.ts、spec/ts/noCFBasicConf.ts)。因为async/await使用原生 Promise,会使控制流变得不可靠,所以如果你在编写需要兼容"控制流开/关"两种场景的库或插件,应使用 Promise 链来做同步。
7.4 Mocha 用户须知
如果使用 Mocha 作为测试框架,控制流同样会在每个测试完成前自动清空。但Mocha 的expect并没有被适配成能理解 Promise——因此你需要使用如 Chai as Promised 之类的断言框架。参见 docs/frameworks.md。
八、架构原理:Protractor / Selenium / 浏览器如何协作
深入理解协作模型(docs/infrastructure.md)有助于你排查测试失败与性能问题。
8.1 三进程模型
一个使用 Selenium WebDriver 的测试涉及三个进程:测试脚本、服务器、浏览器,其通信关系如下:
- Selenium Server负责解释来自测试的命令,并将其转发给一个或多个浏览器;
- 服务器与浏览器之间使用WebDriver Wire Protocol(基于 JSON 的协议)通信,命令由Browser Driver解释执行;
- 在 Protractor 中,测试脚本运行在 Node.js 环境下。
8.2 组件关系
Protractor 与 Selenium 的组合关系需要记住三点:
- Protractor 是 WebDriverJS 的封装;
- WebDriver 命令是异步的,它们被调度到控制流上并返回 Promise 而非原始值;
- 测试脚本发送命令给 Selenium Server,服务器再与浏览器驱动通信。
8.3 自动等待:额外的稳定性命令
Protractor 在对浏览器执行任何动作之前,都会先运行一条额外命令,以确保被测应用已经稳定。例如:
element(by.css('button.myclass')).click();这条代码实际会向 Browser Driver 发送三条命令:
/session/:sessionId/execute_async:Protractor 让浏览器先执行一段自定义 JavaScript,请求 Angular 在完成所有 timeout 与异步请求、应用就绪时做出响应,然后测试才继续;/session/:sessionId/element:发送查找元素的命令;/session/:sessionId/element/:id/click:最后发送点击动作的命令。
这正是 Protractor 相对于裸 WebDriver 的核心价值:为 Angular 应用自动同步等待。对相关超时与同步策略的深入讨论见 docs/timeouts.md。
九、用 Page Objects 组织测试
当你熟悉 Protractor 后,官方强烈建议开始使用Page Objects模式(docs/page-objects.md)。它通过把页面元素信息封装起来,帮助写出更整洁的测试;Page Object 可在多个测试间复用,当应用模板变化时只需更新 Page Object 一处。
9.1 不使用 Page Objects 时
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!'); }); });9.2 使用 Page Objects
第一步:创建 Page Object。针对 angularjs.org 主页"Basics"示例,Page Object 可以这样写:
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();如果使用async/await,则需注意:不使用await的函数不应加async前缀:
var AngularHomepage = function() { var nameInput = element(by.model('yourName')); var greeting = element(by.binding('yourName')); this.get = async function() { await browser.get('http://www.angularjs.org'); }; this.setName = async function(name) { await nameInput.sendKeys(name); }; this.getGreetingText = function() { return greeting.getText(); }; // Not async, returns the element this.getGreeting = function() { return greeting; }; }; module.exports = new AngularHomepage();第二步:修改测试脚本使用 Page Object。注意测试脚本的功能本身不发生变化(不增不减);require的路径相对于你的 spec 文件位置,请相应调整。
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!'); }); });使用async/await的版本:
var angularHomepage = require('./AngularHomepage'); describe('angularjs homepage', function() { it('should greet the named user', async function() { await angularHomepage.get(); await angularHomepage.setName('Julie'); expect(await angularHomepage.getGreetingText()).toEqual('Hello Julie!'); }); });仓库 exampleTypescript/ 目录中提供了 TypeScript 版 Page Object 的完整示例(exampleTypescript/angularPage.ts、exampleTypescript/specPageObjects.ts)。
9.3 配置测试套件(Suites)
Page Objects 文档还介绍了按套件分离测试的能力。在配置文件中设置suites选项:
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. } };从命令行即可轻松切换运行某个套件。只运行 homepage 部分:
protractor protractor.conf.js --suite homepage也可以同时运行多个套件:
protractor protractor.conf.js --suite homepage,search相关源码实现位于 lib/config.ts:不传命令行参数时运行全部套件;--suite=smoke或--suite=smoke,full时仅运行匹配指定套件的模式;也可以在配置文件中通过suite字段设置默认套件。
十、下一步:完整文档导航
本指南已带你完成从安装到运行、从单条断言到多浏览器并行的完整入门闭环。想继续深入学习,官方文档的完整目录见 docs/toc.md,推荐按以下顺序进阶:
- 环境与安装:docs/protractor-setup.md(Protractor 安装)、docs/server-setup.md(Selenium Server 配置)、docs/browser-setup.md(浏览器配置)、docs/frameworks.md(选择测试框架);
- 测试编写:docs/locators.md(Locator 详解)、docs/system-setup.md(被测系统搭建)、docs/debugging.md(调试);
- 进阶参考:lib/config.ts(全部配置项)、docs/api.md(Protractor API)、docs/async-await.md(async/await 用法)、docs/typescript.md(TypeScript 支持)、docs/timeouts.md(超时与同步)。
- 测试
【免费下载链接】protractor
E2E test framework for Angular apps
相关推荐
3大核心优势打造DayZ单机生存终极解决方案
3大核心优势打造DayZ单机生存终极解决方案 想象一下,在《DayZ》的末日世界中,你可以不受网络限制、自由探索生存的每一个细节——这就是DayZCommuni
测试Angular Material 快速入门:从 `ng add` 安装到首个组件实战
Angular Material 快速入门:从 ng add 安装到首个组件实战 本篇技术指南以官方文档 guides/getting started.md h
前端UI组件设计系统Protractor 入门教程:从零搭建 Angular E2E 测试环境
Protractor 入门教程:从零搭建 Angular E2E 测试环境 本教程面向首次接触 Protractor 的开发者,以官方入门文档 docs/tut
测试
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考