fix: handle no-history single-match case in United Ucard claim search
Only two paths now: click the radio in the last history row when a table is shown, or click the Member Details link directly when no history/radio appears at all (single unambiguous match, as seen for patient Nassim). Removes the first-row retry fallback. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -431,79 +431,55 @@ class AutomationUnitedUcardClaimSubmit:
|
||||
|
||||
def _select_search_result(self):
|
||||
"""
|
||||
When the Subscriber ID + DOB search matches multiple coverage records
|
||||
("Duplicate records have been found"), select the LAST row in the
|
||||
results table (the default/most-recent coverage record) and click
|
||||
SELECT. If the "Member Details" name link doesn't show up afterward
|
||||
(the last row turned out to be an inactive coverage record — seen
|
||||
for some providers' patients, e.g. provider "JOHN"), fall back to
|
||||
selecting the FIRST row instead and click SELECT again.
|
||||
After the Subscriber ID + DOB search, the site does one of two things:
|
||||
|
||||
Confirmed against the live site (2026-09-13): a results table with
|
||||
columns Name / Group / Coverage End Date / Address, a radio button
|
||||
in the first cell of each row, and CANCEL / SELECT pill buttons
|
||||
below the table.
|
||||
1. Multiple coverage records exist ("Duplicate records have been
|
||||
found") — a results table appears with a radio button in each
|
||||
row (Name / Group / Coverage End Date / Address columns) and
|
||||
CANCEL / SELECT pill buttons below it. Select the LAST row (the
|
||||
default/most-recent coverage record) and click SELECT.
|
||||
2. A single unambiguous match — no insurance history table and no
|
||||
radio at all; the page goes straight to a "Member Details"
|
||||
table with the patient's name as a link (confirmed live for
|
||||
patient Nassim, 2026-09-15).
|
||||
|
||||
Either path ends by clicking the "Member Details" name link, which
|
||||
replaces the table with a confirmation card and enables the bottom
|
||||
"Next" button.
|
||||
"""
|
||||
try:
|
||||
result_rows = WebDriverWait(self.driver, 15).until(
|
||||
lambda d: d.find_elements(By.CSS_SELECTOR, "table tbody tr") or None
|
||||
lambda d: [r for r in d.find_elements(By.CSS_SELECTOR, "table tbody tr")
|
||||
if r.find_elements(By.CSS_SELECTOR, "input[type='radio']")] or None
|
||||
)
|
||||
except TimeoutException:
|
||||
# A single unambiguous match may skip the results table entirely
|
||||
# and advance straight to Claim Information.
|
||||
try:
|
||||
WebDriverWait(self.driver, 5).until(
|
||||
EC.presence_of_element_located((By.XPATH, "//*[contains(text(),'Claim Information')]"))
|
||||
)
|
||||
print("[UnitedUcard Claim] Single match — already advanced to Claim Information")
|
||||
return True
|
||||
except TimeoutException:
|
||||
return "ERROR: No search results table found after Search"
|
||||
result_rows = None
|
||||
|
||||
if not result_rows:
|
||||
return "ERROR: No matching patient records found"
|
||||
|
||||
total_rows = len(result_rows)
|
||||
result = self._select_row_and_wait_for_member_link(-1, "last", total_rows, member_link_timeout=8)
|
||||
if result is True:
|
||||
return self._click_next("Patient Information step")
|
||||
|
||||
if result != "NO_MEMBER_LINK":
|
||||
if result_rows:
|
||||
result = self._select_last_row_and_click_select(len(result_rows))
|
||||
if result is not True:
|
||||
return result # propagate a hard ERROR (radio/select button not found)
|
||||
else:
|
||||
print("[UnitedUcard Claim] No insurance history/radio shown — single unambiguous match")
|
||||
|
||||
if total_rows < 2:
|
||||
return "ERROR: Member Details link not found after selecting last row"
|
||||
|
||||
print("[UnitedUcard Claim] Last row showed no active member — retrying with first row")
|
||||
try:
|
||||
WebDriverWait(self.driver, 10).until(
|
||||
lambda d: d.find_elements(By.CSS_SELECTOR, "table tbody tr") or None
|
||||
)
|
||||
except TimeoutException:
|
||||
return "ERROR: Results table no longer available to retry with first row"
|
||||
|
||||
result = self._select_row_and_wait_for_member_link(0, "first", total_rows, member_link_timeout=15)
|
||||
result = self._click_member_link_or_advance()
|
||||
if result is True:
|
||||
return self._click_next("Patient Information step")
|
||||
if result == "NO_MEMBER_LINK":
|
||||
return "ERROR: Member Details link not found after selecting either the last or first row"
|
||||
return result
|
||||
|
||||
def _select_row_and_wait_for_member_link(self, row_index, row_label, total_rows, member_link_timeout):
|
||||
def _select_last_row_and_click_select(self, total_rows):
|
||||
"""
|
||||
Clicks the radio button in the row at `row_index` (re-located fresh,
|
||||
since a prior failed SELECT may have altered the DOM), clicks SELECT,
|
||||
then waits up to `member_link_timeout` seconds for the "Member
|
||||
Details" name link to appear.
|
||||
Clicks the radio button in the LAST row of the insurance history
|
||||
table, then clicks SELECT.
|
||||
|
||||
Returns True (link found & clicked), "NO_MEMBER_LINK" (link never
|
||||
appeared — likely an inactive record), or an "ERROR: ..." string for
|
||||
a hard failure (radio/select button not found).
|
||||
Returns True, or an "ERROR: ..." string for a hard failure.
|
||||
"""
|
||||
try:
|
||||
row = self.driver.find_elements(By.CSS_SELECTOR, "table tbody tr")[row_index]
|
||||
rows = [r for r in self.driver.find_elements(By.CSS_SELECTOR, "table tbody tr")
|
||||
if r.find_elements(By.CSS_SELECTOR, "input[type='radio']")]
|
||||
row = rows[-1]
|
||||
except IndexError:
|
||||
return f"ERROR: Could not re-locate {row_label} search result row"
|
||||
return "ERROR: Could not re-locate last search result row"
|
||||
|
||||
try:
|
||||
radio = row.find_element(By.CSS_SELECTOR, "input[type='radio']")
|
||||
@@ -511,7 +487,7 @@ class AutomationUnitedUcardClaimSubmit:
|
||||
try:
|
||||
radio = row.find_element(By.XPATH, ".//*[@role='radio' or contains(@class,'radio')]")
|
||||
except Exception as e:
|
||||
return f"ERROR: Could not find radio button in {row_label} search result row: {e}"
|
||||
return f"ERROR: Could not find radio button in last search result row: {e}"
|
||||
|
||||
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", radio)
|
||||
time.sleep(0.3)
|
||||
@@ -519,7 +495,7 @@ class AutomationUnitedUcardClaimSubmit:
|
||||
radio.click()
|
||||
except Exception:
|
||||
self.driver.execute_script("arguments[0].click();", radio)
|
||||
print(f"[UnitedUcard Claim] Selected {row_label} matching patient record ({total_rows} found)")
|
||||
print(f"[UnitedUcard Claim] Selected last matching patient record ({total_rows} found)")
|
||||
|
||||
# Confirmed against the live site (2026-09-13):
|
||||
# <input type="button" class="btn btn-sm btn-primary pull-right margin-left-10"
|
||||
@@ -543,16 +519,25 @@ class AutomationUnitedUcardClaimSubmit:
|
||||
print("[UnitedUcard Claim] Clicked SELECT (fallback selector)")
|
||||
time.sleep(2)
|
||||
except TimeoutException:
|
||||
return f"ERROR: SELECT button not found after choosing {row_label} patient record"
|
||||
return "ERROR: SELECT button not found after choosing last patient record"
|
||||
|
||||
# After SELECT, a "Member Details" table appears below with the
|
||||
# patient's name as a link. Clicking it does NOT navigate away —
|
||||
# it replaces the table with a confirmation card (avatar initials +
|
||||
# name, Group Number, Subscriber ID, DOB, Address) on the same
|
||||
# Patient Information page, and enables the bottom "Next" button.
|
||||
# If the selected record is inactive, this link never appears.
|
||||
return True
|
||||
|
||||
def _click_member_link_or_advance(self, timeout=15):
|
||||
"""
|
||||
After SELECT (or immediately after Search, for a single
|
||||
unambiguous match with no history table), a "Member Details" table
|
||||
appears with the patient's name as a link. Clicking it does NOT
|
||||
navigate away — it replaces the table with a confirmation card
|
||||
(avatar initials + name, Group Number, Subscriber ID, DOB, Address)
|
||||
on the same Patient Information page, and enables the bottom
|
||||
"Next" button.
|
||||
|
||||
Returns True (link found & clicked, or already advanced to Claim
|
||||
Information), or an "ERROR: ..." string.
|
||||
"""
|
||||
try:
|
||||
member_link = WebDriverWait(self.driver, member_link_timeout).until(
|
||||
member_link = WebDriverWait(self.driver, timeout).until(
|
||||
EC.element_to_be_clickable((By.XPATH,
|
||||
"//*[contains(text(),'Member Details')]/following::table[1]//a[1]"))
|
||||
)
|
||||
@@ -562,8 +547,16 @@ class AutomationUnitedUcardClaimSubmit:
|
||||
time.sleep(2)
|
||||
return True
|
||||
except TimeoutException:
|
||||
print(f"[UnitedUcard Claim] Member Details link did not appear after selecting {row_label} row")
|
||||
return "NO_MEMBER_LINK"
|
||||
pass
|
||||
|
||||
try:
|
||||
WebDriverWait(self.driver, 5).until(
|
||||
EC.presence_of_element_located((By.XPATH, "//*[contains(text(),'Claim Information')]"))
|
||||
)
|
||||
print("[UnitedUcard Claim] Already advanced to Claim Information")
|
||||
return True
|
||||
except TimeoutException:
|
||||
return "ERROR: Member Details link not found after Search"
|
||||
|
||||
def _click_radio_by_id(self, element_id, label=""):
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user