Table of Contents
Creating your First Proper Test
Search for “Reddit” on DuckDuckGo and Select a Search Result
Creating the SpecFlow Scenario
When creating our BDD scenarios, we want to keep them simple and write them in a declarative style, sticking simply to the business logic, rather than writing them in an imperative style. A great article on this can be found at http://itsadeliverything.com/declarative-vs-imperative-gherkin-scenarios-for-cucumber.
- Right-click on the ‘Features’ folder and select ‘Add a New Item’
- In the window that appears, select ‘SpecFlow Feature File’ and create a file called ‘SearchScenarios.feature’
- Open up the ‘SearchScenarios.feature’ file and add the following test scenario. We can also add a SpecFlow tag either above the ‘Scenario’ (to apply to the specific scenario) or above the ‘Feature’ (to apply to all scenarios within the feature)…
Feature: Search Scenarios As a user of DuckDuckGo, I want to be able to search for stuff @Chrome Scenario: 01. Search and select a result Given an internet user is on the search page When they search for "Reddit homepage" And they view the first result Then they see the Reddit homepage
If you’ve been following all the blog posts in this series, you will notice that we have reused some existing steps in this scenario, such as;
– “Given an internet user is on the search page”The other steps will still be undefined.
Creating the Undefined Step Definitions
-
- Copy the method below in SearchSteps class for the undefined When step…
using TechTalk.SpecFlow; namespace ProductAutomation.Steps.SearchSteps { [Binding] public class SearchSteps { [When(@"they search for ""(.*)""")] public void WhenISearchFor(string searchTerm) { throw new PendingStepException(); } } }
- Copy the method below in SearchSteps class for the undefined When step…
- Add the next undefined step for the ‘When I view the first result’ to the RedditSteps.cs step definition class…
using TechTalk.SpecFlow; namespace ProductAutomation.Steps.RedditSteps { [Binding] public class RedditSteps { [When(@"they view the first result")] public void WhenIViewTheFirstResult() { throw new PendingStepException(); } } }
- Add the final undefined step for ‘Then they see the Reddit homepage’ to the RedditSteps.cs step definition class file also, so that the whole thing looks similar to below…
using TechTalk.SpecFlow; namespace ProductAutomation.Steps.RedditSteps { [Binding] public class RedditSteps { [When(@"they view the first result")] public void WhenIViewTheFirstResult() { throw new PendingStepException(); } [Then(@"they see the Reddit homepage")] public void ThenTheySeeTheRedditHomePage() { throw new PendingStepException(); } } }
Creating the Test Methods
We will now create test methods in C# for each of our steps.
Because we are taken to a new ‘search results’ page when we submit a search on Google, we should create a new page class for the search results page.
- Right-click the ‘Pages’ folder, select ‘New File’ and add a new page class file called ‘SearchResultsPage.cs’
- Open up the ‘SearchResultsPage.cs’ class file and edit it so it looks like below (we want it to inherit from BasePage, as that is where we navigate to the page from when we conduct our search on the ‘BasePage’)…
namespace ProductAutomation.Pages { public class SearchResultsPage : BasePage { } }
- Open up the ‘BasePage.cs’ class file in the ‘Pages’ namespace and add the following By locator at the top of the class (From Selenium WebDriver v3.11.0+, PageFactory is obsolete, so instead of using…
[FindsBy(How = How.Name, Using = "q")] public IWebElement SearchField { get; set; }
…We should instead create By locators like below (this is preferred by Simon Stewart himself, the creator of Selenium)…
public By SearchField => By.Id("search_form_input_homepage");
We will use this By locator in our test method for sending keys to the ‘SearchField’ IWebElement. If you right-click on the search field in Chrome web browser and select ‘Inspect Element’, you can see that it has the value of ‘q’ given for its ‘name’, this is what we use to find the element in our test framework.
- Add the following test method within the ‘BasePage.cs’ class…
public SearchResultsPage SearchFor(string searchTerm) { driver.FindElement(SearchField).Clear(); driver.FindElement(SearchField).SendKeys(searchTerm + Keys.Return); return new SearchResultsPage(); }
The whole thing should look like below…
using System; using OpenQA.Selenium; using ProductAutomation.Utils.Selenium; using static ProductAutomation.Utils.Selenium.Settings; namespace ProductAutomation.Pages { public class BasePage { public By SearchField => By.Id("search_form_input_homepage"); protected IWebDriver driver = Driver.CurrentDriver; public string GetTitle => Driver.CurrentDriver.Title; public string GetUrl => Driver.CurrentDriver.Url; public string GetPageSource => Driver.CurrentDriver.PageSource; public void NavigateBaseUrl() { driver.Navigate().GoToUrl(baseUrl); driver.Manage().Window.Maximize(); Console.WriteLine(" :: The base URL is navigated to"); } public SearchResultsPage SearchFor(string searchTerm) { driver.FindElement(SearchField).Clear(); driver.FindElement(SearchField).SendKeys(searchTerm + Keys.Return); return new SearchResultsPage(); } } }
The above added method clears the ‘SearchField’ of any text, and then sends the passed string to the ‘SearchField’, presses the Return key to submit the search, and then returns the ‘SearchResultsPage’ page class after submission.
Because we are taken to a new ‘Reddit’ page after selecting the appropriate search result, we should create another new page class for the ‘Reddit’ page. Because when we click on a search result, we are taken to another page, this would count as a ‘ReturnClick’ method, due to a new page object being returned.
- Right-click the ‘Pages’ folder, select ‘New File’ and add a new page class file called ‘RedditPage.cs’
- Open up the ‘RedditPage.cs’ class file and edit it so it looks like below (we want it to inherit from ‘SearchResultsPage’ class, as that is where we navigate to the page from when we click through on our search result)…
namespace ProductAutomation.Pages { public class RedditPage : SearchResultsPage { } }
- Add the following By locator on the Reddit page…
using OpenQA.Selenium; namespace ProductAutomation.Pages { public class RedditPage : SearchResultsPage { public By redditContentArea = By.Id("SHORTCUT_FOCUSABLE_DIV"); } }
- Open up the ‘SearchResultsPage.cs’ class file in the ‘Pages’ namespace and add the following By locator in the top of the class for the search results container / content area…
public By SearchResultsContainer => By.Id("links");
The whole thing should look like below with the using directive added too…
using OpenQA.Selenium; namespace ProductAutomation.Pages { public class SearchResultsPage : BasePage { public By SearchResultsContainer => By.Id("links"); } }
- Let’s use this By locator to make an element from it and to add an assertion to the ‘When they view the first result’ step (we will still be adding more at the start of this step definition later), with the whole thing looking like below…
using NUnit.Framework; using OpenQA.Selenium; using ProductAutomation.Pages; using TechTalk.SpecFlow; namespace ProductAutomation.Steps.RedditSteps { [Binding] public class RedditSteps { private SearchResultsPage searchResultsPage = new SearchResultsPage(); [When(@"they view the first result")] public void WhenIViewTheFirstResult() { IWebElement searchResults = searchResultsPage.driver.FindElement(searchResultsPage.SearchResultsContainer); Assert.IsTrue(searchResults.Displayed); } [Then(@"they see the Reddit homepage")] public void ThenTheySeeTheRedditHomePage() { throw new PendingStepException(); } } }
- Next, add another By locator at the top of the ‘SearchResultsPage’ class, for the search result links…
public By SearchResultLinks => By.ClassName("result__a");
If we inspect the element of the first listed result, we can see that every result link returned by DuckDuckGo falls in an <a> anchor tag with the class name of “result__a”.
We will use this ‘SearchResults’ locator in our list of search results element and then click the first result and return the RedditPage class
- Add the following test method within the ‘SearchResultsPage’ class, with the whole thing looking like below…
using System.Collections.Generic; using OpenQA.Selenium; namespace ProductAutomation.Pages { public class SearchResultsPage : BasePage { public By SearchResultsContainer => By.Id("links"); public By SearchResultLinks => By.ClassName("result__a"); public RedditPage SelectFirstListedSearchResult() { IList<IWebElement> searchResults = driver.FindElements(SearchResultLinks); searchResults[0].Click(); return new RedditPage(); } } }
Fully defining the Step Definitions
We will now call the test methods from their appropriate step definitions.
- Open up the ‘SearchSteps.cs’ class file again, and refactor the ‘When I search for “<string>”‘ step so it searches for the given search term…
[When(@"I search for ""(.*)""")] public void WhenISearchFor(string searchTerm) { basePage.SearchFor(searchTerm); }
- Open up the ‘RedditSteps.cs’ class file again, and refactor the ‘When they view the first result’ step so that after the assertion of search results, it calls the SelectFirstListedSearchResult() method…
[When(@"they view the first result")] public void WhenIViewTheFirstResult() { IWebElement searchResults = searchResultsPage.driver.FindElement(searchResultsPage.SearchResultsContainer); Assert.IsTrue(searchResults.Displayed); searchResultsPage.SelectFirstListedSearchResult(); }
- Open up the ‘RedditSteps.cs’ class file again, and refactor the ‘Then they see the Reddit homepage’ step so it asserts that the user is on the Reddit page, with the whole class looking like below…
using NUnit.Framework; using OpenQA.Selenium; using ProductAutomation.Pages; using TechTalk.SpecFlow; namespace ProductAutomation.Steps.RedditSteps { [Binding] public class RedditSteps { private SearchResultsPage searchResultsPage = new SearchResultsPage(); private RedditPage redditPage = new RedditPage(); [When(@"they view the first result")] public void WhenIViewTheFirstResult() { IWebElement searchResults = searchResultsPage.driver.FindElement(searchResultsPage.SearchResultsContainer); Assert.IsTrue(searchResults.Displayed); searchResultsPage.SelectFirstListedSearchResult(); } [Then(@"they see the Reddit homepage")] public void ThenTheySeeTheRedditHomePage() { IWebElement redditPageContent = redditPage.driver.FindElement(redditPage.redditContentArea); Assert.IsTrue(redditPageContent.Displayed); } } }