Using Web Anchors to Automate Non-Unique Controls

Article Tools

This very interesting discussion in out forums convinced me to write an article explaining my approach to automating non-unique web (and other environments) controls.

Non-unique Web Controls

If the world was perfect, our application would have unique, well named controls, all easily identifiable and immediately usable by our scripts. More often than not, though, our applications are a mess of unordered controls, with cryptic names, who are deemed non-standard by QTP. In the worst cases, we’re stuck with unnamed non-unique controls, who are only identified by their infamous Index property. And we all know that we should never ever ever trust the Index property.

Here’s an example for such a case:

First

Second

Third

Forth

While it’s absolutely clear to us which WebEdit goes with which label, QTP is completely blind to the connection between the two, and treats all the WebEdits as non-unique unrelated controls. Try to add them to your object repository, and you’ll get stuck with WebEdit, WebEdit_2, WebEdit_3, And WebEdit_4, with only the Index property to set them apart. If it were a table, we could’ve used the GetRowWithCellText and ChildItem commands to build some sort of workaround, but this structure isn’t even a proper table.

But do not despair, a solution will soon follow!

[hideit]

Exploring The HTML Hierarchy

So, what can we do? Well, we could use the fact that a webpage is just a representation of HTML code, and HTML is hierarchical in its nature. Which usually means that if we see some connection between two elements, there’s probably an underline connection between them in the page’s HTML structure. Here’s the HTML code for this example:

<div>
  <span style="width: 60px">First</span>
  <span><input /></span>
</div>
<div>
  <span style="width: 60px">Second</span>
  <span><input /></span>
</div>
<div>
  <span style="width: 60px">Third</span>
  <span><input /></span>
</div>
<div>
  <span style="width: 60px">Forth</span>
  <span><input /></span>
</div>

If you’re not accustomed to reading HTML code, this can be a little disorienting, which is why I always use HTML visualize aids such as IE Dev Toolbar, FireBug And DebugBar .

Here’s DebugBar’s visualization of the HTML structure:

image

It’s now clear that indeed, there is a connection between each label and its matching text-box. Each of them is nested within a SPAN tag wrapper, but they are both under the same DIV wrapper. How can we use that to our advantage? We cannot tell QTP “Find the WebEdit that resides under the same parent as the WebElement ‘First’”. QTP doesn’t operate that way; and in any case doesn’t contain the ability to “climb” to a common parent, but rather only to drill down to the childcontrols via .ChildObjects command. One option would be to write an extensibility that identifies each DIV as a special object – but that’s quite an advanced technique, and I’ll leave it for our Mastering The Web Channel. Another option would be to use the HTML runtime objects.

Using Runtime Objects

Luckily, QTP web objects have the ability to expose their “Runtime backbone”, allowing us to touch the pure HTML DOM entities behind them. This will enable us to harness the full power of HTML hierarchical structure, as the runtime objects has properties which allows us to find their parents, children and siblings in the HTML tree. We can use the .Object property to access a runtime object:

Browser("index:=0").Page("index:=0").WebElement("innertext:=First", "html tag:=SPAN").Object

If we’re using the PDM.DLL debug hack, we could throw this object unto the debug viewer, and take a closer look at it. As you can see, an HTML element’s runtime object contains many interesting properties, events and methods. I’m not going to talk about it in depth, because there are a lot of them, and each deserves a dedicated article on its own, but you’re more than welcomed to Google them and explore their almost infinite usefulness (for example, you can validate an element’s appearance with the .CurrentStyle complex property).

image

We’re interested in the properties which expose the element’s hierarchical relations. Usually, .ChildNodes and .ParentNode are more than enough for just about any operation you could think of (they allow you to freely climb up and down the hierarchy); but to make out lives easier, we’re going to use two particular shortcuts: .NextSibling brings us the next child of the parent of the current node, and .FirstChild brings us the first of the current node’s children. They can be easily replaced by a few .ParentNode and .ChildNodes commands, but they are much shorter.

*In some computers – you’ll have to replace .FirstChild with .LastChild, because the browser will add an extra line brake before the WebEdit

Now, with the HTML structure in one hand, and the commands needed to access it on the other, we’re ready to build our script.

From the Anchor to the Target

Here’s our strategy: It’s very easy to gain a lock on one of the labels (first, second, etc.). We can use that lock as an anchor to a fixed point on the screen, and use the HTML hierarchy to travel from that point to our target – the relevant WebEdit.

Using the visualization DebugBar provides, we can see that if we lock onto the first SPAN tag, we need to jump to its “Brother” (i.e. the second SPAN tag), and from there to its Input child tag (a WebEdit is an Input tag). Phrase this with the runtime properties and you get:

