Paper vs. Steel #1

I want to share my approach to making your page object methods a bit more "less flaky" but also easier to maintain and more flexible. Am not going to try and cover everything in one post, so here is the first.

Populating a field on a page.

Excellent, we all know how to do this.
So if we run this, unless we get an exception, we just expect the username field is now populated.
Well lets change it to a bool so our tests have something to assert against.

So now, we can assert this method returns true, and just let an exception inform us that it has failed.
Still not great, we are either going to get true or exception, so lets add a try/catch so we can catch exceptions and return false.

So now we would be able to use this method for positive and negative testing, if we were expecting an exception, our test would be asserting a false response.

But what about the different exceptions we could receive, if we are always returning false, we don't know what caused it, so for a start we could output this to the console, this introduces a manual task, however I will post about more complex approaches in different post.


So making it a bit more robust, we know we have to interact with an element, so why don't we wait for that element first, if its there, we carry on if not lets wait a bit.

So we have introduced a few new things in this code snippet, firstly I have included my locators dictionary, this approach can be found here. Secondly, I have included a WebDriverWait object, this can be found in the "OpenQA.Selenium.Support.UI" name space.
I have added the time to wait as a parameter to the method, I do this as sometimes you might be testing the speed of the application, other times you may want to use the global timeout, this gives you that flexibility. Also added an error message for when this wait expires, providing specific error to reduce investigation time upon a failure.

So we now wait, and can catch if there is a problem, but we can do more.
Lets check that the element is enabled, you might say, why wouldn't it be, who knows, but by doing this we can provide a specific message again reducing investigation time.

Ok, so we are getting there, but how do we know the field is now populated with what we asked WebDriver to populate it with, we don't, so lets check that as well.

You will see that I have also added to clear the field before populating it, not everyone will want this, but it something to consider, depending on your applications behaviour. You could have a method to clear it first.

So as you can see there are several things we do to reduce flakiness, and also reduce investigation time.
So I also said maintenance, well obviously we aren't going to write all this everytime we want to populated a field, so we extract this to a library or I have added this to my default page object as a protected method, a few tweaks and we end up with this:

We can than call this method from our page objects, as all with have access to it, as they all inherit from DefaultPage.

As mentioned, there is better approach to handling exception, but hopefully you get an idea of what else you can do to reduce flakiness in some of your methods, but also reduce investigation time with failures, with smarter reporting.

I have deliberately called this post Paper vs, Steel, paper was just chosen as I was trying to thing of something weak, and there is always a pile of it on my desk, as its my favourite testing tool,

Then Steel, its a very strong material, but not the strongest, so while I think my approach is strong, I know its not the best, so if you can see a way I can improve it, then let me know, and perhaps future posts can be about titanium methods!

2 comments:

  1. On the other hand, if you take the position (as I do) that the method's purpose is to successfully populate a field, then all that "try/catch/return false" stuff vanishes in a puff of logic, and the method either succeeds or throws an exception. That's a good thing, because then the test has to actively ignore it (via try/catch/ignore) instead of passively (via not checking its result). Something like this:

    /// <summary>
    /// A method for populating a txt field
    /// </summary>
    /// <param name="identifier">The locator used to find the text field</param>
    /// <param name="value">The value you wish to populate the field with</param>
    /// <param name="timeout">How long WebDriver should wait for the element to appear</param>
    /// <param name="friendlyRef">A friendly name for the field, used for error reporting</param>
    /// <returns>True is successfully populated</returns>
    protected void PopulateField(By identifier, string value, int timeout, string friendlyRef)
    {
    WebDriverWait waitForField = new WebDriverWait(WebDriver, TimeSpan.FromSeconds(timeout));
    waitForField.Message = string.Format("Timed out after waiting {0} seconds for the {1} field using {2} identifier", timeout, friendlyRef, identifier.ToString());
    waitForField.Until(d => d.FindElement(identifier));

    if (WebDriver.FindElement(identifier).Enabled == false)
    {
    throw new Exception(string.Format("The {0} field was not enabled", friendlyRef));
    }

    WebDriver.FindElement(identifier).Clear();
    WebDriver.FindElement(identifier).SendKeys(value);

    string txtValue = WebDriver.FindElement(identifier).GetAttribute("value");

    if (txtValue != value)
    {
    throw new Exception(string.Format("The {0} field doesn't contain {1}, it contains {2}", friendlyRef, value, txtValue));
    }
    }

    ReplyDelete
  2. Hey Ross, Funny you should say this, if you were to look at my current code base, I do actually throw exceptions, however as I writing this, I decided returning false, might be beneficial, instead of having separate methods for negative responses. But I could assert that the exception was thrown.

    Perhaps, guess the only reason I have used Try/Catch in these situations is for a better error message, then WebDriver throwing no such element and the locator. But at the same time, a failing test with webdriver exception would get the same treatment as a friendlier message, so code comments would cover the investigation as well as educating new team members.

    Thank for commenting.

    ReplyDelete