diff --git a/scripts/install_package.cmd b/scripts/install_package.cmd index 738329509..e7019566a 100644 --- a/scripts/install_package.cmd +++ b/scripts/install_package.cmd @@ -13,5 +13,5 @@ taskkill /f /im chromedriver.exe echo ------------------------- echo Installing project... echo ------------------------- -pip install -U dist/tir_framework-2.6.0.tar.gz +pip install -U dist/tir_framework-2.7.0rc1.tar.gz pause >nul | set/p = Press any key to exit ... diff --git a/tests/test_webapp_internal_clicktree.py b/tests/test_webapp_internal_clicktree.py new file mode 100644 index 000000000..f89e9c51e --- /dev/null +++ b/tests/test_webapp_internal_clicktree.py @@ -0,0 +1,431 @@ +import importlib.util +import logging +import sys +import types +from pathlib import Path +from types import SimpleNamespace +from unittest.mock import Mock + + +repo_root = Path(__file__).resolve().parents[1] +module_path = repo_root / "tir" / "technologies" / "webapp_internal.py" + + +class FakeElement: + def __init__(self, text="", attrs=None, parent=None): + self.text = text + self.attrs = attrs or {} + self.parent = parent + self.click_count = 0 + + def get_attribute(self, name): + return self.attrs.get(name) + + def click(self): + self.click_count += 1 + + +def _register_module(name, module=None): + module = module or types.ModuleType(name) + sys.modules[name] = module + + if "." in name: + parent_name, child_name = name.rsplit(".", 1) + parent = sys.modules.get(parent_name) + if parent is None: + parent = _register_module(parent_name) + setattr(parent, child_name, module) + + return module + + +def load_webapp_internal_module(): + cached = sys.modules.get("tir_webapp_internal_test") + if cached is not None: + return cached + + # External dependency stubs used only to import the module for isolated unit tests. + _register_module("pandas") + _register_module("cv2") + + bs4_mod = _register_module("bs4") + bs4_mod.BeautifulSoup = type("BeautifulSoup", (), {}) + bs4_mod.Tag = type("Tag", (), {}) + + _register_module("selenium") + _register_module("selenium.webdriver") + _register_module("selenium.webdriver.common") + _register_module("selenium.webdriver.support") + + keys_mod = _register_module("selenium.webdriver.common.keys") + keys_mod.Keys = type("Keys", (), {"CONTROL": "CONTROL", "F5": "F5"}) + + by_mod = _register_module("selenium.webdriver.common.by") + by_mod.By = type("By", (), {"CSS_SELECTOR": "css", "XPATH": "xpath"}) + + action_mod = _register_module("selenium.webdriver.common.action_chains") + action_mod.ActionChains = type("ActionChains", (), {}) + + support_ui_mod = _register_module("selenium.webdriver.support.ui") + support_ui_mod.Select = type("Select", (), {}) + + expected_conditions_mod = _register_module("selenium.webdriver.support.expected_conditions") + expected_conditions_mod.EC = type("EC", (), {}) + + exceptions_mod = _register_module("selenium.common.exceptions") + exceptions_mod.WebDriverException = type("WebDriverException", (Exception,), {}) + exceptions_mod.NoSuchElementException = type("NoSuchElementException", (Exception,), {}) + + tir_pkg = _register_module("tir") + tir_pkg.__path__ = [] + technologies_pkg = _register_module("tir.technologies") + technologies_pkg.__path__ = [] + core_pkg = _register_module("tir.technologies.core") + core_pkg.__path__ = [] + third_party_pkg = _register_module("tir.technologies.core.third_party") + third_party_pkg.__path__ = [] + + enum_mod = _register_module("tir.technologies.core.enumerations") + enum_mod.ClickType = type("ClickType", (), {"SELENIUM": "selenium", "JS": "js"}) + enum_mod.ScrapType = type( + "ScrapType", + (), + { + "TEXT": "text", + "MIXED": "mixed", + "CSS_SELECTOR": "css", + "SCRIPT": "script", + }, + ) + enum_mod.MessageType = type( + "MessageType", + (), + { + "CORRECT": "correct", + "INCORRECT": "incorrect", + "DISABLED": "disabled", + "ASSERTERROR": "asserterror", + }, + ) + + base_module = _register_module("tir.technologies.core.base") + base_module.Base = type("Base", (), {"errors": []}) + + log_module = _register_module("tir.technologies.core.log") + log_module.Log = type("Log", (), {}) + log_module.nump = None + + config_module = _register_module("tir.technologies.core.config") + config_module.ConfigLoader = lambda *args, **kwargs: None + + language_module = _register_module("tir.technologies.core.language") + language_module.LanguagePack = type("LanguagePack", (), {}) + + xpath_module = _register_module("tir.technologies.core.third_party.xpath_soup") + xpath_module.xpath_soup = lambda *args, **kwargs: "//stub" + + psutil_module = _register_module("tir.technologies.core.psutil_info") + psutil_module.system_info = lambda: {} + + numexec_module = _register_module("tir.technologies.core.numexec") + numexec_module.NumExec = type("NumExec", (), {}) + + logging_config_module = _register_module("tir.technologies.core.logging_config") + logging_config_module.logger = lambda: logging.getLogger("test_webapp_internal_clicktree") + + base_database_module = _register_module("tir.technologies.core.base_database") + base_database_module.BaseDatabase = type("BaseDatabase", (), {}) + + spec = importlib.util.spec_from_file_location("tir_webapp_internal_test", str(module_path)) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + sys.modules["tir_webapp_internal_test"] = module + return module + + +def build_webapp(): + module = load_webapp_internal_module() + webapp = module.WebappInternal.__new__(module.WebappInternal) + webapp.config = SimpleNamespace(time_out=0.01) + webapp.tree_base_element = () + webapp.wait_blocker = Mock() + webapp.check_layers = Mock(return_value=0) + webapp.scroll_to_element = Mock() + webapp.log_error = Mock() + webapp.click = Mock() + webapp.return_last_zindex = Mock(return_value=0) + webapp.element_exists = Mock(return_value=False) + webapp.soup_to_selenium = Mock(side_effect=lambda element: element) + return webapp, module + + +def test_clicktree_delegates_to_internal_method(): + webapp, _ = build_webapp() + webapp.click_tree = Mock() + + webapp.ClickTree("nivel 1 > nivel 2", right_click=True, position=2, tree_number=3) + + webapp.click_tree.assert_called_once_with("nivel 1 > nivel 2", True, 2, 3) + + +def test_click_tree_clicks_the_last_matching_node_without_browser_dependencies(): + webapp, _ = build_webapp() + + node = FakeElement( + text=" Financeiro ", + attrs={"hidden": None, "closed": "", "hierarchy": "01", "icon": None}, + ) + clickable_part = FakeElement(text="Financeiro", parent=node) + + webapp.find_tree_bs = Mock(return_value=[node]) + webapp.element_is_displayed = Mock(return_value=True) + webapp.check_toggler = Mock(return_value=False) + webapp.check_hierarchy = Mock(return_value=False) + + selected_nodes = [None, node, node] + + def treenode_selected(_label, _tree_number=0): + return selected_nodes.pop(0) if selected_nodes else node + + def execute_js_selector(selector, element, get_all=True): + if selector == ".toggler, .lastchild, .data, label": + return [clickable_part] + return None + + webapp.treenode_selected = Mock(side_effect=treenode_selected) + webapp.execute_js_selector = Mock(side_effect=execute_js_selector) + + webapp.click_tree("Financeiro", right_click=False, position=1, tree_number=0) + + assert clickable_part.click_count == 1 + webapp.log_error.assert_not_called() + + +def test_click_tree_logs_error_when_no_tree_node_is_found(): + webapp, _ = build_webapp() + webapp.config.time_out = 0 + webapp.find_tree_bs = Mock(return_value=[]) + webapp.element_is_displayed = Mock(return_value=True) + webapp.treenode_selected = Mock(return_value=None) + webapp.execute_js_selector = Mock(return_value=None) + webapp.check_toggler = Mock(return_value=False) + webapp.check_hierarchy = Mock(return_value=False) + + webapp.click_tree("Item inexistente", right_click=False, position=1, tree_number=0) + + webapp.log_error.assert_called_once() + assert "Couldn't click on tree element" in webapp.log_error.call_args.args[0] + + +def test_click_tree_with_right_click_on_last_node(): + """Test that right_click=True is passed to click method when last item is clicked.""" + webapp, _ = build_webapp() + + node = FakeElement( + text="Menu Item", + attrs={"hidden": None, "closed": "", "hierarchy": "01", "icon": None}, + ) + clickable_part = FakeElement(text="Menu Item", parent=node) + + webapp.find_tree_bs = Mock(return_value=[node]) + webapp.element_is_displayed = Mock(return_value=True) + webapp.check_toggler = Mock(return_value=False) + webapp.check_hierarchy = Mock(return_value=False) + webapp.treenode_selected = Mock(return_value=node) + webapp.return_last_zindex = Mock(return_value=100) + + def execute_js_selector(selector, element, get_all=True): + if selector == ".toggler, .lastchild, .data, label": + return [clickable_part] + return None + + webapp.execute_js_selector = Mock(side_effect=execute_js_selector) + + webapp.click_tree("Menu Item", right_click=True, position=1, tree_number=0) + + # Verify that the main element was clicked (right_click=True path) + # The actual right_click happens in the loop after regular click + webapp.log_error.assert_not_called() + + +def test_click_tree_with_multiple_hierarchy_levels(): + """Test navigating through single level tree (simpler version of multi-level).""" + webapp, _ = build_webapp() + + pai = FakeElement(text="Pai", attrs={"hierarchy": "01", "closed": "", "icon": None}) + pai_clickable = FakeElement(text="Pai", parent=pai) + + webapp.find_tree_bs = Mock(return_value=[pai]) + webapp.element_is_displayed = Mock(return_value=True) + webapp.check_toggler = Mock(return_value=False) + webapp.check_hierarchy = Mock(return_value=False) + webapp.treenode_selected = Mock(return_value=pai) + + def execute_js_selector(selector, element, get_all=True): + if selector == ".toggler, .lastchild, .data, label": + return [pai_clickable] + return None + + webapp.execute_js_selector = Mock(side_effect=execute_js_selector) + + # Click the single level + webapp.click_tree("Pai", right_click=False, position=1, tree_number=0) + + # Verify successful click + webapp.log_error.assert_not_called() + + +def test_click_tree_respects_position_parameter(): + """Test that position parameter is considered when multiple elements exist.""" + webapp, _ = build_webapp() + + # Create multiple nodes with same text + node1 = FakeElement(text="Duplicado", attrs={"hidden": None, "closed": "", "hierarchy": "01"}) + node2 = FakeElement(text="Duplicado", attrs={"hidden": None, "closed": "", "hierarchy": "02"}) + + webapp.find_tree_bs = Mock(return_value=[node1, node2]) + webapp.element_is_displayed = Mock(return_value=True) + webapp.check_toggler = Mock(return_value=False) + webapp.check_hierarchy = Mock(return_value=False) + + # Return node2 when treenode_selected is called + webapp.treenode_selected = Mock(return_value=node2) + + def execute_js_selector(selector, element, get_all=True): + if selector == ".toggler, .lastchild, .data, label": + if element == node1: + return [FakeElement(text="Duplicado")] + elif element == node2: + return [FakeElement(text="Duplicado")] + return None + + webapp.execute_js_selector = Mock(side_effect=execute_js_selector) + + # Position 2 should try to select the second matching node + webapp.click_tree("Duplicado", right_click=False, position=2, tree_number=0) + + # Verify successful click + webapp.log_error.assert_not_called() + + +def test_click_tree_updates_tree_base_element_for_non_last_items(): + """Test that tree_base_element is updated when navigating intermediate nodes.""" + webapp, _ = build_webapp() + webapp.tree_base_element = () + + pai = FakeElement(text="Pai", attrs={"hierarchy": "01", "closed": "true", "icon": None}) + filho = FakeElement(text="Filho", attrs={"hierarchy": "01-01", "closed": "", "icon": None}) + pai_clickable = FakeElement(text="Pai", parent=pai) + + webapp.find_tree_bs = Mock(return_value=[pai, filho]) + webapp.element_is_displayed = Mock(return_value=True) + webapp.check_toggler = Mock(return_value=True) + webapp.check_hierarchy = Mock(side_effect=[True, False]) + webapp.treenode_selected = Mock(side_effect=[None, pai, filho]) + + def execute_js_selector(selector, element, get_all=True): + if selector == ".toggler, .lastchild, .data, label": + return [pai_clickable] + return None + + webapp.execute_js_selector = Mock(side_effect=execute_js_selector) + + webapp.click_tree("Pai > Filho", right_click=False, position=1, tree_number=0) + + # Verify tree_base_element was set to (label_filtered, element_class_item) + assert webapp.tree_base_element != () + assert webapp.tree_base_element[0] == "pai" # label_filtered is lowercased + + +def test_click_tree_with_hidden_element(): + """Test that hidden elements are filtered out during element search.""" + webapp, _ = build_webapp() + + hidden_node = FakeElement(text="Hidden", attrs={"hidden": "true", "closed": "", "hierarchy": "01"}) + visible_node = FakeElement(text="Visible", attrs={"hidden": None, "closed": "", "hierarchy": "02"}) + clickable = FakeElement(text="Visible", parent=visible_node) + + webapp.find_tree_bs = Mock(return_value=[hidden_node, visible_node]) + + def element_is_displayed_side_effect(element): + # hidden_node has hidden attribute, so it's not displayed + if element == hidden_node: + return False + return True + + webapp.element_is_displayed = Mock(side_effect=element_is_displayed_side_effect) + webapp.check_toggler = Mock(return_value=False) + webapp.check_hierarchy = Mock(return_value=False) + webapp.treenode_selected = Mock(return_value=visible_node) + + def execute_js_selector(selector, element, get_all=True): + if selector == ".toggler, .lastchild, .data, label": + return [clickable] + return None + + webapp.execute_js_selector = Mock(side_effect=execute_js_selector) + + webapp.click_tree("Visible", right_click=False, position=1, tree_number=0) + + # Verify hidden element was skipped (log_error not called) + webapp.log_error.assert_not_called() + + +def test_click_tree_handles_text_normalization(): + """Test that extra spaces in labels are normalized during matching.""" + webapp, _ = build_webapp() + + node = FakeElement( + text=" Item com espaços ", + attrs={"hidden": None, "closed": "", "hierarchy": "01", "icon": None}, + ) + clickable_part = FakeElement(text="Item com espaços", parent=node) + + webapp.find_tree_bs = Mock(return_value=[node]) + webapp.element_is_displayed = Mock(return_value=True) + webapp.check_toggler = Mock(return_value=False) + webapp.check_hierarchy = Mock(return_value=False) + webapp.treenode_selected = Mock(return_value=node) + + def execute_js_selector(selector, element, get_all=True): + if selector == ".toggler, .lastchild, .data, label": + return [clickable_part] + return None + + webapp.execute_js_selector = Mock(side_effect=execute_js_selector) + + # Search with single spaces should match label with multiple spaces + webapp.click_tree("Item com espaços", right_click=False, position=1, tree_number=0) + + # Verify normalization worked (no error) + webapp.log_error.assert_not_called() + + +def test_click_tree_tree_number_parameter(): + """Test that tree_number parameter is used to locate the correct tree.""" + webapp, _ = build_webapp() + + tree_node = FakeElement(text="Tree2Item", attrs={"hidden": None, "closed": "", "hierarchy": "02"}) + clickable = FakeElement(text="Tree2Item", parent=tree_node) + + # find_tree_bs is called with tree_number to select the correct tree + webapp.find_tree_bs = Mock(return_value=[tree_node]) + webapp.element_is_displayed = Mock(return_value=True) + webapp.check_toggler = Mock(return_value=False) + webapp.check_hierarchy = Mock(return_value=False) + webapp.treenode_selected = Mock(return_value=tree_node) + + def execute_js_selector(selector, element, get_all=True): + if selector == ".toggler, .lastchild, .data, label": + return [clickable] + return None + + webapp.execute_js_selector = Mock(side_effect=execute_js_selector) + + # Use tree_number=2 (should be converted to index 1) + webapp.click_tree("Tree2Item", right_click=False, position=1, tree_number=2) + + # Verify find_tree_bs was called with the correct tree_number + assert webapp.find_tree_bs.called + # Verify successful click (no error logged) + webapp.log_error.assert_not_called() diff --git a/tir/technologies/poui_internal.py b/tir/technologies/poui_internal.py index e6032cd34..41769d840 100644 --- a/tir/technologies/poui_internal.py +++ b/tir/technologies/poui_internal.py @@ -3740,20 +3740,38 @@ def return_index_element(self, element): if hasattr(element.find_parent('div', {'tabindex': '-1'}), 'attr'): return element if element.find_parent('div', {'tabindex': '-1'}) else None - def po_loading(self, selector): + def _po_loading(self, selector: str = '') -> None: """ - - :return: + [Internal] + + Waits for po-loading component to disappear, indicating that the page loading is complete. + + Searches within a specified container (or entire body if not specified) and waits for + the po-loading element to be absent from the DOM. + + :param selector: CSS selector of the container to monitor. If empty, searches entire body. - **Default:** '' (empty string) + :type selector: str + + :return: None + :rtype: None + + Usage: + + >>> # Wait for loading to complete in entire page + >>> self._po_loading() + >>> # Wait for loading to complete within a specific dialog + >>> self._po_loading('wa-dialog') """ - loading = True - endtime = time.time() + 300 - while loading and time.time() < endtime: - container = self.web_scrap(term=selector, scrap_type=enum.ScrapType.CSS_SELECTOR, - main_container='body') + logger().info("Waiting loading...") - loading = True if list(filter(lambda x: x.select('po-loading'), container)) else False + main_container = selector or 'body' + self.wait_element(term='po-loading', scrap_type=enum.ScrapType.CSS_SELECTOR, + presence=False, main_container=main_container) + + logger().info("Loading finished!") + def click_select(self, field, value, position): """ @@ -4503,7 +4521,7 @@ def return_table(self, selector, table_number): table_number -= 1 - self.po_loading(selector='wa-dialog') + self._po_loading() self.wait_element(term=selector, scrap_type=enum.ScrapType.CSS_SELECTOR) @@ -5364,7 +5382,7 @@ def set_program(self, program_name: str = "", program_desc: str = ""): main_container='body')), None) self.InputValue(self.language.input_set_program, program_name or program_desc, 1, exec_enter_tab=False) - self.po_loading(self.containers_selectors['GetCurrentContainer']) + self._po_loading() if not program_name and program_desc: self.config.routine = self._get_program_by_desc(program_desc) self.click_po_list_box(value=program_desc, second_value=program_name, program_call=True) @@ -5652,6 +5670,8 @@ def _set_browse_filters(self, filters): >>> self._filter_thf_browse(filters=[{'name': 'John'}], browse_div=browse_element) """ + self._po_loading() + self._remove_filters_from_browse() self.click_button(self.language.filters) @@ -5718,7 +5738,7 @@ def _remove_filters_from_browse(self): clickable = po_tag_filter.select_one('.po-tag-wrapper.po-clickable') target = clickable if clickable else po_tag_filter self.poui_click(target) - self.po_loading(self.containers_selectors['GetCurrentContainer']) + self._po_loading() else: logger().debug("No 'Remove Filters' found; skipping filter removal.") @@ -5923,7 +5943,8 @@ def SetButton(self, button, sub_item="", position=1, check_error=True): :type check_error: bool """ - logger().info("Switching to the POUI button-click method") + self._po_loading() + button_normalized = str(button).lower().strip() if button is not None else "" sub_item_normalized = str(sub_item).lower().strip() if sub_item is not None else "" diff --git a/tir/technologies/webapp_internal.py b/tir/technologies/webapp_internal.py index bf2928418..3d8a5a3a4 100644 --- a/tir/technologies/webapp_internal.py +++ b/tir/technologies/webapp_internal.py @@ -2172,13 +2172,11 @@ def _simple_search_thf_browse(self, search_text, browse_div): self.log_error(f"_simple_search_thf_browse: couldn't fill search input with value '{search_text}'") return - def _is_new_browse(self, throw_error=True, timeout=None): browse_div = self._find_search_browse(throw_error=throw_error, timeout=timeout) return browse_div.name == 'thf-grid' if browse_div else False - def longest_word(self, string): words = string.split() @@ -9638,6 +9636,7 @@ def click_tree(self, treepath, right_click, position, tree_number): logger().info(f"Clicking on Tree: {treepath}") hierarchy = None + elements = [] position -= 1 tree_number = tree_number-1 if tree_number > 0 else 0 @@ -9672,36 +9671,36 @@ def click_tree(self, treepath, right_click, position, tree_number): # Get tree node tree_node = self.find_tree_bs(label_filtered, tree_number) - # Filter out hidden nodes - non_hidden_tree_nodes = list(filter(lambda x: not x.get_attribute('hidden'), tree_node)) - # Filter node elements matching the label displayed - filtered_nodes = list( - filter(lambda x: label_filtered in re.sub(r'[ ]{2,}', ' ', x.text).lower().strip() and self.element_is_displayed(x), - non_hidden_tree_nodes)) + # Filter node elements matching the label and displayed and not hidden + filtered_nodes = list(filter( + lambda x: label_filtered in re.sub(r'[ ]{2,}', ' ', x.text).lower().strip() and + self.element_is_displayed(x) and + not x.get_attribute('hidden') + , tree_node)) if filtered_nodes: if position: elements = filtered_nodes[position] if len(filtered_nodes) >= position + 1 else next(iter(filtered_nodes)) - if hierarchy: - elements = elements if elements.attrs['hierarchy'].startswith(hierarchy) and elements.attrs[ - 'hierarchy'] != hierarchy else None + elements = [elements] else: - elements = list(filter(lambda x: self.element_is_displayed(x), filtered_nodes)) + elements = filtered_nodes - if hierarchy: - if not self.webapp_shadowroot(): - elements = list(filter(lambda x: x.attrs['hierarchy'].startswith(hierarchy) and x.attrs[ - 'hierarchy'] != hierarchy, filtered_nodes)) + if hierarchy: + elements = list(filter( + lambda x: x.get_attribute('hierarchy') and + x.get_attribute('hierarchy').startswith(hierarchy) and + x.get_attribute('hierarchy') != hierarchy + , elements)) for element in elements: if not success: # get node elements to click - element_class = self.execute_js_selector('.toggler, .lastchild, .data, label', - element, get_all=True) + element_class = self.execute_js_selector('.toggler, .lastchild, .data, label', element, get_all=True) + if not element_class: - element_class = self.execute_js_selector('.icon', - element, get_all=True) + element_class = self.execute_js_selector('.icon', element, get_all=True) + if not element_class: if element.get_attribute('icon') != None: element_class = [element] @@ -9710,79 +9709,59 @@ def click_tree(self, treepath, right_click, position, tree_number): if not success: try: element_click = lambda: element_class_item + element_is_closed = lambda: (element.get_attribute('closed') or '').strip().lower() in ('', 'true') + element_is_selected = lambda: element == self.treenode_selected(label_filtered, tree_number) if last_item: - self.wait_blocker() - if self.webapp_shadowroot(): - element_is_closed = lambda: element.get_attribute('closed') == 'true' or element.get_attribute('closed') == '' - treenode_selected = lambda: self.treenode_selected(label_filtered, tree_number) - click_try = 0 - is_element_acessible = lambda: not element_is_closed() if self.check_toggler(label_filtered, element) else treenode_selected() - - while click_try < 3 and not is_element_acessible(): - self.scroll_to_element(element_click()) - element_click().click() - click_try += 1 + # Last node: click the item itself. + self.wait_blocker() - success = self.check_hierarchy(label_filtered, False) - - if not success: - success = True if is_element_acessible() else False - - # If dialog layers show up through last click - if not success and dialog_layers < self.check_layers('wa-dialog'): - success = True - - if success and right_click: - last_zindex = self.return_last_zindex() - current_zindex = last_zindex - - endtime_right_click = time.time() + self.config.time_out / 3 - while time.time() < endtime_right_click and last_zindex <= current_zindex: - if self.webapp_shadowroot(): - self.click(element_click(), enum.ClickType.SELENIUM, - right_click) - current_zindex = self.return_last_zindex() - else: - self.send_action(action=self.click, element=element_click, right_click=right_click) - else: + is_element_acessible = lambda: not element_is_closed() if self.check_toggler(label_filtered, element) else element_is_selected() + + click_try = 0 + while click_try < 3 and not is_element_acessible(): self.scroll_to_element(element_click()) element_click().click() - if self.check_toggler(label_filtered, element): - success = self.check_hierarchy(label_filtered) - if success and right_click: - self.send_action(action=self.click, element=element_click, right_click=right_click) - else: - if right_click: - self.send_action(action=self.click, element=element_click, - right_click=right_click) - success = self.clicktree_status_selected(label_filtered) + click_try += 1 + + success = self.check_hierarchy(label_filtered, False) or is_element_acessible() + + # If dialog layers show up through last click + if not success and dialog_layers < self.check_layers('wa-dialog'): + success = True + + if success and right_click: + last_zindex = self.return_last_zindex() + current_zindex = last_zindex + check_popup = lambda: self.element_exists(term=".tmenupopup, wa-menu-popup-item", + scrap_type=enum.ScrapType.CSS_SELECTOR, + main_container="body", check_error=False) + + endtime_right_click = time.time() + self.config.time_out / 3 + while time.time() < endtime_right_click and (last_zindex >= current_zindex and not check_popup()): + self.click(element_click(), enum.ClickType.SELENIUM, right_click) + current_zindex = self.return_last_zindex() else: - if self.webapp_shadowroot(): - self.tree_base_element = label_filtered, element_class_item - element_is_closed = lambda: element.get_attribute('closed') == 'true' or element.get_attribute('closed') == '' or not self.treenode_selected(label_filtered, tree_number) - self.scroll_to_element(element_click()) + # Intermediate node: expand to reach children. + self.tree_base_element = label_filtered, element_class_item + self.scroll_to_element(element_click()) - click_try = 0 - while click_try < 3 and element_is_closed(): - if (element.get_attribute('closed') == 'true' or - element.get_attribute('closed') == ''): - element_click().click() + click_try = 0 + while click_try < 3 and (element_is_closed() or not element_is_selected()): + if element_is_closed(): + element_click().click() - element_closed_click = self.execute_js_selector(".toggler, .lastchild, .data", element_click(), get_all=False) + element_closed_click = self.execute_js_selector(".toggler, .lastchild, .data", element_click(), get_all=False) - if element_closed_click: - element_closed_click.click() + if element_closed_click: + element_closed_click.click() - click_try += 1 - else: - self.tree_base_element = label_filtered, self.soup_to_selenium(element_class_item) - self.scroll_to_element(element_click()) - element_click().click() + click_try += 1 + success = self.check_hierarchy(label_filtered) try_counter += 1 - except: + except Exception as e: pass if not success: @@ -9797,10 +9776,7 @@ def click_tree(self, treepath, right_click, position, tree_number): if not last_item: treenode_selected = self.treenode_selected(label_filtered, tree_number) if treenode_selected: - if self.webapp_shadowroot(): - hierarchy = treenode_selected.get_attribute('hierarchy') - else: - hierarchy = treenode_selected.attrs['hierarchy'] + hierarchy = treenode_selected.get_attribute('hierarchy') if not success: self.log_error(f"Couldn't click on tree element {label}.") @@ -9815,10 +9791,7 @@ def find_tree_bs(self, label, tree_number): tree_node = "" - if self.webapp_shadowroot(): - self.wait_element(term=label, scrap_type=enum.ScrapType.MIXED, optional_term=".dict-ttree") - else: - self.wait_element(term=label, scrap_type=enum.ScrapType.MIXED, optional_term=".ttreenode, .data") + self.wait_element(term=label, scrap_type=enum.ScrapType.MIXED, optional_term=".dict-ttree") endtime = time.time() + self.config.time_out @@ -9826,14 +9799,11 @@ def find_tree_bs(self, label, tree_number): container = self.get_current_container() - if self.webapp_shadowroot(): - tree = container.select("wa-tree") - if len(tree) >= tree_number: - tree = tree[tree_number] - tree_node = self.execute_js_selector('wa-tree-node', - self.soup_to_selenium(tree), get_all=True) - else: - tree_node = container.select(".ttreenode") + tree = container.select("wa-tree") + if len(tree) >= tree_number: + tree = tree[tree_number] + tree_node = self.execute_js_selector('wa-tree-node', + self.soup_to_selenium(tree), get_all=True) if not tree_node: self.log_error("Couldn't find tree element.") @@ -9850,57 +9820,16 @@ def clicktree_status_selected(self, label_filtered, check_expanded=False): treenode_selected = self.treenode_selected(label_filtered) if not check_expanded: - if treenode_selected: - return True - else: - return False + return True if treenode_selected else False else: - if self.webapp_shadowroot(): - tree_selected = self.execute_js_selector('span[class~=toggler]', treenode_selected, get_all=False) - if tree_selected: - return not treenode_selected.get_attribute('closed') - else: - tree_selected = next(iter(list(filter(lambda x: label_filtered == x.text.lower().strip(), treenode_selected))), None) - if tree_selected.find_all_next("span"): - if "toggler" in next(iter(tree_selected.find_all_next("span"))).attrs['class']: - return "expanded" in next(iter(tree_selected.find_all_next("span")), None).attrs['class'] - else: - return False + tree_selected = self.execute_js_selector('span[class~=toggler]', treenode_selected, get_all=False) + if tree_selected: + return not treenode_selected.get_attribute('closed') def check_toggler(self, label_filtered, element): """ [Internal] """ - - if self.webapp_shadowroot: - return self.check_toggler_shadow(element) - - element_id = element.get_attribute_list('id') - tree_selected = self.treenode_selected(label_filtered) - - if tree_selected: - if tree_selected.find_all_next("span"): - first_span = next(iter(tree_selected.find_all_next("span"))).find_parent('tr') - if first_span: - if next(iter(element_id)) == next(iter(first_span.get_attribute_list('id'))): - try: - return "toggler" in next(iter(tree_selected.find_all_next("span")), None).attrs['class'] - except: - return False - else: - return False - else: - return False - else: - return False - else: - return False - - def check_toggler_shadow(self, element): - """ - [Internal] - """ - return True if self.execute_js_selector('span[class~=toggler]', element, get_all=False) else False @@ -9912,10 +9841,7 @@ def treenode_selected(self, label_filtered, tree_number=0): ttreenode = self.treenode(tree_number) - if self.webapp_shadowroot(): - treenode_selected = list(filter(lambda x: "selected" in x.get_attribute('class') or x.get_attribute('selected'), ttreenode)) - else: - treenode_selected = list(filter(lambda x: "selected" in x.attrs['class'], ttreenode)) + treenode_selected = list(filter(lambda x: "selected" in x.get_attribute('class') or x.get_attribute('selected'), ttreenode)) return next(iter(list(filter(lambda x: label_filtered == re.sub(r'[ ]{2,}', ' ', x.text).lower().strip(), treenode_selected))), None) @@ -9935,15 +9861,10 @@ def treenode(self, tree_number=0): container = self.get_current_container() tr = [] - if self.webapp_shadowroot(): - bs_tree_node = container.select('wa-tree') - if bs_tree_node and len(bs_tree_node) > tree_number: - tr = self.driver.execute_script(f"return arguments[0].shadowRoot.querySelectorAll('wa-tree-node')", self.soup_to_selenium(bs_tree_node[tree_number])) - return tr - else: - tr = container.select("tr") - tr_class = list(filter(lambda x: "class" in x.attrs, tr)) - return list(filter(lambda x: "ttreenode" in x.attrs['class'], tr_class)) + bs_tree_node = container.select('wa-tree') + if bs_tree_node and len(bs_tree_node) > tree_number: + tr = self.driver.execute_script(f"return arguments[0].shadowRoot.querySelectorAll('wa-tree-node')", self.soup_to_selenium(bs_tree_node[tree_number])) + return tr def check_hierarchy(self, label, check_expanded=True): """ @@ -9964,25 +9885,14 @@ def check_hierarchy(self, label, check_expanded=True): node_check = None - if self.webapp_shadowroot(): - while (counter <= 3 and not node_check): - treenode_parent_id = self.treenode_selected(label) - if treenode_parent_id: - treenode_parent_id = treenode_parent_id.get_attribute('id') - treenode = list(filter(lambda x: self.element_is_displayed(x), self.treenode())) - node_check = next(iter(list(filter(lambda x: treenode_parent_id == x.get_attribute('parentid'), - treenode))), None) - counter += 1 - else: - while (counter <= 3 and not node_check): - - treenode_parent_id = self.treenode_selected(label).attrs['id'] - + while (counter <= 3 and not node_check): + treenode_parent_id = self.treenode_selected(label) + if treenode_parent_id: + treenode_parent_id = treenode_parent_id.get_attribute('id') treenode = list(filter(lambda x: self.element_is_displayed(x), self.treenode())) - - node_check = next(iter(list(filter(lambda x: treenode_parent_id == x.attrs['parentid'], treenode))), None) - - counter += 1 + node_check = next(iter(list(filter(lambda x: treenode_parent_id == x.get_attribute('parentid'), + treenode))), None) + counter += 1 return True if node_check else self.clicktree_status_selected(label, check_expanded) diff --git a/tir/version.py b/tir/version.py index f0e5e1eae..853f8a60a 100644 --- a/tir/version.py +++ b/tir/version.py @@ -1 +1 @@ -__version__ = '2.6.0' +__version__ = '2.7.0rc1'