452 lines
20 KiB
Python
452 lines
20 KiB
Python
"""
|
|
DOM Verification Script - Verify data-testid Implementation
|
|
|
|
Purpose: Verify actual DOM implementation against UI team's claims
|
|
Method: Inspect actual DOM structure at runtime
|
|
Approach: World-class systematic verification with zero tolerance for assumptions
|
|
"""
|
|
import sys
|
|
import os
|
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
|
|
|
from selenium import webdriver
|
|
from selenium.webdriver.chrome.service import Service
|
|
from selenium.webdriver.chrome.options import Options
|
|
from selenium.webdriver.common.by import By
|
|
from selenium.webdriver.support.ui import WebDriverWait
|
|
from selenium.webdriver.support import expected_conditions as EC
|
|
from pages.login_page import LoginPage
|
|
from pages.mandatory_reset_page import MandatoryResetPage
|
|
from pages.profile_editor_page import ProfileEditorPage
|
|
from config.config import TEST_USERNAME, TEST_PASSWORD, BASE_URL
|
|
import time
|
|
import json
|
|
from collections import defaultdict
|
|
|
|
|
|
class DataTestIdVerifier:
|
|
"""World-Class DOM Verifier for data-testid Attributes"""
|
|
|
|
def __init__(self, driver):
|
|
self.driver = driver
|
|
self.results = {
|
|
'profile_editor': {
|
|
'found': [],
|
|
'missing': [],
|
|
'expected': [],
|
|
'unexpected': []
|
|
},
|
|
'mandatory_reset': {
|
|
'found': [],
|
|
'missing': [],
|
|
'expected': [],
|
|
'unexpected': []
|
|
}
|
|
}
|
|
|
|
def verify_profile_editor_attributes(self):
|
|
"""Verify all Profile Editor data-testid attributes"""
|
|
print("\n" + "="*80)
|
|
print("🔍 VERIFYING: Profile Editor Attributes")
|
|
print("="*80)
|
|
|
|
# First, login to ensure we have access
|
|
print("\n📋 Logging in first...")
|
|
self.driver.get(BASE_URL + "/login")
|
|
time.sleep(2)
|
|
|
|
login_page = LoginPage(self.driver)
|
|
login_page.login(identifier=TEST_USERNAME, password=TEST_PASSWORD)
|
|
time.sleep(3)
|
|
|
|
# Check for password reset modal and handle it
|
|
reset_page = MandatoryResetPage(self.driver)
|
|
if reset_page.is_modal_present():
|
|
print("⚠️ Password reset modal detected - handling it...")
|
|
try:
|
|
reset_page.click_continue()
|
|
time.sleep(2)
|
|
# Use old password for reset
|
|
reset_page.reset_password(
|
|
current_password=TEST_PASSWORD,
|
|
new_password=TEST_PASSWORD # Keep same for now
|
|
)
|
|
time.sleep(3)
|
|
except:
|
|
print(" ⚠️ Could not handle password reset - continuing anyway")
|
|
|
|
# Navigate to profile editor
|
|
print("\n📋 Navigating to Profile Editor...")
|
|
self.driver.get(BASE_URL + "/student/profile-builder")
|
|
time.sleep(5) # Give more time for page load
|
|
|
|
# Check if we're actually on the profile editor page
|
|
current_url = self.driver.current_url
|
|
print(f" Current URL: {current_url}")
|
|
|
|
if "profile" not in current_url.lower():
|
|
print(" ⚠️ May not be on profile editor page - checking page content...")
|
|
page_source = self.driver.page_source
|
|
if "profile" in page_source.lower() or "editor" in page_source.lower():
|
|
print(" ✅ Profile editor content detected in page source")
|
|
else:
|
|
print(" ❌ Profile editor content NOT found - page may have redirected")
|
|
|
|
# Wait for page to fully load
|
|
print("\n⏳ Waiting for page to fully load...")
|
|
try:
|
|
WebDriverWait(self.driver, 10).until(
|
|
lambda d: d.execute_script("return document.readyState") == "complete"
|
|
)
|
|
time.sleep(2) # Additional wait for React to render
|
|
except:
|
|
print(" ⚠️ Page load timeout, continuing anyway...")
|
|
|
|
# Try to find any profile_editor attributes to confirm page is loaded
|
|
initial_check = self.driver.find_elements(By.CSS_SELECTOR, "[data-testid^='profile_editor__']")
|
|
print(f" Initial check: Found {len(initial_check)} profile_editor attributes")
|
|
|
|
# Expected attributes from UI team's implementation status
|
|
# Based on 10_FINAL_IMPLEMENTATION_STATUS.md
|
|
expected_attributes = {
|
|
# Page-level (3 according to UI team, not 4)
|
|
'profile_editor__page': 'Page container',
|
|
'profile_editor__progress_value': 'Progress value',
|
|
'profile_editor__back_button': 'Back button',
|
|
# Note: UI team doesn't mention missing_fields_toggle
|
|
|
|
# Tab navigation
|
|
'profile_editor__tabs_container': 'Tabs container',
|
|
'profile_editor__tabs_scroll_left_button': 'Scroll left button',
|
|
'profile_editor__tabs_scroll_right_button': 'Scroll right button',
|
|
'profile_editor__tab_personal_information': 'Tab: Personal Information',
|
|
'profile_editor__tab_contact_information': 'Tab: Contact Information',
|
|
'profile_editor__tab_parent_guardian': 'Tab: Parent/Guardian',
|
|
'profile_editor__tab_education_details': 'Tab: Education Details',
|
|
'profile_editor__tab_focus_areas': 'Tab: Focus Areas',
|
|
'profile_editor__tab_self_assessment': 'Tab: Self-Assessment',
|
|
'profile_editor__tab_hobbies_clubs': 'Tab: Hobbies & Clubs',
|
|
'profile_editor__tab_achievements': 'Tab: Achievements',
|
|
'profile_editor__tab_expectations': 'Tab: Expectations',
|
|
|
|
# Personal Information (11 according to UI team, includes age_input)
|
|
'profile_editor__first_name_input': 'First Name input',
|
|
'profile_editor__last_name_input': 'Last Name input',
|
|
'profile_editor__gender_select': 'Gender select',
|
|
'profile_editor__dob_input': 'DOB input',
|
|
'profile_editor__age_input': 'Age input', # UI team mentions this
|
|
'profile_editor__nationality_input': 'Nationality input',
|
|
'profile_editor__language_input': 'Language input',
|
|
'profile_editor__student_id_input': 'Student ID input',
|
|
'profile_editor__student_cpid_input': 'Student CPID input',
|
|
'profile_editor__specially_abled_checkbox': 'Specially Abled checkbox',
|
|
'profile_editor__specially_abled_details_textarea': 'Specially Abled details',
|
|
|
|
# Contact Information
|
|
'profile_editor__email_input': 'Email input',
|
|
'profile_editor__phone_input': 'Phone input',
|
|
'profile_editor__address_input': 'Address input',
|
|
'profile_editor__city_input': 'City input',
|
|
'profile_editor__state_input': 'State input',
|
|
'profile_editor__zip_code_input': 'ZIP Code input',
|
|
'profile_editor__native_state_input': 'Native State input',
|
|
|
|
# Navigation buttons
|
|
'profile_editor__prev_button': 'Previous button',
|
|
'profile_editor__next_button': 'Next button',
|
|
'profile_editor__cancel_button': 'Cancel button',
|
|
'profile_editor__save_button': 'Save button',
|
|
}
|
|
|
|
# Find all profile_editor attributes in DOM
|
|
print("\n🔍 Searching for profile_editor attributes in DOM...")
|
|
all_elements = self.driver.find_elements(By.CSS_SELECTOR, "[data-testid^='profile_editor__']")
|
|
found_attributes = set()
|
|
|
|
for elem in all_elements:
|
|
try:
|
|
test_id = elem.get_attribute('data-testid')
|
|
if test_id:
|
|
found_attributes.add(test_id)
|
|
self.results['profile_editor']['found'].append({
|
|
'attribute': test_id,
|
|
'tag': elem.tag_name,
|
|
'visible': elem.is_displayed()
|
|
})
|
|
except:
|
|
pass
|
|
|
|
# Check for expected attributes
|
|
print(f"\n📊 Found {len(found_attributes)} profile_editor attributes")
|
|
|
|
for attr, description in expected_attributes.items():
|
|
if attr in found_attributes:
|
|
print(f" ✅ {attr} - {description}")
|
|
self.results['profile_editor']['expected'].append(attr)
|
|
else:
|
|
print(f" ❌ {attr} - {description} - MISSING")
|
|
self.results['profile_editor']['missing'].append({
|
|
'attribute': attr,
|
|
'description': description
|
|
})
|
|
|
|
# Check for unexpected attributes
|
|
unexpected = found_attributes - set(expected_attributes.keys())
|
|
for attr in unexpected:
|
|
print(f" ⚠️ {attr} - UNEXPECTED (not in requirements)")
|
|
self.results['profile_editor']['unexpected'].append(attr)
|
|
|
|
# Check for dynamic attributes (MultiSelectPicker)
|
|
print("\n🔍 Checking for dynamic attributes (MultiSelectPicker)...")
|
|
dynamic_patterns = [
|
|
'profile_editor__short_term_focus__',
|
|
'profile_editor__long_term_focus__',
|
|
'profile_editor__strength__',
|
|
'profile_editor__improvement__',
|
|
'profile_editor__hobby__',
|
|
'profile_editor__club_',
|
|
'profile_editor__expectation__',
|
|
]
|
|
|
|
dynamic_found = defaultdict(list)
|
|
for attr in found_attributes:
|
|
for pattern in dynamic_patterns:
|
|
if attr.startswith(pattern):
|
|
dynamic_found[pattern].append(attr)
|
|
|
|
for pattern, attrs in dynamic_found.items():
|
|
print(f" ✅ {pattern}: Found {len(attrs)} attributes")
|
|
if len(attrs) > 0:
|
|
print(f" Examples: {attrs[:3]}")
|
|
|
|
return found_attributes
|
|
|
|
def verify_mandatory_reset_attributes(self):
|
|
"""Verify all Mandatory Password Reset Modal data-testid attributes"""
|
|
print("\n" + "="*80)
|
|
print("🔍 VERIFYING: Mandatory Password Reset Modal Attributes")
|
|
print("="*80)
|
|
|
|
# Navigate to login and trigger password reset modal
|
|
print("\n📋 Navigating to login page...")
|
|
self.driver.get(BASE_URL + "/login")
|
|
time.sleep(2)
|
|
|
|
# Login to trigger password reset modal
|
|
print("🔑 Logging in to trigger password reset modal...")
|
|
login_page = LoginPage(self.driver)
|
|
login_page.login(identifier=TEST_USERNAME, password=TEST_PASSWORD)
|
|
time.sleep(3)
|
|
|
|
# Check if modal is present
|
|
reset_page = MandatoryResetPage(self.driver)
|
|
if not reset_page.is_modal_present():
|
|
print("⚠️ Password reset modal not present - password may already be reset")
|
|
print(" Skipping mandatory reset verification")
|
|
return set()
|
|
|
|
print("✅ Password reset modal detected")
|
|
|
|
# Expected attributes
|
|
expected_attributes = {
|
|
'mandatory_reset__modal': 'Modal overlay',
|
|
'mandatory_reset__modal_content': 'Modal content',
|
|
'mandatory_reset__continue_button': 'Continue button',
|
|
'mandatory_reset__form': 'Form container',
|
|
'mandatory_reset__current_password_input': 'Current password input',
|
|
'mandatory_reset__current_password_toggle': 'Current password toggle',
|
|
'mandatory_reset__current_password_error': 'Current password error',
|
|
'mandatory_reset__new_password_input': 'New password input',
|
|
'mandatory_reset__new_password_toggle': 'New password toggle',
|
|
'mandatory_reset__new_password_error': 'New password error',
|
|
'mandatory_reset__confirm_password_input': 'Confirm password input',
|
|
'mandatory_reset__confirm_password_toggle': 'Confirm password toggle',
|
|
'mandatory_reset__confirm_password_error': 'Confirm password error',
|
|
'mandatory_reset__back_button': 'Back button',
|
|
'mandatory_reset__submit_button': 'Submit button',
|
|
}
|
|
|
|
# Find all mandatory_reset attributes in DOM
|
|
print("\n🔍 Searching for mandatory_reset attributes in DOM...")
|
|
all_elements = self.driver.find_elements(By.CSS_SELECTOR, "[data-testid^='mandatory_reset__']")
|
|
found_attributes = set()
|
|
|
|
for elem in all_elements:
|
|
try:
|
|
test_id = elem.get_attribute('data-testid')
|
|
if test_id:
|
|
found_attributes.add(test_id)
|
|
self.results['mandatory_reset']['found'].append({
|
|
'attribute': test_id,
|
|
'tag': elem.tag_name,
|
|
'visible': elem.is_displayed()
|
|
})
|
|
except:
|
|
pass
|
|
|
|
# Check for expected attributes
|
|
print(f"\n📊 Found {len(found_attributes)} mandatory_reset attributes")
|
|
|
|
for attr, description in expected_attributes.items():
|
|
if attr in found_attributes:
|
|
print(f" ✅ {attr} - {description}")
|
|
self.results['mandatory_reset']['expected'].append(attr)
|
|
else:
|
|
print(f" ❌ {attr} - {description} - MISSING")
|
|
self.results['mandatory_reset']['missing'].append({
|
|
'attribute': attr,
|
|
'description': description
|
|
})
|
|
|
|
# Check for unexpected attributes
|
|
unexpected = found_attributes - set(expected_attributes.keys())
|
|
for attr in unexpected:
|
|
print(f" ⚠️ {attr} - UNEXPECTED (not in requirements)")
|
|
self.results['mandatory_reset']['unexpected'].append(attr)
|
|
|
|
# Click Continue to see Step 2 (form)
|
|
try:
|
|
continue_btn = self.driver.find_element(By.CSS_SELECTOR, "[data-testid='mandatory_reset__continue_button']")
|
|
if continue_btn.is_displayed():
|
|
print("\n📋 Clicking Continue button to verify Step 2 attributes...")
|
|
continue_btn.click()
|
|
time.sleep(2)
|
|
|
|
# Re-check for form attributes
|
|
form_elements = self.driver.find_elements(By.CSS_SELECTOR, "[data-testid^='mandatory_reset__']")
|
|
form_attributes = set()
|
|
for elem in form_elements:
|
|
try:
|
|
test_id = elem.get_attribute('data-testid')
|
|
if test_id:
|
|
form_attributes.add(test_id)
|
|
except:
|
|
pass
|
|
|
|
print(f"\n📊 After clicking Continue: Found {len(form_attributes)} attributes")
|
|
new_attrs = form_attributes - found_attributes
|
|
if new_attrs:
|
|
print(f" ✅ New attributes found in Step 2: {new_attrs}")
|
|
found_attributes.update(new_attrs)
|
|
except:
|
|
print(" ⚠️ Could not click Continue button (may already be on Step 2)")
|
|
|
|
return found_attributes
|
|
|
|
def generate_verification_report(self):
|
|
"""Generate comprehensive verification report"""
|
|
print("\n" + "="*80)
|
|
print("📊 VERIFICATION REPORT SUMMARY")
|
|
print("="*80)
|
|
|
|
# Profile Editor
|
|
profile_expected = len(self.results['profile_editor']['expected'])
|
|
profile_missing = len(self.results['profile_editor']['missing'])
|
|
profile_unexpected = len(self.results['profile_editor']['unexpected'])
|
|
profile_total_found = len(self.results['profile_editor']['found'])
|
|
|
|
print(f"\n📋 Profile Editor:")
|
|
print(f" ✅ Found: {profile_total_found} attributes")
|
|
print(f" ✅ Expected: {profile_expected} attributes")
|
|
print(f" ❌ Missing: {profile_missing} attributes")
|
|
print(f" ⚠️ Unexpected: {profile_unexpected} attributes")
|
|
|
|
if profile_missing > 0:
|
|
print(f"\n ❌ Missing Attributes:")
|
|
for item in self.results['profile_editor']['missing']:
|
|
print(f" - {item['attribute']}: {item['description']}")
|
|
|
|
# Mandatory Reset
|
|
reset_expected = len(self.results['mandatory_reset']['expected'])
|
|
reset_missing = len(self.results['mandatory_reset']['missing'])
|
|
reset_unexpected = len(self.results['mandatory_reset']['unexpected'])
|
|
reset_total_found = len(self.results['mandatory_reset']['found'])
|
|
|
|
print(f"\n📋 Mandatory Password Reset Modal:")
|
|
print(f" ✅ Found: {reset_total_found} attributes")
|
|
print(f" ✅ Expected: {reset_expected} attributes")
|
|
print(f" ❌ Missing: {reset_missing} attributes")
|
|
print(f" ⚠️ Unexpected: {reset_unexpected} attributes")
|
|
|
|
if reset_missing > 0:
|
|
print(f"\n ❌ Missing Attributes:")
|
|
for item in self.results['mandatory_reset']['missing']:
|
|
print(f" - {item['attribute']}: {item['description']}")
|
|
|
|
# Overall Status
|
|
total_expected = profile_expected + reset_expected
|
|
total_missing = profile_missing + reset_missing
|
|
total_found = profile_total_found + reset_total_found
|
|
|
|
print(f"\n📊 OVERALL STATUS:")
|
|
print(f" ✅ Total Found: {total_found} attributes")
|
|
print(f" ✅ Total Expected: {total_expected} attributes")
|
|
print(f" ❌ Total Missing: {total_missing} attributes")
|
|
|
|
if total_missing == 0:
|
|
print(f"\n 🎉 SUCCESS: All required attributes are present!")
|
|
else:
|
|
print(f"\n ⚠️ WARNING: {total_missing} attributes are still missing")
|
|
|
|
return self.results
|
|
|
|
|
|
def verify_implementation():
|
|
"""Main verification function"""
|
|
print("="*80)
|
|
print("🔍 DATA-TESTID IMPLEMENTATION VERIFICATION")
|
|
print("="*80)
|
|
print("\n🎯 Purpose: Verify UI team's implementation against actual DOM")
|
|
print("💡 Method: Systematic DOM inspection - Zero assumptions")
|
|
print("📋 Approach: World-class verification with 100% accuracy\n")
|
|
|
|
# Setup Chrome driver
|
|
chrome_options = Options()
|
|
chrome_options.add_argument("--start-maximized")
|
|
chrome_options.add_argument("--disable-blink-features=AutomationControlled")
|
|
chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
|
|
chrome_options.add_experimental_option('useAutomationExtension', False)
|
|
|
|
driver = webdriver.Chrome(options=chrome_options)
|
|
verifier = DataTestIdVerifier(driver)
|
|
|
|
try:
|
|
# Step 1: Verify Profile Editor
|
|
profile_attrs = verifier.verify_profile_editor_attributes()
|
|
|
|
# Step 2: Verify Password Reset Modal
|
|
reset_attrs = verifier.verify_mandatory_reset_attributes()
|
|
|
|
# Step 3: Generate Report
|
|
results = verifier.generate_verification_report()
|
|
|
|
# Step 4: Save detailed report
|
|
report_file = os.path.join(os.path.dirname(__file__), '..', 'documentation', 'verification-reports', 'DOM_VERIFICATION_REPORT.json')
|
|
os.makedirs(os.path.dirname(report_file), exist_ok=True)
|
|
|
|
with open(report_file, 'w') as f:
|
|
json.dump(results, f, indent=2)
|
|
|
|
print(f"\n💾 Detailed report saved to: {report_file}")
|
|
|
|
print(f"\n⏸️ Browser will stay open for 30 seconds for manual inspection...")
|
|
time.sleep(30)
|
|
|
|
except Exception as e:
|
|
print(f"\n❌ ERROR: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
print(f"\n⏸️ Browser will stay open for 30 seconds for manual inspection...")
|
|
time.sleep(30)
|
|
finally:
|
|
print(f"\n{'='*80}")
|
|
print("CLEANUP")
|
|
print(f"{'='*80}")
|
|
driver.quit()
|
|
print("✅ Driver closed")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
verify_implementation()
|
|
|