321 lines
13 KiB
Python
Executable File
321 lines
13 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
DOM Locator Validation Script
|
|
|
|
Thoroughly validates all locators against the current DOM.
|
|
Checks if all data-testid attributes are present and correctly implemented.
|
|
"""
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
project_root = Path(__file__).parent.parent
|
|
sys.path.insert(0, str(project_root))
|
|
|
|
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_incomplete_page import ProfileIncompletePage
|
|
from pages.profile_editor_page import ProfileEditorPage
|
|
from utils.driver_manager import DriverManager
|
|
from utils.password_tracker import password_tracker
|
|
from config.config import TEST_USERNAME, TEST_PASSWORD, TEST_NEW_PASSWORD, BASE_URL
|
|
import time
|
|
import json
|
|
|
|
|
|
def get_all_data_testids_from_dom(driver):
|
|
"""Extract all data-testid attributes from the current DOM"""
|
|
testids = {}
|
|
|
|
# Get all elements with data-testid
|
|
elements = driver.find_elements(By.CSS_SELECTOR, "[data-testid]")
|
|
|
|
for element in elements:
|
|
testid = element.get_attribute("data-testid")
|
|
tag = element.tag_name
|
|
element_type = element.get_attribute("type") or tag
|
|
visible = element.is_displayed()
|
|
|
|
if testid:
|
|
testids[testid] = {
|
|
"tag": tag,
|
|
"type": element_type,
|
|
"visible": visible,
|
|
"text": element.text[:50] if element.text else "",
|
|
"value": element.get_attribute("value")[:50] if element.get_attribute("value") else ""
|
|
}
|
|
|
|
return testids
|
|
|
|
|
|
def validate_profile_editor_locators(driver, profile_editor):
|
|
"""Validate all profile editor locators against DOM - Navigates through all tabs"""
|
|
print("\n" + "="*80)
|
|
print("DOM LOCATOR VALIDATION - PROFILE EDITOR")
|
|
print("="*80 + "\n")
|
|
|
|
# Navigate to profile editor
|
|
profile_editor.navigate()
|
|
profile_editor.wait_for_page_load()
|
|
time.sleep(2) # Wait for full render
|
|
|
|
# Collect testids from ALL tabs (many attributes are tab-specific)
|
|
print("📋 Extracting all data-testid attributes from ALL tabs...")
|
|
print(" (Navigating through all 9 tabs to find tab-specific attributes)\n")
|
|
|
|
dom_testids = {}
|
|
tab_names = [
|
|
"Personal Information", "Contact Information", "Parent/Guardian",
|
|
"Education Details", "Focus Areas", "Self-Assessment",
|
|
"Hobbies & Clubs", "Achievements", "Expectations"
|
|
]
|
|
|
|
# Collect from each tab
|
|
for tab_index, tab_name in enumerate(tab_names):
|
|
try:
|
|
print(f" 📝 Tab {tab_index + 1}/9: {tab_name}...")
|
|
profile_editor.navigate_to_tab(tab_index)
|
|
time.sleep(1) # Wait for tab to load
|
|
|
|
# Get testids from this tab
|
|
tab_testids = get_all_data_testids_from_dom(driver)
|
|
# Merge into main dict (overwrites duplicates, which is fine)
|
|
dom_testids.update(tab_testids)
|
|
print(f" ✅ Found {len(tab_testids)} attributes (Total so far: {len(dom_testids)})")
|
|
except Exception as e:
|
|
print(f" ⚠️ Error on tab {tab_index + 1}: {e}")
|
|
continue
|
|
|
|
print(f"\n✅ Total unique attributes found across all tabs: {len(dom_testids)}\n")
|
|
|
|
# Define all expected locators
|
|
expected_locators = {
|
|
# Page-level
|
|
"profile_editor__page": "PAGE",
|
|
"profile_editor__progress_value": "PROGRESS_VALUE",
|
|
"profile_editor__missing_fields_toggle": "MISSING_FIELDS_TOGGLE",
|
|
|
|
# Navigation buttons
|
|
"profile_editor__prev_button": "PREV_BUTTON",
|
|
"profile_editor__next_button": "NEXT_BUTTON",
|
|
"profile_editor__cancel_button": "CANCEL_BUTTON",
|
|
"profile_editor__save_button": "SAVE_BUTTON",
|
|
|
|
# Tabs
|
|
"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",
|
|
|
|
# Step 1: Personal Information
|
|
"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__roll_number_input": "ROLL_NUMBER_INPUT",
|
|
"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",
|
|
|
|
# Step 2: 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",
|
|
|
|
# Step 3: Parent/Guardian
|
|
"profile_editor__father_full_name_input": "FATHER_FULL_NAME_INPUT",
|
|
"profile_editor__father_age_range_select": "FATHER_AGE_RANGE_SELECT",
|
|
"profile_editor__father_occupation_input": "FATHER_OCCUPATION_INPUT",
|
|
"profile_editor__father_email_input": "FATHER_EMAIL_INPUT",
|
|
"profile_editor__mother_full_name_input": "MOTHER_FULL_NAME_INPUT",
|
|
"profile_editor__mother_age_range_select": "MOTHER_AGE_RANGE_SELECT",
|
|
"profile_editor__mother_occupation_input": "MOTHER_OCCUPATION_INPUT",
|
|
"profile_editor__mother_email_input": "MOTHER_EMAIL_INPUT",
|
|
"profile_editor__guardian_different_checkbox": "GUARDIAN_DIFFERENT_CHECKBOX",
|
|
"profile_editor__guardian_full_name_input": "GUARDIAN_FULL_NAME_INPUT",
|
|
"profile_editor__guardian_age_range_select": "GUARDIAN_AGE_RANGE_SELECT",
|
|
"profile_editor__guardian_occupation_input": "GUARDIAN_OCCUPATION_INPUT",
|
|
"profile_editor__guardian_email_input": "GUARDIAN_EMAIL_INPUT",
|
|
|
|
# Step 4: Education Details
|
|
"profile_editor__full_name_input": "FULL_NAME_INPUT",
|
|
"profile_editor__current_grade_input": "CURRENT_GRADE_INPUT",
|
|
"profile_editor__section_input": "SECTION_INPUT",
|
|
"profile_editor__board_stream_select": "BOARD_STREAM_SELECT",
|
|
|
|
# Step 5-9: These are checked via patterns (checkboxes, textareas)
|
|
# Focus Areas, Self-Assessment, Hobbies, Clubs, Expectations, Achievements
|
|
# are validated via multi-select patterns below
|
|
}
|
|
|
|
# Check each expected locator
|
|
print("🔍 Validating expected locators...\n")
|
|
found = []
|
|
missing = []
|
|
found_but_not_expected = []
|
|
|
|
for testid, constant_name in expected_locators.items():
|
|
if testid in dom_testids:
|
|
found.append({
|
|
"testid": testid,
|
|
"constant": constant_name,
|
|
"info": dom_testids[testid]
|
|
})
|
|
print(f"✅ {testid} - {constant_name}")
|
|
else:
|
|
missing.append({
|
|
"testid": testid,
|
|
"constant": constant_name
|
|
})
|
|
print(f"❌ MISSING: {testid} - {constant_name}")
|
|
|
|
# Check for profile_editor testids that we didn't expect
|
|
print("\n🔍 Checking for unexpected profile_editor testids...\n")
|
|
for testid in dom_testids:
|
|
if testid.startswith("profile_editor__") and testid not in expected_locators:
|
|
found_but_not_expected.append({
|
|
"testid": testid,
|
|
"info": dom_testids[testid]
|
|
})
|
|
print(f"⚠️ UNEXPECTED (but present): {testid}")
|
|
|
|
# Summary
|
|
print("\n" + "="*80)
|
|
print("VALIDATION SUMMARY")
|
|
print("="*80)
|
|
print(f"✅ Found: {len(found)}/{len(expected_locators)}")
|
|
print(f"❌ Missing: {len(missing)}")
|
|
print(f"⚠️ Unexpected (but present): {len(found_but_not_expected)}")
|
|
print("="*80 + "\n")
|
|
|
|
# Check multi-select checkboxes (Focus Areas, Strengths, etc.)
|
|
print("\n🔍 Checking Multi-Select Checkboxes...\n")
|
|
multi_select_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__"
|
|
]
|
|
|
|
for pattern in multi_select_patterns:
|
|
matching = [tid for tid in dom_testids if tid.startswith(pattern)]
|
|
if matching:
|
|
print(f"✅ Found {len(matching)} checkboxes matching '{pattern}*'")
|
|
for tid in matching[:5]: # Show first 5
|
|
print(f" - {tid}")
|
|
if len(matching) > 5:
|
|
print(f" ... and {len(matching) - 5} more")
|
|
else:
|
|
print(f"❌ No checkboxes found matching '{pattern}*'")
|
|
|
|
# Save results to file
|
|
results = {
|
|
"found": found,
|
|
"missing": missing,
|
|
"unexpected": found_but_not_expected,
|
|
"all_dom_testids": list(dom_testids.keys()),
|
|
"total_found": len(found),
|
|
"total_expected": len(expected_locators),
|
|
"total_missing": len(missing)
|
|
}
|
|
|
|
results_file = project_root / "analysis" / "dom_validation_results.json"
|
|
results_file.parent.mkdir(exist_ok=True)
|
|
with open(results_file, 'w') as f:
|
|
json.dump(results, f, indent=2)
|
|
|
|
print(f"\n📄 Results saved to: {results_file}")
|
|
|
|
return results
|
|
|
|
|
|
def main():
|
|
"""Main validation function"""
|
|
driver = None
|
|
try:
|
|
print("🚀 Starting DOM Locator Validation...")
|
|
print(f"🌐 Environment: {BASE_URL}\n")
|
|
|
|
# Initialize driver
|
|
driver_manager = DriverManager()
|
|
driver = driver_manager.get_driver(headless=False)
|
|
|
|
# Step 1: Login
|
|
print("[STEP 1] LOGIN")
|
|
print("-" * 80)
|
|
login_page = LoginPage(driver)
|
|
login_page.login(identifier=TEST_USERNAME, password=None)
|
|
print(f"✅ Login successful - URL: {driver.current_url}\n")
|
|
|
|
# Step 2: Handle Password Reset
|
|
print("[STEP 2] PASSWORD RESET CHECK")
|
|
print("-" * 80)
|
|
reset_page = MandatoryResetPage(driver)
|
|
if reset_page.is_modal_present():
|
|
current_password = password_tracker.get_password(TEST_USERNAME, TEST_PASSWORD)
|
|
reset_page.reset_password(
|
|
current_password=current_password,
|
|
new_password=TEST_NEW_PASSWORD,
|
|
confirm_password=TEST_NEW_PASSWORD,
|
|
student_cpid=TEST_USERNAME
|
|
)
|
|
print("✅ Password reset completed\n")
|
|
else:
|
|
print("✅ No password reset required\n")
|
|
|
|
# Step 3: Navigate to Profile Editor
|
|
print("[STEP 3] NAVIGATE TO PROFILE EDITOR")
|
|
print("-" * 80)
|
|
profile_incomplete = ProfileIncompletePage(driver)
|
|
if profile_incomplete.is_modal_present():
|
|
profile_incomplete.click_complete()
|
|
time.sleep(2)
|
|
print("✅ Clicked Complete Profile button\n")
|
|
else:
|
|
# Navigate directly to profile editor
|
|
profile_editor = ProfileEditorPage(driver)
|
|
profile_editor.navigate()
|
|
time.sleep(2)
|
|
print("✅ Navigated directly to profile editor\n")
|
|
|
|
# Step 4: Validate Locators
|
|
profile_editor = ProfileEditorPage(driver)
|
|
results = validate_profile_editor_locators(driver, profile_editor)
|
|
|
|
# Keep browser open for inspection
|
|
print("\n⏸️ Browser will remain open for 60 seconds for manual inspection...")
|
|
print(" Press Ctrl+C to close early\n")
|
|
time.sleep(60)
|
|
|
|
except KeyboardInterrupt:
|
|
print("\n\n⚠️ Validation interrupted by user")
|
|
except Exception as e:
|
|
print(f"\n\n❌ Error during validation: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
finally:
|
|
if driver:
|
|
driver.quit()
|
|
print("\n✅ Browser closed")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
|