256 lines
9.7 KiB
Python
256 lines
9.7 KiB
Python
"""
|
|
Smart Wait Optimizer - World-Class Precision
|
|
|
|
Optimizes wait times by using intelligent detection:
|
|
1. Password tracker - if password was reset, skip reset modal check
|
|
2. Profile completion - if profile is 100%, skip incomplete modal check
|
|
3. Fast detection using robust locators (data-testid)
|
|
4. Animation-aware waits (150ms FAST, 300ms NORMAL, 500ms SLOW)
|
|
5. Tiny padding (50-100ms) for safety
|
|
"""
|
|
from selenium.webdriver.common.by import By
|
|
from selenium.webdriver.support.ui import WebDriverWait
|
|
from selenium.webdriver.support import expected_conditions as EC
|
|
from utils.password_tracker import password_tracker
|
|
from config.config import TEST_NEW_PASSWORD
|
|
import time
|
|
|
|
|
|
class SmartWaitOptimizer:
|
|
"""
|
|
World-class wait optimizer that eliminates unnecessary waits.
|
|
|
|
Uses intelligent detection to skip modal checks when not needed:
|
|
- Password reset modal: Skip if password was already reset
|
|
- Profile incomplete modal: Skip if profile is already complete
|
|
"""
|
|
|
|
# Animation durations from UI source code
|
|
ANIMATION_FAST = 0.15 # 150ms
|
|
ANIMATION_NORMAL = 0.3 # 300ms
|
|
ANIMATION_SLOW = 0.5 # 500ms
|
|
SAFETY_PADDING = 0.05 # 50ms safety padding
|
|
|
|
# Total wait times (animation + padding)
|
|
MODAL_DETECTION_TIMEOUT = ANIMATION_NORMAL + SAFETY_PADDING # 350ms
|
|
QUICK_CHECK_TIMEOUT = ANIMATION_FAST + SAFETY_PADDING # 200ms
|
|
|
|
@staticmethod
|
|
def should_check_password_reset(cpid: str, password_used: str) -> bool:
|
|
"""
|
|
Determine if password reset modal check is needed.
|
|
|
|
Logic:
|
|
- If TEST_NEW_PASSWORD was used to login, reset modal won't appear
|
|
- If password tracker shows password was reset, skip check
|
|
|
|
Args:
|
|
cpid: Student CPID
|
|
password_used: Password that was used for login
|
|
|
|
Returns:
|
|
bool: True if should check for reset modal, False to skip
|
|
"""
|
|
# If TEST_NEW_PASSWORD was used, reset modal won't appear
|
|
if password_used == TEST_NEW_PASSWORD:
|
|
return False
|
|
|
|
# Check password tracker - if password was reset, skip check
|
|
tracked_password = password_tracker._passwords.get(cpid)
|
|
if tracked_password == TEST_NEW_PASSWORD:
|
|
return False
|
|
|
|
# Otherwise, need to check
|
|
return True
|
|
|
|
@staticmethod
|
|
def should_check_profile_incomplete(driver) -> bool:
|
|
"""
|
|
Determine if profile incomplete modal check is needed.
|
|
|
|
Logic:
|
|
- Check if profile completion is already 100%
|
|
- If 100%, skip modal check
|
|
|
|
Args:
|
|
driver: WebDriver instance
|
|
|
|
Returns:
|
|
bool: True if should check for incomplete modal, False to skip
|
|
"""
|
|
try:
|
|
# Quick check for profile completion indicator
|
|
# Look for progress value in dashboard or profile editor
|
|
from selenium.webdriver.common.by import By
|
|
|
|
# Strategy 1: Check for 100% completion indicator
|
|
completion_indicators = [
|
|
"100%",
|
|
"Complete",
|
|
"Profile Complete",
|
|
"profile-complete"
|
|
]
|
|
|
|
page_text = driver.page_source.lower()
|
|
for indicator in completion_indicators:
|
|
if indicator.lower() in page_text:
|
|
# Found completion indicator - profile might be complete
|
|
# But we need to verify it's actually 100%
|
|
# This is a quick check, not definitive
|
|
pass
|
|
|
|
# Strategy 2: Check for profile incomplete modal data-testid
|
|
# If modal is already visible, we need to handle it
|
|
try:
|
|
incomplete_modal = driver.find_elements(
|
|
By.CSS_SELECTOR,
|
|
"[data-testid='profile_incomplete__modal']"
|
|
)
|
|
if incomplete_modal and any(m.is_displayed() for m in incomplete_modal):
|
|
return True # Modal is visible, need to handle
|
|
except:
|
|
pass
|
|
|
|
# Strategy 3: Check for profile editor page
|
|
# If we're on profile editor, profile might be incomplete
|
|
current_url = driver.current_url.lower()
|
|
if "/profile-builder" in current_url or "/profile" in current_url:
|
|
# On profile page - might be incomplete
|
|
# But if we're here, modal might have already been handled
|
|
return False # Skip check, we're already on profile page
|
|
|
|
# Default: Check for modal (safe approach)
|
|
return True
|
|
|
|
except Exception as e:
|
|
# On error, default to checking (safe approach)
|
|
print(f"⚠️ Error checking profile completion: {e}, will check modal")
|
|
return True
|
|
|
|
@staticmethod
|
|
def quick_check_modal_present(driver, modal_locator, timeout=None) -> bool:
|
|
"""
|
|
Quick check if modal is present using fast detection.
|
|
|
|
Uses:
|
|
- Fast animation timeout (200ms)
|
|
- Robust locator (data-testid)
|
|
- No unnecessary waits
|
|
|
|
Args:
|
|
driver: WebDriver instance
|
|
modal_locator: Locator tuple (By, selector)
|
|
timeout: Custom timeout (default: QUICK_CHECK_TIMEOUT)
|
|
|
|
Returns:
|
|
bool: True if modal is present and visible
|
|
"""
|
|
if timeout is None:
|
|
timeout = SmartWaitOptimizer.QUICK_CHECK_TIMEOUT
|
|
|
|
try:
|
|
# Fast check - wait only for animation + padding
|
|
element = WebDriverWait(driver, timeout).until(
|
|
EC.presence_of_element_located(modal_locator)
|
|
)
|
|
# Verify it's visible
|
|
return element.is_displayed()
|
|
except:
|
|
return False
|
|
|
|
@staticmethod
|
|
def wait_for_modal_animation(driver, modal_locator, timeout=None) -> bool:
|
|
"""
|
|
Wait for modal animation to complete.
|
|
|
|
Uses:
|
|
- Normal animation timeout (350ms)
|
|
- Waits for modal to be fully visible
|
|
- Includes safety padding
|
|
|
|
Args:
|
|
driver: WebDriver instance
|
|
modal_locator: Locator tuple (By, selector)
|
|
timeout: Custom timeout (default: MODAL_DETECTION_TIMEOUT)
|
|
|
|
Returns:
|
|
bool: True if modal is present and visible after animation
|
|
"""
|
|
if timeout is None:
|
|
timeout = SmartWaitOptimizer.MODAL_DETECTION_TIMEOUT
|
|
|
|
try:
|
|
# Wait for modal to appear and be visible
|
|
element = WebDriverWait(driver, timeout).until(
|
|
EC.visibility_of_element_located(modal_locator)
|
|
)
|
|
return element.is_displayed()
|
|
except:
|
|
return False
|
|
|
|
@staticmethod
|
|
def smart_wait_for_dashboard(driver, cpid: str, password_used: str, max_wait: float = 5.0):
|
|
"""
|
|
Smart wait for dashboard to load, with intelligent modal detection.
|
|
|
|
Optimizations:
|
|
1. Skip password reset check if password was already reset
|
|
2. Skip profile incomplete check if profile is complete
|
|
3. Use fast detection for modals
|
|
4. Minimal waits with animation-aware timing
|
|
|
|
Args:
|
|
driver: WebDriver instance
|
|
cpid: Student CPID
|
|
password_used: Password that was used for login
|
|
max_wait: Maximum wait time in seconds (default: 5.0)
|
|
"""
|
|
start_time = time.time()
|
|
|
|
# Wait for dashboard to load (navigation)
|
|
try:
|
|
WebDriverWait(driver, max_wait).until(
|
|
lambda d: "/dashboard" in d.current_url or "/student" in d.current_url
|
|
)
|
|
except:
|
|
pass # Continue even if timeout
|
|
|
|
# Quick check: Should we check for password reset modal?
|
|
if SmartWaitOptimizer.should_check_password_reset(cpid, password_used):
|
|
# Need to check - use fast detection
|
|
from pages.mandatory_reset_page import MandatoryResetPage
|
|
reset_page = MandatoryResetPage(driver)
|
|
# Quick check (200ms)
|
|
if SmartWaitOptimizer.quick_check_modal_present(
|
|
driver,
|
|
reset_page.MODAL,
|
|
timeout=SmartWaitOptimizer.QUICK_CHECK_TIMEOUT
|
|
):
|
|
# Modal is present - wait for animation to complete
|
|
time.sleep(SmartWaitOptimizer.ANIMATION_NORMAL + SmartWaitOptimizer.SAFETY_PADDING)
|
|
else:
|
|
# Skip check - password was already reset
|
|
print(f"⚡ Skipping password reset check (password already reset for {cpid})")
|
|
|
|
# Quick check: Should we check for profile incomplete modal?
|
|
if SmartWaitOptimizer.should_check_profile_incomplete(driver):
|
|
# Need to check - use fast detection
|
|
from pages.profile_incomplete_page import ProfileIncompletePage
|
|
profile_incomplete = ProfileIncompletePage(driver)
|
|
# Quick check (200ms)
|
|
if SmartWaitOptimizer.quick_check_modal_present(
|
|
driver,
|
|
profile_incomplete.MODAL,
|
|
timeout=SmartWaitOptimizer.QUICK_CHECK_TIMEOUT
|
|
):
|
|
# Modal is present - wait for animation to complete
|
|
time.sleep(SmartWaitOptimizer.ANIMATION_NORMAL + SmartWaitOptimizer.SAFETY_PADDING)
|
|
else:
|
|
# Skip check - profile is complete
|
|
print(f"⚡ Skipping profile incomplete check (profile already complete)")
|
|
|
|
elapsed = time.time() - start_time
|
|
print(f"⚡ Smart dashboard wait completed in {elapsed:.3f}s")
|
|
|
|
|