I recently decided to give the functional web testing framework Selenium (and the related premium test execution service, Sauce Labs) a trial run, using the Altos Research purchase workflow as a testbed. After a few hours of experimenting, I had run into a stone wall: Automating the test of an jquery autocomplete menu.
In the workflow I wanted to test, users are asked to type the first few letters of the name of a US County into a text field. An AJAX request is then triggered to find matching counties, and a list of matches is presented to users. It looks something like this:
Users are then expected to use the mouse or keyboard to select an item from the menu, which is then added to their shopping cart. Because this technique relies heavily on dynamically created DOM elements and jquery javascript, the normal Selenium Firefox macro recorder/IDE won't record any of the important events required to make the menu behave like it should.
After much experimentation and fiddling around with levers and knobs, I came up with the following successful sequence that would trigger Selenium to correctly invoke the autocomplete menu (the first four entries in this test case). The name of the text field on which the autocomplete is based is 'county_search'. The user will enter the letters 'lake', causing the server to search for all matching counties. One of those matches will be 'Lake County, IL':
The trick is in the XPath expression that tells Selenium how to select the item from the dynamically generated menu. As it turns out, the default behavior of the JQuery autocomplete widget is to render each item inside an anchor (<a>) within a list item (<li>). Or in other words:
<html><body><ul><li><a>Lake County, IL</a></li></ul></body></html>
When the script is converted to Java/Junit source code, it will look like this:
selenium.typeKeys("county_search", "lake");
for (int second = 0;; second++) {
if (second >= 60) fail("timeout");
try { if (selenium.isTextPresent("Lake County, IL")) break; } catch (Exception e) {}
Thread.sleep(1000);
}
selenium.mouseOver("//html/body/ul/li/a[. = \"Lake County, IL\"]");
selenium.click("//html/body/ul/li/a[. = \"Lake County, IL\"]");