Selenium Testing of massive Ajax Apps +

Testing of a web application is a hard work. You don’t handle with data in a direct way. You got a lot of markup around it. How to handle this in a flexible and more robust way could be found in the post Robust Portlet Testing.

If you have fought your way through the markup, you now get to another trap on your way to get your tests flying. The markup is changing, because you are using the Ajax technology. After the page is rendered there are asynchronous calls changing your DOM. You are asking: Where is the problem? The problem of testing is, that you need to teach your test tool, to look at the page in the same way you are looking at it. On the first look you want to get the third row of the table. But wait, it is still loading, even if there is just a minimal delay. If you do automatic tests they should run through all the tests as fast as possible. The tool is not to twiddle one’s thumbs to click on the button.

You need to write your tests in another way. The question isn’t only if the data could be found in the DOM, it is changing to if the surrounding element has loaded could the data be found then. And this is the essential point, where most recording tools like Selenium IDE are failing. It doesn’t give you a hint, what is loading after the DOM is ready the first time. Yes it is possible to add pause or a waitForElement-function before every field, but wouldn’t it be easier to have little helpers for the case.

First let’s have a look at how the waitForElement-function in Selenium is working in Selenium-RC:

for (int second = 0; ; second++) {
    if (second >= timeout) throw new ElementNotFoundException("Element not present: " + locator);
    try {
        if (this.isElementPresent(locator)) break;
    } catch (Exception e) {
    }
    Thread.sleep(1000);
}

It is polling every second, if the element specified be the locator could be found. That is better than adding a pause. Why?

How long does your pause needs to be? 500ms, 1s, 2s? You are not sure about it, I am sure. It is a guessing and it is really slowing down your tests. You have 20 elements in one test. And let’s say you’ve got  50-100 tests at the beginning. Well – you are pausing damn long.

That’s why you should start using the waitForX-functions of Selenium. Now nearly every line of your massive Ajax test has a previous waitForX-function. It doesn’t make your tests more readable, but more stable and as fast as possible.

Now we are in a Java world, writing our Selenium tests and are tired of writing waitForX-functions. The next step is to extend the DefaultSelenium class and adding some tiny helper functions:

  • safeOpen
  • safeGetText
  • safeGetValue
  • safeClick

All these functions are equivalents of the normal Selenium functions, but having a waitForX-function build-in. Normally you are saying: getText for this element and giving a locator. Now use the locator to do a pre-test of the element. If the element could be found, get the text. It is just a little helper, but it makes your tests much cleaner and robust.

We have introduced another trick for the safeOpen function. Because we are fetching our dependend Javascript files asynchronously after the normal rendering has finished (check out the YUI Loader), we add a new element with an id after the Javascript has finished its loading process. Now we can poll if the element is in the DOM and start after all the Javascript has loaded.

Check out the following example and try out how it helps to speed up writing your Ajax tests and reduce the test to the test.

See the following example test. Even if you don’t understand which application is tested, you can read the test:

    @Test(dataProvider = "users")
    public void testUserDetails(User user) throws InterruptedException, ElementNotFoundException {
        selenium.safeOpen(URL);

        // fill in search form
        selenium.safeType(
                browserXPath().elementByClassName("searchForm").textField().toString(),
                user.getUserIdentification());

        selenium.safeClick(
                browserXPath().elementByClassName("searchForm").submitButton().toString());

        // action to detail portlet with service menu
        selenium.safeClick(
                browserXPath().table().tr(1).td(1).a().toString());

        // check if all fields are available
        String[] userDetails = {user.getUserIdentification(), user.getGender().toString(), user.getFirstName(), user.getLastName(), user.getEmail(), user.getMobilePrefix(), user.getMobileNumber()};
        for (int i = 1; i <= userDetails.length; i++) {
            selenium.waitForText(detailXPath().table().tr(i).td(2).toString(),
                    checkEmpty(userDetails[i]));
        }
    }

To see the “unbelievable magic” behind the scenes, check out the AjaxSelenium extension:

import com.thoughtworks.selenium.DefaultSelenium;

public class AjaxSelenium extends DefaultSelenium {
    public void safeClick(String locator) throws ElementNotFoundException, InterruptedException {
        this.waitForElement(locator);
        this.click(locator);
    }

    public void safeOpen(String pageUrl) throws InterruptedException, ElementNotFoundException {
        this.open(pageUrl);
        this.waitForPageToLoad("30000");
        this.waitForElement("//div[@id='javascript-loaded']");
    }

    public String safeGetText(String locator) throws ElementNotFoundException, InterruptedException {
        this.waitForElement(locator);
        return this.getText(locator);
    }

    public String safeGetValue(String locator) throws ElementNotFoundException, InterruptedException {
        this.waitForElement(locator);
        return this.getValue(locator);
    }

    public void safeType(String locator, String text) throws ElementNotFoundException, InterruptedException {
        this.waitForElement(locator);
        this.type(locator,text != null ? text : "");
    }

    public void waitForElement(String locator) throws ElementNotFoundException, InterruptedException {
        for (int second = 0; ; second++) {
            if (second >= timeout) throw new ElementNotFoundException("Element not present: " + locator);
            try {
                if (this.isElementPresent(locator)) break;
            } catch (Exception e) {
            }
            Thread.sleep(1000);
        }
    }

    public void waitForText(String locator, String text) throws InterruptedException, ElementNotFoundException {
        for (int second = 0;; second++) {
            if (second >= timeout) throw new ElementNotFoundException("Text '" + text + "' in element '"+ locator +"' not present.");
            try {
                if (text.trim().equals(this.getText(locator).trim())) break;
            } catch (Exception e) {}
            Thread.sleep(1000);
        }
    }

    public void waitForValue(String locator, String text) throws ElementNotFoundException, InterruptedException {
        for (int second = 0;; second++) {
            if (second >= timeout) throw new ElementNotFoundException("Text '" + text + "' in element '"+ locator +"' not present.");
            try {
                if (text.trim().equals(this.getValue(locator).trim())) break;
            } catch (Exception e) {}
            Thread.sleep(1000);
        }
    }
}

7 Responses to “Selenium Testing of massive Ajax Apps”

  1. Excellent.
    ~ T

  2. How are you using this? Are you using these new functions as par of the IDE or RC? If you are using it as part of IDE, how is the FF extension recognizing your customized code?

    Thanks for the help!

    Stephen

  3. Hello Stephen,

    these functions are code I run with Selenium RC. I think you could also extend the Selenium IDE but then you have to extend the Javascript files with corresponding code.
    Because we run our tests as automated tests, Selenium RC is the best choice for us. In this way we could include the tests in TestNG (or JUnit) and simple run it in different browsers.

    Hope that helps. If you have questions about the setup, don’t hesitate to contact me.

    Cheers
    Simon

  4. I am using WaitForElement for all contorls. But still when i run my test i am getting following errormessage.

    For ex: while clicking button, i am getting below error message:

    click[btnNext, ] on session 058d1db6d44847258753cada7bb83704
    11:55:12.685 INFO – Got result: ERROR: Command execution failure. Please search the forum at http://clearspace.openqa.org for error details from the log window. The error message is: Unspecified error. on session 058d1db6d44847258753cada7bb83704

    This happens for contols like button, sometimes radio button, sometimes checkboxes.

    Please Help!!!!!!!!!!!

  5. //Next Button
    string btnNext = “btnStep3Next”;
    Assert.IsTrue(selenium.IsElementPresent(btnNext), “Next Button is not found”);
    SeleniumUtil.WaitForElement(selenium, btnNext, 60);
    selenium.Click(btnNext);

    public static void WaitForElement(ISelenium selenium, string elementId, int timeout)
    {
    // Loop initialization.
    for (int second = 0; ; second++)
    {
    if (second >= timeout)
    Assert.Fail(String.Format(“Waiting for Element {0} timed out.”, elementId));
    try
    {
    //Search for element and if available then break loop.
    if (selenium.IsElementPresent(elementId)) break;
    }
    catch (Exception)
    { }

    //pause for one second
    Thread.Sleep(1000);
    }

    ErrorMessage:
    click[btnNext, ] on session 058d1db6d44847258753cada7bb83704
    11:55:12.685 INFO – Got result: ERROR: Command execution failure. Please search the forum at http://clearspace.openqa.org for error details from the log window. The error message is: Unspecified error. on session 058d1db6d44847258753cada7bb83704

  6. Hello,

    indeed a strange behaviour.

    Let me understand your code:
    - in the first Assert.isTrue you check if there is the button. But it is normally not there, that’s why you want to step into the waiting loop. But doesn’t the failing assert stop the execution flow?
    - The waiting loop seems to be all right and if there is no failure and the button appears in the time range, the button is clicked. But this is also checked with the first statement, in the other I think we would’t reach that point.

    If the asserts are ignored you could end up with a button click without a real button on the screen. But that is just an assumption. I also have no clue what the “unspecified error” is. Hope you can resolve your problems.

    Cheers
    Simon

  7. Hi Simon: Thank you for the excellent article. PushToTest provides professional technical support for Selenium. Many of our customers are dealing with Ajax applications. Your article presents a great method for overcoming Ajax issues in a Selenium context. -Frank

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>