1164 lines
58 KiB
Python
1164 lines
58 KiB
Python
from selenium import webdriver
|
|
from selenium.common.exceptions import WebDriverException, TimeoutException
|
|
from selenium.webdriver.chrome.service import Service
|
|
from selenium.webdriver.common.by import By
|
|
from selenium.webdriver.common.keys import Keys
|
|
from selenium.webdriver.support.ui import WebDriverWait
|
|
from selenium.webdriver.support import expected_conditions as EC
|
|
from webdriver_manager.chrome import ChromeDriverManager
|
|
import time
|
|
import os
|
|
import base64
|
|
|
|
from unitedsco_browser_manager import get_browser_manager
|
|
|
|
class AutomationUnitedSCOEligibilityCheck:
|
|
def __init__(self, data):
|
|
self.headless = False
|
|
self.driver = None
|
|
|
|
self.data = data.get("data", {}) if isinstance(data, dict) else {}
|
|
|
|
# Flatten values for convenience
|
|
self.memberId = self.data.get("memberId", "")
|
|
self.dateOfBirth = self.data.get("dateOfBirth", "")
|
|
self.firstName = self.data.get("firstName", "")
|
|
self.lastName = self.data.get("lastName", "")
|
|
self.unitedsco_username = self.data.get("unitedscoUsername", "")
|
|
self.unitedsco_password = self.data.get("unitedscoPassword", "")
|
|
|
|
# Use browser manager's download dir
|
|
self.download_dir = get_browser_manager().download_dir
|
|
os.makedirs(self.download_dir, exist_ok=True)
|
|
|
|
def config_driver(self):
|
|
# Use persistent browser from manager (keeps device trust tokens)
|
|
self.driver = get_browser_manager().get_driver(self.headless)
|
|
|
|
def _force_logout(self):
|
|
"""Force logout by clearing cookies for United SCO domain."""
|
|
try:
|
|
print("[UnitedSCO login] Forcing logout due to credential change...")
|
|
browser_manager = get_browser_manager()
|
|
|
|
# First try to click logout button if visible
|
|
try:
|
|
self.driver.get("https://app.dentalhub.com/app/dashboard")
|
|
time.sleep(2)
|
|
|
|
logout_selectors = [
|
|
"//button[contains(text(), 'Log out') or contains(text(), 'Logout') or contains(text(), 'Sign out')]",
|
|
"//a[contains(text(), 'Log out') or contains(text(), 'Logout') or contains(text(), 'Sign out')]",
|
|
"//button[@aria-label='Log out' or @aria-label='Logout' or @aria-label='Sign out']",
|
|
"//*[contains(@class, 'logout') or contains(@class, 'signout')]"
|
|
]
|
|
|
|
for selector in logout_selectors:
|
|
try:
|
|
logout_btn = WebDriverWait(self.driver, 3).until(
|
|
EC.element_to_be_clickable((By.XPATH, selector))
|
|
)
|
|
logout_btn.click()
|
|
print("[UnitedSCO login] Clicked logout button")
|
|
time.sleep(2)
|
|
break
|
|
except TimeoutException:
|
|
continue
|
|
except Exception as e:
|
|
print(f"[UnitedSCO login] Could not click logout button: {e}")
|
|
|
|
# Clear cookies as backup
|
|
try:
|
|
self.driver.delete_all_cookies()
|
|
print("[UnitedSCO login] Cleared all cookies")
|
|
except Exception as e:
|
|
print(f"[UnitedSCO login] Error clearing cookies: {e}")
|
|
|
|
browser_manager.clear_credentials_hash()
|
|
print("[UnitedSCO login] Logout complete")
|
|
return True
|
|
except Exception as e:
|
|
print(f"[UnitedSCO login] Error during forced logout: {e}")
|
|
return False
|
|
|
|
def login(self, url):
|
|
wait = WebDriverWait(self.driver, 30)
|
|
browser_manager = get_browser_manager()
|
|
|
|
try:
|
|
# Check if credentials have changed - if so, force logout first
|
|
if self.unitedsco_username and browser_manager.credentials_changed(self.unitedsco_username):
|
|
self._force_logout()
|
|
self.driver.get(url)
|
|
time.sleep(2)
|
|
|
|
# First check if we're already on a logged-in page (from previous run)
|
|
try:
|
|
current_url = self.driver.current_url
|
|
print(f"[UnitedSCO login] Current URL: {current_url}")
|
|
|
|
# Check if we're already on dentalhub dashboard (not the login page)
|
|
if "app.dentalhub.com" in current_url and "login" not in current_url.lower():
|
|
try:
|
|
# Look for dashboard element or member search
|
|
dashboard_elem = WebDriverWait(self.driver, 3).until(
|
|
EC.presence_of_element_located((By.XPATH,
|
|
'//input[contains(@placeholder,"Search")] | //*[contains(@class,"dashboard")] | '
|
|
'//a[contains(@href,"member")] | //nav'))
|
|
)
|
|
print("[UnitedSCO login] Already logged in - on dashboard")
|
|
return "ALREADY_LOGGED_IN"
|
|
except TimeoutException:
|
|
pass
|
|
except Exception as e:
|
|
print(f"[UnitedSCO login] Error checking current state: {e}")
|
|
|
|
# Navigate to login URL
|
|
self.driver.get(url)
|
|
time.sleep(3)
|
|
|
|
current_url = self.driver.current_url
|
|
print(f"[UnitedSCO login] After navigation URL: {current_url}")
|
|
|
|
# If already on dentalhub dashboard (not login page), we're logged in
|
|
if "app.dentalhub.com" in current_url and "login" not in current_url.lower():
|
|
print("[UnitedSCO login] Already on dashboard")
|
|
return "ALREADY_LOGGED_IN"
|
|
|
|
# Check for OTP input first (in case we're on B2C OTP page)
|
|
try:
|
|
otp_input = WebDriverWait(self.driver, 3).until(
|
|
EC.presence_of_element_located((By.XPATH,
|
|
"//input[@type='tel' or contains(@placeholder,'code') or contains(@aria-label,'Verification')]"))
|
|
)
|
|
print("[UnitedSCO login] OTP input found")
|
|
return "OTP_REQUIRED"
|
|
except TimeoutException:
|
|
pass
|
|
|
|
# Step 1: Click the LOGIN button on the initial dentalhub page
|
|
# This redirects to Azure B2C login
|
|
if "app.dentalhub.com" in current_url:
|
|
try:
|
|
login_btn = WebDriverWait(self.driver, 5).until(
|
|
EC.element_to_be_clickable((By.XPATH,
|
|
"//button[contains(text(),'LOGIN') or contains(text(),'Log In') or contains(text(),'Login')]"))
|
|
)
|
|
login_btn.click()
|
|
print("[UnitedSCO login] Clicked LOGIN button on dentalhub.com")
|
|
time.sleep(5) # Wait for redirect to B2C login page
|
|
except TimeoutException:
|
|
print("[UnitedSCO login] No LOGIN button found on dentalhub page, proceeding...")
|
|
|
|
# Now we should be on the Azure B2C login page (dentalhubauth.b2clogin.com)
|
|
current_url = self.driver.current_url
|
|
print(f"[UnitedSCO login] After LOGIN click URL: {current_url}")
|
|
|
|
# Step 2: Fill in credentials on B2C login page
|
|
if "b2clogin.com" in current_url or "login" in current_url.lower():
|
|
print("[UnitedSCO login] On B2C login page - filling credentials")
|
|
|
|
try:
|
|
# Find email field by id="signInName" (Azure B2C specific)
|
|
email_field = WebDriverWait(self.driver, 10).until(
|
|
EC.element_to_be_clickable((By.XPATH,
|
|
"//input[@id='signInName' or @name='signInName' or @name='Email address' or @type='email']"))
|
|
)
|
|
email_field.clear()
|
|
email_field.send_keys(self.unitedsco_username)
|
|
print(f"[UnitedSCO login] Entered username: {self.unitedsco_username}")
|
|
|
|
# Find password field by id="password"
|
|
password_field = WebDriverWait(self.driver, 10).until(
|
|
EC.presence_of_element_located((By.XPATH,
|
|
"//input[@id='password' or @type='password']"))
|
|
)
|
|
password_field.clear()
|
|
password_field.send_keys(self.unitedsco_password)
|
|
print("[UnitedSCO login] Entered password")
|
|
|
|
# Click "Sign in" button (id="next" on B2C page)
|
|
signin_button = WebDriverWait(self.driver, 10).until(
|
|
EC.element_to_be_clickable((By.XPATH,
|
|
"//button[@id='next'] | //button[@type='submit' and contains(text(),'Sign')]"))
|
|
)
|
|
signin_button.click()
|
|
print("[UnitedSCO login] Clicked Sign in button")
|
|
|
|
# Save credentials hash after login attempt
|
|
if self.unitedsco_username:
|
|
browser_manager.save_credentials_hash(self.unitedsco_username)
|
|
|
|
time.sleep(5) # Wait for login to process
|
|
|
|
# Check for MFA method selection page
|
|
# DentalHub shows: "Phone" / "Authenticator App" radio buttons + "Continue" button
|
|
try:
|
|
continue_btn = self.driver.find_element(By.XPATH,
|
|
"//button[contains(text(),'Continue')]"
|
|
)
|
|
# Check if "Phone" radio is present (MFA selection page)
|
|
phone_elements = self.driver.find_elements(By.XPATH,
|
|
"//*[contains(text(),'Phone')]"
|
|
)
|
|
if continue_btn and phone_elements:
|
|
print("[UnitedSCO login] MFA method selection page detected")
|
|
# Select "Phone" radio button if not already selected
|
|
try:
|
|
phone_radio = self.driver.find_element(By.XPATH,
|
|
"//input[@type='radio' and (contains(@value,'phone') or contains(@value,'Phone'))] | "
|
|
"//label[contains(text(),'Phone')]/preceding-sibling::input[@type='radio'] | "
|
|
"//label[contains(text(),'Phone')]//input[@type='radio'] | "
|
|
"//input[@type='radio'][following-sibling::*[contains(text(),'Phone')]] | "
|
|
"//input[@type='radio']"
|
|
)
|
|
if phone_radio and not phone_radio.is_selected():
|
|
phone_radio.click()
|
|
print("[UnitedSCO login] Selected 'Phone' radio button")
|
|
else:
|
|
print("[UnitedSCO login] 'Phone' already selected")
|
|
except Exception as radio_err:
|
|
print(f"[UnitedSCO login] Could not click Phone radio (may already be selected): {radio_err}")
|
|
# Try clicking the label text instead
|
|
try:
|
|
phone_label = self.driver.find_element(By.XPATH, "//*[contains(text(),'Phone') and not(contains(text(),'Authenticator'))]")
|
|
phone_label.click()
|
|
print("[UnitedSCO login] Clicked 'Phone' label")
|
|
except Exception:
|
|
pass
|
|
|
|
time.sleep(1)
|
|
# Click Continue
|
|
continue_btn.click()
|
|
print("[UnitedSCO login] Clicked 'Continue' on MFA selection page")
|
|
time.sleep(5) # Wait for OTP to be sent
|
|
except Exception:
|
|
pass # No MFA selection page - proceed normally
|
|
|
|
# Check if login succeeded (redirected back to dentalhub dashboard)
|
|
current_url_after_login = self.driver.current_url.lower()
|
|
print(f"[UnitedSCO login] After login URL: {current_url_after_login}")
|
|
|
|
if "app.dentalhub.com" in current_url_after_login and "login" not in current_url_after_login:
|
|
print("[UnitedSCO login] Login successful - redirected to dashboard")
|
|
return "SUCCESS"
|
|
|
|
# Check for OTP input after login / after MFA selection
|
|
try:
|
|
otp_input = WebDriverWait(self.driver, 15).until(
|
|
EC.presence_of_element_located((By.XPATH,
|
|
"//input[@type='tel' or contains(@placeholder,'code') or contains(@placeholder,'Code') or "
|
|
"contains(@aria-label,'Verification') or contains(@aria-label,'verification') or "
|
|
"contains(@aria-label,'Code') or contains(@aria-label,'code') or "
|
|
"contains(@placeholder,'verification') or contains(@placeholder,'Verification') or "
|
|
"contains(@name,'otp') or contains(@name,'code') or contains(@id,'otp') or contains(@id,'code')]"
|
|
))
|
|
)
|
|
print("[UnitedSCO login] OTP input detected -> OTP_REQUIRED")
|
|
return "OTP_REQUIRED"
|
|
except TimeoutException:
|
|
print("[UnitedSCO login] No OTP input detected")
|
|
|
|
# Re-check dashboard after waiting for OTP check
|
|
current_url_after_login = self.driver.current_url.lower()
|
|
if "app.dentalhub.com" in current_url_after_login and "login" not in current_url_after_login:
|
|
print("[UnitedSCO login] Login successful - redirected to dashboard")
|
|
return "SUCCESS"
|
|
|
|
# Check for error messages on B2C page
|
|
try:
|
|
error_elem = self.driver.find_element(By.XPATH,
|
|
"//*[contains(@class,'error') or contains(@class,'alert')]")
|
|
error_text = error_elem.text
|
|
if error_text:
|
|
print(f"[UnitedSCO login] Error on page: {error_text}")
|
|
return f"ERROR: {error_text}"
|
|
except:
|
|
pass
|
|
|
|
# Still on B2C page - might need OTP or login failed
|
|
if "b2clogin.com" in current_url_after_login:
|
|
print("[UnitedSCO login] Still on B2C page - checking for OTP or error")
|
|
# Give it more time for OTP
|
|
try:
|
|
otp_input = WebDriverWait(self.driver, 10).until(
|
|
EC.presence_of_element_located((By.XPATH,
|
|
"//input[@type='tel' or contains(@id,'code') or contains(@name,'code')]"))
|
|
)
|
|
print("[UnitedSCO login] OTP input found on second check")
|
|
return "OTP_REQUIRED"
|
|
except TimeoutException:
|
|
return "ERROR: Login failed - still on B2C page"
|
|
|
|
except TimeoutException as te:
|
|
print(f"[UnitedSCO login] Login form elements not found: {te}")
|
|
return "ERROR: Login form not found"
|
|
except Exception as form_err:
|
|
print(f"[UnitedSCO login] Error filling form: {form_err}")
|
|
return f"ERROR: {form_err}"
|
|
|
|
# If we got here without going through login, we're already logged in
|
|
return "SUCCESS"
|
|
|
|
except Exception as e:
|
|
print(f"[UnitedSCO login] Exception: {e}")
|
|
return f"ERROR:LOGIN FAILED: {e}"
|
|
|
|
def _check_for_error_dialog(self):
|
|
"""Check for and dismiss common error dialogs. Returns error message string or None."""
|
|
error_patterns = [
|
|
("Patient Not Found", "Patient Not Found - please check the Subscriber ID, DOB, and Payer selection"),
|
|
("Insufficient Information", "Insufficient Information - need Subscriber ID + DOB, or First Name + Last Name + DOB"),
|
|
("No Eligibility", "No eligibility information found for this patient"),
|
|
("Error", None), # Generic error - will use the dialog text
|
|
]
|
|
|
|
for pattern, default_msg in error_patterns:
|
|
try:
|
|
dialog_elem = self.driver.find_element(By.XPATH,
|
|
f"//modal-container//*[contains(text(),'{pattern}')] | "
|
|
f"//div[contains(@class,'modal')]//*[contains(text(),'{pattern}')]"
|
|
)
|
|
if dialog_elem.is_displayed():
|
|
# Get the full dialog text for logging
|
|
try:
|
|
modal = self.driver.find_element(By.XPATH, "//modal-container | //div[contains(@class,'modal-dialog')]")
|
|
dialog_text = modal.text.strip()[:200]
|
|
except Exception:
|
|
dialog_text = dialog_elem.text.strip()[:200]
|
|
|
|
print(f"[UnitedSCO step1] Error dialog detected: {dialog_text}")
|
|
|
|
# Click OK/Close to dismiss
|
|
try:
|
|
dismiss_btn = self.driver.find_element(By.XPATH,
|
|
"//modal-container//button[contains(text(),'Ok') or contains(text(),'OK') or contains(text(),'Close')] | "
|
|
"//div[contains(@class,'modal')]//button[contains(text(),'Ok') or contains(text(),'OK') or contains(text(),'Close')]"
|
|
)
|
|
dismiss_btn.click()
|
|
print("[UnitedSCO step1] Dismissed error dialog")
|
|
time.sleep(1)
|
|
except Exception:
|
|
# Try clicking the X button
|
|
try:
|
|
close_btn = self.driver.find_element(By.XPATH, "//modal-container//button[@class='close']")
|
|
close_btn.click()
|
|
except Exception:
|
|
pass
|
|
|
|
error_msg = default_msg if default_msg else f"ERROR: {dialog_text}"
|
|
return f"ERROR: {error_msg}"
|
|
except Exception:
|
|
continue
|
|
|
|
return None
|
|
|
|
def _format_dob(self, dob_str):
|
|
"""Convert DOB from YYYY-MM-DD to MM/DD/YYYY format"""
|
|
if dob_str and "-" in dob_str:
|
|
dob_parts = dob_str.split("-")
|
|
if len(dob_parts) == 3:
|
|
# YYYY-MM-DD -> MM/DD/YYYY
|
|
return f"{dob_parts[1]}/{dob_parts[2]}/{dob_parts[0]}"
|
|
return dob_str
|
|
|
|
def step1(self):
|
|
"""
|
|
Navigate to Eligibility page and fill the Patient Information form.
|
|
|
|
Workflow based on actual DOM testing:
|
|
1. Navigate directly to eligibility page
|
|
2. Fill First Name (id='firstName_Back'), Last Name (id='lastName_Back'), DOB (id='dateOfBirth_Back')
|
|
3. Select Payer: "UnitedHealthcare Massachusetts" from ng-select dropdown
|
|
4. Click Continue
|
|
5. Handle Practitioner & Location page - click paymentGroupId dropdown, select Summit Dental Care
|
|
6. Click Continue again
|
|
"""
|
|
from selenium.webdriver.common.action_chains import ActionChains
|
|
|
|
try:
|
|
print(f"[UnitedSCO step1] Starting eligibility search for: {self.firstName} {self.lastName}, DOB: {self.dateOfBirth}")
|
|
|
|
# Navigate directly to eligibility page
|
|
print("[UnitedSCO step1] Navigating to eligibility page...")
|
|
self.driver.get("https://app.dentalhub.com/app/patient/eligibility")
|
|
time.sleep(3)
|
|
|
|
current_url = self.driver.current_url
|
|
print(f"[UnitedSCO step1] Current URL: {current_url}")
|
|
|
|
# Step 1.1: Fill the Patient Information form
|
|
print("[UnitedSCO step1] Filling Patient Information form...")
|
|
|
|
# Wait for form to load - look for First Name field (id='firstName_Back')
|
|
try:
|
|
WebDriverWait(self.driver, 10).until(
|
|
EC.presence_of_element_located((By.ID, "firstName_Back"))
|
|
)
|
|
print("[UnitedSCO step1] Patient Information form loaded")
|
|
except TimeoutException:
|
|
print("[UnitedSCO step1] Patient Information form not found")
|
|
return "ERROR: Patient Information form not found"
|
|
|
|
# Fill Subscriber ID / Medicaid ID if memberId is provided
|
|
# The field is labeled "Subscriber ID or Medicaid ID" on the DentalHub form
|
|
# Actual DOM field id is 'subscriberId_Front' (not 'subscriberId_Back')
|
|
if self.memberId:
|
|
try:
|
|
subscriber_id_selectors = [
|
|
"//input[@id='subscriberId_Front']",
|
|
"//input[@id='subscriberId_Back' or @id='subscriberID_Back']",
|
|
"//input[@id='memberId_Back' or @id='memberid_Back']",
|
|
"//input[@id='medicaidId_Back']",
|
|
"//label[contains(text(),'Subscriber ID')]/..//input[not(@id='firstName_Back') and not(@id='lastName_Back') and not(@id='dateOfBirth_Back')]",
|
|
"//input[contains(@placeholder,'Subscriber') or contains(@placeholder,'subscriber')]",
|
|
"//input[contains(@placeholder,'Medicaid') or contains(@placeholder,'medicaid')]",
|
|
"//input[contains(@placeholder,'Member') or contains(@placeholder,'member')]",
|
|
]
|
|
subscriber_filled = False
|
|
for sel in subscriber_id_selectors:
|
|
try:
|
|
sid_input = self.driver.find_element(By.XPATH, sel)
|
|
if sid_input.is_displayed():
|
|
sid_input.clear()
|
|
sid_input.send_keys(self.memberId)
|
|
field_id = sid_input.get_attribute("id") or "unknown"
|
|
print(f"[UnitedSCO step1] Entered Subscriber ID: {self.memberId} (field id='{field_id}')")
|
|
subscriber_filled = True
|
|
break
|
|
except Exception:
|
|
continue
|
|
|
|
if not subscriber_filled:
|
|
# Fallback: find visible input that is NOT a known field
|
|
try:
|
|
all_inputs = self.driver.find_elements(By.XPATH,
|
|
"//form//input[@type='text' or not(@type)]"
|
|
)
|
|
known_ids = {'firstName_Back', 'lastName_Back', 'dateOfBirth_Back', 'procedureDate_Back', 'insurerId'}
|
|
for inp in all_inputs:
|
|
inp_id = inp.get_attribute("id") or ""
|
|
if inp_id not in known_ids and inp.is_displayed():
|
|
inp.clear()
|
|
inp.send_keys(self.memberId)
|
|
print(f"[UnitedSCO step1] Entered Subscriber ID in field id='{inp_id}': {self.memberId}")
|
|
subscriber_filled = True
|
|
break
|
|
except Exception as e2:
|
|
print(f"[UnitedSCO step1] Fallback subscriber field search error: {e2}")
|
|
|
|
if not subscriber_filled:
|
|
print(f"[UnitedSCO step1] WARNING: Could not find Subscriber ID field (ID: {self.memberId})")
|
|
except Exception as e:
|
|
print(f"[UnitedSCO step1] Error entering Subscriber ID: {e}")
|
|
|
|
# Fill First Name (id='firstName_Back') - only if provided
|
|
if self.firstName:
|
|
try:
|
|
first_name_input = self.driver.find_element(By.ID, "firstName_Back")
|
|
first_name_input.clear()
|
|
first_name_input.send_keys(self.firstName)
|
|
print(f"[UnitedSCO step1] Entered First Name: {self.firstName}")
|
|
except Exception as e:
|
|
print(f"[UnitedSCO step1] Error entering First Name: {e}")
|
|
else:
|
|
print("[UnitedSCO step1] No First Name provided, skipping")
|
|
|
|
# Fill Last Name (id='lastName_Back') - only if provided
|
|
if self.lastName:
|
|
try:
|
|
last_name_input = self.driver.find_element(By.ID, "lastName_Back")
|
|
last_name_input.clear()
|
|
last_name_input.send_keys(self.lastName)
|
|
print(f"[UnitedSCO step1] Entered Last Name: {self.lastName}")
|
|
except Exception as e:
|
|
print(f"[UnitedSCO step1] Error entering Last Name: {e}")
|
|
else:
|
|
print("[UnitedSCO step1] No Last Name provided, skipping")
|
|
|
|
# Fill Date of Birth (id='dateOfBirth_Back', format: MM/DD/YYYY)
|
|
try:
|
|
dob_input = self.driver.find_element(By.ID, "dateOfBirth_Back")
|
|
dob_input.clear()
|
|
dob_formatted = self._format_dob(self.dateOfBirth)
|
|
dob_input.send_keys(dob_formatted)
|
|
print(f"[UnitedSCO step1] Entered DOB: {dob_formatted}")
|
|
except Exception as e:
|
|
print(f"[UnitedSCO step1] Error entering DOB: {e}")
|
|
return "ERROR: Could not enter Date of Birth"
|
|
|
|
time.sleep(1)
|
|
|
|
# Step 1.2: Select Payer - UnitedHealthcare Massachusetts
|
|
print("[UnitedSCO step1] Selecting Payer...")
|
|
|
|
# First dismiss any blocking dialogs (e.g. Chrome password save)
|
|
try:
|
|
self.driver.execute_script("""
|
|
// Dismiss Chrome password manager popup if present
|
|
var dialogs = document.querySelectorAll('[role="dialog"], .cdk-overlay-container');
|
|
dialogs.forEach(function(d) { d.style.display = 'none'; });
|
|
""")
|
|
except Exception:
|
|
pass
|
|
|
|
payer_selected = False
|
|
|
|
# Strategy 1: Click the ng-select, type to search, and select the option
|
|
try:
|
|
# Find the Payer ng-select by multiple selectors
|
|
payer_selectors = [
|
|
"//label[contains(text(),'Payer')]/following-sibling::ng-select",
|
|
"//label[contains(text(),'Payer')]/..//ng-select",
|
|
"//ng-select[contains(@placeholder,'Payer') or contains(@placeholder,'payer')]",
|
|
"//ng-select[.//input[contains(@placeholder,'Search by Payers')]]",
|
|
]
|
|
payer_ng_select = None
|
|
for sel in payer_selectors:
|
|
try:
|
|
payer_ng_select = self.driver.find_element(By.XPATH, sel)
|
|
if payer_ng_select.is_displayed():
|
|
break
|
|
except Exception:
|
|
continue
|
|
|
|
if payer_ng_select:
|
|
# Scroll to it and click to open
|
|
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", payer_ng_select)
|
|
time.sleep(0.5)
|
|
payer_ng_select.click()
|
|
time.sleep(1)
|
|
|
|
# Type into the search input inside ng-select to filter options
|
|
try:
|
|
search_input = payer_ng_select.find_element(By.XPATH, ".//input[contains(@type,'text') or contains(@role,'combobox')]")
|
|
search_input.clear()
|
|
search_input.send_keys("UnitedHealthcare Massachusetts")
|
|
print("[UnitedSCO step1] Typed payer search text")
|
|
time.sleep(2)
|
|
except Exception:
|
|
# If no search input, try sending keys directly to ng-select
|
|
try:
|
|
ActionChains(self.driver).send_keys("UnitedHealthcare Mass").perform()
|
|
print("[UnitedSCO step1] Typed payer search via ActionChains")
|
|
time.sleep(2)
|
|
except Exception:
|
|
pass
|
|
|
|
# Find and click the matching option
|
|
payer_options = self.driver.find_elements(By.XPATH,
|
|
"//ng-dropdown-panel//div[contains(@class,'ng-option')]"
|
|
)
|
|
for opt in payer_options:
|
|
opt_text = opt.text.strip()
|
|
if "UnitedHealthcare Massachusetts" in opt_text:
|
|
opt.click()
|
|
print(f"[UnitedSCO step1] Selected Payer: {opt_text}")
|
|
payer_selected = True
|
|
break
|
|
|
|
if not payer_selected and payer_options:
|
|
# Select first visible option if it contains "United"
|
|
for opt in payer_options:
|
|
opt_text = opt.text.strip()
|
|
if "United" in opt_text and opt.is_displayed():
|
|
opt.click()
|
|
print(f"[UnitedSCO step1] Selected first matching Payer: {opt_text}")
|
|
payer_selected = True
|
|
break
|
|
|
|
# Close dropdown
|
|
ActionChains(self.driver).send_keys(Keys.ESCAPE).perform()
|
|
time.sleep(0.5)
|
|
else:
|
|
print("[UnitedSCO step1] Could not find Payer ng-select element")
|
|
|
|
except Exception as e:
|
|
print(f"[UnitedSCO step1] Payer selection strategy 1 error: {e}")
|
|
try:
|
|
ActionChains(self.driver).send_keys(Keys.ESCAPE).perform()
|
|
except Exception:
|
|
pass
|
|
|
|
# Strategy 2: JavaScript direct selection if strategy 1 failed
|
|
if not payer_selected:
|
|
try:
|
|
# Try clicking via JavaScript
|
|
clicked = self.driver.execute_script("""
|
|
// Find ng-select near the Payer label
|
|
var labels = document.querySelectorAll('label');
|
|
for (var i = 0; i < labels.length; i++) {
|
|
if (labels[i].textContent.includes('Payer')) {
|
|
var parent = labels[i].parentElement;
|
|
var ngSelect = parent.querySelector('ng-select') || labels[i].nextElementSibling;
|
|
if (ngSelect) {
|
|
ngSelect.click();
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
""")
|
|
if clicked:
|
|
time.sleep(1)
|
|
ActionChains(self.driver).send_keys("UnitedHealthcare Mass").perform()
|
|
time.sleep(2)
|
|
payer_options = self.driver.find_elements(By.XPATH,
|
|
"//ng-dropdown-panel//div[contains(@class,'ng-option')]"
|
|
)
|
|
for opt in payer_options:
|
|
if "UnitedHealthcare" in opt.text and "Massachusetts" in opt.text:
|
|
opt.click()
|
|
print(f"[UnitedSCO step1] Selected Payer via JS: {opt.text.strip()}")
|
|
payer_selected = True
|
|
break
|
|
ActionChains(self.driver).send_keys(Keys.ESCAPE).perform()
|
|
except Exception as e:
|
|
print(f"[UnitedSCO step1] Payer selection strategy 2 error: {e}")
|
|
|
|
if not payer_selected:
|
|
print("[UnitedSCO step1] WARNING: Could not select Payer - form may fail")
|
|
|
|
time.sleep(1)
|
|
|
|
# Step 1.3: Click Continue button (Step 1 - Patient Info)
|
|
try:
|
|
continue_btn = WebDriverWait(self.driver, 10).until(
|
|
EC.element_to_be_clickable((By.XPATH, "//button[contains(text(),'Continue')]"))
|
|
)
|
|
continue_btn.click()
|
|
print("[UnitedSCO step1] Clicked Continue button (Patient Info)")
|
|
time.sleep(4)
|
|
|
|
# Check for error dialogs (modal) after clicking Continue
|
|
error_result = self._check_for_error_dialog()
|
|
if error_result:
|
|
return error_result
|
|
|
|
except Exception as e:
|
|
print(f"[UnitedSCO step1] Error clicking Continue: {e}")
|
|
return "ERROR: Could not click Continue button"
|
|
|
|
# Step 1.4: Handle Practitioner & Location page
|
|
# First check if we actually moved to the Practitioner page
|
|
# by looking for Practitioner-specific elements
|
|
print("[UnitedSCO step1] Handling Practitioner & Location page...")
|
|
|
|
on_practitioner_page = False
|
|
try:
|
|
# Check for Practitioner page elements (paymentGroupId or treatment location)
|
|
WebDriverWait(self.driver, 8).until(
|
|
lambda d: d.find_element(By.ID, "paymentGroupId").is_displayed() or
|
|
d.find_element(By.ID, "treatmentLocation").is_displayed()
|
|
)
|
|
on_practitioner_page = True
|
|
print("[UnitedSCO step1] Practitioner & Location page loaded")
|
|
except Exception:
|
|
# Check if we're already on results page (3rd step)
|
|
try:
|
|
results_elem = self.driver.find_element(By.XPATH,
|
|
"//*[contains(text(),'Selected Patient') or contains(@id,'patient-name') or contains(@id,'eligibility')]"
|
|
)
|
|
if results_elem.is_displayed():
|
|
print("[UnitedSCO step1] Already on Eligibility Results page (skipped Practitioner)")
|
|
return "Success"
|
|
except Exception:
|
|
pass
|
|
|
|
# Check for error dialog again
|
|
error_result = self._check_for_error_dialog()
|
|
if error_result:
|
|
return error_result
|
|
|
|
print("[UnitedSCO step1] Practitioner page not detected, attempting to continue...")
|
|
|
|
if on_practitioner_page:
|
|
try:
|
|
# Click Practitioner Taxonomy dropdown (id='paymentGroupId')
|
|
taxonomy_input = self.driver.find_element(By.ID, "paymentGroupId")
|
|
if taxonomy_input.is_displayed():
|
|
taxonomy_input.click()
|
|
print("[UnitedSCO step1] Clicked Practitioner Taxonomy dropdown")
|
|
time.sleep(1)
|
|
|
|
# Select "Summit Dental Care" option
|
|
try:
|
|
summit_option = WebDriverWait(self.driver, 5).until(
|
|
EC.element_to_be_clickable((By.XPATH,
|
|
"//ng-dropdown-panel//div[contains(@class,'ng-option') and contains(.,'Summit Dental Care')]"
|
|
))
|
|
)
|
|
summit_option.click()
|
|
print("[UnitedSCO step1] Selected: Summit Dental Care")
|
|
except TimeoutException:
|
|
# Select first available option
|
|
try:
|
|
first_option = self.driver.find_element(By.XPATH,
|
|
"//ng-dropdown-panel//div[contains(@class,'ng-option')]"
|
|
)
|
|
option_text = first_option.text.strip()
|
|
first_option.click()
|
|
print(f"[UnitedSCO step1] Selected first available: {option_text}")
|
|
except Exception:
|
|
print("[UnitedSCO step1] No options available in Practitioner dropdown")
|
|
|
|
# Press Escape to close dropdown
|
|
ActionChains(self.driver).send_keys(Keys.ESCAPE).perform()
|
|
time.sleep(1)
|
|
except Exception as e:
|
|
print(f"[UnitedSCO step1] Practitioner Taxonomy handling: {e}")
|
|
|
|
# Step 1.5: Click Continue button (Step 2 - Practitioner)
|
|
try:
|
|
continue_btn2 = WebDriverWait(self.driver, 10).until(
|
|
EC.element_to_be_clickable((By.XPATH, "//button[contains(text(),'Continue')]"))
|
|
)
|
|
continue_btn2.click()
|
|
print("[UnitedSCO step1] Clicked Continue button (Practitioner)")
|
|
time.sleep(5)
|
|
except Exception as e:
|
|
print(f"[UnitedSCO step1] Error clicking Continue on Practitioner page: {e}")
|
|
# Check for error dialog intercepting the click
|
|
error_result = self._check_for_error_dialog()
|
|
if error_result:
|
|
return error_result
|
|
|
|
# Final check for error dialogs after the search
|
|
error_result = self._check_for_error_dialog()
|
|
if error_result:
|
|
return error_result
|
|
|
|
print("[UnitedSCO step1] Patient search completed successfully")
|
|
return "Success"
|
|
|
|
except Exception as e:
|
|
print(f"[UnitedSCO step1] Exception: {e}")
|
|
return f"ERROR:STEP1 - {e}"
|
|
|
|
|
|
def _get_existing_downloads(self):
|
|
"""Get set of existing PDF files in download dir before clicking."""
|
|
import glob
|
|
return set(glob.glob(os.path.join(self.download_dir, "*.pdf")))
|
|
|
|
def _wait_for_new_download(self, existing_files, timeout=15):
|
|
"""Wait for a new PDF file to appear in the download dir."""
|
|
import glob
|
|
for _ in range(timeout * 2): # check every 0.5s
|
|
time.sleep(0.5)
|
|
current = set(glob.glob(os.path.join(self.download_dir, "*.pdf")))
|
|
new_files = current - existing_files
|
|
if new_files:
|
|
# Also wait for download to finish (no .crdownload files)
|
|
crdownloads = glob.glob(os.path.join(self.download_dir, "*.crdownload"))
|
|
if not crdownloads:
|
|
return list(new_files)[0]
|
|
return None
|
|
|
|
def step2(self):
|
|
"""
|
|
Extract data from Selected Patient page, click the "Eligibility" tab
|
|
to navigate to the eligibility details page, then capture PDF.
|
|
|
|
The "Eligibility" tab at the bottom (next to "Benefit Summary" and
|
|
"Service History") may:
|
|
a) Open a new browser tab with eligibility details
|
|
b) Download a PDF file
|
|
c) Load content dynamically on the same page
|
|
We handle all three cases.
|
|
"""
|
|
import glob
|
|
import re
|
|
|
|
try:
|
|
print("[UnitedSCO step2] Starting eligibility capture")
|
|
|
|
# Wait for page to load
|
|
time.sleep(3)
|
|
|
|
current_url = self.driver.current_url
|
|
print(f"[UnitedSCO step2] Current URL: {current_url}")
|
|
|
|
# 1) Extract eligibility status and Member ID from the Selected Patient page
|
|
eligibilityText = "unknown"
|
|
patientName = f"{self.firstName} {self.lastName}".strip()
|
|
foundMemberId = self.memberId # Use provided memberId as default
|
|
|
|
# Extract eligibility status
|
|
try:
|
|
status_elem = WebDriverWait(self.driver, 10).until(
|
|
EC.presence_of_element_located((By.XPATH,
|
|
"//*[contains(text(),'Member Eligible') or contains(text(),'member eligible')]"
|
|
))
|
|
)
|
|
status_text = status_elem.text.strip().lower()
|
|
print(f"[UnitedSCO step2] Found status: {status_text}")
|
|
|
|
if "eligible" in status_text:
|
|
eligibilityText = "active"
|
|
elif "ineligible" in status_text or "not eligible" in status_text:
|
|
eligibilityText = "inactive"
|
|
|
|
except TimeoutException:
|
|
print("[UnitedSCO step2] Eligibility status badge not found")
|
|
except Exception as e:
|
|
print(f"[UnitedSCO step2] Error extracting status: {e}")
|
|
|
|
print(f"[UnitedSCO step2] Eligibility status: {eligibilityText}")
|
|
|
|
# Extract patient name from the page
|
|
page_text = ""
|
|
try:
|
|
page_text = self.driver.find_element(By.TAG_NAME, "body").text
|
|
except Exception:
|
|
pass
|
|
|
|
# Log a snippet of page text around "Selected Patient" for debugging
|
|
try:
|
|
sp_idx = page_text.find("Selected Patient")
|
|
if sp_idx >= 0:
|
|
snippet = page_text[sp_idx:sp_idx+300]
|
|
print(f"[UnitedSCO step2] Page text near 'Selected Patient': {repr(snippet[:200])}")
|
|
except Exception:
|
|
pass
|
|
|
|
# Strategy 1: Try DOM element id="patient-name"
|
|
name_extracted = False
|
|
try:
|
|
name_elem = self.driver.find_element(By.ID, "patient-name")
|
|
extracted_name = name_elem.text.strip()
|
|
if extracted_name:
|
|
patientName = extracted_name
|
|
name_extracted = True
|
|
print(f"[UnitedSCO step2] Extracted patient name from DOM (id=patient-name): {patientName}")
|
|
except Exception:
|
|
pass
|
|
|
|
# Strategy 2: Try various DOM patterns for patient name
|
|
if not name_extracted:
|
|
name_selectors = [
|
|
"//*[contains(@class,'patient-name') or contains(@class,'patientName')]",
|
|
"//*[contains(@class,'selected-patient')]//h3 | //*[contains(@class,'selected-patient')]//h4 | //*[contains(@class,'selected-patient')]//strong",
|
|
"//div[contains(@class,'patient')]//h3 | //div[contains(@class,'patient')]//h4",
|
|
"//*[contains(@class,'eligibility__banner')]//h3 | //*[contains(@class,'eligibility__banner')]//h4",
|
|
"//*[contains(@class,'banner__patient')]",
|
|
]
|
|
for sel in name_selectors:
|
|
try:
|
|
elems = self.driver.find_elements(By.XPATH, sel)
|
|
for elem in elems:
|
|
txt = elem.text.strip()
|
|
# Filter: must look like a name (2+ words, starts with uppercase)
|
|
if txt and len(txt.split()) >= 2 and txt[0].isupper() and len(txt) < 60:
|
|
patientName = txt
|
|
name_extracted = True
|
|
print(f"[UnitedSCO step2] Extracted patient name from DOM: {patientName}")
|
|
break
|
|
if name_extracted:
|
|
break
|
|
except Exception:
|
|
continue
|
|
|
|
# Strategy 3: Regex from page text - multiple patterns
|
|
# IMPORTANT: Use [^\n] to avoid matching across newlines (e.g. picking up "Member Eligible")
|
|
if not name_extracted:
|
|
name_patterns = [
|
|
# Name on the line right after "Selected Patient"
|
|
r'Selected Patient\s*\n\s*([A-Z][A-Za-z\-\']+(?: [A-Z][A-Za-z\-\']+)+)',
|
|
r'Patient Name\s*[\n:]\s*([A-Z][A-Za-z\-\']+(?: [A-Z][A-Za-z\-\']+)+)',
|
|
# "LASTNAME, FIRSTNAME" format
|
|
r'Selected Patient\s*\n\s*([A-Z][A-Za-z\-\']+,\s*[A-Z][A-Za-z\-\']+)',
|
|
# Name on the line right before "Member Eligible" or "Member ID"
|
|
r'\n([A-Z][A-Za-z\-\']+(?: [A-Z]\.?)? [A-Z][A-Za-z\-\']+)\n(?:Member|Date Of Birth|DOB)',
|
|
]
|
|
for pattern in name_patterns:
|
|
try:
|
|
name_match = re.search(pattern, page_text)
|
|
if name_match:
|
|
candidate = name_match.group(1).strip()
|
|
# Validate: not too long, not a header/label, and doesn't contain "Eligible"/"Member"/"Patient"
|
|
skip_words = ("Selected Patient", "Patient Name", "Patient Information",
|
|
"Member Eligible", "Member ID", "Date Of Birth")
|
|
if (len(candidate) < 50 and candidate not in skip_words
|
|
and "Eligible" not in candidate and "Member" not in candidate):
|
|
patientName = candidate
|
|
name_extracted = True
|
|
print(f"[UnitedSCO step2] Extracted patient name from text: {patientName}")
|
|
break
|
|
except Exception:
|
|
continue
|
|
|
|
if not name_extracted:
|
|
print(f"[UnitedSCO step2] WARNING: Could not extract patient name from page")
|
|
|
|
# Extract Member ID from the page (for database storage)
|
|
try:
|
|
member_id_match = re.search(r'Member ID\s*[\n:]\s*(\d+)', page_text)
|
|
if member_id_match:
|
|
foundMemberId = member_id_match.group(1)
|
|
print(f"[UnitedSCO step2] Extracted Member ID from page: {foundMemberId}")
|
|
except Exception as e:
|
|
print(f"[UnitedSCO step2] Could not extract Member ID: {e}")
|
|
|
|
# Extract Date of Birth from page if available (for patient creation)
|
|
extractedDob = ""
|
|
try:
|
|
dob_match = re.search(r'Date Of Birth\s*[\n:]\s*(\d{2}/\d{2}/\d{4})', page_text)
|
|
if dob_match:
|
|
extractedDob = dob_match.group(1)
|
|
print(f"[UnitedSCO step2] Extracted DOB from page: {extractedDob}")
|
|
except Exception:
|
|
pass
|
|
|
|
# 2) Click the "Eligibility" button to navigate to eligibility details
|
|
# The DOM has: <button id="eligibility-link" class="btn btn-link">Eligibility</button>
|
|
# This is near "Benefit Summary" and "Service History" buttons.
|
|
print("[UnitedSCO step2] Looking for 'Eligibility' button (id='eligibility-link')...")
|
|
|
|
# Record existing downloads BEFORE clicking (to detect new downloads)
|
|
existing_downloads = self._get_existing_downloads()
|
|
|
|
# Record current window handles BEFORE clicking (to detect new tabs)
|
|
original_window = self.driver.current_window_handle
|
|
original_windows = set(self.driver.window_handles)
|
|
|
|
eligibility_clicked = False
|
|
|
|
# Strategy 1 (PRIMARY): Use the known button id="eligibility-link"
|
|
try:
|
|
# First check if the button exists and is visible
|
|
elig_btn = WebDriverWait(self.driver, 15).until(
|
|
EC.presence_of_element_located((By.ID, "eligibility-link"))
|
|
)
|
|
# Wait for it to become visible (it's hidden when no results)
|
|
WebDriverWait(self.driver, 10).until(
|
|
EC.visibility_of(elig_btn)
|
|
)
|
|
self.driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", elig_btn)
|
|
time.sleep(0.5)
|
|
elig_btn.click()
|
|
eligibility_clicked = True
|
|
print("[UnitedSCO step2] Clicked 'Eligibility' button (id='eligibility-link')")
|
|
time.sleep(5)
|
|
except Exception as e:
|
|
print(f"[UnitedSCO step2] Could not click by ID: {e}")
|
|
|
|
# Strategy 2: Find the button with exact "Eligibility" text (not "Eligibility Check Results" etc.)
|
|
if not eligibility_clicked:
|
|
try:
|
|
buttons = self.driver.find_elements(By.XPATH, "//button")
|
|
for btn in buttons:
|
|
try:
|
|
text = btn.text.strip()
|
|
if re.match(r'^Eligibility\s*$', text, re.IGNORECASE) and btn.is_displayed():
|
|
self.driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", btn)
|
|
time.sleep(0.5)
|
|
btn.click()
|
|
eligibility_clicked = True
|
|
print(f"[UnitedSCO step2] Clicked button with text 'Eligibility'")
|
|
time.sleep(5)
|
|
break
|
|
except Exception:
|
|
continue
|
|
except Exception as e:
|
|
print(f"[UnitedSCO step2] Button text search error: {e}")
|
|
|
|
# Strategy 3: JavaScript click on #eligibility-link
|
|
if not eligibility_clicked:
|
|
try:
|
|
clicked = self.driver.execute_script("""
|
|
var btn = document.getElementById('eligibility-link');
|
|
if (btn) { btn.scrollIntoView({block: 'center'}); btn.click(); return true; }
|
|
// Fallback: find any button/a with exact "Eligibility" text
|
|
var all = document.querySelectorAll('button, a');
|
|
for (var i = 0; i < all.length; i++) {
|
|
if (/^\\s*Eligibility\\s*$/i.test(all[i].textContent)) {
|
|
all[i].scrollIntoView({block: 'center'});
|
|
all[i].click();
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
""")
|
|
if clicked:
|
|
eligibility_clicked = True
|
|
print("[UnitedSCO step2] Clicked via JavaScript")
|
|
time.sleep(5)
|
|
except Exception as e:
|
|
print(f"[UnitedSCO step2] JS click error: {e}")
|
|
|
|
if not eligibility_clicked:
|
|
print("[UnitedSCO step2] WARNING: Could not click Eligibility button")
|
|
|
|
# 3) Handle the result of clicking: new tab, download, or same-page content
|
|
pdf_path = None
|
|
|
|
# Check for new browser tab/window
|
|
new_windows = set(self.driver.window_handles) - original_windows
|
|
if new_windows:
|
|
new_tab = list(new_windows)[0]
|
|
print(f"[UnitedSCO step2] New tab opened! Switching to it...")
|
|
self.driver.switch_to.window(new_tab)
|
|
time.sleep(5)
|
|
|
|
# Wait for the new page to load
|
|
try:
|
|
WebDriverWait(self.driver, 30).until(
|
|
lambda d: d.execute_script("return document.readyState") == "complete"
|
|
)
|
|
except Exception:
|
|
pass
|
|
time.sleep(2)
|
|
|
|
print(f"[UnitedSCO step2] New tab URL: {self.driver.current_url}")
|
|
|
|
# Capture PDF from the new tab
|
|
pdf_path = self._capture_pdf(foundMemberId)
|
|
|
|
# Close the new tab and switch back to original
|
|
self.driver.close()
|
|
self.driver.switch_to.window(original_window)
|
|
print("[UnitedSCO step2] Closed new tab, switched back to original")
|
|
|
|
# Check for downloaded file
|
|
if not pdf_path:
|
|
downloaded_file = self._wait_for_new_download(existing_downloads, timeout=10)
|
|
if downloaded_file:
|
|
print(f"[UnitedSCO step2] File downloaded: {downloaded_file}")
|
|
pdf_path = downloaded_file
|
|
|
|
# Fallback: capture current page as PDF
|
|
if not pdf_path:
|
|
print("[UnitedSCO step2] No new tab or download detected - capturing current page as PDF")
|
|
|
|
# Wait for any dynamic content
|
|
try:
|
|
WebDriverWait(self.driver, 15).until(
|
|
lambda d: d.execute_script("return document.readyState") == "complete"
|
|
)
|
|
except Exception:
|
|
pass
|
|
time.sleep(3)
|
|
|
|
print(f"[UnitedSCO step2] Capturing PDF from URL: {self.driver.current_url}")
|
|
pdf_path = self._capture_pdf(foundMemberId)
|
|
|
|
if not pdf_path:
|
|
return {"status": "error", "message": "STEP2 FAILED: Could not generate PDF"}
|
|
|
|
print(f"[UnitedSCO step2] PDF saved: {pdf_path}")
|
|
|
|
# Hide browser window after completion
|
|
self._hide_browser()
|
|
|
|
print("[UnitedSCO step2] Eligibility capture complete")
|
|
|
|
return {
|
|
"status": "success",
|
|
"eligibility": eligibilityText,
|
|
"ss_path": pdf_path,
|
|
"pdf_path": pdf_path,
|
|
"patientName": patientName,
|
|
"memberId": foundMemberId
|
|
}
|
|
|
|
except Exception as e:
|
|
print(f"[UnitedSCO step2] Exception: {e}")
|
|
return {"status": "error", "message": f"STEP2 FAILED: {str(e)}"}
|
|
|
|
def _hide_browser(self):
|
|
"""Hide the browser window after task completion using multiple strategies."""
|
|
try:
|
|
# Strategy 1: Navigate to blank page first (clears sensitive data from view)
|
|
try:
|
|
self.driver.get("about:blank")
|
|
time.sleep(0.5)
|
|
except Exception:
|
|
pass
|
|
|
|
# Strategy 2: Minimize window
|
|
try:
|
|
self.driver.minimize_window()
|
|
print("[UnitedSCO step2] Browser window minimized")
|
|
return
|
|
except Exception:
|
|
pass
|
|
|
|
# Strategy 3: Move window off-screen
|
|
try:
|
|
self.driver.set_window_position(-10000, -10000)
|
|
print("[UnitedSCO step2] Browser window moved off-screen")
|
|
return
|
|
except Exception:
|
|
pass
|
|
|
|
# Strategy 4: Use xdotool to minimize (Linux)
|
|
try:
|
|
import subprocess
|
|
subprocess.run(["xdotool", "getactivewindow", "windowminimize"],
|
|
timeout=3, capture_output=True)
|
|
print("[UnitedSCO step2] Browser minimized via xdotool")
|
|
except Exception:
|
|
pass
|
|
|
|
except Exception as e:
|
|
print(f"[UnitedSCO step2] Could not hide browser: {e}")
|
|
|
|
def _capture_pdf(self, member_id):
|
|
"""Capture the current page as PDF using Chrome DevTools Protocol."""
|
|
try:
|
|
pdf_options = {
|
|
"landscape": False,
|
|
"displayHeaderFooter": False,
|
|
"printBackground": True,
|
|
"preferCSSPageSize": True,
|
|
"paperWidth": 8.5,
|
|
"paperHeight": 11,
|
|
"marginTop": 0.4,
|
|
"marginBottom": 0.4,
|
|
"marginLeft": 0.4,
|
|
"marginRight": 0.4,
|
|
"scale": 0.9,
|
|
}
|
|
|
|
file_identifier = member_id if member_id else f"{self.firstName}_{self.lastName}"
|
|
|
|
result = self.driver.execute_cdp_cmd("Page.printToPDF", pdf_options)
|
|
pdf_data = base64.b64decode(result.get('data', ''))
|
|
pdf_path = os.path.join(self.download_dir, f"unitedsco_eligibility_{file_identifier}_{int(time.time())}.pdf")
|
|
with open(pdf_path, "wb") as f:
|
|
f.write(pdf_data)
|
|
return pdf_path
|
|
except Exception as e:
|
|
print(f"[UnitedSCO _capture_pdf] Error: {e}")
|
|
return None
|
|
|
|
|
|
def main_workflow(self, url):
|
|
"""Main workflow that runs all steps."""
|
|
try:
|
|
self.config_driver()
|
|
|
|
login_result = self.login(url)
|
|
print(f"[main_workflow] Login result: {login_result}")
|
|
|
|
if login_result == "OTP_REQUIRED":
|
|
return {"status": "otp_required", "message": "OTP required after login"}
|
|
|
|
if isinstance(login_result, str) and login_result.startswith("ERROR"):
|
|
return {"status": "error", "message": login_result}
|
|
|
|
step1_result = self.step1()
|
|
print(f"[main_workflow] Step1 result: {step1_result}")
|
|
|
|
if isinstance(step1_result, str) and step1_result.startswith("ERROR"):
|
|
return {"status": "error", "message": step1_result}
|
|
|
|
step2_result = self.step2()
|
|
print(f"[main_workflow] Step2 result: {step2_result}")
|
|
|
|
return step2_result
|
|
|
|
except Exception as e:
|
|
return {"status": "error", "message": str(e)}
|