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.
Tuesday, October 20, 2009
Monday, October 19, 2009
finding what you need
So I thought next I would go over the various ways to find the elements that we are looking for on a web page, or windows process. We have already seen how to locate an element by it's path, and if you remember there are several drawbacks to that approach. The most obvious is that if the path changes, our tests will break. One solution is to use the object-mapping feature of TestComplete, which essentially stores all the elements' paths separately from our scripts. However, you have to update them every time your paths change, and this tends to be cumbersome and time consuming. The obvious solution to this problem is to not locate elements by their path, but rather find them based upon some criteria. There are two main ways to find elements: by their html elements, or by their TestComplete elements.
To illustrate, go to http://www.google.com/ again, and use the highlighter tool to select the google search box, and view it in the object browser. You will see something like this:

So the list of elements in the "properties" tab displays the properties of the given element we have selected. Now these are technically the properties of the "Test Object" class. Everything that TC knows about the node is displayed here. Take a second to look through the list of properties. Note the fields "name" and "id". The id is a unique number that TC uses to identify every Test object. The Name field (Textbox("q")) is a generated string that TC creates based upon the html fields. These are NOT the same as the id and name in the html document. If you scroll down to the "outerHTML" field, you will see the actual HTML associated with the element:
INPUT class=lst title="Google Search" maxLength=2048 size=55 name=q autocomplete="off" init="true"
Note that it also has a "Name" equal to "q". Compare this to the Name field, which is Textbox("q"). Obviously in this case the name field is generated based upon the type of element, combined with its html name. It is important to understand the distinction between the HTML elements and the TestObject elements: they are not interchangeable, however in many cases either can be used.
Ok, now that we understand that important distinction, we can look at the first of our Find functions, specific to HTML:
Copy and paste the following into TC:
function searchWithGoogle()
{
var searchBox = Page().NativeWebObject.Find("name","q","INPUT");
var searchButton = Page().NativeWebObject.Find("type","submit","INPUT");
searchBox.value="TestComplete Find Rocks!";
searchButton.Click();
}
The NativeWebObject class is what gives us access to the actual HTML elements, and not the Test Object properties. The NativeWebObject.Find() routine is defined like this:
Page().NativeWebObject.Find(propertyName, propertyValue, TagName)
Looking in the outerHTML field showed me what to search for. So, for the first Find call, I searched for the "INPUT" tag with property "name" with value "q". For the second, i searched for the INPUT tag with type=submit. Right-click on the function and Run it!. It will find the google search box and modify the value and then click the search button. The cool part? Remember in my first post, how the search results page had a different layout and structure than the search page? Our tests wouldn't run from the results page because the paths were slightly different. Well, if you run the routine again, you will see that it works perfectly the second and third times as well.
I want to point out that you can use this find functionality for anything contained in the "outerHTML" field for any element on a web page. You can search for a link with specific text, or look for a picture with a certain size, or even find a input field of a specific class. However, the big catch is that it returns the first element to match the criteria, so be aware that there could be multiple results that match your criteria.
At this point, it would be simple to modify our function to have a "search string" paramater, and we would then have a functionized google search routine, that can search for anything we want. We could even add more code to verify that we are on the correct page, and that we have search results. At this point we are only a couple steps away from creating a complete automated validation of a search page.
Since this post has gotten a little long winded, my next one will focus on finding an element based upon its TestObject properties, and not HTML elements. There will be definite times when the object you are trying to access doesn't have enough unique identifiers to find it consistently by html alone.
To illustrate, go to http://www.google.com/ again, and use the highlighter tool to select the google search box, and view it in the object browser. You will see something like this:

So the list of elements in the "properties" tab displays the properties of the given element we have selected. Now these are technically the properties of the "Test Object" class. Everything that TC knows about the node is displayed here. Take a second to look through the list of properties. Note the fields "name" and "id". The id is a unique number that TC uses to identify every Test object. The Name field (Textbox("q")) is a generated string that TC creates based upon the html fields. These are NOT the same as the id and name in the html document. If you scroll down to the "outerHTML" field, you will see the actual HTML associated with the element:
INPUT class=lst title="Google Search" maxLength=2048 size=55 name=q autocomplete="off" init="true"
Note that it also has a "Name" equal to "q". Compare this to the Name field, which is Textbox("q"). Obviously in this case the name field is generated based upon the type of element, combined with its html name. It is important to understand the distinction between the HTML elements and the TestObject elements: they are not interchangeable, however in many cases either can be used.
Ok, now that we understand that important distinction, we can look at the first of our Find functions, specific to HTML:
Copy and paste the following into TC:
function searchWithGoogle()
{
var searchBox = Page().NativeWebObject.Find("name","q","INPUT");
var searchButton = Page().NativeWebObject.Find("type","submit","INPUT");
searchBox.value="TestComplete Find Rocks!";
searchButton.Click();
}
The NativeWebObject class is what gives us access to the actual HTML elements, and not the Test Object properties. The NativeWebObject.Find() routine is defined like this:
Page().NativeWebObject.Find(propertyName, propertyValue, TagName)
Looking in the outerHTML field showed me what to search for. So, for the first Find call, I searched for the "INPUT" tag with property "name" with value "q". For the second, i searched for the INPUT tag with type=submit. Right-click on the function and Run it!. It will find the google search box and modify the value and then click the search button. The cool part? Remember in my first post, how the search results page had a different layout and structure than the search page? Our tests wouldn't run from the results page because the paths were slightly different. Well, if you run the routine again, you will see that it works perfectly the second and third times as well.
I want to point out that you can use this find functionality for anything contained in the "outerHTML" field for any element on a web page. You can search for a link with specific text, or look for a picture with a certain size, or even find a input field of a specific class. However, the big catch is that it returns the first element to match the criteria, so be aware that there could be multiple results that match your criteria.
At this point, it would be simple to modify our function to have a "search string" paramater, and we would then have a functionized google search routine, that can search for anything we want. We could even add more code to verify that we are on the correct page, and that we have search results. At this point we are only a couple steps away from creating a complete automated validation of a search page.
Since this post has gotten a little long winded, my next one will focus on finding an element based upon its TestObject properties, and not HTML elements. There will be definite times when the object you are trying to access doesn't have enough unique identifiers to find it consistently by html alone.
Subscribe to:
Posts (Atom)