Add CAPTCHA automation beta flow
This commit is contained in:
+200
-27
@@ -57,43 +57,216 @@ def enter_pressed():
|
||||
return pressed
|
||||
|
||||
|
||||
def login(driver, username, password):
|
||||
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)
|
||||
|
||||
# username
|
||||
userid_field = wait.until(
|
||||
EC.visibility_of_element_located((By.XPATH, Locator.userid))
|
||||
)
|
||||
userid_field.send_keys(username)
|
||||
# --- 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
|
||||
|
||||
# password
|
||||
password_field = driver.find_element(By.XPATH, Locator.password)
|
||||
password_field.send_keys(password)
|
||||
# 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)
|
||||
|
||||
print(f"Solve captcha within {LOGIN_CAPTCHA_WAIT_SECONDS} seconds...")
|
||||
password_field = wait.until(
|
||||
EC.visibility_of_element_located((By.XPATH, Locator.password))
|
||||
)
|
||||
password_field.clear()
|
||||
password_field.send_keys(password)
|
||||
|
||||
# wait for manual captcha solve
|
||||
time.sleep(LOGIN_CAPTCHA_WAIT_SECONDS)
|
||||
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})...")
|
||||
|
||||
# click submit
|
||||
submit_btn = wait.until(
|
||||
EC.element_to_be_clickable((By.XPATH, Locator.submitbtn))
|
||||
)
|
||||
submit_btn.click()
|
||||
# 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")
|
||||
|
||||
time.sleep(LOGIN_POST_SUBMIT_WAIT_SECONDS)
|
||||
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)
|
||||
|
||||
accept_browser_alert_if_present(driver)
|
||||
captcha_error = check_captcha_error(driver)
|
||||
if captcha_error:
|
||||
print(f" Manual CAPTCHA rejected: {captcha_error}")
|
||||
continue
|
||||
|
||||
# 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")
|
||||
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):
|
||||
|
||||
Reference in New Issue
Block a user