From afbc691d948a35d31ddc82138660a9a75ed5970a Mon Sep 17 00:00:00 2001 From: Vinicius Oliveira Date: Wed, 4 Mar 2026 17:35:06 -0300 Subject: [PATCH 01/15] add additional PAGE_DOWN action in WebappInternal class for improved grid navigation --- tir/technologies/webapp_internal.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tir/technologies/webapp_internal.py b/tir/technologies/webapp_internal.py index acc0b7d2..f5459165 100644 --- a/tir/technologies/webapp_internal.py +++ b/tir/technologies/webapp_internal.py @@ -7086,6 +7086,7 @@ def get_obscure_gridline(self, grid, row_num=0): row_list = list(filter(lambda x: x.text == before_texts[row_num], grid_lines())) break + ActionChains(self.driver).key_down(Keys.PAGE_DOWN).perform() ActionChains(self.driver).key_down(Keys.PAGE_DOWN).perform() down_count += 1 self.wait_blocker() @@ -9623,6 +9624,7 @@ def lenght_grid_lines(self, grid): if i not in before_texts: before_texts.append(i) + ActionChains(self.driver).key_down(Keys.PAGE_DOWN).perform() ActionChains(self.driver).key_down(Keys.PAGE_DOWN).perform() down_count += 1 self.wait_blocker() From 9172fda7b40e4a965974640e8669d314e7af97b5 Mon Sep 17 00:00:00 2001 From: Vinicius Oliveira Date: Thu, 5 Mar 2026 08:27:03 -0300 Subject: [PATCH 02/15] Add log and update the send_action function --- tir/technologies/webapp_internal.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/tir/technologies/webapp_internal.py b/tir/technologies/webapp_internal.py index f5459165..011f90ac 100644 --- a/tir/technologies/webapp_internal.py +++ b/tir/technologies/webapp_internal.py @@ -7071,6 +7071,7 @@ def get_obscure_gridline(self, grid, row_num=0): before_texts = list(map(lambda x: x.text, before_texts)) after_texts = [] down_count = 0 + logger().debug(f"Starting search for row {row_num+1}. Initial visible lines: {len(before_texts)}") if grid_lines(): self.send_action(action=self.click, element=lambda: next(iter(grid_lines())), click_type=3) endtime = time.time() + self.config.time_out @@ -7084,14 +7085,18 @@ def get_obscure_gridline(self, grid, row_num=0): if len(before_texts) > row_num: row_list = list(filter(lambda x: x.text == before_texts[row_num], grid_lines())) + logger().debug(f"Row found, ending search") break ActionChains(self.driver).key_down(Keys.PAGE_DOWN).perform() + down_count += 1 ActionChains(self.driver).key_down(Keys.PAGE_DOWN).perform() down_count += 1 self.wait_blocker() after_texts = list(map(lambda x: x.text, grid_lines())) + + logger().debug(f"Search completed. Total lines collected: {len(before_texts)}, down_count: {down_count}") return next(iter(row_list), None), down_count @@ -9613,6 +9618,7 @@ def lenght_grid_lines(self, grid): before_texts = list(map(lambda x: x.text, before_texts)) after_texts = [] down_count = 0 + logger().debug(f"Starting line count. Initial visible lines: {len(before_texts)}") if grid_lines(): self.send_action(action=self.click, element=lambda: next(iter(grid_lines())), click_type=3) ActionChains(self.driver).key_down(Keys.SHIFT).key_down(Keys.HOME).perform() @@ -9625,6 +9631,7 @@ def lenght_grid_lines(self, grid): before_texts.append(i) ActionChains(self.driver).key_down(Keys.PAGE_DOWN).perform() + down_count += 1 ActionChains(self.driver).key_down(Keys.PAGE_DOWN).perform() down_count += 1 self.wait_blocker() @@ -9633,6 +9640,7 @@ def lenght_grid_lines(self, grid): ActionChains(self.driver).key_down(Keys.SHIFT).key_down(Keys.HOME).perform() + logger().debug(f"Count completed. Total lines: {len(before_texts)}, down_count: {down_count}") return len(before_texts) else: return len(grid.select("tbody tr")) @@ -10614,6 +10622,9 @@ def send_action(self, action = None, element = None, value = None, right_click=F parent_classes_before = self.get_active_parent_class(element) parent_classes_after = parent_classes_before + children_classes_before = self.get_active_children_classes(element) + children_classes_after = children_classes_before + classes_before = '' classes_after = classes_before @@ -10631,7 +10642,11 @@ def send_action(self, action = None, element = None, value = None, right_click=F endtime = time.time() + self.config.time_out try: - while ((time.time() < endtime) and (soup_before_event == soup_after_event) and (parent_classes_before == parent_classes_after) and (classes_before == classes_after) ): + while ((time.time() < endtime) and \ + (soup_before_event == soup_after_event) and \ + (parent_classes_before == parent_classes_after) and \ + (children_classes_before == children_classes_after) and \ + (classes_before == classes_after) ): logger().debug(f"Trying to send action") if right_click: soup_select = self.get_soup_select(".tmenupopupitem, wa-menu-popup-item") @@ -10656,6 +10671,7 @@ def send_action(self, action = None, element = None, value = None, right_click=F soup_after_event = self.get_current_DOM(twebview=twebview) parent_classes_after = self.get_active_parent_class(element) + children_classes_after = self.get_active_children_classes(element) if element: classes_after = self.get_selenium_attribute(element(), 'class') @@ -10674,11 +10690,12 @@ def send_action(self, action = None, element = None, value = None, right_click=F return False if self.config.smart_test or self.config.debug_log: - logger().debug(f"send_action method result = {soup_before_event != soup_after_event}") - logger().debug(f'send_action selenium status: {parent_classes_before != parent_classes_after}') + logger().debug(f"send_action soup = {soup_before_event != soup_after_event}") + logger().debug(f'send_action parent_classes: {parent_classes_before != parent_classes_after}') + logger().debug(f'send_action children_classes: {children_classes_before != children_classes_after}') + logger().debug(f'send_action classes: {classes_before == classes_after}') return soup_before_event != soup_after_event - def get_selenium_attribute(self, element, attribute): try: return element.get_attribute(attribute) From a3ba28375069cad604f7b539164afa936a0601f4 Mon Sep 17 00:00:00 2001 From: Vinicius Oliveira Date: Thu, 5 Mar 2026 10:36:10 -0300 Subject: [PATCH 03/15] Enhance grid navigation by adding checks for selected cells and improving line counting logic --- tir/technologies/webapp_internal.py | 61 ++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 10 deletions(-) diff --git a/tir/technologies/webapp_internal.py b/tir/technologies/webapp_internal.py index 011f90ac..1ef8d8f7 100644 --- a/tir/technologies/webapp_internal.py +++ b/tir/technologies/webapp_internal.py @@ -7071,11 +7071,20 @@ def get_obscure_gridline(self, grid, row_num=0): before_texts = list(map(lambda x: x.text, before_texts)) after_texts = [] down_count = 0 + last_line_selected = False logger().debug(f"Starting search for row {row_num+1}. Initial visible lines: {len(before_texts)}") if grid_lines(): - self.send_action(action=self.click, element=lambda: next(iter(grid_lines())), click_type=3) - endtime = time.time() + self.config.time_out - while endtime > time.time() and next(reversed(after_texts), None) != next(reversed(before_texts), None): + first_line = lambda: next(iter(grid_lines())) + last_line = lambda: next(reversed(grid_lines())) + + if not first_line().find_elements(By.CSS_SELECTOR, "td.selected-cell"): + self.send_action(action=self.click, element=lambda: first_line(), click_type=3) + ActionChains(self.driver).key_down(Keys.SHIFT).key_down(Keys.HOME).perform() + + endtime = time.time() + self.config.time_out + while endtime > time.time() and \ + next(reversed(after_texts), None) != next(reversed(before_texts), None) and \ + not last_line_selected: after_texts = list(map(lambda x: x.text, grid_lines())) @@ -7088,13 +7097,13 @@ def get_obscure_gridline(self, grid, row_num=0): logger().debug(f"Row found, ending search") break - ActionChains(self.driver).key_down(Keys.PAGE_DOWN).perform() - down_count += 1 ActionChains(self.driver).key_down(Keys.PAGE_DOWN).perform() down_count += 1 self.wait_blocker() after_texts = list(map(lambda x: x.text, grid_lines())) + if last_line().find_elements(By.CSS_SELECTOR, "td.selected-cell"): + last_line_selected = True logger().debug(f"Search completed. Total lines collected: {len(before_texts)}, down_count: {down_count}") @@ -9618,25 +9627,33 @@ def lenght_grid_lines(self, grid): before_texts = list(map(lambda x: x.text, before_texts)) after_texts = [] down_count = 0 + last_line_selected = False logger().debug(f"Starting line count. Initial visible lines: {len(before_texts)}") if grid_lines(): - self.send_action(action=self.click, element=lambda: next(iter(grid_lines())), click_type=3) + first_line = lambda: next(iter(grid_lines())) + last_line = lambda: next(reversed(grid_lines())) + + if not first_line().find_elements(By.CSS_SELECTOR, "td.selected-cell"): + self.send_action(action=self.click, element=lambda: first_line(), click_type=3) ActionChains(self.driver).key_down(Keys.SHIFT).key_down(Keys.HOME).perform() + endtime = time.time() + self.config.time_out - while endtime > time.time() and next(reversed(after_texts), None) != next(reversed(before_texts), None): + while endtime > time.time() and \ + next(reversed(after_texts), None) != next(reversed(before_texts), None) and \ + not last_line_selected: after_texts = list(map(lambda x: x.text, grid_lines())) for i in after_texts: if i not in before_texts: before_texts.append(i) - ActionChains(self.driver).key_down(Keys.PAGE_DOWN).perform() - down_count += 1 ActionChains(self.driver).key_down(Keys.PAGE_DOWN).perform() down_count += 1 self.wait_blocker() after_texts = list(map(lambda x: x.text, grid_lines())) + if last_line().find_elements(By.CSS_SELECTOR, "td.selected-cell"): + last_line_selected = True ActionChains(self.driver).key_down(Keys.SHIFT).key_down(Keys.HOME).perform() @@ -10718,7 +10735,31 @@ def get_active_parent_class(self, element=None): if self.config.smart_test or self.config.debug_log: logger().exception(f"Warning Exception get_active_parent_class: {str(e)}") - + def get_active_children_classes(self, element=None): + """ + Returns class list of all descendants of an element + """ + try: + if element: + from selenium.webdriver.common.by import By + # Pega todos os descendentes do elemento + descendants = element().find_elements(By.XPATH, ".//*") + # Coleta as classes de cada descendente + classes_list = [] + for descendant in descendants: + try: + class_attr = descendant.get_attribute('class') + if class_attr: + classes_list.append(class_attr) + except StaleElementReferenceException: + continue + return classes_list + return [] + except Exception as e: + if self.config.smart_test or self.config.debug_log: + logger().debug(f"Warning Exception get_active_children_classes {str(e)}") + return [] + def image_compare(self, img1, img2): """ Returns differences between 2 images in Gray Scale. From ff2b1184c39a944b319a547ee1ead6e0c751ad34 Mon Sep 17 00:00:00 2001 From: Vinicius Oliveira Date: Thu, 5 Mar 2026 16:40:31 -0300 Subject: [PATCH 04/15] Add has_selected_cell method and improve line selection logic in WebappInternal class --- tir/technologies/webapp_internal.py | 42 +++++++++++++++++++---------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/tir/technologies/webapp_internal.py b/tir/technologies/webapp_internal.py index 1ef8d8f7..986e933d 100644 --- a/tir/technologies/webapp_internal.py +++ b/tir/technologies/webapp_internal.py @@ -6913,6 +6913,16 @@ def selected_cell(self, element): """ return element.get_attribute('class').lower().strip() == 'selected-cell' + def has_selected_cell(self, row_element): + """ + [Internal] + Checks if a row element has any selected cell. + + :param element: Row element to check + :return: True if row has a selected cell, False otherwise + """ + return bool(row_element.find_elements(By.CSS_SELECTOR, "td.selected-cell")) + def get_selenium_column_element(self, xpath): """ [Internal] @@ -7072,14 +7082,18 @@ def get_obscure_gridline(self, grid, row_num=0): after_texts = [] down_count = 0 last_line_selected = False + logger().debug(f"Starting search for row {row_num+1}. Initial visible lines: {len(before_texts)}") if grid_lines(): first_line = lambda: next(iter(grid_lines())) last_line = lambda: next(reversed(grid_lines())) - - if not first_line().find_elements(By.CSS_SELECTOR, "td.selected-cell"): - self.send_action(action=self.click, element=lambda: first_line(), click_type=3) - ActionChains(self.driver).key_down(Keys.SHIFT).key_down(Keys.HOME).perform() + + endtime = time.time() + self.config.time_out + success = False + while endtime > time.time() and not success: + self.click(element=first_line(), click_type=enum.ClickType(3)) + ActionChains(self.driver).key_down(Keys.SHIFT).key_down(Keys.HOME).perform() + success = self.has_selected_cell(row_element=first_line()) endtime = time.time() + self.config.time_out while endtime > time.time() and \ @@ -7102,8 +7116,7 @@ def get_obscure_gridline(self, grid, row_num=0): self.wait_blocker() after_texts = list(map(lambda x: x.text, grid_lines())) - if last_line().find_elements(By.CSS_SELECTOR, "td.selected-cell"): - last_line_selected = True + last_line_selected = self.has_selected_cell(row_element=last_line()) logger().debug(f"Search completed. Total lines collected: {len(before_texts)}, down_count: {down_count}") @@ -9628,15 +9641,19 @@ def lenght_grid_lines(self, grid): after_texts = [] down_count = 0 last_line_selected = False + logger().debug(f"Starting line count. Initial visible lines: {len(before_texts)}") if grid_lines(): first_line = lambda: next(iter(grid_lines())) last_line = lambda: next(reversed(grid_lines())) - if not first_line().find_elements(By.CSS_SELECTOR, "td.selected-cell"): - self.send_action(action=self.click, element=lambda: first_line(), click_type=3) - ActionChains(self.driver).key_down(Keys.SHIFT).key_down(Keys.HOME).perform() - + endtime = time.time() + self.config.time_out + success = False + while endtime > time.time() and not success: + self.click(element=first_line(), click_type=enum.ClickType(3)) + ActionChains(self.driver).key_down(Keys.SHIFT).key_down(Keys.HOME).perform() + success = self.has_selected_cell(row_element=first_line()) + endtime = time.time() + self.config.time_out while endtime > time.time() and \ next(reversed(after_texts), None) != next(reversed(before_texts), None) and \ @@ -9652,8 +9669,7 @@ def lenght_grid_lines(self, grid): self.wait_blocker() after_texts = list(map(lambda x: x.text, grid_lines())) - if last_line().find_elements(By.CSS_SELECTOR, "td.selected-cell"): - last_line_selected = True + last_line_selected = self.has_selected_cell(row_element=last_line()) ActionChains(self.driver).key_down(Keys.SHIFT).key_down(Keys.HOME).perform() @@ -10742,9 +10758,7 @@ def get_active_children_classes(self, element=None): try: if element: from selenium.webdriver.common.by import By - # Pega todos os descendentes do elemento descendants = element().find_elements(By.XPATH, ".//*") - # Coleta as classes de cada descendente classes_list = [] for descendant in descendants: try: From 35165664b3d52a82a49bd0b92840be5d1a42773e Mon Sep 17 00:00:00 2001 From: Vinicius Oliveira Date: Thu, 5 Mar 2026 17:58:10 -0300 Subject: [PATCH 05/15] Refactor send_action method to improve event comparison logic in WebappInternal class --- tir/technologies/webapp_internal.py | 60 ++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/tir/technologies/webapp_internal.py b/tir/technologies/webapp_internal.py index 986e933d..9dfba997 100644 --- a/tir/technologies/webapp_internal.py +++ b/tir/technologies/webapp_internal.py @@ -10652,6 +10652,9 @@ def send_action(self, action = None, element = None, value = None, right_click=F soup_before_event = self.get_current_DOM(twebview=twebview) soup_after_event = soup_before_event + shadow_roots_before = self.get_shadow_roots_content() + shadow_roots_after = shadow_roots_before + parent_classes_before = self.get_active_parent_class(element) parent_classes_after = parent_classes_before @@ -10677,6 +10680,7 @@ def send_action(self, action = None, element = None, value = None, right_click=F try: while ((time.time() < endtime) and \ (soup_before_event == soup_after_event) and \ + (shadow_roots_before == shadow_roots_after) and \ (parent_classes_before == parent_classes_after) and \ (children_classes_before == children_classes_after) and \ (classes_before == classes_after) ): @@ -10703,6 +10707,7 @@ def send_action(self, action = None, element = None, value = None, right_click=F else: soup_after_event = self.get_current_DOM(twebview=twebview) + shadow_roots_after = self.get_shadow_roots_content() parent_classes_after = self.get_active_parent_class(element) children_classes_after = self.get_active_children_classes(element) @@ -10723,11 +10728,17 @@ def send_action(self, action = None, element = None, value = None, right_click=F return False if self.config.smart_test or self.config.debug_log: - logger().debug(f"send_action soup = {soup_before_event != soup_after_event}") + logger().debug(f"send_action soup = {soup_before_event != soup_after_event}") + logger().debug(f'send_action shadow_roots: {shadow_roots_before != shadow_roots_after}') logger().debug(f'send_action parent_classes: {parent_classes_before != parent_classes_after}') logger().debug(f'send_action children_classes: {children_classes_before != children_classes_after}') logger().debug(f'send_action classes: {classes_before == classes_after}') - return soup_before_event != soup_after_event + + return ((soup_before_event != soup_after_event) or \ + (shadow_roots_before != shadow_roots_after) or \ + (parent_classes_before != parent_classes_after) or \ + (children_classes_before != children_classes_after) or \ + (classes_before != classes_after)) def get_selenium_attribute(self, element, attribute): try: @@ -10735,6 +10746,51 @@ def get_selenium_attribute(self, element, attribute): except StaleElementReferenceException: return None + def get_shadow_roots_content(self): + """ + [Internal] + Captures the content of all shadow roots in the current DOM. + Returns a dictionary with element identifiers and their shadow root HTML. + Cleans special characters like \n, \t, and multiple spaces. + """ + try: + script = """ + function getShadowRootsContent() { + const shadowElements = []; + const allElements = document.querySelectorAll('*'); + + allElements.forEach((el, index) => { + if (el.shadowRoot) { + // Get innerHTML and clean special characters + let innerHTML = el.shadowRoot.innerHTML; + // Remove line breaks, tabs, and normalize spaces + innerHTML = innerHTML + .replace(/\\n/g, '') + .replace(/\\t/g, '') + .replace(/\\r/g, '') + .replace(/\\s+/g, ' ') + .trim(); + + const elementInfo = { + tagName: el.tagName, + id: el.id || `shadow-${index}`, + className: el.className, + innerHTML: innerHTML + }; + shadowElements.push(elementInfo); + } + }); + + return JSON.stringify(shadowElements); + } + return getShadowRootsContent(); + """ + result = self.driver.execute_script(script) + return result if result else '[]' + except Exception as e: + if self.config.smart_test or self.config.debug_log: + logger().debug(f"Warning Exception get_shadow_roots_content {str(e)}") + return '[]' def get_active_parent_class(self, element=None): """ From 130209dc348ebd98c21a4b65c7f74c3d79a3cad9 Mon Sep 17 00:00:00 2001 From: Vinicius Oliveira Date: Fri, 6 Mar 2026 09:58:27 -0300 Subject: [PATCH 06/15] Refactor grid line collection logic to improve scrolling and row selection in WebappInternal class --- tir/technologies/webapp_internal.py | 201 ++++++++++++---------------- 1 file changed, 89 insertions(+), 112 deletions(-) diff --git a/tir/technologies/webapp_internal.py b/tir/technologies/webapp_internal.py index 9dfba997..4137a2f8 100644 --- a/tir/technologies/webapp_internal.py +++ b/tir/technologies/webapp_internal.py @@ -7065,63 +7065,79 @@ def get_status_color(self, sl_object): return colors[status] - def get_obscure_gridline(self, grid, row_num=0): + def _scroll_and_collect_grid_lines(self, grid, row_num=None): """ [Internal] - :param grid: - :param row_num: - :return obscured row based in row number: + Scrolls through grid and collects all line texts. + If row_num is provided, captures the row element when found. + Returns tuple: (before_texts, row_element, down_count) """ - grid_lines = None - row_list = [] + grid_lines = lambda: self.execute_js_selector('tbody tr', self.soup_to_selenium(grid)) + before_texts = list(filter(lambda x: hasattr(x, 'text'), grid_lines())) + before_texts = list(map(lambda x: x.text, before_texts)) + after_texts = [] + down_count = 0 + last_line_selected = False + row_element = None - if self.webapp_shadowroot(): - grid_lines = lambda: self.execute_js_selector('tbody tr', self.soup_to_selenium(grid)) - before_texts = list(filter(lambda x: hasattr(x, 'text'), grid_lines())) - before_texts = list(map(lambda x: x.text, before_texts)) - after_texts = [] - down_count = 0 - last_line_selected = False - - logger().debug(f"Starting search for row {row_num+1}. Initial visible lines: {len(before_texts)}") - if grid_lines(): - first_line = lambda: next(iter(grid_lines())) - last_line = lambda: next(reversed(grid_lines())) - - endtime = time.time() + self.config.time_out - success = False - while endtime > time.time() and not success: - self.click(element=first_line(), click_type=enum.ClickType(3)) - ActionChains(self.driver).key_down(Keys.SHIFT).key_down(Keys.HOME).perform() - success = self.has_selected_cell(row_element=first_line()) - - endtime = time.time() + self.config.time_out - while endtime > time.time() and \ - next(reversed(after_texts), None) != next(reversed(before_texts), None) and \ - not last_line_selected: + logger().debug(f"Initial visible lines: {len(before_texts)}") + + if grid_lines(): + first_line = lambda: next(iter(grid_lines())) + last_line = lambda: next(reversed(grid_lines())) + + # Click first line + endtime = time.time() + self.config.time_out + success = False + while endtime > time.time() and not success: + self.click(element=first_line(), click_type=enum.ClickType(3)) + ActionChains(self.driver).key_down(Keys.SHIFT).key_down(Keys.HOME).perform() + success = self.has_selected_cell(row_element=first_line()) + + # Scroll and collect all lines + endtime = time.time() + self.config.time_out + while endtime > time.time() and \ + next(reversed(after_texts), None) != next(reversed(before_texts), None) and \ + not last_line_selected: - after_texts = list(map(lambda x: x.text, grid_lines())) + after_texts = list(map(lambda x: x.text, grid_lines())) - for i in after_texts: - if i not in before_texts: - before_texts.append(i) + for i in after_texts: + if i not in before_texts: + before_texts.append(i) - if len(before_texts) > row_num: - row_list = list(filter(lambda x: x.text == before_texts[row_num], grid_lines())) - logger().debug(f"Row found, ending search") - break + # If looking for specific row and found it, capture the element + if row_num is not None and len(before_texts) > row_num and row_element is None: + row_element = next(iter(list(filter(lambda x: x.text == before_texts[row_num], grid_lines()))), None) + logger().debug(f"Row found during scroll") + break - ActionChains(self.driver).key_down(Keys.PAGE_DOWN).perform() - down_count += 1 - self.wait_blocker() + ActionChains(self.driver).key_down(Keys.PAGE_DOWN).perform() + down_count += 1 + self.wait_blocker() - after_texts = list(map(lambda x: x.text, grid_lines())) - last_line_selected = self.has_selected_cell(row_element=last_line()) - - logger().debug(f"Search completed. Total lines collected: {len(before_texts)}, down_count: {down_count}") + after_texts = list(map(lambda x: x.text, grid_lines())) + last_line_selected = self.has_selected_cell(row_element=last_line()) + + ActionChains(self.driver).key_down(Keys.SHIFT).key_down(Keys.HOME).perform() - return next(iter(row_list), None), down_count + return before_texts, row_element, down_count + def get_obscure_gridline(self, grid, row_num=0): + """ + [Internal] + :param grid: + :param row_num: + :return obscured row based in row number: + """ + logger().debug(f"Starting search for row {row_num+1}") + before_texts, row_element, down_count = self._scroll_and_collect_grid_lines(grid, row_num) + + msg_success = 'Search completed. ' if row_element else f"Row {row_num+1} doesn't found! " + msg_success += f"Total lines collected: {len(before_texts)}, down_count: {down_count}" + + logger().debug(msg_success) + return row_element, down_count def check_grid_memo(self, element): """ @@ -9630,56 +9646,14 @@ def lenght_grid_lines(self, grid): Returns the leght of grid. """ - - grid_lines = None - if self.webapp_shadowroot(): - - grid_lines = lambda: self.execute_js_selector('tbody tr', self.soup_to_selenium(grid)) - before_texts = list(filter(lambda x: hasattr(x, 'text'), grid_lines())) - before_texts = list(map(lambda x: x.text, before_texts)) - after_texts = [] - down_count = 0 - last_line_selected = False - - logger().debug(f"Starting line count. Initial visible lines: {len(before_texts)}") - if grid_lines(): - first_line = lambda: next(iter(grid_lines())) - last_line = lambda: next(reversed(grid_lines())) - - endtime = time.time() + self.config.time_out - success = False - while endtime > time.time() and not success: - self.click(element=first_line(), click_type=enum.ClickType(3)) - ActionChains(self.driver).key_down(Keys.SHIFT).key_down(Keys.HOME).perform() - success = self.has_selected_cell(row_element=first_line()) - - endtime = time.time() + self.config.time_out - while endtime > time.time() and \ - next(reversed(after_texts), None) != next(reversed(before_texts), None) and \ - not last_line_selected: - - after_texts = list(map(lambda x: x.text, grid_lines())) - for i in after_texts: - if i not in before_texts: - before_texts.append(i) - - ActionChains(self.driver).key_down(Keys.PAGE_DOWN).perform() - down_count += 1 - self.wait_blocker() - - after_texts = list(map(lambda x: x.text, grid_lines())) - last_line_selected = self.has_selected_cell(row_element=last_line()) - - ActionChains(self.driver).key_down(Keys.SHIFT).key_down(Keys.HOME).perform() - - logger().debug(f"Count completed. Total lines: {len(before_texts)}, down_count: {down_count}") - return len(before_texts) + logger().debug(f"Starting line count") + before_texts, _, down_count = self._scroll_and_collect_grid_lines(grid, row_num=None) + logger().debug(f"Count completed. Total lines: {len(before_texts)}, down_count: {down_count}") + return len(before_texts) else: return len(grid.select("tbody tr")) - return len(grid_lines) - def TearDown(self): """ Closes the webdriver and ends the test case. @@ -10661,12 +10635,27 @@ def send_action(self, action = None, element = None, value = None, right_click=F children_classes_before = self.get_active_children_classes(element) children_classes_after = children_classes_before - classes_before = '' + classes_before = self.get_selenium_attribute(element(), 'class') if element else '' classes_after = classes_before - if element: - classes_before = self.get_selenium_attribute(element(), 'class') - classes_after = classes_before + loop_check = lambda: ((soup_before_event == soup_after_event) and \ + (shadow_roots_before == shadow_roots_after) and \ + (parent_classes_before == parent_classes_after) and \ + (children_classes_before == children_classes_after) and \ + (classes_before == classes_after)) + + return_check = lambda: ((soup_before_event != soup_after_event) or \ + (shadow_roots_before != shadow_roots_after) or \ + (parent_classes_before != parent_classes_after) or \ + (children_classes_before != children_classes_after) or \ + (classes_before != classes_after)) + + string_debug = lambda: f"Results send_action check:\n" + \ + f"soup = {soup_before_event != soup_after_event}\n" + \ + f'shadow_roots: {shadow_roots_before != shadow_roots_after}\n' + \ + f'parent_classes: {parent_classes_before != parent_classes_after}\n' + \ + f'children_classes: {children_classes_before != children_classes_after}\n' + \ + f'classes: {classes_before != classes_after}' self.wait_blocker() @@ -10678,13 +10667,9 @@ def send_action(self, action = None, element = None, value = None, right_click=F endtime = time.time() + self.config.time_out try: - while ((time.time() < endtime) and \ - (soup_before_event == soup_after_event) and \ - (shadow_roots_before == shadow_roots_after) and \ - (parent_classes_before == parent_classes_after) and \ - (children_classes_before == children_classes_after) and \ - (classes_before == classes_after) ): + while ((time.time() < endtime) and loop_check()): logger().debug(f"Trying to send action") + if right_click: soup_select = self.get_soup_select(".tmenupopupitem, wa-menu-popup-item") if not soup_select: @@ -10728,17 +10713,9 @@ def send_action(self, action = None, element = None, value = None, right_click=F return False if self.config.smart_test or self.config.debug_log: - logger().debug(f"send_action soup = {soup_before_event != soup_after_event}") - logger().debug(f'send_action shadow_roots: {shadow_roots_before != shadow_roots_after}') - logger().debug(f'send_action parent_classes: {parent_classes_before != parent_classes_after}') - logger().debug(f'send_action children_classes: {children_classes_before != children_classes_after}') - logger().debug(f'send_action classes: {classes_before == classes_after}') + logger().debug(string_debug()) - return ((soup_before_event != soup_after_event) or \ - (shadow_roots_before != shadow_roots_after) or \ - (parent_classes_before != parent_classes_after) or \ - (children_classes_before != children_classes_after) or \ - (classes_before != classes_after)) + return return_check() def get_selenium_attribute(self, element, attribute): try: From 4eabfad24aad52150fd135bf0595389043d3fddd Mon Sep 17 00:00:00 2001 From: Vinicius Oliveira Date: Fri, 6 Mar 2026 14:59:11 -0300 Subject: [PATCH 07/15] Add debug logging for row element capture in WebappInternal class --- tir/technologies/webapp_internal.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tir/technologies/webapp_internal.py b/tir/technologies/webapp_internal.py index 4137a2f8..aa8ea7c0 100644 --- a/tir/technologies/webapp_internal.py +++ b/tir/technologies/webapp_internal.py @@ -7108,7 +7108,15 @@ def _scroll_and_collect_grid_lines(self, grid, row_num=None): # If looking for specific row and found it, capture the element if row_num is not None and len(before_texts) > row_num and row_element is None: + logger().debug(f"[DEBUG] Target row_num: {row_num}, before_texts length: {len(before_texts)}") + logger().debug(f"[DEBUG] Target text (before_texts[{row_num}]): '{before_texts[row_num]}'") + logger().debug(f"[DEBUG] Current visible grid_lines count: {len(grid_lines())}") + logger().debug(f"[DEBUG] Visible texts: {[x.text for x in grid_lines()]}") row_element = next(iter(list(filter(lambda x: x.text == before_texts[row_num], grid_lines()))), None) + if row_element: + logger().debug(f"[DEBUG] Row element captured! Element text: '{row_element.text}'") + else: + logger().debug(f"[DEBUG] Row element NOT found in visible lines!") logger().debug(f"Row found during scroll") break From 5a1a60816c963e6baa121cf13fd188d17dd64b7b Mon Sep 17 00:00:00 2001 From: Vinicius Oliveira Date: Fri, 6 Mar 2026 15:12:01 -0300 Subject: [PATCH 08/15] Remove redundant debug logging and enhance row capture logging in WebappInternal class --- tir/technologies/webapp_internal.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/tir/technologies/webapp_internal.py b/tir/technologies/webapp_internal.py index aa8ea7c0..e157a662 100644 --- a/tir/technologies/webapp_internal.py +++ b/tir/technologies/webapp_internal.py @@ -7108,15 +7108,7 @@ def _scroll_and_collect_grid_lines(self, grid, row_num=None): # If looking for specific row and found it, capture the element if row_num is not None and len(before_texts) > row_num and row_element is None: - logger().debug(f"[DEBUG] Target row_num: {row_num}, before_texts length: {len(before_texts)}") - logger().debug(f"[DEBUG] Target text (before_texts[{row_num}]): '{before_texts[row_num]}'") - logger().debug(f"[DEBUG] Current visible grid_lines count: {len(grid_lines())}") - logger().debug(f"[DEBUG] Visible texts: {[x.text for x in grid_lines()]}") row_element = next(iter(list(filter(lambda x: x.text == before_texts[row_num], grid_lines()))), None) - if row_element: - logger().debug(f"[DEBUG] Row element captured! Element text: '{row_element.text}'") - else: - logger().debug(f"[DEBUG] Row element NOT found in visible lines!") logger().debug(f"Row found during scroll") break @@ -7140,6 +7132,8 @@ def get_obscure_gridline(self, grid, row_num=0): """ logger().debug(f"Starting search for row {row_num+1}") before_texts, row_element, down_count = self._scroll_and_collect_grid_lines(grid, row_num) + + logger().debug(f"Text row: {row_element.text}") msg_success = 'Search completed. ' if row_element else f"Row {row_num+1} doesn't found! " msg_success += f"Total lines collected: {len(before_texts)}, down_count: {down_count}" From 0d266566ea73d3aea5686e851c8e385829b09afa Mon Sep 17 00:00:00 2001 From: Vinicius Oliveira Date: Fri, 6 Mar 2026 16:06:09 -0300 Subject: [PATCH 09/15] Add conditional check before performing SHIFT+HOME action in WebappInternal class --- tir/technologies/webapp_internal.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tir/technologies/webapp_internal.py b/tir/technologies/webapp_internal.py index e157a662..4ffc62b4 100644 --- a/tir/technologies/webapp_internal.py +++ b/tir/technologies/webapp_internal.py @@ -7119,7 +7119,8 @@ def _scroll_and_collect_grid_lines(self, grid, row_num=None): after_texts = list(map(lambda x: x.text, grid_lines())) last_line_selected = self.has_selected_cell(row_element=last_line()) - ActionChains(self.driver).key_down(Keys.SHIFT).key_down(Keys.HOME).perform() + if row_num is None: + ActionChains(self.driver).key_down(Keys.SHIFT).key_down(Keys.HOME).perform() return before_texts, row_element, down_count From fd543e88e311101b7b64c4bd39dd2d6593917021 Mon Sep 17 00:00:00 2001 From: Vinicius Oliveira Date: Fri, 6 Mar 2026 17:29:07 -0300 Subject: [PATCH 10/15] Enhance line collection logic by adding DOWN arrow checks after PAGE_DOWN in WebappInternal class --- tir/technologies/webapp_internal.py | 30 ++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/tir/technologies/webapp_internal.py b/tir/technologies/webapp_internal.py index 4ffc62b4..03b2ddd8 100644 --- a/tir/technologies/webapp_internal.py +++ b/tir/technologies/webapp_internal.py @@ -7094,7 +7094,7 @@ def _scroll_and_collect_grid_lines(self, grid, row_num=None): ActionChains(self.driver).key_down(Keys.SHIFT).key_down(Keys.HOME).perform() success = self.has_selected_cell(row_element=first_line()) - # Scroll and collect all lines + # Scroll and collect all lines with PAGE_DOWN endtime = time.time() + self.config.time_out while endtime > time.time() and \ next(reversed(after_texts), None) != next(reversed(before_texts), None) and \ @@ -7119,6 +7119,34 @@ def _scroll_and_collect_grid_lines(self, grid, row_num=None): after_texts = list(map(lambda x: x.text, grid_lines())) last_line_selected = self.has_selected_cell(row_element=last_line()) + # After PAGE_DOWN reaches the end, try DOWN arrow to catch remaining lines + if not row_element: # Only if we haven't found the target row yet + logger().debug("Checking for additional lines with DOWN arrow") + additional_lines_found = True + while additional_lines_found and endtime > time.time(): + ActionChains(self.driver).key_down(Keys.DOWN).perform() + self.wait_blocker() + + after_down = list(map(lambda x: x.text, grid_lines())) + + # Check if new lines appeared + additional_lines_found = False + for i in after_down: + if i not in before_texts: + before_texts.append(i) + additional_lines_found = True + logger().debug(f"Found additional line with DOWN: {i}") + + # If looking for specific row and found it + if row_num is not None and len(before_texts) > row_num and row_element is None: + row_element = next(iter(list(filter(lambda x: x.text == before_texts[row_num], grid_lines()))), None) + logger().debug(f"Target row found with DOWN arrow") + break + + # Stop if no new lines were found + if not additional_lines_found: + break + if row_num is None: ActionChains(self.driver).key_down(Keys.SHIFT).key_down(Keys.HOME).perform() From 95a515c55384fd9f66831ffd546259c684baffcf Mon Sep 17 00:00:00 2001 From: Vinicius Oliveira Date: Mon, 9 Mar 2026 11:12:19 -0300 Subject: [PATCH 11/15] Refactor get_shadow_roots_content method to improve shadow root content capture and cleaning process --- tir/technologies/webapp_internal.py | 66 +++++++++++++---------------- 1 file changed, 29 insertions(+), 37 deletions(-) diff --git a/tir/technologies/webapp_internal.py b/tir/technologies/webapp_internal.py index 03b2ddd8..0cddf3dd 100644 --- a/tir/technologies/webapp_internal.py +++ b/tir/technologies/webapp_internal.py @@ -10670,20 +10670,20 @@ def send_action(self, action = None, element = None, value = None, right_click=F classes_after = classes_before loop_check = lambda: ((soup_before_event == soup_after_event) and \ - (shadow_roots_before == shadow_roots_after) and \ + (sorted(shadow_roots_before) == sorted(shadow_roots_after)) and \ (parent_classes_before == parent_classes_after) and \ (children_classes_before == children_classes_after) and \ (classes_before == classes_after)) return_check = lambda: ((soup_before_event != soup_after_event) or \ - (shadow_roots_before != shadow_roots_after) or \ + (sorted(shadow_roots_before) != sorted(shadow_roots_after)) or \ (parent_classes_before != parent_classes_after) or \ (children_classes_before != children_classes_after) or \ (classes_before != classes_after)) string_debug = lambda: f"Results send_action check:\n" + \ f"soup = {soup_before_event != soup_after_event}\n" + \ - f'shadow_roots: {shadow_roots_before != shadow_roots_after}\n' + \ + f'shadow_roots: {sorted(shadow_roots_before) != sorted(shadow_roots_after)}\n' + \ f'parent_classes: {parent_classes_before != parent_classes_after}\n' + \ f'children_classes: {children_classes_before != children_classes_after}\n' + \ f'classes: {classes_before != classes_after}' @@ -10757,48 +10757,40 @@ def get_selenium_attribute(self, element, attribute): def get_shadow_roots_content(self): """ [Internal] - Captures the content of all shadow roots in the current DOM. - Returns a dictionary with element identifiers and their shadow root HTML. - Cleans special characters like \n, \t, and multiple spaces. + Captures the innerHTML content of shadow roots from specific elements (wa-tab-page). + Returns a list of innerHTML strings for comparison. + Cleans special characters like \n, \t, \r and multiple spaces in Python. """ try: + shadow_contents = [] + term = self.grid_selectors["new_web_app"] + + elements = self.driver.find_elements(By.CSS_SELECTOR, term) + script = """ - function getShadowRootsContent() { - const shadowElements = []; - const allElements = document.querySelectorAll('*'); - - allElements.forEach((el, index) => { - if (el.shadowRoot) { - // Get innerHTML and clean special characters - let innerHTML = el.shadowRoot.innerHTML; - // Remove line breaks, tabs, and normalize spaces - innerHTML = innerHTML - .replace(/\\n/g, '') - .replace(/\\t/g, '') - .replace(/\\r/g, '') - .replace(/\\s+/g, ' ') - .trim(); - - const elementInfo = { - tagName: el.tagName, - id: el.id || `shadow-${index}`, - className: el.className, - innerHTML: innerHTML - }; - shadowElements.push(elementInfo); - } - }); - - return JSON.stringify(shadowElements); + if (arguments[0].shadowRoot) { + return arguments[0].shadowRoot.innerHTML; } - return getShadowRootsContent(); + return null; """ - result = self.driver.execute_script(script) - return result if result else '[]' + + # Check each element individually + for element in elements: + try: + content = self.driver.execute_script(script, element) + if content and self.element_is_displayed(element): + # Clean content + cleaned_content = content.replace('\n', '').replace('\t', '').replace('\r', '').replace('', '') + cleaned_content = re.sub(r'\s+', ' ', cleaned_content).strip() + shadow_contents.append(cleaned_content) + except: + continue + + return shadow_contents except Exception as e: if self.config.smart_test or self.config.debug_log: logger().debug(f"Warning Exception get_shadow_roots_content {str(e)}") - return '[]' + return [] def get_active_parent_class(self, element=None): """ From 4cc442e8145835e0429d0faea4508a1aee33449a Mon Sep 17 00:00:00 2001 From: Vinicius Oliveira Date: Tue, 10 Mar 2026 14:02:48 -0300 Subject: [PATCH 12/15] Refactor change detection logic in WebappInternal class to simplify checks and improve readability --- tir/technologies/webapp_internal.py | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/tir/technologies/webapp_internal.py b/tir/technologies/webapp_internal.py index 0cddf3dd..1ede3632 100644 --- a/tir/technologies/webapp_internal.py +++ b/tir/technologies/webapp_internal.py @@ -10663,29 +10663,18 @@ def send_action(self, action = None, element = None, value = None, right_click=F parent_classes_before = self.get_active_parent_class(element) parent_classes_after = parent_classes_before - children_classes_before = self.get_active_children_classes(element) - children_classes_after = children_classes_before - classes_before = self.get_selenium_attribute(element(), 'class') if element else '' classes_after = classes_before - loop_check = lambda: ((soup_before_event == soup_after_event) and \ - (sorted(shadow_roots_before) == sorted(shadow_roots_after)) and \ - (parent_classes_before == parent_classes_after) and \ - (children_classes_before == children_classes_after) and \ - (classes_before == classes_after)) - - return_check = lambda: ((soup_before_event != soup_after_event) or \ - (sorted(shadow_roots_before) != sorted(shadow_roots_after)) or \ - (parent_classes_before != parent_classes_after) or \ - (children_classes_before != children_classes_after) or \ - (classes_before != classes_after)) + check_changed = lambda: ((soup_before_event != soup_after_event) or \ + (sorted(shadow_roots_before) != sorted(shadow_roots_after)) or \ + (parent_classes_before != parent_classes_after) or \ + (classes_before != classes_after)) string_debug = lambda: f"Results send_action check:\n" + \ f"soup = {soup_before_event != soup_after_event}\n" + \ f'shadow_roots: {sorted(shadow_roots_before) != sorted(shadow_roots_after)}\n' + \ f'parent_classes: {parent_classes_before != parent_classes_after}\n' + \ - f'children_classes: {children_classes_before != children_classes_after}\n' + \ f'classes: {classes_before != classes_after}' self.wait_blocker() @@ -10698,7 +10687,7 @@ def send_action(self, action = None, element = None, value = None, right_click=F endtime = time.time() + self.config.time_out try: - while ((time.time() < endtime) and loop_check()): + while ((time.time() < endtime) and not check_changed()): logger().debug(f"Trying to send action") if right_click: @@ -10725,7 +10714,6 @@ def send_action(self, action = None, element = None, value = None, right_click=F shadow_roots_after = self.get_shadow_roots_content() parent_classes_after = self.get_active_parent_class(element) - children_classes_after = self.get_active_children_classes(element) if element: classes_after = self.get_selenium_attribute(element(), 'class') @@ -10746,7 +10734,7 @@ def send_action(self, action = None, element = None, value = None, right_click=F if self.config.smart_test or self.config.debug_log: logger().debug(string_debug()) - return return_check() + return check_changed() def get_selenium_attribute(self, element, attribute): try: From 408351708422471e94a802a2d81e16bf71b2a725 Mon Sep 17 00:00:00 2001 From: Vinicius Oliveira Date: Tue, 10 Mar 2026 14:32:05 -0300 Subject: [PATCH 13/15] Refactor grid line capture logic to improve text extraction and add delay before selection --- tir/technologies/webapp_internal.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tir/technologies/webapp_internal.py b/tir/technologies/webapp_internal.py index 1ede3632..e62de13e 100644 --- a/tir/technologies/webapp_internal.py +++ b/tir/technologies/webapp_internal.py @@ -7073,14 +7073,12 @@ def _scroll_and_collect_grid_lines(self, grid, row_num=None): Returns tuple: (before_texts, row_element, down_count) """ grid_lines = lambda: self.execute_js_selector('tbody tr', self.soup_to_selenium(grid)) - before_texts = list(filter(lambda x: hasattr(x, 'text'), grid_lines())) - before_texts = list(map(lambda x: x.text, before_texts)) after_texts = [] down_count = 0 last_line_selected = False row_element = None - logger().debug(f"Initial visible lines: {len(before_texts)}") + time.sleep(0.5) if grid_lines(): first_line = lambda: next(iter(grid_lines())) @@ -7093,6 +7091,10 @@ def _scroll_and_collect_grid_lines(self, grid, row_num=None): self.click(element=first_line(), click_type=enum.ClickType(3)) ActionChains(self.driver).key_down(Keys.SHIFT).key_down(Keys.HOME).perform() success = self.has_selected_cell(row_element=first_line()) + + before_texts = list(filter(lambda x: hasattr(x, 'text'), grid_lines())) + before_texts = list(map(lambda x: x.text, before_texts)) + logger().debug(f"Initial visible lines: {len(before_texts)}") # Scroll and collect all lines with PAGE_DOWN endtime = time.time() + self.config.time_out From 0578f5c899f7cfc7cd322b988519e5ed41c66d07 Mon Sep 17 00:00:00 2001 From: Vinicius Oliveira Date: Tue, 10 Mar 2026 16:14:42 -0300 Subject: [PATCH 14/15] Refactor first line selection logic into a separate method for improved readability and maintainability --- tir/technologies/webapp_internal.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/tir/technologies/webapp_internal.py b/tir/technologies/webapp_internal.py index e62de13e..8eba8cf2 100644 --- a/tir/technologies/webapp_internal.py +++ b/tir/technologies/webapp_internal.py @@ -7085,12 +7085,7 @@ def _scroll_and_collect_grid_lines(self, grid, row_num=None): last_line = lambda: next(reversed(grid_lines())) # Click first line - endtime = time.time() + self.config.time_out - success = False - while endtime > time.time() and not success: - self.click(element=first_line(), click_type=enum.ClickType(3)) - ActionChains(self.driver).key_down(Keys.SHIFT).key_down(Keys.HOME).perform() - success = self.has_selected_cell(row_element=first_line()) + self.select_first_line(first_line()) before_texts = list(filter(lambda x: hasattr(x, 'text'), grid_lines())) before_texts = list(map(lambda x: x.text, before_texts)) @@ -7154,6 +7149,24 @@ def _scroll_and_collect_grid_lines(self, grid, row_num=None): return before_texts, row_element, down_count + def select_first_line(self, first_line): + """ + [Internal] + + Selects the first visible row in the grid. + + :param first_line: First row element of the grid. + :type first_line: Selenium object + """ + success = False + + endtime = time.time() + self.config.time_out + while endtime > time.time() and not success: + self.click(element=first_line, click_type=enum.ClickType(3)) + time.sleep(0.5) + ActionChains(self.driver).key_down(Keys.SHIFT).key_down(Keys.HOME).perform() + success = self.has_selected_cell(row_element=first_line) + def get_obscure_gridline(self, grid, row_num=0): """ [Internal] From 1189e31293a82438121d4a4a6e9467045929f94f Mon Sep 17 00:00:00 2001 From: Vinicius Oliveira Date: Tue, 10 Mar 2026 16:21:36 -0300 Subject: [PATCH 15/15] Refactor select_first_line method to select_tr and enhance selection logic with SHIFT + HOME action --- tir/technologies/webapp_internal.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tir/technologies/webapp_internal.py b/tir/technologies/webapp_internal.py index 8eba8cf2..c720fe9c 100644 --- a/tir/technologies/webapp_internal.py +++ b/tir/technologies/webapp_internal.py @@ -7085,7 +7085,8 @@ def _scroll_and_collect_grid_lines(self, grid, row_num=None): last_line = lambda: next(reversed(grid_lines())) # Click first line - self.select_first_line(first_line()) + self.select_tr(first_line()) + ActionChains(self.driver).key_down(Keys.SHIFT).key_down(Keys.HOME).perform() before_texts = list(filter(lambda x: hasattr(x, 'text'), grid_lines())) before_texts = list(map(lambda x: x.text, before_texts)) @@ -7149,7 +7150,7 @@ def _scroll_and_collect_grid_lines(self, grid, row_num=None): return before_texts, row_element, down_count - def select_first_line(self, first_line): + def select_tr(self, first_line): """ [Internal] @@ -7163,8 +7164,7 @@ def select_first_line(self, first_line): endtime = time.time() + self.config.time_out while endtime > time.time() and not success: self.click(element=first_line, click_type=enum.ClickType(3)) - time.sleep(0.5) - ActionChains(self.driver).key_down(Keys.SHIFT).key_down(Keys.HOME).perform() + time.sleep(0.5) success = self.has_selected_cell(row_element=first_line) def get_obscure_gridline(self, grid, row_num=0):