Cucumber BeforeAll钩子未执行,Playwright浏览器初始化失败求助
Cucumber + Playwright 自动化测试:BeforeAll 钩子未执行导致浏览器初始化失败问题
我使用Cucumber结合Playwright进行自动化测试,需要在BeforeAll钩子中启动Chromium浏览器,并将实例传递给页面对象实现浏览器控制。但运行测试时发现BeforeAll钩子未执行,浏览器未完成初始化,抛出以下错误:
TypeError: Cannot read properties of undefined (reading 'locator') at new DashboardPage (C:\Users\Jasurbek\Documents\Projects\core-regression-bdd\_build\src\page_objects\DashboardPage.js:10:30) at new PageFactory (C:\Users\Jasurbek\Documents\Projects\core-regression-bdd\_build\src\page_objects\pageFactory.js:8:34) at Object.<anonymous> (C:\Users\Jasurbek\Documents\Projects\core-regression-bdd\_build\src\step_definitions\campaignWizard.js:7:15) at Module._compile (node:internal/modules/cjs/loader:1254:14) at Module._extensions..js (node:internal/modules/cjs/loader:1308:10) at Object.require.extensions.<computed> [as .js] (C:\Users\Jasurbek\Documents\Projects\core-regression-bdd\node_modules\ts-node\src\index.ts:1608:43) at Module.load (node:internal/modules/cjs/loader:1117:32) at Function.Module._load (node:internal/modules/cjs/loader:958:12) at Module.require (node:internal/modules/cjs/loader:1141:19) at require (node:internal/modules/cjs/helpers:110:18)
项目结构
project_folder: |_ _build // <= compiled files |_ configs |_ node_module |_ src |_ features |_ page_objects |_ step_definitions |_ utils |_ hooks.ts |_ package.json |_ tsconfig.json
相关代码文件
hooks.ts
let browserPage: Page; let pages: PageFactory; BeforeAll({timeout: 120000}, async function (){ const usersRoles = ['admin', 'publisher', 'advertiser', 'agency']; for (const user of usersRoles) { await saveLoginSession(user as User); } browserPage = await createBrowserPage('chrome', {headless: false}); pages = new PageFactory(browserPage); }); Before({tags: '@skipAuth'}, async function () { const browserName = process.env.BROWSER as 'chrome' | 'firefox' | 'webkit' || 'chrome'; browserPage = await createBrowserPage(browserName, {headless: false}); }) After(async function ({ pickle, result}) { if (result!.status === Status.FAILED) { await takeScreenshot.call(this, pickle, browserPage); } }); AfterAll(async function () { await browserPage.close(); }) export {browserPage, pages};
pageFactory.ts
export class PageFactory { constructor(page: Page) { this['Cognito page'] = new CognitoPage(page); this['Dashboard page'] = new DashboardPage(page); this['Home page'] = new LandingPage(page); this['Login page'] = new LoginPage(page); this['Campaign wizard'] = new CampaignWizard(page) } }
tsconfig.json
{ "compilerOptions": { "target": "ES2019", "module": "CommonJS", "moduleResolution": "Node", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, "noImplicitAny": false, "skipLibCheck": true, "typeRoots": [ "node_modules/@types" ], "lib": ["ES6", "DOM", "ES2019"], "outDir": "_build", "resolveJsonModule": true, "incremental": false }, "include": ["configs", "src", "hooks.ts"], "exclude": ["node_modules"], "ts-node": { "compilerOptions": { "module": "commonjs" } } }
cucumber.cjs
module.exports = { default: { formatOptions: { snippetInterface: 'async-await' }, paths: [ 'src/features/' ], dryRun: false, require: [ '_build/src/step_definitions/**/*.js', // <= compiled files '_build/hooks.js' // <= compiled files ], requireModule: [ 'ts-node/register' ], format: [ 'json:test-results/json-report.json', 'progress' ], parallel: process.env.CI ? 10 : 1 } }
package.json
{ "name": "core-regression-bdd", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "pretest": "node _build/src/utils/report/reportInit.js", "test": "cucumber-js --config=configs/cucumber.cjs || true", "posttest": "node _build/src/utils/report/reporter.js" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "@cucumber/cucumber": "^9.5.1", "@playwright/test": "^1.38.0", "@types/fs-extra": "^11.0.2", "@types/node": "^20.8.0", "fs-extra": "^11.1.1", "multiple-cucumber-html-reporter": "^3.5.0", "ts-node": "^10.9.1" }, "dependencies": { "dotenv": "^16.3.1" } }
saveSession.ts
const { credentials: {currentEnv} } = secrets; export async function saveLoginSession(coreUser: User) { const page: Page = await createBrowserPage('chrome', {headless: false}); await authenticateInCognito( secrets.credentials.cognito[currentEnv].username, secrets.credentials.cognito[currentEnv].password, page ); await authenticateInCore( secrets.credentials.core[`${coreUser.toLowerCase()}`].username, secrets.credentials.core[`${coreUser.toLowerCase()}`].password, page ); await page.context().storageState({ path: `${path.resolve('session_data')}/${currentEnv}.${coreUser}.state.json`}); await page.context().clearCookies(); await page.close(); console.info(`💾 Successfully saved the session | ${coreUser}.state.json`); }
createBrowserPage.ts
import {Browser, chromium, firefox, webkit, LaunchOptions, Page} from "@playwright/test"; const browsers = { chrome: chromium, firefox, webkit } export let browserInfo: { type?: string; version?: string } = {}; export async function createBrowserPage(browserName: 'chrome' | 'firefox' | 'webkit' = 'chrome', launchOptions?: LaunchOptions) { const browser: Browser = process.env.browser ? await browsers[process.env.browser].launch(launchOptions) : await browsers[browserName].launch(launchOptions); const page: Page = await browser.newPage(); await page.setViewportSize({width: 1440, height: 780}); browserInfo['type'] = browser.browserType.toString() browserInfo['version'] = browser.version.toString() return page; }
问题分析与解决方案
核心原因
错误栈显示,步骤定义文件(如campaignWizard.js)在加载阶段就直接实例化了PageFactory,但此时BeforeAll钩子尚未执行,browserPage变量还是undefined,导致DashboardPage构造函数调用page.locator()时抛出未定义错误。
解决步骤
调整Cucumber加载顺序
修改cucumber.cjs中的require数组顺序,确保钩子文件先于步骤定义加载:require: [ '_build/hooks.js', // 优先加载钩子 '_build/src/step_definitions/**/*.js' ],延迟页面对象实例化
不要在步骤定义的模块级别直接创建PageFactory实例,而是通过导入的pages变量访问:// 步骤定义文件示例 import { pages } from '../../hooks'; Given('进入Campaign向导页面', async function () { await pages['Campaign wizard'].navigate(); });验证钩子执行状态
在BeforeAll钩子中添加日志,确认是否真的未执行:BeforeAll({timeout: 120000}, async function (){ console.log('BeforeAll钩子已启动'); // 原有代码... });
额外优化建议
使用Cucumber的World对象存储浏览器实例和页面对象,避免全局变量带来的问题(尤其适合并行测试场景):
// hooks.ts中定义自定义World import { setWorldConstructor } from '@cucumber/cucumber'; class CustomWorld { browserPage: Page; pages: PageFactory; } setWorldConstructor(CustomWorld); Before(async function (this: CustomWorld) { if (!this.browserPage) { this.browserPage = await createBrowserPage('chrome', {headless: false}); this.pages = new PageFactory(this.browserPage); } });
步骤定义中通过this访问:
Given('进入Dashboard页面', async function () { await this.pages['Dashboard page'].load(); });
内容的提问来源于stack exchange,提问作者jkalandarov
相关产品推荐
相关产品推荐

