ScreenshotNeo

BlogHow-to

How to Use ScenarioContext in SpecFlow

Use dependency injection to share data within a SpecFlow scenario. Learn its lifecycle, hook limits, parallel-safety considerations, and Reqnroll migration notes.

By the ScreenshotNeo team4 October 20266 min read

In an existing SpecFlow project, inject ScenarioContext into a binding class (or a scenario-level hook), keep the injected instance, and use it to store and retrieve values during that scenario. For SpecFlow 3 and later, avoid ScenarioContext.Current; constructor injection is the recommended pattern, especially when scenarios may run in parallel.

1. Inject ScenarioContext into a binding

This example stores a customer name in a Given step and reads it in a Then step. The code illustrates the pattern; confirm the binding attribute and package setup match your project’s SpecFlow version.

using TechTalk.SpecFlow;

[Binding]
public class AccountSteps
{
    private readonly ScenarioContext _scenarioContext;

    public AccountSteps(ScenarioContext scenarioContext)
    {
        _scenarioContext = scenarioContext;
    }

    [Given("I have a customer")]
    public void GivenIHaveACustomer()
    {
        _scenarioContext["customer"] = "Ada";
    }

    [Then("the customer is available")]
    public void ThenTheCustomerIsAvailable()
    {
        var customer = (string)_scenarioContext["customer"];
        if (customer != "Ada")
        {
            throw new InvalidOperationException("Unexpected customer.");
        }
    }
}

The constructor receives the context for the active scenario. SpecFlow’s binding dependency injection makes it available to the binding; retain it in a field if multiple step methods need it. Use descriptive keys and keep the stored values specific to the scenario.

Sharing a value between binding classes

If separate binding classes participate in one scenario, inject ScenarioContext into each. They receive the context for that scenario, so one binding can write a value and another can read it:

using TechTalk.SpecFlow;

[Binding]
public class CustomerSetupSteps
{
    private readonly ScenarioContext _context;

    public CustomerSetupSteps(ScenarioContext context) => _context = context;

    [Given("the customer identifier is {string}")]
    public void GivenCustomerIdentifier(string id)
    {
        _context["customer-id"] = id;
    }
}

[Binding]
public class CustomerCheckSteps
{
    private readonly ScenarioContext _context;

    public CustomerCheckSteps(ScenarioContext context) => _context = context;

    [Then("the customer identifier is available")]
    public void ThenCustomerIdentifierIsAvailable()
    {
        if (!_context.TryGetValue("customer-id", out string id))
        {
            throw new InvalidOperationException("Customer identifier was not set in this scenario.");
        }

        if (string.IsNullOrWhiteSpace(id))
        {
            throw new InvalidOperationException("Customer identifier is empty.");
        }
    }
}

Depending on the SpecFlow version, the exact generic TryGetValue overload may differ. If it does not compile, use the indexer after checking ContainsKey, or consult the API for the version installed in the project. Prefer explicit step arguments when they make the scenario easier to understand; context is most useful for data that genuinely needs to cross step-definition classes.

2. Understand scope and lifecycle

ScenarioContext represents the currently executing scenario. Its values are temporary test-execution state, not durable application data. It can also expose scenario metadata such as the title and tags through ScenarioInfo.

Need Use Scope
Share a value among steps in the active scenario ScenarioContext One scenario
Share data across scenarios in a feature FeatureContext, when appropriate One feature execution
Share state across a test run A deliberately designed test-run service or fixture Test run, with lifecycle and parallel behavior defined
Persist real application data The application’s database or service Application-defined

Scenario-level and step-level hooks run with an active scenario and can receive its context. Feature hooks and test-run hooks do not have an active scenario context, so do not try to obtain one there. Use a feature-scoped mechanism for feature-level state and a run-scoped mechanism for run-level setup.