Browser("index:=0").Page("index:=0").WebElement("innertext:=First", "html tag:=SPAN").Object.nextSibling.firstchild

*In some computers – you’ll have to replace .FirstChild with .LastChild, because the browser will add an extra line brake before the WebEdit

This refers to the “First” anchor, of course. Naturally, to get to the “Second” anchor, we should just replace the Descriptive Programming description of the WebElement. The code would still work as all the anchors have the same “relationship” with their matching targets.

Let’s test this out: Write something into the first textbox, and execute the following code. You should see a message box with the relevant text.

MsgBox Browser("index:=0").Page("index:=0").WebElement("innertext:=First", "html tag:=SPAN").Object.nextSibling.firstchild.value

Now that we’re sure we’ve got the correct relations between the anchor and the target, we could build a little function to encapsulate this behavior:

Function InputEdit(sEditLabel, sValue)
   Dim oAnchor
   Dim oWebEdit

   Set oAnchor = Browser("index:=0").Page("index:=0").WebElement("innertext:=" & sEditLabel, "html tag:=SPAN")
   Set oWebEdit = oAnchor.Object.NextSibling.FirstChild

   oWebEdit.Value = sValue

End Function

'Here's an example for using the function:
InputEdit "Second", "Some Value"

No Cheating (well, some cheating is OK).

So, is that it? Not really. everything was OK until we cheated by using the command oWebEdit.Value = sValue.

This command does something no user could ever do. It bypasses the application completely, and programmically enters the value into the WebEdit. It would input the field even if it’s read-only, and would enable us to push invalid values into masked edit boxes, or other self-validating controls. This is not true automation testing, but rather hacking into the application.

In order to input the WebEdit as a user would do, we need to perform a .Set operation from QTP. But we’re not using a QTP object, but a runtime object. If we could’ve gotten the QTP object easily, we would be in this mess to begin with. So, how can we climb back up from the Runtime object we have (oWebEdit), to the QTP object we couldn’t get to begin with?

Easy – We’ll cheat just enough to get a lock on the relevant QTP object, and work appropriately from there on. Even though we can’t see it in the object spy, or even through the PDM.DLL hack, each web object has a unique identifier within the HTML DOM. We can access it via the runtime .UniqueID command, and luckily enough, we can use this ID to describe QTP objects via the “attribute/uniqueID” Test-Object property. This technique was previously described by Barak Kinarti in this article.

So, our new function would look something like this:

Function InputEdit(sEditLabel, sValue)
   Dim oAnchor
   Dim oWebEdit
   Dim sID

   Set oAnchor = Browser("index:=0").Page("index:=0").WebElement("innertext:=" & sEditLabel , "html tag:=SPAN")
   Set oWebEdit = oAnchor.Object.NextSibling.FirstChild

   sID = oWebEdit.UniqueID

   Browser("index:=0").Page("index:=0").WebEdit("attribute/uniqueID:=" & sID).Set sValue

End Function

*In some computers – you’ll have to replace .FirstChild with .LastChild, because the browser will add an extra line brake before the WebEdit

Now we’re only cheating when no one is looking, so it’s OK. Basically, as long as we’re not using them for any active operations, runtime objects are relatively safe.

[/hideit]

Other cases and environments

The example we’ve explored is relatively easy. The HTML code was clean and simple, and the path from the anchor to the target was self evident. Real world cases are usually much more complex and obscure, but the basic methodology holds:

Find an anchor, try to establish its relationship with the relevant target, track that relationship through the runtime objects, and formulate it into code.

Use that to get the target’s unique ID, and reach it with QTP.

In cases where the relationship is very tangled up (like javascript trees, for example), don’t even try using the HTML code, and go straight to tools like DebugBar, Firebug, and others.

Is this workaround unique to the Web environment? What about other environments such as .Net and Java?

Well, almost any environments which gives us access to Runtime Objects has similar hierarchical structure and properties. In .Net, for example, we can use the .Parent and .Controls properties to move up and down the application hierarchy, and the .Handle property to describe the target control in QTP (through the “hwnd” TestObject property).

And just like we have DebugBar and Firebug to help us with the HTML hierarchy, we have QTP’s built in .Net spy, as well as external open-source spy tools to help us in .Net and Java.

Actually, other environments usually allow us to accomplish ever greater goals than those described in this article: You can read our article on automating .net custom controls for a more elaborate use of the .Net runtime objects capabilities.

Previous postEffectively Delete Cookies Next postImplementing a GUI Layer with Classes (Russian Translation)

Related Posts

Post Your Comment

You must be logged in to post a comment.