Basic XPath Theory
This chapter will provide you with a basic understanding of XPath. Just enough to cover the basic requirements for writing Selenium tests. XPath is the XML Path Language. Since all HTML, once loaded into a browser, becomes well structured and can be viewed as an XML tree, we can use XPath to traverse it. XPath is a full programming language so you can perform calculations (e.g. last()-1), and use boolean operations ( e.g. or, and). NOTE: To help follow this section you might want to visit the web page: http://compendiumdev.co.uk/selenium/basic web page.html With the page loaded, use the Firebug plugin FireFinder to try out the XPath statements listed. Ill include the listing of the XHTML for the basic web page.html here so you can follow along:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html> <head> <title>Basic Web Page Title</title> </head> <body> <p id="para1" class="main">A paragraph of text</p> <p id="para2" class="sub">Another paragraph of text</p> </body> </html>
15.1 XPath Expressions
XPath expressions select nodes or node-sets from an XML document. e.g. The XPath expression //p would select the following node-set from the example in the Basic HTML Theory section: 161 Figure 15.1: FireFinder matching p tag
15.2 Node Types
XPath has different types of nodes. In the example XHTML these are: Document node (the root of the XML tree): <html> Element node e.g.: <head><title>Basic Web Page Title</title></head> <title>Basic Web Page Title</title> <p id="para1">A paragraph of text</p> Attribute node e.g. id="para1"
15.3 Selections
15.3.1 Start from root
Start selection from the document node with /, this allows you to create absolute path expressions e.g. /html/body/p Matches all the paragraph element nodes.
assertEquals(2,selenium.getXpathCount("/html/body/p"));
15.3.2 Start from Anywhere
Start selection matching anywhere in the document with //, this allows you to create
relative path expressions. e.g. //p Matches all paragraph element nodes.
assertEquals(2,selenium.getXpathCount("//p"));
15.3.3 By Element Attributes
Select attribute elements with @ followed by an attribute name. e.g. //@id Would select all the attribute nodes.
assertEquals(2,selenium.getXpathCount("//@id"));
15.4 Predicates
Predicates help make selections more specific and are surrounded by square brackets.
15.4.1 Predicates can be indexes
//p[2] Matches the second p element node in the node-set
assertEquals("Another paragraph of text",selenium.getText("//p[2]"));
//p[1] Matches the first p element node.
assertEquals("A paragraph of text",selenium.getText("//p[1]"));
15.4.2 Predicates can be attribute selections
//p[@id=para1] Matches the p element node where the value of the attribute id is para1
assertEquals("A paragraph of text", selenium.getText("//p[@id=para1]"));
//p[@class=main] Matches the p element node where the value of the attribute class is main.
assertEquals("A paragraph of text", selenium.getText("//p[@class=main]"));
15.4.3 Predicates can be XPath functions
//p[last()] Select the last paragraph.
assertEquals("Another paragraph of text",selenium.getText("//p[last()]"));
15.4.4 Predicates can be comparative statements
//p[position()>1] This returns all but the first p element.
assertEquals("Another paragraph of text", selenium.getText("//p[position()>1]")); assertEquals("A paragraph of text", selenium.getText("//p[position()>0]"));
15.5 Combining Match Queries
You can combine several selections by using e.g. //p | //head Match any paragraph element node and also get the head element node.
assertEquals(3,selenium.getXpathCount("//p | //head"));
15.6 Wild Card Matches
You can also use wild cards:
15.6.1 node()
node() matches
any type of node (document, element, attribute) e.g //node()[@id=para1] This matches any node with an id of para1.
assertEquals(1,selenium.getXpathCount("//node()[@id=para1]")); //node() matches //body/node() matches
all the nodes (try it, you may not get the results you expect). all the nodes in the body (again, try it to see if you get the value
you expect).
15.6.2 *
Match anything depending on its position with * e.g. @* Matches any attribute. //p[@*=para1] Would match the first paragraph
assertEquals(1,selenium.getXpathCount("//p[@*=para1]")); * can
match nodes e.g. //*[@*] Matches anything with any attribute
assertEquals(2,selenium.getXpathCount("//*[@*]"));
//*[@id] Matches anything with an id attribute.
assertEquals(2,selenium.getXpathCount("//*[@id]")); /html/*
Matches all children of the document node.
assertEquals(2,selenium.getXpathCount("/html/*"));
15.7 Boolean Operators
You can setup matches with multiple conditions.
15.7.1 and
//p[starts-with(@id,para) and contains(.,Another)] Find all paragraphs where the id starts with para and the text contains Another i.e. the second paragraph.
assertEquals("Another paragraph of text", selenium.getText( "//p[starts-with(@id,para) and contains(.,Another)]"));
15.7.2 or
//*[@id=para1 or @id=para2] Find any node where the id is para1 or the id is para2 i.e. our two paragraphs.
assertEquals(2, selenium.getXpathCount("//*[@id=para1 or @id=para2]"));
15.8 XPath Functions
Since XPath is actually a programming language it has built in functions which we can use in our XPath statements. Some common XPath functions are listed below
15.8.1 contains()
contains() allows
you to match the value of attributes and elements based on text anywhere in the comparison item e.g. //p[contains(.,text)] Match any paragraph with text in the main paragraph e.g. Both our paragraphs
assertEquals(2, selenium.getXpathCount("//p[contains(.,text)]")); //p[contains(.,Another)]
Match any paragraph with Another in the paragraph text, in our example this would match the second paragraph.
assertEquals("Another paragraph of text", selenium.getText("//p[contains(.,Another)]"));
//p[contains(@id,1)] This would match any paragraph where the id had 1 in it, in our example this is the first paragraph
assertEquals("A paragraph of text", selenium.getText("//p[contains(@id,1)]"));
15.8.2 starts-with()
starts-with() allows
you to match the value of attributes and elements based on text at the start of the comparison item e.g. //*[starts-with(.,Basic)] Would match any node where the contents of that node start with Basic, in our example this would match the title.
assertEquals("Basic Web Page Title", selenium.getText("//*[starts-with(.,Basic)]")); //*[starts-with(@id,p)]
This would match any node where the id name started with p, in our example this would match the paragraphs.
assertEquals("Basic Web Page Title", selenium.getText("//*[starts-with(.,Basic)]"));
15.8.3 Many More
There are many XPath functions available to you, I have just picked a few of the most common ones that I use. I recommend that you visit some of the web sites below to learn more about XPath functions, and experiment with them. Recommended web sites for function references: http://unow.be/rc/w3xpath1 http://unow.be/rc/msxpathref2 http://unow.be/rc/pitstop13
15.9 XPath optimisation
For our testing we typically want to get the shortest and least brittle XPath statement to identify elements on a page. Some XPath optimisation strategies that I have used are: use the id, use a combination of attributes to make the XPath more specific, start at the first unique element We have to make a trade off between handling change and false positives. So we want the XPath to return the correct item, but dont want the test to break when simple changes are made to the application under test.
15.9.1 Use the ID
If the element has a known id then use that e.g. //*[@id=p2] Or you probably want to be even more specific and state the type e.g. //p[@id=p2]
1http://www.w3schools.com/XPath/xpath
functions.asp
2http://msdn.microsoft.com/en-us/library/ms256115.aspx 3http://www.xmlpitstop.com/ListTutorials/DispContentType/XPath/PageNumber/1.aspx
15.9.2 Use the attributes
If it doesnt have an id, but you can identify it with a combination of attributes then do that. Our example XHTML doesnt have enough nodes to make this clear, but we did this with our initial Search Engine testing. e.g. //input[@name=q and @title=Search]
15.9.3 Start at the first unique element
If there is really nothing to distinguish the element then look up the Ancestor chain and find the first unique element. e.g. //form/table[1]/tbody/tr[1]/td[2]/input[2] This approach starts to introduce the chance of false positives since a new input might be added before the one we want, and the test would start using that instead.
15.10 Selenium XPath Usage
Selenium uses XPath in locators to identify elements e.g.
selenium.isElementPresent("xpath=//p[@id=p1]")
Because only XPath locators start with // it is possible to write XPath locators without adding xpath= on the front. e.g.
selenium.isElementPresent("//p[@id=p1]") The specific XPath command getXpathCount expects
an XPath statement as its argument so you should not use xpath= in front of the XPath locator. Possibly a good reason for not using xpath= in any of your locators, but each of us has personal coding styles so you get to make a choice as to which you prefer. e.g.
selenium.getXpathCount("//p"); //return a count of the p elements
You can combine the XPath statement in the getAttribute statement to get specific attributes from elements e.g.
assertEquals("p2", selenium.getAttribute("xpath=//p[2]@id")); assertEquals("p2",selenium.getAttribute("//p[2]@id"));
The @id (or more specifically @<attribute-name>) means that the statement is not valid XPath but Selenium parses the locator and knows to split off the @id on the end before using it.