views:

2848

answers:

3

I have an internet-explorer only web application.

I'm exploring what we can do to automate the testing.

Selenium looks like a good tool, but to be able to activate links etc I need to tell selenium where they are. The application wasn't built with this kind of testing in mind, so there generally aren't id attributes on the key elements.

No problem, I think, I can use Xpath expressions. But finding the correct Xpath for say, a button, is a royal pain if done by inspecting the source of the page.

With Firefox / Firebug, I can select the element then use "Copy Xpath" to get the expression. Is there any way of doing this with IE?

(I have the IE Developer Toolbar and it's frustratingly close - I can click to select the element of interest, display all sorts of information about it... but I can't see any convenient way of determining the Xpath for it)

+4  A: 

I would use bookmarklets. I have one XPath related, but I don't know if it works in IE. I gotta go but I will test it and give it if it works on IE.

Two bookmarklet sites for Web developers from my bookmarks: Subsimple's bookmarklets and Squarefree's Bookmarklets. Lot of useful things there...

[EDIT] OK, I am back. The bookmarklet I had was for FF only, and wasn't optimal. I finally rewrote it, although using ideas from the original one. Can't find back where I found it.

Expanded JS:

function getNode(node)
{
  var nodeExpr = node.tagName;
  if (nodeExpr == null)  // Eg. node = #text
    return null;
  if (node.id != '')
  {
    nodeExpr += "[@id='" + node.id + "']";
    // We don't really need to go back up to //HTML, since IDs are supposed
    // to be unique, so they are a good starting point.
    return "/" + nodeExpr;
  }
// We don't really need this
//~   if (node.className != '')
//~   {
//~     nodeExpr += "[@class='" + node.className + "']";
//~   }
  // Find rank of node among its type in the parent
  var rank = 1;
  var ps = node.previousSibling;
  while (ps != null)
  {
    if (ps.tagName == node.tagName)
    {
      rank++;
    }
    ps = ps.previousSibling;
  }
  if (rank > 1)
  {
    nodeExpr += '[' + rank + ']';
  }
  else
  {
    // First node of its kind at this level. Are there any others?
    var ns = node.nextSibling;
    while (ns != null)
    {
      if (ns.tagName == node.tagName)
      {
        // Yes, mark it as being the first one
        nodeExpr += '[1]';
        break;
      }
      ns = ns.nextSibling;
    }
  }
  return nodeExpr;
}

var currentNode;
// Standard (?)
if (window.getSelection != undefined) 
  currentNode = window.getSelection().anchorNode;
// IE (if no selection, that's BODY)
else 
  currentNode = document.selection.createRange().parentElement();
if (currentNode == null)
{
  alert("No selection");
  return;
}
var path = [];
// Walk up the Dom
while (currentNode != undefined)
{
  var pe = getNode(currentNode);
  if (pe != null)
  {
    path.push(pe);
    if (pe.indexOf('@id') != -1)
      break;  // Found an ID, no need to go upper, absolute path is OK
  }
  currentNode = currentNode.parentNode;
}
var xpath = "/" + path.reverse().join('/');
alert(xpath);
// Copy to clipboard
// IE
if (window.clipboardData) clipboardData.setData("Text", xpath);
// FF's code to handle clipboard is much more complex 
// and might need to change prefs to allow changing the clipboard content.
// I omit it here as it isn't part of the original request.

You have to select the element and activate the bookmarklet to get its XPath.

Now, the bookmarklet versions (thanks to Bookmarklet Builder):

