Skip to content

Commit 6e9e5d1

Browse files
Completing scenario 10
1 parent 55bada8 commit 6e9e5d1

File tree

6 files changed

+849
-5
lines changed

6 files changed

+849
-5
lines changed

investigation_dataset_ui_app/dataset_fields.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@
352352
},
353353
{
354354
"key": "polyp appears fully resected endoscopically",
355-
"type": "YesNoOptions",
355+
"type": "YesNoUncertainOptions",
356356
"description": "Whether the polyp appears fully resected endoscopically",
357357
"optional": true
358358
}

pages/datasets/investigation_dataset_page.py

Lines changed: 169 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,13 @@ def __init__(self, page: Page):
9999
self.antibiotics_administered_string = "Antibiotics Administered"
100100
self.other_drugs_administered_string = "Other Drugs Administered"
101101

102+
# Other:
103+
self.list_of_multi_line_fields = [
104+
"failure reasons",
105+
"early complications",
106+
"late complications",
107+
]
108+
102109
def select_site_lookup_option(self, option: str) -> None:
103110
"""
104111
This method is designed to select a site from the site lookup options.
@@ -628,7 +635,7 @@ def get_dataset_section(self, dataset_section_name: str) -> Optional[Locator]:
628635
Args:
629636
dataset_section_name (str): The name of the dataset section to locate.
630637
Returns:
631-
Optioanl[Locator]: A Playwright Locator for the matching section if visible, or None if not found or not visible.
638+
Optional[Locator]: A Playwright Locator for the matching section if visible, or None if not found or not visible.
632639
"""
633640
logging.info(f"START: Looking for section '{dataset_section_name}'")
634641

@@ -1172,6 +1179,167 @@ def get_drug_dosage_text_locator(self, drug_type: str, drug_number: int) -> Loca
11721179
locator_prefix = "#HILITE_spanAntibioticDosageUnit"
11731180
return self.page.locator(f"{locator_prefix}{drug_number}")
11741181

