Cucumber + Playwright framework based on javascript
Step 1: Setup new project and playwright+cucumber
Git init and Nodejs init for a new project
cd playwright-cucumber-demo
git init
npm init -y
Install playwright
npm init playwright@latest
# Do you want to use TypeScript or JavaScript? JavaScript (或 TypeScript,本项目以 JS 为例)
# Where to put your end-to-end tests? tests (这个目录之后会被 Cucumber 使用,但先保留)
# Add a GitHub Actions workflow? No (我们将自己创建更定制的流程)
# Install Playwright browsers? Yes
Install cucumber-js
npm install @cucumber/cucumber @cucumber/pretty-formatter
# 如果使用 TypeScript,还需要安装 typescript 和 ts-node
Step 2: Create file structure
playwright-cucumber-demo/
├── features/
│ ├── example.feature # 用 Gherkin 语言编写的功能文件
│ └── step_definitions/
│ └── example.steps.js # 步骤定义文件
├── support/
│ ├── world.js # 自定义 World 对象,用于共享状态
│ └── hooks.js # 钩子函数 (如 before, after)
├── playwright.config.js # Playwright 配置文件
├── package.json
└── .github/└── workflows/└── ci.yml # GitHub Actions 工作流文件 (稍后创建)
Step 3: Cucumber configurations
// cucumber.config.js
/* Refer to https://github.com/cucumber/cucumber-js/blob/main/docs/configuration.md for more details
*/
module.exports = {default: {// 显式指定 feature 文件路径paths: ['features/**/*.feature'],require: ['support/world.js','support/hooks.js','support/register-allure.js', //预留文件,可对allure扩展'features/step_definitions/**/*.js'],format: ['@cucumber/pretty-formatter','html:cucumber-report.html','json:cucumber-report.json'],parallel: 2 // 并行运行的进程数,可根据 CI 环境调整}
}
`npx cucumber-js` execution would trigger below flows:
1.Load the require files in sequence
2.Call setWorldConstructor(CustomWorld) once while it loads world.js
3.Create CustomWorld instance before each scenario execution, so constructor method would be triggered
4.Then Before hook would be triggered
Step 4:Write example feature and step_definition files
# features/example.feature
Feature: 访问 Playwright 官网并搜索Scenario: 在 Playwright 官网搜索Given 我导航到 Playwright 官网When 我在搜索框中输入 "CSS"Then 搜索结果页面应该显示与 "CSS" 相关的内容
// features/step_definitions/example.steps.js
const { Given, When, Then } = require('@cucumber/cucumber');
const { expect } = require('@playwright/test');Given('我导航到 Playwright 官网', async function () {// this.page 来自 World 对象,在 hooks.js 中初始化await this.page.goto('https://playwright.dev');
});When('我在搜索框中输入 {string}', async function (searchTerm) {await this.page.click('button.DocSearch-Button');await this.page.fill('input.DocSearch-Input', searchTerm);
});Then('搜索结果页面应该显示与 {string} 相关的内容', async function (searchTerm) {// 等待搜索结果出现const firstResult = await this.page.waitForSelector('.DocSearch-Hit a');const text = await firstResult.textContent();expect(text).toContain(searchTerm);
});
Step 5: Create support files to integrate Allure
World Extension
Cucumber framework allows a custom World to manage per-scenario state and integrate Allure, so we should define a CustomWorld class to extend World.
Why it's needed: Only when using Cucumber, you need a central place to manage shared state and lifecycles at the scene level (e.g., browser/page and allure operations). Without it, you have to manually create it at each step or manage it through global variables, which is difficult to maintain.
// support/world.js
/* Refer to https://github.com/cucumber/cucumber-js/blob/db0956e0725e2ef00854c0d52c4392db00bdf307/docs/support_files/world.md for more details
*/
const { setWorldConstructor, World } = require('@cucumber/cucumber');
const { chromium } = require('playwright');
// 根据 allure-cucumberjs legacy.ts 的弃用说明,不再继承 AllureCucumberWorld,直接使用 allure-js-commons API
const {attachment, // 添加附件label, // 添加自定义或标准标签link, // 添加链接 (issue / tms)severity, // 严重级别step // 自定义步骤包装
} = require('allure-js-commons');class CustomWorld extends World {constructor(options) {super(options);// 可在此处初始化测试上下文数据}async launchBrowser() {this.browser = await chromium.launch({ headless: true });this.context = await this.browser.newContext();this.page = await this.context.newPage();}async closeBrowser() {if (this.browser) await this.browser.close();}// ===== Allure 辅助方法(采用新 API,避免使用已弃用的 AllureCucumberWorld 实例方法) =====async attachScreenshot(name = 'screenshot') {if (!this.page) return;const buf = await this.page.screenshot();attachment(name, buf, { contentType: 'image/png' });}addSeverity(level = 'normal') {severity(level); // e.g. blocker, critical, normal, minor, trivial}addLabel(name, value) {label(name, value);}addIssue(name, url) {link(url, name, 'issue');}addTms(name, url) {link(url, name, 'tms');}async runStep(title, bodyFn) {return step(title, bodyFn); // 在报告中嵌套展示}
}setWorldConstructor(CustomWorld);
Hook function
// support/hooks.js
/* Refer tohttps://github.com/cucumber/cucumber-js/blob/main/docs/support_files/hooks.mdfor more details
*/
const { Before, After } = require('@cucumber/cucumber');Before(async function () {await this.launchBrowser();
});After(async function () {await this.closeBrowser();
});
Step 6: Add package.json script
"scripts": {"test": "cucumber-js","test:headed": "cucumber-js --world-parameters={\"headed\": true}","test:report": "cucumber-js && open cucumber-report.html"
}