ScreenshotNeo

BlogGuides

SpecFlow Actions: How to Use Them in BDD Tests

Learn what SpecFlow Actions referred to, how to share scenario state across bindings, and how to manage cleanup in BDD tests. Note: SpecFlow is end of life.

By the ScreenshotNeo team4 October 202610 min read

Short answer: “SpecFlow Actions” was the name of a historical 2021 initiative proposing reusable code snippets for focused automation problems and plugins for more complex integrations. The available sources do not establish a current Actions package, API, installation command, or maintenance status. For practical BDD tests, the documented reusable patterns are to organize bindings by responsibility, inject scenario-specific context through constructors, and let scenario-scoped objects own cleanup.

Status matters: SpecFlow’s GitHub organization says SpecFlow would no longer be available after December 31, 2024, and that the organization was archived on January 13, 2025. Treat the Actions announcement and training material as historical; check the exact repository, package, SpecFlow version, and .NET target before adopting surviving code. SpecFlow’s GitHub organization

1. What were SpecFlow Actions?

The July 20, 2021 announcement described an effort to make automation of applications, APIs, websites, mobile apps, and services easier. It proposed two kinds of reusable material:

  • Code snippets: small solutions to specific problems that a team could copy and adapt.
  • Plugins: integrations for more complex problems that could extend SpecFlow.

The announcement invited community ideas and voting, and described a planned development iteration from August 2 through August 13, 2021. Those dates are the original plan, not evidence of present availability. The retrieved sources do not establish current commands, package identifiers, an exact Actions API, or the status of a separate Actions repository. Original SpecFlow Actions announcement

2. Use bindings to keep behavior and automation organized

Keep feature scenarios focused on readable behavior, and put the implementation that drives the application in step-definition classes, also called bindings. When a test suite grows, split bindings across classes by responsibility—for example, order operations in one class and customer operations in another. The training material says binding classes remain discoverable when marked with [Binding]. SpecFlow masterclass training material

A binding should translate a step into an action or assertion. It should avoid becoming a general-purpose store for unrelated scenario data. Put shared scenario data and helpers in an injected context object instead.

3. Share scenario data with constructor injection

Create a plain context class for data multiple steps need, such as an order identifier and submission result. Ask for that context in the constructor of every binding class that needs it, save it in a field, and use the shared instance from step methods. The training material describes context injection as recursively resolving dependencies, so an injected helper can request dependencies of its own. Keep data scenario-specific so parallel or unrelated scenarios do not accidentally share mutable state.

// Illustrative pattern. Adapt the registration and APIs to the exact
// SpecFlow version and project setup in use.
public sealed class OrderContext
{
    public IOrderService OrderService { get; }
    public Order Order { get; set; }
    public OrderResult Result { get; set; }

    public OrderContext(IOrderService orderService)
    {
        OrderService = orderService;
    }
}

[Binding]
public sealed class OrderSteps
{
    private readonly OrderContext _context;

    public OrderSteps(OrderContext context)
    {
        _context = context;
    }

    [When("I submit the order")]
    public void WhenISubmitTheOrder()
    {
        _context.Result = _context.OrderService.Submit(_context.Order);
    }

    [Then("the order is accepted")]
    public void ThenTheOrderIsAccepted()
    {
        if (_context.Result == null || !_context.Result.Accepted)
        {
            throw new AssertionException("Expected the order to be accepted.");
        }
    }
}

This is an explanatory example, not an assertion that it was run or tested. IOrderService, Order, OrderResult, and AssertionException stand for project-defined or test-framework types. The exact binding attributes, dependency setup, and supported .NET target depend on the framework version and project. Verify those details before using the pattern.

Keep the context narrow

  • Store only state genuinely shared by steps in the same scenario.
  • Use typed properties instead of a string-keyed bag where possible; typed properties make consumers and refactors easier to follow.
  • Keep application services and browser helpers behind focused interfaces or helper classes, rather than adding every operation to a single context object.
  • Do not use a static field for scenario state. Static mutable state can leak between scenarios and break parallel execution.
  • Initialize required values explicitly, and make the failure clear when a step runs before its prerequisite.

4. Let scenario-scoped objects own cleanup