1182+
def does_field_contain_expected_value(
1183+
self,
1184+
dataset_area: str,
1185+
dataset_subsection: str | None,
1186+
field_name: str,
1187+
expected_value: str,
1188+
) -> None:
1189+
"""
1190+
Checks if the specified field contains the expected value in the given dataset area and subsection.
1191+
Args:
1192+
dataset_area (str): The dataset section name.
1193+
dataset_subsection (str | None): The dataset subsection name.
1194+
field_name (str): The field label to check.
1195+
expected_value (str): The expected value to look for.
1196+
"""
1197+
logging.debug(
1198+
f"START: does_field_contain_expected_value({dataset_area}, {dataset_subsection}, {field_name}, {expected_value})"
1199+
)
1200+
field_and_value_found = False
1201+
map_of_field_and_elements = self.map_fields_and_values(
1202+
dataset_area, dataset_subsection
1203+
)
1204+
actual_value = map_of_field_and_elements.get(field_name.lower())
1205+
if actual_value is not None:
1206+
actual_value = actual_value.replace("\xa0", "").replace(" ", "")
1207+
if actual_value.replace(" ", "") == "":
1208+
actual_value = ""
1209+
if expected_value.lower() in actual_value.lower():
1210+
field_and_value_found = True
1211+
else:
1212+
raise ValueError(
1213+
f"Value not as expected. Expected: {expected_value}, Actual: '{actual_value}'"
1214+
)
1215+
else:
1216+
raise ValueError(
1217+
f"Field '{field_name}' not found in the dataset area '{dataset_area}' and subsection '{dataset_subsection}'."
1218+
)
1219+
logging.debug("END: does_field_contain_expected_value")
1220+
if field_and_value_found:
1221+
logging.info(
1222+
f"[UI ASSERTIONS COMPLETE] Value as expected. Expected: {expected_value}, Actual: '{actual_value}'"
1223+
)
1224+
1225+
def map_fields_and_values(
1226+
self, dataset_section: str, dataset_subsection: str | None
1227+
) -> dict[str, str]:
1228+
"""
1229+
Maps field labels to their values for a given dataset section and subsection.
1230+
Args:
1231+
dataset_section (str): The name of the dataset section.
1232+
dataset_subsection (str | None): The name of the dataset subsection.
1233+
Returns:
1234+
dict[str, str]: A dictionary mapping field labels to their corresponding values.
1235+
"""
1236+
logging.debug("start: map_fields_and_values()")
1237+
fields_and_elements = self.map_fields_and_elements(
1238+
dataset_section, dataset_subsection
1239+
)
1240+
fields_with_values = {}
1241+
1242+
for field_label, element in fields_and_elements.items():
1243+
# Find all child elements
1244+
value_list = element.locator("xpath=.//*").all()
1245+
field_value = ""
1246+
if not value_list:
1247+
field_value = element.inner_text()
1248+
else:
1249+
for value_from_list in value_list:
1250+
tag_name = value_from_list.evaluate(
1251+
"el => el.tagName.toLowerCase()"
1252+
)
1253+
if tag_name == "select":
1254+
# Get selected option text
1255+
selected_option = value_from_list.locator("option:checked")
1256+
field_value += selected_option.inner_text()
1257+
elif tag_name == "li":
1258+
input_elem = value_from_list.locator("input")
1259+
if input_elem.is_checked():
1260+
label_elem = value_from_list.locator("label")
1261+
field_value = label_elem.inner_text()
1262+
elif tag_name == "input":
1263+
input_type = value_from_list.get_attribute("type")
1264+
if input_type == "text":
1265+
field_value += value_from_list.input_value()
1266+
elif tag_name == "p":
1267+
field_value += value_from_list.inner_text()
1268+
else:
1269+
logging.debug(
1270+
f"tag type not specified, tag ignored = {tag_name}"
1271+
)
1272+
fields_with_values[field_label] = field_value
1273+
1274+
logging.debug("end: map_fields_and_values()")
1275+
return fields_with_values
1276+
1277+
def map_fields_and_elements(
1278+
self, dataset_section: str, dataset_subsection: Optional[str] = None
1279+
) -> dict[str, Locator]:
1280+
"""
1281+
Maps field labels to their corresponding Playwright Locator elements for a given dataset section and subsection.
1282+
Args:
1283+
dataset_section (str): The name of the dataset section.
1284+
dataset_subsection (Optional[str]): The name of the dataset subsection, if any.
1285+
Returns:
1286+
dict[str, Locator]: A dictionary mapping field labels to their corresponding Locator elements.
1287+
"""
1288+
logging.debug(
1289+
f"start: map_fields_and_elements({dataset_section}, {dataset_subsection})"
1290+
)
1291+
fields_and_elements = {}
1292+
1293+
previous_field = ""
1294+
field_name = ""
1295+
line_counter = 2
1296+
1297+
if dataset_subsection is None:
1298+
section = self.get_dataset_section(dataset_section)
1299+
else:
1300+
section = self.get_dataset_subsection(dataset_section, dataset_subsection)
1301+
1302+
if section is None:
1303+
raise ValueError(
1304+
f"Section '{dataset_section}'{' / ' + dataset_subsection if dataset_subsection else ''} not found."
1305+
)
1306+
1307+
list_of_rows = section.locator(".noTableRow").all()
1308+
1309+
for row in list_of_rows:
1310+
elements_in_row = row.locator("xpath=.//*").all()
1311+
element_to_map = None
1312+
1313+
for element in elements_in_row:
1314+
element_class = element.get_attribute("class") or ""
1315+
if "label" in element_class:
1316+
previous_field = field_name
1317+
field_name = element.inner_text().lower().replace(" ?", "").strip()
1318+
if field_name in fields_and_elements:
1319+
name_counter = 2
1320+
while f"{field_name} ({name_counter})" in fields_and_elements:
1321+
name_counter += 1
1322+
field_name = f"{field_name} ({name_counter})"
1323+
elif "userInput" in element_class:
1324+
element_to_map = element
1325+
1326+
if field_name.replace(" ", "") != "":
1327+
fields_and_elements[field_name] = element_to_map
1328+
line_counter = 2
1329+
else:
1330+
if (
1331+
hasattr(self, "list_of_multi_line_fields")
1332+
and previous_field in self.list_of_multi_line_fields
1333+
):
1334+
field_name = previous_field
1335+
fields_and_elements[f"{field_name} ({line_counter})"] = (
1336+
element_to_map
1337+
)
1338+
line_counter += 1
1339+
1340+
logging.debug("end: map_fields_and_elements()")
1341+
return fields_and_elements
1342+
11751343

