CP_AUTOMATION/pages/student_nav_page.py
2025-12-12 19:54:54 +05:30

218 lines
9.8 KiB
Python

"""
Student Navigation Page Object Model
Handles student navigation header including profile dropdown and logout.
Scope: student_nav
"""
import time
from selenium.webdriver.common.by import By
from pages.base_page import BasePage
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from config.config import SHORT_WAIT, BASE_URL
class StudentNavPage(BasePage):
"""Page Object for Student Navigation Header"""
# Navigation links
DASHBOARD_LINK = (By.CSS_SELECTOR, "[data-testid='student_nav__dashboard_link']")
ASSESSMENTS_LINK = (By.CSS_SELECTOR, "[data-testid='student_nav__assessments_link']")
# Profile dropdown button (user icon with dropdown) - IMPLEMENTED ✅
PROFILE_BUTTON = (By.CSS_SELECTOR, "[data-testid='student_nav__profile_button']")
# Profile dropdown menu items - IMPLEMENTED ✅
PROFILE_DROPDOWN = (By.CSS_SELECTOR, "[data-testid='student_nav__profile_dropdown']")
EDIT_PROFILE_BUTTON = (By.CSS_SELECTOR, "[data-testid='student_nav__edit_profile_button']")
RESET_PASSWORD_BUTTON = (By.CSS_SELECTOR, "[data-testid='student_nav__reset_password_button']")
SIGN_OUT_BUTTON = (By.CSS_SELECTOR, "[data-testid='student_nav__sign_out_button']")
def __init__(self, driver):
"""Initialize Student Navigation Page"""
super().__init__(driver)
def click_dashboard(self):
"""Click Dashboard navigation link"""
self.click_element(self.DASHBOARD_LINK)
self.wait.wait_for_url_contains("/dashboard")
def click_assessments(self):
"""Click Assessments navigation link"""
self.click_element(self.ASSESSMENTS_LINK)
self.wait.wait_for_url_contains("/assessments")
def click_profile_button(self):
"""
Click profile button to open dropdown
IMPORTANT: Ensures no modals are blocking (password reset or profile incomplete).
Uses robust modal detection with fallback mechanisms.
"""
# First, ensure no modals are blocking the button
from pages.mandatory_reset_page import MandatoryResetPage
from pages.profile_incomplete_page import ProfileIncompletePage
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from config.config import SHORT_WAIT, MEDIUM_WAIT
reset_page = MandatoryResetPage(self.driver)
profile_incomplete = ProfileIncompletePage(self.driver)
# Handle password reset modal if present
if reset_page.is_modal_present():
print("⚠️ Password reset modal blocking - handling automatically...")
from utils.password_tracker import password_tracker
from config.config import TEST_PASSWORD, TEST_NEW_PASSWORD
current_password = password_tracker.get_password("", TEST_PASSWORD)
reset_page.reset_password(
current_password=current_password,
new_password=TEST_NEW_PASSWORD,
confirm_password=TEST_NEW_PASSWORD,
student_cpid=""
)
# Wait for modal to fully disappear
self._wait_for_modal_overlays_to_disappear()
# Handle profile incomplete modal if present - click "Complete Profile Now" to dismiss
if profile_incomplete.is_modal_present():
print("⚠️ Profile incomplete modal blocking - clicking 'Complete Profile Now'...")
profile_incomplete.click_complete()
# Wait for navigation and modal to disappear
self._wait_for_modal_overlays_to_disappear()
# Final check: Wait for any remaining modal overlays to disappear
self._wait_for_modal_overlays_to_disappear()
# Verify no modals are blocking before clicking
max_retries = 3
for attempt in range(max_retries):
if not reset_page.is_modal_present() and not profile_incomplete.is_modal_present():
break
if attempt < max_retries - 1:
print(f"⚠️ Modals still present, waiting... (attempt {attempt + 1}/{max_retries})")
time.sleep(1)
self._wait_for_modal_overlays_to_disappear()
else:
raise Exception("Modals are still blocking profile button after multiple attempts")
# Use data-testid locator (implemented ✅)
profile_btn = self.wait.wait_for_element_clickable(self.PROFILE_BUTTON)
self.driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", profile_btn)
time.sleep(0.2) # Brief wait for scroll
# Try clicking with retry mechanism
try:
profile_btn.click()
except Exception as e:
# If click is intercepted, wait a bit more and try JavaScript click
if "click intercepted" in str(e).lower():
print("⚠️ Click intercepted, waiting for overlays and retrying with JavaScript...")
self._wait_for_modal_overlays_to_disappear()
time.sleep(0.5)
self.driver.execute_script("arguments[0].click();", profile_btn)
else:
raise
# Wait for dropdown to appear using data-testid (implemented ✅)
time.sleep(0.3) # Brief wait for React state to update
try:
self.wait.wait_for_element_visible(self.SIGN_OUT_BUTTON)
except:
# Check if we're already logged out
current_url = self.driver.current_url
from config.config import BASE_URL
if "/login" in current_url or current_url.rstrip("/") == BASE_URL.rstrip("/"):
return # Already logged out
raise Exception(f"Profile dropdown did not open. Current URL: {current_url}")
def _wait_for_modal_overlays_to_disappear(self):
"""
Wait for all modal overlays to disappear.
Checks for common modal overlay patterns that might block clicks.
"""
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from config.config import SHORT_WAIT
# Check for common modal overlay selectors (high z-index overlays)
modal_overlay_selectors = [
"div.fixed.inset-0.bg-black\\/60.z-\\[99999\\]", # Password reset modal
"div.fixed.inset-0.bg-black\\/50.z-\\[9999\\]", # Profile incomplete modal
"div[class*='fixed'][class*='inset-0'][class*='bg-black'][class*='z-[99999]']", # Variant 1
"div[class*='fixed'][class*='inset-0'][class*='bg-black'][class*='z-[9999]']", # Variant 2
"div.fixed.inset-0[class*='z-[99999]']", # Simplified high z-index
"div.fixed.inset-0[class*='z-[9999]']", # Simplified medium-high z-index
]
for selector in modal_overlay_selectors:
try:
WebDriverWait(self.driver, SHORT_WAIT).until(
EC.invisibility_of_element_located((By.CSS_SELECTOR, selector))
)
except:
# Overlay might not exist or already gone, continue
pass
def click_sign_out(self):
"""
Click Sign Out button in profile dropdown
IMPORTANT: Profile dropdown must be open first (call click_profile_button).
Ensures no modals are blocking before clicking.
"""
# Ensure no modals are blocking before clicking sign out
from pages.mandatory_reset_page import MandatoryResetPage
from pages.profile_incomplete_page import ProfileIncompletePage
reset_page = MandatoryResetPage(self.driver)
profile_incomplete = ProfileIncompletePage(self.driver)
# Wait for any modals to disappear
max_retries = 3
for attempt in range(max_retries):
if not reset_page.is_modal_present() and not profile_incomplete.is_modal_present():
break
if attempt < max_retries - 1:
print(f"⚠️ Modals detected before sign out, waiting... (attempt {attempt + 1}/{max_retries})")
self._wait_for_modal_overlays_to_disappear()
time.sleep(1)
else:
raise Exception("Modals are still blocking sign out button after multiple attempts")
# Final wait for modal overlays
self._wait_for_modal_overlays_to_disappear()
# Use data-testid locator (implemented ✅)
signout_btn = self.wait.wait_for_element_clickable(self.SIGN_OUT_BUTTON)
self.driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", signout_btn)
time.sleep(0.2)
# Try clicking with retry mechanism
try:
signout_btn.click()
except Exception as e:
# If click is intercepted, wait for overlays and try JavaScript click
if "click intercepted" in str(e).lower():
print("⚠️ Sign out click intercepted, waiting for overlays and retrying with JavaScript...")
self._wait_for_modal_overlays_to_disappear()
time.sleep(0.5)
self.driver.execute_script("arguments[0].click();", signout_btn)
else:
raise
# Wait for redirect to login page
self.wait.wait_for_url_contains("/") # Redirects to login (root)
def logout(self):
"""
Complete logout flow: Open profile dropdown and click Sign Out
This is the main method to use for logout.
"""
self.click_profile_button()
self.click_sign_out()