Winning the selenium / angular js battle with protractor-net and Custom Finders

Hackered
Monday, April 6, 2015
by Sean McAlinden

One of the apps I am currently working on is a mixture of predominantly straightforward html forms with a number of Angular JS driven typeaheads and lists thrown into the mix.

The UI acceptance tests so far have been written in C# using SpecFlow and Selenium Web driver utilising the page object pattern.

Whilst Angular JS has been well worth it on the front end, it has caused a whole bunch of intermittent issues within my test stack.

Initially we were thinking we may have to scrap our tests and move to protractor or write some clever hooks into the Angular JS bindings, luckily a colleague came across Protractor-Net by Bruno Baia who has already done the hard work for us, we now interact nicely with Angular JS in our tests from C#.

As I am already using attributes from the Selenium WebDriver Support Classes to drive my page objects I did not want to change course so I had to come up with a way of utilising Protractor-Net with my attribute driven page objects.

Introducing the CustomFinder hook from Selenium WebDriver Support Classes 

A CustomFinder allows us to hook into the Selenium Webdriver [FindsBy] attribute and run custom code to locate the page elements.

I am going to create a simple CustomFinder which utilises the methods provided by Protractor-Net.

Before I forget, you must use the NgWebDriver so don't forget to hook it up, it's very easy: just pass your current WebDriver instance into it's constructor:

var driver = new ChromeDriver();
var ngDriver = new NgWebDriver(driver);

Now that's in place let's create the first CustomFinder...

NgByModelFinder

Ensure you have the correct NuGet packages installed (Selenium WebDriver Support Classes & Protractor-Net)  and then create the following class:

public class NgByModelFinder : By
{
    public NgByModelFinder(string locator)
    {
        FindElementMethod = context => Driver.Current.FindElement(NgBy.Model(locator));
    }
}

As you can see, I've created a simple finder that utilises the NgBy.Model method from Protractor-Net.

I can now update my angular typeahead property attribute with this custom finder and gain the benefits of the protractor bindings:

[FindsBy(How = How.Custom, CustomFinderType = typeof(NgByModelFinder), Using = "my.angular.binding")]
public IWebElement MyTypeAhead { get; set; }

As you can see I've set the FindsBy How property to How.Custom, I've set the CustomFinderType to our new finder and set the ng-models value "my.angular.binding" to the Using property.

NgByRepeaterFinder

This next CustomFinder is for locating a list of elements created by an Angular JS repeater, it is very similar to the previous finder just with some very subtle differences:

public class NgByRepeaterFinder : By
{
    public NgByRepeaterFinder(string locator)
    {
        FindElementsMethod = context => Driver.Current.FindElements(NgBy.Repeater(locator));
    }
}

As you may have noticed, I am now setting the FindElementsMethod (notice the pluralised Elements) and I am also utilising the NgBy.Repeater method.

I can now use this CustomFinder in my page object class to find a collection of web elements:

[FindsBy(How = How.Custom, CustomFinderType = typeof(NgByRepeaterFinder), Using = "item in search.model.items")]
public IList<IWebElement> SearchResults { get; set; }

Summary

There are a few more methods we can use in Protractor-Net which can be implemented for use with the Selenium WebDriver Support Classes in the exact same way, however this post is probably long enough already and hopefully you have seen it is very simple to accomplish.

Happy testing.