Skip to content

Commit 2884cfd

Browse files
Feature/bcss 21306 selenium to playwirght fobtregressiontests scenario 3 (#128)
<!-- markdownlint-disable-next-line first-line-heading --> ## Description <!-- Describe your changes in detail. --> Adding scenario 3 to the playwright test suite along with any relevant utilities, classes and POMs ## Context <!-- Why is this change required? What problem does it solve? --> Adding scenario 3 to the playwright test suite along with any relevant utilities, classes and POMs ## 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 c8e4c61 commit 2884cfd

File tree

17 files changed

+908
-34
lines changed

17 files changed

+908
-34
lines changed

classes/pi_subject.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from dataclasses import dataclass, field
22
from typing import Optional
33
from datetime import date
4+
from classes.subject import Subject
45

56

67
@dataclass
@@ -69,3 +70,48 @@ def to_string(self) -> str:
6970
f"replaced_by_nhs_number = {self.replaced_nhs_number}",
7071
]
7172
return "PISubject:\n" + "\n".join(fields)
73+
74+
@staticmethod
75+
def from_subject(subject: "Subject") -> "PISubject":
76+
"""
77+
Creates a PISubject object from a Subject object.
78+
79+
Args:
80+
subject (Subject): The Subject object to convert.
81+
82+
Returns:
83+
PISubject: The populated PISubject object.
84+
85+
"""
86+
gender = subject.get_gender()
87+
if gender is not None:
88+
gender_code = gender.redefined_value
89+
else:
90+
gender_code = 0 # If None, set to "Not known gender"
91+
return PISubject(
92+
screening_subject_id=subject.screening_subject_id,
93+
nhs_number=subject.nhs_number,
94+
family_name=subject.surname,
95+
first_given_names=subject.forename,
96+
other_given_names=subject.other_names,
97+
previous_family_name=subject.previous_surname,
98+
name_prefix=subject.title,
99+
birth_date=subject.date_of_birth,
100+
death_date=subject.date_of_death,
101+
gender_code=gender_code,
102+
address_line_1=subject.address_line1,
103+
address_line_2=subject.address_line2,
104+
address_line_3=subject.address_line3,
105+
address_line_4=subject.address_line4,
106+
address_line_5=subject.address_line5,
107+
postcode=subject.postcode,
108+
gnc_code=subject.registration_code,
109+
gp_practice_code=subject.gp_practice_code,
110+
nhais_deduction_reason=subject.nhais_deduction_reason,
111+
nhais_deduction_date=subject.nhais_deduction_date,
112+
exeter_system=subject.datasource,
113+
removed_to=subject.removed_to_datasource,
114+
pi_reference=None,
115+
superseded_by_nhs_number=None,
116+
replaced_nhs_number=None,
117+
)

classes/repositories/subject_repository.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def process_pi_subject(self, pio_id: int, pi_subject: PISubject) -> Optional[int
103103

104104
return new_contact_id
105105

106-
def update_pi_subject(self, pi_subject: PISubject) -> None:
106+
def update_pi_subject(self, pio_id: int, pi_subject: PISubject) -> None:
107107
"""
108108
Updates an existing screening subject.
109109
@@ -122,9 +122,7 @@ def update_pi_subject(self, pi_subject: PISubject) -> None:
122122
raise ValueError(
123123
"A PI Reference must be specified when updating an existing subject, for example 'SELF REFERRAL' or 'AUTOMATED TEST'"
124124
)
125-
procedure = "PKG_SSPI.p_process_pi_subject"
126-
params = [pi_subject, None, None, None, None]
127-
self.oracle_db.execute_stored_procedure(procedure, params)
125+
self.process_pi_subject(pio_id, pi_subject)
128126

129127
def get_active_gp_practice_in_hub_and_sc(
130128
self, hub_code: str, screening_centre_code: str

classes/repositories/user_repository.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def get_role_id_for_role(self, role: "UserRoleType") -> int:
9595
df = self.general_query(role)
9696
return int(df["role_id"].iloc[0])
9797

98-
def get_org_code_for_role(self, role: "UserRoleType") -> int:
98+
def get_org_code_for_role(self, role: "UserRoleType") -> str:
9999
"""
100100
Get the ORG CODE for the role.
101101
@@ -108,4 +108,4 @@ def get_org_code_for_role(self, role: "UserRoleType") -> int:
108108
logging.debug(f"Getting ORG CODE for role: {role.user_code}")
109109

110110
df = self.general_query(role)
111-
return int(df["org_code"].iloc[0])
111+
return str(df["org_code"].iloc[0])

classes/subject.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@
1010
from classes.sdd_reason_for_change_type import SDDReasonForChangeType
1111
from classes.ss_reason_for_change_type import SSReasonForChangeType
1212
from classes.ssdd_reason_for_change_type import SSDDReasonForChangeType
13+
from classes.user import User
1314
from utils.date_time_utils import DateTimeUtils
15+
from utils.oracle.oracle import OracleDB
1416
import pandas as pd
17+
import logging
1518

1619

1720
@dataclass
@@ -1302,3 +1305,34 @@ def from_dataframe_row(row: pd.Series) -> "Subject":
13021305
}
13031306

