fix: improve eligibility checks for MassHealth and DentaQuest
- MassHealth: detect SSO/portal maintenance page and return clear error instead of cryptic step1 timeout; wait for SSO redirect to complete before running step1; add modal dismissal and failure screenshot/logging - DentaQuest: detect maintenance page in login and step1; search by member ID + DOB only (remove first/last name to prevent stale data from previous patient being submitted) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -82,6 +82,21 @@ class AutomationDentaQuestEligibilityCheck:
|
|||||||
print(f"[DentaQuest login] Error during forced logout: {e}")
|
print(f"[DentaQuest login] Error during forced logout: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def _is_maintenance_page(self) -> bool:
|
||||||
|
"""Return True if the current page is DentaQuest's maintenance/capacity error page."""
|
||||||
|
try:
|
||||||
|
body = self.driver.find_element(By.TAG_NAME, "body").text
|
||||||
|
markers = [
|
||||||
|
"temporarily unable to service",
|
||||||
|
"maintenance downtime",
|
||||||
|
"capacity problems",
|
||||||
|
"Please try again later",
|
||||||
|
]
|
||||||
|
body_lower = body.lower()
|
||||||
|
return any(m.lower() in body_lower for m in markers)
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
def login(self, url):
|
def login(self, url):
|
||||||
wait = WebDriverWait(self.driver, 30)
|
wait = WebDriverWait(self.driver, 30)
|
||||||
browser_manager = get_browser_manager()
|
browser_manager = get_browser_manager()
|
||||||
@@ -115,6 +130,10 @@ class AutomationDentaQuestEligibilityCheck:
|
|||||||
self.driver.get(url)
|
self.driver.get(url)
|
||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
|
|
||||||
|
if self._is_maintenance_page():
|
||||||
|
print("[DentaQuest login] Maintenance page detected")
|
||||||
|
return "ERROR: DentaQuest server is temporarily unavailable (maintenance). Please try again later."
|
||||||
|
|
||||||
current_url = self.driver.current_url
|
current_url = self.driver.current_url
|
||||||
print(f"[DentaQuest login] After navigation URL: {current_url}")
|
print(f"[DentaQuest login] After navigation URL: {current_url}")
|
||||||
|
|
||||||
@@ -265,6 +284,10 @@ class AutomationDentaQuestEligibilityCheck:
|
|||||||
fields.append(f"DOB: {self.dateOfBirth}")
|
fields.append(f"DOB: {self.dateOfBirth}")
|
||||||
print(f"[DentaQuest step1] Starting member search with: {', '.join(fields)}")
|
print(f"[DentaQuest step1] Starting member search with: {', '.join(fields)}")
|
||||||
|
|
||||||
|
if self._is_maintenance_page():
|
||||||
|
print("[DentaQuest step1] Maintenance page detected")
|
||||||
|
return "ERROR: DentaQuest server is temporarily unavailable (maintenance). Please try again later."
|
||||||
|
|
||||||
# Wait for page to be ready
|
# Wait for page to be ready
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
|
|
||||||
@@ -346,8 +369,7 @@ class AutomationDentaQuestEligibilityCheck:
|
|||||||
fill_date_by_testid("member-search_date-of-birth", dob_month, dob_day, dob_year, "Date of Birth")
|
fill_date_by_testid("member-search_date-of-birth", dob_month, dob_day, dob_year, "Date of Birth")
|
||||||
time.sleep(0.3)
|
time.sleep(0.3)
|
||||||
|
|
||||||
# 3. Fill ALL available search fields (flexible search)
|
# 3. Fill Member ID only (search by member ID + DOB only)
|
||||||
# Fill Member ID if provided
|
|
||||||
if self.memberId:
|
if self.memberId:
|
||||||
try:
|
try:
|
||||||
member_id_input = wait.until(EC.presence_of_element_located(
|
member_id_input = wait.until(EC.presence_of_element_located(
|
||||||
@@ -360,32 +382,6 @@ class AutomationDentaQuestEligibilityCheck:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[DentaQuest step1] Warning: Could not fill member ID: {e}")
|
print(f"[DentaQuest step1] Warning: Could not fill member ID: {e}")
|
||||||
|
|
||||||
# Fill First Name if provided
|
|
||||||
if self.firstName:
|
|
||||||
try:
|
|
||||||
first_name_input = wait.until(EC.presence_of_element_located(
|
|
||||||
(By.XPATH, '//input[@placeholder="First name - 1 char minimum" or contains(@placeholder,"first name") or contains(@name,"firstName") or contains(@id,"firstName")]')
|
|
||||||
))
|
|
||||||
first_name_input.clear()
|
|
||||||
first_name_input.send_keys(self.firstName)
|
|
||||||
print(f"[DentaQuest step1] Entered first name: {self.firstName}")
|
|
||||||
time.sleep(0.2)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[DentaQuest step1] Warning: Could not fill first name: {e}")
|
|
||||||
|
|
||||||
# Fill Last Name if provided
|
|
||||||
if self.lastName:
|
|
||||||
try:
|
|
||||||
last_name_input = wait.until(EC.presence_of_element_located(
|
|
||||||
(By.XPATH, '//input[@placeholder="Last name - 2 char minimum" or contains(@placeholder,"last name") or contains(@name,"lastName") or contains(@id,"lastName")]')
|
|
||||||
))
|
|
||||||
last_name_input.clear()
|
|
||||||
last_name_input.send_keys(self.lastName)
|
|
||||||
print(f"[DentaQuest step1] Entered last name: {self.lastName}")
|
|
||||||
time.sleep(0.2)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[DentaQuest step1] Warning: Could not fill last name: {e}")
|
|
||||||
|
|
||||||
time.sleep(0.3)
|
time.sleep(0.3)
|
||||||
|
|
||||||
# 4. Click Search button
|
# 4. Click Search button
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class AutomationMassHealthEligibilityCheck:
|
|||||||
year, month, day = parts
|
year, month, day = parts
|
||||||
self.dateOfBirth = f"{month.zfill(2)}{day.zfill(2)}{year}"
|
self.dateOfBirth = f"{month.zfill(2)}{day.zfill(2)}{year}"
|
||||||
|
|
||||||
self.download_dir = os.path.abspath("downloads")
|
self.download_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "downloads")
|
||||||
os.makedirs(self.download_dir, exist_ok=True)
|
os.makedirs(self.download_dir, exist_ok=True)
|
||||||
|
|
||||||
def config_driver(self):
|
def config_driver(self):
|
||||||
@@ -51,10 +51,30 @@ class AutomationMassHealthEligibilityCheck:
|
|||||||
driver = webdriver.Chrome(service=s, options=options)
|
driver = webdriver.Chrome(service=s, options=options)
|
||||||
self.driver = driver
|
self.driver = driver
|
||||||
|
|
||||||
|
def _is_maintenance_page(self) -> bool:
|
||||||
|
"""Return True if the current page is a server maintenance/capacity error page."""
|
||||||
|
try:
|
||||||
|
body = self.driver.find_element(By.TAG_NAME, "body").text
|
||||||
|
markers = [
|
||||||
|
"temporarily unable to service",
|
||||||
|
"maintenance downtime",
|
||||||
|
"capacity problems",
|
||||||
|
"Please try again later",
|
||||||
|
]
|
||||||
|
body_lower = body.lower()
|
||||||
|
return any(m.lower() in body_lower for m in markers)
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
def login(self):
|
def login(self):
|
||||||
wait = WebDriverWait(self.driver, 30)
|
wait = WebDriverWait(self.driver, 30)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# Check immediately if the portal is showing a maintenance page
|
||||||
|
if self._is_maintenance_page():
|
||||||
|
print("[login] Maintenance page detected on initial load")
|
||||||
|
return "ERROR: MassHealth portal is temporarily unavailable (maintenance). Please try again later."
|
||||||
|
|
||||||
# Step 1: Click the SIGN IN button on the initial page
|
# Step 1: Click the SIGN IN button on the initial page
|
||||||
signin_button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "a.btn.btn-block.btn-primary[href='https://connectsso.masshealth-dental.org/mhprovider/index.html']")))
|
signin_button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "a.btn.btn-block.btn-primary[href='https://connectsso.masshealth-dental.org/mhprovider/index.html']")))
|
||||||
signin_button.click()
|
signin_button.click()
|
||||||
@@ -76,6 +96,23 @@ class AutomationMassHealthEligibilityCheck:
|
|||||||
login_button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "input[type='submit'][name='submit'][value='Login']")))
|
login_button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "input[type='submit'][name='submit'][value='Login']")))
|
||||||
login_button.click()
|
login_button.click()
|
||||||
|
|
||||||
|
# Wait for SSO redirect to complete and land on the MassHealth portal.
|
||||||
|
# Must check the hostname only — the SSO URL itself contains "provider.masshealth-dental.org"
|
||||||
|
# as a query parameter, so a plain substring check would match too early.
|
||||||
|
print("[login] Waiting for SSO redirect to provider.masshealth-dental.org ...")
|
||||||
|
try:
|
||||||
|
WebDriverWait(self.driver, 30).until(
|
||||||
|
lambda d: d.current_url.startswith("https://provider.masshealth-dental.org")
|
||||||
|
)
|
||||||
|
print(f"[login] Redirect complete. URL: {self.driver.current_url}")
|
||||||
|
except TimeoutException:
|
||||||
|
print(f"[login] Redirect timeout. Still on: {self.driver.current_url}")
|
||||||
|
return "ERROR: Login redirect timed out — check credentials or portal availability."
|
||||||
|
|
||||||
|
if self._is_maintenance_page():
|
||||||
|
print("[login] Maintenance page detected after login submission")
|
||||||
|
return "ERROR: MassHealth portal is temporarily unavailable (maintenance). Please try again later."
|
||||||
|
|
||||||
return "Success"
|
return "Success"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
||||||
@@ -90,6 +127,26 @@ class AutomationMassHealthEligibilityCheck:
|
|||||||
print(f"[step1] current URL after login: {self.driver.current_url}")
|
print(f"[step1] current URL after login: {self.driver.current_url}")
|
||||||
print(f"[step1] page title: {self.driver.title}")
|
print(f"[step1] page title: {self.driver.title}")
|
||||||
|
|
||||||
|
if self._is_maintenance_page():
|
||||||
|
print("[step1] Maintenance page detected")
|
||||||
|
return "ERROR: MassHealth portal is temporarily unavailable (maintenance). Please try again later."
|
||||||
|
|
||||||
|
# Dismiss any post-login modal/alert dialogs (session warnings, notices, etc.)
|
||||||
|
for btn_xpath in [
|
||||||
|
"//button[contains(text(),'OK') or contains(text(),'Ok') or contains(text(),'ok')]",
|
||||||
|
"//button[contains(text(),'Continue') or contains(text(),'Accept') or contains(text(),'Close')]",
|
||||||
|
"//button[contains(text(),'Dismiss') or contains(text(),'Got it')]",
|
||||||
|
"//a[contains(@class,'close') or @data-dismiss='modal']",
|
||||||
|
]:
|
||||||
|
try:
|
||||||
|
btn = WebDriverWait(self.driver, 3).until(EC.element_to_be_clickable((By.XPATH, btn_xpath)))
|
||||||
|
btn.click()
|
||||||
|
print(f"[step1] Dismissed modal via: {btn_xpath}")
|
||||||
|
time.sleep(1)
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
substep = "patient_management"
|
substep = "patient_management"
|
||||||
patient_mgmt = wait.until(
|
patient_mgmt = wait.until(
|
||||||
EC.presence_of_element_located(
|
EC.presence_of_element_located(
|
||||||
@@ -162,7 +219,19 @@ class AutomationMassHealthEligibilityCheck:
|
|||||||
return "Success"
|
return "Success"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[step1] FAILED at substep='{substep}': {e}")
|
print(f"[step1] FAILED at substep='{substep}': {e}")
|
||||||
print(f"[step1] URL at failure: {self.driver.current_url}")
|
try:
|
||||||
|
print(f"[step1] URL at failure: {self.driver.current_url}")
|
||||||
|
print(f"[step1] Page title: {self.driver.title}")
|
||||||
|
body_text = self.driver.find_element(By.TAG_NAME, "body").text
|
||||||
|
print(f"[step1] Page body (first 500 chars): {body_text[:500]}")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
ss_path = os.path.join(self.download_dir, f"step1_failure_{substep}_{int(time.time())}.png")
|
||||||
|
self.driver.save_screenshot(ss_path)
|
||||||
|
print(f"[step1] Screenshot saved: {ss_path}")
|
||||||
|
except Exception as ss_err:
|
||||||
|
print(f"[step1] Could not save screenshot: {ss_err}")
|
||||||
return f"ERROR:STEP1:{substep}"
|
return f"ERROR:STEP1:{substep}"
|
||||||
|
|
||||||
def _cell_text(self, cell):
|
def _cell_text(self, cell):
|
||||||
|
|||||||
Reference in New Issue
Block a user