Files
DentalManagement09/apps/SeleniumService/selenium_DeltaIns_claimSubmitWorker.py
T
GiteadandClaude Opus 5.5 aa5d3bf2d5 fix: Deltains claim tooth surface multi-select
Open the toothSurfaces MUI listbox (retrying if it doesn't appear), check
each surface letter by option text via JS and verify it ends up selected,
then close the popover with Escape or a backdrop click so its overlay no
longer blocks the fee field. Waits for the tooth-number listbox to close
first so its options aren't misread.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 23:13:36 -04:00

890 lines
42 KiB
Python

from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
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 deltains_browser_manager import get_browser_manager
LOGIN_URL = "https://www.deltadentalins.com/ciam/login?TARGET=%2Fprovider-tools%2Fv2"
PROVIDER_TOOLS_URL = "https://www.deltadentalins.com/provider-tools/v2"
# Absolute path to the Backend app root (two levels up from this file's SeleniumService dir)
_SERVICE_DIR = os.path.dirname(os.path.abspath(__file__))
_BACKEND_CWD = os.path.normpath(os.path.join(_SERVICE_DIR, "..", "Backend"))
class AutomationDeltaInsClaimSubmit:
def __init__(self, data):
self.headless = False
self.driver = None
raw = data if isinstance(data, dict) else {}
claim = raw.get("claim", {}) or {}
patient_name = (claim.get("patientName") or "").strip()
parts = patient_name.split()
first_name = parts[0] if parts else ""
last_name = " ".join(parts[1:]) if len(parts) > 1 else ""
self.deltains_username = claim.get("deltainsUsername", "") or raw.get("deltainsUsername", "")
self.deltains_password = claim.get("deltainsPassword", "") or raw.get("deltainsPassword", "")
self.memberId = claim.get("memberId", "")
self.dateOfBirth = claim.get("dateOfBirth", "")
self.serviceDate = claim.get("serviceDate", "")
self.firstName = claim.get("firstName", "") or first_name
self.lastName = claim.get("lastName", "") or last_name
self.serviceLines = claim.get("serviceLines", []) or []
self.claimFiles = claim.get("claimFiles", []) or []
self.providerName = claim.get("providerName", "") or raw.get("providerName", "")
self.download_dir = get_browser_manager().download_dir
os.makedirs(self.download_dir, exist_ok=True)
print(f"[Deltains Claim] Init — member={self.memberId}, "
f"patient={self.firstName} {self.lastName}, "
f"lines={len(self.serviceLines)}")
def config_driver(self):
self.driver = get_browser_manager().get_driver(self.headless)
# ------------------------------------------------------------------ #
# Login — copied from AutomationDeltaInsEligibilityCheck.login() #
# (Okta-based, already proven working for the eligibility flow) #
# ------------------------------------------------------------------ #
def _dismiss_cookie_banner(self):
try:
accept_btn = WebDriverWait(self.driver, 5).until(
EC.element_to_be_clickable((By.ID, "onetrust-accept-btn-handler"))
)
accept_btn.click()
print("[Deltains Claim login] Dismissed cookie consent banner")
time.sleep(1)
except TimeoutException:
print("[Deltains Claim login] No cookie consent banner found")
except Exception as e:
print(f"[Deltains Claim login] Error dismissing cookie banner: {e}")
def _force_logout(self):
try:
print("[Deltains Claim login] Forcing logout due to credential change...")
browser_manager = get_browser_manager()
try:
self.driver.delete_all_cookies()
print("[Deltains Claim login] Cleared all cookies")
except Exception as e:
print(f"[Deltains Claim login] Error clearing cookies: {e}")
browser_manager.clear_credentials_hash()
print("[Deltains Claim login] Logout complete")
return True
except Exception as e:
print(f"[Deltains Claim login] Error during forced logout: {e}")
return False
def login(self, url):
"""
Multi-step login flow for DeltaIns (Okta-based) — identical to the
eligibility worker's login():
1. Enter username (name='identifier') -> click Next
2. Enter password (type='password') -> click Submit
3. Handle MFA: click 'Send me an email' -> wait for OTP
Returns: ALREADY_LOGGED_IN, SUCCESS, OTP_REQUIRED, or ERROR:...
"""
wait = WebDriverWait(self.driver, 30)
browser_manager = get_browser_manager()
try:
if self.deltains_username and browser_manager.credentials_changed(self.deltains_username):
self._force_logout()
self.driver.get(url)
time.sleep(3)
try:
current_url = self.driver.current_url
print(f"[Deltains Claim login] Current URL: {current_url}")
if "provider-tools" in current_url and "login" not in current_url.lower() and "ciam" not in current_url.lower():
print("[Deltains Claim login] Already on provider tools page - logged in")
return "ALREADY_LOGGED_IN"
except Exception as e:
print(f"[Deltains Claim login] Error checking current state: {e}")
print("[Deltains Claim login] Trying provider-tools URL to check session...")
self.driver.get(PROVIDER_TOOLS_URL)
time.sleep(5)
current_url = self.driver.current_url
print(f"[Deltains Claim login] After provider-tools nav URL: {current_url}")
if "provider-tools" in current_url and "login" not in current_url.lower() and "ciam" not in current_url.lower():
print("[Deltains Claim login] Session still valid - already logged in")
return "ALREADY_LOGGED_IN"
print("[Deltains Claim login] Session not valid, navigating to login page...")
self.driver.get(url)
time.sleep(3)
current_url = self.driver.current_url
print(f"[Deltains Claim login] After login nav URL: {current_url}")
if "provider-tools" in current_url and "login" not in current_url.lower() and "ciam" not in current_url.lower():
print("[Deltains Claim login] Already logged in - on provider tools")
return "ALREADY_LOGGED_IN"
self._dismiss_cookie_banner()
print("[Deltains Claim login] Looking for username field...")
username_entered = False
for sel in [
(By.NAME, "identifier"),
(By.ID, "okta-signin-username"),
(By.XPATH, "//input[@type='text' and @autocomplete='username']"),
(By.XPATH, "//input[@type='text']"),
]:
try:
field = WebDriverWait(self.driver, 8).until(EC.presence_of_element_located(sel))
if field.is_displayed():
field.clear()
field.send_keys(self.deltains_username)
username_entered = True
print(f"[Deltains Claim login] Username entered via {sel}")
break
except Exception:
continue
if not username_entered:
return "ERROR: Could not find username field"
time.sleep(1)
for sel in [
(By.XPATH, "//input[@type='submit' and @value='Next']"),
(By.XPATH, "//input[@type='submit']"),
(By.XPATH, "//button[@type='submit']"),
]:
try:
btn = self.driver.find_element(*sel)
if btn.is_displayed():
btn.click()
print(f"[Deltains Claim login] Clicked Next via {sel}")
break
except Exception:
continue
time.sleep(4)
current_url = self.driver.current_url
if "provider-tools" in current_url and "login" not in current_url.lower() and "ciam" not in current_url.lower():
return "ALREADY_LOGGED_IN"
print("[Deltains Claim login] Looking for password field...")
pw_entered = False
for sel in [
(By.XPATH, "//input[@type='password']"),
(By.ID, "okta-signin-password"),
(By.NAME, "password"),
]:
try:
field = WebDriverWait(self.driver, 10).until(EC.presence_of_element_located(sel))
if field.is_displayed():
field.clear()
field.send_keys(self.deltains_password)
pw_entered = True
print(f"[Deltains Claim login] Password entered via {sel}")
break
except Exception:
continue
if not pw_entered:
current_url = self.driver.current_url
if "provider-tools" in current_url and "login" not in current_url.lower():
return "ALREADY_LOGGED_IN"
return "ERROR: Password field not found"
time.sleep(1)
for sel in [
(By.ID, "okta-signin-submit"),
(By.XPATH, "//input[@type='submit']"),
(By.XPATH, "//button[@type='submit']"),
]:
try:
btn = self.driver.find_element(*sel)
if btn.is_displayed():
btn.click()
print(f"[Deltains Claim login] Clicked Sign In via {sel}")
break
except Exception:
continue
if self.deltains_username:
browser_manager.save_credentials_hash(self.deltains_username)
time.sleep(6)
current_url = self.driver.current_url
print(f"[Deltains Claim login] After password submit URL: {current_url}")
if "provider-tools" in current_url and "login" not in current_url.lower() and "ciam" not in current_url.lower():
print("[Deltains Claim login] Login successful - on provider tools")
return "SUCCESS"
print("[Deltains Claim login] Handling MFA...")
try:
body_text = self.driver.find_element(By.TAG_NAME, "body").text
if "security method" in body_text.lower() or "select from the following" in body_text.lower():
print("[Deltains Claim login] MFA method selection page detected")
email_select = None
for sel in [
(By.CSS_SELECTOR, "div[data-se='okta_email'] a.select-factor"),
(By.XPATH, "//div[@data-se='okta_email']//a[contains(@class,'select-factor')]"),
(By.XPATH, "//a[contains(@aria-label,'Select Email')]"),
(By.XPATH, "//div[@data-se='okta_email']//a[@data-se='button']"),
(By.CSS_SELECTOR, "a.select-factor.link-button"),
]:
try:
btn = self.driver.find_element(*sel)
if btn.is_displayed():
email_select = btn
print(f"[Deltains Claim login] Found Email Select via {sel}")
break
except Exception:
continue
if email_select:
email_select.click()
print("[Deltains Claim login] Clicked 'Select' for Email MFA")
time.sleep(5)
else:
print("[Deltains Claim login] Could not find Email Select button")
except Exception as e:
print(f"[Deltains Claim login] Error checking MFA method selection: {e}")
try:
send_btn = WebDriverWait(self.driver, 8).until(
EC.element_to_be_clickable((By.XPATH,
"//input[@type='submit' and @value='Send me an email'] | "
"//input[@value='Send me an email'] | "
"//button[contains(text(),'Send me an email')]"))
)
send_btn.click()
print("[Deltains Claim login] Clicked 'Send me an email'")
time.sleep(5)
except TimeoutException:
print("[Deltains Claim login] No 'Send me an email' button, checking for OTP input...")
try:
otp_input = WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.XPATH,
"//input[@name='credentials.passcode' and @type='text'] | "
"//input[contains(@name,'passcode')]"))
)
print("[Deltains Claim login] OTP input found -> OTP_REQUIRED")
return "OTP_REQUIRED"
except TimeoutException:
pass
current_url = self.driver.current_url
if "provider-tools" in current_url and "login" not in current_url.lower() and "ciam" not in current_url.lower():
return "SUCCESS"
try:
error_elem = self.driver.find_element(By.XPATH,
"//*[contains(@class,'error') or contains(@class,'alert-error')]")
error_text = error_elem.text.strip()[:200]
if error_text:
return f"ERROR: {error_text}"
except Exception:
pass
print("[Deltains Claim login] Could not determine login state - returning OTP_REQUIRED as fallback")
return "OTP_REQUIRED"
except Exception as e:
print(f"[Deltains Claim login] Exception: {e}")
return f"ERROR:LOGIN FAILED: {e}"
def _format_dob(self, dob_str):
if dob_str and "-" in dob_str:
dob_parts = dob_str.split("-")
if len(dob_parts) == 3:
return f"{dob_parts[1]}/{dob_parts[2]}/{dob_parts[0]}"
return dob_str
# ------------------------------------------------------------------ #
# Step 1 — Search patient (same search form as the eligibility flow) #
# ------------------------------------------------------------------ #
def step1_search_patient(self):
"""
Click the 'Submit claim' dashboard tile, which opens a 'Patient search'
modal, and search the patient roster by full name.
"""
try:
print(f"[Deltains Claim step1] Starting — memberId={self.memberId}, "
f"patient={self.firstName} {self.lastName}")
print("[Deltains Claim step1] Clicking 'Submit claim' dashboard tile...")
try:
submit_claim_tile = WebDriverWait(self.driver, 15).until(
EC.element_to_be_clickable((By.XPATH,
"//button[@data-testid='moduleTile'][.//a[contains(text(),'Submit claim')]] | "
"//a[@data-testid='moduleTileLabelLink'][contains(text(),'Submit claim')] | "
"//a[contains(text(),'Submit claim')]"))
)
submit_claim_tile.click()
time.sleep(5)
print("[Deltains Claim step1] Clicked 'Submit claim' tile")
except TimeoutException:
return "ERROR: 'Submit claim' dashboard tile not found"
print("[Deltains Claim step1] Waiting for 'Patient search' modal...")
try:
WebDriverWait(self.driver, 15).until(
EC.presence_of_element_located((By.XPATH,
"//h1[contains(text(),'Patient search')] | "
"//h2[contains(text(),'Patient search')]"))
)
print("[Deltains Claim step1] 'Patient search' modal loaded")
except TimeoutException:
print("[Deltains Claim step1] 'Patient search' modal heading not found, continuing anyway...")
full_name = f"{self.firstName} {self.lastName}".strip()
print(f"[Deltains Claim step1] Searching patient roster by name: '{full_name}'...")
try:
name_field = WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable((By.ID, "patientRosterAutocomplete"))
)
name_field.click()
name_field.send_keys(Keys.CONTROL + "a")
name_field.send_keys(Keys.DELETE)
time.sleep(0.3)
name_field.send_keys(full_name)
time.sleep(1.5)
print(f"[Deltains Claim step1] Name entered: '{name_field.get_attribute('value')}'")
except TimeoutException:
return "ERROR: Patient roster search field not found"
print("[Deltains Claim step1] Clicking search icon button...")
try:
search_btn = WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable((By.XPATH,
"//button[.//*[contains(@class,'dd-icon-Search')]] | "
"//button[@data-testid='autocompleteSearchButton']"))
)
search_btn.click()
time.sleep(5)
print("[Deltains Claim step1] Clicked search icon button")
except TimeoutException:
return "ERROR: Search icon button not found"
print("[Deltains Claim step1] Checking for results...")
try:
patient_card = WebDriverWait(self.driver, 15).until(
EC.presence_of_element_located((By.XPATH,
"//div[contains(@class,'patient-card-root')] | "
"//div[@data-testid='patientCard'] | "
"//div[starts-with(@data-testid,'patientCard')]"))
)
print("[Deltains Claim step1] Patient card found!")
self._patient_card = patient_card
except TimeoutException:
try:
body_text = self.driver.find_element(By.TAG_NAME, "body").text
if "no results" in body_text.lower() or "not found" in body_text.lower() or "no patient" in body_text.lower():
return "ERROR: No patient found with the provided name"
alerts = self.driver.find_elements(By.XPATH, "//*[@role='alert']")
for alert in alerts:
if alert.is_displayed():
return f"ERROR: {alert.text.strip()[:200]}"
except Exception:
pass
return "ERROR: No patient results found within timeout"
print("[Deltains Claim step1] Search completed")
return "SUCCESS"
except Exception as e:
print(f"[Deltains Claim step1] Exception: {e}")
return f"ERROR: step1 failed: {e}"
# ------------------------------------------------------------------ #
# Step 2 — Select transaction type on the "Submit a claim" page #
# (search lands directly on this page — no separate member-page or #
# "Create claim" click is needed, unlike the DDMA flow) #
# ------------------------------------------------------------------ #
def step2_select_transaction_type(self):
"""
Confirm the "Submit a claim" page loaded, then click the
"Statement of completed services" radio button under
"Type of transaction".
"""
try:
print(f"[Deltains Claim step2] Current URL: {self.driver.current_url}")
try:
WebDriverWait(self.driver, 20).until(
EC.presence_of_element_located((By.XPATH,
"//h1[contains(text(),'Submit a claim')] | "
"//*[contains(text(),'Type of transaction')]"))
)
print("[Deltains Claim step2] 'Submit a claim' page loaded")
except TimeoutException:
page_text = self.driver.execute_script("return document.body.innerText;")[:400]
print(f"[Deltains Claim step2] Claim form not detected — page: {page_text}")
return "ERROR: step2 failed: 'Submit a claim' page did not load"
print("[Deltains Claim step2] Clicking 'Statement of completed services' radio...")
try:
radio = WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.XPATH,
"//input[@data-testid='transactionRB_Claim']"))
)
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", radio)
self.driver.execute_script("arguments[0].click();", radio)
time.sleep(1)
print("[Deltains Claim step2] Selected 'Statement of completed services'")
except TimeoutException:
return "ERROR: step2 failed: 'Statement of completed services' radio not found"
return "SUCCESS"
except Exception as e:
print(f"[Deltains Claim step2] Exception: {e}")
return f"ERROR: step2 failed: {e}"
# ------------------------------------------------------------------ #
# Step 4 — Fill service date and procedure lines #
# ------------------------------------------------------------------ #
def _fill_combobox(self, inp, value, label="field"):
"""Type value into a combobox and select the first matching dropdown option."""
try:
inp.click()
inp.send_keys(Keys.CONTROL + "a")
inp.send_keys(Keys.DELETE)
inp.send_keys(str(value))
time.sleep(0.5)
listbox_id = inp.get_attribute("aria-controls") or ""
try:
if listbox_id:
option = WebDriverWait(self.driver, 4).until(
EC.element_to_be_clickable((By.XPATH,
f"//*[@id='{listbox_id}']//*[@role='option'][1]"
))
)
else:
option = WebDriverWait(self.driver, 4).until(
EC.element_to_be_clickable((By.XPATH,
f"//*[@role='listbox']//*[@role='option' and contains(normalize-space(.),'{value}')]"
f" | //*[@role='listbox']//*[@role='option'][1]"
))
)
option.click()
print(f"[Deltains Claim step4] {label}: selected '{value}'")
except TimeoutException:
inp.send_keys(Keys.TAB)
print(f"[Deltains Claim step4] {label}: typed '{value}' (no dropdown)")
except Exception as e:
print(f"[Deltains Claim step4] Warning: could not fill {label}: {e}")
def _fill_by_id(self, element_id, value, label="field"):
"""Fill a plain text/tel input located by its exact id attribute."""
try:
inp = WebDriverWait(self.driver, 8).until(
EC.element_to_be_clickable((By.ID, element_id))
)
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", inp)
inp.click()
inp.send_keys(Keys.CONTROL + "a")
inp.send_keys(Keys.DELETE)
inp.send_keys(str(value))
time.sleep(0.2)
print(f"[Deltains Claim step4] {label}: typed '{value}'")
return inp
except Exception as e:
print(f"[Deltains Claim step4] Warning: could not fill {label} (#{element_id}): {e}")
return None
_SURFACE_OPTIONS_JS = """
const box = document.querySelector('[role="listbox"]');
if (!box) return null;
return Array.from(box.querySelectorAll('[role="option"], li')).map(o => {
const cb = o.querySelector('input[type="checkbox"]');
return {
text: (o.textContent || '').trim().toUpperCase(),
selected: o.getAttribute('aria-selected') === 'true' || !!(cb && cb.checked),
};
});
"""
_SURFACE_CLICK_JS = """
const letter = arguments[0];
const box = document.querySelector('[role="listbox"]');
if (!box) return false;
const opt = Array.from(box.querySelectorAll('[role="option"], li'))
.find(o => (o.textContent || '').trim().toUpperCase() === letter);
if (!opt) return false;
opt.scrollIntoView({block: 'nearest'});
opt.click();
return true;
"""
def _select_tooth_surfaces(self, surface_select, letters, idx):
"""Open the toothSurfaces multi-select and check each letter, verifying
the option ends up selected (aria-selected / checkbox checked)."""
# Make sure the tooth-number listbox is gone so we don't read its options
self._close_mui_popover()
options = None
for _ in range(3):
surface_select.click()
try:
options = WebDriverWait(self.driver, 4).until(
lambda d: d.execute_script(self._SURFACE_OPTIONS_JS) or False)
break
except TimeoutException:
print(f"[Deltains Claim step4] surface[{idx}]: listbox did not open, retrying")
if not options:
print(f"[Deltains Claim step4] surface[{idx}]: could not open surface listbox")
return
print(f"[Deltains Claim step4] surface[{idx}]: options={[o['text'] for o in options]}")
for letter in letters:
state = {o["text"]: o["selected"] for o in (self.driver.execute_script(self._SURFACE_OPTIONS_JS) or [])}
if letter not in state:
print(f"[Deltains Claim step4] surface[{idx}]: option '{letter}' not found")
continue
if state[letter]:
print(f"[Deltains Claim step4] surface[{idx}]: '{letter}' already checked")
continue
self.driver.execute_script(self._SURFACE_CLICK_JS, letter)
time.sleep(0.3)
state = {o["text"]: o["selected"] for o in (self.driver.execute_script(self._SURFACE_OPTIONS_JS) or [])}
print(f"[Deltains Claim step4] surface[{idx}]: '{letter}' checked={state.get(letter)}")
shown = (surface_select.text or "").strip()
print(f"[Deltains Claim step4] surface[{idx}]: field now shows {shown!r}")
def _close_mui_popover(self):
"""Close any open MUI Select popover so its backdrop stops covering
the rest of the form."""
for attempt in range(3):
if not self.driver.find_elements(By.CSS_SELECTOR, '[role="listbox"]'):
return
try:
if attempt == 0:
self.driver.switch_to.active_element.send_keys(Keys.ESCAPE)
else:
self.driver.execute_script("""
const bd = document.querySelector('.MuiPopover-root .MuiBackdrop-root, .MuiMenu-root .MuiBackdrop-root');
if (bd) bd.click();
""")
except Exception as e:
print(f"[Deltains Claim step4] close popover attempt {attempt} failed: {e}")
time.sleep(0.4)
def step4_fill_claim_form(self):
"""
Fill each service line: date of service, procedure code, and fee
via their per-line ids (serviceLines.{idx}.dateOfService,
serviceLines.{idx}.procedure, serviceLines.{idx}.fee — confirmed
against the live Deltains claim page). Tooth/arch/quad/surface
selectors below remain unverified.
"""
try:
active_lines = [ln for ln in self.serviceLines if str(ln.get("procedureCode") or "").strip()]
print(f"[Deltains Claim step4] {len(active_lines)} service line(s)")
for idx, line in enumerate(active_lines):
code = str(line.get("procedureCode") or "").strip().upper()
tooth = str(line.get("toothNumber") or line.get("tooth") or "").strip()
arch = str(line.get("arch") or "").strip()
quad = str(line.get("quad") or line.get("quadrant") or "").strip()
surface = str(line.get("toothSurface") or line.get("surface") or "").strip().upper()
billed = str(line.get("totalBilled") or line.get("billedAmount") or line.get("fee") or "").strip()
billed = billed.replace("$", "").strip()
print(f"[Deltains Claim step4] Line {idx}: code={code} tooth={tooth!r} arch={arch!r} "
f"quad={quad!r} surface={surface!r} billed={billed!r}")
if idx > 0:
try:
add_btn = WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable((By.XPATH,
"//button[contains(normalize-space(text()),'Add service')]"
))
)
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", add_btn)
add_btn.click()
print(f"[Deltains Claim step4] Clicked 'Add service' for line {idx}")
time.sleep(1)
except Exception as e:
print(f"[Deltains Claim step4] Could not click 'Add service': {e}")
formatted_dos = self._format_dob(self.serviceDate) if self.serviceDate else ""
if formatted_dos:
self._fill_by_id(f"serviceLines.{idx}.dateOfService", formatted_dos, f"dateOfService[{idx}]")
if code:
try:
proc_inp = WebDriverWait(self.driver, 8).until(
EC.element_to_be_clickable((By.ID, f"serviceLines.{idx}.procedure"))
)
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", proc_inp)
self._fill_combobox(proc_inp, code, f"procedureCode[{idx}]")
time.sleep(0.5)
except Exception as e:
print(f"[Deltains Claim step4] Could not fill procedure code: {e}")
# Tooth number — MUI single-select (only rendered for codes
# that require it, e.g. fillings like D2392)
if tooth:
try:
tooth_select = WebDriverWait(self.driver, 8).until(
EC.element_to_be_clickable((By.ID, f"mui-component-select-serviceLines.{idx}.toothNumber"))
)
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", tooth_select)
tooth_select.click()
time.sleep(0.5)
padded = tooth.zfill(2)
option = WebDriverWait(self.driver, 5).until(
EC.element_to_be_clickable((By.XPATH,
f"//li[@role='option'][normalize-space(.)='{padded}'] | "
f"//li[@role='option'][normalize-space(.)='{tooth}']"))
)
option.click()
time.sleep(0.3)
print(f"[Deltains Claim step4] tooth[{idx}]: selected '{tooth}'")
except TimeoutException:
print(f"[Deltains Claim step4] tooth[{idx}]: select not present for this procedure code")
except Exception as e:
print(f"[Deltains Claim step4] Could not fill tooth number: {e}")
# Tooth surfaces — MUI multi-select; each letter (B, D, F, I, L,
# M, O) is a checkbox option in a popover listbox that stays
# open until Escape / a backdrop click.
if surface:
try:
surface_select = WebDriverWait(self.driver, 8).until(
EC.element_to_be_clickable((By.ID, f"serviceLines.{idx}.toothSurfaces"))
)
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", surface_select)
surface_letters = []
for c in surface:
if c.isalpha() and c not in surface_letters:
surface_letters.append(c)
self._select_tooth_surfaces(surface_select, surface_letters, idx)
except TimeoutException:
print(f"[Deltains Claim step4] surface[{idx}]: select not present for this procedure code")
except Exception as e:
print(f"[Deltains Claim step4] Could not fill tooth surfaces: {e}")
finally:
self._close_mui_popover()
if billed:
self._fill_by_id(f"serviceLines.{idx}.fee", billed, f"fee[{idx}]")
time.sleep(0.2)
print("[Deltains Claim step4] Done")
return "SUCCESS"
except Exception as e:
print(f"[Deltains Claim step4] Exception: {e}")
return f"ERROR: step4 failed: {e}"
# ------------------------------------------------------------------ #
# Step 5 — Attach files #
# ------------------------------------------------------------------ #
def step5_attach_files(self):
"""
Expand the 'Attachments' accordion section under 'Additional
information' (max 5 files, PDF/JPEG only), then for each claimFile
with a filePath, send it directly to the hidden file input behind
the 'Choose a file' label.
"""
if not self.claimFiles:
print("[Deltains Claim step5] No files to attach")
return "SUCCESS"
try:
section = WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable((By.XPATH,
"//*[contains(text(),'Attachments') and contains(text(),'x-rays')]"))
)
if section.get_attribute("aria-expanded") == "false":
section.click()
time.sleep(1)
print("[Deltains Claim step5] Expanded 'Attachments' section")
except TimeoutException:
print("[Deltains Claim step5] 'Attachments' section header not found, "
"assuming already expanded")
attached = 0
for cf in self.claimFiles:
relative_path = cf.get("filePath") or ""
if not relative_path:
print(f"[Deltains Claim step5] Skipping file with no filePath: {cf}")
continue
abs_path = os.path.normpath(os.path.join(_BACKEND_CWD, relative_path.lstrip("/")))
if not os.path.isfile(abs_path):
print(f"[Deltains Claim step5] File not found on disk: {abs_path}")
continue
print(f"[Deltains Claim step5] Attaching: {abs_path}")
try:
file_input = WebDriverWait(self.driver, 8).until(
EC.presence_of_element_located((By.XPATH,
"//label[contains(@class,'dd-file-uploader-btn')]//input[@type='file'] | "
"//input[@type='file']"))
)
self.driver.execute_script(
"arguments[0].style.display='block'; arguments[0].style.visibility='visible';",
file_input)
file_input.send_keys(abs_path)
time.sleep(1.5)
print(f"[Deltains Claim step5] Attached: {os.path.basename(abs_path)}")
attached += 1
except Exception as e:
print(f"[Deltains Claim step5] Could not attach {abs_path}: {e}")
print(f"[Deltains Claim step5] Attached {attached}/{len(self.claimFiles)} file(s)")
return "SUCCESS"
# ------------------------------------------------------------------ #
# Step 6 — Click Review #
# ------------------------------------------------------------------ #
def step6_click_next(self):
"""Click the 'Review' button (data-testid='ctaReviewButton') to
advance from the claim-entry form to the review screen."""
try:
print(f"[Deltains Claim step6] Current URL: {self.driver.current_url}")
btn = WebDriverWait(self.driver, 15).until(
EC.element_to_be_clickable((By.XPATH,
"//button[@data-testid='ctaReviewButton']"
))
)
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("[Deltains Claim step6] Clicked 'Review'")
time.sleep(2)
print(f"[Deltains Claim step6] URL after Review: {self.driver.current_url}")
return "SUCCESS"
except Exception as e:
print(f"[Deltains Claim step6] Exception: {e}")
return f"ERROR: step6 failed: {e}"
# ------------------------------------------------------------------ #
# Step 7 — Claims summary: check acknowledgement + submit #
# ------------------------------------------------------------------ #
def step7_submit_claim(self):
"""Click the 'Submit' button (data-testid='ctaSubmitButton') on the
review page to finalize the claim."""
try:
print(f"[Deltains Claim step7] Current URL: {self.driver.current_url}")
submit_btn = WebDriverWait(self.driver, 15).until(
EC.element_to_be_clickable((By.XPATH,
"//button[@data-testid='ctaSubmitButton']"
))
)
self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", submit_btn)
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}));
""", submit_btn)
print("[Deltains Claim step7] Clicked 'Submit'")
time.sleep(2)
print(f"[Deltains Claim step7] URL after submit: {self.driver.current_url}")
return "SUCCESS"
except Exception as e:
print(f"[Deltains Claim step7] Exception: {e}")
return f"ERROR: step7 failed: {e}"
def step8_save_confirmation_pdf(self):
"""
Wait for the 'Claim submitted successfully' confirmation modal,
extract the claim identification number, save the page as PDF
via CDP.
"""
try:
WebDriverWait(self.driver, 30).until(
lambda d: "claim submitted successfully" in d.page_source.lower()
)
time.sleep(2)
print(f"[Deltains Claim step8] 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 identification number\s*:\s*(\d+)', body_text, re.IGNORECASE)
if match:
claim_number = match.group(1)
print(f"[Deltains Claim step8] 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"[Deltains Claim step8] Extracted claim number (fallback): {claim_number}")
except Exception as e:
print(f"[Deltains Claim step8] 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"deltains_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"[Deltains Claim step8] PDF saved: {pdf_path}")
except Exception as e:
print(f"[Deltains Claim step8] PDF capture failed: {e}")
return f"ERROR: step8 PDF failed: {e}"
print("[Deltains Claim step8] Clicking 'My claims'...")
try:
my_claims_btn = WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable((By.XPATH,
"//button[contains(normalize-space(text()),'My claims')]"))
)
my_claims_btn.click()
time.sleep(3)
print(f"[Deltains Claim step8] Navigated to My claims: {self.driver.current_url}")
except TimeoutException:
print("[Deltains Claim step8] 'My claims' button not found — leaving confirmation modal open")
return {
"status": "success",
"pdf_path": pdf_path,
"claimNumber": claim_number,
}
except Exception as e:
print(f"[Deltains Claim step8] Exception: {e}")
return f"ERROR: step8 failed: {e}"