This library is an extension to Tapestry that allows you to use XPath expressions to query the Tapestry DOM. The most common use of this is to simplify your page and component tests.
Tapestry XPath is hosted on the Maven central repository
If you don't use Maven, you can download jars from the Maven repository by hand: http://repo1.maven.org/maven2/net/sourceforge/tapestryxpath/tapestry-xpath/1.0.1/
If you use Maven, just include this dependency in your POM:
<dependency> <groupId>net.sourceforge.tapestryxpath</groupId> <artifactId>tapestry-xpath</artifactId> <version>1.0.1</version> </dependency>
For the latest snapshot build you can download directly from: https://oss.sonatype.org/content/groups/public/net/sourceforge/tapestryxpath/tapestry-xpath/1.0.2-SNAPSHOT.
If you use Maven, just include the dependency:
<repository> <id>sonatype-nexus</id> <url>https://oss.sonatype.org/content/groups/public</url> </repository> <dependency> <groupId>net.sourceforge.tapestryxpath</groupId> <artifactId>tapestry-xpath</artifactId> <version>1.0.2-SNAPSHOT</version> </dependency>
The core of Tapestry XPath is the class TapestryXPath. Simply create a TapestryXPath object using the static method xpath() and then use the methods on it to apply the XPath to an object from the Tapestry DOM (usually Document).
If you don't like checked exceptions then you can use the class UncheckedTapestryXPath which does exactly the same thing but throws TapestryXPathException, which is a RuntimeException, rather than JaxenException.
For example, this is a test to check that the rows in a table have particular classes on them (e.g. for zebra stripes)
public void testZebraStripes() { Document page = pageTester.renderPage("MyPage"); List<String> classes = TapestryXPath.xpath("id('mytable')//tr") .selectElementsAttribute(page, "class"); assertEquals(Arrays.asList("odd", "even", "odd", "even"), classes); }
When you create an XPath object, the XPath string will be parsed, so in production code you may want to create the object once and store it for repeated use (e.g. in a static final variable). You generally won't do this for tests because you tend to use lots of different XPath and the parsing performance will not notice for tests.
private static final UncheckedTapestryXPath IMG_IN_TABLE_XPATH = UncheckedTapestryXPath.xpath("//table//img");
Before using XPath, consider adding additional HTML ids if it makes the page/component more testable - i.e. the tests can drive the "need" for an id.
However, that doesn't mean that every component needs an id. One particularly useful feature of XPath is the descendant axis: you may find it useful to make wide use of this structure: id('some_root')//table - the id matches some key element in the page and then the descendant axis (//) tracks down the element you're after.
Descendants makes the test flexible in the face of simple structural changes in the HTML. In other words, if the structure changes from <div id='some_root'><table> to <div id='some_root'><div><table> - the test still works.
A useful way of thinking about this can be to develop the HTML, tests and XPath while being very mindful of how the end user thinks and the concepts they use. If the user would say "I expect to see 1234 in the financials table" then "financials table" is an important concept of and you can add an id and use that directly: <table id="financialsTable">. If they say "I expect to see 1234 in the financials table for IBM" then you might have ids for the companies and classes for the tables, so the XPath might be: id('ibm')//table[@class='financials'].
The user's concepts are much less likely to change than the page structure, so this approach can help you to write less brittle tests; you may also find working like this has other benefits in readability, elegance of code and helping the system to flex nicely when requirements change.