""" Login Page Object Model for Cognitive Prism (Local) Uses data-testid locators as per local implementation standards. Scope: student_login """ import time from selenium.webdriver.common.by import By from pages.base_page import BasePage # URLs imported dynamically to support --url override from config.config import TEST_USERNAME, TEST_PASSWORD, BASE_URL class LoginPage(BasePage): """Page Object for Login Page - Using data-testid locators""" # Locators using data-testid (scope: student_login) - IMPLEMENTED ✅ LOGIN_FORM = (By.CSS_SELECTOR, "[data-testid='student_login__form']") IDENTIFIER_INPUT = (By.CSS_SELECTOR, "[data-testid='student_login__identifier_input']") PASSWORD_INPUT = (By.CSS_SELECTOR, "[data-testid='student_login__password_input']") REMEMBER_CHECKBOX = (By.CSS_SELECTOR, "[data-testid='student_login__remember_checkbox']") ERROR_BANNER = (By.CSS_SELECTOR, "[data-testid='student_login__error_banner']") SUBMIT_BUTTON = (By.CSS_SELECTOR, "[data-testid='student_login__submit_button']") # Toast notification error (appears after login failure) # Uses data-testid attribute (implemented by UI team via toastHelpers.js) ERROR_TOAST = (By.CSS_SELECTOR, "[data-testid='student_login__error_toast']") def __init__(self, driver): """Initialize Login Page""" super().__init__(driver) # Use LOGIN_URL from config (which may have been overridden) from config.config import LOGIN_URL self.url = LOGIN_URL def navigate(self): """ Navigate to login page - optimized for speed If already logged in, will logout first, then navigate to login page. For fresh login, navigates to login page. """ # CRITICAL: Check if already logged in BEFORE navigating current_url = self.driver.current_url if "/dashboard" in current_url or "/student" in current_url: # Already logged in - need to logout first print("🔄 Already logged in - logging out first...") try: from pages.student_nav_page import StudentNavPage nav_page = StudentNavPage(self.driver) nav_page.logout() # Wait for redirect to login (with longer timeout) from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from config.config import MEDIUM_WAIT WebDriverWait(self.driver, MEDIUM_WAIT).until( lambda d: "/login" in d.current_url or d.current_url.rstrip("/") == BASE_URL.rstrip("/") or "/" == d.current_url.rstrip("/") ) print("✅ Logged out successfully - now on login page") # Brief wait for session to clear import time time.sleep(0.5) except Exception as e: # Logout failed - try clearing session and navigating print(f"⚠️ Logout failed during navigate: {e}, clearing session...") try: # Clear session storage self.driver.execute_script("window.localStorage.clear();") self.driver.execute_script("window.sessionStorage.clear();") # Navigate to login self.driver.get(self.url) except: self.driver.get(self.url) else: # Not logged in - navigate directly self.driver.get(self.url) def enter_identifier(self, identifier=TEST_USERNAME): """ Enter student CPID/identifier Args: identifier: Student CPID or email """ element = self.wait.wait_for_element_visible(self.IDENTIFIER_INPUT) element.clear() element.send_keys(identifier) def enter_password(self, password=TEST_PASSWORD): """ Enter password Args: password: Password to enter """ element = self.wait.wait_for_element_visible(self.PASSWORD_INPUT) element.clear() element.send_keys(password) def check_remember_me(self): """Check 'Remember me' checkbox""" checkbox = self.wait.wait_for_element_visible(self.REMEMBER_CHECKBOX) if not checkbox.is_selected(): checkbox.click() def uncheck_remember_me(self): """Uncheck 'Remember me' checkbox""" checkbox = self.find_element(self.REMEMBER_CHECKBOX) if checkbox.is_selected(): checkbox.click() def click_submit(self): """Click Sign In/Submit button""" button = self.wait.wait_for_element_clickable(self.SUBMIT_BUTTON) button.click() def is_error_visible(self): """ Check if error toast/banner is visible Returns: bool: True if error is visible """ try: # Toast notification appears immediately after submit - check quickly return self.is_element_visible(self.ERROR_TOAST, timeout=2) except: try: # Fallback to error banner return self.is_element_visible(self.ERROR_BANNER, timeout=1) except: return False def get_error_message(self): """ Get error message if present Returns: str: Error message text or empty string """ try: # Toast notification - appears immediately toast = self.find_element(self.ERROR_TOAST) if toast.is_displayed(): return toast.text except: pass try: # Fallback to error banner banner = self.find_element(self.ERROR_BANNER) if banner.is_displayed(): return banner.text except: pass return "" def login(self, identifier=TEST_USERNAME, password=None, remember_me=False): """ Complete login flow with SMART password handling SMART MECHANISM: 1. If password is provided, use it directly 2. If password is None, try Excel password (TEST_PASSWORD) first 3. If Excel password fails, automatically try Admin@123 (TEST_NEW_PASSWORD) 4. This handles both fresh students and students who already reset password Args: identifier: Student CPID or email password: Password (if None, tries Excel password then Admin@123) remember_me: Whether to check remember me """ from config.config import TEST_PASSWORD, TEST_NEW_PASSWORD # SMART PASSWORD HANDLING: Always try Excel password first, then Admin@123 if password is None: # Start with Excel password (default from config) password = TEST_PASSWORD print(f"🔑 Trying Excel password first for {identifier}") self.navigate() # Wait for login form using data-testid (implemented ✅) from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from config.config import SHORT_WAIT try: WebDriverWait(self.driver, SHORT_WAIT).until( EC.visibility_of_element_located(self.IDENTIFIER_INPUT) ) except Exception as e: current_url = self.driver.current_url page_title = self.driver.title raise Exception(f"Login form not found. URL: {current_url}, Title: {page_title}, Error: {e}") # Form is ready - fill immediately (no waits) self.enter_identifier(identifier) self.enter_password(password) if remember_me: self.check_remember_me() # Click submit self.click_submit() # Immediately wait for either success (navigation) or error (toast) from config.config import MEDIUM_WAIT # Wait for either navigation OR error toast (whichever appears first) try: WebDriverWait(self.driver, MEDIUM_WAIT).until( lambda d: "/dashboard" in d.current_url or "/student" in d.current_url or "mandatory_reset" in d.page_source.lower() or len(d.find_elements(By.CSS_SELECTOR, "[data-testid='student_login__error_toast']")) > 0 ) except: pass # Check what happened # Check result current_url = self.driver.current_url # Check if still on login page (root URL or contains "login") is_on_login_page = ( current_url.rstrip("/") == BASE_URL.rstrip("/") or current_url.rstrip("/") == f"{BASE_URL}/" or "login" in current_url.lower() ) if is_on_login_page: # Still on login page - check for error toast if self.is_error_visible(): error_msg = self.get_error_message() # SMART FALLBACK: If login failed with Excel password, try Admin@123 # This handles students who have already reset their password if password != TEST_NEW_PASSWORD: print(f"🔄 Excel password failed, trying Admin@123 (reset password)...") time.sleep(3) # Clear form and retry with Admin@123 self.enter_identifier(identifier) self.enter_password(TEST_NEW_PASSWORD) if remember_me: self.check_remember_me() self.click_submit() # Wait for navigation or error try: WebDriverWait(self.driver, MEDIUM_WAIT).until( lambda d: "/dashboard" in d.current_url or "/student" in d.current_url or "mandatory_reset" in d.page_source.lower() or len(d.find_elements(By.CSS_SELECTOR, "[data-testid='student_login__error_toast']")) > 0 ) except: pass # Check result again - minimal wait for navigation (animation-aware) # Animation: 300ms + 50ms padding = 350ms time.sleep(0.35) current_url = self.driver.current_url # Check if we successfully navigated away from login page is_on_login_page = ( current_url.rstrip("/") == BASE_URL.rstrip("/") or current_url.rstrip("/") == f"{BASE_URL}/" or "login" in current_url.lower() ) if not is_on_login_page: # Successfully navigated away - login successful! print(f"✅ Login successful with Admin@123 (password was already reset)!") # Update password tracker for future reference from utils.password_tracker import password_tracker password_tracker.update_password(identifier, TEST_NEW_PASSWORD) # Check if password reset modal appeared (shouldn't, but verify) from pages.mandatory_reset_page import MandatoryResetPage reset_page = MandatoryResetPage(self.driver) if reset_page.is_modal_present(): print("⚠️ Password reset modal appeared after login - this shouldn't happen with Admin@123") return # Success! else: # Still on login page - check for error if self.is_error_visible(): error_msg2 = self.get_error_message() raise Exception(f"Login failed with both passwords. Excel password: {error_msg}, Admin@123: {error_msg2}. URL: {current_url}") else: raise Exception(f"Login failed with both passwords. Excel password error: {error_msg}. URL: {current_url}") else: # Already tried Admin@123, no fallback raise Exception(f"Login failed with Admin@123: {error_msg}. URL: {current_url}") # No error but still on login - might be slow navigation, wait a bit more try: WebDriverWait(self.driver, SHORT_WAIT).until( lambda d: "/dashboard" in d.current_url or "/student" in d.current_url ) except: raise Exception(f"Login failed - still on login page. URL: {current_url}") else: # Success - navigated away from login # Check if password reset modal appeared (for first-time login) from pages.mandatory_reset_page import MandatoryResetPage reset_page = MandatoryResetPage(self.driver) if reset_page.is_modal_present(): print("✅ Password reset modal detected after login - this is expected for first-time login") # Note: The test should handle password reset, not the login method # This is just for logging/information