2
0

scripting_tools.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  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. from editor_python_test_tools.utils import TestHelper as helper
  7. from PySide2 import QtWidgets, QtTest, QtCore
  8. from PySide2.QtCore import Qt
  9. from editor_python_test_tools.utils import Report
  10. import editor_python_test_tools.pyside_utils as pyside_utils
  11. import editor_python_test_tools.hydra_editor_utils as hydra
  12. import azlmbr.editor as editor
  13. import azlmbr.math as math
  14. import azlmbr.bus as bus
  15. import azlmbr.legacy.general as general
  16. import azlmbr.scriptcanvas as scriptcanvas
  17. from scripting_utils.scripting_constants import (SCRIPT_CANVAS_UI, ASSET_EDITOR_UI, NODE_PALETTE_UI, NODE_PALETTE_QT,
  18. TREE_VIEW_QT, SEARCH_FRAME_QT, SEARCH_FILTER_QT, SAVE_STRING, NAME_STRING,
  19. SAVE_ASSET_AS, WAIT_TIME_3, NODE_INSPECTOR_TITLE_KEY, WAIT_FRAMES,
  20. VARIABLE_MANAGER_QT, NODE_INSPECTOR_QT, NODE_INSPECTOR_UI, SCRIPT_EVENT_UI,
  21. VARIABLE_PALETTE_QT, ADD_BUTTON_QT, VARIABLE_TYPES, EVENTS_QT, DEFAULT_SCRIPT_EVENT,
  22. SCRIPT_EVENT_FILE_PATH, PARAMETERS_QT, VARIABLE_MANAGER_QT, NODE_INSPECTOR_QT,
  23. NODE_INSPECTOR_UI, VARIABLE_PALETTE_QT, ADD_BUTTON_QT, VARIABLE_TYPES,
  24. SCRIPT_CANVAS_COMPONENT_PROPERTY_PATH)
  25. class Tests():
  26. new_event_created = ("New Script Event created", "Failed to create a new event")
  27. child_event_created = ("Child Event created", "Failed to create Child Event")
  28. parameter_created = ("Successfully added parameter", "Failed to add parameter")
  29. parameter_removed = ("Successfully removed parameter", "Failed to remove parameter")
  30. def click_menu_option(window, option_text):
  31. """
  32. function for clicking an option from a Qt menu object. This function bypasses menu groups or categories. for example,
  33. if you want to click the Open option from the "File" category provide "Open" as your menu text instead of "File" then "Open".
  34. param window: the qt window object where the menu option is located
  35. param option_text: the label string used in the menu option that you want to click
  36. returns none
  37. """
  38. action = pyside_utils.find_child_by_pattern(window, {"text": option_text, "type": QtWidgets.QAction})
  39. action.trigger()
  40. def save_script_event_file(self, file_path):
  41. """
  42. function for saving a script event file with a user defined file path. Requires asset editor qt object to be initialized
  43. and any required fields in the asset editor to be filled in before asset can be saved.
  44. param self: the script object calling this function
  45. param file_path: full path to the file as a string
  46. returns: true if the Save action is successful and the * character disappears from the asset editor label
  47. """
  48. editor.AssetEditorWidgetRequestsBus(bus.Broadcast, SAVE_ASSET_AS, file_path)
  49. action = pyside_utils.find_child_by_pattern(self.asset_editor_menu_bar, {"type": QtWidgets.QAction, "iconText": SAVE_STRING})
  50. action.trigger()
  51. # wait till file is saved, to validate that check the text of QLabel at the bottom of the AssetEditor,
  52. # if there are no unsaved changes we will not have any * in the text
  53. label = self.asset_editor.findChild(QtWidgets.QLabel, "textEdit")
  54. return helper.wait_for_condition(lambda: "*" not in label.text(), WAIT_TIME_3)
  55. def initialize_editor_object(self):
  56. self.editor_main_window = pyside_utils.get_editor_main_window()
  57. def initialize_sc_editor_objects(self):
  58. self.sc_editor = self.editor_main_window.findChild(QtWidgets.QDockWidget, SCRIPT_CANVAS_UI)
  59. self.sc_editor_main_window = self.sc_editor.findChild(QtWidgets.QMainWindow)
  60. def initialize_variable_manager_object(self):
  61. self.variable_manager = self.sc_editor.findChild(QtWidgets.QDockWidget, VARIABLE_MANAGER_QT)
  62. if not self.variable_manager.isVisible():
  63. self.click_menu_option(self.sc_editor, VARIABLE_MANAGER_QT)
  64. def initialize_asset_editor_object(self):
  65. """
  66. function for initializing qt objects needed for testing around asset editor
  67. param self: the script object calling this function.
  68. returns: None
  69. """
  70. self.asset_editor = self.editor_main_window.findChild(QtWidgets.QDockWidget, ASSET_EDITOR_UI)
  71. self.asset_editor_widget = self.asset_editor.findChild(QtWidgets.QWidget, "AssetEditorWindowClass")
  72. self.asset_editor_row_container = self.asset_editor_widget.findChild(QtWidgets.QWidget, "ContainerForRows")
  73. self.asset_editor_menu_bar = self.asset_editor_widget.findChild(QtWidgets.QMenuBar)
  74. def initialize_node_palette_object(self):
  75. """
  76. function for initializing qt objects needed for testing around the script canvas editor
  77. param self: the script object calling this function
  78. returns: None
  79. """
  80. self.node_palette = self.sc_editor.findChild(QtWidgets.QDockWidget, NODE_PALETTE_QT)
  81. self.node_tree_view = self.node_palette.findChild(QtWidgets.QTreeView, TREE_VIEW_QT)
  82. self.node_tree_search_frame = self.node_palette.findChild(QtWidgets.QFrame, SEARCH_FRAME_QT)
  83. self.node_tree_search_box = self.node_tree_search_frame.findChild(QtWidgets.QLineEdit, SEARCH_FILTER_QT)
  84. def expand_qt_container_rows(self, object_name):
  85. """
  86. function used for expanding qt container rows with expandable children
  87. param self: The script object calling this function
  88. param object_name: qt object name as a string
  89. returns: none
  90. """
  91. children = self.asset_editor_row_container.findChildren(QtWidgets.QFrame, object_name)
  92. for child in children:
  93. check_box = child.findChild(QtWidgets.QCheckBox)
  94. if check_box and not check_box.isChecked():
  95. QtTest.QTest.mouseClick(check_box, QtCore.Qt.LeftButton, QtCore.Qt.NoModifier)
  96. def open_node_palette(self):
  97. """
  98. function for checking if node palette is on and if not turn it on
  99. param self: the script calling this function
  100. returns none
  101. """
  102. if self.sc_editor.findChild(QtWidgets.QDockWidget, NODE_PALETTE_QT) is None:
  103. action = pyside_utils.find_child_by_pattern(self.sc_editor, {"text": NODE_PALETTE_UI, "type": QtWidgets.QAction})
  104. action.trigger()
  105. def open_script_canvas():
  106. """
  107. function for opening the script canvas UI
  108. returns true / false result of helper function's attempt
  109. """
  110. general.open_pane(SCRIPT_CANVAS_UI)
  111. result = helper.wait_for_condition(lambda: general.is_pane_visible(SCRIPT_CANVAS_UI), WAIT_TIME_3)
  112. return result
  113. def open_asset_editor():
  114. """
  115. function for opening the asset editor UI
  116. returns true/false result of helper function's attempt
  117. """
  118. general.open_pane(ASSET_EDITOR_UI)
  119. result = helper.wait_for_condition(lambda: general.is_pane_visible(ASSET_EDITOR_UI), WAIT_TIME_3)
  120. return result
  121. def canvas_node_palette_search(self, node_name, number_of_retries):
  122. """
  123. function for searching the script canvas node palette for user defined nodes. function takes a number of retries as
  124. an argument in case editor/script canvas lags during test.
  125. param self: The script calling this function
  126. param node_name: the name of the node being searched for
  127. param number_of_retries: the number of times to search (click on the search button)
  128. returns: boolean value of the search attempt
  129. """
  130. self.node_tree_search_box.setText(node_name)
  131. helper.wait_for_condition(lambda: self.node_tree_search_box.text() == node_name, WAIT_TIME_3)
  132. # Try clicking ENTER in search box multiple times
  133. found_node = False
  134. for _ in range(number_of_retries):
  135. QtTest.QTest.keyClick(self.node_tree_search_box, QtCore.Qt.Key_Enter, QtCore.Qt.NoModifier)
  136. found_node = helper.wait_for_condition(
  137. lambda: pyside_utils.find_child_by_pattern(self.node_tree_view, {"text": node_name}) is not None, WAIT_TIME_3)
  138. if found_node is True:
  139. break
  140. return found_node
  141. def get_node_palette_node_tree_qt_object (self):
  142. """
  143. function for retrieving the tree view qt object for the node palette
  144. params self: the script calling this function
  145. returns: a tree view qt object
  146. """
  147. node_palette_widget = self.sc_editor.findChild(QtWidgets.QDockWidget, NODE_PALETTE_QT)
  148. node_palette_node_tree = node_palette_widget.findChild(QtWidgets.QTreeView, TREE_VIEW_QT)
  149. return node_palette_node_tree
  150. def get_node_palette_category_qt_object(self, category_name):
  151. """
  152. function for retrieving the qt object for a node palette category
  153. param self: the script calling this function
  154. param category_name: string for the category label you are searching node palette for
  155. returns: the qt object for the node palette category
  156. """
  157. node_palette_node_tree = get_node_palette_node_tree_qt_object(self)
  158. node_palette_category = pyside_utils.find_child_by_pattern(node_palette_node_tree, category_name)
  159. return node_palette_category
  160. def get_node_inspector_node_titles(self, sc_graph_node_inspector, sc_graph):
  161. """
  162. function for retrieving the node inspector's node titles from all nodes in a script canvas graph. function takes
  163. a script canvas graph and node inspector qt widget.
  164. param self: the script calling this function
  165. param sc_graph_node_inspector: the sc graph node inspector qt widget
  166. param sc_graph: the sc graph qt widget
  167. returns: a list of node titles (i.e Print - Utilities/Debug). If there are duplicates of a node then the title
  168. will include ( X Selected) in the string.
  169. """
  170. node_inspector_scroll_area = sc_graph_node_inspector.findChild(QtWidgets.QScrollArea, "")
  171. # perform ctrl+a keystroke to highlight all nodes on the graph
  172. QtTest.QTest.keyClick(sc_graph, "a", Qt.ControlModifier, WAIT_FRAMES)
  173. node_inspector_backgrounds = node_inspector_scroll_area.findChildren(QtWidgets.QFrame, "Background")
  174. titles = []
  175. for background in node_inspector_backgrounds:
  176. background_title = background.findChild(QtWidgets.QLabel, NODE_INSPECTOR_TITLE_KEY)
  177. if background_title.text() is not "":
  178. titles.append(background_title.text())
  179. return titles
  180. def get_main_sc_window_qt_object():
  181. """
  182. function for getting the sc main window qt object.
  183. params: none
  184. returns: a qt widget main window object
  185. """
  186. editor_window = pyside_utils.get_editor_main_window()
  187. sc_editor = editor_window.findChild(QtWidgets.QDockWidget, SCRIPT_CANVAS_UI)
  188. return sc_editor.findChild(QtWidgets.QMainWindow)
  189. def create_new_sc_graph(sc_editor_main_window):
  190. """
  191. function for opening a new script canvas graph file. uses the sc editor window to trigger a new file action
  192. param self: the script calling this function
  193. param sc_editor_main_window: the qt object for the main sc_editor window
  194. returns: none
  195. """
  196. create_new_graph_action = pyside_utils.find_child_by_pattern(
  197. sc_editor_main_window, {"objectName": "action_New_Script", "type": QtWidgets.QAction}
  198. )
  199. create_new_graph_action.trigger()
  200. def create_new_variable(self, new_variable_type):
  201. """
  202. function for creating a new SC variable through variable manager
  203. param self: the script objecting calling this function
  204. param variable_type: The variable data type to create as a string. i.e "Boolean"
  205. returns: none
  206. """
  207. if type(new_variable_type) is not str:
  208. Report.critical_result(["Invalid variable type provided", ""], False)
  209. valid_type = False
  210. for this_type in VARIABLE_TYPES:
  211. if new_variable_type == this_type:
  212. valid_type = True
  213. if not valid_type:
  214. Report.critical_result(["Invalid variable type provided", ""], False)
  215. add_new_variable_button = self.variable_manager.findChild(QtWidgets.QPushButton, ADD_BUTTON_QT)
  216. add_new_variable_button.click() # Click on Create Variable button
  217. helper.wait_for_condition((
  218. lambda: self.variable_manager.findChild(QtWidgets.QTableView, VARIABLE_PALETTE_QT) is not None), WAIT_TIME_3)
  219. # Select variable type
  220. table_view = self.variable_manager.findChild(QtWidgets.QTableView, VARIABLE_PALETTE_QT)
  221. model_index = pyside_utils.find_child_by_pattern(table_view, new_variable_type)
  222. # Click on it to create variable
  223. pyside_utils.item_view_index_mouse_click(table_view, model_index)
  224. def get_sc_editor_node_inspector(sc_editor):
  225. """
  226. function for toggling the node inspector if it's not already turned on and returning the qt widget object
  227. param sc_editor: the script canvas editor qt object
  228. returns: the node inspector qt widget object
  229. """
  230. node_inspector_widget = sc_editor.findChild(QtWidgets.QDockWidget, NODE_INSPECTOR_QT)
  231. if sc_editor.findChild(QtWidgets.QDockWidget, NODE_INSPECTOR_QT) is None:
  232. action = pyside_utils.find_child_by_pattern(sc_editor, {"text": NODE_INSPECTOR_UI, "type": QtWidgets.QAction})
  233. action.trigger()
  234. return node_inspector_widget
  235. def create_script_event(self):
  236. """
  237. Function for creating a script event from the editor's asset editor.
  238. param self: the script calling this function
  239. returns None
  240. """
  241. action = pyside_utils.find_child_by_pattern(self.asset_editor_menu_bar, {"type": QtWidgets.QAction, "text": SCRIPT_EVENT_UI})
  242. action.trigger()
  243. result = helper.wait_for_condition(
  244. lambda: self.asset_editor_row_container.findChild(QtWidgets.QFrame, EVENTS_QT) is not None, WAIT_TIME_3
  245. )
  246. Report.result(Tests.new_event_created, result)
  247. # Add new child event
  248. add_event = self.asset_editor_row_container.findChild(QtWidgets.QFrame, EVENTS_QT).findChild(QtWidgets.QToolButton, "")
  249. add_event.click()
  250. result = helper.wait_for_condition(
  251. lambda: self.asset_editor_widget.findChild(QtWidgets.QFrame, DEFAULT_SCRIPT_EVENT) is not None, WAIT_TIME_3
  252. )
  253. Report.result(Tests.child_event_created, result)
  254. def create_script_event_parameter(self):
  255. add_param = self.asset_editor_row_container.findChild(QtWidgets.QFrame, "Parameters").findChild(QtWidgets.QToolButton, "")
  256. add_param.click()
  257. result = helper.wait_for_condition(
  258. lambda: self.asset_editor_widget.findChild(QtWidgets.QFrame, "[0]") is not None, WAIT_TIME_3
  259. )
  260. Report.result(Tests.parameter_created, result)
  261. def remove_script_event_parameter(self):
  262. remove_param = self.asset_editor_row_container.findChild(QtWidgets.QFrame, "[0]").findChild(QtWidgets.QToolButton, "")
  263. remove_param.click()
  264. result = helper.wait_for_condition(
  265. lambda: self.asset_editor_widget.findChild(QtWidgets.QFrame, "[0]") is None, WAIT_TIME_3
  266. )
  267. Report.result(Tests.parameter_removed, result)
  268. def add_empty_parameter_to_script_event(self, number_of_parameters):
  269. """
  270. Function for adding a new blank parameter to a script event
  271. param self: the script calling this function
  272. param number_of_parameters: the number of empty parameters to add
  273. returns none
  274. """
  275. helper.wait_for_condition(
  276. lambda: self.asset_editor_row_container.findChild(QtWidgets.QFrame, PARAMETERS_QT) is not None, WAIT_TIME_3)
  277. parameters = self.asset_editor_row_container.findChild(QtWidgets.QFrame, PARAMETERS_QT)
  278. add_parameter = parameters.findChild(QtWidgets.QToolButton, "")
  279. for _ in range(number_of_parameters):
  280. add_parameter.click()
  281. def get_script_event_parameter_name_text(self):
  282. """
  283. function for retrieving the name field of script event parameters
  284. param self: the script calling this function
  285. returns a container with all the parameters' editable name fields
  286. """
  287. parameter_names = self.asset_editor_row_container.findChildren(QtWidgets.QFrame, NAME_STRING)
  288. name_fields = []
  289. for parameter_name in parameter_names:
  290. name_fields.append(parameter_name.findChild(QtWidgets.QLineEdit))
  291. return name_fields
  292. def get_script_event_parameter_type_combobox(self):
  293. """
  294. function for retrieving the type field of script event parameters
  295. param self: the script calling this function
  296. returns a container with all the parameters' editable type combo boxes
  297. """
  298. parameter_types = self.asset_editor_row_container.findChildren(QtWidgets.QFrame, "Type")
  299. type_combo_boxes =[]
  300. for parameter_type in parameter_types:
  301. type_combo_boxes.append(parameter_type.findChild(QtWidgets.QComboBox))
  302. return type_combo_boxes
  303. def located_expected_tracer_lines(self, section_tracer, lines):
  304. """
  305. function for parsing game mode's console output for expected test lines. requires section_tracer. duplicates lines
  306. and error lines are not handled by this function
  307. param self: The script calling this function
  308. param section_tracer: python editor tracer object
  309. param lines: list of expected lines
  310. returns true if all the expected lines were detected in the parsed output
  311. """
  312. found_lines = [printInfo.message.strip() for printInfo in section_tracer.prints]
  313. expected_lines = len(lines)
  314. matching_lines = 0
  315. for line in lines:
  316. for found_line in found_lines:
  317. if line == found_line:
  318. matching_lines += 1
  319. return matching_lines >= expected_lines
  320. def create_entity_with_sc_component_asset(entity_name, source_file, position = math.Vector3(512.0, 512.0, 32.0)):
  321. """
  322. function for creating a new entity in the scene w/ a script canvas component. Function also adds as
  323. script canvas file to the script canvas component's source file property.
  324. param entity_name: the name you want to assign the entity
  325. param source_file: the path to script canvas file to be added to the script canvas component
  326. param position: the translation property of the new entity's transform
  327. returns: the entity created by this function
  328. """
  329. sourcehandle = scriptcanvas.SourceHandleFromPath(source_file)
  330. entity = hydra.Entity(entity_name)
  331. entity.create_entity(position, ["Script Canvas"])
  332. script_canvas_component = entity.components[0]
  333. hydra.set_component_property_value(script_canvas_component, SCRIPT_CANVAS_COMPONENT_PROPERTY_PATH, sourcehandle)
  334. return entity