
End-to-end (E2E) testing has become an essential part of modern web development. As applications grow more complex, teams need a testing approach that is understandable to both developers and non-technical stakeholders. This is where Gherkin and Playwright-BDD shine: Gherkin provides human-readable test scenarios, while Playwright offers a fast, reliable browser automation engine.
In this blog, we’ll explore what Gherkin is, how Playwright-BDD works, and finally how to set up a new project combining both.
1. What Is Gherkin Language?
Gherkin is a domain-specific language (DSL) used for writing behavior-driven development (BDD) scenarios.
Its strength lies in clarity — Gherkin documents describe software behavior in plain English using a strict but readable structure.
Key features of Gherkin
- Readable by anyone (QA, dev, BA, product owner)
- Executable when paired with a BDD framework like Cucumber or Playwright-BDD
- Structured using keywords like
Feature,Scenario,Given,When,Then
Gherkin Scenario (search.feature)
Feature: Search Bar
Scenario: Search for a product
Given I am on the homepage
When I search for "laptop"
Then I should see search results related to "laptop"
This scenario reads like a conversation — and that’s the whole point.
2. What Is Playwright-BDD?
Playwright-BDD is a library that integrates Gherkin-style BDD tests with Microsoft Playwright, enabling you to write .feature files and implement steps that interact with browsers.
Why use Playwright-BDD?
- You get Playwright’s speed and robustness
- You keep Gherkin readability
- You structure tests the BDD way
- Easy to scale test suites for large teams
It works similarly to Cucumber, but powered by Playwright under the hood
3. Setting Up a New Project With Playwright-BDD
Below is a clean, step-by-step setup guide.
Step 1 — Create a New Project
mkdir playwright-bdd-project
cd playwright-bdd-project
npm init -y
Step 2 — Install Required Dependencies
npm install -D @playwright/test playwright-bdd
Install the browsers needed for testing
npx playwright install
This downloads Chromium, Firefox, and WebKit browsers.
Step 3 — Project Structure
Create the following directory structure:
playwright-bdd-project/
├── features/
│ ├── steps/
│ └── checkbox.feature
├── playwright.config.ts
└── package.json
Step 4: Configure Playwright
Create `playwright.config.ts`
import { defineConfig } from '@playwright/test';
import { defineBddConfig } from 'playwright-bdd';
const testDir = defineBddConfig({
paths: ['features/**/*.feature'],
steps: ['features/steps/**/*.ts'],
});
export default defineConfig({
testDir,
reporter: 'html',
use: {
baseURL: 'https://practice.expandtesting.com',
},
});
Step 5: Update package.json Scripts
Add these scripts to your `package.json`:
{
"scripts": {
"test": "playwright test",
"test:ui": "playwright test --ui",
"test:headed": "playwright test --headed",
"test:debug": "playwright test --debug",
"bdd:generate": "bddgen",
"bdd:watch": "bddgen --watch"
}
}
Script explanations:
– test – Run tests in headless mode
– test:ui – Run tests with Playwright UI mode
– test:headed – Run tests with visible browser
– test:debug – Run tests in debug mode
– bdd:generate – Generate test files from feature files
– bdd:watch – Watch for changes and regenerate automatically
Step 6 — Create a Feature File
Create: features/checkbox.feature
Feature: Checkboxes
As a user
I want to interact with checkboxes
So that I can test checkbox functionality
Background:
Given I navigate to the checkboxes page
Scenario: Happy Case - Check and uncheck checkboxes
When I check "Checkbox 1"
Then "Checkbox 1" should be checked
Step 7 — Create Step Definitions
Create `features/steps/checkbox.steps.ts`:
import { createBdd, test } from 'playwright-bdd';
import { expect } from '@playwright/test';
const { Given, When, Then } = createBdd(test);
// Export test instance for playwright-bdd
export { test };
Given('I navigate to the checkboxes page', async ({ page }) => {
await page.goto('/checkboxes');
await expect(page).toHaveTitle(/Check Boxes Page/i);
});
When('I check {string}', async ({ page }, checkboxLabel) => {
await page.getByRole('checkbox', { name: checkboxLabel }).check();
});
Then('{string} should be checked', async ({ page }, checkboxLabel) => {
await expect(page.getByRole('checkbox', { name: checkboxLabel })).toBeChecked();
});
Step Definition Breakdown:
1. Import statements:
– `createBdd, test` from `playwright-bdd` – Creates BDD test context
– `expect` from `@playwright/test` – Assertions
2. Given step:
– Navigates to the checkboxes page
– Verifies the page title
3. When step:
– Uses `getByRole(‘checkbox’, { name: … })` – Playwright’s recommended semantic locator
– `{string}` is a parameter that captures text from the feature file
– `.check()` – Checks the checkbox
4. Then step:
– Uses the same locator to find the checkbox
– `.toBeChecked()` – Asserts the checkbox is checked
Why `getByRole`?
– More reliable than CSS selectors
– Follows accessibility best practices
– Easier to read and maintain
– Less brittle when UI changes
Step 8: Generate Test Files
Generate the Playwright test files from your feature files:
npm run bdd:generate
This creates test files in `.features-gen/` directory. These are auto-generated and shouldn’t be edited manually.
Step 9: Run Your Tests
Run the tests:
npm test
Or run with UI mode for a better experience:
npm run test:ui
4. Best Practices
Use Semantic Locators
page.getByRole('checkbox', { name: 'Checkbox 1' }) // good
page.locator('input[type="checkbox"]').first() // avoid
Keep Steps Reusable
Write step definitions that can be reused across multiple scenarios:
When('I check {string}', async ({ page }, checkboxLabel) => {
await page.getByRole('checkbox', { name: checkboxLabel }).check();
});
Use Background for Common Setup
If multiple scenarios need the same setup, use `Background`:
Background:
Given I navigate to the checkboxes page
Write Clear Feature Descriptions
Make your feature files readable for non-technical stakeholders:
Feature: Checkboxes
As a user
I want to interact with checkboxes
So that I can test checkbox functionality
Keep Step Definitions Simple
Each step should do one thing. If a step is too complex, break it into smaller steps.
5. Advanced Topics
Parameter Types
You can use different parameter types in your steps:
When('I check checkbox number {int}', async ({ page }, checkboxNumber) => {
// checkboxNumber is a number
});
When('I check {string} at {date}', async ({ page }, label, date) => {
// date is a Date object
});
Data Tables
Use tables for multiple test data:
Scenario: Check multiple checkboxes
When I check the following checkboxes:
| Checkbox 1 |
| Checkbox 2 |
| Checkbox 3 |
Scenario Outlines
Run the same scenario with different data:
Scenario Outline: Check different checkboxes
When I check "<checkbox>"
Then "<checkbox>" should be checked
Examples:
| checkbox |
| Checkbox 1 |
| Checkbox 2 |
6. Conclusion
Both Playwright and Playwright-BDD have proven to be powerful tools for building a clean and maintainable testing workflow. After working with them extensively on a real project, I’ve seen firsthand how much they can improve test reliability, readability, and speed. I hope that sharing my practical experience gives you a clearer idea of what to expect and helps you decide whether these tools are the right fit for your own project.
Resources
– Playwright Documentation: https://playwright.dev/
– playwright-bdd GitHub: https://github.com/vitalets/playwright-bdd
– Gherkin Syntax Reference: https://cucumber.io/docs/gherkin/reference/
– Blog: https://willholmes.hashnode.dev/executing-bdd-tests-with-playwright