The training material says injected objects implementing IDisposable are disposed after a scenario finishes. A context or helper that owns a browser, temporary directory, or other scenario resource can use that lifecycle to release it. Make the resource owner responsible for its cleanup, and avoid disposing a dependency in multiple places.

public sealed class BrowserContext : IDisposable
{
    public IWebDriver Driver { get; }

    public BrowserContext(IWebDriverFactory factory)
    {
        Driver = factory.Create();
    }

    public void Dispose()
    {
        Driver.Quit();
        Driver.Dispose();
    }
}

[Binding]
public sealed class CheckoutSteps
{
    private readonly BrowserContext _browser;

    public CheckoutSteps(BrowserContext browser)
    {
        _browser = browser;
    }

    [When("I open the checkout page")]
    public void WhenIOpenTheCheckoutPage()
    {
        _browser.Driver.Navigate().GoToUrl("https://example.test/checkout");
    }
}

The browser types here are placeholders for the browser automation library used by a project. Check whether its driver’s cleanup calls are safe together; some resources require only one of these operations. The lifecycle statement comes from the cited training material, so verify disposal behavior against the exact version and configuration in use. Avoid relying on scenario context in hooks that run outside a scenario, where scenario-scoped dependencies may not be available. SpecFlow masterclass training material

5. Choose a reuse boundary: local helper, snippet, or plugin

Need Reasonable boundary What to check
One focused problem in one test project A small helper or copied snippet, adapted locally Inputs, error behavior, version-specific APIs, and ownership of cleanup
A capability used across several bindings in one project An injected, scenario-scoped helper or context Scenario isolation, dependency construction, and parallel execution
A complex integration intended for multiple projects A plugin or separate integration package, if a maintained compatible one exists Repository activity, package status, supported framework and .NET versions, and documentation

This distinction reflects the 2021 initiative’s stated intent: snippets for focused problems and plugins for more complex integrations. It does not establish that a particular plugin is currently installable or maintained. Do not infer a package name or install command from the initiative announcement. Original announcement

6. Check compatibility before adapting old examples

  1. Identify the exact SpecFlow package and version in the project’s dependency files and lock file.
  2. Check the project’s .NET target and test runner integration.
  3. Inspect the candidate repository’s latest release, commits, issues, and supported versions.
  4. Confirm how bindings are discovered, how injected objects are created, and when disposable objects are released for that version.
  5. Build and run the smallest relevant scenario in an isolated branch or test project before expanding use.
  6. Record the verified versions and lifecycle assumptions so future maintainers can reproduce the setup.

These checks matter especially for archived projects. The SpecFlow organization reports that SpecFlow would no longer be available after December 31, 2024 and that it was archived January 13, 2025. That is a project-status fact, not a compatibility guarantee for any particular surviving package or fork. SpecFlow GitHub organization

7. Troubleshoot common binding and context problems

Symptom Likely cause What to check or change
A step is reported as undefined The binding was not discovered, the step text does not match, or a required binding attribute/setup is missing. Confirm the class has the version-appropriate binding marker, the project includes the right SpecFlow integration, and the expression matches the feature step.
Constructor injection fails The requested type cannot be constructed, a constructor dependency is unavailable, or the project’s version/configuration differs from the example. Check constructors and dependency registration for the actual version. Start with a simple context with constructible dependencies.
A value is null in a later step The earlier step did not set it, the steps use different context instances, or the value is not initialized on every path. Verify both bindings receive the same scenario-scoped context and add a clear precondition check before consuming the value.
State leaks between scenarios Mutable state is static, singleton-scoped, or stored outside scenario scope. Move it into a scenario-scoped injected object and avoid shared mutable fields. Check parallel-run behavior.
Browser or temporary resources remain open The object that owns the resource does not clean it up, disposal is not invoked as expected, or cleanup throws early. Keep cleanup with the scenario-scoped owner, verify disposal semantics for the version, and make cleanup safe when setup only partly completed.
Cleanup fails with a missing scenario context A hook or helper runs outside the scenario lifecycle but expects scenario-scoped data. Use only dependencies valid for that hook’s lifecycle, and keep scenario-owned cleanup in a scenario-scoped object or appropriate scenario hook.
An old Actions command or package cannot be found The announcement described an initiative, but the available evidence does not establish a current package, command, or API. Do not guess the identifier. Locate and verify a maintained repository or package for the exact version, or implement the narrow helper locally.
An old example will not compile Framework APIs, language features, package references, or .NET target differ. Check the project’s target and dependency versions, then adapt against documentation for that version; do not assume a historical example is current.

