""" Domains Page Object Model Handles domain listing screen for an assessment. Scope: domains_page, domain_card, domains_final_feedback """ from selenium.webdriver.common.by import By from pages.base_page import BasePage class DomainsPage(BasePage): """Page Object for Domains Page""" # Locators using data-testid (scope: domains_page) PAGE_CONTAINER = (By.CSS_SELECTOR, "[data-testid='domains_page__container']") BACK_BUTTON = (By.CSS_SELECTOR, "[data-testid='domains_page__back_button']") PAGE_TITLE = (By.CSS_SELECTOR, "[data-testid='domains_page__title']") PROGRESS_VALUE = (By.CSS_SELECTOR, "[data-testid='domains_page__progress_value']") # Final feedback modal (scope: domains_final_feedback) FINAL_FEEDBACK_MODAL = (By.CSS_SELECTOR, "[data-testid='domains_final_feedback__modal']") FINAL_FEEDBACK_MODAL_CONTENT = (By.CSS_SELECTOR, "[data-testid='domains_final_feedback__modal__content']") # Rating (1-5) FINAL_FEEDBACK_RATING = (By.CSS_SELECTOR, "[data-testid='domains_final_feedback__rating']") FINAL_FEEDBACK_RATING_1 = (By.CSS_SELECTOR, "[data-testid='domains_final_feedback__rating_1']") FINAL_FEEDBACK_RATING_2 = (By.CSS_SELECTOR, "[data-testid='domains_final_feedback__rating_2']") FINAL_FEEDBACK_RATING_3 = (By.CSS_SELECTOR, "[data-testid='domains_final_feedback__rating_3']") FINAL_FEEDBACK_RATING_4 = (By.CSS_SELECTOR, "[data-testid='domains_final_feedback__rating_4']") FINAL_FEEDBACK_RATING_5 = (By.CSS_SELECTOR, "[data-testid='domains_final_feedback__rating_5']") # Clarity question FINAL_FEEDBACK_CLARITY = (By.CSS_SELECTOR, "[data-testid='domains_final_feedback__clarity']") FINAL_FEEDBACK_CLARITY_YES = (By.CSS_SELECTOR, "[data-testid='domains_final_feedback__clarity_yes']") FINAL_FEEDBACK_CLARITY_NO = (By.CSS_SELECTOR, "[data-testid='domains_final_feedback__clarity_no']") FINAL_FEEDBACK_CLARITY_JUSTIFICATION = (By.CSS_SELECTOR, "[data-testid='domains_final_feedback__clarity_justification']") # Confidence question FINAL_FEEDBACK_CONFIDENCE = (By.CSS_SELECTOR, "[data-testid='domains_final_feedback__confidence']") FINAL_FEEDBACK_CONFIDENCE_YES = (By.CSS_SELECTOR, "[data-testid='domains_final_feedback__confidence_yes']") FINAL_FEEDBACK_CONFIDENCE_NO = (By.CSS_SELECTOR, "[data-testid='domains_final_feedback__confidence_no']") FINAL_FEEDBACK_CONFIDENCE_JUSTIFICATION = (By.CSS_SELECTOR, "[data-testid='domains_final_feedback__confidence_justification']") # Comments FINAL_FEEDBACK_COMMENTS = (By.CSS_SELECTOR, "[data-testid='domains_final_feedback__comments']") FINAL_FEEDBACK_COMMENTS_TEXTAREA = (By.CSS_SELECTOR, "[data-testid='domains_final_feedback__comments_textarea']") # Submit button FINAL_FEEDBACK_SUBMIT_BUTTON = (By.CSS_SELECTOR, "[data-testid='domains_final_feedback__submit_button']") def __init__(self, driver): """Initialize Domains Page""" super().__init__(driver) def wait_for_page_load(self): """Wait for domains page to load""" super().wait_for_page_load() # Call parent method # Wait for back button to be visible self.wait.wait_for_element_visible(self.BACK_BUTTON) def click_back(self): """Click back button - returns to assessments page""" self.click_element(self.BACK_BUTTON) self.wait.wait_for_url_contains("/assessments") def get_overall_progress(self): """ Get overall progress value Returns: str: Progress value """ try: return self.get_text(self.PROGRESS_VALUE) except: return "0%" def get_domain_card(self, domain_id): """ Get domain card element by domain ID Args: domain_id: Domain ID Returns: WebElement: Domain card element """ card_locator = (By.CSS_SELECTOR, f"[data-testid='domain_card__{domain_id}']") return self.find_element(card_locator) def click_domain_action(self, domain_id): """ Click Start/Continue button for a domain Args: domain_id: Domain ID """ action_locator = (By.CSS_SELECTOR, f"[data-testid='domain_card__{domain_id}__action']") self.click_element(action_locator) # Wait for navigation to domain assessment # URL pattern: /assessment/{assignmentId}/domain/{domainId} # Wait for either pattern from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from config.config import EXPLICIT_WAIT try: # Wait for /domain/ in URL (up to 15 seconds) WebDriverWait(self.driver, 15).until( lambda d: "/domain/" in d.current_url ) except: # Fallback: check if we're on assessment page (instructions modal might be showing) try: WebDriverWait(self.driver, 5).until( lambda d: "/assessment/" in d.current_url ) # If we're on assessment page, that's also valid (instructions modal state) current_url = self.driver.current_url if "/assessment/" in current_url: return # Navigation successful, even if domain ID not in URL yet except: pass # If still not navigated, check current URL one more time current_url = self.driver.current_url if "/assessment/" in current_url or "/domain/" in current_url: return # Navigation successful raise Exception(f"Navigation failed. Current URL: {current_url}") def click_start_domain(self, domain_id): """Alias for click_domain_action""" self.click_domain_action(domain_id) def is_domain_locked(self, domain_id): """ Check if domain is locked Args: domain_id: Domain ID Returns: bool: True if domain is locked """ try: card = self.get_domain_card(domain_id) action = self.driver.find_element(By.CSS_SELECTOR, f"[data-testid='domain_card__{domain_id}__action']") # Check if action button is disabled or has "locked" text return action.get_attribute("disabled") is not None or "locked" in action.text.lower() except: return True def is_domain_unlocked(self, domain_id): """ Check if domain is unlocked (opposite of is_domain_locked) Args: domain_id: Domain ID Returns: bool: True if domain is unlocked """ return not self.is_domain_locked(domain_id) def get_all_domain_ids(self): """ Get all domain IDs from cards on the page Returns: list: List of domain IDs """ import re cards = self.driver.find_elements(By.CSS_SELECTOR, "[data-testid^='domain_card__']") ids = [] for card in cards: test_id = card.get_attribute("data-testid") if test_id: match = re.search(r'domain_card__(\d+)', test_id) if match: ids.append(match.group(1)) return list(set(ids)) def is_final_feedback_modal_present(self): """ Check if final feedback modal is present (after all domains completed) Returns: bool: True if modal is visible """ try: return self.is_element_visible(self.FINAL_FEEDBACK_MODAL, timeout=5) except: return False def fill_final_feedback(self, rating=5, clarity=True, clarity_justification="", confidence=True, confidence_justification="", comments=""): """ Fill and submit final feedback form Args: rating: Overall rating (1-5) clarity: Answer to clarity question (True/False) clarity_justification: Justification for clarity (required if clarity=False) confidence: Answer to confidence question (True/False) confidence_justification: Justification for confidence (required if confidence=False) comments: Additional comments text """ if not self.is_final_feedback_modal_present(): raise Exception("Final feedback modal is not present") # Select overall rating rating_locator = getattr(self, f"FINAL_FEEDBACK_RATING_{rating}") self.click_element(rating_locator) # Answer clarity question if clarity: self.click_element(self.FINAL_FEEDBACK_CLARITY_YES) else: self.click_element(self.FINAL_FEEDBACK_CLARITY_NO) if clarity_justification: # Wait for justification textarea to appear self.wait.wait_for_element_visible(self.FINAL_FEEDBACK_CLARITY_JUSTIFICATION, timeout=3) self.send_keys(self.FINAL_FEEDBACK_CLARITY_JUSTIFICATION, clarity_justification) # Answer confidence question if confidence: self.click_element(self.FINAL_FEEDBACK_CONFIDENCE_YES) else: self.click_element(self.FINAL_FEEDBACK_CONFIDENCE_NO) if confidence_justification: # Wait for justification textarea to appear self.wait.wait_for_element_visible(self.FINAL_FEEDBACK_CONFIDENCE_JUSTIFICATION, timeout=3) self.send_keys(self.FINAL_FEEDBACK_CONFIDENCE_JUSTIFICATION, confidence_justification) # Fill comments if comments: self.send_keys(self.FINAL_FEEDBACK_COMMENTS_TEXTAREA, comments) # Submit self.click_element(self.FINAL_FEEDBACK_SUBMIT_BUTTON) # Wait for modal to close self.wait.wait_for_element_invisible(self.FINAL_FEEDBACK_MODAL, timeout=10)