Playwright BDD Framework Step By Step

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"
}

Leave a Comment