QA How-To
How to Build a BDD framework with Cucumber and Playwright (2026)
Learn how to build a BDD framework with Cucumber and Playwright with runnable examples, sound architecture, test data, parallel execution, reporting, CI, and in
20 min read | 3,051 words
TL;DR
Build a thin vertical slice first, then add typed reuse, isolated data, parallel safety, and CI evidence. Keep business expectations visible and tool mechanics behind small adapters.
Key Takeaways
- Start with product risks and one representative workflow.
- Separate test intent, orchestration, data, and tool adapters.
- Validate configuration and test data before expensive setup.
- Give every test isolated runtime and application state.
- Use observable conditions and deterministic cleanup.
- Report failures with sanitized, reproducible evidence.
A maintainable BDD stack connects readable Gherkin scenarios to a small TypeScript automation layer, gives every scenario an isolated browser context, and keeps assertions close to business outcomes. This guide shows how to build a BDD framework with Cucumber and Playwright without turning feature files into disguised scripts.
You will create the project, wire the Cucumber world and hooks, model pages, execute in parallel, collect evidence, and establish team rules that keep the suite useful after its first release.
TL;DR
| Concern | Recommended owner | Why |
|---|---|---|
| Business rule | Feature file | Reviewable by product and QA |
| UI interaction | Step or page object | Reusable technical detail |
| Browser lifecycle | World and hooks | Scenario isolation |
| Assertion | Step definition | Failure points match outcomes |
| Evidence | After hook | Captures failed scenario state |
The shortest reliable path is to build one representative flow, enforce isolation, and add reuse only after repetition is visible.
1. Define the BDD Contract Before Writing Code
BDD is a collaboration practice, not a synonym for Cucumber. Start with examples that expose rules, boundaries, and observable outcomes. A scenario should describe one behavior in domain language. Avoid clicks, selectors, waits, and database implementation details in Gherkin. Agree who reviews features and when examples become executable. This social contract prevents an automation team from producing readable-looking tests that product partners never read.
Implementation review should cover ownership, isolation, observability, and the cost of change. Test the layer without its slowest dependency where possible, then retain a thin end-to-end check that proves integration. Record assumptions in code or schema documentation, validate configuration early, and make errors identify the scenario plus the failed operation. These practices keep this part of the design useful when the suite expands across contributors and CI workers.
A production-ready implementation also needs a negative path. Deliberately pass invalid configuration, unavailable dependencies, malformed data, and an unmet assertion through the layer. Confirm that cleanup still runs and that reports redact secrets. This failure-first exercise reveals coupling sooner than a green demonstration and gives reviewers concrete evidence that the framework can be operated under pressure.
2. Choose a Cucumber Playwright TypeScript Architecture
Use feature files as specifications, step definitions as thin adapters, page or task objects for interaction, and a custom World for scenario state. Dependencies should point inward toward domain intent. A step may call signInPage.signInAs(user), but a page object should never know Cucumber expressions. This separation lets UI helpers run in non-BDD tests and keeps wording changes from breaking browser code.
Implementation review should cover ownership, isolation, observability, and the cost of change. Test the layer without its slowest dependency where possible, then retain a thin end-to-end check that proves integration. Record assumptions in code or schema documentation, validate configuration early, and make errors identify the scenario plus the failed operation. These practices keep this part of the design useful when the suite expands across contributors and CI workers.
A production-ready implementation also needs a negative path. Deliberately pass invalid configuration, unavailable dependencies, malformed data, and an unmet assertion through the layer. Confirm that cleanup still runs and that reports redact secrets. This failure-first exercise reveals coupling sooner than a green demonstration and gives reviewers concrete evidence that the framework can be operated under pressure.
3. Install and Configure the Toolchain
Pin dependencies through the lockfile and run Cucumber through tsx so TypeScript step files load without a separate compile step. Put configuration in cucumber.js, including feature globs, required support files, formatters, and parallelism. Keep browser choice and base URL in environment-backed configuration, validate required values at startup, and never store credentials in feature files.
Implementation review should cover ownership, isolation, observability, and the cost of change. Test the layer without its slowest dependency where possible, then retain a thin end-to-end check that proves integration. Record assumptions in code or schema documentation, validate configuration early, and make errors identify the scenario plus the failed operation. These practices keep this part of the design useful when the suite expands across contributors and CI workers.
A production-ready implementation also needs a negative path. Deliberately pass invalid configuration, unavailable dependencies, malformed data, and an unmet assertion through the layer. Confirm that cleanup still runs and that reports redact secrets. This failure-first exercise reveals coupling sooner than a green demonstration and gives reviewers concrete evidence that the framework can be operated under pressure.
4. Build a BDD Framework with Cucumber and Playwright Lifecycle
Create one browser context per scenario. A context provides isolated cookies, permissions, storage, and cache at a lower cost than sharing accidental state. The hook must always close resources, even when setup or an assertion fails. Attach a screenshot before closing the page. For large suites, a worker-scoped browser can be optimized later, but correctness and isolation come first.
Implementation review should cover ownership, isolation, observability, and the cost of change. Test the layer without its slowest dependency where possible, then retain a thin end-to-end check that proves integration. Record assumptions in code or schema documentation, validate configuration early, and make errors identify the scenario plus the failed operation. These practices keep this part of the design useful when the suite expands across contributors and CI workers.
A production-ready implementation also needs a negative path. Deliberately pass invalid configuration, unavailable dependencies, malformed data, and an unmet assertion through the layer. Confirm that cleanup still runs and that reports redact secrets. This failure-first exercise reveals coupling sooner than a green demonstration and gives reviewers concrete evidence that the framework can be operated under pressure.
5. Write Declarative Features and Stable Steps
Use Cucumber expressions for meaningful parameters and keep step wording consistent. A step definition should perform one domain action or verification. Do not build a giant universal step catalog such as "I click X." It creates ambiguous phrasing and exposes the DOM in business specifications. Prefer roles, labels, and test IDs in Playwright locators, then rely on its actionability checks rather than arbitrary sleeps.
Implementation review should cover ownership, isolation, observability, and the cost of change. Test the layer without its slowest dependency where possible, then retain a thin end-to-end check that proves integration. Record assumptions in code or schema documentation, validate configuration early, and make errors identify the scenario plus the failed operation. These practices keep this part of the design useful when the suite expands across contributors and CI workers.
A production-ready implementation also needs a negative path. Deliberately pass invalid configuration, unavailable dependencies, malformed data, and an unmet assertion through the layer. Confirm that cleanup still runs and that reports redact secrets. This failure-first exercise reveals coupling sooner than a green demonstration and gives reviewers concrete evidence that the framework can be operated under pressure.
6. Add Page Objects Without Hiding Assertions
Page objects should expose cohesive actions and stable observations. Keep most assertions in steps so failure output identifies the violated behavior. A page can return a locator or domain value, while the step uses Playwright assertions or Node assertions. Avoid deep inheritance. Small composition-based components for navigation, dialogs, and tables are easier to maintain than a base page with dozens of unrelated methods.
Implementation review should cover ownership, isolation, observability, and the cost of change. Test the layer without its slowest dependency where possible, then retain a thin end-to-end check that proves integration. Record assumptions in code or schema documentation, validate configuration early, and make errors identify the scenario plus the failed operation. These practices keep this part of the design useful when the suite expands across contributors and CI workers.
A production-ready implementation also needs a negative path. Deliberately pass invalid configuration, unavailable dependencies, malformed data, and an unmet assertion through the layer. Confirm that cleanup still runs and that reports redact secrets. This failure-first exercise reveals coupling sooner than a green demonstration and gives reviewers concrete evidence that the framework can be operated under pressure.
7. Manage Test Data and Environment State
Create data through APIs or fixtures when the behavior under test does not require UI setup. Generate unique identifiers per scenario and register cleanup immediately after creation. Store only scenario-specific values on the World. Tags can select environments or capabilities, but tags should not conceal uncontrolled data dependencies. Make retries a diagnostic tool, not a substitute for deterministic setup.
Implementation review should cover ownership, isolation, observability, and the cost of change. Test the layer without its slowest dependency where possible, then retain a thin end-to-end check that proves integration. Record assumptions in code or schema documentation, validate configuration early, and make errors identify the scenario plus the failed operation. These practices keep this part of the design useful when the suite expands across contributors and CI workers.
A production-ready implementation also needs a negative path. Deliberately pass invalid configuration, unavailable dependencies, malformed data, and an unmet assertion through the layer. Confirm that cleanup still runs and that reports redact secrets. This failure-first exercise reveals coupling sooner than a green demonstration and gives reviewers concrete evidence that the framework can be operated under pressure.
8. Run Scenarios in Parallel Safely
Parallel execution works only when accounts, files, queues, and records are isolated. Use unique users or reserve test identities through a concurrency-safe service. Never depend on scenario order. Split slow suites by tags only after measuring duration, and publish machine-readable output for CI. A failed parallel run should be reproducible locally with the scenario name and seed printed in logs.
Implementation review should cover ownership, isolation, observability, and the cost of change. Test the layer without its slowest dependency where possible, then retain a thin end-to-end check that proves integration. Record assumptions in code or schema documentation, validate configuration early, and make errors identify the scenario plus the failed operation. These practices keep this part of the design useful when the suite expands across contributors and CI workers.
A production-ready implementation also needs a negative path. Deliberately pass invalid configuration, unavailable dependencies, malformed data, and an unmet assertion through the layer. Confirm that cleanup still runs and that reports redact secrets. This failure-first exercise reveals coupling sooner than a green demonstration and gives reviewers concrete evidence that the framework can be operated under pressure.
9. Create Reports, Traces, and CI Feedback
Use Cucumber JSON, JUnit, or HTML formatters for scenario reporting. Screenshots belong to failed scenarios; traces and videos can be retained on failure to control storage. Report application version, browser, environment, commit, and retry attempt. In CI, fail on test failure, upload reports even after failure, and keep secrets masked. The reporting layer should answer what failed, where, with which data, and whether it is repeatable.
Implementation review should cover ownership, isolation, observability, and the cost of change. Test the layer without its slowest dependency where possible, then retain a thin end-to-end check that proves integration. Record assumptions in code or schema documentation, validate configuration early, and make errors identify the scenario plus the failed operation. These practices keep this part of the design useful when the suite expands across contributors and CI workers.
A production-ready implementation also needs a negative path. Deliberately pass invalid configuration, unavailable dependencies, malformed data, and an unmet assertion through the layer. Confirm that cleanup still runs and that reports redact secrets. This failure-first exercise reveals coupling sooner than a green demonstration and gives reviewers concrete evidence that the framework can be operated under pressure.
10. Evolve the Framework Through Review
Track flaky scenarios, runtime, failure categories, and unused step definitions. Review feature wording with product peers and automation design with engineers. Delete duplicate steps instead of adding aliases indefinitely. Treat shared steps as a public API. Version breaking wording changes deliberately, keep the smoke tag small, and use the ${commonLinks.bdd} when the team needs deeper Playwright fixture patterns.
Implementation review should cover ownership, isolation, observability, and the cost of change. Test the layer without its slowest dependency where possible, then retain a thin end-to-end check that proves integration. Record assumptions in code or schema documentation, validate configuration early, and make errors identify the scenario plus the failed operation. These practices keep this part of the design useful when the suite expands across contributors and CI workers.
A production-ready implementation also needs a negative path. Deliberately pass invalid configuration, unavailable dependencies, malformed data, and an unmet assertion through the layer. Confirm that cleanup still runs and that reports redact secrets. This failure-first exercise reveals coupling sooner than a green demonstration and gives reviewers concrete evidence that the framework can be operated under pressure.
11. How to build a BDD framework with Cucumber and Playwright at Scale
Scaling starts with operational constraints rather than more abstraction. Establish a review checklist for new scenarios, define who owns execution and source data, and publish a compatibility policy. New contributors should be able to run one test locally, inspect resolved configuration, and reproduce a CI failure from its recorded identity. Keep fast validation separate from browser execution so malformed inputs fail in seconds.
Create a change test for every extension point. When a command, helper, data field, selector contract, or reporting hook changes, prove compatible behavior and the intended new behavior. Deprecate old capabilities with a removal date instead of maintaining aliases forever. Sample production risks without copying sensitive production records. Capacity-test representative shards in a controlled environment, then set concurrency from evidence rather than available CPU alone.
Documentation should include one happy path, one rejected input, one application failure, and one cleanup failure. Those examples teach semantics more effectively than a directory diagram. Useful related reading includes Cucumber interview questions, Playwright locator strategy, and CI test automation guide. Together, these guides connect implementation choices to selector stability, data ownership, and delivery feedback.
Finally, treat maintenance as planned engineering. Review durations, recurring failure signatures, retry outcomes, and artifact usefulness. Remove tests whose risk has disappeared, consolidate duplicated intent, and refactor only where measurements or repeated changes justify it. A scalable framework is one whose feedback remains trustworthy as the product changes, not merely one that can enqueue more cases.
12. Release Checklist to build a BDD framework with Cucumber and Playwright
Before calling the first version complete, confirm that a clean checkout can install dependencies and execute a documented smoke command. Verify that configuration errors fail before expensive setup, every scenario can run alone, teardown runs after setup or assertion failures, and artifacts use collision-free names. Run at least two cases concurrently and check that accounts, records, variables, browser state, and files do not leak between them.
Review the negative paths with a maintainer who did not write the implementation. The report should identify the business case, source data, failed operation, environment, and application build without exposing credentials. Confirm that code examples, schemas, keyword or helper documentation, and CI commands match the committed implementation. Assign owners for infrastructure, test data, quarantined cases, and vocabulary changes.
Finally, record the framework boundaries. State which product risks it covers, which test types belong elsewhere, and how contributors propose a new abstraction. Establish a small baseline for duration and repeatability so later changes can be compared honestly. This checklist does not guarantee a perfect architecture, but it proves the framework is installable, diagnosable, isolated, and ready to evolve through evidence.
Runnable setup
npm init -y
npm install -D typescript tsx @types/node @cucumber/cucumber playwright
npx playwright install chromium
Core example
// features/support/world.ts
import { World, IWorldOptions, setWorldConstructor } from '@cucumber/cucumber';
import { Browser, BrowserContext, Page } from 'playwright';
export class TestWorld extends World {
browser!: Browser; context!: BrowserContext; page!: Page;
constructor(options: IWorldOptions) { super(options); }
}
setWorldConstructor(TestWorld);
// features/support/hooks.ts
import { After, Before, Status } from '@cucumber/cucumber';
import { chromium } from 'playwright';
import { TestWorld } from './world';
Before(async function (this: TestWorld) {
this.browser = await chromium.launch({ headless: true });
this.context = await this.browser.newContext();
this.page = await this.context.newPage();
});
After(async function (this: TestWorld, scenario) {
if (scenario.result?.status === Status.FAILED) {
await this.attach(await this.page.screenshot(), 'image/png');
}
await this.context?.close();
await this.browser?.close();
});
Example specification or dataset
Feature: Account sign in
Scenario: Valid customer signs in
Given the customer is on the sign in page
When the customer signs in with valid credentials
Then the account dashboard is displayed
Interview Questions and Answers
Q: What problem does this framework solve?
It creates repeatable, isolated tests while separating business intent from tool mechanics. For build a BDD framework with Cucumber and Playwright, I would first identify the contributors, risks, and feedback time we need. The architecture is justified only if it improves diagnosis and change cost.
Q: How do you prevent flaky tests?
I remove shared state, create deterministic data, use condition-based synchronization, and keep every test independently runnable. Retries are visible diagnostics, not a blanket fix. I classify recurring failures and give each flaky test an owner and removal deadline.
Q: How do you support parallel execution?
Each invocation owns its runtime context, application records, credentials, and artifact paths. I avoid static mutable state and order dependencies, then cap concurrency to the environment capacity. I verify the design with shuffled and repeated runs.
Q: Where should assertions live?
Assertions should remain close to the behavior that owns the expectation. Tool adapters return observations or expose stable query operations, while the scenario layer states the business result. This produces failure messages that explain impact instead of only implementation detail.
Q: How do you manage test data?
I model a minimal named dataset, provision records through supported APIs, and clean up deterministically. Secrets stay in an injected secret store and logs are sanitized. Generated cases retain a seed or exact failing example for reproduction.
Q: What belongs in CI reporting?
The report needs the scenario and case identity, commit, environment, tool version, attempt, duration, failure category, and relevant evidence. Artifacts should be uploaded even when tests fail. A developer must be able to reproduce the failure without guessing hidden inputs.
Q: How would you review framework quality?
I track runtime, retry rate, failure categories, maintenance changes, unused abstractions, and time to diagnose. I also review whether the suite covers current product risks. A large pass count is not evidence of useful coverage.
Common Mistakes
- Building abstractions before proving two or three real workflows.
- Sharing mutable drivers, accounts, records, or output files across tests.
- Hiding business assertions inside generic technical helpers.
- Using fixed sleeps instead of observable readiness conditions.
- Logging credentials or sensitive test data in reports.
- Adding retries without classifying and owning the original failure.
- Measuring success by test count rather than risk coverage and feedback quality.
Conclusion
To build a BDD framework with Cucumber and Playwright, begin with a narrow, real workflow and an explicit separation between intent, orchestration, data, and tool adapters. Make isolation and failure evidence part of the first implementation, not a later CI enhancement.
Run the example locally, add one high-risk negative case, and review the resulting failure with the people who will maintain it. If they can locate the cause quickly and change the behavior without editing unrelated layers, the framework has a sound foundation.
Interview Questions and Answers
What problem does this framework solve?
It creates repeatable, isolated tests while separating business intent from tool mechanics. For build a BDD framework with Cucumber and Playwright, I would first identify the contributors, risks, and feedback time we need. The architecture is justified only if it improves diagnosis and change cost.
How do you prevent flaky tests?
I remove shared state, create deterministic data, use condition-based synchronization, and keep every test independently runnable. Retries are visible diagnostics, not a blanket fix. I classify recurring failures and give each flaky test an owner and removal deadline.
How do you support parallel execution?
Each invocation owns its runtime context, application records, credentials, and artifact paths. I avoid static mutable state and order dependencies, then cap concurrency to the environment capacity. I verify the design with shuffled and repeated runs.
Where should assertions live?
Assertions should remain close to the behavior that owns the expectation. Tool adapters return observations or expose stable query operations, while the scenario layer states the business result. This produces failure messages that explain impact instead of only implementation detail.
How do you manage test data?
I model a minimal named dataset, provision records through supported APIs, and clean up deterministically. Secrets stay in an injected secret store and logs are sanitized. Generated cases retain a seed or exact failing example for reproduction.
What belongs in CI reporting?
The report needs the scenario and case identity, commit, environment, tool version, attempt, duration, failure category, and relevant evidence. Artifacts should be uploaded even when tests fail. A developer must be able to reproduce the failure without guessing hidden inputs.
How would you review framework quality?
I track runtime, retry rate, failure categories, maintenance changes, unused abstractions, and time to diagnose. I also review whether the suite covers current product risks. A large pass count is not evidence of useful coverage.
Frequently Asked Questions
What should I automate first?
Automate one stable, high-risk workflow with clear expected outcomes. It should exercise the architecture without requiring every planned abstraction.
Should the framework use fixed waits?
No. Synchronize on an observable UI, API, event, or state condition. Fixed waits slow successful runs and still fail when the system takes longer.
How should secrets be handled?
Inject secrets from a CI secret manager or environment variables. Validate their presence early and redact them from command logs, screenshots where possible, and reports.
Can these tests run in parallel?
Yes, after browser state, accounts, records, files, variables, and artifacts are isolated per invocation. Concurrency should also respect the capacity of the test environment.
How much reuse is appropriate?
Extract reuse after repeated intent is visible. Prefer small domain-focused composition and avoid generic helpers that hide control flow or assertions.
What evidence should a failed test capture?
Capture the named case, failed operation, environment, application version, sanitized inputs, and the most relevant screenshot, trace, log, or request identifier.
How do I keep the suite maintainable?
Review flaky failures, runtime, obsolete coverage, unused abstractions, and changing product risks on a schedule. Give framework layers and quarantined tests explicit owners.
Related Guides
- How to Build a Playwright TypeScript framework from scratch (2026)
- How to Add CI to a test framework (2026)
- How to Add logging to a test framework (2026)
- How to Add parallel execution to a framework (2026)
- How to Add reporting to a test framework (2026)
- How to Build a Cypress framework from scratch (2026)