# Native widgets Qat supports interaction with native elements like widgets, windows, and dialogs created using platform-native APIs such as the Windows API and Cocoa. A common example in Qt applications is native file dialogs for opening or saving files. Qat provides wrappers for these native elements, offering a widget-like interface compatible with API functions such as `wait_for_object()`, `mouse_click()`, and `type_in()`. **Limitations**: - Certain widgets may be inaccessible. - Identifying widgets can be challenging due to the absence of properties like `objectName`. - Events triggered on native elements do not execute synchronously. ## Table of contents - [Windows](#windows) - [Selecting a file](#selecting-a-file) - [Advanced scenario](#advanced-scenario) - [MacOS](#macos) - [Native keyboard events](#native-keyboard-events) ## Windows This section demonstrates how to interact with a native "Open File" dialog in a test script. ### Selecting a file Below is an example of a native "Open File" dialog: ![Open File dialog](images/native_widgets/win_open_dlg.png) Using Qat’s Spy and picker tools, we examine the dialog’s structure: ![Dialog structure](images/native_widgets/win_dlg_tree.png) Since native widgets lack `objectName` or `id` properties, we rely on their type (Dialog) and text (Open) to define them: ```python open_file_dlg_definition = { 'type': 'Dialog', 'text': 'Open' } open_file_dlg = qat.wait_for_object(open_file_dlg_definition) ``` Our objective is to select a file and close the dialog. Unfortunately, the files and folders displayed in the dialog are not standard widgets, making them inaccessible to Qat. Instead, we rely on the text field at the bottom of the dialog to manually input the file path. Using Qat's Spy tool, we observe that the text field is embedded within a *ComboBox*: ![Text field](images/native_widgets/win_dlg_text_tree.png) This structure allows us to define and interact with the ComboBox and text field: ```python # Define the parent Combobox combobox_definition = { 'container': open_file_dlg, 'text': '', 'type': 'ComboBox' } # Define the text field text_field_definition = { 'container': combobox_definition, 'type': 'Edit' } # Access the text field and input a file name text_field = qat.wait_for_object(text_field_definition) qat.type_in(text_field, "Image1.bmp") ``` The following dialog state results after entering the file name: ![Open File dialog with path](images/native_widgets/win_open_dlg_selected.png) Next, we define and click the "Open" button to confirm the selection and close the dialog: ```python # Define the Open button open_btn = { 'container': open_file_dlg, 'text': 'Open', 'type': 'Button' } qat.mouse_click(open_btn) # Verify the dialog is closed qat.wait_for_object_missing(open_file_dlg) ``` This method ensures reliable interaction with the dialog despite its limitations. ### Advanced scenario In this scenario, we address handling an error dialog resulting from an invalid file selection. For instance, if we enter a nonexistent file name: ```python # Enter an invalid file name qat.type_in(text_field, "Image99.bmp") # Define the Open button open_btn = { 'container': open_file_dlg, 'text': 'Open', 'type': 'Button' } # Click the Open button qat.mouse_click(open_btn) ``` An error dialog appears: ![Error dialog](images/native_widgets/win_open_dlg_error.png) Using the Spy tool, we observe that the error dialog shares the same `type` and `text` as the original dialog: ![Error dialog definition](images/native_widgets/win_dlg_tree_error.png) This ambiguity causes the following code to fail: ```python error_dlg_definition = { 'type': 'Dialog', 'text': 'Open' } # This call fails due to multiple matches error_dlg = qat.wait_for_object(error_dlg_definition) ``` To resolve this, we use the `handle` property — a unique but volatile identifier generated by Qat — to distinguish between dialogs: ```python # Get the handle of the original dialog # This must be done before opening the second dialog dialog_handle = open_file_dlg.handle # Enter an invalid file name qat.type_in(text_field, "Image99.bmp") # Define the Open button open_btn = { 'container': open_file_dlg, 'text': 'Open', 'type': 'Button' } # Click the Open button qat.mouse_click(open_btn) # Find all matching dialogs assert qat.wait_for(lambda: len(qat.find_all_objects(open_file_dlg_definition)) > 1) dialogs = qat.find_all_objects(open_file_dlg_definition) # Identify the error dialog error_dialog = None for dlg in dialogs: if dlg.handle != dialog_handle: error_dialog = dlg break assert error_dialog is not None ``` Once identified, we interact with the error dialog’s "Ok" button to dismiss it: ```python # Define and click the Ok button ok_button = { 'container': error_dialog, 'type': 'Button', 'text': 'Ok' } qat.mouse_click(ok_button) # Verify the error dialog is closed qat.wait_for_object_missing(error_dialog) ``` This approach ensures accurate identification and interaction with multiple dialogs. ## MacOS MacOS native widgets, based on the Cocoa framework, are fully supported by Qat. However, certain native dialogs (e.g., file panels) use internal widgets that are inaccessible to Qat. A workaround for these cases is discussed in the next section. Standard Cocoa widgets, or "views," are accessible. Consider the following example of a Cocoa window: ![Cocoa Window](images/native_widgets/cocoa_window.png) Using the Spy tool, we observe that this window is of type `QNSWindow` and that its text is "Cocoa window": ![Cocoa Window Spy](images/native_widgets/cocoa_window_tree.png) Using Qat’s API, you can access this window and its components in a test script: ```python # Define the Cocoa window window_definition = { 'text': 'Cocoa window', 'type': 'QNSWindow' } # Define the text field textfield_definition = { 'container': window_definition, 'type': 'NSTextField' } # Access the text field textfield = qat.wait_for_object(textfield_definition) ``` To enter text in the field and confirm the input, you can use `qat.type_in()`. Since native events like keyboard inputs are asynchronous, Qat’s `wait_for_property_value()` ensures the input has been applied successfully: ```python qat.type_in(textfield, "Hello") qat.type_in(textfield, "") qat.wait_for_property_value(textfield, 'text', "Hello", check=True) ``` Result: ![Cocoa Text](images/native_widgets/cocoa_text.png) Similarly, you can interact with buttons. The following script clicks a button and verifies its updated state: ```python # Define the button button_definition = { 'container': window_definition, 'type': 'CounterButton' } # Access and click the button button = qat.wait_for_object(button_definition) assert button.text == '0' qat.mouse_click(button, check=True) # Confirm the button text has updated assert qat.wait_for_property_value(button, 'text', '1') ``` Result: ![Cocoa Button](images/native_widgets/cocoa_button.png) ## Native keyboard events For some native elements, such as MacOS dialogs ("panels"), Qat cannot directly access child widgets. In these cases, native keyboard events provide an alternative for interaction. For example, consider the "Open File" dialog: ![Macos Open File dialog](images/native_widgets/macos_open_file_dlg.png) You can access the dialog as follows: ```python open_file_dialog = { 'type': 'NSOpenPanel', 'text': 'Open' } dialog = qat.wait_for_object(open_file_dialog) ``` To open the "path editor," which is inaccessible as a widget, send a native keyboard shortcut: ```python qat.shortcut(qat.Platform, 'Ctrl+Shift+g') ``` Here, `qat.Platform` represents the current native platform and enables sending global shortcuts. Since the path editor is inaccessible, rely on delays and keyboard inputs to interact with it: ```python # Wait briefly for the path editor to appear time.sleep(0.5) # Enter a file path and confirm qat.type_in(qat.Platform, "some path") qat.type_in(qat.Platform, "") ``` Finally, close the dialog by using a keyboard event: ```python # Close the path editor time.sleep(0.5) qat.type_in(qat.Platform, "") # Verify the dialog is closed qat.wait_for_object_missing(open_file_dialog, timeout=500) ``` This method provides a way to interact with native MacOS elements when direct widget access is unavailable.