.NET UI Automation
BDD UI Automation via SpecFlow, C#, and Selenium
Setting up a .NET Core Unit Tests project that runs SpecFlow UI automation scenarios using Selenium
So many acronyms and buzzwords in a single title, so before we dive into the implementation let’s break it down:
BDD or Behavior-driven development according to Wikipedia:
is an Agile software development process that encourages collaboration among developers, QA and non-technical or business participants in a software project
The goal here is to allow non-developers to write test cases (or scenarios) that can be translated into actual deliverables — a fully functional end-to-end test.
So what is the goal here anyway?
What we want, is to be able to let non-developer members of an Agile team the ability to write test cases that will “automagically” translate into something our CI/CD pipeline can run and validate.
This guide will walk you through the process of setting up a brand new solution with a single unit tests project that will have everything you need to start creating your own UI automation BDD scenarios using Selenium.
I will be using Visual Studio 2019 and will create a .NET Core 3.1 project, but this technique should work on other versions of .NET Core, or even the full .NET Framework (<4.8). If it doesn’t feel free to reach out with any questions, and I will try to assist as much as I can.
Install the SpecFlow Visual Studio Extension
This extension will add Cucumber syntax highlighting, new file “types” and additional tooling.
Open Visual Studio, and go to Extensions -> Manage Extensions
In the top-right search box type “specflow” and click on the “Download” button on the first result.
Once the download is complete, you will need to close Visual Studio and the installer will start automatically.
Follow all the steps in the installer until it’s done.
Create a new Unit Tests / SpecFlow Project
Start Visual Studio and select “Create a new project”
In the next step, select the xUnit Test Project (.NET Core)
Click “Next” and select your preferred project’s name and location.
I named my project “SpecFlowUiAutomation”.
Click on the “Create” button, and we are done.
Let's delete the default unit test class that was added automatically so that we’ll have a clean slate to work with.
Go ahead and delete the file UnitTest1.cs
Next, let’s add the required NuGet packages to the project:
Right-click on the project’s name, and select “Manage NuGet Packages”.
Before installing any package, open the “Updates” tab and make sure all of the existing package dependencies are up to date. This is required, because when you’ll try to install the latest version of SpecFlow, it will try to reference newer versions of your existing dependencies, and the installation will fail.
- Click on the update tab.
- Check the box “Select all packages”
- Click on the “Update” button.
Once it’s all done, go back to the “Browse” tab to continue.
In the search box, type in “specflow” and install the following packages:
- SpecFlow — This is the basic support for SpefFlow
- SpecFlow.Tools.MsBuild.Generation — This package will convert SpecFlow scenario files into C# code
- SpecFlow.xUnit — This package will integrate SpecFlow with xUnit
While we have the Package Manager window open, let’s also go ahead and install Selenium and all of its dependencies.
Type “selenium” into the search box and install the following packages:
You don’t have to use ChromeDriver — you can use any of the other supported web drivers (Firefox, IE, Edge, etc), in this article we will be automating Chrome, but none of the code is Chrome-specific (other than the code to initially load the driver and launch the browser)
We are all set. Let’s try to rebuild our project:
Before we can start writing automation code, we need to automate the process of launching a web browser at the beginning of each test, and closing it down after the test is executed.
Fortunately, SpecFlow provides us with several hooks.
You can choose to launch a browser and close it after each test, or you can use the same browser instance to run your entire test suite.
If you chose to use the same instance for all of your tests, you take some risk of “state leakage” between tests — you’ll essentially share session state, local storage, cookies, etc between all tests.
If you are comfortable with it, or if this “risk” is manageable — be my guest.
Create a new code file by right-clicking on the project name and selecting Add -> Class. Name the class “TestHooks.cs” and click on “Create”
This new class should have the following code:
Note the [Before] and [After] attributes.
If you choose to have a single browser instance for all tests, you can replace these attributes with [BeforeTestRun] and[AfterTestRun]. Your call.
So “Before” every scenario we will launch a Chrome web browser and store the “handle” to it in our scenario context.
And “After” the scenario is complete, we will retrieve the handle to the browser to shut it down. Easy.
Let’s create our first automation scenario:
Right-click on the project’s name and select “Add -> New Item”
In the modal dialog’s left tree view of installed templates, select “SpecFlow”. This will expose the full list of SpecFlow file types.
For now, we’ll select “SpecFlow Feature File”.
Name your file and click “Add”.
I named my file “WikipediaTitleValidation.feature”.
In the generated scenario file, paste the following test:
So we have 3 scenarios, each validates a simple condition — that the title of the page is matching our expectations.
Rebuild the project and open Visual Studio’s Test Explorer:
So Visual Studio correctly identified 3 new unit tests, but we can’t really run them at this point.
We need to convert our BDD “language” into actual code.
To do that, we will need to create a “Steps” file, where we will “translate” each BDD sentence into a C# method.
Luckily, the SpecFlow integration with Visual Studio is superb.
All you need to do is to right-click on the open “feature” file, and select “Generate Steps Definition”
The following window will pop up:
SpecFlow is basically identifying all the steps in your feature file that do not have a backing code implementation.
We will want to keep all the steps checked, but in the future, you can be specific and choose the steps you want to auto-generate these steps for (for example, if you prefer to split some of the steps into a separate class — CommonSteps.cs for example)
Click the “Generate” button and you’ll be prompted to choose the location the newly created class will be saved.
Simply choose/navigate into your project’s folder, and click the “Save” button.
The default file name would be WikipediaTitleValidationSteps.cs but you can change it. I would suggest keeping this file name or choose a different convention, as long as you are consistent, you should be fine.
Let’s take a look at the generated file:
We can see that we have 2 methods, each representing a “step” in our test cases.
SpecFlow was smart enough to understand that we are using parameters in our steps, so the methods do take in a string parameter each.
Unfortunately, some of SpecFlow’s tooling hasn’t caught up with the most recent version of the library, so some of the auto-generated code does not follow the most up to date design patterns.
We will see the following error message:
This is very easy to fix. We can easily create a constructor and have the scenario context injected into it.
Let’s clean this up and make sure we follow the correct pattern:
So we removed the ScenarioContext’s Singelton/static references and used construction injection instead.
We can now start implementing our automation logic.
Since we have a very simplistic automation logic, I will show you the class in its final form:
We didn’t really need the ScenarioContext reference in our steps, we only needed it to gain access to the WebDriver instance we created earlier.
You can use the ScenarioContext to pass state between different states in the test. You need to remember that each step is being executed in its own WikipediaTitleValidationSteps instance, and in fact, these steps can be defined in multiple “Steps” classes.
The only recommended way to pass state between steps is by using the ScenarioContext object’s dictionary. Please try to avoid using static/globally accessible variables as much as possible.
If we run all of our tests in the test runner now, this is what we’ll see:
You said that anyone can create BDD test cases. I see a lot of code here, what gives?
We’ll, in fact, anyone can create test cases, but each step will have to be “translated” into a proper C# method to be executed.
Over time, your automation project will evolve and grow a rich library of steps that can be reused and shared, and over time a lot of these pre-existing steps will be enough to create additional scenarios without additional coding effort.
Let’s put this theory to the test. Let’s pretend I’m the business owner of the project, and I jump into this unit test project to create a new scenario file: “WikipediaStockSymbolValidation.feature”:
Let’s take a look at how this file looks like in Visual Studio:
You can see that SpecFlow can already map the first step (navigate to a Wikipedia page) as a “known step” — and that step’s color is white.
The new step, where we want to verify the stock symbol is purple, hinting us that it’s unknown, or that there is no underlying code implementation for it.
At this point, you can run these tests, they will simply not run:
Let’s follow the steps we took earlier and create the missing step for this class.
Right-click anywhere on the file’s background, and select “Generate Step Definition”:
Note that only the “new” or “unidentified” steps are being presented here.
Save the file using the same naming convention as before, and here is how I implemented this logic:
After running all our tests, this is what we’ll see:
Not good… but expected. :-)
As it turns out “Apple” (the fruit) is not traded on the NASDAQ exchange, and “Google” is now “Alphabet Inc.”.
Let’s fix our scenarios:
Also note, that when you run all your tests right now, you will see 2 browser windows running at the same time — these scenarios will run in parallel. Pretty neat.
This is also a reminder of why it’s important not to share state between steps — using static or global state might cause the state to leak between the two (or more) windows. If sharing state is required — make sure you use the ScenarioContext.
When it’s all done, we’ll see the following result:
And we are done.
There isn’t a lot out there for C# developers looking to do UI automation and BDD, so I’m excited to share my knowledge with the world, hoping someone else will find it useful.
I hope you enjoyed this write up. If you would like to see more .NET automation articles let me know!