djangoproject.com | nginx.org | python.org | linux.com
version seven.
  http://demongin.org
demongin.org - Using xpaths to Write Better Selenium Test Scripts in Python

Using xpaths to Write Better Selenium Test Scripts in Python

Two example of how to use xpaths in test scripts written in python: first, how to count input boxes in a fieldset; second, how to find the text value for a th tag.


Monday, 2010-01-11 | Careerism, Programming, Testing

XPath uses path expressions to select nodes or node-sets in an XML document. These path expressions look very much like the expressions you see when you work with a traditional computer file system.

w3schools.com

One of the advantages of writing test scripts for web applications with Selenium is that you can use xpaths to find elements of your HTML source. In this way, Selenium can make test-writing a task that is not totally unlike using BeautifulSoup to scrape or interact with an HTML document.

Obviously, if you're working on commercial code, your source code will include a lot of a id and value tags. You'll often see things like this:

<div style="margin-top: 10px;">
<label for="j_password">Password:</label>
<input id="j_password" name="j_password" size="18" type="password" />
</div>
The use of these tags makes them easy to manipulate in your Selenium test scripts: an id attribute functions as a "locator" when writing these scripts in python. Filling the j_password box, for example, is easy-peasy:
sel.type("j_password", "my password")
Frequently, however, it will not be the case that you have the luxury of an id tag. Maybe the engineers are lazy or maybe their framework is generating these things dynamically. Let's say, for example, you were trying to automate the submission of a form that had code like this:
<form target=""> <fieldset class="basic">
<label>Name:</label><input id="_id75:_id77" name="_id75:_id77" value="" size="15" type="text" />
<label>Handle:</label><input id="_id75:_id79" name="_id75:_id79" value="" size="10" type="text" />
</fieldset></form>

At first glance, the name and id values on the input tag are temping, but using what are very obviously randomly generated values in your tests is, as I've mentioned before, weak-sauce test-writing.

Those label tags won't help you either and the size and type attributes of theinput tag are subject to change and too vague (respectively).

The smart thing to do here is write an xpath statement using the fieldset class and the number of the input box you're looking for. This is a sort of "counting method" that is pretty safe to use in contexts where the form isn't subject to change. Check it out:

sel.type("//form[@target='']/fieldset[@class='basic']/input[1]", client_handle)
This starts with the form that has a blank target, then looks for a fieldset with the class "basic", then takes the first input box in that fieldset. If you needed the second input box, you could swap out the "1" for a "2", and so on.

Now, suppose you were trying to automate the submission of a form that had UI code that had even fewer identifiers and could change dynamically (rendering the counting method used above out of the question). Imagine that you were working with a table that was generated on the fly, and produced code that looked like the following:
<tbody>
					<tr>
						<th>Primary Contact:</th>
						<td><input id="" name="editorForm:_id86" value="" type="text"></td>
						<th>Contact Phone:</th>
						<td><input id="" name="editorForm:_id88" value="" type="text"></td>
					</tr>
</tbody>

At first glance, it looks like your engineers (or, more likely, their framework) have left you high and dry on this one: the tbody has neither an id nor a class, ditto the tr, the th, the td and the input.

Code like this is, without a doubt, tough to work with, when you're trying to write effective, scalable and durable tests.

Once again, however, knowing how to write xpaths saves the day, however, as you can find the input using the text within the th's. For the above code, you could fill that input thus:

sel.type("//tbody/tr/th[text()='Primary Contact:']/../td/input[1]", "Timothy O'Connell")
sel.type("//tbody/tr/th[text()='Contact Phone:']/../td/input[1]", "123 456 7890")
Using xpaths is often a lot of extra work, but when you don't have id values, they can be a useful tool in writing better tests.