fix: update DDMA eligibility check worker for new deltadentalma.com sign-in and search flow

Delta Dental MA moved their provider login from providers.deltadentalma.com/onboarding
to www.deltadentalma.com/sign-in with new form field selectors, and moved eligibility
search behind a "Group/Health Connector" search page keyed by subscriber ID + birthdate
instead of the old member-search form. Updates login, OTP/MFA handling, and member
search/selection across the eligibility and claim DDMA workers and their session helpers.
This commit is contained in:
2026-07-04 22:57:59 -04:00
parent 79bae13f35
commit 877feb32fa
5 changed files with 194 additions and 276 deletions

View File

@@ -410,7 +410,7 @@ async def ddma_eligibility(request: Request):
waiting_jobs += 1
# run in background (queued under semaphore)
asyncio.create_task(_ddma_worker_wrapper(sid, data, url="https://providers.deltadentalma.com/onboarding/start/"))
asyncio.create_task(_ddma_worker_wrapper(sid, data, url="https://www.deltadentalma.com/sign-in"))
return {"status": "started", "session_id": sid}
@@ -698,7 +698,7 @@ async def ddma_claim(request: Request):
asyncio.create_task(_ddma_claim_worker_wrapper(
sid, body,
url="https://providers.deltadentalma.com/onboarding/start/"
url="https://www.deltadentalma.com/sign-in"
))
return {"status": "started", "session_id": sid}

View File

@@ -130,17 +130,12 @@ async def start_ddma_claim_run(sid: str, data: dict, url: str):
if otp_value:
print(f"[DDMA Claim OTP poll {poll+1}] OTP received from app, typing it in...")
try:
otp_input = driver.find_element(By.XPATH,
"//input[contains(@aria-label,'Verification') or "
"contains(@placeholder,'verification') or @type='tel' or "
"contains(@aria-lable,'Verification code') or "
"contains(@placeholder,'Enter your verification code')]"
)
otp_input = driver.find_element(By.XPATH, "//input[@data-test-id='MfaCode']")
otp_input.clear()
otp_input.send_keys(otp_value)
try:
verify_btn = driver.find_element(By.XPATH,
"//button[@type='button' and @aria-label='Verify']")
"//button[@data-test-id='ConfirmSubmitButton']")
verify_btn.click()
except Exception:
otp_input.send_keys("\n")
@@ -167,17 +162,14 @@ async def start_ddma_claim_run(sid: str, data: dict, url: str):
# Check if OTP input still visible (user hasn't finished)
try:
driver.find_element(By.XPATH,
"//input[contains(@aria-label,'Verification') or "
"contains(@placeholder,'verification') or @type='tel']"
)
driver.find_element(By.XPATH, "//input[@data-test-id='MfaCode']")
print(f"[DDMA Claim OTP poll {poll+1}] OTP input still visible — waiting...")
except Exception:
# OTP input gone — try navigating to members
if "onboarding" in current_url or "start" in current_url:
if "sign-in" in current_url:
print("[DDMA Claim OTP] OTP input gone, navigating to members page...")
try:
driver.get("https://providers.deltadentalma.com/members")
driver.get("https://www.deltadentalma.com/private/provider/dashboard")
await asyncio.sleep(2)
except Exception:
pass
@@ -189,7 +181,7 @@ async def start_ddma_claim_run(sid: str, data: dict, url: str):
# Final attempt — navigate directly to members page
try:
print("[DDMA Claim OTP] Final attempt — navigating to members page...")
driver.get("https://providers.deltadentalma.com/members")
driver.get("https://www.deltadentalma.com/private/provider/dashboard")
await asyncio.sleep(3)
WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search by member ID"]'))

View File

