tags:

views:

3483

answers:

4

Is there a way to execute some Xpath code in a Selenium test? I know you can execute javascript, but I'm looking for a way to execute some Xpath code beyond simply locating an element on the page.

Anyone run into this?

A: 

Andrew, Interesting question. You might want to try the Selenium Developers forum or the Advanced Usage Forum, since I'm probably the only Selenium Developer that hangs out on StackOverflow right now :)

That said, I'm positive you can do this, but I can't get you the code right now (on a laptop that doesn't have the Selenium source). It will require getting a handle to the "browser bot" (see examples of running arbitrary JS code for how to do that). From there, there may be a function call (or likely chain of calls) that can get you what you want: a call to Selenium's XPath abstraction.

Or, if you're OK with only supporting Firefox, just look up the JS code to do Firefox XPath queries and call that. It's fairly easy to embed in to a storeEval-type of command. Alternatively, remember that Selenium injects an XPath library along side Selenium's JS library. You could call that directly too. You wouldn't get the shortcuts and abstractions that Selenium provides for some simple XPath expressions, but I'm sure it'd work.

Can you share what you're trying to do? Generally this stuff comes up for me when I'm trying to get an attribute or text value from an element.

Patrick Lightbody
A: 

It is my understanding that XPath functions are a part of XPath 2.0. Selenium does not support 2.0. Thus, executing XPath functions in selenium is not possible.

Andrew
A: 

According to the W3C XPath 1.0 spec, there is functions support in XPath 1.0, but with a rather limited set of available functions: http://www.w3.org/TR/xpath#section-String-Functions

Especially the "ends-with" function is not available - I use expressions like

//input[substring(@id, string-length(@id) - string-length('<idSuffix>') +1) = '<idSuffix>']

as a workaround (in this case to find an input element which has an id ending '<idSuffix>').

A: 

Take a look at line 1177 of the htmlutils.js file in the 1.0.1 release of Selenium (this in inside the eval_xpath() function which handles all of the XPath queries):

    var result = xpathResult.iterateNext();

The problem here is that according to the XPathResult specification, iterateNext() will throw an exception if the result type is not an iterable set of nodes. Since Selenium blindly assumes that all XPath queries result in a node set you can't execute a query that returns a number or a string.

What you can do is modify htmlutils.js by replacing lines 1177-1181 with something like the following:

    var iterable =
     xpathResult.resultType == xpathResult.ORDERED_NODE_ITERATOR_TYPE ||
     xpathResult.resultType == xpathResult.UNORDERED_NODE_ITERATOR_TYPE;

    if (iterable) {
     var result = xpathResult.iterateNext();
     while (result) {
         results.push(result);
         result = xpathResult.iterateNext();
     }
    } else if (xpathResult.resultType == xpathResult.NUMBER_TYPE) {
     results.push(inDocument.createTextNode(xpathResult.numberValue));
    }

The idea is that if we see a number value we wrap it inside of a DOM TextNode and pass it on as usual, a change which is more or less transparent as far as Selenium is concerned. Selenium's getText command has special handling for TextNodes (it returns their text content as a String) so now you can do something like

int count = Integer.parseInt(selenium.getText("xpath=count(//td) + 1"), 10);

where selenium is an instance of DefaultSelenium if you're using the SeleniumRC Java client like I am. Of course this is a trivial example since you could have just used getXpathCount() and there are other XPathResult types to handle as well, but this should get you started down the right path.

Also note that the particular lines mentioned above are only for the browser-native execution of XPath queries. Execution using Selenium's built-in XPath library is handled immediately afterwards inside eval_xpath() and would probably require similar modifications - but I haven't looked into this yet.