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.

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
1.What is SpecFlow?
2.SpecFlow vs Cucumber — which should I use?
3.Is SpecFlow free?
4.Does SpecFlow work with .NET 8 and .NET 9?
5.What's the difference between SpecFlow and Reqnroll?
6.How do I generate step definition stubs?
7.What is SpecFlow+ LivingDoc?
8.Can SpecFlow run tests in parallel?
9.How long does it take to learn SpecFlow?
10.Is SpecFlow still relevant?
Practice these questions
Rehearse Selenium and Playwright automation questions covering framework design, waits, locators and CI/CD.
Was this article helpful?
More from SpecFlow C#
SpecFlow for .NET — bindings, hooks, LivingDoc.
Keep building your QA edge
Pillar guides- Automation QA Engineer RoleAutomation QA Engineer roleAutomation QA Engineer job scope, tools, salary, and hiring pipeline.
- SDET RoleSDET role guideWhat SDETs actually do — skills, salary bands, and interview prep for 2026.
- QA Jobs RadarSoftwareTestPilot's QA jobs boardLive QA / SDET / automation job feed, refreshed daily.
Continue reading

Playwright Locator Best Practices (2026) — The Only Guide You Need
11 min read
How to Migrate a Postman Collection to Playwright API Tests (2026 Guide)
12 min read
Why Every QA Engineer Must Master CI/CD Pipelines in 2026 (Or Risk Obsolescence)
12 min readRelated concepts, tools & standards around Automation Testing
A quick reference of the people, companies, frameworks and technologies most often mentioned alongside Automation Testing in real QA teams — useful when you're mapping a learning path, preparing for interviews, or scoping a new project.
Join the QA Community
Connect with fellow testers, share job leads, and get career advice.
Discussion
Ask a question, share your experience, or correct us. Be kind — real people are reading.