#!/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()