SpecFlow C# Test Automation Guide (2026)
The 2026 SpecFlow C# tutorial — set up SpecFlow + .NET 8 + NUnit + Selenium, write production Gherkin feature files, C# step definitions, table transformations, hooks, LivingDoc and CI/CD.

In this article
- 1. What Is SpecFlow?
- 2. SpecFlow vs Cucumber
- 3. Install SpecFlow Step by Step
- 4. Your First Feature File
- 5. Step Definitions in C#
- 6. Scenario Outlines and Examples
- 7. Data Tables and Table Transformations
- 8. Hooks and Step Argument Transformations
- 9. SpecFlow + Selenium Integration
- 10. Living Documentation with SpecFlow+
- 11. CI/CD Integration Patterns
- 12. SpecFlow Best Practices
- 13. Author Bio & Next Steps
- Frequently asked questions
What you'll master: a working SpecFlow + .NET 8 + NUnit + Selenium setup, production-quality Gherkin feature files bound to C# step definitions, table transformations for clean data binding, hooks for setup/teardown, and a CI/CD-ready pipeline.
1. What Is SpecFlow?
SpecFlow is the .NET port of Cucumber — it brings Behavior-Driven Development (BDD) to the Microsoft ecosystem: C#, .NET, Visual Studio and Azure DevOps. After Tricentis acquired SpecFlow in 2020 the project has continued to ship, and SpecFlow 3.9+ supports .NET 6, 7, 8 and 9.
SpecFlow remains the default BDD choice for .NET shops because of:
- Native .NET integration with NUnit, xUnit and MSTest
- Visual Studio + ReSharper / Rider first-class IDE experience with Gherkin IntelliSense and live step linking
- Living documentation via SpecFlow+ LivingDoc
- Azure DevOps, MSBuild and TFS integration out of the box
For broader BDD context, pair this guide with our BDD Cucumber tutorial and the dedicated SpecFlow interview questions hub.
🚀 Skip the boilerplate. Rehearse SpecFlow rounds live with our AI mock interview and review Selenium interview questions alongside the BDD prep.
2. SpecFlow vs Cucumber
| Dimension | SpecFlow | Cucumber-JVM |
|---|---|---|
| Language | C# / VB.NET | Java |
| Test runner | NUnit / xUnit / MSTest | JUnit / TestNG |
| IDE support | Visual Studio + ReSharper | IntelliJ + Eclipse |
| Living docs | SpecFlow+ LivingDoc | ClueCumber, Cucumber Reports |
| Ecosystem | Microsoft / Azure DevOps | Java / Maven / Gradle |
| Best for | .NET shops | Java shops |
Both frameworks use the same Gherkin syntax, so scenarios port between them with minimal change. For a Java + Cucumber alternative, see our Java for Selenium Automation guide.
3. Install SpecFlow Step by Step
Prerequisites
- .NET 8 SDK (recommended) or .NET 9 SDK
- Visual Studio 2022 or JetBrains Rider
- SpecFlow extension for Visual Studio (free, from the Marketplace)
Step 1 — Create a project
dotnet new nunit -n SpecFlowDemo
cd SpecFlowDemoStep 2 — Add SpecFlow packages
dotnet add package SpecFlow
dotnet add package SpecFlow.NUnit
dotnet add package SpecFlow.Tools.MsBuild.Generation
# Selenium integration
dotnet add package Selenium.WebDriver
dotnet add package Selenium.SupportStep 3 — Configure SpecFlow in .csproj
<ItemGroup>
<PackageReference Include="SpecFlow" Version="3.9.7" />
<PackageReference Include="SpecFlow.NUnit" Version="3.9.7" />
<PackageReference Include="SpecFlow.Tools.MsBuild.Generation" Version="3.9.7">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>Step 4 — Verify the install
dotnet build
dotnet testProject structure
SpecFlowDemo/
├── Features/
│ └── Login.feature
├── StepDefinitions/
│ └── LoginSteps.cs
├── Pages/
│ ├── BasePage.cs
│ └── LoginPage.cs
├── Hooks/
│ └── ScenarioHooks.cs
├── Drivers/
│ └── DriverFactory.cs
└── SpecFlowDemo.csproj4. Your First Feature File
Create Features/Login.feature:
Feature: User login
As a registered user
I want to log in to the application
So that I can access my account
Background:
Given the application is running
Scenario: Successful login with valid credentials
Given I am on the login page
When I enter "admin@example.com" as the email
And I enter "Sup3rSecret!" as the password
And I click the "Sign in" button
Then I should be on the dashboard page
And I should see "Welcome, admin"
Scenario: Failed login with invalid password
Given I am on the login page
When I enter "admin@example.com" as the email
And I enter "wrong-password" as the password
And I click the "Sign in" button
Then I should see an error message "Invalid credentials"In Visual Studio, right-click the .feature file → Generate Step Definitions. SpecFlow scaffolds a stub class for every step.
5. Step Definitions in C#
Create StepDefinitions/LoginSteps.cs:
using NUnit.Framework;
using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using SeleniumExtras.WaitHelpers;
using TechTalk.SpecFlow;
using SpecFlowDemo.Pages;
namespace SpecFlowDemo.StepDefinitions
{
[Binding]
public class LoginSteps
{
private readonly ScenarioContext _scenarioContext;
private readonly IWebDriver _driver;
private readonly LoginPage _loginPage;
public LoginSteps(ScenarioContext scenarioContext)
{
_scenarioContext = scenarioContext;
_driver = (IWebDriver)_scenarioContext["driver"];
_loginPage = new LoginPage(_driver);
}
[Given(@"I am on the login page")]
public void GivenIAmOnTheLoginPage() => _loginPage.Navigate();
[When(@"I enter ""([^""]*)"" as the email")]
public void WhenIEnterEmail(string email) => _loginPage.EnterEmail(email);
[When(@"I enter ""([^""]*)"" as the password")]
public void WhenIEnterPassword(string password) => _loginPage.EnterPassword(password);
[When(@"I click the ""([^""]*)"" button")]
public void WhenIClickButton(string label) =>
_driver.FindElement(By.CssSelector("button[type='submit']")).Click();
[Then(@"I should be on the dashboard page")]
public void ThenIShouldBeOnTheDashboardPage()
{
var wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(10));
wait.Until(ExpectedConditions.UrlContains("/dashboard"));
Assert.That(_driver.Url, Does.Contain("/dashboard"));
}
[Then(@"I should see ""([^""]*)""")]
public void ThenIShouldSee(string text) =>
Assert.That(_driver.PageSource, Does.Contain(text));
}
}The [Binding] attribute tells SpecFlow this class contains step definitions. Each method has a [Given], [When] or [Then] attribute with a regex that matches the Gherkin step.
6. Scenario Outlines and Examples
Run the same scenario against multiple datasets:
Scenario Outline: Login with multiple users
Given I am on the login page
When I enter "<email>" as the email
And I enter "<password>" as the password
And I click the "Sign in" button
Then I should see "<greeting>"
Examples:
| email | password | greeting |
| admin@example.com | Sup3rSecret! | Welcome, admin |
| viewer@example.com | ViewerPass1! | Welcome, viewer |
| guest@example.com | GuestPass1! | Welcome, guest |SpecFlow runs this scenario once per row. Step definitions are identical to a regular scenario — SpecFlow substitutes the placeholders automatically.
7. Data Tables and Table Transformations
Basic DataTable usage
Scenario: Apply multiple promo codes
Given I am on the cart page
When I apply the following promo codes:
| code | expected_discount |
| WELCOME10 | 10% |
| SAVE20 | 20% |
| VIP30 | 30% |
Then I should see the total discount applied[When(@"I apply the following promo codes:")]
public void WhenIApplyPromoCodes(Table table)
{
foreach (var row in table.Rows)
{
var code = row["code"];
var expectedDiscount = row["expected_discount"];
// Apply code, verify expected_discount
}
}Table transformations (the killer feature)
public class PromoCode
{
public string Code { get; set; }
public string ExpectedDiscount { get; set; }
}
[Binding]
public class PromoCodeTransforms
{
[StepArgumentTransformation]
public IEnumerable<PromoCode> TransformPromoCodes(Table table) =>
table.CreateSet<PromoCode>();
}
[When(@"I apply the following promo codes:")]
public void WhenIApplyPromoCodes(IEnumerable<PromoCode> promoCodes)
{
foreach (var promo in promoCodes)
{
// Apply promo.Code, verify promo.ExpectedDiscount
}
}This eliminates the foreach (var row in table.Rows) boilerplate and gives you strongly-typed access.
Vertical data tables
Scenario: Verify user profile fields
Given I am on the user profile page
Then the user profile should show:
| field | value |
| name | Alice Johnson |
| email | alice@ex.com |
| role | admin |
| department | Engineering |8. Hooks and Step Argument Transformations
Hooks
[Binding]
public class ScenarioHooks
{
private readonly ScenarioContext _scenarioContext;
public ScenarioHooks(ScenarioContext scenarioContext)
=> _scenarioContext = scenarioContext;
[BeforeScenario]
public void BeforeScenario() =>
_scenarioContext["driver"] = new ChromeDriver();
[BeforeScenario("@database")]
public void BeforeDatabaseScenario() => DatabaseHelper.ResetDatabase();
[AfterScenario]
public void AfterScenario()
{
var driver = (IWebDriver)_scenarioContext["driver"];
driver?.Quit();
}
[BeforeTestRun]
public static void BeforeTestRun() { /* once before all tests */ }
[AfterTestRun]
public static void AfterTestRun() { /* once after all tests */ }
}Step argument transformations
[Binding]
public class Transforms
{
[StepArgumentTransformation(@"(\d+) days? ago")]
public DateTime DaysAgoTransform(int days) =>
DateTime.UtcNow.AddDays(-days);
[StepArgumentTransformation(@"user ""([^""]+)""")]
public User UserTransform(string email) =>
UserRepository.FindByEmail(email);
}Now your Gherkin can say:
Given the user "admin@example.com" registered 30 days agoAnd the step definition accepts User and DateTime directly — no manual parsing.
9. SpecFlow + Selenium Integration
Driver factory
namespace SpecFlowDemo.Drivers
{
public static class DriverFactory
{
public static IWebDriver CreateDriver(string browser) => browser.ToLower() switch
{
"chrome" => new ChromeDriver(),
"firefox" => new FirefoxDriver(),
"edge" => new EdgeDriver(),
_ => throw new ArgumentException($"Unknown browser: {browser}")
};
}
}Page object (POM adapted for SpecFlow)
namespace SpecFlowDemo.Pages
{
public class LoginPage
{
private readonly IWebDriver _driver;
public LoginPage(IWebDriver driver) => _driver = driver;
public void Navigate() =>
_driver.Navigate().GoToUrl("https://example.com/login");
public void EnterEmail(string email) =>
_driver.FindElement(By.Id("email")).SendKeys(email);
public void EnterPassword(string password) =>
_driver.FindElement(By.Id("password")).SendKeys(password);
public void ClickSignIn() =>
_driver.FindElement(By.Id("submit")).Click();
}
}For the full Selenium POM pattern, see our Selenium WebDriver guide and the Java for Selenium deep-dive.
10. Living Documentation with SpecFlow+
SpecFlow+ LivingDoc generates human-readable HTML documentation from your feature files — the BDD payoff. Scenarios become the executable specification of your system.
Setup
dotnet add package SpecFlow.Plus.LivingDocPlugin
dotnet add package SpecFlow.Plus.LivingDocGenerate the docs
livingdoc test-assembly SpecFlowDemo.dll -t TestExecution.jsonYou get an HTML site with a navigable tree of feature files, scenario results, pass/fail trends, execution times and tag filters.
Publish to Azure DevOps / GitHub Pages
# Azure DevOps
- task: CopyFiles@2
inputs:
SourceFolder: '$(Build.ArtifactStagingDirectory)/livingdoc'
TargetFolder: '$(Build.ArtifactStagingDirectory)/drop'
- task: PublishBuildArtifacts@1
inputs:
PathToPublish: '$(Build.ArtifactStagingDirectory)/drop'11. CI/CD Integration Patterns
GitHub Actions
name: SpecFlow Tests
on: [push, pull_request]
jobs:
test:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- run: dotnet restore
- run: dotnet build --no-restore
- run: dotnet test --no-build --logger "trx;LogFileName=test-results.trx"
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: test-results
path: TestResults/Azure DevOps
trigger:
- main
pool:
vmImage: 'windows-latest'
steps:
- task: DotNetCoreCLI@2
inputs:
command: 'test'
arguments: '--logger trx --collect:"XPlat Code Coverage"'
- task: PublishTestResults@2
inputs:
testRunner: 'VSTest'
testResultsFiles: '**/*.trx'Parallel execution
SpecFlow runs in parallel via NUnit's Parallelizable attribute:
[assembly: Parallelizable(ParallelScope.Fixtures)]Each feature file runs in parallel — use carefully, parallel features can't share state.
12. SpecFlow Best Practices
Do
- Use Visual Studio + ReSharper (or Rider) for the best BDD experience
- Generate LivingDoc for every release
- Use Page Object Model for clean step definitions
- Use table transformations for typed data binding
- Use Scenario Outlines for data-driven scenarios
- Use tags to organize by feature, priority and test type
- Use ScenarioContext sparingly — only for driver and minimal cross-step state
- Pair SpecFlow with NUnit for the richest test runner features
Don't
- Don't put business logic in step definitions — they should be thin glue
- Don't write imperative scenarios — use declarative language
- Don't use
ScenarioContext.Current(deprecated in SpecFlow 3) — use constructor injection - Don't share driver instances across scenarios without cleanup
- Don't run scenarios in parallel without isolation
- Don't skip the discovery workshop — BDD without collaboration is keyword-driven testing
Frequently asked questions
What is SpecFlow?
SpecFlow is the .NET port of Cucumber. It lets .NET developers write Gherkin feature files and bind them to C# step definitions — the most popular BDD framework for the Microsoft ecosystem.
SpecFlow vs Cucumber — which should I use?
Use SpecFlow if you're in a .NET shop. Use Cucumber if you're in a Java/JS/Python shop. Both use the same Gherkin syntax, so scenarios port between them with minimal effort.
Is SpecFlow free?
Yes — SpecFlow 3.x is free and open source. Tricentis offers commercial add-ons like SpecFlow+ LivingDoc and SpecFlow+ Excel, but the core framework is free.
Does SpecFlow work with .NET 8 and .NET 9?
Yes — SpecFlow 3.9+ supports .NET 6, 7, 8 and 9. Use the latest version for the best .NET 8/9 experience.
What's the difference between SpecFlow and Reqnroll?
Reqnroll is a community fork of SpecFlow that emerged in 2024 after some uncertainty about SpecFlow's roadmap. For greenfield projects, evaluate both. For existing SpecFlow projects, stay on SpecFlow unless there's a specific reason to migrate.
How do I generate step definition stubs?
In Visual Studio, right-click the .feature file → Generate Step Definitions. SpecFlow creates a stub class with all the steps as methods to implement.
What is SpecFlow+ LivingDoc?
A tool that generates human-readable HTML documentation from your feature files. It's the BDD payoff — scenarios become the executable specification of your system.
Can SpecFlow run tests in parallel?
Yes — via NUnit's [Parallelizable] attribute. Each feature file runs in parallel; be careful with shared state across scenarios.
How long does it take to learn SpecFlow?
For an experienced C# developer: 1–2 weeks to productive. For a developer new to BDD: 4–6 weeks including the Three Amigos workshop process.
Is SpecFlow still relevant?
Yes — SpecFlow 3.9+ is actively maintained and widely deployed in enterprise .NET shops. It remains the default BDD choice for .NET teams.
Practice these questions
Rehearse Selenium and Playwright automation questions covering framework design, waits, locators and CI/CD.
Was this article helpful?
Keep building your QA edge
Pillar guides- AI Mock Interviewpractice these questions with our AI mock interviewLive AI-powered mock interviews with rubric feedback.
- ATS Resume ReviewSoftwareTestPilot's ATS resume checkerFree AI ATS scoring with rewrite suggestions.
- QA Jobs RadarSoftwareTestPilot's QA jobs boardLive QA / SDET / automation job feed, refreshed daily.
Continue reading

Why Every QA Engineer Must Master CI/CD Pipelines in 2026 (Or Risk Obsolescence)
12 min read
Is Cypress Dead? Analyzing 2026 Playwright Market Share
12 min read
Why Tests Pass Locally But Fail in CI/CD (And the 6 Fixes That Actually Work in 2026)
13 min readJoin the QA Community
Connect with fellow testers, share job leads, and get career advice.
Stop Reinventing the Wheel. Upgrade Your QA Arsenal.
Take your testing skills from beginner to Lead Engineer. Supercharge your daily workflow with our premium digital resources.
- ⚡ Ready-to-use testing strategy templates
- 🔥 Advanced API & UI automation guides
- ⏱️ Save 10+ hours a week on test planning