Uncategorized

Playwright vs SpecFlow: BDD Testing Framework Meets Browser Automation

Playwright vs SpecFlow banner

Testing teams often ask whether they should pick Playwright or SpecFlow. The question itself misses something important: these tools solve different problems and work best when paired together.

SpecFlow writes tests in plain English. Playwright clicks buttons and fills forms. One describes what should happen. They solve different problems and work best when paired together.

This post breaks down what each tool does, how they fit together, and the major changes coming to SpecFlow  .NET testing team needs to know about.

What Does SpecFlow Actually Do?

SpecFlow is a Behavior-Driven Development (BDD) framework for .NET applications. Gáspár Nagy created it in 2009 as an open-source project to bring Cucumber-style testing to the Microsoft ecosystem.

At its core, SpecFlow translates human-readable test scenarios into executable code. Product owners write requirements in plain English. Developers wire those requirements to automation code. Testers run the scenarios and generate living documentation.

The scenarios follow Gherkin syntax:

gherkin
Feature: User Login
 
  Scenario: Valid credentials grant access
    Given I am on the login page
    When I enter username "testuser" and password "secret123"
    And I click the login button
    Then I should see the dashboard

This scenario reads like a conversation. Anyone on the team, technical or not, understands what the test validates. SpecFlow parses this file and looks for matching step definitions in C# code.

Here's the catch: SpecFlow cannot click that login button. It cannot type in those form fields. It cannot navigate to any page at all.

SpecFlow needs a browser automation tool underneath it. That's where Playwright enters the picture.

What Does Playwright Actually Do?

Playwright is a browser automation library from Microsoft released in January 2020. It controls Chromium, Firefox, and WebKit through a single API.

When you write Playwright code, you're telling a real browser what to do:

csharp
await page.GotoAsync("https://example.com/login");
await page.FillAsync("#username", "testuser");
await page.FillAsync("#password", "secret123");
await page.ClickAsync("#login-button");
await Expect(page.Locator(".dashboard")).ToBeVisibleAsync();

This code launches a browser, navigates to a URL, types text, clicks elements, and checks results. Playwright handles the messy parts, waiting for elements to load, dealing with network requests, and managing browser contexts.

Playwright works fine on its own for technical teams. Write tests in C#, TypeScript, Python, or Java. Run them with the built-in test runner. Get reports and traces automatically.

But Playwright tests are code. Non-technical stakeholders cannot read them without learning programming syntax.

Why Teams Combine SpecFlow and Playwright

The combination gives you readable test scenarios backed by reliable browser automation.

Product owners contribute to testing without writing code. They review feature files, suggest new scenarios, and approve changes before they ship. The gap between business requirements and technical implementation shrinks.

QA engineers write step definitions once and reuse them across dozens of scenarios. The step "When I click the login button" maps to one Playwright method. Every scenario using that step shares the same automation code.

Developers debug failures faster. When a test fails, the scenario name tells them exactly which user flow broke. The step that failed points to the specific action. Playwright's trace viewer shows what the browser saw at that moment.

The architecture looks like this:

Feature File (Gherkin) 

    ↓

