Wednesday, December 14, 2011

31 Days of Testing—Day 13: Functional Test 201 (Common Problems)

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

Updated: (Updated Case 2. Adam Goucher pointed out I referenced the Wait class which is for explicit waits, yet this case is about implicit waits. Yeesh. Major writing failure. Thankfully I have good pals who keep me straight.)

NOTE: I missed posting this yesterday due to a long trip back and forth to visit the amazing new expansion at the Kalahari in preparation for CodeMash. That coupled with a sick kid left me behind the power curve. Sorry!

Functional testing at the UI, particularly for web tests, can be a pile of convoluted, confusing poo. Headaches from AJAX dynamic calls, oddball JavaScript events interfering with fields you’re trying to work with, horrific DOM models, the list goes on and on.

There’s a nice opener for you, but never fear, I’m going to help you through a few of those—because I’ve suffered through them and would prefer you didn’t have to.

Testing a web application means you need to get down and jinky with exactly how your page is loading. You’ll need to work closely with your developers to understand what sorts of JavaScript you have on your page. You’ll need to look closely at your testing framework to understand how it interacts with the DOM and how it handles dynamic content situations.

Let’s look at three common situations you’ll likely run in to on web applications:

  1. JavaScript is bound to an input field and is disrupting your test’s input to that field.
  2. You’re working with an AJAX-ified page where the element you need isn’t yet loaded.
  3. You’re working with an AJAX-ified page where the content you need isn’t yet loaded.

Case 1: JavaScript is Messing With My Fields!

Problem: You’ve got a JavaScript validation attached to one or more fields you’re trying to interact with. For example, on the Test Studio demo app we use JavaScript to check that the password and username fields both have content before we enable the login button. This JavaScript is causing your test framework some grief and the validation isn’t working to enable the button.

Answer: Varies by framework. When using Selenium, the field’s onchange event only fires when focus leaves that field. You’ll need to click to another field to get the validation to work properly. That might look something like this:

browser.FindElement(By.Id("username")).SendKeys("testuser");
browser.FindElement(By.Id("password")).SendKeys("abc123");
browser.FindElement(By.Id("username")).Click();

In Test Studio, we inject text directly to the DOM, so no events at all get fired on fields. You’ll need to use the “SimulateRealTyping” property to push the input text through the browser itself. This is a property on that specific test step:

SimTyping

Other testing tools/frameworks will have their own approaches to solving this problem. Point being, spend time understanding your app and your tools.

Case 2: AJAX (or some dynamic system) hasn’t yet loaded the element I need!

Problem: The elements you need to work with haven’t yet been loaded on the page.

Have a look at the ASP.NET AJAX demo page for a drop down menu. Use some tool like Firebug to inspect the current DOM and you’ll see the menu elements aren’t actually loaded on the page yet. The menu elements (the ones you want to interact with!) don’t get loaded until you click on the pull-down list. You can’t validate or interact with elements that aren’t yet there!

Answer: Use Selenium’s implicit wait feature  to help out with this. You set a default timeout for the browser session you’re currently using, after which Selenium will wait for the configured amount of time for the elements to appear. If the elements don’t show up in the DOM, you’ll see an Exception, which is what you want – the elements aren’t appearing, so your test is failing. In Selenium this looks like:

[Test]
public void Validate_correct_choices_generate_correct_answer()
{
    browser.Navigate().GoToUrl(
        "http://www.asp.net/ajaxLibrary/AjaxControlToolkitSampleSite/DropDown/DropDown.aspx");
    browser.FindElement(By.Id("ctl00_SampleContent_TextLabel")).Click();
    browser.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromSeconds(10));
    browser.FindElement(By.Id("ctl00_SampleContent_Option1")).Click();
    
    Assert.IsTrue(browser.FindElement(By.Id("ctl00_SampleContent_lblSelection")).Displayed);
}

Note we’re setting the ImplicitWait property to ten seconds. The test will hang around that long waiting for the service call to finish and the DOM to update with the element we need to work with.

In Test Studio we already handle this sort of implicit wait without any extra steps.

Not sure how Watir handles these sorts of situations, but I would assume (yes, bad word!) that it’s very similar.

Case 3: AJAX (or some dynamic system) hasn’t yet loaded the ContentI need!

Problem: The content you need to work with hasn’t yet been loaded on the page.

Answer: Use explicit waits to delay your test’s execution until the content is loaded.

Have a look at the ASP.NET AJAX demo page for cascading menus.

