☰
Protractor 被测系统(System Under Test)搭建指南:URL 驱动的端到端测试与 onPrepare 全局准备实战
2026/9/25 13:04:48 网站建设 项目流程
  • 测试

【免费下载链接】protractor

E2E test framework for Angular apps

项目地址:https://gitcode.com/gh_mirrors/pr/protractor
点击查看免费下载

本文基于本仓库 docs/system-setup.md 展开,系统讲解 Protractor 端到端测试中被测系统的选择与准备方式:为什么只需一个 URL 即可测试任意环境(本地开发、内网预发布乃至公网生产)、手动 bootstrap 与轮询型页面两类特殊场景的规避方案,以及通过onPrepare完成登录等全局预置的正确姿势。读完本文,你将能够为任意 Protractor 项目正确配置被测系统,并写出同步可靠、无竞态条件的测试启动流程。

一、被测系统的本质:真实浏览器 + 一个 URL

Protractor 与许多基于 mock 的单元测试框架最大的不同在于:它使用真实浏览器执行测试。浏览器能访问什么,Protractor 就能测试什么,因此你在决定"测什么"这件事上拥有极大的自由度:

  • 一个跑在localhost上的本地开发服务器;
  • 内网中的预发布(staging)服务器;
  • 甚至公网上直接面向用户的正式(production)服务器。

对 Protractor 而言,它唯一真正需要的东西就是URL。测试启动时通过browser.get(url)加载被测页面,后续所有查找元素、断言、点击等操作都发生在真实浏览器渲染出的页面中。这一点也让 Protractor 与 WebDriver 生态天然对齐:底层连接的可以是本地 Selenium Server、远程 Selenium 服务或直连浏览器驱动,具体连接方式由配置文件中的seleniumAddress、seleniumServerJar、directConnect等选项决定(详见 lib/config.ts)。

不过在"只要给 URL 就行"的宽松背后,文档明确警告了三类必须小心的情况,下面逐一展开。

二、注意事项一:手动 bootstrap 的页面 —— 请使用browser.driver.get

正常情况下,browser.get会在页面加载后主动等待 Angular 完成引导(bootstrap)并进入稳定状态。但如果你的页面不是由 Angular 自动引导,而是采用手动 bootstrap(例如在代码中显式调用angular.bootstrap()或使用ngUpgrade之外的定制引导流程),Protractor 将无法识别并加载该页面。

文档给出的解决方案是:放弃browser.get,改用基础 WebDriver 实例browser.driver.get:

// 手动 bootstrap 页面无法用 browser.get 加载 // browser.get('/ng1/login.html'); // ❌ 不可靠 // 改用基础 webdriver 实例 await browser.driver.get('http://localhost:8080/login.html');

browser.driver是 Protractor 对外暴露的原生WebDriver实例(在 lib/browser.ts 中构造),它完全绕过 Angular 同步机制,直接把命令交给浏览器执行。这带来一个连锁后果:

Protractor 不再知道页面何时加载完成,你需要自行添加wait语句,否则测试容易产生竞态条件(race condition)——元素尚未渲染就开始点击/断言,导致偶发失败。

这正是仓库登录测试配置 spec/withLoginConf.js 中的做法:登录页是非 Angular 手动引导页面,因此先用browser.driver.get打开,再配合browser.driver.wait显式等待登录跳转完成(详见下文第四节)。

何时用browser.driver.get的经验法则:

  • 页面完全不依赖 Angular,或 Angular 引导由自定义逻辑触发;
  • 页面是纯 WebDriver 流程的一部分(如测试前的登录页);
  • 只要使用了browser.driver.get,就应当配套显式等待逻辑,不能依赖 Protractor 的自动同步。

三、注意事项二:用$timeout轮询的页面 —— 改用$interval

Protractor 的核心同步机制是:执行任何操作前,它会等待 Angular 应用没有待处理(pending)的异步任务。如果你在 AngularJS 应用中使用$timeout做持续轮询,那么同一时刻永远有未完成的异步任务,Protractor 会一直等待下去,直到触发超时。

文档明确给出建议:把轮询用的$timeout换成$interval。

// ❌ 持续轮询 + $timeout:Protractor 永远等不到稳定 // $timeout(poll, 1000); // ✅ 持续轮询请使用 $interval(AngularJS 1.2rc3 起提供) $interval(poll, 1000);

