Reputation: 1382
I have at least 3 .feature files in my C# Specflow tests project in which I have the step, for instance:
Given I am at the Home Page
When I first wrote the step in the file Feateure1.feature
and created the step method, I placed it in a step file, let's say, Steps1.cs
, which inherits from a base class that initializes a FirefoxDriver
. All my StepsXXXX.cs
classes inherit from this base class.
Then, I wrote Feature2.feature
, which also has a step Given I am at the Home Page
. And the step was automaticaly bound to the one in Steps1.cs
'Till now, no problem. That's pretty much what I wanted - to have reusable steps throughout the test project. But the problem is, whenever I'm running a scenario that has steps in diferent StepsXXXX
files, I get various browser instances running.
======
I'm pretty sure this is due to the fact that My StepsXXXX
(binding classes) all inherit from this base class that has a IWebDriver of its own, and when the step is called, everything else (including the before/after scenario methods) is called. But I can't figure out how to work around this.
I still want reusable steps. I tried to put these steps in the base class, but it did not work. I thought of changing the bindings too, but specflow uses meaningfull strings to do so, and I don't want to change them to misleading strings.
Has anyone stumbled across this? Any help is really appreciated.
Upvotes: 11
Views: 13714
Reputation: 6781
You can use Scoped bindings using [Scope(Tag = "mytag", Feature = "feature title", Scenario = "scenario title")]
to referred on specific scenario or feateure like this:
Feature: Feateure1
Scenario: demo
Given I am at the Home Page
When ....
[Binding, Scope(Feature = "Feateure1")]
public class Steps1{
[Given(@"Given I am at the Home Page")]
public void GivenIAmAtTheHomePage(){
{ }
}
Feature: Feateure2
Scenario: demo
Given I am at the Home Page
When ....
...
[Binding,Scope(Feature = "Feateure2")]
public class Steps2{
[Given(@"Given I am at the Home Page")]
public void GivenIAmAtTheHomePage(){
{ }
}
Upvotes: 8
Reputation: 1759
i think this is a lot more simple than the question and answers here make it out to be. there are really two questions at play here:
AISki gave you the right answer in the link to documentation about specflow context, but it was not really presented as the answer and there was distraction in presenting an inferior answer as the actual answer.
the answer as to the behavior you see is that you should expect exactly what is happening with the way you set things up. if you have multiple binding classes that create browser instances (and you do if they all have a common base that creates a browser instance) and they have matches in your features, you should expect multiple browser instances.
The answer for what you intend (a single browser shared among your steps) is that you should use the context feature of specflow to control the dependency on a browser instance. this amounts to dependency injection. your step definition classes should take a constructor dependency on something that creates your browser instance - specflow manages dependencies for you and you'll get a new instance for the first of your classes created and then the same one after that.
https://github.com/techtalk/SpecFlow/wiki/Sharing-Data-between-Bindings
Upvotes: 4
Reputation: 8558
I facing the same issue.
I wanted to have one feature file that will call steps in different cs classes. The issue came across when I want to setup and tear down for each scenario.
Using step class constructor and Dispose()
not possible because the scenario uses more than one step class which I don't want to 'setup' multiple time in a scenario.
Using [BeforeScenario]
and [AfterScenario]
for both step classes also makes the runner run the before and after methods in both class that makes it setup run twice.
So what I was done is create another third class called something like BrowserScenarioSetup
put the before and after scenario class in it to setup a browser for the scenario and assign to ScenarioContext.Current
dictionary. When the test run, only one browser created for a scenario and I can use scenario steps defined in any class but just uses Scenario.Context.Current
to get the browser instance.
I can make both step classes have a base step class and create a short method to get browser instance (or any shared instance created in setup) just to hide Scenario.Context.Current
Finally I can mark [BeforeScenario("Browser", "IE")]
and use @Browser and @IE in a feature or scenario to only call this setup method in suitable context.
Upvotes: 3
Reputation: 6961
The problem is that SpecFlow bindings don't respect inheritance. All custom attributes are considered global, and so all SpecFlow does is search for a list of classes with a [Binding]then build up a dictionary for all the [Given]/[When]/[Then]s so that it can evaluate them for a best match. It will then create an instance of the class (if it hasn't already done so) and call the method on it.
As a result your simple cases all stay in the Steps1 class, because its the first perfect match. Your more complicated cases start instantiating more classes, hence multiple browsers, And your attempt to refactor won't work because your abstract base class doesn't have a [Binding] on it.
I'd probably start by flattening all your step class hierarchy, into one big AllSteps.cs class. This may seem counter-productive, but all you are really doing is arranging the code just how the current bindings appear to your SpecFlow features. This way you can start to refactor out the overlaps between the different GWT bindings.
At the moment your bindings are arranged around the scenarios. What you will need to do is refactor them around your functionality. Have a read of Whose Domain is it anyway? before you start and this will probably give you some good ideas. Then have a look at Sharing-Data-between-Bindings on the SpecFlow documentation to work out how to link between your new steps classes.
Upvotes: 6