From 3582da85106ef9e042ca2b558d3defc7cd4d5cad Mon Sep 17 00:00:00 2001 From: Gitead Date: Wed, 8 Jul 2026 00:22:35 -0400 Subject: [PATCH] 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 --- apps/SeleniumService/helpers_ddma_claim.py | 4 +- .../selenium_DDMA_claimSubmitWorker.py | 227 +++++++----------- 2 files changed, 85 insertions(+), 146 deletions(-) diff --git a/apps/SeleniumService/helpers_ddma_claim.py b/apps/SeleniumService/helpers_ddma_claim.py index d251abf5..b96e1712 100644 --- a/apps/SeleniumService/helpers_ddma_claim.py +++ b/apps/SeleniumService/helpers_ddma_claim.py @@ -152,7 +152,7 @@ async def start_ddma_claim_run(sid: str, data: dict, url: str): if "member" in current_url or "dashboard" in current_url or "eligibility" in current_url: try: 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("[DDMA Claim OTP] Member search found — login successful!") login_success = True @@ -184,7 +184,7 @@ async def start_ddma_claim_run(sid: str, data: dict, url: str): 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"]')) + EC.presence_of_element_located((By.XPATH, "//a[contains(@href, 'check-patient-benefits-search')]")) ) print("[DDMA Claim OTP] Member search found — login successful!") login_success = True diff --git a/apps/SeleniumService/selenium_DDMA_claimSubmitWorker.py b/apps/SeleniumService/selenium_DDMA_claimSubmitWorker.py index 1a66cdff..1eba83f6 100644 --- a/apps/SeleniumService/selenium_DDMA_claimSubmitWorker.py +++ b/apps/SeleniumService/selenium_DDMA_claimSubmitWorker.py @@ -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)