BDDfy your browser testing with FluentAutomation

I have been using Cucumber/Watir/Ruby for browser automation and behavior tests. It has worked great so far. Lack of something like Cucumber in .NET world had always baffled me and then SpecFlow happened. SpecFlow works great within .NET eco-system but has it's own overheads like Cucmber does. For example, having to generate lot of unncessary code so that scenario file can match with the executable step defintions, has some maintenance overhaed. Do not get me wrong, when I use the work "overheads". They are features of those tools and they are there for a reason. Recently I came across a library called BDDfy which offers a frictionless alternative to SpecFlow in .NET world.

If you have never used tools like Cucumber/SpecFlow and are new to BDD then I would suggest reading about it here.

BDDfy

Last week I came across this nice little library called BDDfy (pronounced as B D Defy. BDDfy lets you write tests in Given When Then format without have to learn a great deal of things about a new framework.

If this is first time you have heard of BDDfy, I would recommend going through their docs to understand what the library offers. Here is a simple and complete exmaple of a test written using MSTest and BDDfy. The test is to withdraw money from ATM.

[TestMethod]
public void AccountHasSufficientfund()  
{
    new AccountHasSufficientFund()
        .Given(s => s.GivenTheAccountBalanceIs(100), "Given the account balance is $100")
            .And(s => s.AndTheCardIsValid())
            .And(s => s.AndTheMachineContainsEnoughMoney())
        .When(s => s.WhenTheAccountHolderRequests(20),
    "When the account holder requests $20")
        .Then(s => s.ThenTheAtmShouldDispense(20), "Then the ATM should dispense $20")
            .And(s => s.AndTheAccountBalanceShouldBe(80),
        "And the account balance should be $80")
            .And(s => s.AndTheCardShouldBeReturned())
        .BDDfy();
} 

When this test is run, you get an following output

Secnario:Account has sufficient fund  
    Given the account balance is $100
        And the card is valid
        And the machine contains enough money
    When the account holder requests $20
    Then the ATM should dispense $20
        And the account balance should be $80
        And the card should be returned

Nice and easy, isn't it?

FluentAutomation (F14N)

FluentAutomation by@stirno is the best fluent API for interacting with browser using Selenium/WatiN I have come across. If you want a quick overview of what this library is capable of, have a look at this blog post from Scott Hanselman (You can always look at the library documentation if you fancy). Some of the hlighlights of this library are

  1. Fluent and easy to learn API
  2. Ability to share browser instances with single line of code
  3. Ability to run tests on multiple browsers with single line of code
  4. Very nice method chaining
  5. OOB support for PageObject pattern
  6. Integration with scripcs

Just to show how succinct your tests can become with this library, here is a simple example from their documentation

I.Open("http://automation.apphb.com/forms")  
    .Select("Motorcycles").From(".liveExample tr select:eq(0)")
    .Select(2).From(".liveExample tr select:eq(1)")
    .Enter(6).In(".liveExample td.quantity input:eq(0)")
    .Expect
        .Text("$197.72").In(".liveExample tr span:eq(1)")
        .Value(6).In(".liveExample td.quantity input:eq(0)");

Putting BBDfy and F14N together

Combination of BDDfy and F14N can be a nice replacement for Cucmber or SpecFlow. If you have experience of using either Cucmber or SpecFlow then you may have figured out by now how to put these two libraires to use together. For those who are new to this area, I have a simple facebook login scenario below.

This post turned out to be unexpectedly long. I have tried to cut down unncessary code examples. Full code sample for this post is uploaded to github

Scenario: Cannot login to facebook with invalid crdentials  
    Given I am on facebook login page
      And I login using email 'a@b.com' and password 'password'
    Then shows message Incorrect email address

This scenario is trying to login to facebook using an invalid email and password and confirms that an error messages for incorrect email address is displayed. A skeleton for implementing this scenario using BDDfy is below

using NUnit.Framework;  
using TestStack.BDDfy;

TestFixture]  
public class FacebookLoginStory  
{

        [Test]
        public void CannotLoginToFacebookWithInvalidCrdentials()
         {
             new FacebookLoginStory()
                 .Given(s => s.GivenIAmOnFacebookLoginPage())
                 .And(s => s.AndILoginUsingEmailAndPassword("a@b.com", "password"), "And I login using email '{0}' and password '{1}'")
                 .Then(s => s.ThenShowsMessage("Incorrect email address"), "Then error message '{0}' is displayed")
                 .BDDfy();
         }

        private void ThenShowsMessage(string message)
        {
        }

        private void AndILoginUsingEmailAndPassword(string email, string password)
        {
        }

        private void GivenIAmOnFacebookLoginPage()
        {
        }
}

If you have made yourself familiar with BDDfy by now, then I do not have to explain you how the above code works. If you are lazy like me, here is a breif explaination. Adding namespace TestStack.BDDfy makes some extension methods avaialble to your code, namely, Given, When, Then, And and BDDfy. Given, When, Then & And, in their simplest form accept a lamba method that you can define in your test class. Call to BDDfy in the end chains all the lambads together in correct order. If you are from Cucumber background, then these lambads are equivalent to step definitions.

As you can see, the lambads can be parameterised. Being able to pass different parameters to step definitions makes them reusable from different steps. This is a nice way of keeping your test code succict.

