fix: DDMA Create claim stale element, tooth dropdown select, longer page waits

step2: wait for patient list //tbody//tr then 3s stabilize; wait for patient
name link to be element_to_be_clickable before reading href; wait for Create
claim button to be element_to_be_clickable (visible+enabled) then 3s for React
to finish re-rendering.

step3: re-find Create claim button fresh each attempt (avoids stale element
from React re-render); try selenium click → js events → js.click() in sequence;
verify URL changed before declaring success.

step4: open tooth dropdown via JS focus (avoids element-not-interactable on
click); select the matching tooth number option directly from 1-32 listbox
instead of typing characters.

step7: find Submit claim button with individual XPaths to avoid NoneType crash.

claims-page: use wouter setLocation for URL param cleanup so internal search
state stays in sync.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ff
2026-06-07 00:28:01 -04:00
parent d02e8c8dcb
commit 730c41f9b0
2 changed files with 163 additions and 59 deletions

View File

@@ -341,30 +341,32 @@ class AutomationDDMAClaimSubmit:
def step2_open_member_page(self):
"""Navigate to member detail page — same approach as DDMA eligibility step2."""
try:
# Wait for the results table to appear, then let it stabilize
try:
WebDriverWait(self.driver, 10).until(
WebDriverWait(self.driver, 20).until(
EC.presence_of_element_located((By.XPATH, "//tbody//tr"))
)
time.sleep(2)
time.sleep(3) # wait for the list to fully stabilize
except TimeoutException:
print("[DDMA Claim step2] Warning: Results table not found within timeout")
# Find member-details URL from first row — identical to eligibility step2
# Wait until the patient name link in the first row is clickable
detail_url = None
for selector in [
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:
try:
link_el = WebDriverWait(self.driver, 5).until(
EC.presence_of_element_located((By.XPATH, selector))
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] Found detail URL: {href}")
print(f"[DDMA Claim step2] Patient link is clickable: {href}")
break
except Exception:
continue
@@ -384,14 +386,16 @@ class AutomationDDMAClaimSubmit:
print(f"[DDMA Claim step2] Warning — URL: {self.driver.current_url}")
try:
# Wait until Create claim button is visible AND enabled (not just in the DOM)
WebDriverWait(self.driver, 15).until(
EC.presence_of_element_located((By.XPATH, "//button[@aria-label='Create claim']"))
EC.element_to_be_clickable((By.XPATH, "//button[@aria-label='Create claim']"))
)
print("[DDMA Claim step2] 'Create claim' button found")
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 found")
print("[DDMA Claim step2] Warning: 'Create claim' button not clickable in time")
time.sleep(2)
time.sleep(2)
return "SUCCESS"
except Exception as e:
@@ -406,46 +410,92 @@ class AutomationDDMAClaimSubmit:
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)
# Log all buttons on page for debugging
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]}")
# 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 = WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable((By.XPATH,
"//button[@aria-label='Create claim' and @data-react-aria-pressable='true']"
))
)
print(f"[DDMA Claim step3] Found 'Create claim' button, displayed={btn.is_displayed()}, enabled={btn.is_enabled()}")
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 all click methods in sequence until one causes navigation
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)
print("[DDMA Claim step3] Dispatched pointer+click events on 'Create claim'")
# 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
time.sleep(2)
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)
# Switch to new tab if one 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")
print(f"[DDMA Claim step3] Clicked via {method} (attempt {attempt+1})")
time.sleep(2)
print(f"[DDMA Claim step3] Post-click URL: {self.driver.current_url}")
# 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...")
# Wait for claim form — just log, don't fail
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(
@@ -457,7 +507,7 @@ class AutomationDDMAClaimSubmit:
)),
)
)
print(f"[DDMA Claim step3] Claim form loaded")
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}")
@@ -629,7 +679,18 @@ class AutomationDDMAClaimSubmit:
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}]")
time.sleep(0.5)
# 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
except Exception as e:
print(f"[DDMA Claim step4] Could not fill procedure code: {e}")
@@ -638,7 +699,31 @@ class AutomationDDMAClaimSubmit:
try:
tooth_inputs = self.driver.find_elements(By.XPATH, "//input[@aria-label='Tooth']")
if idx < len(tooth_inputs):
self._fill_combobox(tooth_inputs[idx], tooth, f"tooth[{idx}]")
ti = tooth_inputs[idx]
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", ti)
time.sleep(0.3)
# Open the dropdown via JS focus (avoids element-not-interactable on click)
self.driver.execute_script("arguments[0].focus();", ti)
time.sleep(0.5)
# Select the option matching the tooth number from the 1-32 dropdown
listbox_id = ti.get_attribute("aria-controls") or ""
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}]: dropdown not found, skipping")
time.sleep(0.3)
except Exception as e:
print(f"[DDMA Claim step4] Could not fill tooth: {e}")
@@ -663,7 +748,7 @@ class AutomationDDMAClaimSubmit:
except Exception as e:
print(f"[DDMA Claim step4] Could not fill quad: {e}")
# ── Surface (free-text combobox — type directly) ──────────────
# ── Surface (type directly then Tab to billed amount) ─────────
if surface:
try:
surface_inputs = self.driver.find_elements(By.XPATH, "//input[@aria-label='Surface']")
@@ -673,9 +758,6 @@ class AutomationDDMAClaimSubmit:
surf_inp.send_keys(Keys.CONTROL + "a")
surf_inp.send_keys(Keys.DELETE)
surf_inp.send_keys(surface)
# Dismiss any listbox with Escape so it doesn't block next field
time.sleep(0.3)
surf_inp.send_keys(Keys.ESCAPE)
print(f"[DDMA Claim step4] surface[{idx}]: typed '{surface}'")
time.sleep(0.2)
except Exception as e:
@@ -812,13 +894,22 @@ class AutomationDDMAClaimSubmit:
time.sleep(0.5)
# Click Submit claim button
submit_btn = WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable((By.XPATH,
"//button[.//span[contains(text(),'Submit claim')]] | "
"//button[contains(normalize-space(text()),'Submit claim')] | "
"//button[@aria-label='Submit claim']"
))
)
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("""