@@ -146,13 +146,11 @@ async def start_ddma_run(sid: str, data: dict, url: str):
if otp_value:
print(f"[OTP poll {poll+1}] OTP received from app, typing it in...")
try:
otp_input = driver.find_element(By.XPATH,
"//input[contains(@aria-label,'Verification') or contains(@placeholder,'verification') or @type='tel' or contains(@aria-lable,'Verification code') or contains(@placeholder,'Enter your verification code')]"
)
otp_input = driver.find_element(By.XPATH, "//input[@data-test-id='MfaCode']")
otp_input.clear()
otp_input.send_keys(otp_value)
try:
verify_btn = driver.find_element(By.XPATH, "//button[@type='button' and @aria-label='Verify']")
verify_btn = driver.find_element(By.XPATH, "//button[@data-test-id='ConfirmSubmitButton']")
verify_btn.click()
except Exception:
otp_input.send_keys("\n")
@@ -169,7 +167,7 @@ async def start_ddma_run(sid: str, data: dict, url: str):
if "member" in current_url or "dashboard" in current_url or "eligibility" in current_url:
try:
member_search = WebDriverWait(driver, 5).until(
EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search by member ID"]'))
EC.presence_of_element_located((By.XPATH, "//a[contains(@href, 'check-patient-benefits-search')]"))
)
print("[OTP] Member search found — login successful!")
login_success = True
@@ -179,16 +177,14 @@ async def start_ddma_run(sid: str, data: dict, url: str):
# Check if OTP input is still visible (user hasn't finished)
try:
otp_input_elem = driver.find_element(By.XPATH,
"//input[contains(@aria-label,'Verification') or contains(@placeholder,'verification') or @type='tel']"
)
otp_input_elem = driver.find_element(By.XPATH, "//input[@data-test-id='MfaCode']")
print(f"[OTP poll {poll+1}] OTP input still visible - waiting...")
except Exception:
# OTP input gone — may mean login is completing; try members page
if "onboarding" in current_url or "start" in current_url:
if "sign-in" in current_url:
print("[OTP] OTP input gone, trying to navigate to members page...")
try:
driver.get("https://providers.deltadentalma.com/members")
driver.get("https://www.deltadentalma.com/private/provider/dashboard")
await asyncio.sleep(2)
except Exception:
pass
@@ -200,10 +196,10 @@ async def start_ddma_run(sid: str, data: dict, url: str):
# Final check — navigate directly to members page
try:
print("[OTP] Final attempt - navigating to members page...")
driver.get("https://providers.deltadentalma.com/members")
driver.get("https://www.deltadentalma.com/private/provider/dashboard")
await asyncio.sleep(3)
member_search = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search by member ID"]'))
EC.presence_of_element_located((By.XPATH, "//a[contains(@href, 'check-patient-benefits-search')]"))
)
print("[OTP] Member search found — login successful!")
login_success = True

View File

