Files
DentalManagement2025/apps/SeleniumService/selenium_DentaQuest_eligibilityCheckWorker.py

480 lines
22 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 dentaquest_browser_manager import get_browser_manager
class AutomationDentaQuestEligibilityCheck:
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.dentaquest_username = self.data.get("dentaquestUsername", "")
self.dentaquest_password = self.data.get("dentaquestPassword", "")
# 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 DentaQuest domain."""
try:
print("[DentaQuest 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://providers.dentaquest.com/")
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("[DentaQuest login] Clicked logout button")
time.sleep(2)
break
except TimeoutException:
continue
except Exception as e:
print(f"[DentaQuest login] Could not click logout button: {e}")
# Clear cookies as backup
try:
self.driver.delete_all_cookies()
print("[DentaQuest login] Cleared all cookies")
except Exception as e:
print(f"[DentaQuest login] Error clearing cookies: {e}")
browser_manager.clear_credentials_hash()
print("[DentaQuest login] Logout complete")
return True
except Exception as e:
print(f"[DentaQuest 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.dentaquest_username and browser_manager.credentials_changed(self.dentaquest_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"[DentaQuest login] Current URL: {current_url}")
# Check if we're already on dashboard with member search
if "dashboard" in current_url.lower() or "member" in current_url.lower():
try:
member_search = WebDriverWait(self.driver, 3).until(
EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search by member ID"]'))
)
print("[DentaQuest login] Already on dashboard with member search")
return "ALREADY_LOGGED_IN"
except TimeoutException:
pass
except Exception as e:
print(f"[DentaQuest 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"[DentaQuest login] After navigation URL: {current_url}")
# If already on dashboard, we're logged in
if "dashboard" in current_url.lower():
print("[DentaQuest login] Already on dashboard")
return "ALREADY_LOGGED_IN"
# Try to dismiss the modal by clicking OK
try:
ok_button = WebDriverWait(self.driver, 5).until(
EC.element_to_be_clickable((By.XPATH, "//button[normalize-space(text())='Ok' or normalize-space(text())='OK' or normalize-space(text())='Continue']"))
)
ok_button.click()
print("[DentaQuest login] Clicked OK modal button")
time.sleep(3)
except TimeoutException:
print("[DentaQuest login] No OK modal button found")
# Check if we're now on dashboard (session was valid)
current_url = self.driver.current_url
print(f"[DentaQuest login] After modal click URL: {current_url}")
if "dashboard" in current_url.lower():
# Check for member search input to confirm logged in
try:
member_search = WebDriverWait(self.driver, 5).until(
EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search by member ID"]'))
)
print("[DentaQuest login] Session valid - on dashboard with member search")
return "ALREADY_LOGGED_IN"
except TimeoutException:
pass
# Check if OTP is required (popup window or OTP input)
if len(self.driver.window_handles) > 1:
original_window = self.driver.current_window_handle
for window in self.driver.window_handles:
if window != original_window:
self.driver.switch_to.window(window)
print("[DentaQuest login] Switched to popup window")
break
try:
otp_input = WebDriverWait(self.driver, 5).until(
EC.presence_of_element_located((By.XPATH, "//input[@type='tel' or contains(@placeholder,'code') or contains(@aria-label,'Verification')]"))
)
print("[DentaQuest login] OTP input found in popup")
return "OTP_REQUIRED"
except TimeoutException:
self.driver.switch_to.window(original_window)
# Check for OTP input on main 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("[DentaQuest login] OTP input found")
return "OTP_REQUIRED"
except TimeoutException:
pass
# If still on login page, need to fill credentials
if "onboarding" in current_url.lower() or "login" in current_url.lower():
print("[DentaQuest login] Need to fill login credentials")
try:
email_field = WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable((By.XPATH, "//input[@name='username' or @type='text']"))
)
email_field.clear()
email_field.send_keys(self.dentaquest_username)
password_field = wait.until(EC.presence_of_element_located((By.XPATH, "//input[@type='password']")))
password_field.clear()
password_field.send_keys(self.dentaquest_password)
# Click login button
login_button = wait.until(EC.element_to_be_clickable((By.XPATH, "//button[@type='submit']")))
login_button.click()
print("[DentaQuest login] Submitted login form")
# Save credentials hash after login attempt
if self.dentaquest_username:
browser_manager.save_credentials_hash(self.dentaquest_username)
time.sleep(5)
# Check for OTP after login
try:
otp_input = WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.XPATH, "//input[@type='tel' or contains(@placeholder,'code') or contains(@aria-label,'Verification')]"))
)
return "OTP_REQUIRED"
except TimeoutException:
pass
# Check if login succeeded
if "dashboard" in self.driver.current_url.lower():
return "SUCCESS"
except TimeoutException:
print("[DentaQuest login] Login form elements not found")
return "ERROR: Login form not found"
return "SUCCESS"
except Exception as e:
print(f"[DentaQuest login] Exception: {e}")
return f"ERROR:LOGIN FAILED: {e}"
def step1(self):
"""Navigate to member search and enter member ID + DOB"""
wait = WebDriverWait(self.driver, 30)
try:
print(f"[DentaQuest step1] Starting member search for ID: {self.memberId}, DOB: {self.dateOfBirth}")
# Wait for page to be ready
time.sleep(2)
# Parse DOB - format: YYYY-MM-DD
try:
dob_parts = self.dateOfBirth.split("-")
dob_year = dob_parts[0]
dob_month = dob_parts[1].zfill(2)
dob_day = dob_parts[2].zfill(2)
print(f"[DentaQuest step1] Parsed DOB: {dob_month}/{dob_day}/{dob_year}")
except Exception as e:
print(f"[DentaQuest step1] Error parsing DOB: {e}")
return "ERROR: PARSING DOB"
# Get today's date for Date of Service
from datetime import datetime
today = datetime.now()
service_month = str(today.month).zfill(2)
service_day = str(today.day).zfill(2)
service_year = str(today.year)
print(f"[DentaQuest step1] Service date: {service_month}/{service_day}/{service_year}")
# Helper function to fill contenteditable date spans within a specific container
def fill_date_by_testid(testid, month_val, day_val, year_val, field_name):
try:
container = self.driver.find_element(By.XPATH, f"//div[@data-testid='{testid}']")
month_elem = container.find_element(By.XPATH, ".//span[@data-type='month' and @contenteditable='true']")
day_elem = container.find_element(By.XPATH, ".//span[@data-type='day' and @contenteditable='true']")
year_elem = container.find_element(By.XPATH, ".//span[@data-type='year' and @contenteditable='true']")
def replace_with_sendkeys(el, value):
el.click()
time.sleep(0.05)
el.send_keys(Keys.CONTROL, "a")
el.send_keys(Keys.BACKSPACE)
el.send_keys(value)
replace_with_sendkeys(month_elem, month_val)
time.sleep(0.1)
replace_with_sendkeys(day_elem, day_val)
time.sleep(0.1)
replace_with_sendkeys(year_elem, year_val)
print(f"[DentaQuest step1] Filled {field_name}: {month_val}/{day_val}/{year_val}")
return True
except Exception as e:
print(f"[DentaQuest step1] Error filling {field_name}: {e}")
return False
# 1. Fill Date of Service with TODAY's date using specific data-testid
fill_date_by_testid("member-search_date-of-service", service_month, service_day, service_year, "Date of Service")
time.sleep(0.3)
# 2. Fill Date of Birth with patient's DOB using specific data-testid
fill_date_by_testid("member-search_date-of-birth", dob_month, dob_day, dob_year, "Date of Birth")
time.sleep(0.3)
# 3. Fill Member ID
member_id_input = wait.until(EC.presence_of_element_located(
(By.XPATH, '//input[@placeholder="Search by member ID"]')
))
member_id_input.clear()
member_id_input.send_keys(self.memberId)
print(f"[DentaQuest step1] Entered member ID: {self.memberId}")
time.sleep(0.3)
# 4. Click Search button
try:
search_btn = wait.until(EC.element_to_be_clickable(
(By.XPATH, '//button[@data-testid="member-search_search-button"]')
))
search_btn.click()
print("[DentaQuest step1] Clicked search button")
except TimeoutException:
# Fallback
try:
search_btn = self.driver.find_element(By.XPATH, '//button[contains(text(),"Search")]')
search_btn.click()
print("[DentaQuest step1] Clicked search button (fallback)")
except:
member_id_input.send_keys(Keys.RETURN)
print("[DentaQuest step1] Pressed Enter to search")
time.sleep(5)
# Check for "no results" error
try:
error_msg = WebDriverWait(self.driver, 3).until(EC.presence_of_element_located(
(By.XPATH, '//*[contains(@data-testid,"no-results") or contains(@class,"no-results") or contains(text(),"No results") or contains(text(),"not found") or contains(text(),"No member found") or contains(text(),"Nothing was found")]')
))
if error_msg and error_msg.is_displayed():
print("[DentaQuest step1] No results found")
return "ERROR: INVALID MEMBERID OR DOB"
except TimeoutException:
pass
print("[DentaQuest step1] Search completed successfully")
return "Success"
except Exception as e:
print(f"[DentaQuest step1] Exception: {e}")
return f"ERROR:STEP1 - {e}"
def step2(self):
"""Get eligibility status and capture screenshot"""
wait = WebDriverWait(self.driver, 90)
try:
print("[DentaQuest step2] Starting eligibility capture")
# Wait for results to load
time.sleep(3)
# Try to find eligibility status from the results
eligibilityText = "unknown"
try:
# Look for a link or element with eligibility status
status_elem = wait.until(EC.presence_of_element_located((
By.XPATH,
"//a[contains(@href,'eligibility')] | //*[contains(@class,'status')] | //*[contains(text(),'Active') or contains(text(),'Inactive') or contains(text(),'Eligible')]"
)))
eligibilityText = status_elem.text.strip().lower()
print(f"[DentaQuest step2] Found status element: {eligibilityText}")
# Normalize status
if "active" in eligibilityText or "eligible" in eligibilityText:
eligibilityText = "active"
elif "inactive" in eligibilityText or "ineligible" in eligibilityText:
eligibilityText = "inactive"
except TimeoutException:
print("[DentaQuest step2] Could not find specific eligibility status")
# Try to find patient name
patientName = ""
try:
# Look for the patient name in the results
name_elem = self.driver.find_element(By.XPATH, "//h1 | //div[contains(@class,'name')] | //*[contains(@class,'member-name') or contains(@class,'patient-name')]")
patientName = name_elem.text.strip()
print(f"[DentaQuest step2] Found patient name: {patientName}")
except:
pass
# Wait for page to fully load
try:
WebDriverWait(self.driver, 30).until(
lambda d: d.execute_script("return document.readyState") == "complete"
)
except Exception:
pass
time.sleep(1)
# Capture full page screenshot
print("[DentaQuest step2] Capturing screenshot")
total_width = int(self.driver.execute_script(
"return Math.max(document.body.scrollWidth, document.documentElement.scrollWidth, document.documentElement.clientWidth);"
))
total_height = int(self.driver.execute_script(
"return Math.max(document.body.scrollHeight, document.documentElement.scrollHeight, document.documentElement.clientHeight);"
))
dpr = float(self.driver.execute_script("return window.devicePixelRatio || 1;"))
self.driver.execute_cdp_cmd('Emulation.setDeviceMetricsOverride', {
"mobile": False,
"width": total_width,
"height": total_height,
"deviceScaleFactor": dpr,
"screenOrientation": {"angle": 0, "type": "portraitPrimary"}
})
time.sleep(0.2)
# Capture screenshot
result = self.driver.execute_cdp_cmd("Page.captureScreenshot", {"format": "png", "fromSurface": True})
image_data = base64.b64decode(result.get('data', ''))
screenshot_path = os.path.join(self.download_dir, f"dentaquest_ss_{self.memberId}_{int(time.time())}.png")
with open(screenshot_path, "wb") as f:
f.write(image_data)
print(f"[DentaQuest step2] Screenshot saved: {screenshot_path}")
# Restore original metrics
try:
self.driver.execute_cdp_cmd('Emulation.clearDeviceMetricsOverride', {})
except Exception:
pass
# Close the browser window after screenshot
try:
from dentaquest_browser_manager import get_browser_manager
get_browser_manager().quit_driver()
print("[DentaQuest step2] Browser closed")
except Exception as e:
print(f"[DentaQuest step2] Error closing browser: {e}")
output = {
"status": "success",
"eligibility": eligibilityText,
"ss_path": screenshot_path,
"patientName": patientName
}
print(f"[DentaQuest step2] Success: {output}")
return output
except Exception as e:
print(f"[DentaQuest step2] Exception: {e}")
# Cleanup download folder on error
try:
dl = os.path.abspath(self.download_dir)
if os.path.isdir(dl):
for name in os.listdir(dl):
item = os.path.join(dl, name)
try:
if os.path.isfile(item) or os.path.islink(item):
os.remove(item)
except Exception:
pass
except Exception:
pass
return {"status": "error", "message": str(e)}
def main_workflow(self, url):
try:
self.config_driver()
self.driver.maximize_window()
time.sleep(3)
login_result = self.login(url)
if login_result.startswith("ERROR"):
return {"status": "error", "message": login_result}
if login_result == "OTP_REQUIRED":
return {"status": "otp_required", "message": "OTP required after login"}
step1_result = self.step1()
if step1_result.startswith("ERROR"):
return {"status": "error", "message": step1_result}
step2_result = self.step2()
if step2_result.get("status") == "error":
return {"status": "error", "message": step2_result.get("message")}
return step2_result
except Exception as e:
return {
"status": "error",
"message": str(e)
}