To build an automated test framework using Playwright, Cucumber (BDD), and the Page Object Model (POM) pattern with TypeScript, follow this structured setup.
Cucumber Framework Official docs
1.Initialize Project & Dependencies:Node.js required.
Create a project directory and install the necessary dependencies for Playwright, @cucumber/cucumber, and TypeScript.
Bash
mkdir playwright-cucumber-pom
cd playwright-cucumber-pom
npm init -y
npm install --save-dev @playwright/test @cucumber/cucumber ts-node typescript @types/node
npx playwright install
2.Configure TypeScript & Cucumber:Set up root configuration files.
Create a tsconfig.json file in your root folder:
JSON
{
"compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true
}
}
Create a cucumber.json file in your root folder to define execution profiles:
JSON
{
"default": {
"formatOptions": {
"snippetInterface": "async-await"
},
"paths": ["src/tests/features/**/*.feature"],
"require": ["src/tests/steps/**/*.ts", "src/hooks/hooks.ts"],
"requireModule": ["ts-node/register"],
"format": ["progress", "html:reports/cucumber-report.html"]
}
}
3.Set Up Browser Hooks & Context Management:Manages browser lifecycle.
Create src/hooks/hooks.ts to manage launching and closing the browser instance before and after scenarios.
TypeScript
import { Before, After, BeforeAll, AfterAll } from '@cucumber/cucumber';
import { Browser, BrowserContext, Page, chromium } from '@playwright/test';
let browser: Browser;
let context: BrowserContext;
export let page: Page;
BeforeAll(async () => {
browser = await chromium.launch({ headless: false });
});
Before(async () => {
context = await browser.newContext();
page = await context.newPage();
});
After(async () => {
await page.close();
await context.close();
});
AfterAll(async () => {
await browser.close();
});
4.Create the Page Object Model (POM):Encapsulate page UI elements and actions.
Create src/pages/LoginPage.ts to store page selectors and interaction logic.
TypeScript
import { Page, Locator } from '@playwright/test';
export class LoginPage {
private page: Page;
private usernameInput: Locator;
private passwordInput: Locator;
private loginButton: Locator;
constructor(page: Page) {
this.page = page;
this.usernameInput = page.locator('#user-name');
this.passwordInput = page.locator('#password');
this.loginButton = page.locator('#login-button');
}
async navigateTo(url: string) {
await this.page.goto(url);
}
async login(username: string, password: string) {
await this.usernameInput.fill(username);
await this.passwordInput.fill(password);
await this.loginButton.click();
}
}
5.Write the Feature File:Define BDD scenarios using Gherkin.
Create src/tests/features/login.feature:
Gherkin
Feature: User Authentication
Scenario: Successful login with valid credentials
Given I navigate to "https://www.saucedemo.com/"
When I log in with username "standard_user" and password "secret_sauce"
Then I should see the inventory page title "Swag Labs"
6.Implement Step Definitions:Map Feature steps to Page Object code.
Create src/tests/steps/loginSteps.ts:
TypeScript
import { Given, When, Then } from '@cucumber/cucumber';
import { expect } from '@playwright/test';
import { page } from '../../hooks/hooks';
import { LoginPage } from '../../pages/LoginPage';
let loginPage: LoginPage;
Given('I navigate to {string}', async (url: string) => {
loginPage = new LoginPage(page);
await loginPage.navigateTo(url);
});
When('I log in with username {string} and password {string}', async (username: string, password: string) => {
await loginPage.login(username, password);
});
Then('I should see the inventory page title {string}', async (expectedTitle: string) => {
const actualTitle = await page.title();
expect(actualTitle).toBe(expectedTitle);
});
7.Add Execution Script & Run Tests:Execute tests via npm.
Update your package.json file to include a test command:
JSON
"scripts": {
"test": "cucumber-js"
}
Execute your framework in the terminal:
Bash
npm test
Configure Reporting in Cucumber Framework
1. Install Reporter Package:
Bash
npm install --save-dev multiple-cucumber-html-reporter
2. Configure JSON Output in cucumber.json:
Ensure Cucumber outputs test results in JSON format so the reporter engine can process them:
JSON
{
"default": {
"requireModule": ["ts-node/register"],
"require": ["src/hooks/**/*.ts", "src/tests/steps/**/*.ts"],
"format": ["json:reports/cucumber_report.json"]
}
}
3. Create Report Generation Generator Script (reporter.js):
Create reporter.js at the root of your project:
JavaScript
const report = require('multiple-cucumber-html-reporter');
report.generate({
jsonDir: './reports/',
reportPath: './reports/html-report/',
metadata: {
browser: { name: 'chromium', version: 'latest' },
device: 'Local Test Engine',
platform: { name: 'Windows/Linux' }
},
customData: {
title: 'Execution Info',
data: [
{ label: 'Project', value: 'Playwright Cucumber Framework' },
{ label: 'Environment', value: 'Staging' }
]
}
});
3. Update your package.json to unify test execution, reporting, and linting into seamless npm commands:
JSON
"scripts": {
"test": "cucumber-js || npm run report",
"test:smoke": "cucumber-js --tags '@smoke' || npm run report",
"test:parallel": "cucumber-js --parallel 4 || npm run report",
"report": "node reporter.js"
}