DEV Community

Cover image for Is Your BDD Framework Just a Fancy Way to Write Manual Test Cases in Gherkin?
Anand Pawar
Anand Pawar

Posted on • Originally published at Medium

Is Your BDD Framework Just a Fancy Way to Write Manual Test Cases in Gherkin?

Gherkin is not a test automation tool. It never was.

Yet here we are, five years into your SDET career, and you're staring at a feature file that reads like a step-by-step manual for a human tester. Given I log in with username "admin" and password "password123". When I click the "Submit" button. Then I see the text "Welcome" on the screen.

You've written two years of these. Your team calls it BDD. Your manager calls it "living documentation." And somewhere in the back of your mind, a quiet voice whispers: This is just a manual test case with extra steps.

That voice is right.

Let me say it plainly: if your Gherkin scenarios describe how the system works instead of what it should do, you are not doing BDD. You are writing manual test cases in a structured English format and calling it automation. The only thing you've automated is the illusion of progress.

The problem isn't Gherkin. The problem is how we use it.

Most teams adopt BDD because someone read a blog post about "collaboration" and "shared understanding." They install Cucumber or SpecFlow. They write feature files. They map steps to Selenium or Playwright code. And they call it a day.

But look closely at what happens next. The product owner never reads the feature files. The developer skims them once and goes back to writing code. The QA engineer — that's you — becomes the sole maintainer of a growing pile of Gherkin that nobody else touches.

You're not facilitating collaboration. You're translating manual test cases into a format that requires a compiler.

Here's the real test. Take any feature file from your project. Hand it to a developer who has never seen it. Ask them to implement the feature using only the Gherkin as a spec. If they can write production code from it, you have real BDD. If they ask you for clarification, you have documentation theater.

I've seen teams with hundreds of feature files. Beautifully formatted. Perfect indentation. Tags for every regression cycle. And not a single one of them could survive that test.

So what went wrong?

The mistake is subtle but fatal. We confused structure with abstraction. Gherkin gives us a structure: Given-When-Then. But structure alone doesn't create abstraction. Abstraction means hiding implementation details behind intent.

A good Gherkin scenario says: Given a user with an expired subscription. It does not say: Given I navigate to the login page, enter "test@example.com" in the email field, enter "password123" in the password field, click "Sign In", navigate to the billing page, and check that the subscription status is "expired".

The first version captures intent. The second version captures keystrokes.

When you write the second version, you've created a test that breaks every time the UI changes. You've also created a document that nobody can read without knowing the exact state of every page element. That's not living documentation. That's dead weight.

Here's the uncomfortable truth for you, the five-year SDET who suspects something is off.

You're probably the most technical person on your QA team. You can write Playwright selectors in your sleep. You understand async/await. You know when to use page.waitForSelector versus page.waitForLoadState. And yet, you're spending your days writing Gherkin that a junior manual tester could have written.

The reason is organizational inertia. Someone decided "we do BDD" three years ago. The framework is built. The CI pipeline expects feature files. Changing it would require admitting that the emperor has no clothes.

But here's what I want you to consider: you can fix this without throwing away the framework.

The solution is not to abandon Gherkin. The solution is to push the abstraction down one layer. Write your step definitions so thin they're almost empty. Move the real logic into page objects or service layers. Let your Gherkin describe what, and let your code describe how.

Here's what that looks like in practice. I'll use Playwright with TypeScript because that's what most teams are using today.

The wrong way:

// step-definitions/login.steps.ts
import { Given, When, Then } from '@cucumber/cucumber';
import { page } from '../world';

Given('I log in with username {string} and password {string}', async (username: string, password: string) => {
  await page.goto('https://example.com/login');
  await page.fill('#email', username);
  await page.fill('#password', password);
  await page.click('button[type="submit"]');
  await page.waitForURL('**/dashboard');
});

When('I click the "Submit" button', async () => {
  await page.click('button:has-text("Submit")');
});

Then('I see the text {string} on the screen', async (text: string) => {
  await page.waitForSelector(`text=${text}`);
});
Enter fullscreen mode Exit fullscreen mode

This is manual testing with a compiler. Every step is a UI interaction. Every scenario is a script. If the login page changes its URL or the button text changes, every feature file breaks.

The right way:

// step-definitions/login.steps.ts
import { Given, When, Then } from '@cucumber/cucumber';
import { loginPage } from '../pages/login-page';
import { dashboardPage } from '../pages/dashboard-page';

Given('I am logged in as a standard user', async () => {
  await loginPage.loginAs('standard_user');
});

When('I submit the form', async () => {
  await loginPage.submit();
});

Then('I should see the dashboard', async () => {
  await dashboardPage.isVisible();
});
Enter fullscreen mode Exit fullscreen mode
// pages/login-page.ts
import { Page } from '@playwright/test';

export class LoginPage {
  constructor(private page: Page) {}

  async loginAs(userType: string) {
    const credentials = this.getCredentialsFor(userType);
    await this.page.goto('/login');
    await this.page.getByLabel('Email').fill(credentials.email);
    await this.page.getByLabel('Password').fill(credentials.password);
  }

  async submit() {
    await this.page.getByRole('button', { name: 'Sign In' }).click();
    await this.page.waitForURL('/dashboard');
  }

  private getCredentialsFor(userType: string) {
    // Returns credentials from a fixture or config
    return { email: `${userType}@example.com`, password: 'test123' };
  }
}
Enter fullscreen mode Exit fullscreen mode

Notice the difference. The Gherkin now says what happens, not how. The step definition is three lines. The real logic lives in the page object, where it belongs.

When the login page changes, you update one page object. Not twenty feature files. When a new team member reads the feature file, they understand the business flow without knowing the DOM structure. When the product owner reviews the scenarios, they can actually follow along.

This is what BDD was supposed to be.

But here's the part nobody tells you. Even with perfect abstraction, BDD has a cost. You're maintaining three layers of code: feature files, step definitions, and page objects. Each layer is a point of failure. Each layer requires synchronization.

For a team of five, that overhead might be worth it. For a team of two, it's probably not. For a solo QA engineer maintaining a legacy suite, it's a trap.

So what should you do?

Start by auditing your feature files. Pick five at random. Count how many steps describe UI interactions versus business intent. If more than half are UI interactions, you have a problem.

Then, refactor one scenario. Move the implementation details into a page object. Rewrite the Gherkin to describe intent. Run the tests. See if they still pass. See if they're easier to read.

If they are, you have your answer. The framework wasn't the problem. The abstraction was.

What this teaches us.

BDD is a communication tool, not a test automation tool. When we treat it like automation, we get the worst of both worlds: verbose tests that nobody reads and brittle scenarios that break constantly.

The teams that succeed with BDD are the ones who treat the feature file as a contract, not a script. They write scenarios that a non-technical stakeholder could review. They keep step definitions thin. They invest in page objects and service layers.

And they're honest about the tradeoff. BDD adds structure, but it also adds ceremony. For some projects, that ceremony is worth it. For others, a well-structured Playwright test with descriptive test names and good assertions is more valuable than a hundred feature files.

Here's my challenge to you.

Take one feature file from your current project. Rewrite it so that a product manager could read it and nod along. Then rewrite the step definitions so that a developer could change the UI without touching the Gherkin.

If you can do that, you're not writing manual test cases in Gherkin anymore. You're doing BDD.

If you can't, you know what you're really maintaining.

And that's okay. The first step is admitting you have a problem. The second step is deciding what to do about it.

What's one scenario in your current suite that you know is just a manual test in disguise?

Top comments (0)