fix: complete United Ucard claim flow and remember OTP device
- Click the first visible 'Next' button on the Patient Information step instead of a stale hidden one, so the wizard advances after member select - Read procedure fee from totalBilled/billedAmount (matching every other claim worker) instead of a fee key the frontend never sends - Implement claim confirmation PDF capture + claim number extraction - Check "Remember this device" on the OTP page so future logins skip OTP Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -160,6 +160,22 @@ async def start_uniteducard_claim_run(sid: str, data: dict, url: str):
|
||||
)
|
||||
otp_input.clear()
|
||||
otp_input.send_keys(otp_value)
|
||||
|
||||
# Check "Remember this device" so future logins skip OTP.
|
||||
# Confirmed against live site (2026-09-15):
|
||||
# <span role="checkbox" data-cy="data-checkbox-rememberDevice"
|
||||
# aria-checked="false" ...>
|
||||
try:
|
||||
remember_checkbox = driver.find_element(By.CSS_SELECTOR,
|
||||
"[data-cy='data-checkbox-rememberDevice']")
|
||||
if remember_checkbox.get_attribute("aria-checked") != "true":
|
||||
driver.execute_script(
|
||||
"arguments[0].scrollIntoView({block:'center'});", remember_checkbox)
|
||||
remember_checkbox.click()
|
||||
print("[UnitedUcard Claim OTP] Checked 'Remember this device'")
|
||||
except Exception as remember_err:
|
||||
print(f"[UnitedUcard Claim OTP] Could not check 'Remember this device': {remember_err}")
|
||||
|
||||
try:
|
||||
try:
|
||||
verify_btn = driver.find_element(By.ID, "continuebtn")
|
||||
|
||||
@@ -179,6 +179,22 @@ async def start_uniteducard_run(sid: str, data: dict, url: str):
|
||||
)
|
||||
otp_input.clear()
|
||||
otp_input.send_keys(otp_value)
|
||||
|
||||
# Check "Remember this device" so future logins skip OTP.
|
||||
# Confirmed against live site (2026-09-15):
|
||||
# <span role="checkbox" data-cy="data-checkbox-rememberDevice"
|
||||
# aria-checked="false" ...>
|
||||
try:
|
||||
remember_checkbox = driver.find_element(By.CSS_SELECTOR,
|
||||
"[data-cy='data-checkbox-rememberDevice']")
|
||||
if remember_checkbox.get_attribute("aria-checked") != "true":
|
||||
driver.execute_script(
|
||||
"arguments[0].scrollIntoView({block:'center'});", remember_checkbox)
|
||||
remember_checkbox.click()
|
||||
print("[UnitedUcard OTP] Checked 'Remember this device'")
|
||||
except Exception as remember_err:
|
||||
print(f"[UnitedUcard OTP] Could not check 'Remember this device': {remember_err}")
|
||||
|
||||
try:
|
||||
# Try the confirmed id first — the broader text/type
|
||||
# fallback below can match the WRONG button if more
|
||||
|
||||
@@ -22,6 +22,8 @@ from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
import time
|
||||
import os
|
||||
import re
|
||||
import base64
|
||||
|
||||
from uniteducard_browser_manager import get_browser_manager
|
||||
|
||||
@@ -285,13 +287,26 @@ class AutomationUnitedUcardClaimSubmit:
|
||||
the visible label is a plain <div> ("Next"); on the Claim Information
|
||||
step it's instead a chevron_right.jpg image with no text. Either way
|
||||
the click target is whichever clickable ancestor wraps it.
|
||||
|
||||
On the Patient Information step specifically, after a member is
|
||||
selected and its details card is shown, the old text-based "Next"
|
||||
div is hidden (not removed) and a new chevron_right.jpg-based Next
|
||||
renders below the member info section. Both XPath branches can
|
||||
therefore match simultaneously, with the hidden old one appearing
|
||||
first in DOM order — so this must pick the first *visible* match,
|
||||
not just the first *present* one, or it silently clicks a dead
|
||||
element and the wizard never advances.
|
||||
"""
|
||||
try:
|
||||
next_label = WebDriverWait(self.driver, 15).until(
|
||||
EC.presence_of_element_located((By.XPATH,
|
||||
"//div[normalize-space(text())='Next'] | "
|
||||
"//img[contains(@src,'chevron_right')]"))
|
||||
)
|
||||
def _first_visible_next(driver):
|
||||
for el in driver.find_elements(By.XPATH,
|
||||
"//div[normalize-space(text())='Next'] | "
|
||||
"//img[contains(@src,'chevron_right')]"):
|
||||
if el.is_displayed():
|
||||
return el
|
||||
return None
|
||||
|
||||
next_label = WebDriverWait(self.driver, 15).until(_first_visible_next)
|
||||
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", next_label)
|
||||
time.sleep(0.3)
|
||||
# Prefer clicking a clickable ancestor (button/a/[role=button]) so
|
||||
@@ -650,7 +665,7 @@ class AutomationUnitedUcardClaimSubmit:
|
||||
<div onclick="saveProcCode()">Save Procedure</div>
|
||||
"""
|
||||
procedure_code = str(line.get("procedureCode", "")).strip()
|
||||
fee = line.get("fee")
|
||||
fee = line.get("totalBilled") or line.get("billedAmount") or line.get("fee")
|
||||
procedure_date = line.get("procedureDate") or self.serviceDate
|
||||
|
||||
if not procedure_code:
|
||||
@@ -811,6 +826,69 @@ class AutomationUnitedUcardClaimSubmit:
|
||||
return True
|
||||
|
||||
def step7_save_confirmation_pdf(self):
|
||||
"""TODO: capture the claim confirmation number and save/print the confirmation as PDF
|
||||
into self.download_dir. Return {"pdf_path": "...", "claimNumber": "..."}."""
|
||||
return {}
|
||||
"""
|
||||
Wait for the "Claim Submitted" confirmation page, extract the Claim
|
||||
ID, and save the page as a PDF.
|
||||
|
||||
Confirmed against the live site (2026-09-15):
|
||||
<h1>Claim Submitted</h1>
|
||||
Claim ID: <a>260483578000</a>
|
||||
Submitted On: Tue Sep 15 2026 22:29:39 GMT-0400 (Eastern Daylight Time)
|
||||
"""
|
||||
try:
|
||||
WebDriverWait(self.driver, 30).until(
|
||||
lambda d: "claim submitted" in d.page_source.lower()
|
||||
)
|
||||
time.sleep(2)
|
||||
print(f"[UnitedUcard Claim step7] Confirmation page URL: {self.driver.current_url}")
|
||||
|
||||
claim_number = None
|
||||
try:
|
||||
body_text = self.driver.find_element(By.TAG_NAME, "body").text
|
||||
match = re.search(r'Claim ID:?\s*(\d{6,})', body_text, re.IGNORECASE)
|
||||
if match:
|
||||
claim_number = match.group(1)
|
||||
print(f"[UnitedUcard Claim step7] Extracted claim number: {claim_number}")
|
||||
else:
|
||||
match = re.search(r'\b(\d{10,})\b', body_text)
|
||||
if match:
|
||||
claim_number = match.group(1)
|
||||
print(f"[UnitedUcard Claim step7] Extracted claim number (fallback): {claim_number}")
|
||||
except Exception as e:
|
||||
print(f"[UnitedUcard Claim step7] Could not extract claim number: {e}")
|
||||
|
||||
shared_downloads = os.path.join(_SERVICE_DIR, "downloads")
|
||||
os.makedirs(shared_downloads, exist_ok=True)
|
||||
safe_member = "".join(c for c in str(self.memberId) if c.isalnum() or c in "-_.")
|
||||
safe_claim = ("_" + claim_number[:20]) if claim_number else ""
|
||||
timestamp = time.strftime("%Y%m%d_%H%M%S")
|
||||
pdf_filename = f"uniteducard_claim_confirmation_{safe_member}{safe_claim}_{timestamp}.pdf"
|
||||
pdf_path = os.path.join(shared_downloads, pdf_filename)
|
||||
|
||||
try:
|
||||
pdf_data = self.driver.execute_cdp_cmd("Page.printToPDF", {
|
||||
"printBackground": True,
|
||||
"paperWidth": 8.5,
|
||||
"paperHeight": 11,
|
||||
"marginTop": 0.4,
|
||||
"marginBottom": 0.4,
|
||||
"marginLeft": 0.4,
|
||||
"marginRight": 0.4,
|
||||
})
|
||||
pdf_bytes = base64.b64decode(pdf_data["data"])
|
||||
with open(pdf_path, "wb") as f:
|
||||
f.write(pdf_bytes)
|
||||
print(f"[UnitedUcard Claim step7] PDF saved: {pdf_path}")
|
||||
except Exception as e:
|
||||
print(f"[UnitedUcard Claim step7] PDF capture failed: {e}")
|
||||
return f"ERROR: step7 PDF failed: {e}"
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"pdf_path": pdf_path,
|
||||
"claimNumber": claim_number,
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
print(f"[UnitedUcard Claim step7] Exception: {e}")
|
||||
return f"ERROR: step7 failed: {e}"
|
||||
|
||||
Reference in New Issue
Block a user