451 lines
18 KiB
Python
451 lines
18 KiB
Python
"""
|
|
Domain Assessment Page Object Model
|
|
|
|
Handles in-progress domain test experience.
|
|
Scope: domain_assessment, domain_question
|
|
"""
|
|
import time
|
|
from selenium.webdriver.common.by import By
|
|
from pages.base_page import BasePage
|
|
|
|
|
|
class DomainAssessmentPage(BasePage):
|
|
"""Page Object for Domain Assessment Page"""
|
|
|
|
# Locators using data-testid (scope: domain_assessment)
|
|
PAGE = (By.CSS_SELECTOR, "[data-testid='domain_assessment__page']")
|
|
BACK_BUTTON = (By.CSS_SELECTOR, "[data-testid='domain_assessment__back_button']")
|
|
PROGRESS_VALUE = (By.CSS_SELECTOR, "[data-testid='domain_assessment__progress_value']")
|
|
TIMER_VALUE = (By.CSS_SELECTOR, "[data-testid='domain_assessment__timer_value']")
|
|
|
|
# Navigation buttons
|
|
PREV_BUTTON = (By.CSS_SELECTOR, "[data-testid='domain_assessment__prev_button']")
|
|
NEXT_BUTTON = (By.CSS_SELECTOR, "[data-testid='domain_assessment__next_button']")
|
|
SUBMIT_BUTTON = (By.CSS_SELECTOR, "[data-testid='domain_assessment__submit_button']")
|
|
|
|
# Modals
|
|
INSTRUCTIONS_MODAL = (By.CSS_SELECTOR, "[data-testid='domain_assessment__instructions_modal']")
|
|
INSTRUCTIONS_MODAL_CONTENT = (By.CSS_SELECTOR, "[data-testid='domain_assessment__instructions_modal__content']")
|
|
INSTRUCTIONS_CONTINUE_BUTTON = (By.CSS_SELECTOR, "[data-testid='domain_assessment__instructions_modal__continue_button']")
|
|
|
|
SUBMIT_MODAL = (By.CSS_SELECTOR, "[data-testid='domain_assessment__submit_modal']")
|
|
SUBMIT_MODAL_CONTENT = (By.CSS_SELECTOR, "[data-testid='domain_assessment__submit_modal__content']")
|
|
SUBMIT_MODAL_REVIEW_BUTTON = (By.CSS_SELECTOR, "[data-testid='domain_assessment__submit_modal__review_button']")
|
|
SUBMIT_MODAL_CONFIRM_BUTTON = (By.CSS_SELECTOR, "[data-testid='domain_assessment__submit_modal__confirm_button']")
|
|
SUBMIT_MODAL_CANCEL_BUTTON = (By.CSS_SELECTOR, "[data-testid='domain_assessment__submit_modal__cancel_button']")
|
|
|
|
GUIDANCE_MODAL = (By.CSS_SELECTOR, "[data-testid='domain_assessment__guidance_modal']")
|
|
GUIDANCE_MODAL_CONTENT = (By.CSS_SELECTOR, "[data-testid='domain_assessment__guidance_modal__content']")
|
|
GUIDANCE_DISMISS_BUTTON = (By.CSS_SELECTOR, "[data-testid='domain_assessment__guidance_modal__dismiss_button']")
|
|
|
|
SUCCESS_MODAL = (By.CSS_SELECTOR, "[data-testid='domain_assessment__success_modal']")
|
|
SUCCESS_MODAL_CONTENT = (By.CSS_SELECTOR, "[data-testid='domain_assessment__success_modal__content']")
|
|
SUCCESS_MODAL_MESSAGE = (By.CSS_SELECTOR, "[data-testid='domain_assessment__success_modal__message']")
|
|
|
|
# Action bar
|
|
ACTION_BAR = (By.CSS_SELECTOR, "[data-testid='domain_assessment__action_bar']")
|
|
|
|
def __init__(self, driver):
|
|
"""Initialize Domain Assessment Page"""
|
|
super().__init__(driver)
|
|
|
|
def wait_for_page_load(self):
|
|
"""Wait for domain assessment page to load"""
|
|
super().wait_for_page_load() # Call parent method
|
|
# Wait for either the page container OR instructions modal (both indicate page loaded)
|
|
try:
|
|
# First check if instructions modal is present (that's also a valid state)
|
|
if self.is_instructions_modal_present():
|
|
return # Page loaded, instructions modal showing
|
|
# Otherwise wait for the actual page
|
|
self.wait.wait_for_element_visible(self.PAGE, timeout=15)
|
|
return # Page element found
|
|
except:
|
|
# If page not found, check if instructions modal is there
|
|
try:
|
|
if self.is_instructions_modal_present():
|
|
return # Instructions modal is present, page is loaded
|
|
except:
|
|
pass
|
|
|
|
# If neither found, try waiting for action bar or header as fallback
|
|
try:
|
|
self.wait.wait_for_element_visible(self.ACTION_BAR, timeout=5)
|
|
return # Action bar found, page is loaded
|
|
except:
|
|
pass
|
|
|
|
# Try to find a question element (if questions are visible, page is loaded)
|
|
try:
|
|
from selenium.webdriver.common.by import By
|
|
from selenium.webdriver.support.ui import WebDriverWait
|
|
from selenium.webdriver.support import expected_conditions as EC
|
|
WebDriverWait(self.driver, 5).until(
|
|
EC.presence_of_element_located((By.CSS_SELECTOR, "[data-testid^='domain_question__']"))
|
|
)
|
|
return # Questions found, page is loaded
|
|
except:
|
|
pass
|
|
|
|
# Last resort: try back button (but don't fail if not found)
|
|
try:
|
|
self.wait.wait_for_element_visible(self.BACK_BUTTON, timeout=3)
|
|
return # Back button found, page is loaded
|
|
except:
|
|
pass
|
|
|
|
# Final check: if URL is correct, consider page loaded (even if elements not found yet)
|
|
current_url = self.driver.current_url
|
|
if "/assessment/" in current_url and "/domain/" in current_url:
|
|
# URL indicates we're on the right page - give it a moment and return
|
|
import time
|
|
time.sleep(1) # Brief wait for elements to appear
|
|
return # URL indicates we're on the right page
|
|
|
|
# If we get here, page might still be loading - don't raise exception
|
|
# Let the caller handle it or wait longer
|
|
pass
|
|
|
|
def click_back(self):
|
|
"""Click back button - returns to domains page"""
|
|
self.click_element(self.BACK_BUTTON)
|
|
self.wait.wait_for_url_contains("/domains")
|
|
|
|
def get_progress(self):
|
|
"""Get current progress value"""
|
|
try:
|
|
return self.get_text(self.PROGRESS_VALUE)
|
|
except:
|
|
return "0"
|
|
|
|
def get_timer_value(self):
|
|
"""Get timer value"""
|
|
try:
|
|
return self.get_text(self.TIMER_VALUE)
|
|
except:
|
|
return ""
|
|
|
|
def get_current_question_id(self):
|
|
"""
|
|
Get current question ID from URL or page
|
|
|
|
Returns:
|
|
str: Question ID or None
|
|
"""
|
|
import re
|
|
current_url = self.driver.current_url
|
|
match = re.search(r'/question/(\d+)', current_url)
|
|
if match:
|
|
return match.group(1)
|
|
return None
|
|
|
|
def get_question_element(self, question_id):
|
|
"""
|
|
Get question shell element
|
|
|
|
Args:
|
|
question_id: Question ID
|
|
|
|
Returns:
|
|
WebElement: Question element
|
|
"""
|
|
question_locator = (By.CSS_SELECTOR, f"[data-testid='domain_question__{question_id}']")
|
|
return self.find_element(question_locator)
|
|
|
|
def get_question_text(self, question_id):
|
|
"""Get question text"""
|
|
text_locator = (By.CSS_SELECTOR, f"[data-testid='domain_question__{question_id}__text']")
|
|
return self.get_text(text_locator)
|
|
|
|
def answer_multiple_choice(self, question_id, option_label):
|
|
"""
|
|
Answer multiple choice question
|
|
|
|
Args:
|
|
question_id: Question ID
|
|
option_label: Option label (a, b, c, etc.)
|
|
"""
|
|
option_locator = (By.CSS_SELECTOR, f"[data-testid='domain_question__{question_id}__option_{option_label}']")
|
|
self.click_element(option_locator)
|
|
|
|
def answer_true_false(self, question_id, value):
|
|
"""
|
|
Answer true/false question
|
|
|
|
Args:
|
|
question_id: Question ID
|
|
value: True or False (boolean or string "True"/"False")
|
|
"""
|
|
# Convert boolean to string if needed
|
|
if isinstance(value, bool):
|
|
value_str = "True" if value else "False"
|
|
else:
|
|
value_str = str(value)
|
|
if value_str.lower() in ['true', 'yes']:
|
|
value_str = "True"
|
|
elif value_str.lower() in ['false', 'no']:
|
|
value_str = "False"
|
|
|
|
tf_locator = (By.CSS_SELECTOR, f"[data-testid='domain_question__{question_id}__truefalse_{value_str}']")
|
|
self.click_element(tf_locator)
|
|
|
|
def answer_rating_scale(self, question_id, score):
|
|
"""
|
|
Answer rating scale question
|
|
|
|
Args:
|
|
question_id: Question ID
|
|
score: Rating score (1-5, etc.)
|
|
"""
|
|
rating_locator = (By.CSS_SELECTOR, f"[data-testid='domain_question__{question_id}__rating_{score}']")
|
|
self.click_element(rating_locator)
|
|
|
|
def answer_rating(self, question_id, score):
|
|
"""Alias for answer_rating_scale"""
|
|
self.answer_rating_scale(question_id, score)
|
|
|
|
def answer_open_ended(self, question_id, text):
|
|
"""
|
|
Answer open-ended question
|
|
|
|
Args:
|
|
question_id: Question ID
|
|
text: Answer text
|
|
"""
|
|
textarea_locator = (By.CSS_SELECTOR, f"[data-testid='domain_question__{question_id}__textarea']")
|
|
self.send_keys(textarea_locator, text)
|
|
|
|
def answer_matrix(self, question_id, row_index, column_index):
|
|
"""
|
|
Answer matrix question
|
|
|
|
Args:
|
|
question_id: Question ID
|
|
row_index: Row index
|
|
column_index: Column index
|
|
"""
|
|
matrix_locator = (By.CSS_SELECTOR, f"[data-testid='domain_question__{question_id}__matrix_{row_index}_{column_index}']")
|
|
self.click_element(matrix_locator)
|
|
|
|
def click_previous(self):
|
|
"""Click Previous button"""
|
|
self.click_element(self.PREV_BUTTON)
|
|
# Wait for previous question to load
|
|
self.wait_for_page_load()
|
|
|
|
def click_next(self):
|
|
"""Click Next button"""
|
|
self.click_element(self.NEXT_BUTTON)
|
|
# Wait for next question to load
|
|
self.wait_for_page_load()
|
|
|
|
def click_submit(self):
|
|
"""Click Submit button - opens submit confirmation modal"""
|
|
self.click_element(self.SUBMIT_BUTTON)
|
|
# Wait for submit modal
|
|
self.wait.wait_for_element_visible(self.SUBMIT_MODAL)
|
|
|
|
def confirm_submit(self):
|
|
"""Confirm submission - clicks confirm in submit modal"""
|
|
self.click_element(self.SUBMIT_MODAL_CONFIRM_BUTTON)
|
|
# Wait for success modal
|
|
self.wait.wait_for_element_visible(self.SUCCESS_MODAL)
|
|
|
|
def review_before_submit(self):
|
|
"""Click review button in submit modal"""
|
|
self.click_element(self.SUBMIT_MODAL_REVIEW_BUTTON)
|
|
# Modal closes, back to questions
|
|
|
|
def dismiss_guidance(self):
|
|
"""Dismiss guidance modal if present"""
|
|
try:
|
|
if self.is_element_visible(self.GUIDANCE_MODAL, timeout=2):
|
|
self.click_element(self.GUIDANCE_DISMISS_BUTTON)
|
|
# Wait for modal to close
|
|
self.wait.wait_for_element_invisible(self.GUIDANCE_MODAL, timeout=5)
|
|
except:
|
|
pass
|
|
|
|
def is_instructions_modal_present(self):
|
|
"""Check if instructions modal is present"""
|
|
try:
|
|
return self.is_element_visible(self.INSTRUCTIONS_MODAL, timeout=3)
|
|
except:
|
|
return False
|
|
|
|
def dismiss_instructions_modal(self):
|
|
"""Dismiss instructions modal by clicking continue button"""
|
|
try:
|
|
if self.is_instructions_modal_present():
|
|
self.click_element(self.INSTRUCTIONS_CONTINUE_BUTTON)
|
|
# Wait for modal to close
|
|
self.wait.wait_for_element_invisible(self.INSTRUCTIONS_MODAL, timeout=5)
|
|
except:
|
|
pass
|
|
|
|
def wait_for_success_modal(self):
|
|
"""Wait for success modal after submission"""
|
|
self.wait.wait_for_element_visible(self.SUCCESS_MODAL)
|
|
|
|
def is_submit_modal_present(self):
|
|
"""Check if submit confirmation modal is present"""
|
|
try:
|
|
return self.is_element_visible(self.SUBMIT_MODAL, timeout=3)
|
|
except:
|
|
return False
|
|
|
|
def is_success_modal_present(self):
|
|
"""Check if success modal is present"""
|
|
try:
|
|
return self.is_element_visible(self.SUCCESS_MODAL, timeout=5)
|
|
except:
|
|
return False
|
|
|
|
def close_success_modal(self):
|
|
"""Close success modal if present"""
|
|
try:
|
|
# Success modal usually auto-closes or redirects
|
|
# Wait for it to disappear (no fixed sleep needed)
|
|
from selenium.webdriver.support.ui import WebDriverWait
|
|
from selenium.webdriver.support import expected_conditions as EC
|
|
from config.config import SHORT_WAIT
|
|
WebDriverWait(self.driver, SHORT_WAIT).until(
|
|
EC.invisibility_of_element_located(self.SUCCESS_MODAL)
|
|
)
|
|
except:
|
|
# Modal might have already closed or redirected
|
|
pass
|
|
|
|
def is_next_button_visible(self):
|
|
"""Check if next button is visible and enabled"""
|
|
try:
|
|
next_btn = self.find_element(self.NEXT_BUTTON)
|
|
return next_btn.is_displayed() and next_btn.is_enabled()
|
|
except:
|
|
return False
|
|
|
|
def is_submit_button_visible(self):
|
|
"""Check if submit button is visible and enabled"""
|
|
try:
|
|
submit_btn = self.find_element(self.SUBMIT_BUTTON)
|
|
return submit_btn.is_displayed() and submit_btn.is_enabled()
|
|
except:
|
|
return False
|
|
|
|
def get_all_questions(self):
|
|
"""
|
|
Get all question IDs on current page
|
|
|
|
Returns:
|
|
list: List of question IDs
|
|
"""
|
|
import re
|
|
questions = self.driver.find_elements(By.CSS_SELECTOR, "[data-testid^='domain_question__']")
|
|
question_ids = []
|
|
for question in questions:
|
|
test_id = question.get_attribute("data-testid")
|
|
if test_id:
|
|
match = re.search(r'domain_question__(\d+)', test_id)
|
|
if match:
|
|
q_id = match.group(1)
|
|
if q_id not in question_ids:
|
|
question_ids.append(q_id)
|
|
return question_ids
|
|
|
|
def get_question_type(self, question_id):
|
|
"""
|
|
Determine question type by checking available answer options
|
|
|
|
Args:
|
|
question_id: Question ID
|
|
|
|
Returns:
|
|
str: Question type (multiple_choice, true_false, rating_scale, open_ended, matrix)
|
|
"""
|
|
try:
|
|
# Check for multiple choice options (A, B, C, D, E)
|
|
if self.is_element_present((By.CSS_SELECTOR, f"[data-testid='domain_question__{question_id}__option_A']"), timeout=1):
|
|
return "multiple_choice"
|
|
|
|
# Check for true/false
|
|
if self.is_element_present((By.CSS_SELECTOR, f"[data-testid='domain_question__{question_id}__truefalse_True']"), timeout=1):
|
|
return "true_false"
|
|
|
|
# Check for rating scale
|
|
if self.is_element_present((By.CSS_SELECTOR, f"[data-testid='domain_question__{question_id}__rating_1']"), timeout=1):
|
|
return "rating_scale"
|
|
|
|
# Check for open ended
|
|
if self.is_element_present((By.CSS_SELECTOR, f"[data-testid='domain_question__{question_id}__textarea']"), timeout=1):
|
|
return "open_ended"
|
|
|
|
# Check for matrix
|
|
if self.is_element_present((By.CSS_SELECTOR, f"[data-testid^='domain_question__{question_id}__matrix_']"), timeout=1):
|
|
return "matrix"
|
|
|
|
return "unknown"
|
|
except:
|
|
return "unknown"
|
|
|
|
def get_question_options(self, question_id):
|
|
"""
|
|
Get available options for multiple choice question
|
|
|
|
Args:
|
|
question_id: Question ID
|
|
|
|
Returns:
|
|
list: List of option labels (a, b, c, etc.)
|
|
"""
|
|
import re
|
|
options = self.driver.find_elements(By.CSS_SELECTOR, f"[data-testid^='domain_question__{question_id}__option_']")
|
|
option_labels = []
|
|
for option in options:
|
|
test_id = option.get_attribute("data-testid")
|
|
if test_id:
|
|
match = re.search(r'option_([a-z0-9]+)', test_id)
|
|
if match:
|
|
option_labels.append(match.group(1))
|
|
return option_labels
|
|
|
|
def select_option(self, question_id, option_label):
|
|
"""Select an option for multiple choice question"""
|
|
self.answer_multiple_choice(question_id, option_label)
|
|
|
|
def select_true_false(self, question_id, value):
|
|
"""Select true/false value"""
|
|
self.answer_true_false(question_id, value)
|
|
|
|
def select_rating(self, question_id, score):
|
|
"""Select rating score"""
|
|
self.answer_rating(question_id, score)
|
|
|
|
def enter_open_ended(self, question_id, text):
|
|
"""Enter text for open-ended question"""
|
|
self.answer_open_ended(question_id, text)
|
|
|
|
def select_matrix_cell(self, question_id, row_index, column_index, value=True):
|
|
"""Select matrix cell"""
|
|
self.answer_matrix(question_id, row_index, column_index)
|
|
|
|
def get_matrix_dimensions(self, question_id):
|
|
"""
|
|
Get matrix dimensions
|
|
|
|
Returns:
|
|
tuple: (rows, cols)
|
|
"""
|
|
import re
|
|
matrix_cells = self.driver.find_elements(By.CSS_SELECTOR, f"[data-testid^='domain_question__{question_id}__matrix_']")
|
|
rows = set()
|
|
cols = set()
|
|
for cell in matrix_cells:
|
|
test_id = cell.get_attribute("data-testid")
|
|
if test_id:
|
|
match = re.search(r'matrix_(\d+)_(\d+)', test_id)
|
|
if match:
|
|
rows.add(int(match.group(1)))
|
|
cols.add(int(match.group(2)))
|
|
return (len(rows) if rows else 0, len(cols) if cols else 0)
|
|
|