Monday, December 12, 2011

31 Days of Testing—Day 12: Functional Test 101

Updated: Index to all posts in this series is here!

In my last post I gave you a bit of a drive-by spewing of some of the things I think are critical to keep your automation maintainable, particularly in the functional test world.

Now let’s take a look at what a functional UI test looks like. Again, there’s a huge difference in the specifics between platforms and frameworks/tools. I’m going to focus on a very simple web UI test to kick off here. I’m using Selenium 2.15. Selenium’s an open source web testing framework available on nearly every platform you could care to write code for. Watir for Ruby is another example of an open source web testing framework. Test Studio, the commercial product I’m involved with, has a free testing framework as its underpinnings.

These tools are used in stand alone tests, but they’re also used extensively as backing for other specification-style testing tools such as Cucumber, Fitness, Capybara, and others.

All these frameworks use roughly the same approach to testing your web applications:

  • Start up a browser
  • Navigate somewhere
  • Find things of interest on the page to interact with
    • Inspect text
    • Input text
    • Click
    • Scroll
    • Blow up

(I may be exaggerating with the Blow Up part.)

Without further ado, let’s take a look at how a functional test with Selenium is built. For simplicity’s sake and readability I’m leaving this test a bit more linear than I normally would. I’ll show you some potential refactorings/optimizations in later posts—including some discussion of the Page Object Pattern I’ve mentioned previously.

You may need a separate test framework to actually execute your functional framework inside of. Selenium doesn’t come with any form of test runner, reporting engine, or even Assert support. The examples below use NUnit to drive the tests around

The app we’ll be testing is the Test Studio demo app hosted at Heroku. We’ll use two simple tests: check for the login link on the home page, and validate that we can log on to the system.

First test first. Skipping over setting up the project and references, the first thing you’ll need to do is declare a test fixture, plus a variable for the browser we’ll be working with. Selenium supports many different browser drivers, all of which implement the IWebDriver interface.

[TestFixture]
public class Home_page_displays_correctly
{
    IWebDriver browser;

Now we can use a fixture setup method to instantiate our browser. I’m using the FireFox driver, so there are a few profile and binary executable file things I need to deal with. NOTE: In a true implementation, I’d have this instantiation handled by some sort of provider so I could flexibly stand up Firefox, Chrome, IE, or whatever browser I wanted this run to be with.

[TestFixtureSetUp]
public void Run_once_before_anything()
{
    var profile = new FirefoxProfile();
    var exe = new FirefoxBinary();
    browser = new FirefoxDriver(exe, profile);
 
    browser.Navigate().GoToUrl("http://growing-planet-634.herokuapp.com/welcome")
}

The last step is to actually navigate to the URL/page we’re going to work with. You need to understand that the navigation will cause WebDriver to wait for the page’s “onload” event to fire. This is a handy blocking step, meaning you don’t need to worry about adding in code to pause and wait for the page to load. You also need to understand this does not have anything to do with dynamic content. You’ll need to handle that yourself. More on that later.

We’ve navigated to the home page, and now it’s time to find the elements we want to test for. Each framework is subtly different, but the general idea is the same: specify an HTML ID, CSS selector, XPath, or some other form of find logic so the framework can locate the element you want. In our WebDriver test we do the following:

[Test]
public void Login_link_is_correct()
{
    IWebElement loginLink = browser.FindElement(By.Id("login_link"));
    Assert.IsTrue(loginLink.Text.Equals("Login"));
    Assert.IsTrue(loginLink.GetAttribute("href").Contains("/login"));
}

We’re grabbing the element with the ID of “login_link”, then we’re checking text and attribute values on that element.  We’re using NUnit Asserts to make the actual test.

How do we know which IDs to work with? First, best answer: talk with the developers who created that UI. Second, nearly as good an answer: use a tool like Firebug, IE Developer Toolbar, or the DOM Explorer in Test Studio to help you walk through the page’s DOM.

We can also interact with elements on the page in the same fashion. If I want to log in, I’ll use these steps right after the initial navigation step:

browser.FindElement(By.Id("login_link")).Click();
 
browser.FindElement(By.Id("username")).SendKeys("testuser");
browser.FindElement(By.Id("password")).SendKeys("abc123");
browser.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromSeconds(10));
browser.FindElement(By.Id("login_button")).Click();

I can interact with elements by first finding them, then using sensible commands like Click() for buttons/links, or SendKeys for input fields.

The fourth statement in this group sets an implicit wait for the navigation after the final Click() event  on the login button. While WebDriver nicely waited for the page to completely load after the Navigate call above, it doesn’t know to wait for a page load after clicking a link. We’re putting in an implicit wait to have Selenium pause before failing subsequent actions after that navigation.

In this case, that subsequent action is another test validating the presence of the logoff  link.

[Test]
public void Logout_link_displays()
{
    Assert.IsTrue(browser.FindElement(By.LinkText("Logout")).Displayed);
}

Without the implicit wait, WebDriver would blow past this assert, failing it because the page hadn’t properly loaded.

Implicit and explicit waits are one of the most critical things an automation developer needs to understand. The Selenium documentation has a great article on it which you need to read and understand—you’ll save yourself a lot of pain!

This brief overview showed some common themes regardless of the automation framework you’re using. You need to create a browser, have it navigate somewhere. That navigation may or may not handle delays necessary for your testing. Once you’re at a page you can find and interact with elements on that page.

I’ll follow this with a post on dealing with some common troublesome issues like AJAX or other dynamic content, pop up windows, file system dialogs, and others.

No comments:

Subscribe (RSS)

The Leadership Journey