309 lines
13 KiB
Python
309 lines
13 KiB
Python
"""
|
|
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
|