Part of what I’ll be blogging about over the next few months is my journey to become an expert with Test Studio. I’m not an expert yet, but I do bring a long history of applying tooling and frameworks to solving real-world problems, and I’m going to be working through many of those same problems in short order—figuring out how to solve those same problems is an awesome way to learn a new tool or framework’s strengths and weaknesses. I hope that by sharing my discoveries I’ll help you work through some of the same common problems!

First up in this informal series: dealing with test-hostile UIs.

If you’ve been around web UI test automation for any length of time then you’ve likely had to deal with UIs that are unfriendly, if not outright hostile, to automation.

If you’re lucky you’ve been able to work with a clean Document Object Model (DOM) where you’ve got easy, clear element locators to work with. (By “element locators” I mean good ID values, CSS classes, or relatively simple XPaths.) These friendly locators make it simple to write precise tests which are, over time, more maintainable. If you’re lucky your DOM is well-structured and has some semantic context to it, too. Hopefully you’re also able to avoid table-driven layouts which can greatly drive up a DOM’s complexity, too. (Tables are fine for tabular data, just not for general layout.)

NOTE: If you’re unfamiliar with the basics of a DOM, I highly recommend the W3School’s DOM tutorial. It’s a terrific resource for learning more about the DOM.

Unfortunately, many folks have to suffer with DOMs that aren’t so helpful. I’ve had far too many occasions where I’ve had to deal with some or all of the following:

  • Nested tables driving layout
  • Overuse of <div> elements leaving no discernable semantic structure
  • Few static IDs
  • Dynamic IDs with no discernable pattern
  • Muddled CSS classes

There are a huge number of factors for why DOMs get to this state, but this is the reality for many folks writing automation.

With that in mind, let’s look at a problem I’ve had to solve a number of times: diving in to a web page to crack open a way to validate content inside an unhelpful DOM.

Here’s an example of a page similar to what I’ve worked with in the past:

   1: <html>
   2: <head>
   3: <link media="screen" type="text/css" href="/styles.css" rel="stylesheet">
   4: </head>
   5: <body>
   6: <div id="header">
   7: <h1>My Most Excellent Site</h1>
   8: </div>
   9: <div id="mainContent">
  10: <table id="NewsLetter">
  11: <tr>
  12: <td class="MainContentArea">
  13: <h2>Today's News</h2>
  14: <div class="NewsItem">
  15: <div class="NewsItemTitle"><strong>testReach Conference Kickoff</strong></div>
  16: <div class="NewsItemContent">testReach, the new conference targeted at software testing professionals, will have its first meeting of the organizing committee on Thursday.</div>
  17: </div>
  18: <div class="NewsItem">
  19: <div class="NewsItemTitle"><strong>Dogfooding's Sucesses Continue to Rise</strong></div>
  20: <div class="NewsItemContent">We continue to have more and more sucesses with dogfooding throughout Telerik. ("Dogfooding" is a term referring to a company using its own products for regular business or projects.)
  21: <p>A few examples of teams using dogfooding:</p>
  22: <ul>
  23: <li><strong>ASP.NET Component Team</strong> is using Test Studio. </li>
  24: <li><strong>SiteFinity Team</strong> is using Test Studio. </li>
  25: <li><strong>WPF Controls Team</strong> is using Test Studio. </li>
  26: </ul>
  27: </div>
  28: </div>
  29: <div class="NewsItem">
  30: <div class="NewsItemTitle"><strong>Telerik University Touted in Media</strong></div>
  31: <div class="NewsItemContent">Telerik University, Telerik's program for educating young IT professionals, has been mentioned in a number of news outlets across Europe. This unique program displays Telerik's commitment to helping develop a base of highly skilled IT professionals in the greater Sophia region.</div>
  32: </div>
  33: <div class="NewsItem">
  34: <div class="NewsItemTitle"><strong>Bacon Mountains Confirmed as Top Draw at CodeMash</strong></div>
  35: <div class="NewsItemContent">Surprisingly, a poll of CodeMash attendees has put the infamous Bacon Mountains as the top draw for the CodeMash conference. Bacon topped CodeMash's unique environment (the conference is hosted at an indoor waterpark in the middle of winter in northern Ohio) and the stellar content provided by world-reknowned speakers such as Joe O'Brien, Chad Fowler, and Scott Chacon. Jim Holmes, President of CodeMash's Board of Directors, was unsurprised. "Yeah, so what?" he responded.</div>
  36: </div>
  37: </td>
  38: <td class="NewsLetterSideBar">
  39: <div class="SideBarItem">
  40: <div class="SidebarTitle"><strong>Announcements</strong></div>
  41: <div class="SidebarContent">
  42: <ul>
  43: <li><strong>Jim Holmes Joins as Evangelist! </strong>Jim Holmes has joined our team as the new evangelist!</li>
  44: <li><strong>New Release Today </strong>Today marks our major R2 release of everything.</li>
  45: <li><strong>Ping Pong Table Missing </strong>Apparently someone borrowd the ping pong table this weekend. Please return it.</li>
  46: </ul>
  47: </div>
  48: </div>
  49: <div class="SideBarItem">
  50: <div class="SidebarTitle"><strong>Events</strong></div>
  51: <div class="SidebarContent">
  52: <ul>
  53: <li><strong>Ping Pong Tournament </strong>Runs 18 November, 2011, in the basement lounge.</li>
  54: <li><strong>Brown Bag Lunch: Maintainable Tests </strong>1200 in conference room A10-22.5. Jim Holmes presenting.</li>
  55: <li><strong>Morning Trail Run Group </strong>Meet up every weekday morning for a casual 30Km run through the local trails. Group meets at 0430 at the company bar. Run finishes by the infirmary.</li>
  56: </ul>
  57: </div>
  58: </div>
  59: </td>
  60: <tr>
  61: </table>
  62: </div>
  63: </body>
  64: </html>

