567 lines
16 KiB
Python
567 lines
16 KiB
Python
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, exact=False):
|
|
|
|
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_xpath = (
|
|
f"//div[@id='{element_id}']//div[contains(@class,'ui-select-choices-row')]"
|
|
f"[normalize-space()='{value}']"
|
|
if exact else
|
|
"//div[contains(@class,'ui-select-choices-row')]"
|
|
f"//*[normalize-space()='{value}' or starts-with(normalize-space(),'{value}')]"
|
|
)
|
|
option = wait.until(
|
|
EC.element_to_be_clickable(
|
|
(
|
|
By.XPATH,
|
|
option_xpath
|
|
)
|
|
)
|
|
)
|
|
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)
|