13041307
return Subject(**field_map)
1308+
1309+
def populate_subject_object_from_nhs_no(self, nhs_no: str) -> "Subject":
1310+
"""
1311+
Populates a Subject object from the NHS number.
1312+
Args:
1313+
nhs_no (str): The NHS number to populate the subject from.
1314+
Returns:
1315+
Subject: A populated Subject object from the database
1316+
"""
1317+
from utils.oracle.subject_selection_query_builder import (
1318+
SubjectSelectionQueryBuilder,
1319+
)
1320+
1321+
nhs_no_criteria = {"nhs number": nhs_no}
1322+
subject = Subject()
1323+
user = User()
1324+
builder = SubjectSelectionQueryBuilder()
1325+
1326+
query, bind_vars = builder.build_subject_selection_query(
1327+
criteria=nhs_no_criteria,
1328+
user=user,
1329+
subject=subject,
1330+
subjects_to_retrieve=1,
1331+
)
1332+
1333+
logging.debug(
1334+
"[SUBJECT ASSERTIONS] Executing base query to populate subject object"
1335+
)
1336+
1337+
subject_df = OracleDB().execute_query(query, bind_vars)
1338+
return self.from_dataframe_row(subject_df.iloc[0])

classes/user.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Optional
22
from classes.organisation_complex import Organisation
3-
import pandas as pd
3+
from classes.user_role_type import UserRoleType
44

55