8. Browser-based BDD tests: capture evidence when useful

Screenshot evidence can help diagnose a failed browser scenario or document what a page displayed. It is separate from the SpecFlow Actions initiative. If you take screenshots locally, decide whether the test needs the viewport or the entire page, wait for the relevant page state, and avoid capturing secrets or personal data. In CI, keep artifacts tied to the failed scenario and apply the project’s normal retention rules.

DIY: capture a page with Playwright for .NET

This standalone C# example illustrates a local browser screenshot; Playwright is not part of SpecFlow Actions. Add the Playwright .NET package to a project that targets a supported .NET version, install its browser as required by the Playwright setup instructions, then run this program. Exact package and browser-install steps depend on the project environment.

using Microsoft.Playwright;

using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.Chromium.LaunchAsync(
    new BrowserTypeLaunchOptions { Headless = true });
var page = await browser.NewPageAsync();
await page.GotoAsync("https://stripe.com", new PageGotoOptions
{
    WaitUntil = WaitUntilState.NetworkIdle,
    Timeout = 30000
});
await page.ScreenshotAsync(new PageScreenshotOptions
{
    Path = "shot.png",
    FullPage = true
});

For reliable tests, prefer waiting for a specific selector that represents the state under test when possible; network idle can be unsuitable for pages with continuous requests. Use a bounded timeout and capture diagnostics on failure. Pin browser and package versions in CI where reproducibility matters.

9. Or skip the browser setup

For a browser screenshot without managing a local browser, ScreenshotNeo is a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF. The API supports full-page capture, waiting, selectors, custom headers and cookies, device and viewport options, and other capture controls. See the ScreenshotNeo API documentation.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const bytes = new Uint8Array(await res.arrayBuffer());
await (await import('node:fs/promises')).writeFile('shot.webp', bytes);

Cookie banners, newsletter popups, and chat widgets are removed before capture, and each of those steps can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed; response headers report the page verdict and billing status. An MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs. The Free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000 screenshots.

Sign up for 1,000 free screenshots a month, with no card required.

10. Performance, reliability, and cost considerations

  • Keep browser ownership clear: reusing a browser process can reduce startup overhead in a local test suite, while keeping isolated pages or contexts per scenario helps prevent state leaks. Confirm the test framework’s lifecycle and parallel execution model before sharing a process.
  • Use bounded waits: waiting for a meaningful element is generally more targeted than sleeping for a fixed delay. Set timeouts so a stalled page fails with useful diagnostics instead of hanging a job.
  • Capture selectively: screenshots and PDFs consume time, memory, network, and artifact storage. Capture on failure or at deliberate checkpoints when a full set is not needed.
  • Make CI reproducible: pin framework, browser, and test dependencies; record environment differences; and retain enough logs to distinguish application failures from browser setup problems.
  • Estimate service costs from actual volume: for ScreenshotNeo, the supplied plans are Free with 1,000 shots/month, Starter at $5 for 3,000, Growth at $15 for 15,000, Pro at $39 for 60,000, Scale at $99 for 250,000, and Business at $249 for 1,000,000. Yearly billing gives two months free; every feature is on every plan. Check current plan details before purchase.

11. FAQ

Are SpecFlow Actions the same thing as step definitions?

No. Actions was the name of a proposed initiative for reusable snippets and plugins. Step definitions or bindings are the code that connects feature steps to test behavior.

Can I use ScenarioContext instead of making my own context class?

The research supports constructor-based injection of context and helper classes, but does not establish version-specific ScenarioContext APIs. Check the documentation for the exact version in your project before choosing that API.

Does end of life mean existing SpecFlow tests stop running?

The cited status says the project is no longer available and its organization is archived; it does not establish what will happen to a particular existing test suite. Existing behavior depends on its packages, runtime, and build environment.

Do I need a screenshot service to write BDD tests?

No. A local browser automation library can capture screenshots. A service is an option when you want to request captures without managing browser installation and execution yourself.