What Is BDD (and Why Most Teams Get It Wrong)
Cucumber-JVM has shipped a major version roughly every 18 months for the last decade. Most teams still run scenarios that look exactly like the ones they wrote in 2017 — brittle, UI-heavy, and owned exclusively by QA. There's a reason for that, and it's not laziness. It's a foundational misread of what BDD actually is.
The technical problem is straightforward: teams adopted the tooling — Gherkin files, step definitions, a Cucumber runner — without adopting the practice. The result is a slow, fragile end-to-end suite dressed in natural language, with none of the specification-by-example benefits that justify the overhead. The Gherkin becomes a test script, not a shared contract.
By the end of this article you'll be able to distinguish BDD-as-collaboration from BDD-as-test-runner, identify where the practice breaks down in real codebases, and apply concrete corrections. This matters now because AI-assisted spec generation (via ChatGPT, Claude, or Cursor) is pushing more teams toward Gherkin at scale — amplifying every existing misuse pattern.
Hands-on courses in Python, BDD, AI-powered testing, APIs, and CI/CD automation.
BDD Is a Collaboration Protocol, Not a Test Layer
Behaviour-Driven Development, as Dan North defined it, is a discovery and communication technique: structured conversations between a developer, a tester, and a business stakeholder that produce concrete examples before a line of code is written. The output of that conversation — a Gherkin scenario — is a specification first and an executable test second. Reversing that priority is where most implementations go wrong.
In a modern test architecture, BDD scenarios belong at the acceptance layer: they verify that a system behaves according to agreed business rules, not that a button renders at a specific pixel offset. They sit above unit tests and integration contracts (Pact), and below performance baselines (k6). SpecFlow, Behave, Cucumber-JVM 7, and Cypress 13's experimental Cucumber integration all occupy this same layer — the distinction between them is runtime and ecosystem, not philosophy. Choosing Playwright over Selenium 4 as the driver underneath your step definitions is an implementation detail; the collaboration ritual is the point.
Writing Scenarios That Actually Drive Design
A well-formed scenario specifies intent, not procedure. Compare these two takes on the same feature:
# ❌ Procedural — describes UI clicks, not business behaviour
Scenario: User logs in
Given I navigate to "/login"
And I fill in "#email" with "user@example.com"
And I fill in "#password" with "secret"
When I click "#submit-btn"
Then I should see ".dashboard-header"
# ✅ Intent-first — specifies the business rule
Scenario: Authenticated users reach their dashboard
Given a registered user with valid credentials
When the user authenticates
Then the user lands on their personalised dashboard
The second scenario survives a full UI redesign. Its step definitions can be backed by a direct API call in most environments, dropping suite runtime significantly — one platform team at a mid-size SaaS shop moved 60% of their "login flow" scenarios off the browser layer entirely after this rewrite, cutting that suite from 18 minutes to 4. The Gherkin didn't change; the step implementation did.
In Python with Behave, that intent-first step looks like this:
# features/steps/auth_steps.py (Behave)
from behave import given, when, then
import httpx
@given("a registered user with valid credentials")
def step_registered_user(context):
context.credentials = {"email": "user@example.com", "password": "s3cr3t"}
@when("the user authenticates")
def step_authenticate(context):
r = httpx.post(f"{context.base_url}/api/auth/token", json=context.credentials)
assert r.status_code == 200, r.text
context.token = r.json()["access_token"]
@then("the user lands on their personalised dashboard")
def step_dashboard(context):
r = httpx.get(
f"{context.base_url}/api/me/dashboard",
headers={"Authorization": f"Bearer {context.token}"},
)
assert r.status_code == 200
assert "widgets" in r.json()
When a scenario genuinely requires browser rendering — a React hydration bug, a CSS-driven interaction — reach for Playwright (faster, built-in auto-wait, first-class TypeScript) over Selenium 4. Use Selenium 4 when you need cross-browser grid coverage across legacy IE-equivalent targets or when your org already runs a Selenium Grid and the migration cost isn't justified. In GitHub Actions, tag browser-dependent scenarios and run them on a separate job to keep the fast API-backed suite on the critical path:
# .github/workflows/bdd.yml
jobs:
acceptance-api:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pip install behave httpx
- run: behave --tags="~@browser"
acceptance-browser:
runs-on: ubuntu-latest
needs: acceptance-api
steps:
- uses: actions/checkout@v4
- run: pip install behave playwright
- run: playwright install chromium --with-deps
- run: behave --tags="@browser"
This structure makes the cost of each scenario visible in CI. If acceptance-browser creeps past 8 minutes, that's a signal to audit tag assignments — not to add more runners.
Where Senior Engineers Still Burn Time
The most common mistake at the senior level is treating the feature file as the source of truth for test coverage. Scenario count becomes a proxy metric, so engineers add scenarios to satisfy a dashboard rather than to capture a discovered behaviour. The org-level cause is a reporting culture that equates "more Gherkin" with "more quality." The fix is tracking scenario-to-defect correlation: if a scenario never catches a regression, it's either redundant or misplaced at the wrong layer.
A second persistent mistake is coupling step definitions to a single driver — usually a Playwright or Selenium session — without an abstraction boundary. When the team needs to run the same scenario against a REST API in staging and a browser in production smoke tests, the step file becomes a conditional mess. A thin driver interface (a Python protocol or TypeScript interface) that each context implements keeps step definitions readable and the driver swap invisible to the Gherkin layer. This is not over-engineering; it's the same boundary you'd draw in any hexagonal architecture.
Three Myths That Keep BDD Suites Slow and Fragile
Myth 1: Gherkin is a test scripting language. It's a specification language. The moment a scenario describes how to operate the UI rather than what the system should do, it's a test script in a trenchcoat. Myth 2: BDD replaces unit tests. It doesn't. BDD scenarios validate business rules; unit tests validate logic contracts. Treating BDD as the primary safety net produces suites that are slow to run and slow to fail — the worst combination. The test pyramid isn't scripture, but its underlying principle — fast feedback at the lowest meaningful layer — is still correct. Myth 3: Non-technical stakeholders will write or maintain Gherkin. In practice, product managers contribute during the Three Amigos session and then step back. The engineering team owns the files. Designing a BDD process around the fantasy of PM-authored scenarios leads to over-engineered step libraries and under-specified business rules.
The corrective framing: BDD is a pre-code ritual that produces executable specifications as a by-product. The Three Amigos meeting — developer, tester, product — happens before sprint work begins, not after. The Gherkin written in that session drives implementation. If your team writes scenarios after the feature ships, you're writing regression tests in Gherkin syntax, which is a valid choice but not BDD. Name it accurately and you'll stop expecting the wrong benefits from it.
BDD done correctly reduces ambiguity before code is written, produces a living specification that survives refactors, and keeps acceptance suites fast by pushing scenarios to the lowest viable layer. The next concrete step: audit your current feature files for procedural step language and count how many scenarios could run against the API instead of the browser. That ratio is a more honest signal of BDD health than scenario count or pass rate.
Note: This article is for informational purposes only and is not a substitute for professional advice. If you need guidance on specific situations described in this article, consider consulting a qualified professional.