Compare commits

...
2 Commits
2 changed files with 133 additions and 45 deletions
@@ -343,6 +343,20 @@ class AutomationCCAClaimSubmit:
if not verified:
return "ERROR: 'Verify Eligibility' button not found"
# Multiple records popup: pick the first member (top Select)
try:
sel_btn = WebDriverWait(self.driver, 12).until(
EC.element_to_be_clickable((By.ID, "buttonSelectMember0"))
)
print("[CCA Claim step2] Multiple records popup — clicking top Select")
try:
sel_btn.click()
except Exception:
self.driver.execute_script("arguments[0].click();", sel_btn)
time.sleep(2)
except TimeoutException:
print("[CCA Claim step2] No multiple-records popup")
# Wait for eligibility result
print("[CCA Claim step2] Waiting for eligibility result...")
try:
@@ -449,11 +463,43 @@ class AutomationCCAClaimSubmit:
# Extract Encounter ID from the FIRST row (newest claim is at the top)
claim_number = None
# Try table cells first (Encounter ID is the first column)
for sel in [
# Pick the latest claim for this member: rows matching DOB, highest Encounter ID
try:
def _variants(d):
d = self._format_dob(d) if d else ""
if not d:
return []
m, dd, y = d.split("/")
return list({d, f"{int(m)}/{int(dd)}/{y}", f"{m}/{dd}/{y[-2:]}"})
dos_v = _variants(self.serviceDate)
dob_v = _variants(self.dateOfBirth)
rows = self.driver.execute_script("""
return Array.from(document.querySelectorAll('table tbody tr')).map(function(tr){
return Array.from(tr.querySelectorAll('td')).map(function(td){return td.innerText.trim();});
}).filter(function(c){return c.length;});
""") or []
# Rows for this member (by DOB); the largest Encounter ID is the latest claim
id_rows = [c for c in rows if re.match(r'\d{10,}', c[0])]
dob_rows = [c for c in id_rows if any(v in " | ".join(c) for v in dob_v)]
if not dob_rows:
# DOB not shown/matched — fall back to the service date
dob_rows = [c for c in id_rows if any(v in " | ".join(c) for v in dos_v)]
if dob_rows:
best = max(dob_rows, key=lambda c: int(re.match(r'\d+', c[0]).group(0)))
claim_number = re.match(r'\d+', best[0]).group(0)
print(f"[CCA Claim step4] Encounter ID {claim_number} = latest of "
f"{len(dob_rows)} row(s) for DOB {dob_v[:1]}: {best}")
else:
print(f"[CCA Claim step4] No row matched DOB={dob_v} or DOS={dos_v}; "
f"falling back to top row. Rows seen: {rows[:6]}")
except Exception as e:
print(f"[CCA Claim step4] Row matching failed: {e!r}")
# Fallback: top row (Encounter ID is the first column)
for sel in ([] if claim_number else [
(By.XPATH, "//table//tbody//tr[1]/td[1]"),
(By.XPATH, "//*[contains(@class,'wbx-table-results')]//tr[1]/td[1]"),
]:
]):
try:
cell = WebDriverWait(self.driver, 8).until(
EC.presence_of_element_located(sel))
@@ -806,27 +852,56 @@ class AutomationCCAClaimSubmit:
print(f"[CCA Claim grid] Filled {input_id}={value!r}")
return True
except Exception as e:
print(f"[CCA Claim grid] Fill {input_id} failed: {e}")
print(f"[CCA Claim grid] Fill {input_id} failed: {e!r}")
return False
def click_cell_and_fill_retry(col_expr, input_id, value, attempts=3):
"""Retry when the editor input doesn't appear after the cell click
(the grid sometimes hasn't finished committing the previous row)."""
from selenium.webdriver.common.action_chains import ActionChains
for attempt in range(1, attempts + 1):
if click_cell_and_fill(col_expr, input_id, value):
return True
print(f"[CCA Claim grid] {input_id} attempt {attempt}/{attempts} failed")
time.sleep(1.5)
if attempt >= 2:
# Escape any stray popup/editor and try a double-click on the cell
try:
ActionChains(self.driver).send_keys(Keys.ESCAPE).perform()
cell = self.driver.find_element(
By.XPATH,
f"(//div[contains(@class,'e-gridcontent')]//tr)[{row_num}]/td[{col_expr}]")
ActionChains(self.driver).double_click(cell).perform()
time.sleep(0.5)
field = self.driver.find_element(By.ID, input_id)
field.send_keys(Keys.CONTROL + "a")
field.send_keys(Keys.DELETE)
field.send_keys(str(value))
print(f"[CCA Claim grid] Filled {input_id}={value!r} via double-click")
return True
except Exception as e:
print(f"[CCA Claim grid] Double-click fallback failed: {e!r}")
return False
# CODE
code_ok = click_cell_and_fill(col_map["CODE"], "ServicesCODE", code)
code_ok = click_cell_and_fill_retry(col_map["CODE"], "ServicesCODE", code)
time.sleep(1.0) # let the grid finish its code lookup before the next cell
# TOOTH — skip if empty
tooth_ok = True
if tooth:
tooth_ok = click_cell_and_fill(col_map["TOOTH"], "ServicesTOOTH", tooth)
tooth_ok = click_cell_and_fill_retry(col_map["TOOTH"], "ServicesTOOTH", tooth)
# Surfaces — skip if empty
if surface_chars:
surf_ids = ["ServicesSURF1","ServicesSURF2","ServicesSURF3",
"ServicesSURF4","ServicesSURF5"]
for si, char in enumerate(surface_chars[:5]):
click_cell_and_fill(col_map[f"SURF{si+1}"], surf_ids[si], char)
click_cell_and_fill_retry(col_map[f"SURF{si+1}"], surf_ids[si], char)
# Billed Amount
# QTY and Service Date auto-fill after CODE is entered and this cell is clicked
billed_ok = click_cell_and_fill(col_map["BILLED_AMOUNT"], "ServicesBILLED_AMOUNT", billed_str)
billed_ok = click_cell_and_fill_retry(col_map["BILLED_AMOUNT"], "ServicesBILLED_AMOUNT", billed_str)
if not (code_ok and tooth_ok and billed_ok):
missing = [name for name, ok in
@@ -954,23 +1029,36 @@ class AutomationCCAClaimSubmit:
# ---- Handle confirmation modal popup ----
print("[CCA Claim step3] Waiting for confirmation modal...")
time.sleep(1.5)
modal_confirmed = False
for sel in [
modal_selectors = [
# Most specific: Submit/Confirm/Yes button inside an active modal
(By.XPATH, "//div[contains(@class,'modal') and contains(@class,'in')]//button[contains(text(),'Submit') or contains(text(),'Confirm') or contains(text(),'Yes')]"),
(By.XPATH, "//div[@class='modal-footer']//button[contains(text(),'Submit') or contains(text(),'Confirm') or contains(text(),'Yes')]"),
(By.XPATH, "//button[@ng-click and (contains(@ng-click,'confirm') or contains(@ng-click,'Confirm') or contains(@ng-click,'submit') or contains(@ng-click,'Submit'))]"),
(By.XPATH, "//div[contains(@class,'modal')]//button[not(contains(@class,'cancel')) and not(contains(text(),'Cancel')) and not(contains(text(),'No'))]"),
]:
try:
btn = WebDriverWait(self.driver, 5).until(EC.element_to_be_clickable(sel))
btn.click()
modal_confirmed = True
print(f"[CCA Claim step3] Clicked modal confirm via {sel}")
break
except Exception:
continue
]
# Poll for the modal's own button first (patterns 1-2). The looser patterns (3-4)
# also match the page's own "Submit Claim" button behind the popup, so they are
# only tried after the modal-specific ones have had time to appear.
start = time.monotonic()
while time.monotonic() - start < 8 and not modal_confirmed:
active = modal_selectors[:2] if time.monotonic() - start < 5 else modal_selectors
for sel in active:
try:
btn = next((b for b in self.driver.find_elements(*sel)
if b.is_displayed() and b.is_enabled()), None)
if btn:
btn.click()
modal_confirmed = True
print(f"[CCA Claim step3] Clicked modal confirm via {sel}")
break
except Exception:
continue
if not modal_confirmed:
time.sleep(0.3)
if not modal_confirmed:
time.sleep(0.3)
if not modal_confirmed:
print("[CCA Claim step3] No confirmation modal found — may have auto-submitted")
@@ -978,7 +1066,7 @@ class AutomationCCAClaimSubmit:
# Wait for final confirmation page
print("[CCA Claim step3] Waiting for submission confirmation...")
try:
WebDriverWait(self.driver, 30).until(
WebDriverWait(self.driver, 10).until(
lambda d: any(x in d.find_element(By.TAG_NAME, "body").text.lower() for x in [
"claim submitted", "claim number", "confirmation", "success",
"has been submitted", "claim id", "assigned the number",
@@ -291,15 +291,15 @@ class AutomationMassHealthClaimsLogin:
(By.XPATH, "//input[@name='dateInput' and @placeholder='mm/dd/yyyy' and @ng-model='date']")
)
)
time.sleep(1)
time.sleep(0.3)
# Use JavaScript to set the value directly to avoid date picker interference
self.driver.execute_script(f"arguments[0].value = '{formatted_dob}'; arguments[0].dispatchEvent(new Event('input', {{ bubbles: true }})); arguments[0].dispatchEvent(new Event('change', {{ bubbles: true }}));", dob_input)
time.sleep(1)
time.sleep(0.3)
# Press Tab to move to next field and ensure the date is set
dob_input.send_keys(Keys.TAB)
time.sleep(1)
time.sleep(0.3)
# Step 2: Fill Member Number
if self.memberId:
@@ -309,15 +309,15 @@ class AutomationMassHealthClaimsLogin:
(By.XPATH, "//input[@placeholder='Member Number' and @ng-model='vm.number']")
)
)
time.sleep(1)
time.sleep(0.3)
member_input.click()
time.sleep(1)
time.sleep(0.3)
member_input.clear()
time.sleep(1)
time.sleep(0.3)
member_input.send_keys(self.memberId)
time.sleep(1)
time.sleep(0.3)
member_input.send_keys(Keys.TAB)
time.sleep(1)
time.sleep(0.3)
# Step 3: Fill First Name and Last Name
# if self.firstName:
@@ -383,12 +383,12 @@ class AutomationMassHealthClaimsLogin:
(By.XPATH, "//input[@ng-model='serviceLine.procedure' and @placeholder='Code']")
)
)
time.sleep(1)
time.sleep(0.3)
# Step 2: Type the procedure code to trigger typeahead
procedure_input.clear()
procedure_input.send_keys(self.procedureCode)
time.sleep(3) # Wait for typeahead to load
time.sleep(0.5) # brief debounce; the dropdown wait below does the real waiting
# Step 3: Look for the typeahead dropdown and select the first option
# The dropdown appears as ul with class 'dropdown-menu'
@@ -405,13 +405,13 @@ class AutomationMassHealthClaimsLogin:
if first_option:
print("DEBUG: Found first option in procedure dropdown, clicking...")
first_option.click()
time.sleep(2)
time.sleep(0.5)
return "Success"
except:
# If no dropdown appears, just press Tab to move to next field
print("DEBUG: No procedure dropdown found, pressing Tab")
procedure_input.send_keys(Keys.TAB)
time.sleep(1)
time.sleep(0.3)
return "Success"
return "Success"
@@ -433,16 +433,16 @@ class AutomationMassHealthClaimsLogin:
(By.XPATH, "//input[@ng-model='serviceLine.toothCode' and @placeholder='Tooth']")
)
)
time.sleep(1)
time.sleep(0.3)
# Step 2: Type the tooth number
tooth_input.clear()
tooth_input.send_keys(tooth_value.upper()) # Convert to uppercase as per onkeyup
time.sleep(2)
time.sleep(0.5)
# Press Tab to move to next field
tooth_input.send_keys(Keys.TAB)
time.sleep(1)
time.sleep(0.3)
return "Success"
except Exception as e:
@@ -463,16 +463,16 @@ class AutomationMassHealthClaimsLogin:
(By.XPATH, "//input[@ng-model='serviceLine.surface' and @placeholder='Surface']")
)
)
time.sleep(1)
time.sleep(0.3)
# Step 2: Type the tooth surface (convert to uppercase)
surface_input.clear()
surface_input.send_keys(surface_value.upper())
time.sleep(2)
time.sleep(0.5)
# Press Tab to move to next field
surface_input.send_keys(Keys.TAB)
time.sleep(1)
time.sleep(0.3)
return "Success"
except Exception as e:
@@ -491,12 +491,12 @@ class AutomationMassHealthClaimsLogin:
(By.XPATH, "//select[@ng-model='serviceLine.quadrant']")
)
)
time.sleep(1)
time.sleep(0.3)
# Step 2: Use Select class to choose Upper Right
select = Select(quadrant_dropdown)
select.select_by_visible_text("Upper Right")
time.sleep(2)
time.sleep(0.5)
return "Success"
except Exception as e:
@@ -515,12 +515,12 @@ class AutomationMassHealthClaimsLogin:
(By.XPATH, "//select[@ng-model='serviceLine.arch']")
)
)
time.sleep(1)
time.sleep(0.3)
# Step 2: Use Select class to choose Entire Oral Cavity
select = Select(arch_dropdown)
select.select_by_visible_text("Entire Oral Cavity")
time.sleep(2)
time.sleep(0.5)
return "Success"
except Exception as e:
@@ -541,16 +541,16 @@ class AutomationMassHealthClaimsLogin:
(By.XPATH, "//input[@ng-model='serviceLine.authorizationNumber' and @placeholder='No.']")
)
)
time.sleep(1)
time.sleep(0.3)
# Step 2: Type the authorization number
auth_input.clear()
auth_input.send_keys(auth_number)
time.sleep(2)
time.sleep(0.5)
# Press Tab to move to next field
auth_input.send_keys(Keys.TAB)
time.sleep(1)
time.sleep(0.3)
return "Success"
except Exception as e: