Using IBM Rational Functional Tester: Understanding and Using the TestObject.find Method

The TestObject.find method is a powerful part of Functional Tester that makes automated scripts easier to maintain. This introductorty article shows you easy ways to include TestObject.find in your automation framework.

Mark Nowacki, Advisory Software Engineer, IBM Software Group, Lotus Software

Mark Nowacki has worked with test automation tools for 15 years. He is the overall IBM Workplace Managed Client (WMC) GUI Automation Coordinator, as well as automation team lead for the WMC Messaging, Instant Contacts, and Activity Explorer test team.



Lisa Nodwell (lnodwell@us.ibm.com), Advisory Software Engineer, IBM Software Group, Lotus Software

Lisa Nodwell has been working with software automation tools for 16 years. She has extensive experience with large test automation projects using Segue SilkTest and, currently, with IBM Rational Functional Tester.



11 July 2006

Also available in Chinese

Part 1: Basics of the TestObject.find method

The TestObject.find method is a Java™ method that the IBM® Rational® Functional Tester (RFT) tool uses to locate a TestObject in the application under test (AUT) dynamically, at runtime. By using it, you avoid having to record actions to add the TestObject to the Object Map.

Mapped objects use stored, static, recognition properties and object hierarchies to verify that the script uses the correct control during playback. Although object recognition is quick with recorded objects, updating the properties can be time-consuming, especially when you need to change the property weight values or text properties of an object to regular expression (Regex) values. The find method gives you the option of eliminating most recorded controls from the Object Map.

The TestObject.find method is more robust in the 6.X release of RFT. Performance is now nearly equal to that of using a mapped object.


Benefits of using the find method

One of the best reasons to use find instead of the Object Map is that you can easily change recognition properties of controls stored in Java or properties files. The advantage is that you do not have to change the recognition properties stored in an Object Map, so you avoid the more tedious and time-consuming way of using the Object Map UI to make the changes or rerecording the object to update it.


Understanding and using find

To understand the find method and its relationship to a mapped object, think about a control in your application that is likely to change, thus will require you to update an Object Map. The control may be something simple, such as an application that has a user-command button with a label that changes from release to release. For example, perhaps your AUT includes a dialog that involves a button labeled Open. In the previous release, that button was labeled OK. For the next release, the button will still be labeled Open, but there will also be a new button labeled Open with….

If you recorded the OK button to the Object Map, you will need to change the recognition properties to accommodate the label changes, plus you will need to record the new Open with… button. (For this example, we are ignoring possible changes to the object hierarchy in the application that result from UI changes.)

Next, assume that this dialog also mentions other buttons, such as Cancel and Help. You can see that the buttons can be differentiated by their labels, even if they appear in the application dialog in a different order in the future. Therefore, as long as you can differentiate between the buttons by the words on the labels, you do not have to be concerned about the index property or any other properties.


Exploring find with ClassicsJavaA

You can also use RFT to capture information about the controls that you may need to find later. For this illustration, use the ClassicsJavaA sample application provided with RFT and follow these steps.

  1. Create a temporary Empty Functional Test script to hold your Object Map while you work on defining the properties of the buttons.
  2. From the menu in the empty test scrip, select Script > Open Test Object Map.
  3. Then select Applications > ClassicsJavaA to start the application.
  4. Click the Place Order button.
  5. Click the OK button in the Member Logon dialog.

The Place an Order dialog should now be displayed. You can use this dialog to see how to use find to locate the buttons in the dialog and to get a TestObject for each control.

Figure 1. The Place an Order dialog from the ClassicsJavaA application
Place an Order dialog from the ClassicsJavaA application

You’ll see three buttons in this next dialog: Related Items, Place Order, and Cancel. Look at the buttons as they appear in an Object Map, then proceed with these steps:

  1. In the Private Test Object Map, select Test Object > Insert Object(s) from the menu.
  2. Drag the Object Finder control over the Place an Order dialog, moving it to the title bar so that the entire dialog is surrounded by a red outline, then release the mouse button.
  3. Now, from the Insert a GUI Object into the Object Map dialog, select Include all available objects on this window and click Finish.
  4. Click the Place Order button.

You should now have a javax.swing.JFrame object in your Object Map. Select the JFrame control so that you can look at the Recognition properties for the dialog. For this control, the label, or text, is the most significant of two attributes that define the control. The second important attribute is the class of the control, because there may be many JFrame objects displayed, but probably only one labeled Place an Order. Continue with these steps:

  1. Expand the hierarchy to find the button labels.
  2. Select the Cancel button so that you can examine the Recognition properties that RFT uses to describe the button in the Object Map. There are four properties listed, two of which are crucial to defining the correct button: the class and the accessibleContext.accessibleName properties (see Figure 2.)
