Answer
Playwright + Cucumber BDD Integration
Installation
Bash
npm install --save-dev @cucumber/cucumber @playwright/test
npm install --save-dev playwright-cucumber-js
# OR use the community package:
npm install --save-dev @badeball/cypress-cucumber-preprocessor # (for Cypress)
npm install --save-dev @cucumber/cucumber
Popular setup with @cucumber/cucumber + @playwright/test directly:
Bash
npm install --save-dev @cucumber/cucumber @playwright/test playwright
Feature File
Gherkin
# features/login.feature
Feature: User Authentication
As a registered user
I want to log in to the application
So that I can access my account
Scenario: Successful login with valid credentials
Given I am on the login page
When I enter email "user@test.com" and password "password123"
And I click the Sign In button
Then I should be redirected to the dashboard
And I should see the welcome message "Welcome back"
Scenario Outline: Failed login with invalid credentials
Given I am on the login page
When I enter email "<email>" and password "<password>"
And I click the Sign In button
Then I should see error "<error>"
Examples:
| email | password | error |
| wrong@test.com | password123 | Invalid email or password |
| user@test.com | wrongpass | Invalid email or password |
| notanemail | password123 | Please enter a valid email |
Step Definitions
TypeScript
// features/steps/login.steps.ts
import { Given, When, Then } from '@cucumber/cucumber';
import { chromium, Browser, BrowserContext, Page } from '@playwright/test';
import { expect } from '@playwright/test';
let browser: Browser;
let context: BrowserContext;
let page: Page;
Given('I am on the login page', async () => {
browser = await chromium.launch({ headless: true });
context = await browser.newContext();
page = await context.newPage();
await page.goto(process.env.BASE_URL + '/login');
});
When('I enter email {string} and password {string}', async (email: string, password: string) => {
await page.getByLabel('Email').fill(email);
await page.getByLabel('Password').fill(password);
});
When('I click the Sign In button', async () => {
await page.getByRole('button', { name: 'Sign in' }).click();
});
Then('I should be redirected to the dashboard', async () => {
await expect(page).toHaveURL(/dashboard/);
});
Then('I should see the welcome message {string}', async (message: string) => {
await expect(page.getByRole('heading')).toContainText(message);
});
Then('I should see error {string}', async (errorText: string) => {
await expect(page.locator('.error-message')).toContainText(errorText);
});
// Teardown
After(async () => {
await context?.close();
await browser?.close();
});
Cucumber Config
TypeScript
// cucumber.config.ts
export default {
default: {
paths: ['features/**/*.feature'],
require: ['features/steps/**/*.ts'],
requireModule: ['ts-node/register'],
format: ['progress', 'html:reports/cucumber-report.html'],
parallel: 2,
retry: 1,
},
};
Run Cucumber Tests
Bash
npx cucumber-js --config cucumber.config.ts
World Context (Share Page Between Steps)
TypeScript
// support/world.ts
import { setWorldConstructor, World } from '@cucumber/cucumber';
import { Browser, BrowserContext, Page, chromium } from '@playwright/test';
class PlaywrightWorld extends World {
browser!: Browser;
context!: BrowserContext;
page!: Page;
async init() {
this.browser = await chromium.launch();
this.context = await this.browser.newContext();
this.page = await this.context.newPage();
}
}
setWorldConstructor(PlaywrightWorld);