To actually interact with a browser instance and navigate to facebook login page, key in email and password and login, we would use F14N API. Here are completed steps using F14N

private const string EmailInput = "input[id='email']";  
private const string PasswordInput = "input[id='pass']";  
private const string LoginButton = "input[value='Log in']";

private void GivenIAmOnFacebookLoginPage()  
{
    I.Open("http://www.facebook.com");
}

private void AndILoginUsingEmailAndPassword(string email, string password)  
{
    I.Enter(email).In(EmailInput);
    I.Enter(password).In(PasswordInput);
    I.Click(LoginButton);
}

private void ThenShowsMessage(string message)  
{
    I.Assert.Text(message);
}

In first method, we tell browser to navigate to http://www.facebook.com. In second method, we key in email, password and click submit button. In last method, we verify that particular message is present on page.

Lets add some Page Object flavor

Steps defined in the above examples are reusable but not to a large extent. What I mean is, I can write multiple tests in the FacebookLoginStory class using method AndILoginUsingEmailAndPassword in order to test various login scenarios. But if I want to use login as a step in some other larger scenario that I am testing, then this method hidden in a class is not much useful. It is more like a black-box. It does logs you in but you the pieces it uses in order to achieve that are all within the method body and cannot be easily reused outside of the containing class. This is where page object pattern shines. Anf guess what, F14N has native support for page object pattern. There is an example of this on F14N's website but we would be turning our facebook example into one that uses page object pattern.

In simple terms, for page object pattern, you define a class for every page of the webiste that you are testing. In our facebook example, we would define a class to represent facebook login page1. After a member logs in, she is navigated to his timeline. We can define another page to represent user's timeline. In order for your class to become a page object, you would need to inherit from a base class PageObject<T>. The exammple in the documentation on F14N site seems to be out of date. When I tried inheriting from non-generic PageObject class, it did not work

Here is how our page objects looks

public class FacebookLoginPage : PageObject<FacebookLoginPage>  
{
        public FacebookLoginPage(FluentTest test) : base(test)
        {
            Url = "http://www.facebook.com";
            At = () => I.Expect.Exists(EmailInput);
        }
}

public class FacebookHomePage : PageObject<FacebookLoginPage>  
{
        public FacebookHomePage(FluentTest test) : base(test) {}
}

Page object pattern is a big topic in itself and warrants a separate article. I would try to briefly explain what above lines of code are doing. Let's see how much justice I can do to it.

  1. FacebookLoginPage inherits from PageObject<FacebookLoginPage>. You are passing the self type as T to base. This in order for F14N to know what is the type of current page object
  2. In the constructor, we set the Url property to http://facebook.com. This seerves two purposes, one, if you instantiate this class and call Go method on the PageObject<T> class then current browser instance would be navigated to this url. Second, if you create an instance of this class and you can assert that url in current browser instance is this url.
  3. Second line in the constructor, we are assigning a lambda to property At. This lambda is executed after you call Go in order to navigate to this page. The outcome of the lamda determines that browser has indeed navigated to the page correctly. If the expectation set in At lambda cannot be met, then F14N would throw and exception telling you that navigation has failed.

FacebookHomePage class does not have much in terms of functionality as I do not need it do anything for the purpose of my tests. In the true TDD sprit, I would add more code to it as I go along.

Next, we add a Login method to the login page object. In this method, we move the code from our AndILoginUsingEmailAndPassword step in previous section. After some simple refactoring this is how our FacebookLoginPage class looks

public class FacebookLoginPage : PageObject<FacebookLoginPage>  
    {
        private const string EmailInput = "input[id='email']";
        private const string PasswordInput = "input[id='pass']";
        private const string LoginButton = "input[value='Log in']";

        public FacebookLoginPage(FluentTest test) : base(test)
        {
            Url = "http://www.facebook.com";
            At = () => I.Expect.Exists(EmailInput);
        }

        public FacebookHomePage Login(string email, string password)
        {
            I.Enter(email).In(EmailInput);
            I.Enter(password).In(PasswordInput);
            I.Click(LoginButton);

            if (I.Find(LoginButton) != null) return null;

            return Switch<FacebookHomePage>();
        }
    }

With this in place, whenever you need to be able to login in order to test logged in user journeys, all you have to do is, create an instance of this class, call Login method with correct email and password and you get a handle back to home page. Step definitions from the previous section can use this class. I am not going to provide the code for that as this post has already become too long. All the code from the post can be found on github

In closing

IMO, this approach has two advantages. First, I can use my existing C# skills to automate browser tests. This is a big boost to productivity. Second, I can now see my browser tests, along with my unit/integration tests in the same solution. This is not a big plus, but it does save me from opening another IDE and thus some memory.

One drawback, which can be big one, is that only developers have access to scenarios. Because scenarios are now coded into your test classes, other team memebrs like business analysts and testers would have difficulty in reading, understanding and modidying scenarios. For teams that are mature in their agile practices, this can be a big put off. But if you do not have need for non-techies to see or change scenarios, then this is a good option.

footnotes

1. The usual login page for facebook and the one they redirect you to in case of errors are different. You might want to model them as two different page objects depending on what kind og interaction you need to do with those pages. I have kept it simple for the purpose of this post.

Suhas Chatekar

Discussion

  • Comment with Disqus
  • Comment with Facebook
comments powered by Disqus