New Podcast
Managing Traffic During Peak Demand; Plus, Microsoft, Akamai Outages

Engineering

Tips for a front-end test-friendly web application Part I: Targeting

By Chris Chua
| | 15 min read

Summary


In web application development, testing ensures that your brand spankin’ new application works. In particular, you want to be able to add new features to the application without breaking the existing features.

Selenium is used to test websites by performing actions as if they were made by a user behind a computer. They are used widely in testing for web applications. Twitter Bootstrap uses PhantomJS , another browser automation technology, for testing its functionality. AngularJS even provides its own API for browser automation testing.

At ThousandEyes, we use Selenium to perform periodic page load tests for our customers. We chose Selenium as it uses existing browsers such as Google Chrome, and hence, closely reflects the web performance experienced by users.

Web applications can be difficult to test if they aren’t made with testing in mind. This is especially true for Single Page Applications (SPAs). SPAs support heavy interaction without incurring additional page loads (e.g. Facebook, Gmail). Instead of page loads, these SPAs use AJAX requests to relay data back and forth from the server.

tl;dr

Best practices to keep in mind as you develop your web application to make testing much easier:

  • Add classes that are meaningful
  • Classes should indicate the element’s functionality and state
  • Dynamically generated classes and IDs are not helpful for testing
  • Never hard-code content in test code!

Since it’s best to learn by doing, let’s go ahead and jump into some scenarios.

Note about test scenarios In each test scenario, there are some actions that need to be executed in a certain way to reach a goal. These are snippets of interaction derived from actual web applications. To run Selenium tests on them, run the tests on the Live URL provided. The test scenarios are gists running through bl.ocks.org, a service created by Mike Bostock @mbostock. Timers used in the JavaScript code were intended to simulate AJAX requests and page loads which, in practice, have indeterminate completion times. All code examples presented here are public domain so feel free to copy or modify them. :)

Test Scenario 1

Live: http://bl.ocks.org/chrisirhc/raw/5631245/ (Gist, Plunker)

In this page, there are three boxes which appear in a random order on each page load.

Write a script to press the GO buttons in order of the numbers in the boxes. Remember, the script must work across reloads of that page.

Test Scenario 2

Live: http://bl.ocks.org/chrisirhc/raw/5541721/ (Gist, Plunker)

Here is a simple page with a button.

Write a script that clicks on the Go button then waits for processing to be done before clicking on the button again.

Now, whip out that inspector and go get that Horray message!

Use functional names in IDs and classes for action elements

IDs and classes should communicate the element’s significance to the application.

Script for Test Scenario 1

