Cucumber Playwright POM框架跨文件复用步骤报错:page未定义
问题:Cucumber Playwright跨步骤文件复用page对象失败报错
问题场景
- 已搭建Cucumber Playwright POM框架,包含两个特性文件:
userLogin.feature、bookHotel.feature,三个步骤定义文件:userLogin.steps.js、bookHotel.steps.js、searchHotel.steps.js - 单独运行
userLogin.feature时功能正常,但运行复用了userLogin.steps.js中3个步骤和searchHotel.steps.js中1个步骤的bookHotel.feature时,抛出ReferenceError: page is not defined错误
错误日志
Scenario: Booking Hotel scenario # features/bookHotel.feature:3 ✔ Before # steps/userLogin.steps.js:11 ✔ Given User navigate to the Hotel app # steps/userLogin.steps.js:21 ✔ Then User should see the login page # steps/userLogin.steps.js:26 ✔ Then User will login to the app # steps/userLogin.steps.js:34 ✖ Then User will search for the hotel # steps/searchHotel.steps.js:6 ReferenceError: page is not defined at World.<anonymous> (/Users/abcd/Documents/PlaywrightCucumberTraining/steps/searchHotel.steps.js:7:45) ✔ After # steps/userLogin.steps.js:17 1 scenario (1 failed) 4 steps (1 failed, 3 passed)
相关代码
userLogin.feature
Feature: User Login feature Scenario: User Login scenario Given User navigate to the Hotel app Then User should see the login page Then User will login to the app
userLogin.steps.js
const { Given, Then, Before, After, BeforeAll } = require('@cucumber/cucumber'); const { expect } = require('@playwright/test'); const { chromium } = require('playwright'); const LoginPage = require('../pages/login.page'); const SearchHotelPage = require('../pages/searchHotel.page'); const {searchHotelSelectors} = require('../data/testData.json'); require('dotenv').config(); let browser, context, page; Before(async () => { browser = await chromium.launch({ headless: false }); context = await browser.newContext(); page = await context.newPage(); }); After(async () => { await browser.close(); }); Given('User navigate to the Hotel app', { timeout: 20000 }, async () => { await page.goto(process.env.url); }); Then('User should see the login page',{ timeout: 20000 }, async () => { await expect(page.locator('text=XYZ Launches The XYZ Hotel App!')).toBeVisible(); const loginPage = new LoginPage(page); await loginPage.login(); }); Then('User will login to the app',{ timeout: 20000 }, async () => { await expect(page.locator('text=Welcome to XYZ Group of Hotels')).toBeVisible(); }); // Then('User will search for the hotel',{ timeout: 20000 }, async () => { // const searchHotelPage=new SearchHotePage(this.page); // //console.log(testData.searchHotelDetails.location) // await searchHotelPage.searchHotel(searchHotelSelectors.searchHotelDetails.location); // });
bookHotel.feature
Feature: Booking Hotel feature Scenario: Booking Hotel scenario Given User navigate to the Hotel app Then User should see the login page Then User will login to the app Then User will search for the hotel
searchHotel.steps.js
const { Given, Then, Before, After } = require('@cucumber/cucumber'); const SearchHotelPage = require('../pages/searchHotel.page'); const {searchHotelSelectors} = require('../data/testData.json'); require('dotenv').config(); Then('User will search for the hotel',{ timeout: 20000 }, async () => { const searchHotelPage=new SearchHotelPage(page); await searchHotelPage.searchHotel(searchHotelSelectors.searchHotelDetails.location); });
解答
1. 跨特性/步骤文件复用步骤完全可行
Cucumber原生支持跨文件复用步骤定义,只要步骤文本完全匹配,不管定义在哪个.steps.js文件里,Cucumber都会自动匹配执行。你的场景里已经验证了这一点(bookHotel.feature成功复用了userLogin.steps.js的三个步骤),问题出在page对象的作用域限制,而非步骤复用本身。
2. 解决page is not defined报错
你在userLogin.steps.js中定义的page是文件局部变量,无法被其他步骤文件访问。Cucumber提供World对象用于在不同步骤文件间共享上下文,正确做法是将page挂载到World实例上:
步骤1:修改userLogin.steps.js的钩子与步骤实现
把局部变量改为挂载到this(即Cucumber的World对象),注意使用普通function而非箭头函数(箭头函数的this不指向World):
// 替换原Before/After钩子 Before(async function () { this.browser = await chromium.launch({ headless: false }); this.context = await this.browser.newContext(); this.page = await this.context.newPage(); }); After(async function () { await this.browser.close(); }); // 替换所有使用page的步骤 Given('User navigate to the Hotel app', { timeout: 20000 }, async function () { await this.page.goto(process.env.url); }); Then('User should see the login page',{ timeout: 20000 }, async function () { await expect(this.page.locator('text=XYZ Launches The XYZ Hotel App!')).toBeVisible(); const loginPage = new LoginPage(this.page); await loginPage.login(); }); Then('User will login to the app',{ timeout: 20000 }, async function () { await expect(this.page.locator('text=Welcome to XYZ Group of Hotels')).toBeVisible(); });
步骤2:修改searchHotel.steps.js的步骤实现
同样通过this.page访问共享的page对象,使用普通function:
Then('User will search for the hotel',{ timeout: 20000 }, async function () { const searchHotelPage=new SearchHotelPage(this.page); await searchHotelPage.searchHotel(searchHotelSelectors.searchHotelDetails.location); });
3. 额外优化建议
- 单独创建
hooks.js文件,将Before/After钩子统一放在这里管理,避免钩子与步骤定义混杂,提升代码模块化程度。 - 所有步骤函数必须使用普通
function,不能用箭头函数,否则无法通过this访问World对象的共享上下文。
内容的提问来源于stack exchange,提问作者newbee
相关产品推荐
相关产品推荐

