""" United Ucard (OneHealthcareID) eligibility check worker. Scaffolded from selenium_UnitedSCO_eligibilityCheckWorker.py — same shape (config_driver / login / step1 / step2 / main_workflow) so it plugs into the same helpers_*_eligibility.py + agent.py session/OTP pattern. Login flow (confirmed against the live site, 2026-09-13): 0. https://www.uhcdental.com/content/provider/dental.html?state=abc (public site) span.header-signin-btn ("Sign In") -> a.sign-in-button-link ("Provider Login") which OIDC-redirects to identity.onehealthcareid.com (or straight back to the dashboard if there's already a valid SSO session) 1. https://identity.onehealthcareid.com/oneapp/index.html#/login input#username -> button#btnLogin ("Continue") 2. #/signin/password input#login-pwd -> button#btnLogin ("Continue") 3. #/rba/options ("Verify Your Identity") button#textMsg ("Via Text Message") 4. #/rba/options/text ("Access Code") input#otpBox -> button#continuebtn ("Continue") 5. Lands on the UHC Dental provider dashboard: https://secure.uhcdental.com/.../postloginhomescreen.html?... step1()/step2() (the actual eligibility search + PDF capture, once logged in) are left as TODO stubs — fill these in once we know the exact DOM for the member search + eligibility result pages on the dashboard. """ from selenium.common.exceptions import WebDriverException, TimeoutException from selenium.webdriver.common.by import By 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 class AutomationUnitedUcardEligibilityCheck: def __init__(self, data): self.headless = False self.driver = None self.data = data.get("data", {}) if isinstance(data, dict) else {} # Flatten values for convenience self.memberId = self.data.get("memberId", "") self.dateOfBirth = self.data.get("dateOfBirth", "") self.firstName = self.data.get("firstName", "") self.lastName = self.data.get("lastName", "") self.uniteducard_username = self.data.get("uniteducardUsername", "") self.uniteducard_password = self.data.get("uniteducardPassword", "") self.payment_group_id = self.data.get("paymentGroupId", "") # Use browser manager's download dir self.download_dir = get_browser_manager().download_dir os.makedirs(self.download_dir, exist_ok=True) def config_driver(self): # Use persistent browser from manager (keeps device trust tokens) self.driver = get_browser_manager().get_driver(self.headless) def _force_logout(self): """Force logout by clearing cookies for the One Healthcare ID domain.""" try: print("[UnitedUcard login] Forcing logout due to credential change...") browser_manager = get_browser_manager() try: self.driver.delete_all_cookies() print("[UnitedUcard login] Cleared all cookies") except Exception as e: print(f"[UnitedUcard login] Error clearing cookies: {e}") browser_manager.clear_credentials_hash() print("[UnitedUcard login] Logout complete") return True except Exception as e: print(f"[UnitedUcard login] Error during forced logout: {e}") return False def login(self, url): """ Log in via https://www.uhcdental.com/content/provider/dental.html (Sign In -> Provider Login), which OIDC-redirects through identity.onehealthcareid.com. Returns one of: "ALREADY_LOGGED_IN" / "SUCCESS" - logged in, caller should proceed to step1 "OTP_REQUIRED" - caller should poll for OTP entry "ERROR: " - login failed """ wait = WebDriverWait(self.driver, 30) browser_manager = get_browser_manager() try: # Check if credentials have changed - if so, force logout first if self.uniteducard_username and browser_manager.credentials_changed(self.uniteducard_username): self._force_logout() self.driver.get(url) time.sleep(2) # Navigate to the public UHC Dental provider portal (NOT directly to # identity.onehealthcareid.com) — Sign In -> Provider Login triggers # the OIDC redirect to the identity login page. self.driver.get(url) time.sleep(3) current_url = self.driver.current_url print(f"[UnitedUcard login] Current URL: {current_url}") # If we've already landed on the UHC Dental provider dashboard # (secure.uhcdental.com/.../postloginhomescreen.html), a prior # session is still valid. if "secure.uhcdental.com" in current_url.lower(): print("[UnitedUcard login] Already logged in - on UHC Dental dashboard") return "ALREADY_LOGGED_IN" # ── Step 0: public site -> Sign In -> Provider Login ──────────── # Confirmed against live site (2026-09-13): # Sign In (opens dropdown) # Provider Login if "onehealthcareid.com" not in current_url.lower(): try: signin_btn = WebDriverWait(self.driver, 10).until( EC.element_to_be_clickable((By.XPATH, "//span[contains(@class,'header-signin-btn') or contains(text(),'Sign In')]")) ) signin_btn.click() print("[UnitedUcard login] Clicked 'Sign In' to open dropdown") time.sleep(1) except TimeoutException: return "ERROR: 'Sign In' button not found on UHC Dental provider site" # Match by exact visible text "Provider Login" — the dropdown # also has an "Administrator Login" link that shares the same # class AND the same oidc/authorize href pattern (just a # different client_id), so text is the only reliable # differentiator. It's also always the first item in the # dropdown (Provider Login above Administrator Login). clicked_provider_login = False try: provider_login_link = WebDriverWait(self.driver, 10).until( EC.presence_of_element_located((By.XPATH, "//a[normalize-space(text())='Provider Login']")) ) self.driver.execute_script( "arguments[0].scrollIntoView({block:'center'});", provider_login_link ) time.sleep(0.3) try: provider_login_link.click() print("[UnitedUcard login] Clicked 'Provider Login' (direct)") clicked_provider_login = True except Exception as direct_click_err: print(f"[UnitedUcard login] Direct click failed ({direct_click_err}); trying JS click") self.driver.execute_script("arguments[0].click();", provider_login_link) print("[UnitedUcard login] Clicked 'Provider Login' (JS)") clicked_provider_login = True time.sleep(3) except TimeoutException: pass if not clicked_provider_login: # Fallback: the sign-in dropdown's first link is always # "Provider Login" (Administrator Login is second) — # click by position instead of matching text/href. try: dropdown_link = WebDriverWait(self.driver, 5).until( EC.presence_of_element_located((By.XPATH, "(//a[contains(@class,'sign-in-button-link')])[1]")) ) self.driver.execute_script("arguments[0].click();", dropdown_link) print("[UnitedUcard login] Clicked first dropdown link (Provider Login, by position)") clicked_provider_login = True time.sleep(3) except Exception as e: return f"ERROR: 'Provider Login' link not found in Sign In dropdown: {e}" current_url = self.driver.current_url print(f"[UnitedUcard login] After Provider Login click URL: {current_url}") # SSO may skip straight back to the dashboard if already authenticated if "secure.uhcdental.com" in current_url.lower(): print("[UnitedUcard login] Already logged in - redirected straight to dashboard") return "ALREADY_LOGGED_IN" # Check for OTP input first (in case device left mid-OTP) try: WebDriverWait(self.driver, 3).until( EC.presence_of_element_located((By.XPATH, "//input[@type='tel' or contains(@placeholder,'code') or contains(@placeholder,'Code') or " "contains(@aria-label,'Verification') or contains(@id,'otp') or contains(@name,'otp')]")) ) print("[UnitedUcard login] OTP input found on landing") return "OTP_REQUIRED" except TimeoutException: pass # ── Step 1: "One Healthcare ID or Email Address" ──────────────── # Confirmed against live site (2026-09-13): # # try: username_field = wait.until( EC.element_to_be_clickable((By.ID, "username")) ) username_field.clear() username_field.send_keys(self.uniteducard_username) print(f"[UnitedUcard login] Entered username: {self.uniteducard_username}") except TimeoutException: return "ERROR: Username field not found on login page" try: continue_btn = wait.until( EC.element_to_be_clickable((By.ID, "btnLogin")) ) continue_btn.click() print("[UnitedUcard login] Clicked Continue (btnLogin) after username") time.sleep(2) except TimeoutException: return "ERROR: Continue button (btnLogin) not found after entering username" # ── Step 2: "Enter Your Password" page (#/signin/password) ───── # Confirmed against live site (2026-09-13): # # # The email field on this page is pre-filled/read-only from step 1. try: password_field = wait.until( EC.presence_of_element_located((By.ID, "login-pwd")) ) password_field.clear() password_field.send_keys(self.uniteducard_password) print("[UnitedUcard login] Entered password") except TimeoutException: return "ERROR: Password field not found on login page" try: signin_button = wait.until( EC.element_to_be_clickable((By.ID, "btnLogin")) ) signin_button.click() print("[UnitedUcard login] Clicked Continue (btnLogin) on password page") except TimeoutException: return "ERROR: Continue button (btnLogin) not found on password page" if self.uniteducard_username: browser_manager.save_credentials_hash(self.uniteducard_username) time.sleep(5) # Wait for login to process current_url_after_login = self.driver.current_url.lower() print(f"[UnitedUcard login] After login URL: {current_url_after_login}") if "secure.uhcdental.com" in current_url_after_login: print("[UnitedUcard login] Login successful - redirected to UHC Dental dashboard") return "SUCCESS" # ── Step 3: "Verify Your Identity" MFA method page (#/rba/options) ── # Confirmed against live site (2026-09-13): # if "rba/options" in current_url_after_login: try: text_msg_btn = WebDriverWait(self.driver, 10).until( EC.element_to_be_clickable((By.ID, "textMsg")) ) text_msg_btn.click() print("[UnitedUcard login] Clicked 'Via Text Message' on MFA options page") time.sleep(3) except TimeoutException: print("[UnitedUcard login] MFA options page detected but 'Via Text Message' button not found") current_url_after_login = self.driver.current_url.lower() print(f"[UnitedUcard login] After MFA method selection URL: {current_url_after_login}") # Check for OTP input after submitting credentials try: WebDriverWait(self.driver, 15).until( EC.presence_of_element_located((By.XPATH, "//input[@type='tel' or contains(@placeholder,'code') or contains(@placeholder,'Code') or " "contains(@aria-label,'Verification') or contains(@aria-label,'verification') or " "contains(@name,'otp') or contains(@name,'code') or contains(@id,'otp') or contains(@id,'code')]" )) ) print("[UnitedUcard login] OTP input detected -> OTP_REQUIRED") return "OTP_REQUIRED" except TimeoutException: print("[UnitedUcard login] No OTP input detected") # Re-check after waiting for OTP check current_url_after_login = self.driver.current_url.lower() if "secure.uhcdental.com" in current_url_after_login: print("[UnitedUcard login] Login successful - redirected to UHC Dental dashboard") return "SUCCESS" # Check for error messages on the page try: error_elem = self.driver.find_element(By.XPATH, "//*[contains(@class,'error') or contains(@class,'alert')]") error_text = error_elem.text if error_text: print(f"[UnitedUcard login] Error on page: {error_text}") return f"ERROR: {error_text}" except Exception: pass return "ERROR: Login did not complete - still on identity domain" except Exception as e: print(f"[UnitedUcard login] Exception: {e}") return f"ERROR:LOGIN FAILED: {e}" def _format_dob(self, dob_str): """Convert DOB from YYYY-MM-DD to MM/DD/YYYY format""" if dob_str and "-" in dob_str: dob_parts = dob_str.split("-") if len(dob_parts) == 3: # YYYY-MM-DD -> MM/DD/YYYY return f"{dob_parts[1]}/{dob_parts[2]}/{dob_parts[0]}" return dob_str def step1(self): """ Fill in the "Eligibility Search" form — it's the default tab already showing on the dashboard (postloginhomescreen.html) right after login. Confirmed against live site (2026-09-13): - defaults blank; fill with today's date, MM/DD/YYYY - Member Date of Birth, MM/DD/YYYY - Subscriber ID "Search By: Subscriber ID" and "Search For: Individual" are already the default radio selections, so no extra clicks needed there. """ from datetime import date def _fill_field(field_id, value, label): """Wait for field to be clickable, scroll into view, then fill it. Falls back to a JS value-set (+dispatch input/change events, which Angular's ng-model listens for) if send_keys hits an ElementNotInteractable error — the dashboard's Angular app can take a beat to finish rendering/enabling these fields.""" field = WebDriverWait(self.driver, 20).until( EC.presence_of_element_located((By.ID, field_id)) ) self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", field) time.sleep(0.3) try: WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.ID, field_id))) field.clear() field.send_keys(value) print(f"[UnitedUcard step1] Entered {label}: {value}") except Exception as e: print(f"[UnitedUcard step1] send_keys failed for {label} ({e}); falling back to JS set") self.driver.execute_script(""" var el = arguments[0]; var val = arguments[1]; var nativeSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set; nativeSetter.call(el, val); el.dispatchEvent(new Event('input', { bubbles: true })); el.dispatchEvent(new Event('change', { bubbles: true })); el.dispatchEvent(new Event('keyup', { bubbles: true })); """, field, value) print(f"[UnitedUcard step1] Entered {label} via JS: {value}") return field try: print(f"[UnitedUcard step1] Starting eligibility search for memberId={self.memberId}, DOB={self.dateOfBirth}") # Give the dashboard's Angular app a moment to finish rendering # after the redirect from login — filling fields too early was # throwing "element not interactable". WebDriverWait(self.driver, 20).until( lambda d: d.execute_script("return document.readyState") == "complete" ) time.sleep(2) # Service Date - default to today. # NOTE: serviceDate/eligMemDob both use onkeydown="dateFunction(...)", # a JS input mask that auto-inserts "/" as you type raw digits # (same as manually typing it). Sending an already-formatted # "MM/DD/YYYY" string double-processes the slashes through the # mask and corrupts the stored value — send digits only instead, # matching what a human typing the field actually does. try: today_digits = date.today().strftime("%m%d%Y") _fill_field("serviceDate", today_digits, "Service Date") except TimeoutException: return "ERROR: Service Date field (serviceDate) not found on Eligibility Search page" except Exception as e: return f"ERROR: Could not enter Service Date: {e}" # Attempt 1: Subscriber ID + DOB (default "Search By" radio). # Skipped if we have no member ID. if self.memberId: err = self._search_by_subscriber_id(_fill_field) if err: return err if self._search_succeeded(): print("[UnitedUcard step1] Eligibility search submitted (Subscriber ID)") return "Success" print("[UnitedUcard step1] Subscriber ID + DOB search did not return a result") else: print("[UnitedUcard step1] No memberId provided; going straight to name search") # Attempt 2: fall back to First Name + Last Name + DOB. if not (self.firstName and self.lastName and self.dateOfBirth): return "ERROR: Subscriber ID + DOB search failed and first name / last name / DOB are not all available for a name search" print(f"[UnitedUcard step1] Falling back to name search: {self.firstName} {self.lastName}, DOB={self.dateOfBirth}") err = self._search_by_name(_fill_field) if err: return err if self._search_succeeded(): print("[UnitedUcard step1] Eligibility search submitted (Name)") return "Success" return "ERROR: Eligibility search returned no result by Subscriber ID + DOB or by Name + DOB" except Exception as e: print(f"[UnitedUcard step1] Exception: {e}") return f"ERROR:STEP1 - {e}" def _click_search(self): search_btn = WebDriverWait(self.driver, 10).until( EC.presence_of_element_located((By.XPATH, "//input[@type='button' and @value='search']")) ) self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", search_btn) time.sleep(0.3) try: search_btn.click() except Exception: self.driver.execute_script("arguments[0].click();", search_btn) print("[UnitedUcard step1] Clicked SEARCH button") time.sleep(3) def _select_search_by(self, value): """Click the 'Search By' radio (value 'searchbyname' or the subscriber ID one).""" radio = WebDriverWait(self.driver, 10).until( EC.presence_of_element_located((By.CSS_SELECTOR, f"input[type='radio'][name='searchby'][value='{value}']")) ) self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", radio) try: radio.click() except Exception: self.driver.execute_script("arguments[0].click();", radio) time.sleep(1) print(f"[UnitedUcard step1] Selected Search By radio: {value}") def _search_by_subscriber_id(self, _fill_field): """Returns an error string, or None once SEARCH has been clicked.""" try: dob_digits = re.sub(r"\D", "", self._format_dob(self.dateOfBirth)) _fill_field("eligMemDob", dob_digits, "Member DOB") except Exception as e: return f"ERROR: Could not enter Member Date of Birth: {e}" try: _fill_field("eligSubsId", self.memberId, "Subscriber ID") except Exception as e: return f"ERROR: Could not enter Subscriber ID: {e}" time.sleep(0.5) try: self._click_search() except Exception as e: return f"ERROR: Could not click SEARCH button: {e}" return None def _search_by_name(self, _fill_field): """Switch to the 'Name' radio and search by first/last name + DOB. Returns an error string, or None once SEARCH has been clicked.""" try: self._select_search_by("searchbyname") except Exception as e: return f"ERROR: Could not select the Name search radio: {e}" # Switching the radio re-renders the form, so re-enter Service Date too. try: from datetime import date _fill_field("serviceDate", date.today().strftime("%m%d%Y"), "Service Date") except Exception as e: return f"ERROR: Could not enter Service Date: {e}" for field_id, value, label in (("eligFname", self.firstName, "First Name"), ("eligLname", self.lastName, "Last Name")): try: _fill_field(field_id, value, label) except Exception as e: return f"ERROR: Could not enter {label}: {e}" try: dob_digits = re.sub(r"\D", "", self._format_dob(self.dateOfBirth)) _fill_field("eligMemDob", dob_digits, "Member DOB") except Exception as e: return f"ERROR: Could not enter Member Date of Birth: {e}" time.sleep(0.5) try: self._click_search() except Exception as e: return f"ERROR: Could not click SEARCH button: {e}" return None def _handle_duplicate_records(self): """If the portal lists duplicate member records ("Duplicate records have been found."), select the Active one (falling back to the last row) and resubmit. Returns True if a record was selected.""" radios = self.driver.find_elements(By.CSS_SELECTOR, "input[type='radio'][name='refineMemDupliGroup']") radios = [r for r in radios if r.is_displayed()] if not radios: return False chosen = None for r in radios: try: row_text = r.find_element(By.XPATH, "./ancestor::tr[1]").text.lower() except Exception: row_text = "" if "active" in row_text and "termed" not in row_text and "inactive" not in row_text: chosen = r break if chosen is None: chosen = radios[-1] print(f"[UnitedUcard step1] {len(radios)} duplicate records; selecting value={chosen.get_attribute('value')}") self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", chosen) try: chosen.click() except Exception: self.driver.execute_script("arguments[0].click();", chosen) time.sleep(1.5) # Click the "select" button (ng-click="refineSearch()") shown below the duplicate list. try: select_btn = WebDriverWait(self.driver, 10).until( EC.element_to_be_clickable((By.XPATH, "//input[@type='button' and @value='select' and contains(@ng-click,'refineSearch')]")) ) self.driver.execute_script("arguments[0].scrollIntoView({block:'center'});", select_btn) try: select_btn.click() except Exception: self.driver.execute_script("arguments[0].click();", select_btn) print("[UnitedUcard step1] Clicked select button for duplicate record") except Exception as e: print(f"[UnitedUcard step1] Could not click select button: {e}") return True def _search_succeeded(self): """True once the browser lands on the Eligibility Summary page; False as soon as the form shows a "Member not found" error (or after 30s). Duplicate-record lists are resolved along the way.""" deadline = time.time() + 30 duplicates_handled = False while time.time() < deadline: try: if "eligibility-summary" in self.driver.current_url.lower(): return True body = self.driver.find_element(By.TAG_NAME, "body").text.lower() if "member not found" in body: print("[UnitedUcard step1] Portal reported: Member not found") return False if not duplicates_handled and self._handle_duplicate_records(): duplicates_handled = True continue except Exception as e: print(f"[UnitedUcard step1] Result check error: {e}") time.sleep(0.5) return False def step2(self): """ Extract patient/eligibility info from the "Eligibility Summary" page (secure.uhcdental.com/.../eligibility-summary.html) that step1()'s SEARCH click lands on, then capture it as a PDF. Confirmed against live site (2026-09-13) via screenshot/PDF export: "Patient" panel -> patient full name (e.g. "WINGHUN KWONG") "Insurance Information" -> status badges: "In Network", "Eligible" (or "Not Eligible"/"Ineligible"), "No Essential Health Benefits", and a MASKED Subscriber ID (e.g. "******444") — we deliberately do NOT use that masked ID to overwrite the patient record. Exact DOM ids/classes for the badges haven't been inspected yet, so this uses text-based extraction (same approach as UnitedSCO/DDMA) as a robust starting point. """ try: print("[UnitedUcard step2] Starting eligibility capture") try: WebDriverWait(self.driver, 20).until( lambda d: d.execute_script("return document.readyState") == "complete" ) except Exception: pass # Wait for the summary content (not just the page shell) before reading/printing it. try: WebDriverWait(self.driver, 20).until( lambda d: "insurance information" in d.find_element(By.TAG_NAME, "body").text.lower() ) except Exception: print("[UnitedUcard step2] WARNING: 'Insurance Information' not visible yet; capturing anyway") time.sleep(2) current_url = self.driver.current_url print(f"[UnitedUcard step2] Current URL: {current_url}") page_text = "" try: page_text = self.driver.find_element(By.TAG_NAME, "body").text except Exception: pass # ── Eligibility status ─────────────────────────────────────── eligibilityText = "unknown" lower_text = page_text.lower() if "not eligible" in lower_text or "ineligible" in lower_text: eligibilityText = "inactive" elif "eligible" in lower_text: eligibilityText = "active" print(f"[UnitedUcard step2] Eligibility status: {eligibilityText}") # ── Patient name ───────────────────────────────────────────── patientName = f"{self.firstName} {self.lastName}".strip() name_extracted = False # Strategy 1: text right after the "Patient" heading, e.g. # "Patient\nWINGHUN KWONG\nSUBSCRIBER/INSURED" try: name_match = re.search( r'Patient\s*\n\s*([A-Z][A-Za-z\-\']+(?:\s+[A-Z][A-Za-z\-\']+)+)\s*\n', page_text ) if name_match: candidate = name_match.group(1).strip() if candidate and len(candidate) < 60 and "eligible" not in candidate.lower(): patientName = candidate.title() name_extracted = True print(f"[UnitedUcard step2] Extracted patient name from text: {patientName}") except Exception as e: print(f"[UnitedUcard step2] Name regex failed: {e}") # Strategy 2: DOM heuristic fallback — heading/strong element # following a "Patient" label if not name_extracted: try: elems = self.driver.find_elements(By.XPATH, "//*[contains(text(),'Patient')]/following::*[self::h1 or self::h2 or self::h3 or self::strong][1]" ) for elem in elems: txt = elem.text.strip() if txt and len(txt.split()) >= 2 and txt[0].isupper() and len(txt) < 60: patientName = txt.title() name_extracted = True print(f"[UnitedUcard step2] Extracted patient name from DOM: {patientName}") break except Exception: pass if not name_extracted: print("[UnitedUcard step2] WARNING: Could not extract patient name from page; using form values") # ── Member ID ──────────────────────────────────────────────── # The Subscriber ID badge is masked, but "Product ID" is the full ID # (the value the Subscriber ID search field accepts, e.g. "D0033034"). # This page looks the same however the search was done (ID or name), # so always prefer it; fall back to the ID we searched with. memberId = self.memberId pid_match = re.search(r'Product ID:\s*([A-Za-z0-9]+)', page_text) if pid_match: memberId = pid_match.group(1) print(f"[UnitedUcard step2] Member ID from Product ID: {memberId}") else: print(f"[UnitedUcard step2] WARNING: Product ID not found on page; using searched ID '{memberId}'") # ── Capture PDF ────────────────────────────────────────────── pdf_path = self._capture_pdf(memberId) if not pdf_path: return {"status": "error", "message": "STEP2 FAILED: Could not generate PDF"} print(f"[UnitedUcard step2] PDF saved: {pdf_path}") self._hide_browser() print("[UnitedUcard step2] Eligibility capture complete") return { "status": "success", "eligibility": eligibilityText, "ss_path": pdf_path, "pdf_path": pdf_path, "patientName": patientName, # NOTE: the Subscriber ID badge is masked (e.g. "******444"), so # we return the page's Product ID (falling back to the searched ID). "memberId": memberId, } except Exception as e: print(f"[UnitedUcard step2] Exception: {e}") return {"status": "error", "message": f"STEP2 FAILED: {str(e)}"} def _capture_pdf(self, member_id): """Capture the current page as PDF using Chrome DevTools Protocol.""" try: pdf_options = { "landscape": False, "displayHeaderFooter": False, "printBackground": True, "preferCSSPageSize": True, "paperWidth": 8.5, "paperHeight": 11, "marginTop": 0.4, "marginBottom": 0.4, "marginLeft": 0.4, "marginRight": 0.4, "scale": 0.9, } file_identifier = member_id if member_id else f"{self.firstName}_{self.lastName}" result = self.driver.execute_cdp_cmd("Page.printToPDF", pdf_options) pdf_data = base64.b64decode(result.get('data', '')) pdf_path = os.path.join(self.download_dir, f"uniteducard_eligibility_{file_identifier}_{int(time.time())}.pdf") with open(pdf_path, "wb") as f: f.write(pdf_data) return pdf_path except Exception as e: print(f"[UnitedUcard _capture_pdf] Error: {e}") return None def _hide_browser(self): """Hide the browser window after task completion using multiple strategies.""" try: try: self.driver.get("about:blank") time.sleep(0.5) except Exception: pass try: self.driver.minimize_window() print("[UnitedUcard step2] Browser window minimized") return except Exception: pass try: self.driver.set_window_position(-10000, -10000) print("[UnitedUcard step2] Browser window moved off-screen") return except Exception: pass except Exception as e: print(f"[UnitedUcard step2] Could not hide browser: {e}") def main_workflow(self, url): """Main workflow that runs all steps.""" try: self.config_driver() login_result = self.login(url) print(f"[main_workflow] Login result: {login_result}") if login_result == "OTP_REQUIRED": return {"status": "otp_required", "message": "OTP required after login"} if isinstance(login_result, str) and login_result.startswith("ERROR"): return {"status": "error", "message": login_result} step1_result = self.step1() print(f"[main_workflow] Step1 result: {step1_result}") if isinstance(step1_result, str) and step1_result.startswith("ERROR"): return {"status": "error", "message": step1_result} step2_result = self.step2() print(f"[main_workflow] Step2 result: {step2_result}") return step2_result except Exception as e: return {"status": "error", "message": str(e)}