3. Use context safely and keep scenarios clear

  • Prefer injection. Pass the context through the binding constructor or a scenario-level hook instead of reaching for global state.
  • Choose clear keys. Names like customer-id communicate intent better than generic keys such as data or value.
  • Store only what needs to cross steps. A large collection of hidden values makes a scenario difficult to understand and debug.
  • Validate reads. A missing key usually means setup did not run, a key name differs, or a step sequence is incomplete. Fail with an explanatory message.
  • Keep scenarios focused. For long workflows, make setup explicit and split unrelated behavior into separate scenarios rather than accumulating opaque context state.
  • Do not substitute static fields. Shared static state can leak between scenarios and cause interference when execution is parallel.

Injection helps keep scenario state associated with the scenario that owns it. It does not make arbitrary objects placed in the context safe for concurrent mutation or turn them into persistent storage.

4. Use ScenarioContext in hooks

A scenario hook can receive the active context through its parameters. Keep scenario-specific initialization in scenario hooks; feature and test-run hooks have different lifetimes.

using TechTalk.SpecFlow;

[Binding]
public class ScenarioMetadataHooks
{
    [BeforeScenario]
    public void BeforeScenario(ScenarioContext scenarioContext)
    {
        var title = scenarioContext.ScenarioInfo.Title;
        // Use scenario metadata for scenario-scoped setup or diagnostics.
    }
}

This is an illustrative hook pattern. Check the hook signatures supported by the SpecFlow version in your project. Avoid using a scenario hook as a hidden place to create business state that the scenario’s Given steps should make explicit.

5. SpecFlow and Reqnroll migration notes

SpecFlow reached end of life on December 31, 2024, according to the reviewed tutorial. For new work, consider Reqnroll and check its current migration documentation and package compatibility before changing a project. Reqnroll retains the ScenarioContext and FeatureContext concepts, while the namespace changes from TechTalk.SpecFlow to Reqnroll.

For an existing project, treat migration as a project change rather than assuming an old SpecFlow suite automatically works with a current .NET runtime. Review the installed framework and runner packages, update imports and dependencies as required by the migration guidance, and run the suite in the target environment.

6. Common errors and fixes

Symptom Likely cause Fix
ScenarioContext.Current is obsolete or unavailable Legacy static access is being used with a newer SpecFlow version. Inject ScenarioContext into the binding or scenario hook and use that instance.
A key lookup fails or returns no expected value The writing step did not run, the key differs, or the read happens in another scenario. Check step order and key spelling; verify both steps run in the same scenario and give missing-value errors a clear message.
Context is unavailable in a feature or test-run hook Those hooks do not execute inside an active scenario. Move scenario-specific work to a scenario/step hook. Use a feature- or run-scoped mechanism for data with that lifetime.
Scenarios interfere when running in parallel State may be held in static fields or another shared mutable object. Keep scenario values in the injected context; isolate any intentionally shared service and define its synchronization and lifetime.
A context value has an unexpected type The same key was written with a different value type, or a cast assumes the wrong type. Use a consistent key-to-type convention and validate the retrieved value before use.
SpecFlow attributes or namespaces do not resolve after migration Packages and source imports belong to different frameworks. Follow the current Reqnroll migration steps, update the namespace and packages together, then build against the project’s target runtime.

7. Or skip the browser setup

If the scenario needs a screenshot of a page, you can capture it with one HTTP request using ScreenshotNeo. The same parameter names used by other screenshot APIs also work. See the API documentation for the available options.

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

ScreenshotNeo accepts cookie and consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots.

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

8. Frequently asked questions

Can I use ScenarioContext to pass data between scenarios?

No. It belongs to the active scenario. For feature-wide data, consider FeatureContext; for broader state, choose a mechanism with an explicit lifetime and parallel-execution behavior.

Can I read the scenario title or tags from the context?

Scenario metadata is available through ScenarioContext.ScenarioInfo. Use it for metadata-driven setup or diagnostics, while keeping behavior-relevant inputs visible in the scenario where practical.

Should every value go into ScenarioContext?

No. Use it for values that need to pass between steps or binding classes. Explicit step inputs and focused scenarios are easier to follow when they fit the workflow.

Does a SpecFlow project automatically run on current .NET after migration?

No such compatibility should be assumed. Check the current migration guidance and package support for the project’s target framework and runner.