Initial commit - Selenium automation framework
This commit is contained in:
@@ -0,0 +1,560 @@
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
from selenium.common.exceptions import NoAlertPresentException, TimeoutException, StaleElementReferenceException
|
||||
from .locators import Locator
|
||||
from .config_reader import (
|
||||
get_login_captcha_wait_seconds,
|
||||
get_login_post_submit_wait_seconds,
|
||||
get_step_delay_seconds,
|
||||
)
|
||||
import time
|
||||
import msvcrt
|
||||
|
||||
STEP_DELAY_SECONDS = get_step_delay_seconds()
|
||||
LOGIN_CAPTCHA_WAIT_SECONDS = get_login_captcha_wait_seconds()
|
||||
LOGIN_POST_SUBMIT_WAIT_SECONDS = get_login_post_submit_wait_seconds()
|
||||
|
||||
|
||||
def pause_after_step(seconds=STEP_DELAY_SECONDS):
|
||||
|
||||
time.sleep(seconds)
|
||||
|
||||
|
||||
def wait_for_manual_input(prompt="Press Enter in the terminal when you want to finish this run..."):
|
||||
|
||||
print(prompt)
|
||||
try:
|
||||
input()
|
||||
except EOFError as exc:
|
||||
raise RuntimeError(
|
||||
"Manual input is required for this test. Run pytest with -s so Enter can be captured."
|
||||
) from exc
|
||||
|
||||
|
||||
def clear_pending_console_input():
|
||||
|
||||
while msvcrt.kbhit():
|
||||
msvcrt.getwch()
|
||||
|
||||
|
||||
def enter_pressed():
|
||||
|
||||
if not msvcrt.kbhit():
|
||||
return False
|
||||
|
||||
pressed = False
|
||||
while msvcrt.kbhit():
|
||||
key = msvcrt.getwch()
|
||||
if key in ("\r", "\n"):
|
||||
pressed = True
|
||||
|
||||
return pressed
|
||||
|
||||
|
||||
def login(driver, username, password):
|
||||
|
||||
wait = WebDriverWait(driver, 20)
|
||||
|
||||
# username
|
||||
userid_field = wait.until(
|
||||
EC.visibility_of_element_located((By.XPATH, Locator.userid))
|
||||
)
|
||||
userid_field.send_keys(username)
|
||||
|
||||
# password
|
||||
password_field = driver.find_element(By.XPATH, Locator.password)
|
||||
password_field.send_keys(password)
|
||||
|
||||
print(f"Solve captcha within {LOGIN_CAPTCHA_WAIT_SECONDS} seconds...")
|
||||
|
||||
# wait for manual captcha solve
|
||||
time.sleep(LOGIN_CAPTCHA_WAIT_SECONDS)
|
||||
|
||||
# click submit
|
||||
submit_btn = wait.until(
|
||||
EC.element_to_be_clickable((By.XPATH, Locator.submitbtn))
|
||||
)
|
||||
submit_btn.click()
|
||||
|
||||
time.sleep(LOGIN_POST_SUBMIT_WAIT_SECONDS)
|
||||
|
||||
accept_browser_alert_if_present(driver)
|
||||
|
||||
# wait for dashboard
|
||||
wait.until(
|
||||
EC.visibility_of_element_located((By.XPATH, Locator.dashboard_marker))
|
||||
)
|
||||
|
||||
close_tac_expiry_modal_if_present(driver)
|
||||
|
||||
print("Login successful")
|
||||
|
||||
|
||||
def accept_browser_alert_if_present(driver, timeout=5):
|
||||
|
||||
try:
|
||||
alert = WebDriverWait(driver, timeout).until(EC.alert_is_present())
|
||||
print(f"Browser alert accepted: {alert.text}")
|
||||
alert.accept()
|
||||
return True
|
||||
except (TimeoutException, NoAlertPresentException):
|
||||
return False
|
||||
|
||||
|
||||
def close_tac_expiry_modal_if_present(driver, timeout=5):
|
||||
|
||||
try:
|
||||
close_button = WebDriverWait(driver, timeout).until(
|
||||
EC.element_to_be_clickable((By.XPATH, Locator.tacexpiryclosebtn))
|
||||
)
|
||||
except TimeoutException:
|
||||
return False
|
||||
|
||||
try:
|
||||
close_button.click()
|
||||
except Exception:
|
||||
driver.execute_script("arguments[0].click();", close_button)
|
||||
|
||||
print("Closed TAC Expiry modal")
|
||||
return True
|
||||
|
||||
|
||||
def handle_transient_popups(driver):
|
||||
|
||||
accepted_alert = accept_browser_alert_if_present(driver, timeout=2)
|
||||
closed_modal = close_tac_expiry_modal_if_present(driver, timeout=2)
|
||||
return accepted_alert or closed_modal
|
||||
|
||||
|
||||
def backup_current_session_token(driver):
|
||||
|
||||
session_backup = {
|
||||
"current_url": driver.current_url,
|
||||
"cookies": driver.get_cookies(),
|
||||
}
|
||||
print(f"Backed up current browser session with {len(session_backup['cookies'])} cookies")
|
||||
return session_backup
|
||||
|
||||
|
||||
def save_session_backup(driver, test_context, name="session_backup"):
|
||||
|
||||
backup = backup_current_session_token(driver)
|
||||
backup["created_at"] = datetime.now().isoformat(timespec="seconds")
|
||||
|
||||
path = os.path.join(test_context["folder"], f"{name}.json")
|
||||
with open(path, "w", encoding="utf-8") as backup_file:
|
||||
json.dump(backup, backup_file, indent=2)
|
||||
|
||||
test_context.setdefault("extra_folders", [])
|
||||
print(f"Session backup saved: {path}")
|
||||
return backup
|
||||
|
||||
|
||||
def logout(driver):
|
||||
|
||||
wait = WebDriverWait(driver, 20)
|
||||
handle_transient_popups(driver)
|
||||
|
||||
try:
|
||||
user_menu = wait.until(
|
||||
EC.element_to_be_clickable((By.XPATH, Locator.usermenutoggle))
|
||||
)
|
||||
driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", user_menu)
|
||||
try:
|
||||
user_menu.click()
|
||||
except Exception:
|
||||
driver.execute_script("arguments[0].click();", user_menu)
|
||||
|
||||
wait.until(
|
||||
EC.visibility_of_element_located(
|
||||
(
|
||||
By.XPATH,
|
||||
"//li[contains(@class,'dropdown') and contains(@class,'navbar-user') and contains(@class,'open')]"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
logout_link = wait.until(
|
||||
EC.element_to_be_clickable((By.XPATH, Locator.logoutlink))
|
||||
)
|
||||
try:
|
||||
logout_link.click()
|
||||
except Exception:
|
||||
driver.execute_script("arguments[0].click();", logout_link)
|
||||
|
||||
accept_browser_alert_if_present(driver, timeout=3)
|
||||
wait.until(
|
||||
EC.visibility_of_element_located((By.XPATH, Locator.userid))
|
||||
)
|
||||
print("Logout successful")
|
||||
return True
|
||||
except TimeoutException:
|
||||
print("Logout link was not found; browser will still close from fixture teardown")
|
||||
return False
|
||||
|
||||
def select_ui_dropdown(driver, dropdown_label, value):
|
||||
|
||||
wait = WebDriverWait(driver, 20)
|
||||
|
||||
# 1️⃣ Open dropdown
|
||||
dropdown = wait.until(
|
||||
EC.element_to_be_clickable(
|
||||
(By.XPATH, f"//span[@aria-label='{dropdown_label} activate']")
|
||||
)
|
||||
)
|
||||
dropdown.click()
|
||||
|
||||
# 2️⃣ Find search box
|
||||
search_box = wait.until(
|
||||
EC.visibility_of_element_located((By.CSS_SELECTOR, "input.ui-select-search"))
|
||||
)
|
||||
|
||||
# 3️⃣ Type product name
|
||||
search_box.clear()
|
||||
search_box.send_keys(value)
|
||||
|
||||
# 4️⃣ Wait for dropdown options to load
|
||||
option_xpath = f"//div[contains(@class,'ui-select-choices-row')]//span[contains(text(),'{value}')]"
|
||||
|
||||
option = wait.until(
|
||||
EC.element_to_be_clickable((By.XPATH, option_xpath))
|
||||
)
|
||||
|
||||
# 5️⃣ Click option
|
||||
option.click()
|
||||
|
||||
print(f"{value} selected successfully")
|
||||
|
||||
|
||||
def select_labeled_ui_dropdown(driver, field_label, value):
|
||||
|
||||
wait = WebDriverWait(driver, 20)
|
||||
|
||||
dropdown_xpath = (
|
||||
f"//label[normalize-space()='{field_label}']"
|
||||
"/following::span[contains(@class,'ui-select-toggle')][1]"
|
||||
)
|
||||
|
||||
dropdown = wait.until(
|
||||
EC.element_to_be_clickable(
|
||||
(
|
||||
By.XPATH,
|
||||
dropdown_xpath
|
||||
)
|
||||
)
|
||||
)
|
||||
driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", dropdown)
|
||||
|
||||
try:
|
||||
dropdown.click()
|
||||
except Exception:
|
||||
driver.execute_script("arguments[0].click();", dropdown)
|
||||
|
||||
search_xpath = (
|
||||
f"//label[normalize-space()='{field_label}']"
|
||||
"/following::input[contains(@class,'ui-select-search')][1]"
|
||||
)
|
||||
|
||||
search_box = wait.until(
|
||||
EC.visibility_of_element_located(
|
||||
(By.XPATH, search_xpath)
|
||||
)
|
||||
)
|
||||
search_box.click()
|
||||
search_box.send_keys(Keys.CONTROL, "a")
|
||||
search_box.clear()
|
||||
search_box.send_keys(value)
|
||||
|
||||
wait.until(
|
||||
EC.presence_of_element_located(
|
||||
(By.XPATH, "//div[contains(@class,'ui-select-choices-row')]")
|
||||
)
|
||||
)
|
||||
|
||||
def _visible_choice_rows():
|
||||
rows = driver.find_elements(By.XPATH, "//div[contains(@class,'ui-select-choices-row')]")
|
||||
return [row for row in rows if row.is_displayed()]
|
||||
|
||||
option = None
|
||||
visible_rows = _visible_choice_rows()
|
||||
|
||||
for row in visible_rows:
|
||||
if row.text.strip() == value:
|
||||
option = row
|
||||
break
|
||||
|
||||
if option is None:
|
||||
available_options = [row.text.strip() for row in visible_rows if row.text.strip()]
|
||||
raise AssertionError(
|
||||
f"Exact option '{value}' was not available in {field_label}. "
|
||||
f"Visible options: {available_options}"
|
||||
)
|
||||
|
||||
driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", option)
|
||||
try:
|
||||
option.click()
|
||||
except Exception:
|
||||
driver.execute_script("arguments[0].click();", option)
|
||||
|
||||
print(f"{value} selected successfully from {field_label}")
|
||||
|
||||
|
||||
def open_trading_maintenance_page(driver, menu_xpath):
|
||||
|
||||
wait = WebDriverWait(driver, 20)
|
||||
|
||||
trading_maintenance = wait.until(
|
||||
EC.presence_of_element_located((By.XPATH, Locator.tradingmaintenance))
|
||||
)
|
||||
driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", trading_maintenance)
|
||||
|
||||
submenu = driver.find_elements(By.XPATH, menu_xpath)
|
||||
submenu_visible = any(item.is_displayed() for item in submenu)
|
||||
|
||||
if not submenu_visible:
|
||||
try:
|
||||
wait.until(EC.element_to_be_clickable((By.XPATH, Locator.tradingmaintenance)))
|
||||
trading_maintenance.click()
|
||||
except Exception:
|
||||
driver.execute_script("arguments[0].click();", trading_maintenance)
|
||||
|
||||
menu_item = wait.until(
|
||||
EC.element_to_be_clickable((By.XPATH, menu_xpath))
|
||||
)
|
||||
try:
|
||||
menu_item.click()
|
||||
except Exception:
|
||||
driver.execute_script("arguments[0].click();", menu_item)
|
||||
|
||||
|
||||
def click_visible_menu_item(driver, menu_xpath):
|
||||
|
||||
wait = WebDriverWait(driver, 20)
|
||||
menu_items = driver.find_elements(By.XPATH, menu_xpath)
|
||||
|
||||
for menu_item in menu_items:
|
||||
if menu_item.is_displayed():
|
||||
driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", menu_item)
|
||||
try:
|
||||
menu_item.click()
|
||||
except Exception:
|
||||
driver.execute_script("arguments[0].click();", menu_item)
|
||||
return
|
||||
|
||||
menu_item = wait.until(
|
||||
EC.element_to_be_clickable((By.XPATH, menu_xpath))
|
||||
)
|
||||
try:
|
||||
menu_item.click()
|
||||
except Exception:
|
||||
driver.execute_script("arguments[0].click();", menu_item)
|
||||
|
||||
|
||||
def type_into_field(driver, locator, value):
|
||||
|
||||
wait = WebDriverWait(driver, 20)
|
||||
|
||||
field = wait.until(
|
||||
EC.visibility_of_element_located(locator)
|
||||
)
|
||||
field.clear()
|
||||
field.send_keys(value)
|
||||
|
||||
|
||||
def select_bootstrap_dropdown_by_id(driver, element_id, value):
|
||||
|
||||
wait = WebDriverWait(driver, 20)
|
||||
|
||||
container = wait.until(
|
||||
EC.presence_of_element_located((By.ID, element_id))
|
||||
)
|
||||
driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", container)
|
||||
|
||||
toggle_xpath = (
|
||||
f"//div[@id='{element_id}']//span[contains(@class,'ui-select-toggle')]"
|
||||
f"|//div[@id='{element_id}']//span[contains(@class,'btn-default')]"
|
||||
f"|//div[@id='{element_id}']//span[contains(@class,'ui-select-placeholder')]"
|
||||
f"|//div[@id='{element_id}']//input[contains(@class,'ui-select-search')]"
|
||||
)
|
||||
|
||||
toggle = wait.until(
|
||||
EC.presence_of_element_located((By.XPATH, toggle_xpath))
|
||||
)
|
||||
|
||||
try:
|
||||
wait.until(EC.element_to_be_clickable((By.XPATH, toggle_xpath)))
|
||||
toggle.click()
|
||||
except Exception:
|
||||
driver.execute_script("arguments[0].click();", toggle)
|
||||
|
||||
try:
|
||||
search_box = wait.until(
|
||||
EC.visibility_of_element_located(
|
||||
(By.XPATH, f"//div[@id='{element_id}']//input[contains(@class,'ui-select-search')]")
|
||||
)
|
||||
)
|
||||
except TimeoutException:
|
||||
search_box = wait.until(
|
||||
EC.visibility_of_element_located(
|
||||
(By.XPATH, "//input[contains(@class,'ui-select-search') and not(@aria-hidden='true')]")
|
||||
)
|
||||
)
|
||||
|
||||
search_box.click()
|
||||
search_box.send_keys(Keys.CONTROL, "a")
|
||||
search_box.clear()
|
||||
search_box.send_keys(value)
|
||||
|
||||
option = wait.until(
|
||||
EC.element_to_be_clickable(
|
||||
(
|
||||
By.XPATH,
|
||||
"//div[contains(@class,'ui-select-choices-row')]"
|
||||
f"//*[normalize-space()='{value}' or starts-with(normalize-space(),'{value}')]"
|
||||
)
|
||||
)
|
||||
)
|
||||
option.click()
|
||||
|
||||
print(f"{value} selected successfully from {element_id}")
|
||||
|
||||
|
||||
def click_grid_button_for_row_text(driver, row_text, button_text):
|
||||
|
||||
wait = WebDriverWait(driver, 20)
|
||||
|
||||
button_xpath = (
|
||||
"//div[contains(@class,'ui-grid-row')]"
|
||||
f"[contains(.,'{row_text}')]"
|
||||
f"//button[normalize-space()='{button_text}']"
|
||||
)
|
||||
|
||||
last_error = None
|
||||
|
||||
for _ in range(3):
|
||||
try:
|
||||
button = wait.until(
|
||||
EC.presence_of_element_located((By.XPATH, button_xpath))
|
||||
)
|
||||
driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", button)
|
||||
wait.until(EC.element_to_be_clickable((By.XPATH, button_xpath)))
|
||||
|
||||
refreshed_button = driver.find_element(By.XPATH, button_xpath)
|
||||
|
||||
try:
|
||||
refreshed_button.click()
|
||||
except Exception:
|
||||
driver.execute_script("arguments[0].click();", refreshed_button)
|
||||
|
||||
print(f"Clicked {button_text} for row containing {row_text}")
|
||||
return
|
||||
except StaleElementReferenceException as exc:
|
||||
last_error = exc
|
||||
time.sleep(1)
|
||||
|
||||
if last_error:
|
||||
raise last_error
|
||||
|
||||
|
||||
def click_first_grid_button(driver, button_text):
|
||||
|
||||
wait = WebDriverWait(driver, 20)
|
||||
button_xpath = f"(//button[normalize-space()='{button_text}' and not(contains(@class,'ng-hide'))])[1]"
|
||||
last_error = None
|
||||
|
||||
for _ in range(3):
|
||||
try:
|
||||
button = wait.until(
|
||||
EC.presence_of_element_located((By.XPATH, button_xpath))
|
||||
)
|
||||
driver.execute_script("arguments[0].scrollIntoView({block: 'start'});", button)
|
||||
refreshed_button = driver.find_element(By.XPATH, button_xpath)
|
||||
|
||||
try:
|
||||
refreshed_button.click()
|
||||
except Exception:
|
||||
driver.execute_script("arguments[0].click();", refreshed_button)
|
||||
|
||||
print(f"Clicked first visible {button_text}")
|
||||
return
|
||||
except StaleElementReferenceException as exc:
|
||||
last_error = exc
|
||||
time.sleep(1)
|
||||
|
||||
if last_error:
|
||||
raise last_error
|
||||
|
||||
|
||||
def type_first_visible_input(driver, xpath, value):
|
||||
|
||||
wait = WebDriverWait(driver, 20)
|
||||
|
||||
def _find_visible_input(_driver):
|
||||
elements = _driver.find_elements(By.XPATH, xpath)
|
||||
for element in elements:
|
||||
if element.is_displayed() and element.is_enabled():
|
||||
return element
|
||||
return False
|
||||
|
||||
element = wait.until(_find_visible_input)
|
||||
driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", element)
|
||||
element.click()
|
||||
element.clear()
|
||||
element.send_keys(value)
|
||||
|
||||
|
||||
def toggle_bootstrap_dropdown_by_id(driver, element_id):
|
||||
|
||||
wait = WebDriverWait(driver, 20)
|
||||
|
||||
toggle_xpath = (
|
||||
f"//div[@id='{element_id}']//span[contains(@class,'ui-select-toggle')]"
|
||||
f"|//div[@id='{element_id}']//span[contains(@class,'btn-default')]"
|
||||
f"|//div[@id='{element_id}']//span[contains(@class,'ui-select-placeholder')]"
|
||||
f"|//div[@id='{element_id}']//input[contains(@class,'ui-select-search')]"
|
||||
)
|
||||
|
||||
toggle = wait.until(
|
||||
EC.presence_of_element_located((By.XPATH, toggle_xpath))
|
||||
)
|
||||
driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", toggle)
|
||||
|
||||
try:
|
||||
wait.until(EC.element_to_be_clickable((By.XPATH, toggle_xpath)))
|
||||
toggle.click()
|
||||
except Exception:
|
||||
driver.execute_script("arguments[0].click();", toggle)
|
||||
|
||||
|
||||
def click_page_body(driver):
|
||||
|
||||
body = WebDriverWait(driver, 20).until(
|
||||
EC.presence_of_element_located((By.TAG_NAME, "body"))
|
||||
)
|
||||
body.click()
|
||||
|
||||
|
||||
def toggle_labeled_ui_dropdown(driver, field_label):
|
||||
|
||||
wait = WebDriverWait(driver, 20)
|
||||
|
||||
dropdown_xpath = (
|
||||
f"//label[normalize-space()='{field_label}']"
|
||||
"/following::span[contains(@class,'ui-select-toggle')][1]"
|
||||
)
|
||||
|
||||
dropdown = wait.until(
|
||||
EC.element_to_be_clickable((By.XPATH, dropdown_xpath))
|
||||
)
|
||||
driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", dropdown)
|
||||
|
||||
try:
|
||||
dropdown.click()
|
||||
except Exception:
|
||||
driver.execute_script("arguments[0].click();", dropdown)
|
||||
Reference in New Issue
Block a user