cascading

Use some tool like Firebug to inspect the current DOM and you’ll see the menu elements are there, but not their content. Make some choices for the menus, look at the DOM. Now you’ll see the content exists after having been loaded by an AJAX callback. Because the elements exist, but not their content, we need to handle this with an explicit wait. (See the awesome Selenium article on explicit and implicit waits. You need to read this!)

In the example above we need to navigate to the page, then make a series of selctions. In each dropdown selection list, we’ll need to get that selection element, then wait for the specific item we want to appear. That content is loaded by the previous selection, or in the case of the Make selection, by the actual page loading.

Here’s how we write the first section of code to wait and select our option in the Make pulldown. After that, we use WebDriverWait to explicitly wait until our target element (the make of “Acura” in this case) appears. Once the content is loaded in to the options we can then select the specific item from the option list.

[Test]
public void Working_with_no_content()
{
    string make = "Acura";
    string model = "Integra";
    string color = "Sea Green";
 
    browser.Navigate().GoToUrl(
        "http://www.asp.net/ajaxLibrary/AjaxControlToolkitSampleSite/CascadingDropDown/CascadingDropDown.aspx");
    var listOfMakes = browser.FindElement(By.Id("ctl00_SampleContent_DropDownList1"));
 
    WebDriverWait wait = new WebDriverWait(browser, TimeSpan.FromSeconds(10));
    wait.Until<IWebElement>((d) =>
    {
        return d.FindElement(By.XPath(
            "id('ctl00_SampleContent_DropDownList1')/option[text()='"+make+"']"));
    });
    var makeOptions = new SelectElement(listOfMakes);
    makeOptions.SelectByText(make);

Actions on the remaining two lists are the same. For the final confirmation message, also AJAX-ified, we can go back to our implicit wait pattern shown earlier—that message is in an element which isn’t loaded on the page. (NOTE: The test for the confirmation message isn’t a great one. A better one would be to ensure the proper combination of make+model+color is rendered as well as the general message – I took this shortcut for brevity’s sake because this example’s already somewhat lengthy.)

browser.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromSeconds(10));
Assert.IsTrue(browser.FindElement(By.Id("ctl00_SampleContent_Label1")).Displayed);
The entire test looks like this:
[Test]
public void Working_with_no_content()
{
    string make = "Acura";
    string model = "Integra";
    string color = "Sea Green";
 
    browser.Navigate().GoToUrl(
        "http://www.asp.net/ajaxLibrary/AjaxControlToolkitSampleSite/CascadingDropDown/CascadingDropDown.aspx");
    var listOfMakes = browser.FindElement(By.Id("ctl00_SampleContent_DropDownList1"));
 
    WebDriverWait wait = new WebDriverWait(browser, TimeSpan.FromSeconds(10));
    wait.Until<IWebElement>((d) =>
    {
        return d.FindElement(By.XPath(
            "id('ctl00_SampleContent_DropDownList1')/option[text()='"+make+"']"));
    });
    var makeOptions = new SelectElement(listOfMakes);
    makeOptions.SelectByText(make);
 
 
    var listOfModels = browser.FindElement(By.Id("ctl00_SampleContent_DropDownList2"));
    wait.Until<IWebElement>((d) =>
    {
        return d.FindElement(By.XPath(
            "id('ctl00_SampleContent_DropDownList2')/option[text()='"+model+"']"));
    });
    var modelOptions = new SelectElement(listOfModels);
    modelOptions.SelectByText(model);
 
    var listOfColors = browser.FindElement(By.Id("ctl00_SampleContent_DropDownList3"));
    wait.Until<IWebElement>((d) =>
    {
        return d.FindElement(By.XPath(
            "id('ctl00_SampleContent_DropDownList3')/option[text()='"+color+"']"));
    });
    var colorOptions = new SelectElement(listOfColors);
    colorOptions.SelectByText(color);
 
    browser.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromSeconds(10));
    Assert.IsTrue(browser.FindElement(By.Id("ctl00_SampleContent_Label1")).Displayed);
}
Test Studio handles this in a similar fashion: wait for the item you need to work with to appear, then interact with it. If you’re working straight in code with the Telerik Testing framework, then it’s not overly different from above in concept. If you’re working with the Test Studio standalone edition the test would look like this:
studio_ajax_cascade

Wrapping Up

There you have it. A quick walk-through of three common situations on web pages that can give automation folks grief.

1 comment: