# Menus and actions tutorial Menus are typically displayed in a menu bar or as context menus opened by right-clicking or by using a dedicated keyboard key. Both types of menu use the same underlying implementation and can be accessed in the same way with Qat. However, QtQuick/QML and QWidget frameworks offers two different implementations of such menus that require slightly different approaches when writing tests. This tutorial shows how to interact with each type of menu using the Qat API. ## Table of contents - [QML menus](#qml-menus) - [QWidget menus](#qwidget-menus) - [Actions](#actions) - [QML](#qml) - [QWidgets](#qwidgets) ## QML menus QML menus are built with standard components: each _Menu_ contains child _MenuItems_ displayed in a _ListView_. All of these can be accessed like any other QML component with the Qat API. Here is an example of a pull-down menu: !["QML menu example"](./images/menus/menu_qml.png) ```qml ApplicationWindow { objectName: "appWindow" menuBar: MenuBar { Menu { title: "File" objectName: "fileMenu" MenuItem { text: "New..." } Menu { title: "Open" objectName: "openSubMenu" MenuItem { text: "File" } MenuItem { text: "Project" } } } } // Other contents } ``` We can test the "Open project" feature by clicking on the corresponding menu: ```python menu_bar = { 'type': 'MenuBar' } # Open the 'File' menu file_item = { 'container': menu_bar, 'text': 'File', 'type': 'MenuBarItem' } qat.mouse_click(file_item) # Open the submenu file_menu = { 'objectName': 'fileMenu' } open_submenu = { 'container': file_menu, 'text': 'Open', 'type': 'MenuItem' } qat.mouse_click(open_submenu) # Click on the 'Project' menu sub_menu = { 'objectName': 'openSubMenu' } project_menu = { 'container': sub_menu, 'text': 'Project', 'type': 'MenuItem' } qat.mouse_click(project_menu) ``` There are two important things to consider when interacting with QML menus: 1. When identifying a _MenuItem_ or _MenuBarItem_ by its text, it is usually required to add its 'type' property too. The reason is that for each _Menu[Bar]Item_, Qt also creates a child _IconLabel_ with the same 'text' property. Adding the 'type' property allows Qat to find the desired item. 2. When creating a submenu, Qt automatically adds a _MenuItem_ to its parent menu with its 'text' set to the 'title' of the _Menu_ component. This means that to open the submenu, we need to find and click on the generated _MenuItem_ and not on the _Menu_ component. Here is a visualization of the previous example: !["QML sub-menu"](./images/menus/menu_qml_submenu.png) ## QWidget menus Contrary to QML implementation, QWidget's menus do not use standard widgets to display each item. To allow interacting with such items, Qat creates virtual widgets wrapping each menu item and providing a widget-like interface to be used with API functions such as _wait_for_object()_, _mouse_click()_ and others. Here is an example of a pull-down menu: !["QWidget menu example"](./images/menus/menu_qwidget.png) ```cpp // Assuming current class inherits from QMainWindow auto* menuToolBar = menuBar(); auto* fileMenu = menuToolBar->addMenu("File"); fileMenu->setObjectName("fileMenu"); fileMenu->addAction("New..."); auto* openMenu = fileMenu->addMenu("Open"); openMenu->setObjectName("openSubMenu"); openMenu->addAction("File"); openMenu->addAction("Project"); ``` Thanks to Qat's virtual widgets, we can write tests in a very similar way to the QML version (although note that the 'type' property is not needed in item definitions): ```python menu_bar = { 'type': 'QMenuBar' } # Open the 'File' menu file_item = { 'container': menu_bar, 'text': 'File' } qat.mouse_click(file_item) # Open the submenu file_menu = { 'objectName': 'fileMenu' } open_submenu = { 'container': file_menu, 'text': 'Open' } qat.mouse_click(open_submenu) # Click on the 'Project' menu sub_menu = { 'objectName': 'openSubMenu' } project_menu = { 'container': sub_menu, 'text': 'Project' } qat.mouse_click(project_menu) ``` Virtual menu items are created when a definition includes a 'container' property pointing to a _QMenu_ or _QMenuBar_ instance and a 'text' or 'objectName' property identifying a specific menu item. The virtual items returned by _wait_for_object()_ are of type ___MenuWrapper___ which provides the following read-only properties: - __text__ : The text of the menu item. - __visible__ : The "visible" property of the parent menu. - __enabled__ : The "enabled" property of the associated action. - __checked__ : The "checked" property of the associated action. - __action__ : The associated action which can be used to access other properties and methods. Also note that if a menu's text contains an ampersand '&' shortcut, the '&' is optional in its Qat definition. For example if a menu is defined as follows: ```cpp openMenu->addAction("&File"); ``` Then these two definitions will work: ```python # Use visible text only file_menu_1 = { 'container': sub_menu, 'text': 'File' } qat.mouse_click(file_menu_1) # Include '&' shortcut file_menu_2 = { 'container': sub_menu, 'text': '&File' } qat.mouse_click(file_menu_2) ``` ## Actions It is also possible to trigger actions directly, without using the associated menus. ### QML Here is a simple QML example with a single action: ```qml Menu { title: "Open" objectName: "openSubMenu" Action { text: "File" onTriggered: console.log("Opening file...") } } ``` The action can be explicitly triggered: ```python sub_menu = { 'objectName': 'openSubMenu' } open_file_action = { 'container': sub_menu, 'text': 'File', 'type': 'Action' } action = qat.wait_for_object_exists(open_file_action) action.trigger() ``` ### QWidgets Here is an example adding a QAction to an existing menu: ```cpp auto* openMenu = fileMenu->addMenu("Open"); openMenu->setObjectName("openSubMenu"); auto* openFileAction = new QAction("File", openMenu); QObject::connect(openFileAction, &QAction::triggered, []() { std::cout << "Opening file..." << std::endl; }); openMenu->addAction(openFileAction); ``` The action can be explicitly triggered: ```python sub_menu = { 'objectName': 'openSubMenu' } open_file_action = { 'container': sub_menu, 'text': 'File', 'type': 'QAction' } action = qat.wait_for_object_exists(open_file_action) action.trigger() ```