Figure 2. Example of an Object Map using the ClassicsJavaA application
Example of an Object Map using the ClassicsJavaA application
  1. Find the correct button. Many dialogs include a Cancel button, so you first need to find the dialog TestObject that contains the correct Cancel button. If you find the correct dialog first, it is easier to find the correct button within that dialog.
  2. Go back to your empty test script. Because the dialog is a high-level object, you can start your TestObject search by defining the RootTestObject. After you have done that, you can use the find method to locate the dialog TestObject:

RootTestObject root = getRootTestObject();

You are ready to use the find method to locate the Place an Order dialog, using the class information for the window.

Note: Be sure to print the properties of all TestObjects you find, so you can review them.

Your commands should look something like this:

	// get all test objects of type JFrame (dialog)
    TestObject[] to = root.
         find(atDescendant("class", "javax.swing.JFrame"));

    // print dialog test object properties to console
    for (int i = 0; i < to.length; i++)
       {
 System.out.println (to[i].getProperties());
 }

The resulting screen output should look something like this (minus most of the properties, to save space):

	{height=600, displayable=true, undecorated=false, ..., 
	class=javax.swing.JFrame, title=Place an Order, …}

Next, instead of the above code, use the find method to locate the Place an Order dialog by using the label text for that window in the dialog:

	// get all test objects that have the title "Place an Order"
    TestObject[] to = root.
       find(atDescendant("title", "Place an Order"));
    
    // print test object properties to console
    for (int i = 0; i < to.length; i++)
 {
       System.out.println(to[i].getProperties());
       }

