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 check_captcha_error(driver) -> str: """Check if there's a CAPTCHA error message on the page.""" try: # First handle any alerts try: alert = driver.switch_to.alert alert_text = alert.text print(f" DEBUG: Alert detected in check_captcha_error: {alert_text}") if "logout" in alert_text.lower() or "session" in alert_text.lower() or "previous" in alert_text.lower(): alert.accept() print(" DEBUG: Accepted session/logout alert") time.sleep(2) except: pass error_selectors = [ "//div[@id='error']//strong[normalize-space()='Invalid Captcha']", "//div[@id='error']//span[contains(@class,'help-block')]//strong[contains(text(),'Invalid')]", "//*[contains(@class,'error') and contains(text(),'Invalid Captcha')]", ] for selector in error_selectors: elements = driver.find_elements(By.XPATH, selector) for el in elements: if el.is_displayed() and el.text.strip(): return el.text.strip() except Exception as e: print(f" DEBUG: check_captcha_error exception: {e}") return "" def check_login_success(driver) -> bool: """Check if login was successful using multiple indicators.""" try: # Handle any pending alerts first try: alert = driver.switch_to.alert alert_text = alert.text print(f" DEBUG: Alert detected: {alert_text}") if "logout" in alert_text.lower() or "session" in alert_text.lower() or "previous" in alert_text.lower(): alert.accept() print(" DEBUG: Accepted session/logout alert") time.sleep(2) except: pass # Wait a bit for page to load after submit time.sleep(3) # Print current URL and page title for debugging print(f" DEBUG: Current URL: {driver.current_url}") print(f" DEBUG: Page title: {driver.title}") # Multiple dashboard indicators dashboard_indicators = [ "//span[normalize-space()='TRADING MAINTENANCE']", "//a[contains(@href, 'tradingMaintenance')]", "//span[contains(text(), 'Dashboard')]", "//h1[contains(text(), 'Dashboard')]", "//div[contains(@class, 'dashboard')]", "//a[contains(text(), 'Trading')]", "//div[contains(@class, 'navbar')]//span[contains(text(), 'Trading')]", ] wait = WebDriverWait(driver, 15) for indicator in dashboard_indicators: try: wait.until(EC.visibility_of_element_located((By.XPATH, indicator))) print(f" DEBUG: Found dashboard indicator: {indicator}") return True except: continue # URL-based check if "home" in driver.current_url.lower() or "dashboard" in driver.current_url.lower(): print(" DEBUG: Login success detected via URL") return True return False except Exception as e: print(f" DEBUG: check_login_success failed: {e}") return False def login( driver, username, password, max_captcha_retries=5, captcha_metrics=None, max_login_duration_seconds=1200, ): wait = WebDriverWait(driver, 20) # --- AUTOMATED CAPTCHA SOLVING WITH RETRY ON FAILURE --- from tests.captcha_solver import solve_and_fill_captcha, find_refresh_button, wait_for_new_captcha login_deadline = time.monotonic() + max_login_duration_seconds captcha_attempt = 0 while True: captcha_attempt += 1 # Refreshing after a failed CAPTCHA clears the login form, so refill # both credentials before every automatic or manual retry. userid_field = wait.until( EC.visibility_of_element_located((By.XPATH, Locator.userid)) ) userid_field.clear() userid_field.send_keys(username) password_field = wait.until( EC.visibility_of_element_located((By.XPATH, Locator.password)) ) password_field.clear() password_field.send_keys(password) if captcha_metrics is not None: captcha_metrics["tries"] = captcha_attempt captcha_metrics["max_retries"] = max_captcha_retries print(f"Solving CAPTCHA (attempt {captcha_attempt})...") # Once automatic solving has run for the configured duration, ask for # the CAPTCHA value in a browser prompt and continue the same login flow. if time.monotonic() >= login_deadline: print("Automatic CAPTCHA retries timed out; waiting for manual CAPTCHA entry.") manual_value = driver.execute_script( "return window.prompt('Automatic CAPTCHA solving timed out. Enter the CAPTCHA shown on the page:');" ) if not manual_value: raise RuntimeError("Manual CAPTCHA entry was cancelled") captcha_input = wait.until( EC.visibility_of_element_located((By.NAME, "captcha")) ) captcha_input.clear() captcha_input.send_keys(manual_value.strip()) wait.until(EC.element_to_be_clickable((By.XPATH, Locator.submitbtn))).click() time.sleep(LOGIN_POST_SUBMIT_WAIT_SECONDS) accept_browser_alert_if_present(driver) handle_transient_popups(driver) captcha_error = check_captcha_error(driver) if captcha_error: print(f" Manual CAPTCHA rejected: {captcha_error}") continue if check_login_success(driver): print("Login successful after manual CAPTCHA entry") if captcha_metrics is not None: captcha_metrics["correct_attempt"] = captcha_attempt captcha_metrics.setdefault("captcha_attempts", []).append({ "attempt": captcha_attempt, "captcha_text": "manual", "variant": manual_value.strip(), "status": "manual", "screenshot": "", "error": "", }) close_tac_expiry_modal_if_present(driver) return True continue def attempt_callback(attempt_num, captcha_text, variant, status, screenshot_path="", error=""): if captcha_metrics is not None: captcha_metrics.setdefault("captcha_attempts", []).append({ "attempt": attempt_num, "captcha_text": captcha_text, "variant": variant, "status": status, "screenshot": screenshot_path, "error": error }) if not solve_and_fill_captcha(driver, max_retries=1, attempt_callback=attempt_callback): print(" Automatic CAPTCHA solve failed; retrying until the login deadline.") driver.refresh() time.sleep(3) continue # solve_and_fill_captcha already clicks submit, so just wait and check result time.sleep(LOGIN_POST_SUBMIT_WAIT_SECONDS) accept_browser_alert_if_present(driver) # Handle any popups that appear handle_transient_popups(driver) # Check for CAPTCHA error (invalid captcha message) captcha_error = check_captcha_error(driver) if captcha_error: print(f" CAPTCHA validation failed: {captcha_error}") # Reload the page to get a fresh CAPTCHA and keep retrying until # the overall login deadline is reached. print(f" Reloading page to get fresh CAPTCHA (attempt {captcha_attempt + 1})...") driver.refresh() time.sleep(3) continue # Check for successful login - use improved detection if check_login_success(driver): print("Login successful") if captcha_metrics is not None: captcha_metrics["correct_attempt"] = captcha_attempt close_tac_expiry_modal_if_present(driver) return True # If we get here, something else is wrong print(" Login not confirmed, retrying...") driver.refresh() time.sleep(3) 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) def visible_elements(xpath): return [element for element in driver.find_elements(By.XPATH, xpath) if element.is_displayed()] submenu_visible = any( item.is_enabled() for item in visible_elements(menu_xpath) ) if not submenu_visible: maintenance_items = visible_elements(Locator.tradingmaintenance) if not maintenance_items: wait.until(lambda current_driver: visible_elements(Locator.tradingmaintenance)) maintenance_items = visible_elements(Locator.tradingmaintenance) maintenance = maintenance_items[0] driver.execute_script( "arguments[0].scrollIntoView({block: 'center'});", maintenance ) try: wait.until(lambda current_driver: maintenance.is_enabled()) maintenance.click() except Exception: driver.execute_script("arguments[0].click();", maintenance) menu_item = wait.until( lambda current_driver: next( (item for item in visible_elements(menu_xpath) if item.is_enabled()), False, ) ) 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)