from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.common.exceptions import StaleElementReferenceException, TimeoutException from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import WebDriverWait def _menu_text_xpath(tag_name, label): return ( f"//{tag_name}[contains(normalize-space(),\"{label}\")]" f"|//{tag_name}[contains(.,\"{label}\")]" ) def open_bootstrap_nav_submenu(driver, top_menu_label, submenu_label, final_href_part): wait = WebDriverWait(driver, 20) top_menu = wait.until( EC.element_to_be_clickable( ( By.XPATH, "//ul[contains(@class,'nav') and contains(@class,'navbar-nav')]" f"//a[contains(@class,'dropdown-toggle') and contains(normalize-space(),'{top_menu_label}')]" ) ) ) try: top_menu.click() except Exception: driver.execute_script("arguments[0].click();", top_menu) top_menu_container = wait.until( lambda _driver: _find_open_top_menu_container(_driver, top_menu_label) ) submenu = wait.until( lambda _driver: _find_clickable_submenu(_driver, top_menu_container, submenu_label) ) try: submenu.click() except Exception: driver.execute_script("arguments[0].click();", submenu) final_option = wait.until( lambda _driver: _find_clickable_final_option(_driver, top_menu_container, final_href_part) ) driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", final_option) try: final_option.click() except Exception: driver.execute_script("arguments[0].click();", final_option) def _find_open_top_menu_container(driver, top_menu_label): containers = driver.find_elements( By.XPATH, "//ul[contains(@class,'nav') and contains(@class,'navbar-nav')]/li" ) normalized_label = top_menu_label.strip().lower() for container in containers: classes = (container.get_attribute("class") or "").lower() text = container.text.strip().lower() if normalized_label in text and "open" in classes: return container return False def _find_clickable_submenu(driver, top_menu_container, submenu_label): normalized_label = submenu_label.strip().lower() submenu_links = top_menu_container.find_elements( By.XPATH, ".//ul[contains(@class,'dropdown-menu')]//a" ) for link in submenu_links: text = link.text.strip().lower() if normalized_label in text and link.is_displayed() and link.is_enabled(): return link return False def _find_clickable_final_option(driver, top_menu_container, final_href_part): final_links = top_menu_container.find_elements( By.XPATH, f".//ul[contains(@class,'dropdown-menu')]//a[contains(@href,'{final_href_part}')]" ) for link in final_links: if link.is_displayed() and link.is_enabled(): return link return False def select_select2_value_by_container_id(driver, container_id, value, exact=False): wait = WebDriverWait(driver, 20) container = wait.until( EC.element_to_be_clickable((By.ID, container_id)) ) driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", container) try: container.click() except Exception: driver.execute_script("arguments[0].click();", container) try: search_input = wait.until( EC.visibility_of_element_located( (By.XPATH, "//div[contains(@class,'select2-drop-active')]//input[contains(@class,'select2-input')]") ) ) search_input.click() search_input.send_keys(Keys.CONTROL, "a") search_input.send_keys(value) except TimeoutException: pass try: option = wait.until( lambda _driver: _find_select2_option(_driver, value, exact=exact) ) except TimeoutException as exc: available_options = _get_visible_select2_option_texts(driver) raise TimeoutException( f"Option '{value}' did not load for {container_id}. " f"Visible options: {available_options}" ) from exc try: option.click() except Exception: driver.execute_script("arguments[0].click();", option) def _find_select2_option(driver, value, exact=False): options = driver.find_elements( By.XPATH, "//div[contains(@class,'select2-drop-active')]//div[contains(@class,'select2-result-label')]" ) normalized_value = value.strip().lower() visible_options = [] for option in options: try: if option.is_displayed(): visible_options.append(option) except StaleElementReferenceException: return False for option in visible_options: text = _safe_element_text(option) if text is None: return False if text == normalized_value: return option for option in visible_options: text = _safe_element_text(option) if text is None: return False option_code = text.split("(", 1)[0].strip() if option_code == normalized_value: return option if exact: return False for option in visible_options: text = _safe_element_text(option) if text is None: return False if text.startswith(normalized_value): return option for option in visible_options: text = _safe_element_text(option) if text is None: return False if normalized_value in text: return option return False def _safe_element_text(element): try: return element.text.strip().lower() except StaleElementReferenceException: return None def click_select2_option_by_text(driver, container_id, value): wait = WebDriverWait(driver, 20) container = wait.until(EC.element_to_be_clickable((By.ID, container_id))) driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", container) try: container.click() except Exception: driver.execute_script("arguments[0].click();", container) option = wait.until(lambda _driver: _find_select2_option(_driver, value)) driver.execute_script("arguments[0].scrollIntoView({block: 'nearest'});", option) try: option.click() except Exception: driver.execute_script("arguments[0].click();", option) def wait_for_select2_option_by_text(driver, container_id, value, timeout_seconds=30, attempts=3): wait = WebDriverWait(driver, timeout_seconds) last_error = None for _ in range(attempts): container = wait.until(EC.element_to_be_clickable((By.ID, container_id))) driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", container) try: container.click() except Exception: driver.execute_script("arguments[0].click();", container) try: option = wait.until(lambda _driver: _find_select2_option(_driver, value)) try: container.click() except Exception: driver.execute_script("arguments[0].click();", container) return option except TimeoutException as exc: last_error = exc try: container.click() except Exception: driver.execute_script("arguments[0].click();", container) if last_error: available_options = _get_visible_select2_option_texts(driver) raise TimeoutException( f"Option '{value}' did not load for {container_id}. " f"Visible options: {available_options}" ) from last_error raise TimeoutException(f"Option '{value}' did not load for {container_id}") def _get_visible_select2_option_texts(driver): options = driver.find_elements( By.XPATH, "//div[contains(@class,'select2-drop-active')]//div[contains(@class,'select2-result-label')]" ) return [option.text.strip() for option in options if option.is_displayed()]