Tuesday, October 20, 2009

Find functionality

So, as previously discussed there is a second way to find elements using TestComplete...by their TestObject properties.

To start, open up Internet Explorer, navigate back to http://www.google.com/, and click on the object browser tab in TestComplete. Expand iexplore, and then click on the Page("http://www.google.com/") node. On the right half of the page, click on the Methods tab, and take a second to scroll through the list. These are the methods that TC found as available in the iexplore process, on the page node. You will see a total of five find functions, all of which are usable interchangeably. Most of these are usable interchangeably, the main distinction between them is where they start looking from, and whether they return one or multiple results. Here they are, along with their parameters:

Find(propertyName, propertyValue, depth, refresh)

FindAll(propertyName, propertyValue, depth, refresh)

FindAllChildren(propertyName, propertyValue, depth, refresh)

FindChild(propertyName, propertyValue, depth, refresh)

FindId(id, refresh)

Here is a quick explanation of the differences between the five Find functions. First, Find, FindChild, and FindId all return a single TestObject. FindAll and FindAllChildren return an array of TestObjects. The only other difference is that FindChild and FindAllChildren do NOT check the current node, they start at the first child node.

Depth is how deep in the tree TC should look, and refresh determines if TestComplete needs to refresh its cache of the Page again (necessary if something has changed).

The last thing to know is that propertyName and propertyValue can be variants OR arrays. this is great, because you can find an element based upon several criteria, to make sure you have the correct one. However, due to some inherent issues with the way javascript and TestComplete works, you have to convert the arrays to a VBScript array first. However, that over-complicates things, so I'm going to ignore them for the time being.

The only other thing to note is that FindId searches for the TestObject unique id. This is the fastest and most efficient Find method, but there is no way of knowing the Id of an element before hand, since they are generated at run-time, they will always be different. You could, however, get the Id of an TestObject you are already at, and use the FindId function to reference it later. But there are other ways to do this same thing. (such as assigning the TestObject to a variant). I personally don't use the FindId function at all.

So, for our purposes of web-based testing, we really only need two find functions, Find() and FindAll(). Using the object finder, highlight the google search box, and view it in the object browser. Now, scroll through the properties, and start thinking about which of these you can use to find this later. It needs to be something unique, and something that won't ever change.

We have several options:

Name = Textbox("q")
Title = Google Search
ObjectType = Textbox
className = lst

Any or all of these could possibly be used to find our textbox. Lets try some and see how they work. Copy and paste the following into TC:

function Find()
{
var searchBox = Page().Find("Title","Google Search",100);
var searchButton = Page().Find("type","submit",100);

searchBox.value="TC Find still rocks";
searchButton.Click();

}

Go ahead and run this script. It finds the textbox correctly, and modifies the text, however, it clicks the "I feel lucky" button, and not the google search button. Why? It turns out, both buttons have type = submit. So TC returned the first node that matched our criteria, which was not the correct one. Why didn't this happen in our last example using NativeWebObject.Find? It turns out that the TestObject.Find function parses the tree in reverse order, so it will always return the last element on the page, whereas NativeWebObject.Find goes from top to bottom.

There are a couple ways we could fix this. We could use FindAll, and get an array if we want to have access to both buttons. We could then do whatever we wanted, like returning the last element, or Of course, the easiest way to fix this is to get better search criteria.

So, ask yourself, how does a person know which one to click on? Well, in this case the text on the buttons is different, so lets try a Find based upon that.

function Find()
{
var searchBox = Page().Find("Title","Google Search",100);
var searchButton = Page().Find("value","Google Search",100);

searchBox.value="TC Find still rocks";
searchButton.Click();

}

Now, when we run our function, we click the correct button. So this works great...except that if we try to run this function a second time, on the Google Results page, it doesn't work!!! Why not? Well, the button text on the Google results page says "Search", whereas on the Google search page it says "Google Search". So TC can't find it. So what do we do now? Well, there are once again several options. We can add a wildcard "*" to the string, but that doesn't really work either. I think its easier to just change our search and search for name. (as I discussed earlier we could also pass an array of Names and Values, and search for multiple criteria)

function Find()
{
var searchBox = Page().Find("Name","Textbox*q*",100);
var searchButton = Page().Find("Name","SubmitButton*btnG*",100);


searchBox.value="TC Find still rocks";
searchButton.Click();

}

So now we have, once again, a function that works for both the google search page, and the google results page. We could easily functionize this with some parameters, and call it with whatever we want.

But you can see, its much harder and more complicated to use the TestObject.Find functionality, instead of the NativeWebObject.Find functionality, but we have much greater control over what we are searching for. However, for any modern web page, NativeWebObject.Find will work for 90% of the elements. Any decent programming shop should be using some sort of unique identifier for their web page elements, whether it be id, name or class. But, at least we know how to use the other if we have no other option.

1 comment: