On browser based testing

March 15, 2017

When searching my mail archive today, I discovered a mail from 2011. I had been discussing browser based testing with someone at a meetup. A couple of days later I sent him an email illustrating what our setup looks like (or looked like).

Since it was basically written as a blog post for an audience of 1 and it’s still relevant, I’ve reformatted and shared it here.

Pasted from mail

Past few days I’ve been thinking about how to show you what we’re doing with testing. Let me show you a few of the things we were talking about:

The Test

This is a test from the print view test suite, where we want to make sure we don’t accidentally show any information in a print view which is not there in the screen view.

[Test]
[Row(Country.Be)]
[Row(Country.Nl)]
[Row(Country.De)]
public void ConfirmationPage_PrintViewDoesntHaveMoreTextThanScreenView(Country country)
{
    Country = country;
    PassHomePage();
    PassPersonalDataPage();
    PassOrderPage();
    ConfirmationPage.RemoveInvisibleElements();

    AssertTextContainsText(ConfirmationPage.CleanText, ConfirmationPage.PrintView().RemoveInvisibleElements().CleanText);
}

You’ll notice a few things:

  • The test is short :)
  • This tests the 4th page in a 4 page flow, the test class has helper methods to pass the first three pages (fill in data, submit)
    • ConfirmationPage is the AccesLayer class which uses Watin to interact with an IE instance
      • ConfirmationPage inherits some general Page properties from Page, and adds the ConfirmationPage specific options
      • Input fields
      • Validation errors
      • Buttons
      • Links
      • Texts
      • We only bother to add the elements we need in a test, KISS and YAGNI
  • The tests never interact with Watin or IE themselves, all goes through the AccessLayer

A Helper

The helper to pass the PersonalData page:

private void PassPersonalDataPage()
{
    PersonalDataPage
        .Gender(Gender.Male)
        .FirstName("Bobejaan")
        .LastName("Svenson")
        .Street("Boswegel")
        .Nr(34)
        .City("Leuven")
        .EMail("noemail.noemail@ichoosr.com")
        .EMailVerification("noemail.noemail@ichoosr.com")
        .Phone("04 444 44 44")
        .IAgree(true);
    switch (Country)
    {
        case Country.Be:
            PersonalDataPage.Zip("3000");
            break;
        case Country.Nl:
            PersonalDataPage.Zip("1111AA");
            break;
        case Country.De:
            PersonalDataPage.Zip("10117");
            break;
        default:
            throw new NotImplementedException();
    }

    PersonalDataPage.Next();
}

You’ll notice:

  • All methods of a Page class (AccessLayer) return the instance PageClass itself, so we can elegantly chain
    • Obviously some methods return another value or a boolean, but all methods that can return the instance, do
  • This makes it very readable
  • We didn’t bother with configurable input data here, YAGNI
  • There is no checking whether any of these interactions with the Page succeeded
    • That is handled by the AccessLayer

The AccessLayer

Now a bit from a Page class (AccessLayer)

The PersonalData page has a form with gender, name, … that kind of stuff
These are the 3 methods of PersonalDataPage involved in setting the gender in the form:

public PersonalDataPage Gender(Gender value)
{
    AssertIsCurrentPage();
    switch (value)
    {
        case iChoosr.Domain.Gender.Female:
            RadioButtonGenderFemale.Checked = true;
            break;
        case iChoosr.Domain.Gender.Male:
            RadioButtonGenderMale.Checked = true;
            break;
        case iChoosr.Domain.Gender.Unknown:
            RadioButtonGenderFemale.Checked = false;
            RadioButtonGenderMale.Checked = false;
            break;
    }
    return this;
}

private RadioButton RadioButtonGenderMale
{
    get
    {
        AssertIsCurrentPage();
        switch (Country)
        {
            case Country.Be://fallthrough
            case Country.Nl:
                return Browser.RadioButton("Meneer");
            case Country.De:
                return Browser.RadioButton("Herr");
            default:
                throw new NotImplementedException();
        }
    }
}

private RadioButton RadioButtonGenderFemale
{
    get
    {
        AssertIsCurrentPage();
        switch (Country)
        {
            case Country.Be://fallthrough
            case Country.Nl:
                return Browser.RadioButton("Mevrouw");
            case Country.De:
                return Browser.RadioButton("Frau");
            default:
                throw new NotImplementedException();
        }
    }
}

You’ll notice:

  • The accessor Gender starts by verifying that we are on the page we think we are, of not, an exception is thrown with a very clear message
    • The finding of the radio buttons and the manipulation of them is separated
  • We find them by label text, which differs for the 3 language versions of the site
  • The value which we pass to Gender() is not a string but an enum, because that happens to be the way we’re representing it in the rest of the code. This makes it look familiar and high level

In Summary

There’s a few other things we do, but this is the gist of what I told you
So basically:

  • We write our tests in C#
  • We write our AccessLayer in C#
  • We’re using Watin to have the AccessLayer interact with IE
  • Our tests use the same testing framework as our unit tests (which we’re already familiar with)