AssetPicker_UI_UX.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. """
  2. Copyright (c) Contributors to the Open 3D Engine Project.
  3. For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. SPDX-License-Identifier: Apache-2.0 OR MIT
  5. """
  6. def AssetPicker_UI_UX():
  7. import pyside_utils
  8. @pyside_utils.wrap_async
  9. async def run_test():
  10. """
  11. Summary:
  12. Verify the functionality of Asset Picker and UI/UX properties
  13. Expected Behavior:
  14. The asset picker opens and is labeled appropriately ("Pick Model Asset" in this instance).
  15. The Asset Picker window can be resized and moved around the screen.
  16. The file tree expands/retracts appropriately and a scroll bar is present when the menus extend
  17. beyond the length of the window.
  18. The assets are limited to a valid type for the field selected (model assets in this instance)
  19. The asset picker is closed and the selected asset is assigned to the mesh component.
  20. Test Steps:
  21. 1) Open a simple level
  22. 2) Create entity and add Mesh component
  23. 3) Access Entity Inspector
  24. 4) Click Asset Picker (Model Asset)
  25. a) Collapse all the files initially and verify if scroll bar is not visible
  26. b) Expand/Verify Top folder of file path
  27. c) Expand/Verify Nested folder of file path
  28. d) Verify if the ScrollBar appears after expanding folders
  29. e) Collapse Nested and Top Level folders and verify if collapsed
  30. f) Verify if the correct files are appearing in the Asset Picker
  31. g) Move the widget and verify position
  32. h) Resize the widget
  33. g) Assign Model asset
  34. 5) Verify if Model Asset is assigned via both OK/Enter options
  35. Note:
  36. - This test file must be called from the O3DE Editor command terminal
  37. - Any passed and failed tests are written to the Editor.log file.
  38. Parsing the file or running a log_monitor are required to observe the test results.
  39. :return: None
  40. """
  41. import os
  42. from PySide2 import QtWidgets, QtTest, QtCore
  43. from PySide2.QtCore import Qt
  44. import azlmbr.asset as asset
  45. import azlmbr.bus as bus
  46. import azlmbr.editor as editor
  47. import azlmbr.entity as entity
  48. import azlmbr.legacy.general as general
  49. import azlmbr.math as math
  50. import editor_python_test_tools.hydra_editor_utils as hydra
  51. from editor_python_test_tools.utils import Report
  52. from editor_python_test_tools.utils import TestHelper as helper
  53. file_path = ["AutomatedTesting", "Assets", "Objects", "Foliage"]
  54. def select_entity_by_name(entity_name):
  55. searchFilter = entity.SearchFilter()
  56. searchFilter.names = [entity_name]
  57. entities = entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter)
  58. editor.ToolsApplicationRequestBus(bus.Broadcast, 'MarkEntitySelected', entities[0])
  59. def is_asset_assigned(component, interaction_option):
  60. path = os.path.join("assets", "objects", "foliage", "cedar.fbx.azmodel")
  61. expected_asset_id = asset.AssetCatalogRequestBus(bus.Broadcast, 'GetAssetIdByPath', path, math.Uuid(),
  62. False)
  63. result = hydra.get_component_property_value(component, "Controller|Configuration|Model Asset")
  64. expected_asset_str = expected_asset_id.invoke("ToString")
  65. result_str = result.invoke("ToString")
  66. Report.info(f"Asset assigned for {interaction_option} option: {expected_asset_str == result_str}")
  67. return expected_asset_str == result_str
  68. def move_and_resize_widget(widget):
  69. # Move the widget and verify position
  70. initial_position = widget.pos()
  71. x, y = initial_position.x() + 5, initial_position.y() + 5
  72. widget.move(x, y)
  73. curr_position = widget.pos()
  74. asset_picker_moved = (
  75. "Asset Picker widget moved successfully",
  76. "Failed to move Asset Picker widget"
  77. )
  78. Report.result(asset_picker_moved, curr_position.x() == x and curr_position.y() == y)
  79. # Resize the widget and verify size
  80. width, height = (
  81. widget.geometry().width() + 10,
  82. widget.geometry().height() + 10,
  83. )
  84. widget.resize(width, height)
  85. asset_picker_resized = (
  86. "Resized Asset Picker widget successfully",
  87. "Failed to resize Asset Picker widget"
  88. )
  89. Report.result(asset_picker_resized, widget.geometry().width() == width and widget.geometry().height() ==
  90. height)
  91. def verify_expand(model_index, tree):
  92. initially_collapsed = (
  93. "Folder initially collapsed",
  94. "Folder unexpectedly expanded"
  95. )
  96. expanded = (
  97. "Folder expanded successfully",
  98. "Failed to expand folder"
  99. )
  100. # Check initial collapse
  101. Report.result(initially_collapsed, not tree.isExpanded(model_index))
  102. # Expand at the specified index
  103. tree.expand(model_index)
  104. # Verify expansion
  105. Report.result(expanded, tree.isExpanded(model_index))
  106. def verify_collapse(model_index, tree):
  107. collapsed = (
  108. "Folder hierarchy collapsed successfully",
  109. "Failed to collapse folder hierarchy"
  110. )
  111. tree.collapse(model_index)
  112. Report.result(collapsed, not tree.isExpanded(model_index))
  113. def verify_files_appeared(model, allowed_asset_extensions, parent_index=QtCore.QModelIndex()):
  114. indices = [parent_index]
  115. while len(indices) > 0:
  116. parent_index = indices.pop(0)
  117. for row in range(model.rowCount(parent_index)):
  118. cur_index = model.index(row, 0, parent_index)
  119. cur_data = cur_index.data(Qt.DisplayRole)
  120. if (
  121. "." in cur_data
  122. and (cur_data.lower().split(".")[-1] not in allowed_asset_extensions)
  123. and not cur_data[-1] == ")"
  124. ):
  125. Report.info(f"Incorrect file found: {cur_data}")
  126. return False
  127. indices.append(cur_index)
  128. return True
  129. async def asset_picker(allowed_asset_extensions, asset, interaction_option):
  130. active_modal_widget = await pyside_utils.wait_for_modal_widget()
  131. if active_modal_widget:
  132. dialog = active_modal_widget.findChildren(QtWidgets.QDialog, "AssetPickerDialogClass")[0]
  133. asset_picker_title = (
  134. "Asset Picker window is titled as expected",
  135. "Asset Picker window has an unexpected title"
  136. )
  137. Report.result(asset_picker_title, dialog.windowTitle() == "Pick ModelAsset")
  138. tree = dialog.findChildren(QtWidgets.QTreeView, "m_assetBrowserTreeViewWidget")[0]
  139. scroll_area = tree.findChild(QtWidgets.QWidget, "qt_scrollarea_vcontainer")
  140. scroll_bar = scroll_area.findChild(QtWidgets.QScrollBar)
  141. # a) Collapse all the files initially and verify if scroll bar is not visible
  142. tree.collapseAll()
  143. await pyside_utils.wait_for_condition(lambda: not scroll_bar.isVisible(), 0.5)
  144. scroll_bar_hidden = (
  145. "Scroll Bar is not visible before tree expansion",
  146. "Scroll Bar is visible before tree expansion"
  147. )
  148. Report.result(scroll_bar_hidden, not scroll_bar.isVisible())
  149. # Get Model Index of the file paths
  150. model_index_1 = pyside_utils.find_child_by_pattern(tree, file_path[0])
  151. model_index_2 = pyside_utils.find_child_by_pattern(model_index_1, file_path[1])
  152. # b) Expand/Verify Top folder of file path
  153. verify_expand(model_index_1, tree)
  154. # c) Expand/Verify Nested folder of file path
  155. verify_expand(model_index_2, tree)
  156. # d) Verify if the ScrollBar appears after expanding folders
  157. tree.expandAll()
  158. await pyside_utils.wait_for_condition(lambda: scroll_bar.isVisible(), 0.5)
  159. scroll_bar_visible = (
  160. "Scroll Bar is visible after tree expansion",
  161. "Scroll Bar is not visible after tree expansion"
  162. )
  163. Report.result(scroll_bar_visible, scroll_bar.isVisible())
  164. # e) Collapse Nested and Top Level folders and verify if collapsed
  165. verify_collapse(model_index_2, tree)
  166. verify_collapse(model_index_1, tree)
  167. # f) Verify if the correct files are appearing in the Asset Picker
  168. asset_picker_correct_files_appear = (
  169. "Expected assets populated in the file picker",
  170. "Found unexpected assets in the file picker"
  171. )
  172. Report.result(asset_picker_correct_files_appear, verify_files_appeared(tree.model(),
  173. allowed_asset_extensions))
  174. # While we are here we can also check if we can resize and move the widget
  175. move_and_resize_widget(active_modal_widget)
  176. # g) Assign asset
  177. tree.collapseAll()
  178. await pyside_utils.wait_for_condition(
  179. lambda: len(dialog.findChildren(QtWidgets.QFrame, "m_searchWidget")) > 0, 0.5)
  180. search_widget = dialog.findChildren(QtWidgets.QFrame, "m_searchWidget")[0]
  181. search_line_edit = search_widget.findChildren(QtWidgets.QLineEdit, "textSearch")[0]
  182. search_line_edit.setText(asset)
  183. tree.expandAll()
  184. asset_model_index = pyside_utils.find_child_by_pattern(tree, asset)
  185. await pyside_utils.wait_for_condition(lambda: asset_model_index.isValid(), 2.0)
  186. tree.expand(asset_model_index)
  187. tree.setCurrentIndex(asset_model_index)
  188. if interaction_option == "ok":
  189. button_box = dialog.findChild(QtWidgets.QDialogButtonBox, "m_buttonBox")
  190. ok_button = button_box.button(QtWidgets.QDialogButtonBox.Ok)
  191. await pyside_utils.click_button_async(ok_button)
  192. elif interaction_option == "enter":
  193. QtTest.QTest.keyClick(tree, Qt.Key_Enter, Qt.NoModifier)
  194. # 1) Open an existing simple level
  195. hydra.open_base_level()
  196. # 2) Create entity and add Mesh component
  197. entity_position = math.Vector3(125.0, 136.0, 32.0)
  198. entity = hydra.Entity("TestEntity")
  199. entity.create_entity(entity_position, ["Mesh"])
  200. # 3) Access Entity Inspector
  201. editor_window = pyside_utils.get_editor_main_window()
  202. entity_inspector = editor_window.findChild(QtWidgets.QDockWidget, "Inspector")
  203. component_list_widget = entity_inspector.findChild(QtWidgets.QWidget, "m_componentListContents")
  204. # 4) Click on Asset Picker (Model Asset)
  205. select_entity_by_name("TestEntity")
  206. general.idle_wait(0.5)
  207. attached_button = component_list_widget.findChildren(QtWidgets.QPushButton, "attached-button")[0]
  208. # Assign Model Asset via OK button
  209. pyside_utils.click_button_async(attached_button)
  210. await asset_picker(["azmodel", "fbx"], "cedar.fbx (ModelAsset)", "ok")
  211. # 5) Verify if Model Asset is assigned
  212. try:
  213. mesh_success = await pyside_utils.wait_for_condition(lambda: is_asset_assigned(entity.components[0],
  214. "ok"))
  215. except pyside_utils.EventLoopTimeoutException as err:
  216. print(err)
  217. mesh_success = False
  218. model_asset_assigned_ok = (
  219. "Successfully assigned Model asset via OK button",
  220. "Failed to assign Model asset via OK button"
  221. )
  222. Report.result(model_asset_assigned_ok, mesh_success)
  223. # Clear Model Asset
  224. hydra.get_set_test(entity, 0, "Controller|Configuration|Model Asset", None)
  225. select_entity_by_name("TestEntity")
  226. general.idle_wait(0.5)
  227. attached_button = component_list_widget.findChildren(QtWidgets.QPushButton, "attached-button")[0]
  228. # Assign Model Asset via Enter
  229. pyside_utils.click_button_async(attached_button)
  230. await asset_picker(["azmodel", "fbx"], "cedar.fbx (ModelAsset)", "enter")
  231. # 5) Verify if Model Asset is assigned
  232. try:
  233. mesh_success = await pyside_utils.wait_for_condition(lambda: is_asset_assigned(entity.components[0],
  234. "enter"))
  235. except pyside_utils.EventLoopTimeoutException as err:
  236. print(err)
  237. mesh_success = False
  238. model_asset_assigned_enter = (
  239. "Successfully assigned Model Asset via Enter button",
  240. "Failed to assign Model Asset via Enter button"
  241. )
  242. Report.result(model_asset_assigned_enter, mesh_success)
  243. run_test()
  244. if __name__ == "__main__":
  245. from editor_python_test_tools.utils import Report
  246. Report.start_test(AssetPicker_UI_UX)