Skip to content

Commit 5c2fbf5

Browse files
Adding scenario 12 and relevant POMs / Classes / Utils (#144)
Fixing scenario 11 <!-- markdownlint-disable-next-line first-line-heading --> ## Description <!-- Describe your changes in detail. --> Adding scenario 12 and fixing scenario 11, and broken subject creation. ## Context <!-- Why is this change required? What problem does it solve? --> Adding scenario 12 and fixing scenario 11, and broken subject creation. ## Type of changes <!-- What types of changes does your code introduce? Put an `x` in all the boxes that apply. --> - [x] Refactoring (non-breaking change) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would change existing functionality) - [ ] Bug fix (non-breaking change which fixes an issue) ## Checklist <!-- Go over all the following points, and put an `x` in all the boxes that apply. --> - [x] I am familiar with the [contributing guidelines](https://github.com/nhs-england-tools/playwright-python-blueprint/blob/main/CONTRIBUTING.md) - [x] I have followed the code style of the project - [x] I have added tests to cover my changes (where appropriate) - [x] I have updated the documentation accordingly - [ ] This PR is a result of pair or mob programming --- ## Sensitive Information Declaration To ensure the utmost confidentiality and protect your and others privacy, we kindly ask you to NOT including [PII (Personal Identifiable Information) / PID (Personal Identifiable Data)](https://digital.nhs.uk/data-and-information/keeping-data-safe-and-benefitting-the-public) or any other sensitive data in this PR (Pull Request) and the codebase changes. We will remove any PR that do contain any sensitive information. We really appreciate your cooperation in this matter. - [x] I confirm that neither PII/PID nor sensitive data are included in this PR and the codebase changes.
1 parent 36ba642 commit 5c2fbf5

13 files changed

+1316
-45
lines changed

classes/data/data_creation.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from classes.organisation.organisation_complex import Organisation
66
from classes.subject.gender_type import GenderType
77
from classes.address.address import Address
8-
from classes.person.person import Person
8+
from classes.person.person_data import Person
99
from classes.screening.region_type import RegionType
1010
from classes.subject.pi_subject import PISubject
1111
from utils.nhs_number_tools import NHSNumberTools
@@ -113,17 +113,17 @@ def generate_random_subject(
113113
pi_subject = PISubject()
114114
pi_subject.nhs_number = NHSNumberTools.generate_random_nhs_number()
115115
person = self.generate_random_person(random_words_list, GenderType.NOT_KNOWN)
116-
pi_subject.family_name = person.get_surname()
117-
pi_subject.first_given_names = person.get_forename()
118-
pi_subject.other_given_names = person.get_other_forenames()
119-
pi_subject.previous_family_name = person.get_previous_surname()
120-
pi_subject.name_prefix = person.get_title()
116+
pi_subject.family_name = person.surname
117+
pi_subject.first_given_names = person.forename
118+
pi_subject.other_given_names = person.other_forenames
119+
pi_subject.previous_family_name = person.previous_surname
120+
pi_subject.name_prefix = person.title
121121
pi_subject.birth_date = datetime.date.today() - datetime.timedelta(
122122
days=60 * 365
123123
)
124124

125125
pi_subject.death_date = None
126-
gender = person.get_gender()
126+
gender = person.gender
127127
if gender is not None:
128128
pi_subject.gender_code = gender.redefined_value
129129
else:
@@ -225,15 +225,15 @@ def generate_random_person(
225225
)
226226

227227
person = Person()
228-
person.set_title(self.generate_random_title(gender))
229-
person.set_forename(random_words_list.get("forename", ""))
230-
person.set_surname(random_words_list.get("surname", ""))
231-
person.set_gender(gender)
228+
person.title = self.generate_random_title(gender)
229+
person.forename = random_words_list.get("forename", "")
230+
person.surname = random_words_list.get("surname", "")
231+
person.gender = gender
232232

233233
if self.rand.randint(0, 100) < 5:
234-
person.set_other_forenames(random_words_list.get("forename2", ""))
234+
person.other_forenames = random_words_list.get("forename2", "")
235235
if self.rand.randint(0, 100) < 5:
236-
person.set_previous_surname(random_words_list.get("surname2", ""))
236+
person.previous_surname = random_words_list.get("surname2", "")
237237

238238
logging.debug("generateRandomPerson: end")
239239
return person

classes/event/event_status_type.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ class EventStatusType(Enum):
8686
"A318",
8787
"Post-investigation Appointment NOT Required - Result Letter Created",
8888
)
89+
A319 = (
90+
305787,
91+
"A319",
92+
"Refer follow-up test after return from symptomatic referral letter (Patient & GP)",
93+
)
8994
A320 = (160160, "A320", "Refer Another Test")
9095
A321 = (203141, "A321", "Manual Patient Result Letter Created")
9196
A322 = (203142, "A322", "GP Copy of Manual Patient Result Letter on Queue")
@@ -174,6 +179,7 @@ class EventStatusType(Enum):
174179
A370 = (160161, "A370", "Diagnostic Test Result Letter sent to GP")
175180
A371 = (20080, "A371", "Surgery Patient Result letter Printed")
176181
A372 = (20081, "A372", "Refer Symptomatic, GP Letter Printed")
182+
A373 = (305780, "A373", "Symptomatic result recorded")
177183
A374 = (20082, "A374", "Return to Surveillance After Symptomatic Referral")
178184
A375 = (
179185
160176,
@@ -188,6 +194,11 @@ class EventStatusType(Enum):
188194
A383 = (20421, "A383", "Handover into Symptomatic Care - Patient Letter Printed")
189195
A384 = (20420, "A384", "Discharged from Screening - GP letter not required")
190196
A385 = (20419, "A385", "Handover into Symptomatic Care")
197+
A389 = (
198+
305783,
199+
"A389",
200+
"Refer Another Diagnostic Test after return from Symptomatic Referral",
201+
)
191202
A391 = (20083, "A391", "Patient Discharge Letter Printed - No Patient Contact")
192203
A392 = (20084, "A392", "Patient Discharge Letter Printed - Patient Choice")
193204
A394 = (

classes/person/person_data.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from dataclasses import dataclass
2+
from typing import Optional
3+
from classes.subject.gender_type import GenderType
4+
5+
6+
@dataclass
7+
class Person:
8+
"""
9+
Represents a person with name, title, previous surname, other forenames, and gender.
10+
Provides methods to get full name and string representation.
11+
"""
12+
13+
surname: Optional[str] = None
14+
forename: Optional[str] = None
15+
title: Optional[str] = None
16+
other_forenames: Optional[str] = None
17+
previous_surname: Optional[str] = None
18+
gender: Optional[GenderType] = None
19+
20+
def get_full_name(self) -> str:
21+
"""
22+
Returns the full name of the person.
23+
"""
24+
return f"{self.title or ''} {self.forename or ''} {self.surname or ''}".strip()
25+
26+
def __str__(self) -> str:
27+
"""
28+
Returns a string representation of the person.
29+
"""
30+
return f"{self.title or ''} {self.forename or ''} {self.surname or ''} (gender={self.gender})".strip()

pages/screening_subject_search/advance_fobt_screening_episode_page.py

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,24 @@ def __init__(self, page: Page):
9696
self.redirect_to_reestablish_suitability_for_diagnostic_test_repatient_contact = self.page.get_by_role(
9797
"button", name="Redirect to re-establish"
9898
)
99+
self.mdt_referral_required_button = self.page.get_by_role(
100+
"button", name="MDT Referral Required"
101+
)
102+
self.mdt_referral_not_required_button = self.page.get_by_role(
103+
"button", name="MDT Referral Not Required"
104+
)
105+
self.non_neoplastic_and_other_non_bowel_cancer_result_button = (
106+
self.page.get_by_role(
107+
"button", name="Non-neoplastic and Other Non-bowel Cancer Result"
108+
)
109+
)
110+
self.return_to_fobt_after_symptomatic_referral_button = self.page.get_by_role(
111+
"button", name="Return to FOBT after Symptomatic Referral"
112+
)
113+
self.refer_another_diagnostic_test_after_return_from_symptomatic_referral_button = self.page.get_by_role(
114+
"button",
115+
name="Refer Another Diagnostic Test after return from Symptomatic Referral",
116+
)
99117
# Contact recording locators
100118
self.contact_direction_dropdown = self.page.get_by_label("Contact Direction")
101119
self.contact_made_between_dropdown = self.page.get_by_label(
@@ -117,9 +135,6 @@ def __init__(self, page: Page):
117135
self.ct_colonography_test_type_dropdown = self.page.locator(
118136
"#UI_EXT_TEST_TYPE_38"
119137
)
120-
self.invite_for_diagnostic_test_button = self.page.get_by_role(
121-
"button", name="Invite for Diagnostic Test >>"
122-
)
123138

124139
def click_suitable_for_endoscopic_test_button(self) -> None:
125140
"""Click the 'Suitable for Endoscopic Test' button."""
@@ -396,3 +411,29 @@ def select_any_practitioner(self) -> None:
396411
return
397412

398413
logging.warning("[CONTACT RECORD] No valid practitioner found to select")
414+
415+
def click_mdt_referral_required_button(self) -> None:
416+
"""Click the 'MDT Referral Required' button."""
417+
self.safe_accept_dialog(self.mdt_referral_required_button)
418+
419+
def click_mdt_referral_not_required_button(self) -> None:
420+
"""Click the 'MDT Referral Not Required' button."""
421+
self.safe_accept_dialog(self.mdt_referral_not_required_button)
422+
423+
def click_non_neoplastic_and_other_non_bowel_cancer_result_button(self) -> None:
424+
"""Click the 'Non-neoplastic and Other Non-bowel Cancer Result' button."""
425+
self.safe_accept_dialog(
426+
self.non_neoplastic_and_other_non_bowel_cancer_result_button
427+
)
428+
429+
def click_return_to_fobt_after_symptomatic_referral_button(self) -> None:
430+
"""Click the 'Return to FOBT after Symptomatic Referral' button."""
431+
self.safe_accept_dialog(self.return_to_fobt_after_symptomatic_referral_button)
432+
433+
def click_refer_another_diagnostic_test_after_return_from_symptomatic_referral_button(
434+
self,
435+
) -> None:
436+
"""Click the 'Refer Another Diagnostic Test after return from Symptomatic Referral' button."""
437+
self.click(
438+
self.refer_another_diagnostic_test_after_return_from_symptomatic_referral_button
439+
)

pages/screening_subject_search/diagnostic_test_outcome_page.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ class ReasonForOnwardReferral(StrEnum):
1414
CURRENTLY_UNSUITABLE_FOR_ENDOSCOPIC_REFERRAL = "20358"
1515
FURTHER_CLINICAL_ASSESSMENT = "20359"
1616
INCOMPLETE_COLONIC_VISUALISATION = "20481"
17+
POLYP_EXCISION = "203011"
18+
CORRECTIVE_SURGERY = "203012"
19+
SUSPECTED_CANCER_SURGERY = "203013"
1720

1821

1922
class DiagnosticTestOutcomePage(BasePage):
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
from datetime import datetime
2+
from playwright.sync_api import Page
3+
from pages.base_page import BasePage
4+
from utils.calendar_picker import CalendarPicker
5+
6+
7+
class NonNeoplasticResultFromSymptomaticProcedurePage(BasePage):
8+
"""Non Neoplastic Result From Symptomatic Procedure Page locators, and methods for interacting with the page."""
9+
10+
def __init__(self, page: Page):
11+
super().__init__(page)
12+
# Non Neoplastic Result From Symptomatic Procedure - page locators
13+
self.date_of_symptomatic_procedure_calendar_button = self.page.locator(
14+
"#UI_SURGERY_DATE__LinkOrButton"
15+
)
16+
self.alert_textbox = self.page.locator("#UI_SPAN_RECALL_TEXT")
17+
self.all_tests = self.page.locator("#UI_ID_RECALL_ANCHOR_DATE_EXT_TEST_ID")
18+
self.save_button = self.page.get_by_role("button", name="Save")
19+
20+
def click_date_of_symptomatic_procedure_calendar_button(self) -> None:
21+
"""Click the date of symptomatic procedure calendar button."""
22+
self.click(self.date_of_symptomatic_procedure_calendar_button)
23+
24+
def enter_date_of_symptomatic_procedure(self, date: datetime) -> None:
25+
"""
26+
Enter the date of the symptomatic procedure.
27+
Args:
28+
date (datetime): The date to be entered in the date of symptomatic procedure field. Example: datetime(2023, 10, 25)
29+
"""
30+
self.click_date_of_symptomatic_procedure_calendar_button()
31+
CalendarPicker(self.page).v1_calender_picker(date)
32+
33+
def assert_text_in_alert_textbox(self, expected_text: str) -> None:
34+
"""
35+
Assert that the expected text is present in the alert textbox.
36+
Args:
37+
expected_text (str): The text expected to be found in the alert textbox. Example: "This is a test alert"
38+
"""
39+
actual_text = self.alert_textbox.inner_text()
40+
assert (
41+
expected_text in actual_text
42+
), f"Expected text '{expected_text}' not found in alert textbox. Actual text: '{actual_text}'"
43+
44+
def select_test_number(self, test_number: int) -> None:
45+
"""
46+
Select a test from the all tests dropdown by its index.
47+
Args:
48+
test_number (int): The index of the test to select (1-based index). Example: if you want to select the 1st test pass in 1
49+
"""
50+
self.click(self.all_tests.nth(test_number - 1))
51+
52+
def click_save_button(self) -> None:
53+
"""Click the 'Save' button."""
54+
self.click(self.save_button)

pages/screening_subject_search/reopen_fobt_screening_episode_page.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ def __init__(self, page: Page):
2929
self.reopen_to_reschedule_diagnostic_test_button = self.page.get_by_role(
3030
"button", name="Reopen to Reschedule Diagnostic Test"
3131
)
32+
self.reopen_to_rerecord_outcome_from_symptomatic_referral_button = (
33+
self.page.get_by_role(
34+
"button", name="Reopen to Re-record Outcome from Symptomatic Referral"
35+
)
36+
)
3237

3338
def click_reopen_to_book_an_assessment_button(self) -> None:
3439
"""Click the 'Reopen to book an assessment' button."""
@@ -55,3 +60,9 @@ def click_reopen_to_confirm_diagnostic_test_result_and_outcome_button(self) -> N
5560
def click_reopen_to_reschedule_diagnostic_test_button(self) -> None:
5661
"""Click the 'Reopen to Reschedule Diagnostic Test' button."""
5762
self.safe_accept_dialog(self.reopen_to_reschedule_diagnostic_test_button)
63+
64+
def click_reopen_to_rerecord_outcome_from_symptomatic_referral_button(self) -> None:
65+
"""Click the 'Reopen to Re-record Outcome from Symptomatic Referral' button."""
66+
self.safe_accept_dialog(
67+
self.reopen_to_rerecord_outcome_from_symptomatic_referral_button
68+
)
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from playwright.sync_api import Page
2+
from pages.base_page import BasePage
3+
4+
5+
class ReturnFromSymptomaticReferralPage(BasePage):
6+
"""Return From Symptomatic Referral Page locators, and methods for interacting with the page."""
7+
8+
def __init__(self, page: Page):
9+
super().__init__(page)
10+
# Return From Symptomatic Referral - page locators
11+
self.radiological_or_endoscopic_referral_dropdown = self.page.locator(
12+
"#referralTypeId"
13+
)
14+
self.reason_for_onward_referral_dropdown = self.page.locator(
15+
"#referralReasonId"
16+
)
17+
self.save_button = self.page.get_by_role("button", name="Save")
18+
19+
def select_radiological_or_endoscopic_referral_option(
20+
self, referral_type: str
21+
) -> None:
22+
"""
23+
Select a radiological or endoscopic referral from the dropdown.
24+
Args:
25+
referral_type (str): The referral type to be selected in the dropdown. Example: "Colonoscopy"
26+
"""
27+
self.radiological_or_endoscopic_referral_dropdown.select_option(
28+
label=referral_type
29+
)
30+
31+
def select_reason_for_onward_referral_option(
32+
self, reason_for_referral: str
33+
) -> None:
34+
"""
35+
Select a reason for onward referral from the dropdown.
36+
Args:
37+
reason_for_referral (str): The reason for onward referral to be selected in the dropdown. Example: "Further Clinical Assessment"
38+
"""
39+
self.reason_for_onward_referral_dropdown.select_option(
40+
label=reason_for_referral
41+
)
42+
43+
def click_save_button(self) -> None:
44+
"""Click the 'Save' button."""
45+
self.click(self.save_button)

pages/screening_subject_search/subject_demographic_page.py

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
from playwright.sync_api import Page, expect
1+
from playwright.sync_api import Page, Dialog
22
from pages.base_page import BasePage
33
from datetime import datetime
44
from utils.calendar_picker import CalendarPicker
5+
import logging
56

67

78
class SubjectDemographicPage(BasePage):
@@ -13,11 +14,9 @@ def __init__(self, page: Page):
1314
# Subject Demographic - page filters
1415
self.forename_field = self.page.get_by_role("textbox", name="Forename")
1516
self.surname_field = self.page.get_by_role("textbox", name="Surname")
16-
self.postcode_field = self.page.get_by_role("textbox", name="Postcode")
17+
self.postcode_field = self.page.locator("#UI_SUBJECT_POSTCODE")
1718
self.dob_field = self.page.get_by_role("textbox", name="Date of Birth")
18-
self.update_subject_data_button = self.page.get_by_role(
19-
"button", name="Update Subject Data"
20-
)
19+
self.update_subject_data_button = self.page.locator("#BTN_DEMOG_UPDATE_SUBJECT")
2120
self.temporary_address_show_link = (
2221
self.page.locator("font")
2322
.filter(has_text="Temporary Address show")
@@ -126,6 +125,39 @@ def click_update_subject_data_button(self) -> None:
126125
"""Clicks on the 'Update Subject Data' button"""
127126
self.click(self.update_subject_data_button)
128127

128+
def click_update_subject_data_button_and_assert_dialog(
129+
self, expected_text: str
130+
) -> None:
131+
"""
132+
Clicks on the 'Update Subject Data' button and asserts that a dialog appears with the expected text.
133+
Args:
134+
expected_text (str): The text expected to be found in the dialog.
135+
"""
136+
self._dialog_assertion_error = None
137+
138+
def handle_dialog(dialog: Dialog):
139+
"""
140+
Handles the dialog and asserts that the dialog contains the expected text.
141+
Args:
142+
dialog (Dialog): the playwright dialog object
143+
"""
144+
logging.info(f"Dialog appeared with message: {dialog.message}")
145+
actual_text = dialog.message
146+
try:
147+
assert (
148+
expected_text in actual_text
149+
), f"Expected dialog to contain '{expected_text}', but got '{actual_text}'"
150+
except AssertionError as e:
151+
self._dialog_assertion_error = e
152+
try:
153+
dialog.accept()
154+
except Exception:
155+
logging.warning("Dialog already accepted or handled")
156+
157+
self.page.once("dialog", lambda dialog: handle_dialog(dialog))
158+
self.click(self.update_subject_data_button)
159+
self.page.wait_for_timeout(2000)
160+
129161
def get_dob_field_value(self) -> str:
130162
"""
131163
Returns the value in the date of birth input textbox

0 commit comments

Comments
 (0)