With my awesometastic ninja CSS skills the rendered page looks something like this:

1-Newsletter

The premise of this page is that it may be pulling the news items from a database or aggregating from several different sources. To test this system I’ll need to validate a number of things around the content being pulled in and rendered.

One of my tests might be validating the number of news items displayed in the main content area of the newsletter. If we’re using a baseline dataset, I should see an expected number of items in the newsletter’s main panel. A second test might be validating that the titles of the news items in that panel should also be from an expected set.

In my layout, the news items are <div> elements decorated with the CSS class “NewsItem”. They are contained in a <td> table cell within the NewsLetter ID’d <table>. Note there are no useful ID attributes in any of those elements…

Test One: Counting NewsItem Elements

Depending on the overall UI, I can’t simply count the number of items using the NewsItem CSS class – there may be other items elsewhere on the page which also use that style. Ergo, I need to be very specific with my locators: I need to first narrow down to only the NewsItems within the specific MainContentArea <td> cell of the table.

There are two different approaches I can use to nail this down: XPath, and a good Find Expression in Test Studio or the WebAii framework. I’ll start with XPath.

There are a number of tools you can use to figure out exactly what XPath identifies the NewsItems elements. In the interest of time and space, I’ll not go through this here, but simply point you to a blog post I wrote a couple years ago which walks you through this exercise using Selenium IDE, Firebug and XPath Checker.

The XPath to locate all NewsItem elements within the MainContentPanel is this:

   1: //td[@class='MainContentArea']//div[@class='NewsItemTitle']

Now for the actual work in Test Studio. I’m skipping project and test setup and am focusing only on getting this portion of the test working. We’ll need to use a Coded or Script test step. Insert one via the UI:

2-Script Button

Edit the newly inserted step by right-clicking on it and selecting “View Code.”

3-View Code

So back to the test: I need to get a collection of the NewsItem elements in the content area and count them.

Two simple statements of code let me get the job done:

IList<Element> newsItems = 
    Find.AllByXPath("//td[@class='MainContentArea']//div[@class='NewsItem']");
Assert.AreEqual(4, newsItems.Count);

The Find class gives us a number of extremely handy methods for locating elements – and with the ability to use a wide range of ways to locate those elements. You can use IDs, CSS classes, nodes, and yes, XPath. The Find.AllBy* methods return collections. If you’re looking for a single element then you can just use the Find.By* equivalent. (See the Testing Framework API Reference help file in Test Studio’s documentation directory for a full listing of the framework’s functionality.)

Note: I’ve hardwired the XPath locator into this code example. Locators shouldn’t ever be stored in your test code – they should stored off in Test Studio’s Element Repository where appropriate, or in the equivalent of a Page Object, dictionary, or other single source. I’ll be writing and speaking about maintainable automation extensively, and I’ll always call out where I’m breaking my own rules for brevity’s sake in examples.