11761344
def normalize_label(text: str) -> str:
11771345
"""

pages/screening_subject_search/advance_episode_page.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,9 @@ def __init__(self, page: Page):
142142
self.date_of_symptomatic_procedure_input = self.page.get_by_role(
143143
"textbox", name="Date of Symptomatic Procedure"
144144
)
145+
self.initiate_close_button = self.page.get_by_role(
146+
"button", name="Initiate Close"
147+
)
145148

146149
# Contact recording locators
147150
self.contact_direction_dropdown = self.page.get_by_label("Contact Direction")
@@ -201,6 +204,10 @@ def click_other_post_investigation_button(self) -> None:
201204
"""Click the 'Other Post-investigation' button."""
202205
self.safe_accept_dialog(self.other_post_investigation_button)
203206

207+
def click_initiate_close_button(self) -> None:
208+
"""Click the 'Initiate Close' button."""
209+
self.safe_accept_dialog(self.initiate_close_button)
210+
204211
def get_latest_event_status_cell(self, latest_event_status: str) -> Locator:
205212
"""Get the cell containing the latest event status."""
206213
return self.page.get_by_role("cell", name=latest_event_status, exact=True)

pages/screening_subject_search/diagnostic_test_outcome_page.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import logging
2-
from click import option
31
from playwright.sync_api import Page, expect, Locator
42
from pages.base_page import BasePage
53
from enum import StrEnum
4+
from typing import List
65

76

87
class ReferralProcedureType(StrEnum):
@@ -69,6 +68,22 @@ def select_test_outcome_option(self, option: str) -> None:
6968
"""
7069
self.test_outcome_dropdown.select_option(option)
7170

71+
def test_outcome_dropdown_contains_options(self, options: List[str]) -> None:
72+
"""
73+
Asserts that all provided options are present in the Outcome of Diagnostic Test dropdown.
74+
75+
Args:
76+
options (List[str]): List of option strings to check.
77+
"""
78+
dropdown_options = [
79+
opt.inner_text()
80+
for opt in self.test_outcome_dropdown.locator("option").all()
81+
]
82+
for item in options:
83+
assert (
84+
item in dropdown_options
85+
), f"Dropdown is missing expected option: '{item}'"
86+
7287
def verify_reason_for_symptomatic_referral(self, symptomatic_reason: str) -> None:
7388
"""
7489
Verify reason for symptomatic referral is visible.

pages/screening_subject_search/handover_into_symptomatic_care_page.py

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,17 @@ def __init__(self, page: Page):
1515
self.calendar_button = self.page.get_by_role("button", name="Calendar")
1616
self.consultant_link = self.page.locator("#UI_NS_CONSULTANT_PIO_SELECT_LINK")
1717
self.practitioner_dropdown = self.page.locator("#UI_SCREENING_PRACTITIONER")
18+
self.non_screening_practitioner_link = self.page.locator(
19+
"#UI_NS_PRACTITIONER_PIO_SELECT_LINK"
20+
)
1821
self.notes_textbox = self.page.get_by_role("textbox", name="Notes")
1922
self.save_button = self.page.get_by_role("button", name="Save")
2023
self.cease_from_program_dropdown = self.page.locator(
2124
"#UI_CEASE_FROM_PROGRAM_ID"
2225
)
2326
self.mdt_date_field = self.page.locator("#UI_MDT_DATE")
2427
self.site_dropdown = self.page.locator("#UI_NS_SITE_SELECT_LINK")
28+
self.date_responsibility_accepted_field = self.page.locator("#UI_OTHER_DATE")
2529

2630
def select_referral_dropdown_option(self, value: str) -> None:
2731
"""
@@ -34,12 +38,27 @@ def select_referral_dropdown_option(self, value: str) -> None:
3438