@@ -13,7 +13,7 @@ from datetime import date
from ddma_browser_manager import get_browser_manager
MEMBERS_URL = "https://providers.deltadentalma.com/members"
MEMBERS_URL = "https://www.deltadentalma.com/private/provider/dashboard"
# Absolute path to the Backend app root (two levels up from this file's SeleniumService dir)
_SERVICE_DIR = os.path.dirname(os.path.abspath(__file__))
@@ -61,7 +61,7 @@ class AutomationDDMAClaimSubmit:
print("[DDMA Claim login] Forcing logout due to credential change...")
browser_manager = get_browser_manager()
try:
self.driver.get("https://providers.deltadentalma.com/")
self.driver.get("https://www.deltadentalma.com/")
time.sleep(2)
for selector in [
"//button[contains(text(), 'Log out') or contains(text(), 'Logout') or contains(text(), 'Sign out')]",
@@ -100,10 +100,10 @@ class AutomationDDMAClaimSubmit:
try:
current_url = self.driver.current_url
print(f"[DDMA Claim login] Current URL: {current_url}")
logged_in_patterns = ["member", "dashboard", "eligibility", "search", "patients"]
logged_in_patterns = ["member", "dashboard", "eligibility", "search", "patients", "private/provider"]
is_logged_in_url = any(p in current_url.lower() for p in logged_in_patterns)
if is_logged_in_url and "onboarding" not in current_url.lower():
if is_logged_in_url and "sign-in" not in current_url.lower():
# Navigate to members page to confirm
if "member" not in current_url.lower():
try:
@@ -132,7 +132,7 @@ class AutomationDDMAClaimSubmit:
# Check if session redirected us straight to member search
try:
current_url = self.driver.current_url
if "onboarding" not in current_url.lower():
if "sign-in" not in current_url.lower():
member_search = WebDriverWait(self.driver, 3).until(
EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search by member ID"]'))
)
@@ -165,8 +165,7 @@ class AutomationDDMAClaimSubmit:
try:
otp_candidate = WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.XPATH,
"//input[contains(@aria-label,'Verification code') or "
"contains(@placeholder,'Enter your verification code')]"
"//input[@data-test-id='MfaCode']"
))
)
if otp_candidate:
@@ -191,7 +190,7 @@ class AutomationDDMAClaimSubmit:
# Fill login form
try:
email_field = WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable((By.XPATH, "//input[@name='username' and @type='text']"))
EC.element_to_be_clickable((By.XPATH, "//input[@data-test-id='Username']"))
)
except TimeoutException:
return "ERROR: Login form not found"
@@ -200,21 +199,13 @@ class AutomationDDMAClaimSubmit:
email_field.send_keys(self.massddma_username)
password_field = wait.until(
EC.presence_of_element_located((By.XPATH, "//input[@name='password' and @type='password']"))
EC.presence_of_element_located((By.XPATH, "//input[@data-test-id='Password']"))
)
password_field.clear()
password_field.send_keys(self.massddma_password)
try:
remember_me = wait.until(EC.element_to_be_clickable(
(By.XPATH, "//label[.//span[contains(text(),'Remember me')]]")
))
remember_me.click()
except Exception:
print("[DDMA Claim login] Remember me checkbox not found (continuing)")
login_button = wait.until(EC.element_to_be_clickable(
(By.XPATH, "//button[@type='submit' and @aria-label='Sign in']")
(By.XPATH, "//button[@data-test-id='SigninSubmitButton']")
))
login_button.click()
@@ -225,8 +216,7 @@ class AutomationDDMAClaimSubmit:
try:
otp_candidate = WebDriverWait(self.driver, 30).until(
EC.presence_of_element_located((By.XPATH,
"//input[contains(@aria-lable,'Verification code') or "
"contains(@placeholder,'Enter your verification code')]"
"//input[@data-test-id='MfaCode']"
))
)
if otp_candidate:
@@ -240,8 +230,8 @@ class AutomationDDMAClaimSubmit:
return "SUCCESS"
except Exception:
pass
if "onboarding" in self.driver.current_url.lower() or "login" in self.driver.current_url.lower():
return "ERROR: LOGIN FAILED: Still on login/onboarding page"
if "sign-in" in self.driver.current_url.lower():
return "ERROR: LOGIN FAILED: Still on login page"
print("[DDMA Claim login] Assuming login succeeded")
return "SUCCESS"

View File

