fix: update DDMA claim worker for new provider portal

DDMA redesigned their claims-submission flow, so the worker now navigates
via the dashboard's "Submit Claims" quick link, the "Member Search" and
"Claim Detail" sidebar sections, checkbox-based member selection, indexed
treatment-row fields, a multi-file upload input, and the new confirmation
page layout.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-18 14:36:14 -04:00
co-authored by Claude Sonnet 5
parent d91e74c959
commit 7b8a7af5d7
@@ -245,18 +245,58 @@ class AutomationDDMAClaimSubmit:
# ID + Birthdate (same search form as the eligibility flow) #
# ------------------------------------------------------------------ #
def step1_search_patient(self):
"""Open the claims-submission page and search for the patient."""
"""From the dashboard, click 'Submit Claims' and search for the patient."""
wait = WebDriverWait(self.driver, 30)
try:
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)
if "dashboard" not in self.driver.current_url.lower():
print(f"[DDMA Claim step1] Not on dashboard — navigating to {MEMBERS_URL}")
self.driver.get(MEMBERS_URL)
time.sleep(2)
try:
submit_claims_link = wait.until(EC.element_to_be_clickable(
(By.XPATH, "//a[@href='/private/provider/claims-submission']")
))
submit_claims_link.click()
print("[DDMA Claim step1] Clicked 'Submit Claims' quick link")
except Exception as e:
print(f"[DDMA Claim step1] Could not click 'Submit Claims' link ({e}) — falling back to direct navigation")
self.driver.get(claims_url)
time.sleep(2)
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")
# Claims-submission page opens on the "Service Office" step — click the
# "Member Search" sidebar item to get to the birthdate/subscriber search form.
try:
member_search_link = wait.until(EC.element_to_be_clickable(
(By.XPATH, "//span[contains(@class,'mainLabel') and normalize-space(text())='Member Search']")
))
member_search_link.click()
print("[DDMA Claim step1] Clicked 'Member Search' sidebar link")
time.sleep(1)
except Exception as e:
print(f"[DDMA Claim step1] Warning: Could not click 'Member Search' sidebar link: {e}")
# Fill Member ID
if self.memberId:
try:
member_id_input = wait.until(EC.element_to_be_clickable(
(By.XPATH, "//input[@data-test-id='memberId']")
))
member_id_input.click()
member_id_input.send_keys(Keys.CONTROL, "a")
member_id_input.send_keys(Keys.BACKSPACE)
member_id_input.send_keys(self.memberId)
print(f"[DDMA Claim step1] Filled Member ID: {self.memberId}")
except Exception as e:
print(f"[DDMA Claim step1] Warning: Could not fill Member ID: {e}")
# Fill Birthdate (masked mm/dd/yyyy input — send digits only, mask inserts slashes)
if self.dateOfBirth:
try:
@@ -266,7 +306,7 @@ class AutomationDDMAClaimSubmit:
day = dob_parts[2].zfill(2)
birthdate_input = wait.until(EC.element_to_be_clickable(
(By.XPATH, "//input[@data-test-id='InStateSearchBirthdate']")
(By.XPATH, "//input[@data-test-id='memberBirthDate']")
))
birthdate_input.click()
birthdate_input.send_keys(Keys.CONTROL, "a")
@@ -276,23 +316,11 @@ class AutomationDDMAClaimSubmit:
except Exception as 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-test-id='InStateSearchButton']")
(By.XPATH, "//button[@data-test-id='memberSearchButton']")
))
search_btn.click()
print("[DDMA Claim step1] Clicked Search button")
@@ -318,7 +346,11 @@ class AutomationDDMAClaimSubmit:
# Step 2 — Select the matching family member row → Member detail page #
# ------------------------------------------------------------------ #
def step2_open_member_page(self):
"""Pick the family member row matching this patient and open their detail page."""
"""Pick the family member row matching this patient and check its selection box.
The results table (columns: checkbox, Member, Member Id, Role, Group Name,
Date Of Birth, Termination Date) stays on the same page — no navigation happens.
"""
try:
# Wait for the results table to appear, then let it stabilize
try:
@@ -337,11 +369,15 @@ class AutomationDDMAClaimSubmit:
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()
dob_year = dob_parts[0]
dob_month = dob_parts[1].zfill(2)
dob_day = dob_parts[2].zfill(2)
expected_dob = f"{dob_month}/{dob_day}/{dob_year}"
for row in rows:
cells = row.find_elements(By.XPATH, "./td")
if len(cells) >= 3 and cells[2].text.strip().lower() == expected_dob:
# cells: [0]=checkbox, [1]=Member, [2]=Member Id, [3]=Role,
# [4]=Group Name, [5]=Date Of Birth, [6]=Termination Date
if len(cells) >= 6 and cells[5].text.strip() == expected_dob:
target_row = row
break
except Exception as e:
@@ -361,26 +397,27 @@ class AutomationDDMAClaimSubmit:
return "ERROR: step2 failed: no family member rows found"
try:
name_link = target_row.find_element(By.XPATH, ".//a")
name_link.click()
print("[DDMA Claim step2] Clicked into patient detail page")
checkbox = target_row.find_element(By.XPATH, ".//i[contains(@class,'fa-check-square')]")
checkbox.click()
print("[DDMA Claim step2] Checked the matching family member row")
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}"
print(f"[DDMA Claim step2] Warning: Could not click member checkbox: {e}")
return f"ERROR: step2 failed: could not click member checkbox: {e}"
time.sleep(2) # let the detail page navigate/render
print(f"[DDMA Claim step2] Member detail page loaded: {self.driver.current_url}")
time.sleep(1)
try:
# Wait until Create claim button is visible AND enabled (not just in the DOM)
WebDriverWait(self.driver, 15).until(
EC.element_to_be_clickable((By.XPATH, "//button[@aria-label='Create claim']"))
claim_detail_link = WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable(
(By.XPATH, "//span[contains(@class,'mainLabel') and normalize-space(text())='Claim Detail']")
)
)
print("[DDMA Claim step2] 'Create claim' button is clickable")
time.sleep(3) # let React finish any remaining re-renders
except TimeoutException:
print("[DDMA Claim step2] Warning: 'Create claim' button not clickable in time")
claim_detail_link.click()
print("[DDMA Claim step2] Clicked 'Claim Detail' sidebar link")
time.sleep(2)
except Exception as e:
print(f"[DDMA Claim step2] Warning: Could not click 'Claim Detail' sidebar link: {e}")
return f"ERROR: step2 failed: could not click 'Claim Detail' link: {e}"
return "SUCCESS"
@@ -389,118 +426,23 @@ class AutomationDDMAClaimSubmit:
return f"ERROR: step2 failed: {e}"
# ------------------------------------------------------------------ #
# Step 3 — Click "Create claim" button #
# Step 3 — No-op: step2 already navigates into the Claim Detail form #
# on the new site (no separate "Create claim" button exists there). #
# ------------------------------------------------------------------ #
def step3_click_create_claim(self):
"""Click the 'Create claim' button on the Member Information page."""
"""Confirm the Claim Detail / Treatment Form is present (navigation already happened in step2)."""
try:
print(f"[DDMA Claim step3] Current URL: {self.driver.current_url}")
handles_before = set(self.driver.window_handles)
url_before = self.driver.current_url
self.driver.execute_script("window.scrollTo(0, 0);")
time.sleep(0.5)
# Re-find the button fresh each attempt to avoid stale element references.
# The page may re-render after step2, so we do not cache the element.
CLAIM_BTN_XPATHS = [
"//button[@aria-label='Create claim']",
"//button[contains(normalize-space(text()),'Create claim')]",
"//button[.//span[contains(text(),'Create claim')]]",
]
btn = None
for xpath in CLAIM_BTN_XPATHS:
try:
btn = WebDriverWait(self.driver, 8).until(
EC.element_to_be_clickable((By.XPATH, xpath))
)
if btn:
print(f"[DDMA Claim step3] Found 'Create claim' via: {xpath}")
break
except Exception:
continue
if not btn:
all_btns = self.driver.find_elements(By.XPATH, "//button")
print(f"[DDMA Claim step3] Buttons on page: {[b.get_attribute('aria-label') or b.text for b in all_btns]}")
return "ERROR: step3 failed: 'Create claim' button not found"
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", btn)
time.sleep(0.5)
# Try direct Selenium click first, fall back to JS events
clicked = False
for attempt, method in enumerate(["selenium", "js_events", "js_click"]):
try:
# Re-find fresh on each attempt to avoid stale reference
for xpath in CLAIM_BTN_XPATHS:
try:
btn = WebDriverWait(self.driver, 5).until(
EC.element_to_be_clickable((By.XPATH, xpath))
)
if btn:
break
except Exception:
continue
if method == "selenium":
btn.click()
elif method == "js_events":
self.driver.execute_script("""
var el = arguments[0];
el.dispatchEvent(new PointerEvent('pointerover', {bubbles:true, cancelable:true, composed:true}));
el.dispatchEvent(new PointerEvent('pointerdown', {bubbles:true, cancelable:true, composed:true}));
el.dispatchEvent(new PointerEvent('pointerup', {bubbles:true, cancelable:true, composed:true}));
el.dispatchEvent(new MouseEvent('click', {bubbles:true, cancelable:true, composed:true}));
""", btn)
else:
self.driver.execute_script("arguments[0].click();", btn)
print(f"[DDMA Claim step3] Clicked via {method} (attempt {attempt+1})")
time.sleep(2)
# Check if navigation happened (URL changed or new tab opened)
handles_after = set(self.driver.window_handles)
new_handles = handles_after - handles_before
if new_handles:
self.driver.switch_to.window(new_handles.pop())
print(f"[DDMA Claim step3] Switched to new tab: {self.driver.current_url}")
clicked = True
break
if self.driver.current_url != url_before:
print(f"[DDMA Claim step3] URL changed to: {self.driver.current_url}")
clicked = True
break
print(f"[DDMA Claim step3] URL unchanged after {method}, retrying...")
except Exception as e:
print(f"[DDMA Claim step3] {method} click failed: {e}, retrying...")
if not clicked:
page_text = self.driver.execute_script("return document.body.innerText;")[:300]
print(f"[DDMA Claim step3] No navigation after all click attempts — page: {page_text}")
return "ERROR: step3 failed: button clicked but no navigation occurred"
# Wait for claim form to load
try:
WebDriverWait(self.driver, 20).until(
EC.any_of(
EC.presence_of_element_located((By.XPATH, "//input[contains(@id,'procedureCode')]")),
EC.presence_of_element_located((By.XPATH, "//span[@data-type='month' and @contenteditable='true']")),
EC.presence_of_element_located((By.XPATH,
"//*[contains(text(),'date of service') or contains(text(),'Date of service') "
"or contains(text(),'Procedure code')]"
)),
)
)
print(f"[DDMA Claim step3] Claim form loaded: {self.driver.current_url}")
except TimeoutException:
page_text = self.driver.execute_script("return document.body.innerText;")[:400]
print(f"[DDMA Claim step3] Claim form not detected — page: {page_text}")
time.sleep(1)
WebDriverWait(self.driver, 15).until(
EC.presence_of_element_located((By.XPATH,
"//*[contains(text(),'Treatment Form')] | //input[@data-test-id='claimRowServiceDate0']"
))
)
print("[DDMA Claim step3] Treatment Form is present — no-op (step2 already navigated here)")
return "SUCCESS"
except TimeoutException:
page_text = self.driver.execute_script("return document.body.innerText;")[:400]
print(f"[DDMA Claim step3] Treatment Form not detected — page: {page_text}")
return "ERROR: step3 failed: Treatment Form not found"
except Exception as e:
print(f"[DDMA Claim step3] Exception: {e}")
return f"ERROR: step3 failed: {e}"
@@ -591,200 +533,117 @@ class AutomationDDMAClaimSubmit:
print(f"[DDMA Claim step4] Warning: could not fill {label}: {e}")
def step4_fill_claim_form(self):
"""Fill service date then all procedure line fields."""
"""Fill each treatment row: service date, procedure code, tooth number, surface, amount.
The new Claim Detail Treatment Form has one row per procedure, each with
indexed fields (ServiceDate{i}, Surface{i}, Amount{i} by data-test-id;
procedure code and tooth number have no id, so they're matched positionally).
"""
try:
month, day, year = self._parse_service_date()
# ── Service date (once, at the top of the form) ──────────────────
if month and day and year:
print(f"[DDMA Claim step4] Filling service date: {month}/{day}/{year}")
try:
dos_container = WebDriverWait(self.driver, 8).until(
EC.presence_of_element_located((By.XPATH,
"//*[@data-testid and contains(@data-testid,'date-of-service')] | "
"//*[contains(@aria-label,'Select date of service')]/ancestor::div[1]"
))
)
month_el = dos_container.find_element(By.XPATH, ".//span[@data-type='month' and @contenteditable='true']")
day_el = dos_container.find_element(By.XPATH, ".//span[@data-type='day' and @contenteditable='true']")
year_el = dos_container.find_element(By.XPATH, ".//span[@data-type='year' and @contenteditable='true']")
for elem, val in [(month_el, month), (day_el, day), (year_el, year)]:
elem.click()
elem.send_keys(Keys.CONTROL, "a")
elem.send_keys(Keys.BACKSPACE)
elem.send_keys(val)
time.sleep(0.05)
print("[DDMA Claim step4] Service date filled")
except Exception:
self._fill_spinbutton("month", month)
self._fill_spinbutton("day", day)
self._fill_spinbutton("year", year)
else:
service_date_str = f"{month}/{day}/{year}" if (month and day and year) else None
if not service_date_str:
print(f"[DDMA Claim step4] No valid service date: {self.serviceDate!r}")
time.sleep(0.3)
active_lines = [ln for ln in self.serviceLines if str(ln.get("procedureCode") or "").strip()]
print(f"[DDMA Claim step4] {len(active_lines)} service line(s)")
for idx, line in enumerate(active_lines):
code = str(line.get("procedureCode") or "").strip().upper()
tooth = str(line.get("toothNumber") or line.get("tooth") or "").strip()
arch = str(line.get("arch") or "").strip()
quad = str(line.get("quad") or line.get("quadrant") or "").strip()
surface = str(line.get("toothSurface") or line.get("surface") or "").strip().upper()
billed = str(line.get("totalBilled") or line.get("billedAmount") or line.get("fee") or "").strip()
billed = billed.replace("$", "").strip()
code = str(line.get("procedureCode") or "").strip().upper()
tooth = str(line.get("toothNumber") or line.get("tooth") or "").strip()
surface = str(line.get("toothSurface") or line.get("surface") or "").strip().upper()
billed = str(line.get("totalBilled") or line.get("billedAmount") or line.get("fee") or "").strip()
billed = billed.replace("$", "").strip()
print(f"[DDMA Claim step4] Line {idx}: code={code} tooth={tooth!r} arch={arch!r} "
f"quad={quad!r} surface={surface!r} billed={billed!r}")
print(f"[DDMA Claim step4] Line {idx}: code={code} tooth={tooth!r} surface={surface!r} billed={billed!r}")
# ── Click "Add a procedure" for lines after the first ─────────
# ── Click "+ Add another treatment" for lines after the first ──
if idx > 0:
try:
add_btn = WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable((By.XPATH,
"//button[.//span[contains(text(),'Add a procedure')]] | "
"//button[contains(normalize-space(text()),'Add a procedure')] | "
"//*[contains(text(),'Add a procedure') and @role='button']"
))
add_link = WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable((By.XPATH, "//a[@data-test-id='addNewRowButton']"))
)
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", add_btn)
add_btn.click()
print(f"[DDMA Claim step4] Clicked 'Add a procedure' for line {idx}")
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", add_link)
add_link.click()
print(f"[DDMA Claim step4] Clicked 'Add another treatment' for line {idx}")
time.sleep(1)
except Exception as e:
print(f"[DDMA Claim step4] Could not click 'Add a procedure': {e}")
print(f"[DDMA Claim step4] Could not click 'Add another treatment': {e}")
# ── Procedure code ────────────────────────────────────────────
# ── Service date ────────────────────────────────────────────
if service_date_str:
try:
date_inp = WebDriverWait(self.driver, 8).until(
EC.element_to_be_clickable((By.XPATH, f"//input[@data-test-id='claimRowServiceDate{idx}']"))
)
date_inp.click()
date_inp.send_keys(Keys.CONTROL, "a")
date_inp.send_keys(Keys.BACKSPACE)
date_inp.send_keys(f"{month}{day}{year}")
print(f"[DDMA Claim step4] serviceDate[{idx}]: filled {service_date_str}")
except Exception as e:
print(f"[DDMA Claim step4] Could not fill service date[{idx}]: {e}")
time.sleep(0.2)
# ── Procedure code (no id/data-test-id — matched positionally) ─
if code:
try:
proc_inputs = self.driver.find_elements(By.XPATH,
"//input[contains(@id,'procedureCode') and contains(@id,'-input')]"
)
proc_inp = proc_inputs[idx] if idx < len(proc_inputs) else proc_inputs[-1]
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", proc_inp)
self._fill_combobox(proc_inp, code, f"procedureCode[{idx}]")
# Wait for the tooth input on the second row to be present AND interactable
if tooth:
try:
WebDriverWait(self.driver, 8).until(
lambda d: (
len(d.find_elements(By.XPATH, "//input[@aria-label='Tooth']")) > idx and
d.find_elements(By.XPATH, "//input[@aria-label='Tooth']")[idx].is_enabled() and
d.find_elements(By.XPATH, "//input[@aria-label='Tooth']")[idx].is_displayed()
)
)
except Exception:
time.sleep(1) # fallback: just wait a second
proc_inputs = self.driver.find_elements(By.XPATH, "//input[@aria-label='e.g. D1234']")
proc_inp = proc_inputs[idx] if idx < len(proc_inputs) else (proc_inputs[-1] if proc_inputs else None)
if proc_inp:
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", proc_inp)
self._fill_combobox(proc_inp, code, f"procedureCode[{idx}]")
else:
print(f"[DDMA Claim step4] procedureCode[{idx}]: input not found")
except Exception as e:
print(f"[DDMA Claim step4] Could not fill procedure code: {e}")
print(f"[DDMA Claim step4] Could not fill procedure code[{idx}]: {e}")
time.sleep(0.2)
# ── Tooth ─────────────────────────────────────────────────────
# ── Tooth Number (custom scrollable dropdown, no native <select>) ─
if tooth:
try:
tooth_inputs = self.driver.find_elements(By.XPATH, "//input[@aria-label='Tooth']")
if idx < len(tooth_inputs):
ti = tooth_inputs[idx]
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", ti)
time.sleep(0.3)
listbox_id = ti.get_attribute("aria-controls") or ""
# Click the "^" chevron toggle button to open the dropdown.
# It lives in the same container as the tooth input —
# try progressively wider ancestor scopes.
toggle_clicked = False
for ancestor_steps in range(1, 6):
ancestor_axis = "/".join([".."] * ancestor_steps)
toggle_xpath = (
f"//input[@aria-label='Tooth'][{idx+1}]"
f"/{ancestor_axis}//button"
)
toggles = self.driver.find_elements(By.XPATH, toggle_xpath)
if toggles:
try:
self.driver.execute_script("arguments[0].click();", toggles[0])
print(f"[DDMA Claim step4] tooth[{idx}]: clicked toggle btn (ancestor depth {ancestor_steps})")
toggle_clicked = True
break
except Exception:
continue
if not toggle_clicked:
# Fallback: JS focus the input to open the combobox
self.driver.execute_script("arguments[0].focus();", ti)
print(f"[DDMA Claim step4] tooth[{idx}]: no toggle btn found, used JS focus")
time.sleep(0.5)
# Select the matching tooth number from the 1-32 listbox
try:
if listbox_id:
option = WebDriverWait(self.driver, 5).until(
EC.element_to_be_clickable((By.XPATH,
f"//*[@id='{listbox_id}']//*[@role='option' and normalize-space(.)='{tooth}']"
))
)
else:
option = WebDriverWait(self.driver, 5).until(
EC.element_to_be_clickable((By.XPATH,
f"//*[@role='listbox']//*[@role='option' and normalize-space(.)='{tooth}']"
))
)
option.click()
print(f"[DDMA Claim step4] tooth[{idx}]: selected '{tooth}' from dropdown")
except TimeoutException:
print(f"[DDMA Claim step4] tooth[{idx}]: option '{tooth}' not found in dropdown")
time.sleep(0.3)
tooth_triggers = self.driver.find_elements(By.XPATH, "//span[contains(@class,'filtrable')]")
if idx < len(tooth_triggers):
trigger = tooth_triggers[idx]
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", trigger)
trigger.click()
time.sleep(0.4)
option = WebDriverWait(self.driver, 5).until(
EC.element_to_be_clickable((By.XPATH,
f"//*[@role='option' and normalize-space(.)='{tooth}'] | "
f"//li[normalize-space(.)='{tooth}'] | "
f"//*[contains(@class,'dropdown')]//*[normalize-space(.)='{tooth}']"
))
)
option.click()
print(f"[DDMA Claim step4] tooth[{idx}]: selected '{tooth}'")
else:
print(f"[DDMA Claim step4] tooth[{idx}]: dropdown trigger not found")
except Exception as e:
print(f"[DDMA Claim step4] Could not fill tooth: {e}")
print(f"[DDMA Claim step4] Could not fill tooth[{idx}]: {e}")
time.sleep(0.2)
# ── Arch ──────────────────────────────────────────────────────
if arch:
try:
arch_inputs = self.driver.find_elements(By.XPATH, "//input[@aria-label='Arch']")
if idx < len(arch_inputs):
self._fill_combobox(arch_inputs[idx], arch, f"arch[{idx}]")
time.sleep(0.3)
except Exception as e:
print(f"[DDMA Claim step4] Could not fill arch: {e}")
# ── Quad ──────────────────────────────────────────────────────
if quad:
try:
quad_inputs = self.driver.find_elements(By.XPATH, "//input[@aria-label='Quad']")
if idx < len(quad_inputs):
self._fill_combobox(quad_inputs[idx], quad, f"quad[{idx}]")
time.sleep(0.3)
except Exception as e:
print(f"[DDMA Claim step4] Could not fill quad: {e}")
# ── Surface (type directly then Tab to billed amount) ─────────
# ── Surface ──────────────────────────────────────────────────
if surface:
try:
surface_inputs = self.driver.find_elements(By.XPATH, "//input[@aria-label='Surface']")
if idx < len(surface_inputs):
surf_inp = surface_inputs[idx]
surf_inp.click()
surf_inp.send_keys(Keys.CONTROL + "a")
surf_inp.send_keys(Keys.DELETE)
surf_inp.send_keys(surface)
print(f"[DDMA Claim step4] surface[{idx}]: typed '{surface}'")
time.sleep(0.2)
surf_inp = WebDriverWait(self.driver, 5).until(
EC.element_to_be_clickable((By.XPATH, f"//input[@data-test-id='claimRowSurface{idx}']"))
)
self._fill_text_input(surf_inp, surface, f"surface[{idx}]")
except Exception as e:
print(f"[DDMA Claim step4] Could not fill surface: {e}")
print(f"[DDMA Claim step4] Could not fill surface[{idx}]: {e}")
time.sleep(0.2)
# ── Billed amount ─────────────────────────────────────────────
# ── Amount ───────────────────────────────────────────────────
if billed:
try:
billed_inputs = self.driver.find_elements(By.XPATH,
"//input[@aria-label='Enter billed amount']"
amount_inp = WebDriverWait(self.driver, 5).until(
EC.element_to_be_clickable((By.XPATH, f"//input[@data-test-id='claimRowAmount{idx}']"))
)
if idx < len(billed_inputs):
self._fill_text_input(billed_inputs[idx], billed, f"billedAmount[{idx}]")
time.sleep(0.2)
self._fill_text_input(amount_inp, billed, f"amount[{idx}]")
except Exception as e:
print(f"[DDMA Claim step4] Could not fill billed amount: {e}")
print(f"[DDMA Claim step4] Could not fill amount[{idx}]: {e}")
time.sleep(0.2)
print("[DDMA Claim step4] Done")
return "SUCCESS"
@@ -797,12 +656,12 @@ class AutomationDDMAClaimSubmit:
# Step 5 — Attach files #
# ------------------------------------------------------------------ #
def step5_attach_files(self):
"""For each claimFile with a filePath, click 'Add a file' and upload it."""
"""Upload all claimFiles at once via the multi-file drag-and-drop input."""
if not self.claimFiles:
print("[DDMA Claim step5] No files to attach")
return "SUCCESS"
attached = 0
abs_paths = []
for cf in self.claimFiles:
relative_path = cf.get("filePath") or ""
if not relative_path:
@@ -814,49 +673,38 @@ class AutomationDDMAClaimSubmit:
if not os.path.isfile(abs_path):
print(f"[DDMA Claim step5] File not found on disk: {abs_path}")
continue
abs_paths.append(abs_path)
print(f"[DDMA Claim step5] Attaching: {abs_path}")
try:
# Click "Add a file" button/link
add_file_btn = WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable((By.XPATH,
"//button[.//span[contains(text(),'Add a file')]] | "
"//button[contains(normalize-space(text()),'Add a file')] | "
"//*[contains(text(),'Add a file') and (@role='button' or self::label)]"
))
)
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", add_file_btn)
ActionChains(self.driver).move_to_element(add_file_btn).click().perform()
time.sleep(1)
if not abs_paths:
print("[DDMA Claim step5] No valid files found on disk to attach")
return "SUCCESS"
# Find the file input that appeared (may be hidden)
file_input = WebDriverWait(self.driver, 8).until(
EC.presence_of_element_located((By.XPATH, "//input[@type='file']"))
)
# Make hidden inputs interactable
self.driver.execute_script("arguments[0].style.display='block';", file_input)
file_input.send_keys(abs_path)
time.sleep(1.5)
print(f"[DDMA Claim step5] Attached: {os.path.basename(abs_path)}")
attached += 1
except Exception as e:
print(f"[DDMA Claim step5] Could not attach {abs_path}: {e}")
try:
file_input = WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.XPATH, "//input[@data-test-id='fileUploadInput']"))
)
# The input may be visually hidden behind a drag-and-drop overlay.
self.driver.execute_script("arguments[0].style.display='block';", file_input)
# <input multiple> accepts several paths joined by newlines in one send_keys call.
file_input.send_keys("\n".join(abs_paths))
time.sleep(1.5)
print(f"[DDMA Claim step5] Attached {len(abs_paths)}/{len(self.claimFiles)} file(s): "
f"{[os.path.basename(p) for p in abs_paths]}")
except Exception as e:
print(f"[DDMA Claim step5] Could not attach files: {e}")
return f"ERROR: step5 failed: {e}"
print(f"[DDMA Claim step5] Attached {attached}/{len(self.claimFiles)} file(s)")
return "SUCCESS"
# ------------------------------------------------------------------ #
# Step 6 — Click Next step #
# ------------------------------------------------------------------ #
def step6_click_next(self):
"""Click the 'Next step' button (React Aria — dispatches pointer events directly)."""
"""Click the sidebar 'Submit Claim' navigation button to advance from the Claim Detail form."""
try:
print(f"[DDMA Claim step6] Current URL: {self.driver.current_url}")
btn = WebDriverWait(self.driver, 15).until(
EC.element_to_be_clickable((By.XPATH,
"//button[@data-testid='next-step-btn'] | "
"//button[@aria-label='Next step']"
))
EC.element_to_be_clickable((By.XPATH, "//button[@data-test-id='submitClaimNavigationButton']"))
)
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", btn)
time.sleep(0.5)
@@ -876,85 +724,40 @@ class AutomationDDMAClaimSubmit:
return f"ERROR: step6 failed: {e}"
# ------------------------------------------------------------------ #
# Step 7 — Claims summary: check acknowledgement + submit #
# Step 7 — No-op: step6's "Submit Claim" click already finalizes the #
# claim on the new site (no separate acknowledgement/summary page). #
# ------------------------------------------------------------------ #
def step7_submit_claim(self):
"""On the claims summary page, tick the acknowledgement checkbox then click Submit claim."""
"""Confirm the confirmation page is present (submission already happened in step6)."""
try:
print(f"[DDMA Claim step7] Current URL: {self.driver.current_url}")
# Wait for the acknowledgement checkbox to appear
checkbox = WebDriverWait(self.driver, 15).until(
EC.presence_of_element_located((By.XPATH,
"//input[@type='checkbox'] | "
"//*[@role='checkbox'] | "
"//label[contains(.,'submitting this claim')]//input | "
"//*[contains(@aria-label,'submitting this claim')]"
))
WebDriverWait(self.driver, 20).until(
lambda d: "claim submitted successfully" in d.page_source.lower()
)
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", checkbox)
time.sleep(0.3)
self.driver.execute_script("""
var el = arguments[0];
el.dispatchEvent(new PointerEvent('pointerover', {bubbles:true, cancelable:true, composed:true}));
el.dispatchEvent(new PointerEvent('pointerdown', {bubbles:true, cancelable:true, composed:true}));
el.dispatchEvent(new PointerEvent('pointerup', {bubbles:true, cancelable:true, composed:true}));
el.dispatchEvent(new MouseEvent('click', {bubbles:true, cancelable:true, composed:true}));
""", checkbox)
print("[DDMA Claim step7] Checked acknowledgement checkbox")
time.sleep(0.5)
# Click Submit claim button
submit_btn = None
for xpath in [
"//button[.//span[contains(text(),'Submit claim')]]",
"//button[contains(normalize-space(text()),'Submit claim')]",
"//button[@aria-label='Submit claim']",
]:
try:
submit_btn = WebDriverWait(self.driver, 8).until(
EC.element_to_be_clickable((By.XPATH, xpath))
)
if submit_btn:
break
except Exception:
continue
if not submit_btn:
return "ERROR: step7 failed: Submit claim button not found"
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", submit_btn)
time.sleep(0.3)
self.driver.execute_script("""
var el = arguments[0];
el.dispatchEvent(new PointerEvent('pointerover', {bubbles:true, cancelable:true, composed:true}));
el.dispatchEvent(new PointerEvent('pointerdown', {bubbles:true, cancelable:true, composed:true}));
el.dispatchEvent(new PointerEvent('pointerup', {bubbles:true, cancelable:true, composed:true}));
el.dispatchEvent(new MouseEvent('click', {bubbles:true, cancelable:true, composed:true}));
""", submit_btn)
print("[DDMA Claim step7] Clicked 'Submit claim'")
time.sleep(2)
print(f"[DDMA Claim step7] URL after submit: {self.driver.current_url}")
print("[DDMA Claim step7] Confirmation page detected — no-op (step6 already submitted)")
return "SUCCESS"
except TimeoutException:
print(f"[DDMA Claim step7] Confirmation page not detected — URL: {self.driver.current_url}")
return "ERROR: step7 failed: confirmation page not detected"
except Exception as e:
print(f"[DDMA Claim step7] Exception: {e}")
return f"ERROR: step7 failed: {e}"
def step8_save_confirmation_pdf(self):
"""Wait for the Thank-you page, extract the claim number, save page as PDF."""
"""Wait for the confirmation page, extract the claim number, save page as PDF."""
import re
try:
# Wait for confirmation page
WebDriverWait(self.driver, 30).until(
lambda d: "thank" in d.page_source.lower() or "submitted claim" in d.page_source.lower()
lambda d: "claim submitted successfully" in d.page_source.lower()
)
time.sleep(2)
print(f"[DDMA Claim step8] Confirmation page URL: {self.driver.current_url}")
# Extract claim number from page text
# Extract claim number from page text (row: "Claim Number" label + value, e.g. "202626032473000")
claim_number = None
try:
body_text = self.driver.find_element(By.TAG_NAME, "body").text
match = re.search(r'submitted claim\s+(\d{10,})', body_text, re.IGNORECASE)
match = re.search(r'claim number\s*\n?\s*(\d{6,})', body_text, re.IGNORECASE)
if match:
claim_number = match.group(1)
print(f"[DDMA Claim step8] Extracted claim number: {claim_number}")