Step Definitions (C#)

    ↓

Page Objects (C#)

    ↓

Playwright API

    ↓

Browser Engine

Each layer has a single job. Feature files describe behavior. Step definitions translate words to actions. Page objects organize element selectors. The playwright talks to browsers.

Major News: SpecFlow End-of-Life

Tricentis acquired SpecFlow in 2020 and announced its end-of-life in December 2024. The project officially shut down on December 31, 2024. GitHub repositories are deleted. Support documentation is disabled.

This news caught many teams off guard. The last stable SpecFlow release (version 3.9) shipped in May 2022. No updates followed for over two years despite growing demand for .NET 8 support.

Your existing SpecFlow projects will not suddenly stop working. NuGet packages remain available for download. Tests will keep running. But no bug fixes, no security patches, and no new .NET version support will arrive.

Teams maintaining SpecFlow projects face a choice: stay on aging infrastructure or migrate to the successor project.

Reqnroll: The SpecFlow Successor

Gáspár Nagy, the original SpecFlow creator, forked the codebase in early 2024 and rebooted it as Reqnroll (pronounced "wreck-n-roll").

Reqnroll maintains full compatibility with SpecFlow v4. The migration path is straightforward:

  1. Replace NuGet packages (SpecFlow.* becomes Reqnroll.*)

  2. Update namespace imports (TechTalk.SpecFlow becomes Reqnroll)

  3. Run tests

A compatibility package exists for teams who want to delay the namespace changes. Install Reqnroll.SpecFlowCompatibility and your existing code run without modifications.

Reqnroll already supports .NET 8 and .NET 9. Development continues actively with scenario-level parallelization and other improvements landing regularly. Over 5,000 projects migrated within the first year.

The Visual Studio extension works with both SpecFlow and Reqnroll projects. JetBrains Rider has full support through dedicated plugins.

Setting Up Playwright with SpecFlow (or Reqnroll)

Here's how to wire these tools together in a .NET project.

Project Structure

MyTestProject/

Features/

Login.feature

StepDefinitions/

LoginSteps.cs

Pages/

LoginPage.cs

Hooks/

BrowserHooks.cs

MyTestProject.csproj

NuGet Packages

For SpecFlow (legacy):

xml

For Reqnroll (recommended):

xml

Browser Lifecycle Hooks

The hooks file manages browser setup and teardown:

csharp
using Microsoft.Playwright;
using Reqnroll;

[Binding]
public class BrowserHooks
{
    private static IPlaywright? playwright;
    private static IBrowser?
browser;
    private IBrowserContext? context;
    private IPage?
page;
   
    private readonly ScenarioContext scenarioContext;
   
    public BrowserHooks(ScenarioContext scenarioContext)
    {
       
scenarioContext = scenarioContext;
    }
   
    [BeforeTestRun]
    public static async Task BeforeTestRun()
    {
        playwright = await Playwright.CreateAsync();
       
browser = await playwright.Chromium.LaunchAsync(new()
        {
            Headless = true
        });
    }
   
    [BeforeScenario]
    public async Task BeforeScenario()
    {
       
context = await browser!.NewContextAsync();
       
page = await context.NewPageAsync();
       
scenarioContext["Page"] = page;
    }
   
    [AfterScenario]
    public async Task AfterScenario()
    {
        if (
page != null) await page.CloseAsync();
        if (
context != null) await context.CloseAsync();
    }
   
    [AfterTestRun]
    public static async Task AfterTestRun()
    {
        if (
browser != null) await browser.CloseAsync();
        if (
playwright != null) _playwright.Dispose();
    }
}

This setup launches one browser for the entire test run. Each scenario gets a fresh browser context, isolated cookies, storage, and state. Tests run fast without browser startup overhead on every scenario.

Page Object Model

Page objects keep element selectors organized:

csharp
using Microsoft.Playwright;

public class LoginPage
{
    private readonly IPage page;
   
    public LoginPage(IPage page)
    {
       
page = page;
    }
   
    private ILocator UsernameField => page.Locator("#username");
    private ILocator PasswordField =>
page.Locator("#password");
    private ILocator LoginButton => page.Locator("#login-button");
    private ILocator ErrorMessage =>
page.Locator(".error-message");
   
    public async Task NavigateAsync()
    {
        await _page.GotoAsync("https://example.com/login");
    }
   
    public async Task EnterCredentialsAsync(string username, string password)
    {
        await UsernameField.FillAsync(username);
        await PasswordField.FillAsync(password);
    }
   
    public async Task ClickLoginAsync()
    {
        await LoginButton.ClickAsync();
    }
   
    public async Task HasErrorMessageAsync()
    {
        return await ErrorMessage.IsVisibleAsync();
    }
}

When selectors change, you update one file. Every step definition using this page object automatically picks up the fix.

Step Definitions

Step definitions bridge Gherkin to Playwright:

csharp
using Microsoft.Playwright;
using Reqnroll;

[Binding]
public class LoginSteps
{
    private readonly ScenarioContext scenarioContext;
    private IPage Page => (IPage)
scenarioContext["Page"];
    private LoginPage? loginPage;
   
    public LoginSteps(ScenarioContext scenarioContext)
    {
       
scenarioContext = scenarioContext;
    }
   
    [Given(@"I am on the login page")]
    public async Task GivenIAmOnTheLoginPage()
    {
        loginPage = new LoginPage(Page);
        await
loginPage.NavigateAsync();
    }
   
    [When(@"I enter username ""(.*)"" and password ""(.*)""")]
    public async Task WhenIEnterCredentials(string username, string password)
    {
        await loginPage!.EnterCredentialsAsync(username, password);
    }
   
    [When(@"I click the login button")]
    public async Task WhenIClickTheLoginButton()
    {
        await
loginPage!.ClickLoginAsync();
    }
   
    [Then(@"I should see the dashboard")]
    public async Task ThenIShouldSeeTheDashboard()
    {
        await Expect(Page.Locator(".dashboard")).ToBeVisibleAsync();
    }
   
    [Then(@"I should see an error message")]
    public async Task ThenIShouldSeeAnErrorMessage()
    {
        var hasError = await _loginPage!.HasErrorMessageAsync();
        Assert.That(hasError, Is.True);
    }
}

Notice how the step text matches the feature file exactly. SpecFlow/Reqnroll uses regular expressions to extract parameters like username and password.

When to Skip SpecFlow and Use Playwright Alone

Not every project benefits from BDD tooling. The overhead of maintaining feature files, step definitions, and bindings adds cost. That cost should deliver value.

Skip SpecFlow when:

1. Your team is entirely technical. Developers and SDETs who read code fluently gain little from Gherkin translation layers. Playwright's native test runner offers faster feedback loops.

2. Test scenarios are simple. A login test with three steps doesn't need elaborate abstraction. Write Playwright code directly and move on.

3. Iteration speed matters most. Early-stage products change constantly. Maintaining step definition mappings slows down rapid experimentation.

4. You're building utility automation. Screenshots, PDF generation, and web scraping, these tasks don't need BDD structure.

When SpecFlow (or Reqnroll) Adds Clear Value

Add BDD tooling when:

1. Non-technical stakeholders review tests. Product managers, business analysts, and compliance teams can read feature files. They detect requirement gaps before code ships.

2. Regulatory requirements demand documentation. Healthcare, finance, and government projects often need audit trails showing what was tested. Living documentation generated from feature files satisfies these requirements.

3. You're in a .NET shop with Visual Studio. The tooling integration is deep. Syntax highlighting, step navigation, and generation work smoothly.

4. Step reuse across many scenarios saves time. Common actions like "Given I am logged in as an admin" appear in hundreds of scenarios. Write the step once, reference it everywhere.

5. Your team practices Behavior-Driven Development. BDD is a collaboration practice, not just a testing format. If your team holds Three sessions and writes specifications by example, SpecFlow/Reqnroll fits naturally.

Comparing the Two Approaches

Here's how the two setups stack up across the factors that matter most to testing teams.

Aspect

Playwright Alone

Playwright + SpecFlow / Reqnroll

Test format

C# code files

Gherkin feature files + C# step definitions

Readable by non-devs

No

Yes

Setup complexity

Lower

Higher

Maintenance overhead

Lower

Higher

Step reuse

Manual (methods)

Built-in via step definitions

Living documentation

Via Playwright reporters

Native LivingDoc support

IDE support

Visual Studio, VS Code, Rider

Best in Visual Studio; works in Rider

Learning curve

Playwright API only

Playwright API + Gherkin + SpecFlow bindings

Best for

Technical teams, utility automation

Cross-functional teams, regulated industries

The right choice depends on who needs to read your tests and how often requirements change through collaboration.

Common Mistakes Teams Make

Writing too many scenario steps. Long scenarios with 15+ steps become fragile. Each step is a potential failure point. Keep scenarios focused on one behavior.

Duplicating automation logic in step definitions. Step definitions should call page objects, not contain raw Playwright code. Duplication leads to maintenance nightmares.

Ignoring scenario outlines for data variations. Testing the same flow with different inputs? Use scenario outlines with example tables instead of copying entire scenarios.

gherkin
Scenario Outline: Login with various credentials
  Given I am on the login page
  When I enter username "" and password ""
  And I click the login button
  Then I should see ""
 
Examples:
  | username | password  | expected_result |
  | valid    | correct   | dashboard       |
  | valid    | wrong     | error message   |
  | invalid  | correct   | error message   |

Skipping browser context isolation. Sharing state between scenarios causes flaky tests. Each scenario should start fresh.

Not handling async properly. Playwright operations return tasks. Missing await keywords causes race conditions and unpredictable failures.

Running Tests in CI/CD Pipelines

Both tools integrate smoothly with modern CI systems. Here's a GitHub Actions example:

yaml
name: BDD Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
     
      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '8.0.x'
         
      - name: Install Playwright browsers
        run: pwsh bin/Debug/net8.0/playwright.ps1 install --with-deps
       
      - name: Run tests
        run: dotnet test --logger "trx;LogFileName=results.trx"
       
      - name: Publish results
        uses: actions/upload-artifact@v4
        with:
          name: test-results
          path: '**/results.trx'

For Azure DevOps, similar steps apply with the appropriate task syntax. The key points: install Playwright browsers before running tests, capture results for reporting, and run headless in CI environments.

What Comes Next

The testing landscape keeps shifting. AI-assisted test generation promises to reduce the manual effort of writing both Gherkin scenarios and step definitions. Visual test builders may lower the technical barrier further.

For now, Playwright and Reqnroll form a solid foundation for .NET teams practicing BDD. Playwright handles browser automation reliably. Reqnroll provides the collaboration layer that brings non-technical team members into the testing process.

Pick the approach that matches your team's composition and project needs. If business stakeholders need test visibility, add the BDD layer. If your team is technical and moves fast, Playwright alone may serve you better.

Either way, your tests should run fast, fail clearly, and detect bugs before users do.

Frequently Asked Questions

Is SpecFlow dead?

SpecFlow reached end-of-life on December 31, 2024. Tricentis deleted the GitHub repositories and disabled support. Existing packages remain on NuGet but receive no updates. Teams should migrate to Reqnroll for continued support and .NET 8/9 compatibility.

Can I use Playwright without SpecFlow?

Absolutely. Playwright includes its own test runner with assertions, parallelization, and reporting. Many teams use Playwright directly without BDD layers. Choose based on whether non-technical stakeholders need to read your tests.

Does Reqnroll work with Playwright?

Yes. Reqnroll is fully compatible with Playwright through the same patterns used with SpecFlow. The browser hooks, page objects, and step definitions shown in this guide work identically with Reqnroll packages.

Should I migrate from SpecFlow to Reqnroll?

Yes. SpecFlow receives no further updates. Reqnroll provides .NET 8/9 support, active development, and a clear migration path. Most projects migrate in under an hour.

Can SpecFlow run tests in parallel?

SpecFlow supports parallel execution at the feature level. Reqnroll adds scenario-level parallelization for finer control. Both require proper test isolation to avoid shared state conflicts.

What's the difference between SpecFlow and Cucumber?

Cucumber is the original BDD framework for Java and Ruby. SpecFlow brought the same Gherkin-based approach to .NET. They share syntax and concepts but target different language ecosystems.

Share this post

Experience AI-Powered Testing in Action with Supatest AI

Loading...