CP_AUTOMATION/scripts/validate_dom_locators.py
2025-12-12 19:54:54 +05:30

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()