Your Automated System Tests Should Be a Joy to Write, Part 2

Let's say that you already know how to write a test procedure. Or, at least, you know someone who knows how to write a test procedure. You have a pile of manual procedures that are just waiting to be automated. You've chosen your test tool and proven that it works great with your application.

Your next hurtle is "how do I structure my test code?" Not the procedure itself, but all the helpers. You need to refer to the same controls (labels, buttons, inputs, drop-downs, etc) many times in your tests. Some of the controls in the application are a little tricky, so you want helper methods to make that easier. How do you organize this code?

A great place to start is the Page Object pattern. The idea there is that you have a class (either static or a singleton object) that contains the smarts of how to get to all the elements on that page. Instead of writing a test that looks like this (α):

Application().SomeNonsense().Container().Element("whoopWhoopElement3zz*!").CrypticMysticMethod(*).Button("new_document").Click();

You write something that looks more like this:

DocumentPage.CreateNewDocumentButton().Click();

This has several advantages:
  • Anyone reading your test doesn't get caught up on the bazillion tool-specific, framework-specific methods that need to be called to get the one screen element that you're interested in. 
  • It adds discoverability: if I know there is a DocumentPage, then I can use my IDE's autocomplete to see what elements have already been defined. 
  • If the path to NewButton ever changes, I only need to update the Page Object.
  • If my workflow around clicking the New button ever changes (I'm presented with a new dialog, for example), I can quickly find all the test procedures that interact with the specific NewButton on the DocumentPage.

In summary: make helper methods that grab the screen objects you want; put those helper methods in classes grouped by the pages where you find them in the application.

Easy, right? So easy no one could screw it up, right?

Oh, gentle reader, I wish I could tell you that's the end of the story. Now I give you: Ways People Try to Screw It Up.

1. I don't think I'll use this control again - can I just put the element selector directly in the test instead of putting it in the page object?

No. Do not put your element selectors in the tests. Not. Even. Once. It makes the test harder to read, it's harder to maintain, and, even though you *think* it will never get used again, it probably will. Put the element selector in the page object class.

1.1. Can't I just do it this one time?

NO!

2. If I just need to do one thing with the control, should I just do that in the Page Object method?

When possible, the Page Object should return the control and let the test decide how it wants to interact with the control. For example, don't do this:

public void ClickCreateNewDocumentButton() {
    /*framework-specific code*/.Button("new_document").Click();
}

Do this instead:

public ScreenElement CreateNewDocumentButton() {
    return /*framework-specific code*/.Button("new_document");
}

The important thing is to return a useful abstraction from your page object. Your testing tool likely already returns a fairly useful abstraction, giving you the ability to locate, click, check visibility, etc. Even if you think you will only ever want to do a click on a think or get the text, you're better off returning the whole object for consistency and flexibility later on.

3. Campbell McCleverface made a tool/found a tool online/downloaded some code that will automatically make page object files for us. Isn't that a good thing?

I've looked at a number of these helper tools - some of them are pretty neat. Unfortunately, in the end, it's better to make the page objects by hand.

One way these tools fall down is they don't have great support for custom controls. Maybe you're working on an application with only standard controls, but in my experience, there has always been a need to have at least one custom widget in the application that just doesn't work with the out-of-the-box test tools. When this happens, Campbell's Super Cool Tool will either fail to recognize it or will break it down into the 30+ pieces that make up the custom control, each piece being relatively meaningless from a user's perspective. Custom controls deserve special, careful, treatment.

Another way these tools fall down is that you really don't want to be given everything on the page. It's unlikely every element will be used in a test. There are repeated elements that are shared between pages - you will only test those one or two times. Page object methods that exist but aren't used become a maintenance burden - all that extra code needs to be kept up to date. Even if the tool keeps them up to date for you, it often won't notice that something's been removed. Now you've spent hours trying to figure out why the Save method doesn't give you the button on the screen only to find out that there's a new Update method that the tool injected along with the other 300 methods. You're tired, you're late for supper, it's getting dark, and now you think there might be wolves.

4. Should I just make all the page object methods first?

No, for similar reasons as above. While you are more clever than Campbell McCleverface's Page Object Maker, it's still better to wait till you actually need the page object method to do a test. Code you're not using is code that still needs to be maintained. Also, there are some nice benefits of the page object pattern that you're missing by just shot-gunning all the page object methods at once, such as 'is this thing actually tested'? If it's not tested, there won't be a page object for it - that's much easier to see.

5. So, page object is the perfect pattern and doesn't get any better?

Actually, no. However, making it even better will have to wait till the next blog post.

Closing

Helper methods are great. They're even better when you group them by visual areas of the application. For more thoughts on why the Page Object pattern is great, check out this post by Martin Fowler: https://martinfowler.com/bliki/PageObject.html

Note

α) The way your test framework gets controls from the screen will vary from tool to tool. For example, if you're using Selenium WebDriver, it might look like this:

driver.findElement(By.xPath("//div[contains(@class, 'whoopWhoopElement3zz')]/a"));

If you're using a desktop testing tool like TestComplete, it might look something like this:

myApplication.WaitWindow("#44444", "Thing", 0, 1000).WaitWindow("whoopWhoopElement3zz", "", 0, 500).Window("New Document", "");

Even with these two tools, there are several variations depending on what coding language you're using.

The point is that it doesn't matter. They don't belong in the tests. They belong in a Page Objects, every time.

Comments

Popular posts from this blog

A Cold Day in an SUV

Selenium Would Be Awesome If I Didn't Have to Design Around It

Your Automated System Tests Should Be a Joy to Write, Part 3