@@ -10,6 +10,7 @@ import time
import os
import re
import base64
import calendar
from ddma_browser_manager import get_browser_manager
@@ -45,7 +46,7 @@ class AutomationDeltaDentalMAEligibilityCheck:
# Try to click logout button if visible
try:
self.driver.get("https://providers.deltadentalma.com/")
self.driver.get("https://www.deltadentalma.com/")
time.sleep(2)
logout_selectors = [
@@ -99,27 +100,27 @@ class AutomationDeltaDentalMAEligibilityCheck:
current_url = self.driver.current_url
print(f"[login] Current URL: {current_url}")
logged_in_patterns = ["member", "dashboard", "eligibility", "search", "patients"]
logged_in_patterns = ["member", "dashboard", "eligibility", "search", "patients", "private/provider"]
is_logged_in_url = any(pattern in current_url.lower() for pattern in logged_in_patterns)
if is_logged_in_url and "onboarding" not in current_url.lower():
if is_logged_in_url and "sign-in" not in current_url.lower():
print("[login] Already on logged-in page - skipping login entirely")
if "member" not in current_url.lower():
try:
member_search = WebDriverWait(self.driver, 5).until(
EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search by member ID"]'))
EC.presence_of_element_located((By.XPATH, "//a[contains(@href, 'check-patient-benefits-search')]"))
)
print("[login] Found member search input - returning ALREADY_LOGGED_IN")
return "ALREADY_LOGGED_IN"
except TimeoutException:
members_url = "https://providers.deltadentalma.com/members"
members_url = "https://www.deltadentalma.com/private/provider/dashboard"
print(f"[login] Navigating to members page: {members_url}")
self.driver.get(members_url)
time.sleep(2)
try:
member_search = WebDriverWait(self.driver, 5).until(
EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search by member ID"]'))
EC.presence_of_element_located((By.XPATH, "//a[contains(@href, 'check-patient-benefits-search')]"))
)
print("[login] Member search found - ALREADY_LOGGED_IN")
return "ALREADY_LOGGED_IN"
@@ -137,9 +138,9 @@ class AutomationDeltaDentalMAEligibilityCheck:
current_url = self.driver.current_url
print(f"[login] URL after navigation: {current_url}")
if "onboarding" not in current_url.lower():
if "sign-in" not in current_url.lower():
member_search = WebDriverWait(self.driver, 3).until(
EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search by member ID"]'))
EC.presence_of_element_located((By.XPATH, "//a[contains(@href, 'check-patient-benefits-search')]"))
)
if member_search:
print("[login] Session valid - skipping login")
@@ -172,7 +173,7 @@ class AutomationDeltaDentalMAEligibilityCheck:
try:
otp_candidate = WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located(
(By.XPATH, "//input[contains(@aria-lable,'Verification code') or contains(@placeholder,'Enter your verification code') or contains(@aria-label,'Verification code')]")
(By.XPATH, "//input[@data-test-id='MfaCode']")
)
)
if otp_candidate:
@@ -189,7 +190,7 @@ class AutomationDeltaDentalMAEligibilityCheck:
time.sleep(2)
try:
member_search = WebDriverWait(self.driver, 5).until(
EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search by member ID"]'))
EC.presence_of_element_located((By.XPATH, "//a[contains(@href, 'check-patient-benefits-search')]"))
)
if member_search:
print("[login] Already authenticated after modal dismiss")
@@ -200,30 +201,21 @@ class AutomationDeltaDentalMAEligibilityCheck:
# Fill login form
try:
email_field = WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable((By.XPATH, "//input[@name='username' and @type='text']"))
EC.element_to_be_clickable((By.XPATH, "//input[@data-test-id='Username']"))
)
except TimeoutException:
print("[login] Could not find login form - page may have changed")
return "ERROR: Login form not found"
email_field = wait.until(EC.presence_of_element_located((By.XPATH, "//input[@name='username' and @type='text']")))
email_field = wait.until(EC.presence_of_element_located((By.XPATH, "//input[@data-test-id='Username']")))
email_field.clear()
email_field.send_keys(self.massddma_username)
password_field = wait.until(EC.presence_of_element_located((By.XPATH, "//input[@name='password' and @type='password']")))
password_field = wait.until(EC.presence_of_element_located((By.XPATH, "//input[@data-test-id='Password']")))
password_field.clear()
password_field.send_keys(self.massddma_password)
# Remember me
try:
remember_me_checkbox = wait.until(EC.element_to_be_clickable(
(By.XPATH, "//label[.//span[contains(text(),'Remember me')]]")
))
remember_me_checkbox.click()
except Exception:
print("[login] Remember me checkbox not found (continuing).")
login_button = wait.until(EC.element_to_be_clickable((By.XPATH, "//button[@type='submit' and @aria-label='Sign in']")))
login_button = wait.until(EC.element_to_be_clickable((By.XPATH, "//button[@data-test-id='SigninSubmitButton']")))
login_button.click()
# Save credentials hash after login attempt
@@ -234,7 +226,7 @@ class AutomationDeltaDentalMAEligibilityCheck:
try:
otp_candidate = WebDriverWait(self.driver, 30).until(
EC.presence_of_element_located(
(By.XPATH, "//input[contains(@aria-lable,'Verification code') or contains(@placeholder,'Enter your verification code')]")
(By.XPATH, "//input[@data-test-id='MfaCode']")
)
)
if otp_candidate:
@@ -247,7 +239,7 @@ class AutomationDeltaDentalMAEligibilityCheck:
current_url = self.driver.current_url.lower()
if "member" in current_url or "dashboard" in current_url:
member_search = WebDriverWait(self.driver, 5).until(
EC.presence_of_element_located((By.XPATH, '//input[@placeholder="Search by member ID"]'))
EC.presence_of_element_located((By.XPATH, "//a[contains(@href, 'check-patient-benefits-search')]"))
)
print("[login] Login successful - now on member search page")
return "SUCCESS"
@@ -264,7 +256,7 @@ class AutomationDeltaDentalMAEligibilityCheck:
except TimeoutException:
pass
if "onboarding" in self.driver.current_url.lower() or "login" in self.driver.current_url.lower():
if "sign-in" in self.driver.current_url.lower():
print("[login] Login failed - still on login page")
return "ERROR:LOGIN FAILED: Still on login page"
@@ -275,103 +267,66 @@ class AutomationDeltaDentalMAEligibilityCheck:
print("[login] Exception during login:", e)
return f"ERROR:LOGIN FAILED: {e}"
def _select_provider_dropdown(self):
"""
Click the provider dropdown on the member search page and select the option
matching self.providerName (case-insensitive). Falls back to the first option.
The button data-testid is 'member-search_provider_select-btn'.
"""
try:
short_wait = WebDriverWait(self.driver, 5)
try:
provider_btn = short_wait.until(
EC.element_to_be_clickable(
(By.XPATH, '//button[@data-testid="member-search_provider_select-btn"]')
)
)
except TimeoutException:
print("[DDMA step1] No provider dropdown found — skipping")
return
provider_btn.click()
time.sleep(0.5)
try:
WebDriverWait(self.driver, 5).until(
EC.presence_of_element_located((By.XPATH, "//*[@role='option']"))
)
except TimeoutException:
print("[DDMA step1] Provider listbox did not open")
return
options = self.driver.find_elements(By.XPATH, "//*[@role='option']")
print(f"[DDMA step1] Provider options: {[o.text.strip() for o in options]}")
target = (self.providerName or "").strip().lower()
selected = False
if target:
for opt in options:
if target in opt.text.lower():
opt.click()
print(f"[DDMA step1] Selected provider: '{opt.text.strip()}'")
selected = True
break
if not selected and options:
options[0].click()
print(f"[DDMA step1] No match for '{self.providerName}', selected first: '{options[0].text.strip()}'")
time.sleep(0.3)
except Exception as e:
print(f"[DDMA step1] Provider selection error (non-fatal): {e}")
def step1(self):
"""Fill search form with all available fields (flexible search)."""
"""Open the Group/Health Connector search page and search by Subscriber ID + Birthdate."""
wait = WebDriverWait(self.driver, 30)
try:
fields = []
if self.memberId:
fields.append(f"ID: {self.memberId}")
if self.firstName:
fields.append(f"FirstName: {self.firstName}")
if self.lastName:
fields.append(f"LastName: {self.lastName}")
fields.append(f"SubscriberID: {self.memberId}")
if self.dateOfBirth:
fields.append(f"DOB: {self.dateOfBirth}")
print(f"[DDMA step1] Starting search with: {', '.join(fields)}")
# Wait for the search page to be fully loaded before interacting
wait.until(EC.presence_of_element_located(
(By.XPATH, '//input[@placeholder="Search by member ID"]')
))
time.sleep(1)
search_page_url = "https://www.deltadentalma.com/private/provider/check-patient-benefits-search"
# Select provider from dropdown based on NPI settings
self._select_provider_dropdown()
# Try navigating straight to the search page first — avoids any click/visibility issues
print(f"[DDMA step1] Navigating directly to search page: {search_page_url}")
self.driver.get(search_page_url)
time.sleep(2)
def replace_with_sendkeys(el, value):
el.click()
el.send_keys(Keys.CONTROL, "a")
el.send_keys(Keys.BACKSPACE)
el.send_keys(value)
if "check-patient-benefits-search" not in self.driver.current_url.lower():
# Direct deep-link didn't stick (e.g. bounced back to sign-in/dashboard) —
# fall back to clicking the Group/Health Connector link from the dashboard.
print(f"[DDMA step1] Direct navigation redirected to: {self.driver.current_url} — falling back to clicking the link")
# 1. Fill Member ID if provided
if self.memberId:
if "private/provider/dashboard" not in self.driver.current_url.lower():
dashboard_url = "https://www.deltadentalma.com/private/provider/dashboard"
print(f"[DDMA step1] Navigating to dashboard: {dashboard_url}")
self.driver.get(dashboard_url)
time.sleep(2)
link_xpath = "//a[contains(@href, 'check-patient-benefits-search')]"
try:
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"[DDMA step1] Entered Member ID: {self.memberId}")
time.sleep(0.2)
except Exception as e:
print(f"[DDMA step1] Warning: Could not fill Member ID: {e}")
connector_link = WebDriverWait(self.driver, 20).until(
EC.element_to_be_clickable((By.XPATH, link_xpath))
)
connector_link.click()
except TimeoutException:
# Element may exist but be hidden (e.g. a responsive duplicate) — try any match via JS click
candidates = self.driver.find_elements(By.XPATH, link_xpath)
print(f"[DDMA step1] element_to_be_clickable timed out; found {len(candidates)} matching link(s) in DOM")
if candidates:
try:
self.driver.execute_script("arguments[0].click();", candidates[0])
except Exception as js_err:
print(f"[DDMA step1] JS click failed: {js_err}")
print(f"[DDMA step1] Current URL: {self.driver.current_url}")
return "ERROR: Could not open Group/Health Connector search page"
else:
print(f"[DDMA step1] No Group/Health Connector link found in DOM. Current URL: {self.driver.current_url}")
return "ERROR: Could not open Group/Health Connector search page"
# 2. Fill DOB if provided
try:
wait.until(lambda d: "check-patient-benefits-search" in d.current_url)
except TimeoutException:
print(f"[DDMA step1] Click did not navigate to search page. Current URL: {self.driver.current_url}")
return "ERROR: Could not open Group/Health Connector search page"
print(f"[DDMA step1] On Group/Health Connector search page: {self.driver.current_url}")
# Fill Birthdate (masked mm/dd/yyyy input — send digits only, mask inserts slashes)
if self.dateOfBirth:
try:
dob_parts = self.dateOfBirth.split("-")
@@ -379,54 +334,47 @@ class AutomationDeltaDentalMAEligibilityCheck:
month = dob_parts[1].zfill(2)
day = dob_parts[2].zfill(2)
dob_container = wait.until(
EC.presence_of_element_located(
(By.XPATH, "//div[@data-testid='member-search_date-of-birth']")
)
)
month_elem = dob_container.find_element(By.XPATH, ".//span[@data-type='month' and @contenteditable='true']")
day_elem = dob_container.find_element(By.XPATH, ".//span[@data-type='day' and @contenteditable='true']")
year_elem = dob_container.find_element(By.XPATH, ".//span[@data-type='year' and @contenteditable='true']")
replace_with_sendkeys(month_elem, month)
time.sleep(0.05)
replace_with_sendkeys(day_elem, day)
time.sleep(0.05)
replace_with_sendkeys(year_elem, year)
print(f"[DDMA step1] Filled DOB: {month}/{day}/{year}")
birthdate_input = wait.until(EC.element_to_be_clickable(
(By.XPATH, "//input[@data-test-id='InStateSearchBirthdate']")
))
birthdate_input.click()
birthdate_input.send_keys(Keys.CONTROL, "a")
birthdate_input.send_keys(Keys.BACKSPACE)
birthdate_input.send_keys(f"{month}{day}{year}")
print(f"[DDMA step1] Filled Birthdate: {month}/{day}/{year}")
except Exception as e:
print(f"[DDMA step1] Warning: Could not fill DOB: {e}")
print(f"[DDMA step1] Warning: Could not fill Birthdate: {e}")
# Fill Subscriber ID (== member ID)
if self.memberId:
try:
subscriber_input = wait.until(EC.element_to_be_clickable(
(By.XPATH, "//input[@data-test-id='InStateSearchSubscriberId']")
))
subscriber_input.clear()
subscriber_input.send_keys(self.memberId)
print(f"[DDMA step1] Filled Subscriber ID: {self.memberId}")
except Exception as e:
print(f"[DDMA step1] Warning: Could not fill Subscriber ID: {e}")
time.sleep(0.3)
# Click Search button
continue_btn = wait.until(EC.element_to_be_clickable(
(By.XPATH, '//button[@data-testid="member-search_search-button"]')
search_btn = wait.until(EC.element_to_be_clickable(
(By.XPATH, "//button[@data-test-id='InStateSearchButton']")
))
continue_btn.click()
search_btn.click()
print("[DDMA step1] Clicked Search button")
# Wait for either results row or no-results message (up to 15s)
# Wait for the Members results table to render
try:
WebDriverWait(self.driver, 15).until(
EC.any_of(
EC.presence_of_element_located((By.XPATH, "//tbody//tr")),
EC.presence_of_element_located((By.XPATH, '//div[@data-testid="member-search-result-no-results"]')),
)
EC.presence_of_element_located((By.XPATH, "//table//tbody//tr"))
)
time.sleep(3)
time.sleep(2)
except TimeoutException:
pass # proceed and let step2 handle missing results
# Check for no-results error
try:
error_msg = self.driver.find_element(By.XPATH, '//div[@data-testid="member-search-result-no-results"]')
if error_msg:
print("[DDMA step1] Error: No results found")
return "ERROR: INVALID SEARCH CRITERIA"
except Exception:
pass
print("[DDMA step1] Warning: No results table appeared within timeout")
return "ERROR: INVALID SEARCH CRITERIA"
print("[DDMA step1] Search completed successfully")
return "Success"
@@ -455,99 +403,93 @@ class AutomationDeltaDentalMAEligibilityCheck:
foundMemberId = self.memberId or ""
patientName = ""
# Extract patient name from search results row
try:
first_row = self.driver.find_element(By.XPATH, "(//tbody//tr)[1]")
row_text = first_row.text.strip()
print(f"[DDMA step2] First row text: {row_text[:150]}...")
# The Members table lists the whole family sharing this subscriber ID —
# find the row matching this patient's DOB (falls back to last name, then first row).
rows = self.driver.find_elements(By.XPATH, "//table//tbody//tr")
print(f"[DDMA step2] Found {len(rows)} family member row(s)")
lines = row_text.split('\n') if row_text else []
for line in lines:
line = line.strip()
if not line:
continue
if re.match(r"^[A-Za-z\s\-']{2,60}$", line):
patientName = line
print(f"[DDMA step2] Extracted patient name from row: '{patientName}'")
target_row = None
if self.dateOfBirth and rows:
try:
dob_parts = self.dateOfBirth.split("-")
dob_year, dob_month, dob_day = dob_parts[0], int(dob_parts[1]), int(dob_parts[2])
expected_dob = f"{calendar.month_abbr[dob_month]} {dob_day}, {dob_year}".lower()
for row in rows:
cells = row.find_elements(By.XPATH, "./td")
if len(cells) >= 3 and cells[2].text.strip().lower() == expected_dob:
target_row = row
break
except Exception as e:
print(f"[DDMA step2] DOB matching error (non-fatal): {e}")
if target_row is None and self.lastName and rows:
for row in rows:
if self.lastName.strip().lower() in row.text.lower():
target_row = row
break
except Exception as e:
print(f"[DDMA step2] Error reading first row: {e}")
if target_row is None and rows:
target_row = rows[0]
print("[DDMA step2] No DOB/name match — defaulting to first family member row")
if target_row is not None:
try:
cells = target_row.find_elements(By.XPATH, "./td")
if cells:
patientName = cells[0].text.strip()
print(f"[DDMA step2] Extracted patient name from row: '{patientName}'")
if len(cells) >= 2 and cells[1].text.strip():
foundMemberId = cells[1].text.strip()
if len(cells) >= 7:
status_text = cells[6].text.strip().lower()
if "active" in status_text:
eligibilityText = "active"
elif "inactive" in status_text or "termed" in status_text:
eligibilityText = "inactive"
else:
eligibilityText = status_text
print(f"[DDMA step2] Found eligibility status: {eligibilityText}")
except Exception as e:
print(f"[DDMA step2] Error reading matched row cells: {e}")
# Click into the member's detail page
try:
name_link = target_row.find_element(By.XPATH, ".//a")
name_link.click()
print(f"[DDMA step2] Clicked into detail page for: {patientName}")
except Exception as e:
print(f"[DDMA step2] Warning: Could not click patient name link: {e}")
else:
print("[DDMA step2] Warning: No family member rows found in search results")
# Wait for the detail page to load (URL moves away from the search page)
try:
short_wait = WebDriverWait(self.driver, 3)
status_link = short_wait.until(EC.presence_of_element_located((
By.XPATH,
"(//tbody//tr)[1]//a[contains(@href, 'member-eligibility-search')]"
)))
eligibilityText = status_link.text.strip().lower()
print(f"[DDMA step2] Found eligibility status: {eligibilityText}")
WebDriverWait(self.driver, 15).until(
lambda d: "check-patient-benefits-search" not in d.current_url
)
print(f"[DDMA step2] Navigated to detail page: {self.driver.current_url}")
except Exception:
try:
alt_status = self.driver.find_element(By.XPATH, "//*[contains(text(),'Active') or contains(text(),'Inactive') or contains(text(),'Eligible')]")
eligibilityText = alt_status.text.strip().lower()
if "active" in eligibilityText or "eligible" in eligibilityText:
eligibilityText = "active"
elif "inactive" in eligibilityText:
eligibilityText = "inactive"
print(f"[DDMA step2] Found eligibility via alternative: {eligibilityText}")
except Exception:
pass
print(f"[DDMA step2] Warning: Still on search page: {self.driver.current_url}")
# Find the member-details URL from the first row
print("[DDMA step2] Looking for patient detail link...")
detail_url = None
link_selectors = [
"(//table//tbody//tr)[1]//td[1]//a",
"(//tbody//tr)[1]//a[contains(@href, 'member-details')]",
"(//tbody//tr)[1]//a[contains(@href, 'member')]",
"//a[contains(@href, 'member-details')]",
]
for selector in link_selectors:
# Wait for meaningful content on the detail page
for selector in [
"//*[contains(text(),'Date of Birth') or contains(text(),'Address') or contains(text(),'Member ID')]",
"//table", "//h1", "//h2",
]:
try:
link_el = WebDriverWait(self.driver, 5).until(
WebDriverWait(self.driver, 15).until(
EC.presence_of_element_located((By.XPATH, selector))
)
href = link_el.get_attribute("href")
if href and "member-details" in href:
detail_url = href
print(f"[DDMA step2] Found detail URL: {href}")
break
print(f"[DDMA step2] Detail page content loaded: {selector}")
break
except Exception:
continue
if detail_url:
# Always navigate via driver.get() — this blocks until the new page loads,
# unlike click() which returns immediately and causes a race condition.
print(f"[DDMA step2] Navigating to detail page: {detail_url}")
self.driver.get(detail_url)
time.sleep(3) # Let JavaScript finish rendering all sections
# Confirm we actually landed on the detail page (not redirected away)
try:
WebDriverWait(self.driver, 15).until(
lambda d: "member-details" in d.current_url
)
print(f"[DDMA step2] Confirmed on detail page: {self.driver.current_url}")
except Exception:
print(f"[DDMA step2] Warning: URL after navigation: {self.driver.current_url}")
# Wait for meaningful content on the detail page
for selector in [
"//*[contains(text(),'Date of Birth') or contains(text(),'Address') or contains(text(),'Member ID')]",
"//table", "//h1", "//h2",
]:
try:
WebDriverWait(self.driver, 15).until(
EC.presence_of_element_located((By.XPATH, selector))
)
print(f"[DDMA step2] Detail page content loaded: {selector}")
break
except Exception:
continue
time.sleep(3) # Let JavaScript finish rendering all sections
# Get full page text as lines
# Fallback: refine patient name from the detail page text if row extraction failed
if not patientName:
try:
page_lines = self.driver.execute_script(
"return document.body.innerText;"
@@ -602,8 +544,6 @@ class AutomationDeltaDentalMAEligibilityCheck:
break
if patientName:
break
else:
print("[DDMA step2] Warning: Could not find patient detail link")
# Clean patient name — strip any remaining date artifacts
if patientName: