547 lines
24 KiB
Python
Executable File
547 lines
24 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Complete Student Journey Test Script - OPTIMIZED
|
|
|
|
Systematic end-to-end test covering the entire student flow:
|
|
1. Login
|
|
2. Password Reset (if new student) → Admin@123
|
|
3. Profile Completion (if not 100%)
|
|
4. Start Assessment
|
|
5. Complete All Domains/Phases
|
|
6. Provide All Feedbacks
|
|
7. Complete Assessment
|
|
|
|
OPTIMIZED: Uses smart explicit waits instead of time.sleep()
|
|
"""
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
project_root = Path(__file__).parent.parent
|
|
sys.path.insert(0, str(project_root))
|
|
|
|
from pages.login_page import LoginPage
|
|
from pages.mandatory_reset_page import MandatoryResetPage
|
|
from pages.profile_incomplete_page import ProfileIncompletePage
|
|
from pages.profile_editor_page import ProfileEditorPage
|
|
from pages.student_nav_page import StudentNavPage
|
|
from pages.assessments_page import AssessmentsPage
|
|
from pages.domains_page import DomainsPage
|
|
from pages.domain_assessment_page import DomainAssessmentPage
|
|
from pages.domain_feedback_page import DomainFeedbackPage
|
|
from pages.feedback_survey_page import FeedbackSurveyPage
|
|
from utils.driver_manager import DriverManager
|
|
from config.config import BASE_URL, TEST_NEW_PASSWORD
|
|
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
|
|
|
|
|
|
def smart_wait_for_url_change(driver, current_url, timeout=MEDIUM_WAIT):
|
|
"""Wait for URL to change from current URL"""
|
|
wait = WebDriverWait(driver, timeout)
|
|
wait.until(lambda d: d.current_url != current_url)
|
|
|
|
|
|
def complete_student_journey(student_cpid, student_password, headless=False):
|
|
"""
|
|
Complete student journey test - OPTIMIZED with smart waits
|
|
|
|
Args:
|
|
student_cpid: Student CPID
|
|
student_password: Student password
|
|
headless: Run in headless mode
|
|
"""
|
|
driver = None
|
|
try:
|
|
print(f"\n{'='*80}")
|
|
print(f"COMPLETE STUDENT JOURNEY TEST (OPTIMIZED)")
|
|
print(f"{'='*80}")
|
|
print(f"Student: {student_cpid}")
|
|
print(f"{'='*80}\n")
|
|
|
|
# Initialize driver
|
|
driver_manager = DriverManager()
|
|
driver = driver_manager.get_driver(headless=headless)
|
|
|
|
# STEP 1: LOGIN
|
|
print("[STEP 1] LOGIN")
|
|
print("-" * 80)
|
|
login_page = LoginPage(driver)
|
|
login_page.login(identifier=student_cpid, password=student_password)
|
|
# Wait for navigation (handled by login method)
|
|
print(f"✅ Login successful - URL: {driver.current_url}\n")
|
|
|
|
# STEP 2: PASSWORD RESET (if new student)
|
|
print("[STEP 2] PASSWORD RESET CHECK")
|
|
print("-" * 80)
|
|
reset_page = MandatoryResetPage(driver)
|
|
if reset_page.is_modal_present():
|
|
print(" ✅ Password reset modal found (new student)")
|
|
print(f" Resetting password to: {TEST_NEW_PASSWORD}")
|
|
reset_page.reset_password(
|
|
current_password=student_password,
|
|
new_password=TEST_NEW_PASSWORD,
|
|
confirm_password=TEST_NEW_PASSWORD
|
|
)
|
|
print(" ✅ Password reset completed")
|
|
student_password = TEST_NEW_PASSWORD
|
|
else:
|
|
print(" ✅ No password reset required (existing student)")
|
|
print()
|
|
|
|
# STEP 3: PROFILE COMPLETION (if not 100%)
|
|
print("[STEP 3] PROFILE COMPLETION CHECK")
|
|
print("-" * 80)
|
|
profile_incomplete = ProfileIncompletePage(driver)
|
|
if profile_incomplete.is_modal_present():
|
|
progress = profile_incomplete.get_progress_value()
|
|
print(f" Profile incomplete - Current Progress: {progress}")
|
|
print(" Clicking Complete Profile button...")
|
|
profile_incomplete.click_complete()
|
|
# Wait for navigation (handled by click_complete)
|
|
print(f" Navigated to: {driver.current_url}")
|
|
|
|
# Complete profile to 100%
|
|
print("\n [STEP 3.1] Completing Profile to 100%...")
|
|
profile_editor = ProfileEditorPage(driver)
|
|
profile_editor.wait_for_page_load()
|
|
|
|
# Check progress
|
|
progress = profile_editor.get_progress_value()
|
|
print(f" Current Profile Progress: {progress}")
|
|
|
|
if "100%" in progress:
|
|
print(" ✅ Profile is 100% complete!")
|
|
else:
|
|
print(f" ⚠️ Profile is {progress} complete")
|
|
print(" Note: Profile completion automation requires student data from Excel")
|
|
print(" For now, profile can be completed manually or with process_students.py")
|
|
# Navigate back to dashboard
|
|
driver.get(f"{BASE_URL}/student/dashboard")
|
|
WebDriverWait(driver, SHORT_WAIT).until(
|
|
EC.url_contains("/dashboard")
|
|
)
|
|
else:
|
|
print(" ✅ Profile already complete or modal not present")
|
|
print()
|
|
|
|
# STEP 4: NAVIGATE TO ASSESSMENTS
|
|
print("[STEP 4] NAVIGATE TO ASSESSMENTS")
|
|
print("-" * 80)
|
|
try:
|
|
nav = StudentNavPage(driver)
|
|
nav.click_assessments()
|
|
# Wait for navigation (handled by click_assessments)
|
|
print(f"✅ Navigated to Assessments via nav link - URL: {driver.current_url}\n")
|
|
except Exception as e:
|
|
print(f" ⚠️ Could not click nav link: {e}")
|
|
print(" Trying direct navigation to assessments...")
|
|
# Fallback: Navigate directly
|
|
from config.config import ASSESSMENTS_URL
|
|
driver.get(ASSESSMENTS_URL)
|
|
WebDriverWait(driver, SHORT_WAIT).until(
|
|
EC.url_contains("/assessments")
|
|
)
|
|
print(f"✅ Navigated to Assessments directly - URL: {driver.current_url}\n")
|
|
|
|
# STEP 5: START ASSESSMENT
|
|
print("[STEP 5] START ASSESSMENT")
|
|
print("-" * 80)
|
|
assessments_page = AssessmentsPage(driver)
|
|
assessments_page.wait_for_page_load()
|
|
|
|
# Get available assessments
|
|
try:
|
|
assessment_ids = assessments_page.get_assessment_ids()
|
|
print(f" Found {len(assessment_ids)} assessment(s): {assessment_ids}")
|
|
except Exception as e:
|
|
print(f" ⚠️ Could not get assessment IDs: {e}")
|
|
print(" Trying to find assessment cards manually...")
|
|
assessment_ids = []
|
|
try:
|
|
cards = assessments_page.get_all_assessment_cards()
|
|
print(f" Found {len(cards)} assessment card(s)")
|
|
if cards:
|
|
import re
|
|
test_id = cards[0].get_attribute("data-testid")
|
|
if test_id:
|
|
match = re.search(r'assessment_card__(\d+)', test_id)
|
|
if match:
|
|
assessment_ids = [match.group(1)]
|
|
except Exception as e2:
|
|
print(f" ⚠️ Error finding cards: {e2}")
|
|
|
|
if not assessment_ids:
|
|
print(" ⚠️ No assessments available")
|
|
if not headless:
|
|
print(" Keeping browser open for 30 seconds for inspection...")
|
|
import time
|
|
time.sleep(30) # Only use sleep for final inspection delay
|
|
return
|
|
|
|
# Start first available assessment
|
|
first_assessment_id = assessment_ids[0]
|
|
print(f" Starting assessment ID: {first_assessment_id}")
|
|
try:
|
|
current_url = driver.current_url
|
|
assessments_page.click_begin_assessment(first_assessment_id)
|
|
# Wait for URL change (handled by click_begin_assessment)
|
|
print(f"✅ Assessment started - URL: {driver.current_url}\n")
|
|
except Exception as e:
|
|
print(f" ⚠️ Error starting assessment: {e}")
|
|
if not headless:
|
|
print(" Keeping browser open for 30 seconds for inspection...")
|
|
import time
|
|
time.sleep(30) # Only use sleep for final inspection delay
|
|
return
|
|
|
|
# STEP 6: COMPLETE ALL DOMAINS
|
|
print("[STEP 6] COMPLETE ALL DOMAINS")
|
|
print("-" * 80)
|
|
domains_page = DomainsPage(driver)
|
|
domains_page.wait_for_page_load()
|
|
|
|
# Get all domains
|
|
try:
|
|
domain_ids = domains_page.get_all_domain_ids()
|
|
print(f" Found {len(domain_ids)} domain(s): {domain_ids}")
|
|
except Exception as e:
|
|
print(f" ⚠️ Could not get domain IDs: {e}")
|
|
if not headless:
|
|
print(" Keeping browser open for 30 seconds for inspection...")
|
|
import time
|
|
time.sleep(30) # Only use sleep for final inspection delay
|
|
return
|
|
|
|
if not domain_ids:
|
|
print(" ⚠️ No domains available")
|
|
return
|
|
|
|
for idx, domain_id in enumerate(domain_ids, 1):
|
|
print(f"\n [DOMAIN {idx}/{len(domain_ids)}] Domain ID: {domain_id}")
|
|
print(" " + "-" * 76)
|
|
|
|
# Check if domain is unlocked
|
|
try:
|
|
if domains_page.is_domain_locked(domain_id):
|
|
print(f" ⚠️ Domain {domain_id} is locked - skipping")
|
|
continue
|
|
except:
|
|
print(f" ⚠️ Could not check lock status - trying to proceed...")
|
|
|
|
# Start domain assessment
|
|
print(f" Starting domain assessment...")
|
|
try:
|
|
current_url = driver.current_url
|
|
domains_page.click_domain_action(domain_id)
|
|
# Wait for URL change (handled by click_domain_action)
|
|
print(f" ✅ Domain assessment started")
|
|
except Exception as e:
|
|
print(f" ⚠️ Error starting domain: {e}")
|
|
continue
|
|
|
|
# STEP 6.1: COMPLETE DOMAIN ASSESSMENT
|
|
print(f" [STEP 6.1] Completing Domain Assessment...")
|
|
domain_assessment = DomainAssessmentPage(driver)
|
|
domain_assessment.wait_for_page_load()
|
|
|
|
# Dismiss guidance modal if present (non-blocking)
|
|
try:
|
|
domain_assessment.dismiss_guidance()
|
|
print(f" Dismissed guidance modal if present")
|
|
except:
|
|
pass
|
|
|
|
# Answer all questions in the domain
|
|
questions_answered = 0
|
|
max_questions = 50 # Safety limit
|
|
question_attempts = 0
|
|
|
|
while question_attempts < max_questions:
|
|
question_attempts += 1
|
|
print(f" [Question {question_attempts}] Processing...")
|
|
|
|
# Get current question ID
|
|
current_question_id = domain_assessment.get_current_question_id()
|
|
if not current_question_id:
|
|
# Try to find question on page
|
|
try:
|
|
questions = driver.find_elements(By.CSS_SELECTOR, "[data-testid^='domain_question__']")
|
|
if questions:
|
|
test_id = questions[0].get_attribute("data-testid")
|
|
if test_id:
|
|
import re
|
|
match = re.search(r'domain_question__(\d+)', test_id)
|
|
if match:
|
|
current_question_id = match.group(1)
|
|
except:
|
|
pass
|
|
|
|
if not current_question_id:
|
|
print(f" ⚠️ No question found - may have completed all questions")
|
|
break
|
|
|
|
print(f" Question ID: {current_question_id}")
|
|
|
|
# Try to answer the question (try different question types)
|
|
answered = False
|
|
try:
|
|
# Try multiple choice (option a)
|
|
try:
|
|
domain_assessment.answer_multiple_choice(current_question_id, "a")
|
|
print(f" ✅ Answered: Multiple choice (option a)")
|
|
answered = True
|
|
except:
|
|
# Try true/false
|
|
try:
|
|
domain_assessment.answer_true_false(current_question_id, True)
|
|
print(f" ✅ Answered: True/False (True)")
|
|
answered = True
|
|
except:
|
|
# Try rating
|
|
try:
|
|
domain_assessment.answer_rating(current_question_id, 3)
|
|
print(f" ✅ Answered: Rating (3)")
|
|
answered = True
|
|
except:
|
|
# Try open ended
|
|
try:
|
|
domain_assessment.answer_open_ended(current_question_id, "Automated test response for question.")
|
|
print(f" ✅ Answered: Open-ended")
|
|
answered = True
|
|
except:
|
|
# Try matrix (first cell)
|
|
try:
|
|
domain_assessment.answer_matrix(current_question_id, 0, 0)
|
|
print(f" ✅ Answered: Matrix (0,0)")
|
|
answered = True
|
|
except Exception as e:
|
|
print(f" ⚠️ Could not answer question: {e}")
|
|
except Exception as e:
|
|
print(f" ⚠️ Error answering question: {e}")
|
|
|
|
if answered:
|
|
questions_answered += 1
|
|
|
|
# Try to navigate to next question
|
|
try:
|
|
# Check if next button is visible and clickable
|
|
next_button = driver.find_elements(By.CSS_SELECTOR, "[data-testid='domain_assessment__next_button']")
|
|
if next_button and next_button[0].is_displayed() and next_button[0].is_enabled():
|
|
domain_assessment.click_next()
|
|
# Wait for next question to load (handled by click_next)
|
|
print(f" Navigated to next question")
|
|
continue
|
|
else:
|
|
# Check if submit button is visible
|
|
submit_button = driver.find_elements(By.CSS_SELECTOR, "[data-testid='domain_assessment__submit_button']")
|
|
if submit_button and submit_button[0].is_displayed() and submit_button[0].is_enabled():
|
|
print(f" All questions answered. Submitting...")
|
|
domain_assessment.click_submit()
|
|
# Wait for submit modal (handled by click_submit)
|
|
break
|
|
else:
|
|
print(f" ⚠️ No next or submit button found")
|
|
break
|
|
except Exception as e:
|
|
print(f" ⚠️ Error navigating: {e}")
|
|
break
|
|
|
|
print(f" ✅ Answered {questions_answered} question(s)")
|
|
|
|
# Handle submit confirmation modal if present
|
|
try:
|
|
submit_modal = driver.find_elements(By.CSS_SELECTOR, "[data-testid='domain_assessment__submit_modal']")
|
|
if submit_modal:
|
|
print(f" Submit confirmation modal found - confirming...")
|
|
domain_assessment.confirm_submit()
|
|
# Wait for success modal (handled by confirm_submit)
|
|
except Exception as e:
|
|
print(f" ⚠️ Error handling submit modal: {e}")
|
|
|
|
# Wait for success modal (non-blocking, short wait)
|
|
try:
|
|
success_modal = WebDriverWait(driver, SHORT_WAIT).until(
|
|
EC.presence_of_element_located((By.CSS_SELECTOR, "[data-testid='domain_assessment__success_modal']"))
|
|
)
|
|
print(f" ✅ Domain assessment submitted successfully")
|
|
# Wait for modal to auto-close or redirect (short wait)
|
|
WebDriverWait(driver, SHORT_WAIT).until(
|
|
EC.invisibility_of_element_located((By.CSS_SELECTOR, "[data-testid='domain_assessment__success_modal']"))
|
|
)
|
|
except:
|
|
# Success modal might have already closed or not present
|
|
pass
|
|
|
|
# STEP 6.2: PROVIDE DOMAIN FEEDBACK
|
|
print(f" [STEP 6.2] Providing Domain Feedback...")
|
|
domain_feedback = DomainFeedbackPage(driver)
|
|
|
|
try:
|
|
# Wait for feedback modal with short timeout
|
|
if domain_feedback.is_modal_present():
|
|
print(f" Domain feedback modal found")
|
|
|
|
# Submit feedback with answers
|
|
try:
|
|
domain_feedback.submit_feedback(
|
|
question1_yes=True,
|
|
question1_text="Automated feedback response for testing.",
|
|
question2_text="This is automated feedback for testing purposes."
|
|
)
|
|
# Wait for modal close and redirect (handled by submit_feedback)
|
|
print(f" ✅ Domain feedback submitted")
|
|
except Exception as e:
|
|
print(f" ⚠️ Error submitting feedback: {e}")
|
|
# Try skip as fallback
|
|
try:
|
|
domain_feedback.skip_feedback()
|
|
print(f" Skipped feedback")
|
|
except:
|
|
pass
|
|
else:
|
|
print(f" ⚠️ No domain feedback modal found - may have auto-closed")
|
|
except Exception as e:
|
|
print(f" ⚠️ Error handling feedback: {e}")
|
|
|
|
# Navigate back to domains page
|
|
print(f" Returning to domains page...")
|
|
try:
|
|
current_url = driver.current_url
|
|
driver.back()
|
|
# Wait for URL change
|
|
WebDriverWait(driver, SHORT_WAIT).until(
|
|
lambda d: d.current_url != current_url
|
|
)
|
|
domains_page = DomainsPage(driver)
|
|
domains_page.wait_for_page_load()
|
|
except Exception as e:
|
|
print(f" ⚠️ Error returning to domains: {e}")
|
|
# Try direct navigation
|
|
try:
|
|
current_url = driver.current_url
|
|
base_url = current_url.split('/assessment/')[0]
|
|
assessment_id = current_url.split('/assessment/')[1].split('/')[0]
|
|
driver.get(f"{base_url}/assessment/{assessment_id}/domains")
|
|
domains_page = DomainsPage(driver)
|
|
domains_page.wait_for_page_load()
|
|
except:
|
|
pass
|
|
|
|
print(f"\n✅ All domains processed!\n")
|
|
|
|
# STEP 7: FINAL FEEDBACK (from domains page)
|
|
print("[STEP 7] FINAL FEEDBACK")
|
|
print("-" * 80)
|
|
|
|
# Check for final feedback modal on domains page
|
|
try:
|
|
if domains_page.is_final_feedback_modal_present():
|
|
print(" Final feedback modal found (from domains page)")
|
|
domains_page.fill_final_feedback(
|
|
question1_yes=True,
|
|
question1_reason="Automated final feedback response.",
|
|
question2_text="This is automated final feedback for testing purposes."
|
|
)
|
|
# Wait handled by fill_final_feedback
|
|
print(" ✅ Final feedback submitted")
|
|
else:
|
|
print(" ⚠️ Final feedback modal not found on domains page")
|
|
except Exception as e:
|
|
print(f" ⚠️ Error handling final feedback from domains page: {e}")
|
|
|
|
# Also check for feedback survey modal
|
|
print("\n[STEP 7.1] FEEDBACK SURVEY")
|
|
print("-" * 80)
|
|
|
|
try:
|
|
feedback_survey = FeedbackSurveyPage(driver)
|
|
|
|
# Check for overall modal
|
|
if feedback_survey.is_overall_modal_present():
|
|
print(" Final feedback modal (overall) found")
|
|
|
|
try:
|
|
feedback_survey.set_overall_rating(4)
|
|
print(" Selected overall rating: 4")
|
|
feedback_survey.submit_feedback()
|
|
# Wait handled by submit_feedback
|
|
print(" ✅ Overall feedback submitted")
|
|
except Exception as e:
|
|
print(f" ⚠️ Error with overall feedback: {e}")
|
|
|
|
# Check for per-question modal
|
|
if feedback_survey.is_per_question_modal_present():
|
|
print(" Final feedback modal (per-question) found")
|
|
|
|
try:
|
|
question_ratings = driver.find_elements(By.CSS_SELECTOR, "[data-testid^='feedback_survey__question_']")
|
|
question_ids = set()
|
|
for element in question_ratings:
|
|
test_id = element.get_attribute("data-testid")
|
|
if test_id:
|
|
import re
|
|
match = re.search(r'question_(\d+)_rating', test_id)
|
|
if match:
|
|
question_ids.add(match.group(1))
|
|
|
|
for q_id in question_ids:
|
|
try:
|
|
feedback_survey.set_question_rating(q_id, 4)
|
|
feedback_survey.set_question_comment(q_id, "Automated feedback comment")
|
|
print(f" Provided feedback for question {q_id}")
|
|
except:
|
|
pass
|
|
|
|
feedback_survey.submit_feedback()
|
|
# Wait handled by submit_feedback
|
|
print(" ✅ Per-question feedback submitted")
|
|
except Exception as e:
|
|
print(f" ⚠️ Error with per-question feedback: {e}")
|
|
|
|
if not feedback_survey.is_overall_modal_present() and not feedback_survey.is_per_question_modal_present():
|
|
print(" ⚠️ Final feedback modal not available")
|
|
except Exception as e:
|
|
print(f" ⚠️ Error handling final feedback: {e}")
|
|
|
|
print(f"\n{'='*80}")
|
|
print("COMPLETE JOURNEY TEST FINISHED!")
|
|
print(f"{'='*80}\n")
|
|
|
|
# Keep browser open for inspection (only if not headless)
|
|
if not headless:
|
|
print("Keeping browser open for 60 seconds for inspection...")
|
|
import time
|
|
time.sleep(60) # Only use sleep for final inspection delay
|
|
|
|
except Exception as e:
|
|
print(f"\n❌ ERROR: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
if driver and not headless:
|
|
print("\nKeeping browser open for 60 seconds for debugging...")
|
|
import time
|
|
time.sleep(60) # Only use sleep for final inspection delay
|
|
finally:
|
|
if driver:
|
|
driver.quit()
|
|
|
|
|
|
def main():
|
|
"""Main entry point"""
|
|
import argparse
|
|
|
|
parser = argparse.ArgumentParser(description='Complete student journey test (OPTIMIZED)')
|
|
parser.add_argument('--cpid', type=str, required=True, help='Student CPID')
|
|
parser.add_argument('--password', type=str, required=True, help='Student password')
|
|
parser.add_argument('--headless', action='store_true', help='Run in headless mode')
|
|
|
|
args = parser.parse_args()
|
|
|
|
complete_student_journey(args.cpid, args.password, headless=args.headless)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|