We’re not locked in to XPath for solving this particular problem. XPath won’t ever be confused with an easy-to-understand way to locate elements in your DOM. Moreover, some versions of Internet Explorer have serious performance issues with resolving XPath.

Instead of XPath we can look to the test framework’s own find by expression functionality. We can replace the two statements above with these:

HtmlTableCell contentArea = 
    Find.ByAttributes<HtmlTableCell>("class=MainContentArea");
IList<HtmlDiv> newsItems = 
    contentArea.Find.AllByExpression<HtmlDiv>("class=NewsItem");
Assert.AreEqual<int>(4, newsItems.Count);

The first statement returns us a strongly typed HtmlTableCell found in the DOM matching the class MainContentArea. We use that to chain the next Find – we’ve limited the scope of the DOM to only that particular table cell – so the Find.AllByExpression gets us a collection of NewsItem-decorated <div> elements in that cell.

This form of chaining gives us great power for crafting very powerful, flexible expressions since you’ve access to the entire element’s contents and DOM subset.

With this first test out of the way, let’s move on to the second one.

Test Two: Validating the Items’ Titles

Again, we’ve the premise I’m using a baseline dataset which I can base my expectations on. With that in mind, I need to again get a collection of the NewsItems in the MainContentArea, then iterate through that collection and compare the titles against expectations.

An XPath-based solution could look like this:

IList<string> expectedTitles = 
 new List<string>() { "testReach Conference Kickoff", 
 "Dogfooding's Sucesses Continue to Rise", 
 "Telerik University Touted in Media", 
 "Bacon Mountains Confirmed as Top Draw at CodeMash" };
 
IList<Element> titles = 
    Find.AllByXPath("//td[@class='MainContentArea']//div[@class='NewsItemTitle']/strong");
foreach (var title in titles)
{
    Assert.IsTrue(expectedTitles.Contains(title.TextContent));
}

This works just like the previous test: use Find to get a collection based off an XPath. We then have to use a quick foreach to iterate through the list and ensure each item is contained in our list of expected titles. We have to do this because Enumerable.SequenceEqual (implemented by IList) compares contents in the same order for both instances. Maybe our news items don’t always come through in the same order. (That’s another test.)

If you do a lot of work comparing lists, you might consider writing your own extension methods to handle these comparisons. NUnit, another popular testing framework, has some great functionality in its CollectionAssert methods.

Here’s how it looks using the WebAii framework’s Expressions:

IList<string> expectedTitles =
 new List<string>() { "testReach Conference Kickoff", 
 "Dogfooding's Sucesses Continue to Rise", 
 "Telerik University Touted in Media", 
 "Bacon Mountains Confirmed as Top Draw at CodeMash" };
HtmlTableCell contentArea = 
    Find.ByAttributes<HtmlTableCell>("class=MainContentArea");
IList<HtmlDiv> itemTitles = 
    contentArea.Find.AllByExpression<HtmlDiv>("class=NewsItemTitle");
 
foreach (var itemTitle in itemTitles)
{
    var titleText = 
        itemTitle.Find.ByExpression("tagname=strong");
    Assert.IsTrue(expectedTitles.Contains(titleText.TextContent));
}
This looks much the same as above: Narrow things down to the main content area element, then chain a Find within that element to pull out the news items. Note that in the foreach loop we have to extract out the actual text by using another Find for the <strong> element holding the title itself. (Believe me, this is based on real-world markup I’ve had to deal with!)

Wrapping It All Up

Working with test-hostile DOMs is a matter of fact for some test automation folks. If you’re saddled with this sort of DOM, I encourage you to try and work with your UI developers to gradually clean up some of the problems. Get them helping you to write a few automation tests so they see the difficulties the complex DOM is causing. They may be able to help you out with some adjustments to the system.

If the system can’t be adjusted, then you can continue to fall back on XPath or the Find Expressions to help you navigate complex DOMs and get the appropriate level of maintainable tests in place.

Please feel free to pass on your own experiences or concerns, either in the blog’s comments or directly to me via e-mail: jim.holmes@telerik.com. I love hearing the issues folks are facing in the real world!

About the author

Jim Holmes

Jim Holmes

has around 25 years IT experience. He is co-author of "Windows Developer Power Tools" and Chief Cat Herder of the CodeMash Conference. He's a blogger and evangelist for Telerik’s Test Studio, an awesome set of tools to help teams deliver better software. Find him as @aJimHolmes on Twitter.


Related Posts

Comments

Comments are disabled in preview mode.