Initial commit - Selenium automation framework
This commit is contained in:
@@ -0,0 +1,264 @@
|
||||
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()]
|
||||
Reference in New Issue
Block a user