How to Generate E2E Test Cases with Playwright Codegen and BDD

This article compares two E2E test case generation approaches—recording via Playwright’s codegen and behavior‑driven development (BDD) using Gherkin—detailing their principles, code examples, selector handling, DSL design, and the trade‑offs in flexibility, difficulty, and readability for web testing.

MoonWebTeam
MoonWebTeam
MoonWebTeam
How to Generate E2E Test Cases with Playwright Codegen and BDD

Background

E2E testing sits at the top of the testing pyramid, offering high coverage with few test cases, but writing them can be costly due to element locating and debugging. This article explores two E2E test case generation methods: recording‑based generation and BDD‑based generation.

Recording‑Based Test Case Generation

Overview

Playwright’s codegen feature records browser actions in real time and generates test code in the language of choice, eliminating the need to write and debug selectors manually.

npx playwright codegen demo.playwright.dev/todomvc

The tool opens a browser window and a code window, captures actions, assertions, and can output code for multiple languages.

Playwright prefers role, text, and test‑id selectors, falling back to CSS or XPath, and can improve selectors to make them unique.

Mechanism

Running codegen spawns a rendering subprocess based on Chromium and a recording subprocess.

During interaction, Playwright serializes actions such as clicks into a JSON‑like structure, e.g.:

"o": [
  {"k":"name","v":"click"},
  {"k":"selector","v":"internal:attr=[placeholder='What needs to be done?'i]"},
  {"k":"button","v":"left"},
  {"k":"clickCount","v":1}
]

The selector format is internal:[selectorType]=[body], where selectorType can be role, attr, text, testid, etc., and modifier (s|i) indicates case sensitivity.

internal : scope of the selector (e.g., inside an iframe).

selectorType : type of selector.

body : actual selector content.

modifier : case‑sensitivity flag.

These objects are later transformed into language‑specific test code.

export type Action = ClickAction | CheckAction | ClosesPageAction | OpenPageAction | UncheckAction | FillAction | NavigateAction | PressAction | SelectAction | SetInputFilesAction | AssertTextAction | AssertValueAction | AssertCheckedAction;

Finally, the generated source code for each language is sent via WebSocket to a recorder that assembles the complete test script.

BDD Test Case Generation

What Is BDD?

Behavior‑Driven Development (BDD) extends TDD by using natural‑language specifications that are shared among developers, testers, and business stakeholders. It improves collaboration and produces readable test cases.

Why Use BDD?

Automation support: BDD integrates with automation frameworks for fast, reliable execution.

Readability and maintainability: Natural‑language scenarios are easy for all team members to understand.

Integrated development and testing: Close collaboration reduces friction and speeds delivery.

BDD Workflow

Understand business requirements.

Write user stories.

Define scenarios using Gherkin keywords (Feature, Given, When, Then).

Implement executable test steps.

Run tests with tools such as Cucumber.

Gherkin Keywords

Feature : High‑level description of functionality.

Given : Initial context.

When : Event or action.

Then : Expected outcome.

BDD Tooling

Cucumber reads plain‑text feature files and maps them to step definitions. Example step definitions in JavaScript:

const assert = require('assert');
const { Given, When, Then } = require('@cucumber/cucumber');

Given('test', function () {});

Given('today is {string}', function (givenDay) {
  this.today = givenDay;
});

When("I ask whether it's Friday yet", function () {
  this.actualAnswer = isItFriday(this.today);
});

Then('I should be told {string}', function (expectedAnswer) {
  assert.strictEqual(this.actualAnswer, expectedAnswer);
});

Running cucumber-js executes the scenarios.

Atomic BDD Commands

To increase reusability, high‑level steps are broken into atomic commands such as jump, wait, clickElement, inputText, etc.

Given('jump to [{string}]', function (url) {});
When('wait [{int}] seconds', function (time) {});
When('click element [{string}]', function (locator) {});
Then('element [{string}] exists', function (locator) {});

DSL and Parser

A domain‑specific language (DSL) represents a scenario as a list of steps, each containing a command and parameters. The DSL can be interpreted to run tests directly or to generate code in multiple languages.

export interface Scenario {
  name: string;
  steps: Step[];
}
export interface Step {
  /** command */
  command: Command;
  /** parameters */
  params: (string | number)[];
}
export enum Command {
  Jump = 'jump',
  Wait = 'wait',
  ExistElement = 'existElement',
  ClickElement = 'clickElement',
  InputText = 'inputText',
  Press = 'press',
}

Conclusion

Recording‑based generation is simple but less flexible and harder to understand, while BDD‑based generation requires more effort up front but yields higher flexibility, better readability, and stronger alignment with business requirements.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

e2e testingBDDPlaywrightGherkin
MoonWebTeam
Written by

MoonWebTeam

Official account of MoonWebTeam. All members are former front‑end engineers from Tencent, and the account shares valuable team tech insights, reflections, and other information.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.