IE
(I had to break it in two parts, because IE doesn't like very long bookmarklets (max size varies depending on IE versions!). You have to activate the first one (function def) then the second one. Tested with IE6.)

javascript:function getNode(node){var nodeExpr=node.tagName;if(!nodeExpr)return null;if(node.id!=''){nodeExpr+="[@id='"+node.id+"']";return "/"+nodeExpr;}var rank=1;var ps=node.previousSibling;while(ps){if(ps.tagName==node.tagName){rank++;}ps=ps.previousSibling;}if(rank>1){nodeExpr+='['+rank+']';}else{var ns=node.nextSibling;while(ns){if(ns.tagName==node.tagName){nodeExpr+='[1]';break;}ns=ns.nextSibling;}}return nodeExpr;}
javascript:function o__o(){var currentNode=document.selection.createRange().parentElement();var path=[];while(currentNode){var pe=getNode(currentNode);if(pe){path.push(pe);if(pe.indexOf('@id')!=-1)break;}currentNode=currentNode.parentNode;}var xpath="/"+path.reverse().join('/');clipboardData.setData("Text", xpath);}o__o();

FF

javascript:function o__o(){function getNode(node){var nodeExpr=node.tagName;if(nodeExpr==null)return null;if(node.id!=''){nodeExpr+="[@id='"+node.id+"']";return "/"+nodeExpr;}var rank=1;var ps=node.previousSibling;while(ps!=null){if(ps.tagName==node.tagName){rank++;}ps=ps.previousSibling;}if(rank>1){nodeExpr+='['+rank+']';}else{var ns=node.nextSibling;while(ns!=null){if(ns.tagName==node.tagName){nodeExpr+='[1]';break;}ns=ns.nextSibling;}}return nodeExpr;}var currentNode=window.getSelection().anchorNode;if(currentNode==null){alert("No selection");return;}var path=[];while(currentNode!=undefined){var pe=getNode(currentNode);if(pe!=null){path.push(pe);if(pe.indexOf('@id')!=-1)break;}currentNode=currentNode.parentNode;}var xpath="/"+path.reverse().join('/');alert(xpath);}o__o();
PhiLho
Looks interesting, but I've no idea how to add a bookmarklet :( Can you point me at at something that explains this?
Paul
I hope you get such answer...To add a bookmarklet is dead simple: copy the javascript: line (one by one in the case of my IE marklets) and paste it in the address bar of the browser (and hit Return). You can then add it to favorites (they are called favlets too) to call them back. JS is then run.
PhiLho
I get "Error: ps is null. Source file: javascript:function%..."Is this code now defunct in FF 3.5.5?
Photodeus
@Photodeus: Oops! Obviously you are the first one to test the FF version... I had a typo, a ps instead of ns (in ns=ps.nextSibling). Fixed in the answer. Thanks for reporting.
PhiLho
+1  A: 

Since bookmarklet use puzzled Paul, I though I should add a little introduction to their usage. I do it in a separate message to avoid mixing stuff.

Bookmarklets (also called favlets) are little JavaScript scripts (sic) designed to be pasted in the address bar of the browser (like any other URL) and thus to run on the current page.
After running it (paste, hit Enter), you can bookmark it for reuse (add it to favorites in IE). Note that the browser might bookmark the original URL instead, you have then to edit the bookmark and replace the URL with your script.
Of course, you can add it to the URL bar for quick access too.

These scripts act like being part of the current page, accessing global JS variables and functions, Dom objects, etc.
They can be super simple, like the seminal javascript: alert("Hello world!"); or quite complex like the one above. If it returns a value (or if last expression has a value), the value replaces the current page. To avoid this, you can finish the script with alert (to display a result) or wrap the script in a function definition and call this function, like I did above. (Some also put void(0); at the end, but I saw it is seen as bad practice.)

The function solution has the advantage of making all variables of the script local to the applet (if declared with var, of course), avoiding interferences/side-effects with scripts on the local page. That's also why the wrapping function should have a name unlikely to clash with a local script.

Note that some browsers (read: "IE") can limit the size of favlets, the max. length varying with version (tending to decrease). That's why all useless whitespace is removed (the bookmarlet builder linked above is good for that) and I removed the explict comparisons with null and undefined I usually do. I had also to split the favlet in two, first part defining a function (living as long as the page isn't changed/refreshed), second part using it.

Useful tool, particularly on browsers not allowing user scripts (à la Greasemonkey) or without this extension.

PhiLho
+1  A: 

there's a toolbar named "ie debugbar" you can use it to find the exact xpath..