# 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](#naming-objects) - [Improving performance with the "type" property](#improving-performance-with-the-type-property) - [Example usage](#example-usage) - [Enhancing object identification with object hierarchy](#enhancing-object-identification-with-object-hierarchy) - [Example usage](#example-usage-1) - [Group operation on objects](#group-operation-on-objects) - [Example usage](#example-usage-2) - [Non-recursive search with the "parent" property](#non-recursive-search-with-the-parent-property) - [Conclusion](#conclusion) ## 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: ```python 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: ```python 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: ```QML Rectangle { color: "green" property string text: "Hello" Text { anchors.fill: parent text: parent.text } } ``` You can access the label itself with the following definition: ```python label = { "text": "Hello", "type": "GreenLabel" # "Rectangle" would work too since it's the base class } ``` Similarly, you can access the underlying _Text_ control with: ```python 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"](./images/identify_objects/dual_window.png) 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: ```python 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: ```python 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"](./images/identify_objects/preferences.png) The corresponding QML code is as follows: ```QML 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: ```python 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: ```python 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: ```python 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: ```bash 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.