这一论断在 docs/timeouts.md 中有更详细的佐证:如果 AngularJS 应用持续轮询$timeout或$http,Protractor 会无限等待并超时;轮询场景应使用$interval。对于使用 Zone.js 的 Angular(2+)应用,类似的思路是:把长期运行的异步任务放到 Angular Zone 之外执行,避免阻塞测试继续。

从底层看,Protractor 通过注入脚本装饰$timeout/$interval来追踪待处理任务(配置项untrackOutstandingTimeouts与此相关,见 lib/config.ts)。因此,被测应用能否被正确同步,本质上取决于应用侧异步 API 的选择——这也是"设置被测系统"环节需要重点审查的代码层面因素。

四、注意事项三:全局测试准备 ——onPrepare的三种形态

如果测试前需要做全局准备(最典型的场景是登录),文档要求你把它放进配置文件的onPrepare属性中。onPrepare是一个多态(polymorphic)属性,既可以是函数,也可以是文件名:

  • 函数形态:直接内联在配置文件里,例如 spec/onPrepareConf.js:
exports.config = { framework: 'jasmine', specs: ['onPrepare/*_spec.js'], baseUrl: env.baseUrl + '/ng1/', onPrepare: () => { browser.params.password = '12345'; // 预设一个全局参数 } };
  • 文件形态:传入相对路径字符串,Protractor 会用 Node.js 的require加载该文件并执行其内容。例如 spec/onPrepareFileConf.js 与对应的 spec/onPrepare/startup.js:
// onPrepareFileConf.js exports.config = { specs: ['onPrepare/*_spec.js'], baseUrl: env.baseUrl + '/ng1/', onPrepare: 'onPrepare/startup.js' // 字符串 → 文件路径 };
// onPrepare/startup.js —— 文件内容即为要执行的准备逻辑 browser.params.password = '12345';

文件形态下,路径是相对于配置文件所在目录解析的——这一点在 lib/configParser.ts 中有明确实现:onPrepare与其他文件路径类配置(如seleniumServerJar、chromeDriver、firefoxPath)一起,被统一转换为相对于当前配置文件的路径。

验证这两种形态等价性的测试见 spec/onPrepare/onPrepare_spec.js,其中的断言expect(browser.params.password).toEqual('12345')证明onPrepare中写入的browser.params确实在测试用例中生效。

4.1onPrepare可以返回 Promise —— 时序保证的关键

文档强调了一个容易被忽略但至关重要的细节:

onPrepare可以返回一个 Promise,Protractor 会等待它完成后再继续执行。当准备过程涉及异步调用(例如与浏览器交互)时,必须这样做;否则 Protractor 无法保证执行顺序,可能在准备完成前就开始跑测试。

也就是说,如果onPrepare里有异步操作,却不返回 Promise,测试与准备之间就会出现竞态。正确写法是让onPrepare变成async函数或显式返回 Promise,例如 spec/onPreparePromiseConf.js:

onPrepare: async() => { browser.params.password = '12345'; return await new Promise(resolve => { setTimeout(resolve, 1000); // 模拟耗时准备 }); }

文件形态同样支持返回 Promise——文件导出一个async函数即可,见 spec/onPreparePromiseFileConf.js 对应的 spec/onPrepare/asyncstartup.js:

module.exports = async() => { browser.params.password = '12345'; return await new Promise((resolve) => { setTimeout(resolve, 1000); }); }

4.2 源码级验证:onPrepare到底怎么被执行的

onPrepare的"函数或文件名二选一"语义,由 lib/util.ts 中的内部助手runFilenameOrFn_统一实现:

  1. 若传入的是字符串,则以configDir为基准执行require(path.resolve(configDir, filenameOrFn))加载文件;
  2. 加载结果若为函数,则await filenameOrFn.apply(null, args)调用它;
  3. await保证了返回的 Promise 会被等待——这正是"Protractor 会等 onPrepare 的 Promise 完成"这一行为的技术根基。

调用链上,lib/runner.ts 在setTestPreparer(config.onPrepare)(约 L61)注册准备函数,随后在真正执行用例前先运行插件准备、再执行runFilenameOrFn_(this.config_.configDir, this.preparer_)(约 L92-L94)。结合 lib/config.ts 对onPrepare的注释还可以确认:当使用 multiCapabilities 并行跑多个浏览器时,onPrepare会对每个 capability 各执行一次;且此时 Protractor 全局对象与测试框架全局(如 Jasmine)都可用,你可以利用browser.getProcessedConfig()读取当前正在执行的 capability。

五、实战案例:用onPrepare完成登录(完整可复制)

仓库在 spec/withLoginConf.js 中提供了一个完整的"测试前登录"配置,是系统设置文档直接推荐的范例。完整解读如下:

const env = require('./environment.js'); exports.config = { seleniumAddress: env.seleniumAddress, // 连接已运行的 Selenium Server SELENIUM_PROMISE_MANAGER: false, // 关闭 WebDriver 控制流,改用 async/await framework: 'jasmine', specs: ['login/login_spec.js'], // 待运行的测试 capabilities: env.capabilities, // 浏览器能力 baseUrl: env.baseUrl + '/ng1/', // 被测应用基地址 onPrepare: async() => { // 1) 登录页是非 Angular 手动 bootstrap 页面,必须用 browser.driver.get await browser.driver.get(env.baseUrl + '/ng1/login.html'); // 2) 直接通过 WebDriver 原生 API 填写表单 await browser.driver.findElement(by.id('username')).sendKeys('Jane'); await browser.driver.findElement(by.id('password')).sendKeys('1234'); await browser.driver.findElement(by.id('clickme')).click(); // 3) 登录需要时间:显式等待跳转到 index.html 才继续 return await browser.driver.wait(async() => { const url = await browser.driver.getCurrentUrl(); return /index/.test(url); }, 10000); } };

这个例子把本文第二、四节的要点全部落地:

  • 登录页不可用browser.get→ 用browser.driver.get打开,并全程使用browser.driver.findElement(而非element(...))操作表单;
  • 准备是异步的→onPrepare声明为async并返回 Promise(return await browser.driver.wait(...));
  • 显式等待消除竞态→ 用browser.driver.wait轮询当前 URL 是否包含index,超时上限 10 秒,确认登录跳转完成后才放行测试。

配套的测试用例见 spec/login/login_spec.js:登录态通过 cookie 保持,测试直接browser.get(baseUrl + '/ng1/index.html')进入主页面,断言登录后的 cookietestcookie值为Jane-1234,从而验证onPrepare建立的会话确实在整个测试过程中有效。

六、为被测系统补齐配套配置

除了onPrepare与baseUrl,搭建被测系统时通常还会涉及以下配置(均出自 lib/config.ts 的完整注释):

配置项作用说明
baseUrl被测应用基地址browser.get()传入相对路径时会基于它解析(url.resolve语义),见 lib/config.ts
getPageTimeout页面加载超时(毫秒)默认 10 秒,超时错误形如Timed out waiting for page to load after 10000ms,可全局配置或按次传参browser.get(address, timeout),见 docs/timeouts.md
allScriptsTimeoutAngular 同步超时(毫秒)默认约 11 秒,若应用异步任务迟迟不结束会触发,见 docs/timeouts.md
capabilities/multiCapabilities单/多浏览器能力可附加count、shardTestFiles、maxInstances等并行参数,见 lib/config.ts
specs/suites指定要跑的测试specs为相对当前配置文件的路径模式,见 lib/config.ts

两个直接的工程建议:

  1. 为被测应用准备多种环境地址:由于被测系统只需 URL,可在配置中按环境切换baseUrl(如http://localhost:4200与https://staging.example.com),配合params甚至命令行参数(--params.xxx,见 lib/config.ts)实现一套配置多环境复用;
  2. 把"页面是否由 Angular 引导、是否持续轮询"当作被测系统的验收标准:上线测试前先检查应用侧这两类代码特征,能从根本上避免同步超时与竞态,远比在测试侧打补丁可靠。

小结

"设置被测系统"对 Protractor 来说,本质是回答三个问题:被测页面能不能被browser.get正常加载?应用会不会让 Protractor 永远等不到稳定?进入主流程前需要做什么全局准备?本文从 docs/system-setup.md 出发,结合仓库源码与配置示例给出了完整答案:手动 bootstrap 页面用browser.driver.get+ 显式等待;轮询用$interval替代$timeout;登录等全局准备放进onPrepare(函数或文件皆可,异步时必须返回 Promise)。沿着这三条主线,即可在任意环境上搭建出稳定、可复现的端到端测试被测系统。

  • 测试

【免费下载链接】protractor

E2E test framework for Angular apps

项目地址:https://gitcode.com/gh_mirrors/pr/protractor
点击查看免费下载

相关推荐

上一篇:ComfyUI Florence2模型加载深度解析与实战指南
下一篇:Haystack 与 MongoDB Atlas 集成实战:MongoDBAtlasDocumentStore 与双检索器(Embedding / Full-Text)完全指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询