click | xpath=(//div[contains(text(), "1")]/button)	
click | xpath=(//div[contains(text(), "2")]/button)	
click | xpath=(//div[contains(text(), "3")]/button)

Problem

When you’re resorting to using XPath in a Selenium script, that’s usually a code smell.

First, let’s consider that this gets really ugly very quickly. XPath is a last resort to target elements on the page. It was used at a time when web developers XHTML was the de facto standard. Yes, you can still do that, but why do it now? CSS selectors are so concise and you’ve probably already mastered it through using jQuery and writing stylesheets for your application.

Second, and, more importantly, the XPath is targeting boxes that contain certain text. Since this particular command tests the business logic of the web application and not the presentation logic, it violates the principle of separation of concerns.

Third, the script is very fragile to changes to the content. In real-world scenarios, it’s very rare that the box would contain any permanent text that could be used for targeting the button.

While the boxes and buttons have IDs (box_0, button_0, etc.), the IDs do not correspond to their functionality in the page. They might as well be randomly generated IDs as they can’t be used in the test script.

Improvement

First, we can add the classes step-1, step-2, step-3 to the buttons to indicate that they are consecutive steps. That can be further improved by prefixing (namespacing) this with el- (for element).

Second, add a wrapping container to isolate this interactivity into its own context. This makes the unaffected by changes in the layout of the page. For example, if another container is added with similar step by step buttons (with the el-step-1 class) , the current test will still work since it will be targeting a context correctly. In this case, I call it widget-processor, since it’s a widget that processes something.

In some cases where your application makes heavy use of classes, you could further prefix it with test- to make it explicit that this class is used for testing the application and to prevent namespace collisions.

So by adding el-step-1, el-step-2, el-step-3 to the buttons, you get the following page.

Live: http://bl.ocks.org/chrisirhc/raw/5882715/ (Gist, Plunker)

Script after improvement

click | css=widget-processor > .el-step-1	
click | css=widget-processor > .el-step-2	
click | css=widget-processor > .el-step-3

Guidelines

Communicate the element’s significance to the application through IDs and classes. Remember to namespace the classes. If an element has multiple meanings in different contexts, consider separating it into two different elements. Make use of element hierarchy of HTML to separate different blocks that have different responsibilities. This also helps in styling of the application through CSS and leads to a more semantic web. Reading the stylesheet and the JavaScript code also makes more sense now.

See Nicolas Gallagher’s article on front-end architecture for more thoughts on naming classes.

Add targetable DOM feedback to indicate application state

Script for Scenario 2

Here’s one way to write a script for this:

click | css=button	
waitForCondition | selenium.getText('css=button') == "Done" | 10000
click | css=button

Problem

What’s wrong with this innocent-looking script? There’s no targetable DOM feedback when the processing is complete or if has even started. Again, the script is targeting changes in the textual content on the page, so it suffers from the same problems as the Scenario 1.

Improvement

Let’s add classes that indicate the state of the application or widget. The loading and done states immediately come to mind. Notice that there are actually three states. The third state, the initial state, can be expressed by the absence of these two classes. When adding these classes, it is important to consider what states are meaningful to be tested. They may also come in handy for styling, of course.

This adds four lines in the script:

function startProcessing() {
   ...
   $myBtn
       .addClass('state-loading')
   ...
} 
...
function processingDone() {
   ...
   $myBtn
       .removeClass('state-loading')
       .addClass('state-done')
   ...
}

Live: http://bl.ocks.org/chrisirhc/raw/5541870/ (Gist, Plunker)

Script after improvement

click | id=myBtn	
waitForCondition | selenium.isElementPresent('css=#myBtn.state-done') | 10000
click | id=myBtn

The Selenium code is now more meaningful and readable. The code is also less fragile to changes in the page, since both the change and the button are meaningfully identified. The state-done class clearly describes that the button is now finished with some sort of processing. The state-loading is useful as now you can do an assertion to ensure that loading has started.

Takeaway

Tests should be written agnostic to the way the content is presented. The developer of the web application should keep in mind the semantic nature of HTML and the Document Object Model (DOM). The DOM provides semantic information that we can take advantage of, such as IDs and classes.

Note about testing content

In the case that you are testing the presentation logic, you need to test that the content displayed is the correct string. When you do this, you should always use the same source of message constants as the application code. For example, this could be a messages.json file or messages.properties file. Never hard-code strings of presentation layer content in test code!

Just for fun

Live: http://bl.ocks.org/chrisirhc/raw/5883173/ (Gist, Plunker)

To end this article, I’ll present a case that combines the worst of both scenarios above. See if you can write a script for this one!

This could be your web application. I certainly wouldn't want to be the person writing the tests for it...

Conclusion

I know some of these are not easy changes, as the developer may have to think harder about using test-friendly designs rather than “something that just works”. However, it will definitely help with maintainability of the testing, which would reduce costs in the long run. In the next part of this series, I’ll talk about what problems can arise when the backend isn’t designed with front-end testing in mind.

Related reads:

Subscribe to the ThousandEyes Blog

Stay connected with blog updates and outage reports delivered while they're still fresh.

Upgrade your browser to view our website properly.

Please download the latest version of Chrome, Firefox or Microsoft Edge.

More detail