How I Get Manual Testers Writing E2E Automated Tests In C# .NET Using Specflow

Over the past couple of weeks, I’ve been covering how to use Playwright to create E2E tests using C# .NET. Thus far, I’ve covered how to write tests in a purely C# .NET testing way, that is, without BDD or any particular cucumber like syntax. It more or less means that the person writing the tests needs to know a fair bit of C# before they can write tests without getting themselves into trouble. It works, but it’s not great.

Next in the Playwright series, I was going to cover how you can use Specflow with Playwright to make it dead simple for your QA team to write E2E tests, without any knowledge of C# at all. But half way through writing, I realized that many of the topics covered weren’t about Playwright at all, it was more about my personal methodology on how I structure tests within Specflow to better enable non technical people to get involved.

Now I’m just going to say off the bat, that I *know* that people will hate the way I structure my tests. It actually runs counter to Specflow’s own documentation on how to build out a test suite. But reality often does not match the perfect world that the docs portray. And so this writeup is purely if you are a developer getting your manual testers into the groove of writing automated tests.

What Is Specflow?

Specflow is a testing framework that transforms your tests to use “Behavior Driven Development” (BDD) type language to build out your test suite. In simple terms, it takes the language of

Given
When
Then

And turns them into automated tests.

That’s the holistic view. The more C# .NET centric view is that it’s a Visual Studio addon, that maps BDD type language to individual methods. For example, if I have a have a BDD line that says this :

Given I am on URL 'https://dotnetcoretutorials.com'

It will be “mapped” to a C# method such as

[Given("Given I am on '(.*)'")]
public void GivenIAmOnUrl(string url)
{
    _driver.NavigateToUrl(url);
}

The beauty of this type of development is that should another test require a similar type of navigation to a URL, you simply write the same BDD line and it will map to the same method automatically under the hood.

In addition, Specflow comes with other pieces of the testing framework such as a lightweight DI, a test runner, assertions library etc.

The thing about Specflow however, is that it in of itself does not automate the browser at all. It actually passes that off to things like Selenium or Playwright, you can even use your favorite assertion library like NUnit, MSTest or XUnit. Specflow should be seen as a lightweight testing framework that enforces BDD like test writing, but what goes under the hood is still largely up to you.

I don’t want to dig too deep into getting Specflow up and running because the documentation is actually fairly good and it’s more or less just installing a Visual Studio Extension. So download it, give it a crack, and then come back here when you are ready to continue.

The Page Object Model

Specflow (And more specifically Selenium) typically suggest a “Page Object Model” pattern to encapsulate all logic for a given page. This promotes reusability and means that all logic, selectors, and page behaviour is located in a single place. If across tests you are visiting the same page and clicking the same button, it does make sense to encapsulate that logic somewhere.

Let’s imagine that I’m trying to write a test that goes to Google and types in a search value, then clicks the search button. The “Page Object Model” would look something like this :

public class GooglePageObject
{
    private const string GoogleUrl = "https://google.com";

    //The Selenium web driver to automate the browser
    private readonly IWebDriver _webDriver;

    public GooglePageObject(IWebDriver webDriver)
    {
        _webDriver = webDriver;
    }

    //Finding elements by ID
    private IWebElement SearchBoxElement => _webDriver.FindElement(By.Xpath("//input[@title='Search']"));
    private IWebElement SearchButtonElement => _webDriver.FindElement(By.Xpath("(//input[@value='Google Search'])[2]"));

    public void EnterSearchValue(string text)
    {
        //Clear text box
        SearchBoxElement.Clear();
        //Enter text
        SearchBoxElement.SendKeys(text);
    }

    public void PressSearchButton()
    {
        SearchButtonElement.Click();
    }
}

This encapsulates all logic for how we interact with the Google Search page in this one class. To follow on, the Specflow steps might look like so in C# code :

[Given("the search value is (.*)")]
public void GivenTheSearchValueIs(string text)
{
    _googlePageObject.EnterSearchValue(text);
}

[When("the search button is clicked")]
public void WhenTheSearchButtonIsClicked()
{
    _googlePageObject.PressSearchButton();
}

Simple enough! And our Specflow steps would obviously read :

Given the search value is ABC
When the search button is clicked
[...]

Now this obviously works, but here’s the problem I have with it.

The entire test has more or less been written by a developer (Or an automated QA specialist). There is no way that the encapsulation of this page could be written in C# by someone who doesn’t themselves know C# a decent amount. Additionally, while not pictured, there is an entire dependency injection flow to actually inject the page object model into our tests. Can you imagine explaining dependency injection to someone who has never written a line of code in their lives?

Furthermore, let’s say that on this google search page, we wish to click the “I’m feeling lucky” button in a future test

The addition of this button being able to be used in tests requires someone to write the C# code to support it. Of course, once it’s done, it’s done and can be re-used across tests but again.. I find it isn’t so much testers writing their own automated tests as much as developers doing it for them, and a QA slapping some BDD on the top at the very end.

Creating Re-usable BDD Steps

And this is where we get maybe a little bit controversial. The way I format tests does away with the Page Object Model pattern, in fact, it almost does away with some parts of BDD all together. If I was to write these tests, here’s how I would create the steps :

[Given("I type '(.*)' into a textbox with xpath '(.*)'")]
public void WhenITypeIntoATextboxWithXpath(string text, string xpath)
{
    _webDriver.FindElement(By.Xpath(xpath)).SendKeys(text);
}

[When("I click the button with xpath '(.*)'")]
public void WhenIClickTheButtonWithXpath(string xpath)
{
    _webDriver.FindElement(By.Xpath(xpath)).Click();
}

And the BDD would look like :

Given I type 'ABC' into a textbox with xpath '//input[@title="Search"]'
When I click the button with xpath '(//input[@value="Google Search"])[2]'
[...]

I can even create more simplified steps based off just element IDs:

[Given("I type '(.*)' into a textbox with Id '(.*)'")]
public void WhenITypeIntoATextboxWithId(string text, string id)
{
    _webDriver.FindElement(By.Id(id)).SendKeys(text);
}

By creating steps like this, I actually have to write very minimal C# code. I’ve even created steps that are “When an element ‘div’ has a property ‘class’ of value ‘myClass'”. Now instead of having to front load a tonne of C# training to my manual QA, I instead teach them about XPath. I give a nice 1 hour lesson on using Chrome Dev Tools to find elements, show them how to test whether their XPath will work correctly, and away we go.

Typically I can spend a day creating a bunch of re-usable steps, and then testers only ever have to worry about writing BDD style Given/When/Then that uses existing selectors.

The Good, The Bad, The Ugly

When it comes to looking at the good of this test writing strategy, it’s easy to see that enabling testers to focus on writing BDD and actually coming up with the test scenarios allows immature or non technical QA teams to start writing tests from Day 1.

It does come at a cost however. BDD is designed that the same test scenarios a tester would run manually, can essentially be cloned to be automated. However a human would never write a test that specifically calls out XPath in the test steps, nor would they realistically be able to execute a test manually that had XPath littered throughout the test description.

For me, getting testers into the mode of writing automated tests without the overhead of learning C# far outweigh any loss of readability, and that’s why I continue to create test suites that follow this pattern.

Leave a Comment