如何使用Puppeteer、Cucumber及JS(npm)实现浏览器单例?新手求助Puppeteer浏览器单例配置指导
嘿,作为刚接触JavaScript和Puppeteer的新手,想要在Cucumber测试里实现浏览器单例其实没那么复杂——这样做的核心好处是避免每个测试场景都重新启动浏览器,能大幅提升测试速度,还能减少资源消耗。我来一步步带你搞定整个配置流程!
第一步:初始化项目并安装依赖
首先,咱们先搭好项目基础:
- 创建一个新文件夹,打开终端进入它
- 初始化npm项目:
npm init -y - 安装所需的开发依赖:
npm install @cucumber/cucumber puppeteer --save-dev@cucumber/cucumber:Cucumber的官方JS版本,用来写BDD测试puppeteer:Chrome官方的无头浏览器自动化工具
第二步:创建浏览器单例管理模块
咱们需要一个专门的文件来管理浏览器实例,确保整个测试生命周期里只有一个浏览器实例在运行。在项目根目录创建browserSingleton.js,代码如下:
let browserInstance = null; // 获取浏览器单例的方法 async function getBrowser() { if (!browserInstance) { // 启动浏览器,新手可以把headless设为false,方便看到浏览器操作 browserInstance = await require('puppeteer').launch({ headless: 'new', // 最新的无头模式,想可视化就改成false args: ['--start-maximized'] // 可选:启动时最大化窗口 }); console.log('浏览器已启动'); } return browserInstance; } // 关闭浏览器的方法 async function closeBrowser() { if (browserInstance) { await browserInstance.close(); browserInstance = null; console.log('浏览器已关闭'); } } module.exports = { getBrowser, closeBrowser };
这里的逻辑很简单:用一个变量browserInstance保存浏览器实例,每次调用getBrowser()时,先判断实例是否存在——不存在就启动新浏览器,存在就直接返回已有实例,这样就实现了单例。
第三步:配置Cucumber钩子(Hooks)
Cucumber的钩子能帮我们在测试的不同阶段执行代码,比如所有测试开始前启动浏览器,所有测试结束后关闭它。在项目根目录创建features/support/hooks.js(需要先建features/support文件夹),代码如下:
const { BeforeAll, AfterAll, Before } = require('@cucumber/cucumber'); const { getBrowser, closeBrowser } = require('../../browserSingleton'); let page = null; // 所有测试场景开始前,只执行一次:启动浏览器 BeforeAll(async function () { await getBrowser(); }); // 每个测试场景开始前,创建一个新页面(避免场景之间的状态干扰) Before(async function () { const browser = await getBrowser(); page = await browser.newPage(); // 把page实例绑定到Cucumber的World对象里,方便步骤定义里使用 this.page = page; }); // 所有测试场景结束后,只执行一次:关闭浏览器 AfterAll(async function () { await closeBrowser(); });
这里要注意:每个场景都要创建新页面,而不是复用同一个页面——不然前一个场景的操作会影响后一个场景,比如登录状态、页面内容残留,导致测试结果不准确。
第四步:写一个测试示例验证单例
接下来咱们写一个简单的BDD测试来验证单例是否正常工作:
1. 创建Feature文件
在features文件夹下创建example.feature:
Feature: 浏览器单例测试 验证Puppeteer浏览器单例在Cucumber测试中正常工作 Scenario: 访问Google首页 Given 我打开浏览器 When 我访问"https://www.google.com" Then 页面标题应该包含"Google" Scenario: 访问GitHub首页 Given 我打开浏览器 When 我访问"https://github.com" Then 页面标题应该包含"GitHub"
2. 创建步骤定义文件
在features/step_definitions文件夹下创建steps.js:
const { Given, When, Then } = require('@cucumber/cucumber'); const assert = require('assert'); Given('我打开浏览器', async function () { // 这里不需要做额外操作,因为Before钩子已经帮我们创建了page console.log('当前使用的是同一个浏览器实例'); }); When('我访问"{string}"', async function (url) { await this.page.goto(url); }); Then('页面标题应该包含"{string}"', async function (expectedText) { const pageTitle = await this.page.title(); assert.ok(pageTitle.includes(expectedText), `页面标题不包含${expectedText},实际标题是:${pageTitle}`); });
第五步:运行测试
最后,在package.json里添加测试脚本:
{ "scripts": { "test": "cucumber-js" } }
现在在终端运行:
npm test
你会看到终端输出“浏览器已启动”,然后两个场景依次执行,最后输出“浏览器已关闭”——这说明两个场景用的是同一个浏览器实例,单例生效了!
新手注意事项
- 如果想要看到浏览器的实际操作,把
browserSingleton.js里的headless: 'new'改成headless: false即可 - 单例模式下一定要确保每个场景用新页面,不然会出现测试污染的问题
- 如果某个特殊场景需要独立的浏览器实例,可以单独写一个钩子来重启浏览器,但大部分测试场景不需要这么做
内容的提问来源于stack exchange,提问作者Saurabh Garg

