fix: update DDMA claim submit worker for new deltadentalma.com claims-submission flow
Site now uses a dedicated claims-submission page with a Subscriber ID + Birthdate search form instead of the member-ID search box, and login detection now checks for the check-patient-benefits-search link rather than the old member-search input placeholder. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@ import os
|
||||
import re
|
||||
import json
|
||||
import base64
|
||||
import calendar
|
||||
from datetime import date
|
||||
|
||||
from ddma_browser_manager import get_browser_manager
|
||||
@@ -108,7 +109,7 @@ class AutomationDDMAClaimSubmit:
|
||||
if "member" not in current_url.lower():
|
||||
try:
|
||||
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("[DDMA Claim login] Already logged in")
|
||||
return "ALREADY_LOGGED_IN"
|
||||
@@ -117,7 +118,7 @@ class AutomationDDMAClaimSubmit:
|
||||
time.sleep(2)
|
||||
try:
|
||||
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("[DDMA Claim login] Already logged in — member search found")
|
||||
return "ALREADY_LOGGED_IN"
|
||||
@@ -134,7 +135,7 @@ class AutomationDDMAClaimSubmit:
|
||||
current_url = self.driver.current_url
|
||||
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("[DDMA Claim login] Session valid — skipping login")
|
||||
@@ -180,7 +181,7 @@ class AutomationDDMAClaimSubmit:
|
||||
time.sleep(2)
|
||||
try:
|
||||
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("[DDMA Claim login] Already authenticated after modal dismiss")
|
||||
return "ALREADY_LOGGED_IN"
|
||||
@@ -239,94 +240,24 @@ class AutomationDDMAClaimSubmit:
|
||||
print(f"[DDMA Claim login] Exception: {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 Claim 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 Claim step1] Provider listbox did not open")
|
||||
return
|
||||
|
||||
options = self.driver.find_elements(By.XPATH, "//*[@role='option']")
|
||||
print(f"[DDMA Claim 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 Claim step1] Selected provider: '{opt.text.strip()}'")
|
||||
selected = True
|
||||
break
|
||||
|
||||
if not selected and options:
|
||||
options[0].click()
|
||||
print(f"[DDMA Claim step1] No match for '{self.providerName}', selected first: '{options[0].text.strip()}'")
|
||||
|
||||
time.sleep(0.3)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[DDMA Claim step1] Provider selection error (non-fatal): {e}")
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Step 1 — Navigate directly to /members then search patient #
|
||||
# (same as eligibility — bypasses onboarding date/location screen) #
|
||||
# Step 1 — Navigate to claims-submission page, search by Subscriber #
|
||||
# ID + Birthdate (same search form as the eligibility flow) #
|
||||
# ------------------------------------------------------------------ #
|
||||
def step1_search_patient(self):
|
||||
"""Search for the patient — identical to DDMA eligibility step1.
|
||||
Does NOT navigate: login already left the browser on the search page."""
|
||||
"""Open the claims-submission page and search for the patient."""
|
||||
wait = WebDriverWait(self.driver, 30)
|
||||
|
||||
def replace_with_sendkeys(el, value):
|
||||
el.click()
|
||||
el.send_keys(Keys.CONTROL, "a")
|
||||
el.send_keys(Keys.BACKSPACE)
|
||||
el.send_keys(value)
|
||||
|
||||
try:
|
||||
print(f"[DDMA Claim step1] Current URL: {self.driver.current_url}")
|
||||
print(f"[DDMA Claim step1] Waiting for member search input...")
|
||||
claims_url = "https://www.deltadentalma.com/private/provider/claims-submission"
|
||||
print(f"[DDMA Claim step1] Navigating to claims-submission page: {claims_url}")
|
||||
self.driver.get(claims_url)
|
||||
time.sleep(2)
|
||||
|
||||
# Select provider from dropdown based on NPI settings
|
||||
self._select_provider_dropdown()
|
||||
if "claims-submission" not in self.driver.current_url.lower():
|
||||
print(f"[DDMA Claim step1] Warning: Ended up at {self.driver.current_url} instead of claims-submission page")
|
||||
|
||||
# Fill Member ID
|
||||
if self.memberId:
|
||||
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 Claim step1] Entered Member ID: {self.memberId}")
|
||||
time.sleep(0.2)
|
||||
except Exception as e:
|
||||
print(f"[DDMA Claim step1] Warning: Could not fill Member ID: {e}")
|
||||
|
||||
# Fill DOB
|
||||
# Fill Birthdate (masked mm/dd/yyyy input — send digits only, mask inserts slashes)
|
||||
if self.dateOfBirth:
|
||||
try:
|
||||
dob_parts = self.dateOfBirth.split("-")
|
||||
@@ -334,46 +265,47 @@ class AutomationDDMAClaimSubmit:
|
||||
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']")
|
||||
birthdate_input = wait.until(EC.element_to_be_clickable(
|
||||
(By.XPATH, "//input[@data-test-id='InStateSearchBirthdate']")
|
||||
))
|
||||
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 Claim step1] Filled DOB: {month}/{day}/{year}")
|
||||
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 Claim step1] Filled Birthdate: {month}/{day}/{year}")
|
||||
except Exception as e:
|
||||
print(f"[DDMA Claim step1] Warning: Could not fill DOB: {e}")
|
||||
print(f"[DDMA Claim 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 Claim step1] Filled Subscriber ID: {self.memberId}")
|
||||
except Exception as e:
|
||||
print(f"[DDMA Claim step1] Warning: Could not fill Subscriber ID: {e}")
|
||||
|
||||
time.sleep(0.3)
|
||||
|
||||
# Click Search
|
||||
search_btn = wait.until(EC.element_to_be_clickable(
|
||||
(By.XPATH, '//button[@data-testid="member-search_search-button"]')
|
||||
(By.XPATH, "//button[@data-test-id='InStateSearchButton']")
|
||||
))
|
||||
search_btn.click()
|
||||
print("[DDMA Claim step1] Clicked Search button")
|
||||
|
||||
# Wait for the member search result page to load
|
||||
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"]')),
|
||||
)
|
||||
)
|
||||
time.sleep(4)
|
||||
|
||||
# Wait for the Members results table to render
|
||||
try:
|
||||
no_results = self.driver.find_element(By.XPATH, '//div[@data-testid="member-search-result-no-results"]')
|
||||
if no_results:
|
||||
return "ERROR: No patient found with given search criteria"
|
||||
except Exception:
|
||||
pass
|
||||
WebDriverWait(self.driver, 15).until(
|
||||
EC.presence_of_element_located((By.XPATH, "//table//tbody//tr"))
|
||||
)
|
||||
time.sleep(2)
|
||||
except TimeoutException:
|
||||
print("[DDMA Claim step1] Warning: No results table appeared within timeout")
|
||||
return "ERROR: No patient found with given search criteria"
|
||||
|
||||
print("[DDMA Claim step1] Search completed")
|
||||
return "SUCCESS"
|
||||
@@ -383,54 +315,61 @@ class AutomationDDMAClaimSubmit:
|
||||
return f"ERROR: step1 failed: {e}"
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Step 2 — Click patient name → Member Information page #
|
||||
# Step 2 — Select the matching family member row → Member detail page #
|
||||
# ------------------------------------------------------------------ #
|
||||
def step2_open_member_page(self):
|
||||
"""Navigate to member detail page — same approach as DDMA eligibility step2."""
|
||||
"""Pick the family member row matching this patient and open their detail page."""
|
||||
try:
|
||||
# Wait for the results table to appear, then let it stabilize
|
||||
try:
|
||||
WebDriverWait(self.driver, 20).until(
|
||||
EC.presence_of_element_located((By.XPATH, "//tbody//tr"))
|
||||
EC.presence_of_element_located((By.XPATH, "//table//tbody//tr"))
|
||||
)
|
||||
time.sleep(3) # wait for the list to fully stabilize
|
||||
time.sleep(2)
|
||||
except TimeoutException:
|
||||
print("[DDMA Claim step2] Warning: Results table not found within timeout")
|
||||
|
||||
# Wait until the patient name link in the first row is clickable
|
||||
detail_url = None
|
||||
PATIENT_LINK_XPATHS = [
|
||||
"(//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 PATIENT_LINK_XPATHS:
|
||||
rows = self.driver.find_elements(By.XPATH, "//table//tbody//tr")
|
||||
print(f"[DDMA Claim step2] Found {len(rows)} family member row(s)")
|
||||
|
||||
target_row = None
|
||||
|
||||
if self.dateOfBirth and rows:
|
||||
try:
|
||||
link_el = WebDriverWait(self.driver, 20).until(
|
||||
EC.element_to_be_clickable((By.XPATH, selector))
|
||||
)
|
||||
href = link_el.get_attribute("href")
|
||||
if href and "member-details" in href:
|
||||
detail_url = href
|
||||
print(f"[DDMA Claim step2] Patient link is clickable: {href}")
|
||||
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 Claim 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:
|
||||
continue
|
||||
|
||||
if not detail_url:
|
||||
return "ERROR: step2 failed: could not find member-details link"
|
||||
if target_row is None and rows:
|
||||
target_row = rows[0]
|
||||
print("[DDMA Claim step2] No DOB/name match — defaulting to first family member row")
|
||||
|
||||
self.driver.get(detail_url)
|
||||
print(f"[DDMA Claim step2] Navigating to: {detail_url}")
|
||||
if target_row is None:
|
||||
return "ERROR: step2 failed: no family member rows found"
|
||||
|
||||
try:
|
||||
WebDriverWait(self.driver, 15).until(
|
||||
lambda d: "member-details" in d.current_url
|
||||
)
|
||||
print(f"[DDMA Claim step2] Member Information page loaded: {self.driver.current_url}")
|
||||
except TimeoutException:
|
||||
print(f"[DDMA Claim step2] Warning — URL: {self.driver.current_url}")
|
||||
name_link = target_row.find_element(By.XPATH, ".//a")
|
||||
name_link.click()
|
||||
print("[DDMA Claim step2] Clicked into patient detail page")
|
||||
except Exception as e:
|
||||
print(f"[DDMA Claim step2] Warning: Could not click patient name link: {e}")
|
||||
return f"ERROR: step2 failed: could not click patient link: {e}"
|
||||
|
||||
time.sleep(2) # let the detail page navigate/render
|
||||
print(f"[DDMA Claim step2] Member detail page loaded: {self.driver.current_url}")
|
||||
|
||||
try:
|
||||
# Wait until Create claim button is visible AND enabled (not just in the DOM)
|
||||
|
||||
Reference in New Issue
Block a user