Starting Out With UI Automation Using SpecFlow

By Louise Gibbs

UI Automation can be slow, clunky and prone to error. Future maintenance, on a changing code base, is inevitable. Setting up tests, fixing them when they break, and adapting them as the software changes can be a huge challenge.

It sounds like hell, but I love it! It’s like a series of puzzle’s that needs to be solved - and I love puzzles. If you buy a puzzle book from your local newsagents, the puzzles at the start of the book will be a lot easier than the ones at the end of the book. If you are a complete novice at that type of puzzle, then it is only sensible to start from the beginning with the easier puzzles.

Regardless of prior automation experience, if you are moving onto a new project then you should be approaching it like a novice. Start by identifying some ‘easy’ tests first. I always start with a test that launches the application. If you can’t do this, then all tests you write will be useless.

Next, focus on creating basic tests that navigate around the application, open specific pages and perform some basic actions. Button presses are pretty straight forward, so are a good place to start. When starting out, don’t think so much about the tests you will need long term but think more about creating tests that ‘work’ and build them up from there. Add the more complex actions later once you’ve got the basic framework in place and have built up some confidence.

In this example, I’m going to talk through how I’d get starting on a completely new UI Automation project.

Pre-requisites - Setting up SpecFlow and Visual Studio

The examples used in this article are taken from tests developed using SpecFlow in Visual Studio. The code is written in C#.

There is a link to my GitHub repository in the useful links section, which includes some example SpecFlow tests that have been setup. You can use this as a guide when creating your own solution, or you could practice adding your own tests first to really get used to the structure detailed in this article. Tests are added by adding ‘feature’ files, and the steps inside each step are kept in ‘step definition’ files. There will be more about this in Step 3 of this article.

After creating a project (or using mine from GitHub), You’ll need to install some NuGet packages. This can be done by selecting Tools > NuGet Package Manager > Manage NuGet Package Manager for Solution.

NuGet packages which I have installed include: 

  • Microsoft.Extensions.Configurations.Json

  • Microsoft.NET.Test.Sdk

  • Selenium.Support

  • Selenium.WebDriver

  • Selenium.WebDriver.ChromeDriver

  • Specflow

  • Specflow.Tools.MsBuild.Generation

  • SpecFlow.xUnit

  • Xunit

  • Xunit.core

  • Xunit.runner.visualstudio

You’ll also need to install the following extension. This can be done by clicking Tools > Extensions and Updated. 

  • SpecFlow for Visual Studio 2017

Step 1 - Launch an application using Automation

This is the most important test you could write. It only requires 1 step, but this step will be used on every single test you develop. Let’s face it, an application that can’t be launched is going to be pretty useless.

As I said before, this test is not likely to be used in the final solution. However, having this work in place will benefit all tests that you eventually develop. Also, having just one simple test that passes can be a huge confidence boost.

The following code can be used to launch Google Chrome, navigate to the chosen URL (GoToUrl) and maximise the window (Window.Maximise()) (so you can actually see what is happening on the browser and so all tests start with a known browser size).

public static IWebDriver Driver { get; set; }

 

public static void InitializeTest()

{

    Driver = new ChromeDriver(Environment.CurrentDirectory);

    Driver.Navigate().GoToUrl("https://automationintesting.online/");

    Driver.Manage().Window.Maximize();

}

In Visual Studio, you can assign attributes to methods. Attributes can be used to apply a specific property or characteristic to a class. They can be added to the start of a class or method, within square brackets. When creating Specflow tests, they can be used to apply steps within a scenario to a particular method (there will be more about this in step 3 when we create a step binding). Attributes can also be used to state which method should be run before and after each scenario without the need to add that step to each scenario.

The [BeforeScenario] and [AfterScenario] attributes indicate code that must be run before and after the scenario has been run. In this case, the InitializeTest() method will be run which will open the Chrome browser and navigate to the webpage. Once the test is run, the driver will close the browser window. This will prevent multiple browsers being left open after each test.

[BeforeScenario]

public void StartScenario()

{

    InitializeTest();

}

 

[AfterScenario]

public void AfterScenario()

{

    TestUtilities.Driver.Close();

}

 

Step 2: Basic test script

In SpecFlow, instead of tests we have ‘Scenarios’. These scenarios use the Gherkin syntax where each step starts with the words ‘Given’, ‘When’ or ‘Then’. More information on how to setup SpecFlow scenarios using the Gherkin syntax can be found using this link.

For now we are going to write a step that allows the user to complete the Contact Us form that appears on the home page. The first thing I would do is write out the Gherkin script for the scenario. It allows me to plan out what I actually want the script to do. One of the benefits of Gherkin is that it provides a friendly script which anyone can understand, regardless of technical ability. Everyone can understand what the test should do.

This is what the scenario might look like:

Scenario: Complete contact us form

When I submit some details in the contact details form

Then I should be told that the form was submitted

In SpecFlow, purple text indicates that the step cannot be found (we haven’t actually created these steps yet - we do that in Step 3 below). This is ok for now, the test will still run but it will fail because it can’t find the method that is bound to this step.

Step 3: Creating the scenario step binding

We have the steps written out, we now have to bind those steps to some code (that will actually interact with the browser if needed). If you right click on a step, and the step already exists, you will be taken to that step. If it doesn’t exist, you should see this message appear:

If you click ‘yes’, the code required for the step to be created will be copied to the clipboard (which can save a lot of time when you are binding a lot of steps!) 


This is the message that appears when you attempt to navigate to a step binding when the step hasn't yet been created. The message informs you that the binding does not exist, and asks if you want to add the step binding skeleton to the clipboard.

When you paste the code, it will look something like this: 

[When(@"I submit some details in the contact details form")]

public void WhenISubmitSomeDetailsInTheContactDetailsForm()

{

    ScenarioContext.Current.Pending();

}

If you return to the SpecFlow scenario, the step should have turned white or black (depending on if you have dark or light mode enabled). The test will still fail, as you still need to add some actual code to the method, but the test will still have something to look for when it runs that particular step.

You will need to do this for every step in the scenario that is unbound.

It is important to remember that the steps are case sensitive. If you are adding this step to another scenario, the wording (including the case of the letters) needs to be exactly the same.

Step 4: How to Set up locators

Before writing the code for each step, you need to set up the locators.

Locators are used to find elements that appear on the page. Without these, we can’t interact with anything on the page.

What I like to do is write down a list of elements that I will need to interact with for each step.

For the ‘Contact Us’ form, we will need to setup a locator for each text box in the form and the submit button. 

If you right click on an element and click ‘inspect’, you will see the information related to that element in the DevTools:

 A small section of the xpaths belonging to the elements found in the contact us form on the restful booker form. This can be viewed by right clicking on an element, and selecting 'inspect' from the menu.

We use the function FindElement() to search for a specific xpath.

The xpath has to be in a very specific format:

“//tagname[@Attribute=’value’]”

If we are trying to locate a specific element, the tag name is the first word in the element code. For example, this could be ‘form’, ‘input’ or ‘div’. The attribute can be anything that is assigned to the element. For example, ‘text’ or ‘class’. The value has to match the attribute value for that element.

You can search for a specific element, and then try to locate an element inside that element. To do this, you can add additional xpaths to the end of another xpath.

These are the locators that I set up using information found in the DevTools.

public IWebElement ContactSection => Driver.FindElement(By.XPath(".//div[@class='row contact']//div[@class='col-sm-5']"));

 

public IWebElement FormSection => ContactSection.FindElement(By.XPath("//form"));

public IWebElement NameTextBox => FormSection.FindElement(By.Id("name"));

public IWebElement EmailTextBox => FormSection.FindElement(By.Id("email"));

public IWebElement PhoneTextBox => FormSection.FindElement(By.Id("phone"));

public IWebElement SubjectTextBox => FormSection.FindElement(By.Id("subject"));

public IWebElement MessageTextBox => FormSection.FindElement(By.Id("description"));

public IWebElement SubmitButton => FormSection.FindElement(By.Id("submitContact"));

In the first example, ‘ContactSection’, we search for an element with the tagname ‘div’ which has the attribute ‘class’ which equals ‘row contact’. An additional xpath has been added to the end of the original xpath, searching for another element within the first element. This second element we’re searching for has a tagname ‘div’ which has an attribute ‘class’ that equals ‘col-sm-5’. 

public IWebElement ContactSection => Driver.FindElement(By.XPath(".//div[@class='row contact']//div[@class='col-sm-5']"));

The next element, FormSection, also existed within the ContactSection. So instead of looking through the entire webpage, we only want to search within the ContactSection. Only 1 form exists in this section, so we only need to look for an element with the tagname ‘form’.

public IWebElement FormSection => ContactSection.FindElement(By.XPath("//form"));

Avoiding excessively long xpaths makes the tests easier to maintain in the future. It also reduces the amount of duplicate code. If I were to use a single xpath for the FormSection element, it would look like this:

".//div[@class='row contact']//div[@class='col-sm-5']//form"

For other elements that appeared on the page, we’ve only used the IDs to identify the element. These also appear as attributes in devtools, but you can locate elements using just the IDs if they are available. It makes the locators simpler and easier to understand.

