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

306 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
from config.config import LOGIN_URL, 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)
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