243 lines
10 KiB
Python
243 lines
10 KiB
Python
"""
|
|
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)
|
|
|