66
class User:
@@ -159,3 +159,30 @@ def from_dataframe_row(self, row) -> "User":
159159
pio_id=row["pio_id"],
160160
organisation=organisation,
161161
)
162+
163+
@staticmethod
164+
def from_user_role_type(user_role_type: "UserRoleType") -> "User":
165+
"""
166+
Creates a User object from a UserRoleType object using UserRepository methods.
167+
168+
Args:
169+
user_role_type (UserRoleType): The UserRoleType object.
170+
171+
Returns:
172+
User: The constructed User object.
173+
"""
174+
from classes.repositories.user_repository import UserRepository
175+
176+
user_repo = UserRepository()
177+
pio_id = user_repo.get_pio_id_for_role(user_role_type)
178+
role_id = user_repo.get_role_id_for_role(user_role_type)
179+
org_id = user_repo.get_org_id_for_role(user_role_type)
180+
org_code = user_repo.get_org_code_for_role(user_role_type)
181+
182+
organisation = Organisation(new_id=org_id, new_code=str(org_code))
183+
return User(
184+
user_id=pio_id,
185+
role_id=role_id,
186+
pio_id=pio_id,
187+
organisation=organisation,
188+
)
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Utility Guide: SSPIChangeSteps
2+
3+
The `SSPIChangeSteps` utility provides a simple interface for simulating an SSPI update to change a subject's date of birth in the BCSS system. This is particularly useful for automated testing scenarios where you need to set a subject's age to a specific value.
4+
5+
---
6+
7+
## Table of Contents
8+
9+
- [Utility Guide: SSPIChangeSteps](#utility-guide-sspichangesteps)
10+
- [Table of Contents](#table-of-contents)
11+
- [Overview](#overview)
12+
- [Example Usage](#example-usage)
13+
- [Method Reference](#method-reference)
14+
- [Implementation Details](#implementation-details)
15+
16+
---
17+
18+
## Overview
19+
20+
The main method provided by this utility is:
21+
22+
```python
23+
sspi_update_to_change_dob_received(nhs_no: str, age_to_change_to: int)
24+
```
25+
26+
This method will:
27+
28+
- Retrieve the subject by NHS number.
29+
- Calculate the correct date of birth for the specified age (taking leap years into account).
30+
- Update the subject's date of birth in the database as if it was received from an SSPI update.
31+
32+
## Example Usage
33+
34+
```python
35+
from utils.sspi_change_steps import SSPIChangeSteps
36+
37+
nhs_no = "1234567890"
38+
target_age = 75
39+
40+
SSPIChangeSteps().sspi_update_to_change_dob_received(nhs_no, target_age)
41+
```
42+
43+
This will update the subject with NHS number `1234567890` to have an age of 75.
44+
45+
---
46+
47+
## Method Reference
48+
49+
`sspi_update_to_change_dob_received`
50+
51+
```python
52+
def sspi_update_to_change_dob_received(nhs_no: str, age_to_change_to: int) -> None
53+
```
54+
55+
**Parameters:**
56+
57+
- `nhs_no` (str): The NHS number of the subject to update.
58+
- `age_to_change_to` (int): The age to change the subject's date of birth to.
59+
60+
**Description:**
61+
62+
Calculates the correct date of birth for the given age and updates the subject in the database as if the change was received from an SSPI update.
63+
64+
---
65+
66+
## Implementation Details
67+
68+
- The utility uses the `Subject` and `PISubject` classes to represent and update subject data.
69+
- The date of birth is calculated using `DateTimeUtils.calculate_birth_date_for_age`, which ensures the correct age is set, accounting for leap years.
70+
- The update is performed as the automated process user (user ID 2).

pages/screening_practitioner_appointments/appointment_detail_page.py

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from playwright.sync_api import Page, expect
22
from pages.base_page import BasePage
3+
from enum import StrEnum
34

45

56
class AppointmentDetailPage(BasePage):
@@ -13,6 +14,10 @@ def __init__(self, page: Page):
1314
self.attended_check_box = self.page.locator("#UI_ATTENDED")
1415
self.calendar_button = self.page.get_by_role("button", name="Calendar")
1516
self.save_button = self.page.get_by_role("button", name="Save")
17+
self.cancel_radio = self.page.get_by_role("radio", name="Cancel")
18+
self.reason_for_cancellation_dropwdown = self.page.get_by_label(
19+
"Reason for Cancellation"
20+
)
1621

1722
def check_attendance_radio(self) -> None:
1823
"""Checks the attendance radio button."""
@@ -26,9 +31,16 @@ def click_calendar_button(self) -> None:
2631
"""Clicks the calendar button."""
2732
self.click(self.calendar_button)
2833

29-
def click_save_button(self) -> None:
30-
"""Clicks the save button."""
31-
self.click(self.save_button)
34+
def click_save_button(self, accept_dialog: bool = False) -> None:
35+
"""
36+
Clicks the save button.
37+
Args:
38+
accept_dialog (bool): Whether to accept the dialog.
39+
"""
40+
if accept_dialog:
41+
self.safe_accept_dialog(self.save_button)
42+
else:
43+
self.click(self.save_button)
3244

3345
def verify_text_visible(self, text: str) -> None:
3446
"""Verifies that the specified text is visible on the page."""
@@ -60,3 +72,30 @@ def wait_for_attendance_radio(self, timeout_duration: float = 30000) -> None:
6072
timeout_duration - elapsed if timeout_duration - elapsed > 0 else 1000
6173
)
6274
)
75+
76+
def check_cancel_radio(self) -> None:
77+
"""Checks the cancel radio button."""
78+
self.cancel_radio.check()
79+
80+
def select_reason_for_cancellation_option(self, option: str) -> None:
81+
"""
82+
Selects the reason for cancellation from the dropdown.
83+
Args:
84+
option: The reason for cancellation to select.
85+
The options are in the ReasonForCancellationOptions class
86+
"""
87+
self.reason_for_cancellation_dropwdown.select_option(value=option)
88+
89+
90+
class ReasonForCancellationOptions(StrEnum):
91+
"""Enum for cancellation reason options"""
92+
93+
PATIENT_REQUESTS_DISCHARGE_FROM_SCREENING = "6008"
94+
PATIENT_UNSUITABLE_RECENTLY_SCREENED = "6007"
95+
PATIENT_UNSUITABLE_CURRENTLY_UNDERGOING_TREATMENT = "6006"
96+
PATIENT_CANCELLED_TO_CONSIDER = "6005"
97+
PATIENT_CANCELLED_MOVED_OUT_OF_AREA = "6003"
98+
SCREENING_CENTRE_CANCELLED_OTHER_REASON = "6002"
99+
CLINIC_UNAVAILABLE = "6001"
100+
PRACTITIONER_UNAVAILABLE = "6000"
101+
PATIENT_CANCELLED_OTHER_REASON = "6004"
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from playwright.sync_api import Page
2+
from pages.base_page import BasePage
3+
4+
5+
class ReopenFOBTScreeningEpisodePage(BasePage):
6+
"""Reopen FOBT Screening Episode Page locators, and methods for interacting with the page."""
7+
8+
def __init__(self, page: Page):
9+
super().__init__(page)
10+
self.page = page
11+
12+
self.reopen_to_book_an_assessment_button = self.page.get_by_role(
13+
"button", name="Reopen to book an assessment"
14+
)
15+
16+
def click_reopen_to_book_an_assessment_button(self) -> None:
17+
"""Click the 'Reopen to book an assessment' button."""
18+
self.safe_accept_dialog(self.reopen_to_book_an_assessment_button)

pages/screening_subject_search/subject_screening_summary_page.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ def __init__(self, page: Page):
8383
self.latest_event_status_cell = self.page.locator(
8484
"td.epihdr_label:text('Latest Event Status') + td.epihdr_data"
8585
)
86+
self.reopen_fobt_screening_episode_button = self.page.get_by_role(
87+
"button", name="Reopen FOBT Screening Episode"
88+
)
8689

8790
def wait_for_page_title(self) -> None:
8891
"""Waits for the page to be the Subject Screening Summary"""
@@ -393,6 +396,10 @@ def assert_latest_event_status(self, expected_status: str) -> None:
393396
actual_status == expected_status
394397
), f"[LATEST EVENT STATUS MISMATCH] Expected '{expected_status}', but found '{actual_status}' in UI."
395398

399+
def click_reopen_fobt_screening_episode_button(self) -> None:
400+
"""Click on the 'Reopen FOBT Screening Episode' button"""
401+
self.click(self.reopen_fobt_screening_episode_button)
402+
396403

397404
class ChangeScreeningStatusOptions(Enum):
398405
"""Enum for Change Screening Status options."""

tests/regression/regression_tests/fobt_regression_tests/test_scenario_1.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,12 @@ def test_scenario_1(page: Page) -> None:
5252
"age (y/d)": "65/25",
5353
"active gp practice in hub/sc": "BCS01/BCS001",
5454
}
55-
nhs_no = CreateSubjectSteps().create_custom_subject(requirements, user_role)
55+
nhs_no = CreateSubjectSteps().create_custom_subject(requirements)
5656
if nhs_no is None:
5757
raise ValueError("NHS No is 'None'")
5858

5959
# Then Comment: NHS number
60-
logging.info(f"Created subject's NHS number: {nhs_no}")
60+
logging.info(f"[SUBJECT CREATION] Created subject's NHS number: {nhs_no}")
6161

6262
# Then my subject has been updated as follows:
6363
criteria = {

0 commit comments

Comments
 (0)