feat: chatbot screenshot-only eligibility detect, Sun Life/DentaQuest auto-check, chatbot attachment handoff to preauth; fix TuftsSCO preauth Selenium reliability
- Chatbot: submitting with only a screenshot attached (no text) now triggers the same "AI Detect & Check Eligibility" flow as the Copy Agent page - detect-eligibility-info now also extracts the visible insurance payer name and picks Tufts SCO auto-check when both Sun Life and DentaQuest are detected, else MassHealth - Chatbot-attached files are now handed off to both claim and preauth forms unconditionally, not just claims - TuftsSCO preauth Selenium worker: verify-and-retry the Tooth field (typing was silently getting reset by duplicate-procedure-code warning banners), add a final re-verification pass across all rows, fix acknowledgement-checkbox targeting/verification, and verify the "Next step" click actually advances the wizard instead of trusting a blind click
This commit is contained in:
@@ -596,6 +596,36 @@ class AutomationTuftsSCOPreAuth:
|
||||
except Exception as e:
|
||||
print(f"[TuftsSCO PreAuth step4] Warning: could not fill {label}: {e}")
|
||||
|
||||
def _fill_tooth_field(self, idx, tooth, context=""):
|
||||
"""Fill the Tooth input for row idx, verifying the DOM value against what was typed
|
||||
and retrying if it didn't stick. Entering the same procedure code on multiple rows
|
||||
(e.g. one code across several teeth) triggers a duplicate-code warning banner that can
|
||||
re-render the form and silently reset earlier rows' Tooth values — callers should re-run
|
||||
this for every row after the whole form is filled, not just once at type-time."""
|
||||
tooth = str(tooth).strip()
|
||||
actual = None
|
||||
for attempt in range(3):
|
||||
tooth_inputs = self.driver.find_elements(By.XPATH, "//input[@aria-label='Tooth']")
|
||||
if idx >= len(tooth_inputs):
|
||||
return None
|
||||
tooth_inp = tooth_inputs[idx]
|
||||
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", tooth_inp)
|
||||
self.driver.execute_script("arguments[0].focus();", tooth_inp)
|
||||
time.sleep(0.2)
|
||||
tooth_inp.send_keys(Keys.CONTROL + "a")
|
||||
tooth_inp.send_keys(Keys.DELETE)
|
||||
tooth_inp.send_keys(tooth)
|
||||
time.sleep(0.3)
|
||||
actual = (tooth_inp.get_attribute("value") or "").strip()
|
||||
if actual == tooth:
|
||||
print(f"[TuftsSCO PreAuth step4] tooth[{idx}]{context}: confirmed '{tooth}'")
|
||||
return actual
|
||||
print(f"[TuftsSCO PreAuth step4] tooth[{idx}]{context}: attempt {attempt + 1} — "
|
||||
f"expected '{tooth}', field shows {actual!r}, retrying")
|
||||
print(f"[TuftsSCO PreAuth step4] WARNING: tooth[{idx}]{context} did not stick — "
|
||||
f"expected '{tooth}', field shows {actual!r} after 3 attempts")
|
||||
return actual
|
||||
|
||||
def _fill_text_input(self, inp, value, label="field"):
|
||||
try:
|
||||
inp.click()
|
||||
@@ -655,17 +685,8 @@ class AutomationTuftsSCOPreAuth:
|
||||
|
||||
if tooth:
|
||||
try:
|
||||
tooth_inputs = self.driver.find_elements(By.XPATH, "//input[@aria-label='Tooth']")
|
||||
if idx < len(tooth_inputs):
|
||||
tooth_inp = tooth_inputs[idx]
|
||||
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", tooth_inp)
|
||||
self.driver.execute_script("arguments[0].focus();", tooth_inp)
|
||||
time.sleep(0.2)
|
||||
tooth_inp.send_keys(Keys.CONTROL + "a")
|
||||
tooth_inp.send_keys(Keys.DELETE)
|
||||
tooth_inp.send_keys(str(tooth))
|
||||
print(f"[TuftsSCO PreAuth step4] tooth[{idx}]: typed '{tooth}'")
|
||||
time.sleep(0.3)
|
||||
self._fill_tooth_field(idx, tooth)
|
||||
time.sleep(0.1)
|
||||
except Exception as e:
|
||||
print(f"[TuftsSCO PreAuth step4] Could not fill tooth: {e}")
|
||||
|
||||
@@ -714,6 +735,24 @@ class AutomationTuftsSCOPreAuth:
|
||||
except Exception as e:
|
||||
print(f"[TuftsSCO PreAuth step4] Could not fill billed amount: {e}")
|
||||
|
||||
# Adding a row with a procedure code already used by an earlier row triggers a
|
||||
# duplicate-code warning banner that re-renders the form — this can silently reset
|
||||
# a previously-confirmed Tooth value on an earlier row. Re-check every row now that
|
||||
# no more rows will be added (no more duplicate-code banners can fire after this).
|
||||
print("[TuftsSCO PreAuth step4] Final tooth verification pass...")
|
||||
for idx, line in enumerate(active_lines):
|
||||
tooth = str(line.get("toothNumber") or line.get("tooth") or "").strip()
|
||||
if not tooth:
|
||||
continue
|
||||
tooth_inputs = self.driver.find_elements(By.XPATH, "//input[@aria-label='Tooth']")
|
||||
if idx >= len(tooth_inputs):
|
||||
continue
|
||||
actual = (tooth_inputs[idx].get_attribute("value") or "").strip()
|
||||
if actual == tooth:
|
||||
continue
|
||||
print(f"[TuftsSCO PreAuth step4] Final check: tooth[{idx}] regressed to {actual!r}, re-filling '{tooth}'")
|
||||
self._fill_tooth_field(idx, tooth, context=" (final pass)")
|
||||
|
||||
print("[TuftsSCO PreAuth step4] Done")
|
||||
return "SUCCESS"
|
||||
|
||||
@@ -777,27 +816,56 @@ class AutomationTuftsSCOPreAuth:
|
||||
# ── Step 6: Click "Next step" ──────────────────────────────────────────────
|
||||
|
||||
def step6_click_next(self):
|
||||
"""Click the 'Next step' button."""
|
||||
"""Click the 'Next step' button and verify the wizard actually advanced to the
|
||||
acknowledgement page — the URL doesn't change (SPA), so a click that's silently
|
||||
ignored (e.g. button still disabled from a validation error) would otherwise look
|
||||
identical to a successful one in the logs."""
|
||||
try:
|
||||
print(f"[TuftsSCO PreAuth 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']"
|
||||
))
|
||||
)
|
||||
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", btn)
|
||||
time.sleep(0.5)
|
||||
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("[TuftsSCO PreAuth step6] Clicked 'Next step'")
|
||||
time.sleep(2)
|
||||
|
||||
advanced = False
|
||||
for attempt in range(3):
|
||||
btn = WebDriverWait(self.driver, 15).until(
|
||||
EC.element_to_be_clickable((By.XPATH,
|
||||
"//button[@data-testid='next-step-btn'] | "
|
||||
"//button[@aria-label='Next step']"
|
||||
))
|
||||
)
|
||||
disabled = (btn.get_attribute("disabled") is not None) or \
|
||||
((btn.get_attribute("aria-disabled") or "").lower() == "true")
|
||||
if disabled:
|
||||
print(f"[TuftsSCO PreAuth step6] 'Next step' is disabled (attempt {attempt + 1}) — "
|
||||
f"a form field may still be invalid/empty; waiting before retry")
|
||||
time.sleep(1.5)
|
||||
continue
|
||||
|
||||
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", btn)
|
||||
time.sleep(0.5)
|
||||
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(f"[TuftsSCO PreAuth step6] Clicked 'Next step' (attempt {attempt + 1})")
|
||||
|
||||
try:
|
||||
WebDriverWait(self.driver, 6).until(
|
||||
EC.presence_of_element_located((By.XPATH,
|
||||
"//label[contains(.,'submitting this')] | "
|
||||
"//*[contains(@aria-label,'submitting this')]"
|
||||
))
|
||||
)
|
||||
advanced = True
|
||||
print("[TuftsSCO PreAuth step6] Confirmed wizard advanced to the acknowledgement step")
|
||||
break
|
||||
except TimeoutException:
|
||||
print(f"[TuftsSCO PreAuth step6] Page did not advance after click (attempt {attempt + 1}), retrying")
|
||||
|
||||
print(f"[TuftsSCO PreAuth step6] URL after Next: {self.driver.current_url}")
|
||||
if not advanced:
|
||||
return "ERROR: step6 failed: 'Next step' click did not advance the wizard after 3 attempts"
|
||||
return "SUCCESS"
|
||||
except Exception as e:
|
||||
print(f"[TuftsSCO PreAuth step6] Exception: {e}")
|
||||
@@ -805,30 +873,65 @@ class AutomationTuftsSCOPreAuth:
|
||||
|
||||
# ── Step 7: Acknowledge + submit ────────────────────────────────────────────
|
||||
|
||||
@staticmethod
|
||||
def _is_checkbox_checked(el):
|
||||
try:
|
||||
if el.is_selected():
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
state = (el.get_attribute("aria-checked") or el.get_attribute("checked") or "").lower()
|
||||
return state in ("true", "checked")
|
||||
|
||||
def step7_submit_preauth(self):
|
||||
"""On the pre-auth summary page, tick the acknowledgement checkbox then submit."""
|
||||
try:
|
||||
print(f"[TuftsSCO PreAuth step7] Current URL: {self.driver.current_url}")
|
||||
|
||||
checkbox = WebDriverWait(self.driver, 15).until(
|
||||
EC.presence_of_element_located((By.XPATH,
|
||||
"//input[@type='checkbox'] | "
|
||||
"//*[@role='checkbox'] | "
|
||||
"//label[contains(.,'submitting this')]//input | "
|
||||
"//*[contains(@aria-label,'submitting this')]"
|
||||
))
|
||||
)
|
||||
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("[TuftsSCO PreAuth step7] Checked acknowledgement checkbox")
|
||||
time.sleep(0.5)
|
||||
# A bare "//input[@type='checkbox']" would match the FIRST checkbox in the DOM,
|
||||
# which may not be the acknowledgement box (e.g. a row-selection checkbox in the
|
||||
# service-line table). Try specific, label-anchored selectors before falling back.
|
||||
checkbox = None
|
||||
for xpath in [
|
||||
"//label[contains(.,'submitting this')]//input[@type='checkbox']",
|
||||
"//*[contains(@aria-label,'submitting this')]",
|
||||
"//label[contains(translate(., 'ACKNOWLEDGE', 'acknowledge'),'acknowledge')]//input[@type='checkbox']",
|
||||
"//*[contains(translate(@aria-label, 'ACKNOWLEDGE', 'acknowledge'),'acknowledge')]",
|
||||
"//input[@type='checkbox']",
|
||||
"//*[@role='checkbox']",
|
||||
]:
|
||||
try:
|
||||
checkbox = WebDriverWait(self.driver, 5).until(
|
||||
EC.presence_of_element_located((By.XPATH, xpath))
|
||||
)
|
||||
print(f"[TuftsSCO PreAuth step7] Found acknowledgement checkbox via: {xpath}")
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
if checkbox is None:
|
||||
return "ERROR: step7 failed: could not find acknowledgement checkbox"
|
||||
|
||||
for attempt in range(3):
|
||||
if self._is_checkbox_checked(checkbox):
|
||||
break
|
||||
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)
|
||||
time.sleep(0.5)
|
||||
if attempt > 0:
|
||||
print(f"[TuftsSCO PreAuth step7] Acknowledgement checkbox retry {attempt + 1}")
|
||||
|
||||
if self._is_checkbox_checked(checkbox):
|
||||
print("[TuftsSCO PreAuth step7] Confirmed acknowledgement checkbox is checked")
|
||||
else:
|
||||
print("[TuftsSCO PreAuth step7] WARNING: acknowledgement checkbox did not register as checked after 3 attempts")
|
||||
|
||||
all_btns = self.driver.find_elements(By.XPATH, "//button")
|
||||
print(f"[TuftsSCO PreAuth step7] Buttons: {[b.get_attribute('aria-label') or b.text[:40] for b in all_btns]}")
|
||||
|
||||
Reference in New Issue
Block a user