The following locator, which we’ve setup using the IDs, could also be setup using an xpath. Either of the following examples will identify the same element.

FormSection.FindElement(By.Id("name"));

FormSection.FindElement(By.XPath("//input[@id=’name’]"));

I like to make XPaths as simple as possible, using only the most essential information. Too much detail, and the code becomes hard to understand and difficult to maintain. This becomes particularly problematic when more developers start working on the tests. They also need to be complex enough to reduce the risk of the wrong element being identified. If more than 1 element on the page matches the xpath, then it will automatically choose the first 1 it finds. If there are 2 buttons, then the wrong button could be clicked.

It is always good to experiment with different xpaths and also consider alternative locator strategies such as CSS Selectors. Links to some useful cheat sheets have been provided in the resources section which contain information on how to setup locators using xpaths or CSS selectors.

Step 5: How to write code for scenario steps using locators

Now we have the locators setup, we can use them to write the code for the step.

We can insert text into a textbox by taking the element and sending keys:

NameTextBox.SendKeys("Louise");

We can click on a button by taking the element and clicking on it: 

SubmitButton.Click();

We just need to add these lines of code to the step binding. Here is the complete code for the step where we complete the ‘Contact Us’ form. 

[When(@"I submit some details in the contact details form")]

public void WhenISubmitSomeDetailsInTheContactDetailsForm()

{

    NameTextBox.SendKeys("Louise");

    EmailTextBox.SendKeys("test@test.com");

    PhoneTextBox.SendKeys("01234567890");

    SubjectTextBox.SendKeys("Subject");

    MessageTextBox.SendKeys("This is a message about booking a room");

 

    SubmitButton.Click();

}

Step 6: Confirm the test has passed

The next step in our test is required to confirm that the form was actually submitted successfully. Since this article is about UI tests, we’re going to confirm that a message has appeared on the screen. This step is an important step because if this is wrong, then the test is less valuable.

To confirm the test has passed, we are going to check for the message ‘Thanks for getting in touch’ which appears on the page once the form has been submitted by the user.

To create this step, you follow the same steps as the step to complete the form. 

 

  1. Create the step binding: 

[Then(@"I should be told that the form was submitted")]

public void ThenIShouldBeToldThatTheFormWasSubmitted()

{

    ScenarioContext.Current.Pending();

}

  1. Setup the locator

For this we needed the message header that appeared in the contact us section: 

public IWebElement HeaderText => ContactSection.FindElement(By.XPath(".//h2"));

  1. Write the code for the step using the locators

[Then(@"I should be told that the form was submitted")]

public void ThenIShouldBeToldThatTheFormWasSubmitted()

{

    Assert.Contains("Thanks for getting in touch", HeaderText.Text);

}

An alternative way to check that the message was submitted successfully could be to query an API or the database to confirm the correct information was submitted. You could also login to the admin page and see if the form appears in the list of messages. You could also add some more rigorous checks, like making sure the correct name is used in the message and the full message (not just the header) matches what is expected to appear. If you start with the simplest checks, and get this working, you can expand and improve the test in the future.

Summary

These are the basic steps I follow when developing a new automated test. For more complicated tests, I still start small. I make sure that I have a basic test that ‘works’ and then gradually build up the complexity until I have something that includes all the required steps and checks.

I follow this approach even if I have been asked to work on an already established project. Setting up simple tests is a great way to get to know the code. One of the benefits of SpecFlow is that you have the infrastructure in place, for example I start by writing some simple tests using the various functions and methods that are already written. The first tests you write might not be used in the final product, but gaining that initial familiarity makes developing and maintaining more complex tests a lot easier.

The initial test may not be perfect. It probably doesn’t cover all the required steps and checks. It’s likely to even have bugs in it (all code, including automated tests, can have bugs in them) which increase the risk of false positives. You can fix these later as you improve the test. Sending out code reviews to other testers and developers on your team is a great way to get feedback. It helps you improve your programming skills as the team can suggest more efficient ways of writing code. It is also nice to get the whole team involved as they can suggest ways to improve the tests and increase their value.

Useful Resources

A link to my GitHub repository. It includes some basic tests I’ve written for the RestfulBookerPlatform website. 

https://github.com/LouiseJGibbs/RestfulBooker

RestfulBookerPlatform website, a useful website for practicing setting UI automated tests.
https://automationintesting.online/

SpecFlow website, containing some useful resources on how to setup SpecFlow scenarios
https://specflow.org/

More information about the Gherkin syntax used when writing SpecFlow scenarios

https://specflow.org/bdd/gherkin/

XPath cheat sheet

https://devhints.io/xpath

CSS Locators cheat sheet
https://www.freecodecamp.org/news/css-selectors-cheat-sheet/