3539
def select_practitioner_from_index(self, practitioner_index: int) -> None:
3640
"""
37-
Select the first option from the Practitioner dropdown.
41+
Select the an option from the Practitioner dropdown via index.
3842
Args:
3943
practitioner_index (int): The index of the practitioner to select.
4044
"""
4145
self.practitioner_dropdown.select_option(index=practitioner_index)
4246

47+
def select_non_screening_practitioner_link(self, practitioner_index: int) -> None:
48+
"""
49+
Select the a non screening practitioner from the non screening practitioner dropdown.
50+
Args:
51+
practitioner_index (int): The index of the non screening practitioner to select.
52+
"""
53+
self.non_screening_practitioner_link.click()
54+
select_locator = self.page.locator('select[id^="UI_RESULTS_"]:visible')
55+
select_locator.first.wait_for(state="visible")
56+
57+
# Find all option elements inside the select and click the one at the given index
58+
option_elements = select_locator.first.locator("option")
59+
option_elements.nth(practitioner_index).wait_for(state="visible")
60+
self.click(option_elements.nth(practitioner_index))
61+
4362
def click_calendar_button(self) -> None:
4463
"""Click the calendar button to open the calendar picker."""
4564
self.click(self.calendar_button)
@@ -56,6 +75,21 @@ def select_consultant(self, value: str) -> None:
5675
option_locator.wait_for(state="visible")
5776
self.click(option_locator)
5877

78+
def select_consultant_from_index(self, consultant_index: int) -> None:
79+
"""
80+
Select the a consultant from the consultant dropdown.
81+
Args:
82+
consultant_index (int): The index of the consultant to select.
83+
"""
84+
self.click(self.consultant_link)
85+
select_locator = self.page.locator('select[id^="UI_RESULTS_"]:visible')
86+
select_locator.first.wait_for(state="visible")
87+
88+
# Find all option elements inside the select and click the one at the given index
89+
option_elements = select_locator.first.locator("option")
90+
option_elements.nth(consultant_index).wait_for(state="visible")
91+
self.click(option_elements.nth(consultant_index))
92+
5993
def fill_notes(self, notes: str) -> None:
6094
"""
6195
Fill the 'Notes' textbox with the provided text.
@@ -88,6 +122,16 @@ def enter_mdt_date(self, date: datetime) -> None:
88122
"""
89123
CalendarPicker(self.page).calendar_picker_ddmmyyyy(date, self.mdt_date_field)
90124

125+
def enter_date_responsibility_accepted_field(self, date: datetime) -> None:
126+
"""
127+
Enters a date into the 'Date Responsibility Accepted' field
128+
Args:
129+
date (datetime): The date to enter.
130+
"""
131+
CalendarPicker(self.page).calendar_picker_ddmmyyyy(
132+
date, self.date_responsibility_accepted_field
133+
)
134+
91135
def select_site_dropdown_option_index(self, index: int) -> None:
92136
"""
93137
Select a given option from the site dropdown.
@@ -118,3 +162,20 @@ def fill_with_cancer_details(self) -> None:
118162
self.fill_notes("Handover notes for Cancer scenario")
119163
self.click_save_button()
120164
self.page.wait_for_timeout(500) # Timeout to allow subject to update in the DB.
165+
166+
def perform_referral_to_specific_clinician_scenario(self) -> None:
167+
"""
168+
Complete the Handover into Symptomatic Care form with the Referral to Specific Clinician scenario:
169+
Referral: Referral to Specific Clinician
170+
Date Responsibility Accepted: Today
171+
Non Screening Consultant: Last option
172+
Non Screening Practitioner: Last option
173+
Note: Handover notes - referral to Specific Clinician
174+
"""
175+
self.select_referral_dropdown_option("Referral to Specific Clinician")
176+
self.enter_date_responsibility_accepted_field(datetime.today())
177+
self.select_consultant_from_index(-1)
178+
self.select_non_screening_practitioner_link(-1)
179+
self.fill_notes("Handover notes - referral to Specific Clinician")
180+
self.click_save_button()
181+
self.page.wait_for_timeout(500) # Timeout to allow subject to update in the DB.

0 commit comments

Comments
 (0)