In both cases, you will find only one object and the properties displayed will be the same. That is the ideal situation. To increase the likelihood of that result in every situation, you can combine the two properties into one find call:

	// get all test objects of type JFrame (dialog) 
    // AND have the caption "Place an Order"
    TestObject[] to = root.
       find(atDescendant(("class", "javax.swing.JFrame", 
                          "title", "Place an Order"));

Now that you have found the control that contains the button with the label that you were searching for, you can use find to locate the correct button:

	// capture the correct dialog as a test object
    TestObject toPlaceAnOrderDialog = to[0];
    
    // reuse the test object array, finding all buttons on the
    //   "Place an Order" dialog that have the caption "Cancel"
    to = toPlaceAnOrderDialog.
          find(atDescendant("class", "javax.swing.JButton", 
                            "text", "Cancel"));
    
    // verify that only one button was found
    System.out.println(to.length);
    
    // capture the correct button as a GuiTestObject
    GuiTestObject cancelButton = new GuiTestObject(to[0]);

You can call find from any TestObject. Depending on the object you select, the search is restricted to objects in the hierarchy below the one you select.

In the example above, the code uses atDescendant to locate the button. However, there are several methods that you can use with find:

  • atChild searches for any direct child of the TestObject.
  • atDescendant looks for any child of the TestObject.
  • atList lets you specify a list of atChild, atDescendant, and atProperty objects, so you can narrow the search.

Part 2: Examples of practical applications of the TestObject.find Method

Experimenting with various properties will help you determine which properties will work best for finding objects in your application. As soon as you understand how find helps you define your controls dynamically, you can begin to write getter methods, so you can locate objects without relying on a recorded Object Map.

Defining Controls As Part of the Dialog

For the Place an Order dialog, for instance, you might decide to define the controls as part of the dialog. In that case, you would first find the dialog, then look inside the dialog for the control you want to modify. For that scenario, your code design might look like this:

      public class PlaceAnOrder
       {
       public static GuiTestObject getDialog()
         {
         RootTestObject root = getRootTestObject();
   TestObject[] to = root.
      find(atDescendant("title", "Place an Order"));
         return new GuiTestObject (to[0]);
         }

       public static GuiTestObject getButtonCancel()
          {
          TestObject[] to = getDialog()
              .find(atDescendant("class", "javax.swing.JButton",
                                 "text", "Cancel"));
          return new GuiTestObject(to[0]);
          }
       }

Here is an example of how you might use these methods in a script:

	public void testMain(Object[] args) 
       {
       // Find the Place an Order dialog
       GuiTestObject dialogPlaceAnOrder = PlaceAnOrder.getDialog();
       GuiTestObject cancelOrder = PlaceAnOrder.getButtonCancel();
       cancelOrder.click ();
       }

Defining Methods for Finding Common Objects

To find more common button labels, such as Cancel, you may need to develop more general methods that find any button that exists in your application. For example, you might use getButton("Cancel", placeAnOrderDialog), where parent is the parent window. For this second scenario, your design would look something like this example:

	public class ClassicsJavaUI 
       {
       public static GuiTestObject getButton(String buttonName, 
          TestObject parent) 
          {
          TestObject[] to = parent.find(SubitemFactory.atDescendant 
             ("class", "javax.swing.JButton", "text", buttonName));
          return(new GuiTestObject(to[0]));
          }

       public static GuiTestObject getButton(String buttonName)
          {
          RootTestObject root = RationalTestScript.getRootTestObject();
          TestObject[] to = root.find(SubitemFactory.atDescendant
             ("class", "avax.swing.JButton", "text", buttonName));
          return (new GuiTestObject(to[0]));
          }
    
       public static GuiTestObject getDialog(String dialogName)
          {
          RootTestObject root = RationalTestScript.getRootTestObject();
          TestObject[] to = root.find(SubitemFactory.atDescendant
             ("class", "javax.swing.JFrame", "title", dialogName));
          System.out.println (to.length);
          return (new GuiTestObject (to[0]));
          }
       }

    public class PlaceOrderWindow
       {
       public static GuiTestObject getDialog()
          {
          return ClassicsJavaUI.getDialog("Place an Order");
          }
       public static GuiTestObject getButtonPlaceOrder()
          {
          return ClassicsJavaUI.getButton("Place an Order", getDialog());
          }
       }

Here is an example of how you might use these methods in a script or, more appropriately, with a class that defines the Place An Order dialog:

	public void testMain(Object[] args) 
       {
       GuiTestObject dialog = PlaceOrderWindow.getDialog();
       GuiTestObject buttonPlaceOrder = 
                    PlaceOrderWindow.getButtonPlaceOrder();
       buttonPlaceOrder.click ();
       }

The application you are testing will determine how you define your methods for finding the controls. The following section is an actual example of how a system was designed around the find method.


Actual Use of the RFT find in an Eclipse-based Application

This example of using the find method in testing the application based on Eclipse was limited to only a part of the AUT as a way to work out the design of the object getter methods. There is a discernable pattern with objects in this application. Standard SWT objects were predominant, but there are some special, custom classes provided by the application development team.

Test developers created generic methods to get all objects of a particular class or to get just one object at a given index under a specific parent object. To allow the automation to work on localized versions of the application, the test developers decided to use object indices as the key property for finding objects, rather than text. The classes described below are designed to do this.

Note: All methods in these classes are static, so the test developer does not need to instantiate an object of this type to use them. They can be called by using the format <class name>. <method name> (<parameters>).

The FindAppObjects.java class enables you to use two different methods: getAllObjects and getObjectAtIndex . These methods are not usually meant to be used directly, but they are the basis for other methods. However, you can use them directly to determine which objects are found in which indices.

      /**
     * Given a parent object, find all descendant test objects with a
     * given class.
     * @param parent TestObject
     * @param className String
     * @return Array of TestObject or null if no objects of type 
     *  className were found.
     */
    public static TestObject[] getAllObjects(TestObject parent, 
                                             String className)

getAllObjects takes a parent TestObject and a class-type string as input, and then returns an array of TestObjects that include all descendents of the parent object that are of the specified class type. This example returns all child dialogs of the RootTestObject:

RootTestObject root = getRootTestObject ();
    TestObject[] dialogs = 
       FindAppObjects.getAllObjects(root, "javax.swing.JFrame");

Selecting the correct parent TestObject is important, so that you get only the objects that you are looking for among the results. As an example, suppose that the following represents an object hierarchy in the AUT:

application
composite
group0
button0
button1
group1
button0
button1

Calling getAllObjects (application, "button") will return an array of four buttons. However, you cannot quickly determine which indices match which buttons without looping through the array and printing each index and the button[index].getProperty("text") string.

Instead of that cumbersome process, it makes more sense to do more than one hierarchical call:

// get all groups in the application
TestObject[] groups = FindAppObjects.getAllObjects
      (application, "group");

// limit search to only buttons in the first group found
TestObject[] buttons = FindAppObjects.getAllObjects
      (groups[0], "button"); 

// click the first button found under the group
new Button(to[0]).click();

The getAllObjects (group0, "button") call returns only two buttons, and they are likely to be in the correct order (button0, then button1), as displayed on the screen. If there are only a few buttons in the entire application or dialog, it may be easier just to get all buttons and figure out the indices.

/**
     * Given a parent object, find the descendent test object
     * with a given class, at a given one-based index.
     * 
     * @param parent TestObject
     * @param className String
     * @param index int, zero-based index
     * @return TestObject or null if index is greater than 
     * the total number of objects found.
     */
    public static TestObject getObjectAtIndex 
          (TestObject parent, String className, int index)

getObjectAtIndex refines the use of getAllObjects. It takes a parent TestObject, a class-type string, and a zero-based index integer, and then returns a single TestObject. This code, for example, returns the first button found below the given (parent) group:

              TestObject to = getObjectAtIndex(group1, BUTTON, 0);

These methods return TestObjects, and those may not be the specific type of object you need to find. The above methods both return generic TestObjects. Many times, you will want to use a specific type of object instead. When using these methods, be sure to explicitly cast the returned TestObject(s) to the correct type of object before using in your code.

The FindBasicAppObjects.java class provides further refinement and assistance. It is a subclass of FindAppObjects.java, and uses the getObjectAtIndex method. It contains getter methods that return the child object of type class at index from TestObject parent. This was already cast as the correct class of product object, so you do not need to cast it yourself. This example returns a subset of the standard Eclipse SWT widget types, such as org.eclipse.swt.widgets.Button:

/**
     * Find the index-specified org.eclipse.swt.widgets.Button 
     * child control under the specified parent.
     * 
     * @param parent TestObject
     * @param index int, zero-based
     * @return WButton
     */
    public static WButton getButton(TestObject parent, int index)

getDialog is the only method in FindBasicAppObjects.java that does not take a parent, because it assumes that the parent is the AUT. To be able to recognize the correct dialog even if the dialog has a mutable caption, getDialog takes a regular expression (Regex) object as a parameter, as this shows:

        /**
     * Find a org.eclipse.swt.widgets.Shell control
     * with a caption specified by the given 
     * Regular Expression (Regex).
     * 
     * @param dialogCaption Regex object containing dialog's 
     *        expected caption
     * @return WFrame
     */
    public static WFrame getDialog (Regex dialogCaption)

Compare the code for the Place an Order dialog (at the beginning of this section) with the same behavior using FindAppObjects and FindBasicAppObjects in the following example. It assumes that the Place an Order dialog is an Eclipse dialog of type WFrame and that the buttons are of type WButton.

    public class PlaceAnOrder extends RationalTestScript
       {
       private final int CANCEL = 2;
       private final Regex DIALOG_CAPTION = new Regex
            ("^Place an Order$");

       public WFrame getDialog()
          {
          return FindBasicAppObjects.getDialog(DIALOG_CAPTION);
          }

       public WButton getButtonCancel()
          {
          return FindBasicAppObjects.getButton(getDialog(),CANCEL);
          }

    public void testMain(Object[] args)
       {
       getButtonCancel().click();
       }
    }

The preceding code is written in a way that’s easy to maintain, with each part of the code segregated into understandable chunks. For coders who prefer a more compact layout, that code could be rewritten this way:

public class PlaceAnOrder extends RationalTestScript
       {
       public void testMain(Object[] args)
          {
          FindBasicAppObjects.
             getButton (FindBasicAppObjects.
             getDialog (DIALOG_CAPTION), CANCEL).click();
          }
       }

Always Write Unit Tests

In addition to the find-based getter methods, the FindBasicAppObjects.java class mentioned previously includes rudimentary unit test methods for each of the defined object types. Each of these unit test methods takes an object of a specific class and a string (where the string is expected to be the name of the getter method that you are testing). This name is sent to the console to track pass-or-fail results.

The unit test methods first verify that the object provided is not null, and then perform one or more very basic actions on the object. For example, the unit test method for a WButton object prints the text property (label) for the object. You can then see whether the getter method is returning the correct button.

Using unit test methods to test every getter method is recommended. Unit testing is often regarded as extra work, but after any change to the application’s user interface (UI), running the unit tests will verify that the methods continue to find the correct controls in the AUT and will confirm that any code changes in the getter methods are working correctly. Running unit tests assures that test case code will continue to find and return the correct objects.

      /**
    * Unit test for button controls.
    * 
    * @param b
    * @param methodName Name of method under test
    */
    public static void verifyButton(WButton b, String methodName)

Example of calling a unit test method:

      FindBasicAppObjects.
       verifyButton(getButtonCancel(),"getButtonCancel");

Limitations and Recommendations

Always find the control before you use it; never depend on TestObjects saved in your code. The object you find may change (or be deleted and recreated) between the first time you find the object and the next time you need to use it. Be sure you have the correct object by calling find each time you work with the object.

You cannot record scripts if you are using find. Because your TestObjects are not defined in the Object Map, the recorder will not pick up the correct object name while recording. You can still use recording for basic structural code that you need for your scripts, but you will need to substitute your find-based getter methods for the mapped-control getters that are recorded.

Many objects may appear to be the same. There may be more than one OK button in existence when you call the find method. Make sure that you have the correct button by using a distinguishing property, such as the dialog that the OK button is in. Print the result of the getProperties method on the TestObject to the console to see which properties can help you differentiate the object you seek from similar objects.

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Rational software on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Rational
ArticleID=144043
ArticleTitle=Using IBM Rational Functional Tester: Understanding and Using the TestObject.find Method
publish-date=07112006