How to identify objects

In GUI testing, accurately identifying objects is essential for automation scripts to interact effectively with the user interface. Qat uses Python dictionaries containing property/value pairs to represent Qt objects within a tested application. Unlike relying on mouse positions or pixel coordinates, which can be unreliable due to varying screen resolutions and dynamic UI elements, using dictionaries allows for more robust and flexible object identification.

By storing object properties such as “objectName”, “id”, “text”, etc., in dictionaries, testers can create a reliable and consistent method for identifying Qt objects. This approach not only enhances the maintainability of automation scripts but also provides a scalable solution for handling dynamic UI elements and changes in application layout.

Such dictionaries are called “object definitions” and are required by a lot of API functions such as wait_for_object(), mouse_click(), type_in(), etc.

This tutorial explains how to create object definitions to keep test execution fast, reliable and flexible.

Table of contents

Naming objects

In GUI testing, the most efficient way to identify objects is by providing them with unique identifiers, typically through the objectName property. This property is available for all Qt objects, which encompass all classes that inherit from QObject.

When such identifier is available, the corresponding object definition is straightforward:

object_definition = {
   "objectName": "someUniqueName"
}
widget = qat.wait_for_object(object_definition)

Such trivial cases can even be simplified by using the objectName itself as a definition:

widget = qat.wait_for_object("someUniqueName")

Assigning a unique name to each object in an application can greatly enhance the efficiency and reliability of object identification. However, this process may be tedious, error-prone, and sometimes challenging, especially in large and complex applications.

The following sections will delve into strategies to address scenarios where the objectName property cannot be utilized or is insufficient for reliable object identification.

Improving performance with the “type” property

To narrow down the number of potential matches while enhancing performance, one effective strategy is to include the type of the object in its definition. When Qat searches for objects during test execution, it quickly discards objects that do not match the specified type, without comparing any other property. This optimization results in noticeably faster execution times.

The type property is a custom property managed by Qat for each object and is not part of the Qt framework. It serves a similar purpose to the className property but also considers the class inheritance hierarchy.

For instance, suppose you define a custom button type CustomButton that inherits from QPushButton. In that case, you can still use “QPushButton” for the type property of your custom button definition. This approach allows tests to remain decoupled from the application implementation by using generic base classes instead of concrete types while still enhancing overall performance.

Moreover, the type property can be helpful in distinguishing objects that share common properties.

Example usage

Consider a custom QML control defined in GreenLabel.qml that displays text on a green background:

Rectangle {
   color: "green"
   property string text: "Hello"

   Text {
      anchors.fill: parent
      text: parent.text
   }
}

You can access the label itself with the following definition:

label = {
   "text": "Hello",
   "type": "GreenLabel" # "Rectangle" would work too since it's the base class
}

Similarly, you can access the underlying Text control with:

text = {
   "text": "Hello",
   "type": "Text"
}

By leveraging the type property, testers can improve the efficiency and flexibility of object identification, leading to more reliable and scalable test suites.

Enhancing object identification with object hierarchy

Utilizing the object hierarchy is a powerful strategy to ensure robust and accurate object identification in GUI testing.

Object definitions support hierarchical structures through the container custom property. The value assigned to container can be another object definition or an Object instance returned by one of the wait_for_object*() API functions.

When the container property is present in a definition, Qat follows a hierarchical search approach. It first locates the specified container object and then narrows down the search to its children.

This process is recursive, meaning that the container definition can itself contain a container property, enabling the definition of a complete tree of object definitions that mirror the widget organization of the tested application.

By employing this strategy, Qat can accurately identify a component even if it is reused in multiple places within the application GUI.

Example usage

Consider an application with two windows, each containing the same menu bar. The menu is also displayed as a context menu on right-click:

"Two-window app"

Now, suppose you want to close the second window by clicking on the Exit menu of its main menu.

You can accomplish this with the following definitions to first open the appropriate menu:

second_window = {
   'type': 'QMainWindow',
   'windowTitle': 'Second window'
}

second_window_menu = {
   'container': second_window,
   'type': 'QMenuBar'
}

file_menu_entry = {
   'container': second_window_menu,
   'text': 'File'
}
qat.mouse_click(file_menu_entry)

Then, proceed to click on the Exit menu:

file_menu = {
   'container': second_window_menu,
   'title': 'File',
   'type': 'QMenu'
}

exit_menu = {
   'container': file_menu,
   'text': 'Exit'
}
qat.mouse_click(exit_menu)

In this example, we were able to accurately identify the desired menu, even in the absence of an objectName property.

Group operation on objects

In certain scenarios, applying the same operation or performing the same verification on multiple objects at once can be advantageous. Instead of defining and searching for each object individually, an object definition can act as a “filter” to retrieve all objects sharing common properties when used with the qat.find_all_objects() function.

Example usage

Consider a hierarchy of preferences as depicted below:

"Preferences tree"

The corresponding QML code is as follows:

ColumnLayout {
   objectName: "preferences"

   // General preferences
   Text {
      text: "Preferences"
   }
   CheckBox {
      text: "Dark theme"
   }
   CheckBox {
      text: "Auto save"
   }

   // Editor preferences
   ColumnLayout {
      Text {
         text: "Editor"
      }
      CheckBox {
         text: "Auto complete"
      }
      CheckBox {
         text: "Spell check"
      }
      CheckBox {
         text: "Auto scroll"
      }
   }
}

Using the qat.find_all_objects() function allows iteration over all checkboxes to verify that none of them are checked:

preference_checkbox = {
   'container': {'objectName': 'preferences'},
   'type': 'CheckBox'
}

all_checkboxes = qat.find_all_objects(preference_checkbox)
for checkbox in all_checkboxes:
   assert not checkbox.checked

If all objects are children of the same parent, the children property can also be used to retrieve only direct children of a specific object. Unlike the previous example, the children property returns all objects without filtering them:

general_preferences = qat.wait_for_object{'objectName': 'preferences'}

for child in general_preferences.children:
   if child.type == 'CheckBox':
      assert child.checked

In this example, only the two top-level checkboxes will be verified.

Non-recursive search with the “parent” property

In some cases, using the container property may not provide the desired result due to the recursive nature of the search process, leading to ambiguous definitions.

In the previous example, suppose we want to use the qat.find_all_objects() function to retrieve only the top-level children. This can be achieved by incorporating the parent property into the filter:

general_preference_checkbox = {
   'parent': {'objectName': 'preferences'},
   'type': 'CheckBox'
}

general_checkboxes = qat.find_all_objects(general_preference_checkbox)
for checkbox in general_checkboxes:
   print(checkbox.text)
   assert not checkbox.checked

In this example, only the checkboxes under the “Preferences” section will be retrieved, and the output of this test will be:

Auto save
Dark theme

Conclusion

In this tutorial, we explored efficient object identification strategies with Qat. We learned about various techniques, including leveraging unique identifiers, utilizing object hierarchy, accessing groups of objects, and performing non-recursive searches.

By employing these strategies, testers can enhance the reliability and scalability of their automation scripts. Efficient object identification is crucial for creating robust automation suites that accurately interact with the user interface of applications.

With a solid understanding of these techniques, testers can effectively address challenges in object identification and build comprehensive automation suites with Qat.