In this article, we will see an end to end example of using Specflow based BDD specifications and the tests will be executed via Selenium Webdriver.
The tests could be as simple as testing the login functionality of an application. It is just that, we will describe those tests using Specflow and the Step implementations will use Selenium driver and commands and NUnit as assertion framework.
We will also use NUnit based Specflow runner (as opposed to Specrun which is not open source and introduces a random delay in the free version).
Let’s Get Started.
To get started, let’s create a Unit test Project in Visual Studio and install the following pre-requisites:
#1) Create a unit test project
Install Nuget package for Nunit and Specflow.
Install-Package Specflow.NUnit
#2) Install Selenium’s Webdriver for chrome browser.
This can be installed via Nuget Package Manager console too through the below command.
Install-Package Selenium.WebDriver.ChromeDriver
#3) Install Selenium Webdriver libraries and Selenium Wait Helpers for adding ExpectedCondition waits.
Install-Package Selenium.WebDriver
Install-Package DotNetSeleniumExtras.WaitHelpers
#4) Now remove the test.cs file from the project that is auto-created.
We are doing this step to avoid any confusion as we will be using feature files and step definition files for Specflow.
#5) Create folders for Features and Step Definitions to store feature and Step definition implementation files.
This is not a mandatory step but is useful to organize the features and step implementations in separate folders.
#6) At the end of the above steps, the solution structure and the packages.config should look as shown below.
1 | <?xml version= "1.0" encoding= "utf-8" ?> |
3 | <package id= "DotNetSeleniumExtras.WaitHelpers" version= "3.11.0" targetFramework= "net461" /> |
4 | <package id= "MSTest.TestAdapter" version= "1.3.2" targetFramework= "net461" /> |
5 | <package id= "MSTest.TestFramework" version= "1.3.2" targetFramework= "net461" /> |
6 | <package id= "Newtonsoft.Json" version= "10.0.3" targetFramework= "net461" /> |
7 | <package id= "NUnit" version= "3.0.0" targetFramework= "net461" /> |
8 | <package id= "Selenium.WebDriver" version= "3.141.0" targetFramework= "net461" /> |
9 | <package id= "Selenium.WebDriver.ChromeDriver" version= "2.45.0" targetFramework= "net461" /> |
Feature And Step Implementation
Now let’s get started with the feature file and the actual step implementation.
About the feature – The sample feature will be of testing/validating the search functionality of Youtube Website. We will be searching for a keyword and asserting that the user got redirected to the search results page.
Add a new feature file and name it as YoutubeSearchFeature.feature
Add a search functionality scenario & feature description as shown below:
1 | Feature: YoutubeSearchFeature In order to test search functionality on youtube |
3 | I want to ensure functionality is working end to end |
5 | Scenario: Youtube should search for the given keyword and should navigate to search results page |
6 | Given I have navigated to youtube website |
7 | And I have entered India as search keyword |
8 | When I press the search button |
9 | Then I should be navigate to search results page |
The above scenario expects the test to:
- Navigate to Youtube Website: This will require a Web automation framework like Selenium, which will use a Webdriver to navigate to a webpage on a browser.
- Search for a keyword: This step will involve looking for appropriate input elements and buttons in order to key-in the keyword and execute the search respectively.
- Assert that the search results are displayed and the user is on the results page: This step will involve assertions around verifying if the user landed on the correct page.
Now let’s see the Step implementations each of the steps.
Before that, let’s understand how we will be integrating Selenium logic/code in the existing Specflow definition.
Selenium or any other tool (or Unit testing stubs/mocks/drivers etc) are essentially an intermediate part of the Step Execution, but the key thing to understand is the way to integrate both these Frameworks.
Specflow enables users to write test specifications. It does not dictate the tool that should be used. Hence the test developer is free to choose as many testing tools as he wants to depend on the use case that is being solved.
In order to use Selenium in this scenario, we need the following:
- An instance of WebDriver (we will be using ChromeDriver for simplicity), which will enable the user to actually navigate to the webpage using a browser as per Driver implementation.
- Few WebElement declarations (and can be done as part of Step implementations only) which are required to interact with the user and pass inputs and perform actions etc.
- Few assertions on Window title, urls, etc which can be executed on driver instance.
We will be creating an instance of ChromeWebdriver in the Step Implementations file.
Hence, let’s create the Step Definition file. As we saw in the last article, Specflow does provide a mechanism to auto-generate the Step definitions (which can later be customized/modified as required).
- Navigate to the feature file, Right-click and select “Generate Step Definitions”.
- Create a new file in the StepDefinitions folder as we created earlier and name the file as YoutubeSearchFeatureSteps.cs
- Ensure that all the Steps of the scenario have been bound to the Step definitions appropriately.
Tip – Modifying Auto-generated Step definitions:
Now if you carefully observe, the Step definition that got generated for our search keyword step i.e. “I have entered India as search keyword” the auto-generated code, does not identify/separate the search keyword and hence it does not parameterize it.
1 | [Given(@& "I have entered India as search keyword" )] |
2 | public void GivenIHaveEnteredIndiaAsSearchKeyword() |
4 | ScenarioContext.Current.Pending(); |
But this is not what we want. We need the search keyword to be parameterized, otherwise, for every keyword search, we will have to create a custom Step definition.
So, let’s see, how to modify this Step definition to a more generic one, which will enable to parameterize the search Keyword. This can be done through simple regex matcher.
Refer to the below code sample. We have replaced the search keyword through a regex matcher i.e. “(.*)” What this will do is that it will replace the value of any keyword that you will pass from the Scenario and will set the value of the search keyword in the input parameter named “searchString” in the below code sample.
1 | [Given( @"I have entered (.*) as search keyword" )] |
2 | public void GivenIHaveEnteredIndiaAsSearchKeyword(String searchString) |
4 | ScenarioContext.Current.Pending() |
This way, it keeps the code modular and avoids repeated boilerplate code for each Step implementation.
Selenium Integration And Step Definition Logic
Now let’s see the actual integration of Selenium with Specflow. Once the step definitions are generated, we will now add code to them in order to execute the actual test Scenario.
Let’s see, where we can place & initialize the Selenium Web driver instance so that it is available throughout the Scenario execution. We will be placing the Driver as a private field of the Binding Class that got generated. The driver will be initialized as a part of the class Constructor.
In this way, the driver remains initialized for the entire course of the duration of the test as we just have one Binding file for all the Steps (and it gets initialized before the test execution starts).
Also note that we will also be implementing the IDisposable interface, so as to Dispose the driver instance after which it is no longer required. Placing it in Dispose() Method will guarantee that once the class’s Object is getting disposed of, the driver instance can be disposed of too.
This is how the code for declaration and initialization of WebDriver instance looks like:
2 | public class YoutubeSearchFeatureSteps : IDisposable |
4 | private String searchKeyword; |
5 | private ChromeDriver chromeDriver; |
6 | public YoutubeSearchFeatureSteps() => chromeDriver = new ChromeDriver(); |
10 | if (chromeDriver != null ) |
12 | chromeDriver.Dispose(); |
With the above, the driver instance can be used as part of any Step implementation which is a part of the scenario execution.
Let’s now see the Step Implementation of each individual scenario.
#1) Arrange Steps:
1 | Given I have navigated to youtube website |
2 | And I have entered India as search keyword |
Both of these steps involve interacting with the driver instance. The first step open’s the browser window and navigates to the youtube website
The second step looks for search input button and enters “India” as the search keyword.
Below is the implementation for both of these steps:
1 | [[Given( @"I have navigated to youtube website" )] |
2 | public void GivenIHaveNavigatedToYoutubeWebsite() |
4 | chromeDriver.Navigate().GoToUrl(https: |
5 | Assert.IsTrue(chromeDriver.Title.ToLower().Contains( "youtube" )); |
8 | [Given( @"I have entered (.*) as search keyword" )] |
9 | public void GivenIHaveEnteredIndiaAsSearchKeyword(String searchString) |
11 | this .searchKeyword = searchString.ToLower(); |
12 | var searchInputBox = chromeDriver.FindElementById( "search" ); |
13 | var wait = new WebDriverWait(chromeDriver, TimeSpan.FromSeconds(2)); |
14 | wait.Until(ExpectedConditions.ElementIsVisible(By.Id( "search" ))); |
15 | searchInputBox.SendKeys(searchKeyword); |
For the first Step, notice the Assertion that it ensures that the navigation to youtube was successful by checking the window title.
Note: There can be various ways of placing Assertions on different web elements or driver properties, but the end goal of this tutorial is just to illustrate with the most simplistic way.
In the second step, we have added a Dynamic wait using ExpectedConditions which will ensure that the search box is visible before the code tries to key-in the search keyword.
Also, we are storing the searchString in a private field searchKeyword. This is done so that the searchKeyword can be used in other Step implementations too.
Tip – Passing data across the Steps
Passing/Storing data by this approach (i.e. through class variables) is one of the means through which data can be shared across Step bindings.
There are other ways to do this as well like Specflow itself provides a Dynamic Dictionary Object called ScenarioContext. We will see more details about this in the upcoming articles.
#2) Act Step
1 | When I press the search button |
Now let’s look at the actual action, which is clicking on the Search button. The step implementation file will search for the search button and click it in order to execute the scenario step.
The code for this step looks as shown below:
1 | [When( @"I press the search button" )] |
2 | public void WhenIPressTheSearchButton() |
4 | var searchButton = chromeDriver.FindElementByCssSelector( "button#search-icon-legacy" ); |
#3) Finally the Assert Step:
1 | Then I should navigate to search results page |
In this step, we are just verifying from the Driver properties as to whether the URL and the page title contains the search keyword or not.
The code for this step is shown below:
1 | [Then( @"I should be navigate to search results page" )] |
2 | public void ThenIShouldBeNavigateToSearchResultsPage() |
5 | Assert.IsTrue(chromeDriver.Url.ToLower().Contains(searchKeyword)); |
6 | Assert.IsTrue(chromeDriver.Title.ToLower().Contains(searchKeyword)); |
Execution
Now, let’s try to execute the Scenario and see the results. Once the Scenario is executed, all the Scenario Steps will be executed Sequentially. The test will be opening a browser, navigating to a website and then performing some action.
The output of the test can be seen by clicking the “Output” button from the test summary which shows the success/failure of each individual step.
Tips
Intermediate failing Steps
In case of a Scenario having intermediate steps that get failed, please note that in those scenarios Specflow will simply not execute any remaining Steps of that Scenario and will mark the result of the test as failed.
Running tests with NUnit Adapter
For this Example, we have executed our tests using the Specflow.NUnit test runner (that we had installed via Nuget Package Manager).
This is different in a few ways as shown below from the Specrun runner that we had used in the earlier article.
- Specflow.NUnit runner is open source.
- It does not introduce any delay while executing the tests.
Conclusion
In this article, we saw an end to end example of Integrating Selenium with Specflow framework through a simple test scenario of a video search on the Youtube application.
While integrating Selenium, we also went through, how to share data across different bindings through private class fields. We also covered running the test on NUnit runner Vs the Specrun runner and compared both in detail.
Code Files
YoutubeSearchFeature.feature
1 | Feature: YoutubeSearchFeature |
2 | In order to test search functionality on youtube |
4 | I want to ensure functionality is working end to end |
7 | Scenario: Youtube should search for the given keyword and should navigate to search results page |
8 | Given I have navigated to youtube website |
9 | And I have entered India as search keyword |
10 | When I press the search button |
11 | Then I should be navigate to search results page |
YoutubeSearchFeatureSteps.cs
3 | using OpenQA.Selenium.Chrome; |
4 | using OpenQA.Selenium.Support.UI; |
5 | using SeleniumExtras.WaitHelpers; |
7 | using System.Collections.Generic; |
9 | using TechTalk.SpecFlow; |
11 | namespace SepcflowSelenium.StepDefinitions |
14 | public class YoutubeSearchFeatureSteps : IDisposable |
16 | private String searchKeyword; |
18 | private ChromeDriver chromeDriver; |
20 | public YoutubeSearchFeatureSteps() => chromeDriver = new ChromeDriver(); |
22 | [Given( @"I have navigated to youtube website" )] |
23 | public void GivenIHaveNavigatedToYoutubeWebsite() |
25 | chromeDriver.Navigate().GoToUrl(https: |
26 | Assert.IsTrue(chromeDriver.Title.ToLower().Contains( "youtube" )); |
29 | [Given( @"I have entered (.*) as search keyword" )] |
30 | public void GivenIHaveEnteredIndiaAsSearchKeyword(String searchString) |
32 | this .searchKeyword = searchString.ToLower(); |
33 | var searchInputBox = chromeDriver.FindElementById( "search" ); |
34 | var wait = new WebDriverWait(chromeDriver, TimeSpan.FromSeconds(2)); |
35 | wait.Until(ExpectedConditions.ElementIsVisible(By.Id( "search" ))); |
36 | searchInputBox.SendKeys(searchKeyword); |
39 | [When( @"I press the search button" )] |
40 | public void WhenIPressTheSearchButton() |
42 | var searchButton = chromeDriver.FindElementByCssSelector( "button#search-icon-legacy" ); |
46 | [Then( @"I should be navigate to search results page" )] |
47 | public void ThenIShouldBeNavigateToSearchResultsPage() |
50 | System.Threading.Thread.Sleep(2000); |
52 | Assert.IsTrue(chromeDriver.Url.ToLower().Contains(searchKeyword)); |
53 | Assert.IsTrue(chromeDriver.Title.ToLower().Contains(searchKeyword)); |
59 | if (chromeDriver != null ) |
61 | chromeDriver.Dispose(); |
No comments:
Post a Comment