Building Powerful and Reliable WebDriver Automated Tests without Learning the API

I’d like to take a quick break from the technical side of things in order to introduce this particular topic which I strongly believe in.

One of the most common complaints surrounding automation relates to learning the API (in this case Selenium WebDriver). Less confident programmers (usually testers) will usually confuse the host language (e.g. Java) and the automation API (Selenium WebDriver) and fail to understand where one begins and the other ends. Similarly – from my experience – experienced Java developers will view the API with suspicion and complain that “they don’t have enough time” to learn it or need some sort of training in order to do so.

There is some truth in the latter statement. I’ve found it quite difficult at times to find really solid advice and examples with regard to Selenium WebDriver (one of the reasons why I started this blog). So let’s put this statement out there:

It is possible (and so easy!) to write your tests in such a way that the user does not need to know ANYTHING about Selenium WebDriver and how it works or why.

One of the things I love about WebDriver is that it so fast and powerful. But as Peter Parker would say, with great power comes great responsibility…or something like that at least. WebDriver is utterly unforgiving if you don’t respect it. By this, I am of course talking about implicit/explicit waits – or rather WebDriver falling over in a heap if it can’t act upon some element that you haven’t told it to wait for.

So what do I propose?

A simple solution really. I assume you’re currently using the Page Object pattern. If not, take a step back and learn this as it’s a fundamental technique which contributes to solid tests. Let’s have a look at a fictional example:

public class HomePage extends WebDriverPage {

private static final String SEARCH_FIELD = "//*[@id='search']";
private static final String SUBMIT = "//*[@id='submit']";

    public HomePage(WebDriverProvider driverProvider) {
        super(driverProvider);
    }


    public void go() {
        get("http://www.bensnape.com");
    }


    public void search(String content) {
        findElement(By.xpath(SEARCH_FIELD)).sendKeys(content);
        findElement(By.xpath(SUBMIT)).click();
    }
}

Let’s assume this page object represents www.bensnape.com and allows you to navigate to the page – go() and do a search using search(). Now the code for this looks fine, right? But what if in our automated test we navigated to this page via a link and not via the blocking WebDriver get() before trying to call the search() method. What happens? WebDriver falls over because the page hasn’t finished loading.

The typical reaction would be to put some sort of wait (implicit or explicit) at the start of the search() method. However, this is bad practise for the following reasons:

  1. It would be very difficult to anticipate how your page objects might be used in further iterations of your project, or longer term by other projects in related areas.
  2. Putting waits everywhere would clutter your code and hamper readability.

The solution is to introduce a class or set of classes to extend all your page objects from which contain easy-to-use, re-usable and efficient helper methods. Let’s implement that class now:

public class CommonMethods extends WebDriverPage {

    public CommonMethods(WebDriverProvider driverProvider) {
        super(driverProvider);
    }

    public void clickLink(String xpath) {
        WebElement link = new WebDriverWait(webDriver(), 10).until(ExpectedConditions.elementToBeClickable(By.xpath(xpath)));
		
	link.click();
    }
	
    public void fillField(String xpath, String content) {
	WebElement field = new WebDriverWait(webDriver(), wait).until(ExpectedConditions.visibilityOfElementLocated(By.xpath(xpath)));
		
	field.sendKeys(content);
    }
	
}

And so we extend that common class to wrap all our WebDriver API calls with targeted and explicit waits related only to the element we are interacting with in clean, easy to understand methods of our own choosing – namely clickLink() and fillField().

public class HomePage extends CommonMethods {

private static final String SEARCH_FIELD = "//*[@id='search']";
private static final String SUBMIT = "//*[@id='submit']";

    public HomePage(WebDriverProvider driverProvider) {
        super(driverProvider);
    }

    public void go() {
        get("http://www.bensnape.com");
    }

    public void search(String content) {
        fillField(SEARCH_FIELD, content);
        clickLink(SUBMIT);
    }
}

I introduced this simple but powerful concept at Yell. It enables less experienced programmers to easily perform complicated API calls without needing to learn the WebDriver API at all. By simply abstracting the technical implementation inside easy-to-understand business language – e.g. clickLink, fillField, selectFromDropdown etc. code readability, durability and stability vastly improve, along with testers’ output and confidence levels.

4 thoughts on “Building Powerful and Reliable WebDriver Automated Tests without Learning the API

  1. Pingback: Test Automation » Blog Archive » Highlighting Elements in Selenium WebDriver

  2. Hi! Nice blogs I must say. I have some doubt regarding this one. If you say we use our own function to wrap the WebDriver APIs doesn’t it-
    1) Increase overhead as we need to come out with names, scope and other details.
    2) Increase difficulty in maintaining? Yes I agree it will be more intuitive and readable but we can’t rely on people on getting all the mapping with WebDriver APIs just from name. We need to maintain that. And say someone who is if not expert but normal user of Selenium who joins your team from a different organization, how is he supposed to understand the code in a easy way. Framework developer and detail documentation gain importance.
    The second point as per me is not Agile as perfectly working APIs are replaced and personally I don’t find any difficulty reading WebDriver code as I find the names OK.
    I must admit am just a novice when it comes to these matter and we are having an argument to which method to follow in our team. You must have got which team I am in. ;)
    Please validate and enlighten me.

  3. Hi Maitreya,

    Thanks for your comments. WRT the points you raised:

    Yes it does increase overhead slightly. However, it also increases reliability and best practise as it encourages people to do things in one way only (initially!). I think this is particularly important, for example, when it comes to the various ways of implementing WebDriver waits which can be confusing for WebDriver newcomers – both implicit and explicit (and there are various ways of implementing the latter – FluentWait, WebDriverWait and use of the ExpectedConditons class).

    I favour the approach of restricting/simplifying the possible approaches via a common library (for the less confident programmers) whilst giving the more confident ones freedom to innovate where appropriate – as I have found that there is rarely a ‘one size fits all’ way of doing things for more complicated interactions with the browser.

    I think it is also worth mentioning that if you have a single entry point a large proportion of your test code – say 60-70% of your code across multiple projects is via a common library – then any new additions, stability fixes etc. to WebDriver’s API will be more easily integrated.

    Through experience I’ve toned down my initial “lock the framework down” approach as I think it’s important that you let people innovate. However, there are also many people who want it to “just work” and a clear, simple and powerful framework helps them.

    Regarding reporting: I use JBehave (BDD framework) with WebDriver which has great reporting. However, I’ve got a number of ideas for improvements (probably in the form of a Maven plugin) which I’ll be looking at developing in the near future.

    Ben

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>