From 0255f091e840e644785358ac5bb364cd2637426e Mon Sep 17 00:00:00 2001 From: test Date: Mon, 23 Sep 2024 21:20:19 +0600 Subject: [PATCH 1/2] [Add] implement shadow-root --- .../action_declarations/info.py | 17 +-- .../Shared_Resources/LocateElement.py | 141 +++++++++++++++--- 2 files changed, 126 insertions(+), 32 deletions(-) diff --git a/Framework/Built_In_Automation/Sequential_Actions/action_declarations/info.py b/Framework/Built_In_Automation/Sequential_Actions/action_declarations/info.py index 34a77cbdd..92e43b929 100644 --- a/Framework/Built_In_Automation/Sequential_Actions/action_declarations/info.py +++ b/Framework/Built_In_Automation/Sequential_Actions/action_declarations/info.py @@ -92,21 +92,10 @@ "fail message", ) patterns = [ - "^parent \d parameter$", - "^sibling \d parameter$", - "^child \d parameter$", - "^following \d parameter$", - "^next \d parameter$", - "^preceding \d parameter$", - "^previous \d parameter$", - - "^src parent \d parameter$", "^src sibling \d parameter$", "^src child \d parameter$", "^src following \d parameter$", "^src next \d parameter$", "^src preceding \d parameter$", "^src previous \d parameter$", - "^dst parent \d parameter$", "^dst sibling \d parameter$", "^dst child \d parameter$", "^dst following \d parameter$", "^dst next \d parameter$", "^dst preceding \d parameter$", "^dst previous \d parameter$", - "^source parent \d parameter$", "^source sibling \d parameter$", "^source child \d parameter$", "^source following \d parameter$", "^source next \d parameter$", "^source preceding \d parameter$", "^source prevoius \d parameter$", - "^destination parent \d parameter$", "^destination sibling \d parameter$", "^destination child \d parameter$", "^destination following \d parameter$", "^destination next \d parameter$", "^destination preceding \d parameter$","^destination previous \d parameter$", - "^desired parent \d parameter$", "^desired sibling \d parameter$", "^desired child \d parameter$", "^desired following \d parameter$", "^desired next \d parameter$", "^desired preceding \d parameter$", "^desired previous \d parameter$", - + r'^sr *(src |source |dst |destination |desired )?(parent|sibling|child|next|following|previous|preceding) (\d )*parameter$', + r'^sr *(src |source |dst |destination |desired )?element parameter$', ] + # List of supported mobile platforms - must be lower case supported_platforms = ("android", "ios") diff --git a/Framework/Built_In_Automation/Shared_Resources/LocateElement.py b/Framework/Built_In_Automation/Shared_Resources/LocateElement.py index ef23c5e20..40930aa3e 100644 --- a/Framework/Built_In_Automation/Shared_Resources/LocateElement.py +++ b/Framework/Built_In_Automation/Shared_Resources/LocateElement.py @@ -7,6 +7,7 @@ import sys, time, re import inspect import traceback +from typing import Literal from pathlib import Path from Framework.Utilities import CommonUtil from Framework.Utilities.CommonUtil import passed_tag_list, failed_tag_list @@ -26,13 +27,119 @@ global driver_type driver_type = None - MODULE_NAME = inspect.getmodulename(__file__) +def build_css_selector_query(dataset:list[list[str]]) -> str: + """ Builds css selector query from dataset """ + try: + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + query = "" + element_parameter_list = [] + parent_parameter_list = [] + for left, mid, right in dataset: + mid_ = mid.replace(" ", "").lower() + if "elementparameter" == mid_: + element_parameter_list.append((left, right)) + if left == "css selector": + return right + elif "parent" in mid_ and "parameter" in mid_: + parent_parameter_list.append([left, right]) + elif "sibling" in mid_ and "parameter" in mid_: + CommonUtil.ExecLog(sModuleInfo, "Sibling parameter is not supported in css selector", 2) + elif "child" in mid_ and "parameter" in mid_: + CommonUtil.ExecLog(sModuleInfo, "Child parameter is not supported in css selector", 2) + elif "preceding" in mid_ and "parameter" in mid_: + CommonUtil.ExecLog(sModuleInfo, "Preceding parameter is not supported in css selector", 2) + elif "following" in mid_ and "parameter" in mid_: + CommonUtil.ExecLog(sModuleInfo, "Following parameter is not supported in css selector", 2) + + for left, right in parent_parameter_list: + if left == "tag": + query = right + query + break + else: + if len(parent_parameter_list) > 0: + query = "*" + query + + for left, right in parent_parameter_list: + if left == "tag": + pass + elif left == "text": + CommonUtil.ExecLog(sModuleInfo, "Text parameter is not supported in css selector", 2) + elif left == "xpath": + CommonUtil.ExecLog(sModuleInfo, "xpath parameter is not supported in css selector", 2) + elif left != "index": + quote = "'" if '"' in right else '"' + query += f"[{left}={quote}{right}{quote}]" + + if len(parent_parameter_list) > 0: + query += " " + + for left, right in element_parameter_list: + if left == "tag": + query += right + break + else: + query += "*" + + for left, right in element_parameter_list: + if left == "tag": + pass + elif left == "text": + CommonUtil.ExecLog(sModuleInfo, "Text parameter is not supported in css selector", 2) + elif left == "xpath": + CommonUtil.ExecLog(sModuleInfo, "xpath parameter is not supported in css selector", 2) + elif left != "index": + quote = "'" if '"' in right else '"' + query += f"[{left}={quote}{right}{quote}]" -def Get_Element(step_data_set, driver, query_debug=False, return_all_elements=False, element_wait=None): + return query + + except: + CommonUtil.Exception_Handler(sys.exc_info()) + return "" + +get_element_return_type = list[selenium.webdriver.remote.webelement.WebElement] | Literal["zeuz_failed"] | selenium.webdriver.remote.webelement.WebElement +def shadow_root_elements(shadow_root_ds: list[list[str]], element_ds: list[list[str]], Filter: str, element_wait: float, return_all_elements: bool) -> get_element_return_type: + """ Finds the shadow root container and the element inside there, both in css-selector method""" + try: + sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME + child_shadow_root_ds = [] + for left, mid, right in shadow_root_ds: + mid = mid.strip().lower() + if mid.startswith("sr"): + child_shadow_root_ds.append((left, mid, right)) + if len(child_shadow_root_ds) > 0: + # handle nested roots later + CommonUtil.ExecLog(sModuleInfo, "Nested shadow root is not supported yet", 2) + return "zeuz_failed" + else: + element_query = build_css_selector_query(shadow_root_ds) + index = _locate_index_number(shadow_root_ds) + index = 0 if index is None else index + elements = generic_driver.find_elements(By.CSS_SELECTOR, element_query) + filtered_elements = filter_elements(elements, Filter) + shadow_container_element = filtered_elements[index] + shadow_root_element = generic_driver.execute_script('return arguments[0].shadowRoot', shadow_container_element) + + element_query = build_css_selector_query(element_ds) + index = _locate_index_number(element_ds) + index = 0 if index is None else index + elements = shadow_root_element.find_elements(By.CSS_SELECTOR, element_query) + filtered_elements = filter_elements(elements, Filter) + if return_all_elements: + return filtered_elements + elif len(filtered_elements) == 0: + return [] + else: + return filtered_elements[index] + except: + return CommonUtil.Exception_Handler(sys.exc_info()) + + +def Get_Element(step_data_set, driver, query_debug=False, return_all_elements=False, element_wait=None) -> get_element_return_type: """ - This funciton will return "zeuz_failed" if something went wrong, else it will always return a single element + This function will return "zeuz_failed" if something went wrong, else it will always return a single element if you are trying to produce a query from a step dataset, make sure you provide query_debug =True. This is good when you are just trying to see how your step data would be converted to a query for testing local runs """ @@ -62,19 +169,8 @@ def Get_Element(step_data_set, driver, query_debug=False, return_all_elements=Fa # We need to switch to default content just in case previous action switched to something else try: if driver_type == "selenium": - pass #generic_driver.switch_to.default_content() - # we need to see if there are more than one handles. Since we cannot know if we had switch - # windows before, we are going to assume that we can always safely switch to default handle 0 - """ - try: - all_windows = generic_driver.window_handles - generic_driver.switch_to.window(all_windows[0]) - True - except: - True - """ + pass elif driver_type == "appium": - # If we find a '|' character in the left column, then try to check the platform # and filter the appropriate data for the left column by removing '|' device_platform = ( @@ -128,6 +224,8 @@ def Get_Element(step_data_set, driver, query_debug=False, return_all_elements=Fa get_parameter = "" Filter = "" text_filter_cond = False + shadow_root_ds = [] + element_ds = [] for row in step_data_set: if row[1] == "save parameter": if row[2] != "ignore": @@ -147,7 +245,13 @@ def Get_Element(step_data_set, driver, query_debug=False, return_all_elements=Fa element_wait = float(right) elif left == "text filter": text_filter_cond = right in ("yes", "true", "ok", "enable") + elif row[1].strip().lower().startswith("sr"): + shadow_root_ds.append([row[0], row[1][2:].strip(), row[2]]) + else: + element_ds.append([row[0], row[1], row[2]]) + if len(shadow_root_ds) > 0: + return shadow_root_elements(shadow_root_ds, element_ds, Filter, element_wait, return_all_elements) if get_parameter != "": @@ -238,9 +342,9 @@ def text_filter(step_data_set, Filter, element_wait, return_all_elements): """ suppose dom has
Hello  World
the text will be converted to "Hello world" - Thats why (text, element parameter, Hello world) does not work + That's why (text, element parameter, Hello world) does not work But (*text, element parameter, Hello world) works! - So for now we don't need this python script for now as we have an existing solution + So for now we don't need this python script as we have an existing solution """ try: sModuleInfo = inspect.currentframe().f_code.co_name + " : " + MODULE_NAME @@ -1084,7 +1188,8 @@ def filter_elements(all_matching_elements_visible_invisible, Filter): return all_matching_elements else: return all_matching_elements_visible_invisible - except: + except Exception as e: + CommonUtil.Exception_Handler(sys.exc_info()) all_matching_elements = [] return all_matching_elements From 46f3fcf08f01823bb446c7258b1ca814b58dac94 Mon Sep 17 00:00:00 2001 From: test Date: Mon, 23 Sep 2024 21:46:25 +0600 Subject: [PATCH 2/2] error to warning --- Framework/MainDriverApi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Framework/MainDriverApi.py b/Framework/MainDriverApi.py index 7865817b5..5aebe96f1 100644 --- a/Framework/MainDriverApi.py +++ b/Framework/MainDriverApi.py @@ -1275,7 +1275,7 @@ def send_dom_variables(): verify=False ) if res.status_code == 500: - CommonUtil.ExecLog(sModuleInfo, res.json()["info"], 3) + CommonUtil.ExecLog(sModuleInfo, res.json()["info"], 2) elif res.status_code == 404: CommonUtil.ExecLog(sModuleInfo, 'The chatbot API does not exist, server upgrade needed', 2) return