Skip to content

Commit 5d405f1

Browse files
Feature/bcss 21031 selenium to playwright manually cease a subject (#114)
<!-- markdownlint-disable-next-line first-line-heading --> ## Description <!-- Describe your changes in detail. --> Added tests and any required utils / updates for the feature: manually cease a subject. This includes all of the test scenarios from the feature file and the addition of the manual_cease util and util guide. ## Context <!-- Why is this change required? What problem does it solve? --> This is part of the Selenium to Playwright migration work ## 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. --------- Signed-off-by: AndyG <[email protected]> Co-authored-by: adrianoaru-nhs <[email protected]>
1 parent f4441b5 commit 5d405f1

File tree

7 files changed

+1210
-0
lines changed

7 files changed

+1210
-0
lines changed

docs/utility-guides/ManualCease.md

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# Utility Guide: Manual Cease Workflow
2+
3+
This utility facilitates creation, UI automation, and database verification for manual cease workflows in the BCSS Playwright test suite.<br> It includes:
4+
5+
1. Generating a subject eligible for manual cease via database creation and timeline event execution.
6+
2. Performing the cease workflow through the browser UI, supporting both 'immediate' and 'with disclaimer' flows.
7+
3. Validating dynamic database fields after cease is completed.
8+
9+
## Table of Contents
10+
11+
- [Utility Guide: Manual Cease Workflow](#utility-guide-manual-cease-workflow)
12+
- [Table of Contents](#table-of-contents)
13+
- [Creating Subjects for Cease](#creating-subjects-for-cease)
14+
- [Arguments](#arguments)
15+
- [How to use this method](#how-to-use-this-method)
16+
- [UI Workflow Methods](#ui-workflow-methods)
17+
- [Standard Flow (with Disclaimer)](#standard-flow-with-disclaimer)
18+
- [Immediate Flow](#immediate-flow)
19+
- [DB Verification](#db-verification)
20+
- [Arguments](#arguments-1)
21+
- [How to use this method](#how-to-use-this-method-1)
22+
- [Enums and Constants](#enums-and-constants)
23+
- [EXPECT markers](#expect-markers)
24+
- [Status \& Reason Enums](#status--reason-enums)
25+
26+
## Creating Subjects for Cease
27+
28+
The `create_manual_cease_ready_subject` method creates a subject with Inactive screening status and executes timed events, preparing it for the cease flow.
29+
30+
### Arguments
31+
32+
`screening_centre`:
33+
Type: str
34+
Screening centre to associate with the subject. Defaults to "BCS002".
35+
36+
`base_age`:
37+
Type: int
38+
Age threshold used during subject creation. Defaults to 75.
39+
40+
### How to use this method
41+
42+
```python
43+
from utils.manual_cease_util import create_manual_cease_ready_subject
44+
nhs_number = create_manual_cease_ready_subject(screening_centre="BCS002", base_age=75)
45+
```
46+
47+
## UI Workflow Methods
48+
49+
### Standard Flow (with Disclaimer)
50+
51+
`process_manual_cease_with_disclaimer(page: Page, reason: str = "Informed Dissent")` automates the full cease workflow including disclaimer steps.
52+
53+
```python
54+
from utils.manual_cease_util import process_manual_cease_with_disclaimer
55+
process_manual_cease_with_disclaimer(page, reason="Moved Away")
56+
```
57+
58+
### Immediate Flow
59+
60+
`process_manual_cease_immediate(page: Page, reason: str = "Informed Dissent")` performs the cease workflow without recording disclaimer letters.
61+
62+
```python
63+
from utils.manual_cease_util import process_manual_cease_immediate
64+
process_manual_cease_immediate(page, reason="Deceased")
65+
```
66+
67+
## DB Verification
68+
69+
The method `verify_manual_cease_db_fields_dynamic(nhs_number: str, expected: dict[str, object])` dynamically validates updated DB fields based on supplied expectations.
70+
71+
### Arguments
72+
73+
nhs_number:
74+
Type: str
75+
NHS number of the subject to validate.
76+
77+
expected:
78+
Type: dict[str, object]
79+
A dictionary mapping human-readable field labels to expected values. Supports assertions like TODAY, NULL, UNCHANGED, or direct values.
80+
81+
### How to use this method
82+
83+
```python
84+
from utils.manual_cease_util import verify_manual_cease_db_fields_dynamic, EXPECT
85+
86+
verify_manual_cease_db_fields_dynamic(nhs_number, {
87+
"Screening Status": 4008,
88+
"Screening Status Reason": 43,
89+
"Ceased Confirmation Date": EXPECT.TODAY,
90+
"Ceased Confirmation Details": "AUTO TEST: notes"
91+
})
92+
```
93+
94+
## Enums and Constants
95+
96+
### EXPECT markers
97+
98+
Used in DB assertions to express flexible expectations.
99+
EXPECT.TODAY: Field must match today's date.
100+
EXPECT.NULL: Field must be null.
101+
EXPECT.UNCHANGED: Field must exist but is not asserted.
102+
EXPECT.MATCH_USER_ID: Field must be a valid user ID.
103+
104+
### Status & Reason Enums
105+
106+
The utility includes enums for easier mapping in test code:
107+
108+
```python
109+
from utils.manual_cease_util import ScreeningStatus, ScreeningStatusReason
110+
111+
ScreeningStatus.CEASED # → 4008
112+
ScreeningStatusReason.DECEASED # → 45
113+
```
114+
115+
Refer to the source code in `utils/manual_cease_util.py` for additional mappings and helper logic.
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
from playwright.sync_api import Page
2+
import logging
3+
from datetime import datetime
4+
from pages.base_page import BasePage
5+
6+
7+
class ManualCeasePage(BasePage):
8+
"""This class contains locators to interact with the manual cease page."""
9+
10+
def __init__(self, page: Page):
11+
self.page = page
12+
self.request_cease_button = page.locator("input[name='BTN_REQUEST_CEASE']")
13+
self.cease_reason_dropdown = page.locator("#A_C_RequestCeaseReason")
14+
self.notes_textbox = page.get_by_role("textbox", name="Notes (up to 500 char)")
15+
self.date_confirmed_field = page.get_by_label("Date Confirmed")
16+
self.confirm_cease_button = page.get_by_role("button", name="Confirm Cease")
17+
self.save_request_cease_button = page.locator(
18+
"input[name='BTN_SAVE'][value='Save Request Cease']"
19+
)
20+
self.summary_table = page.locator("#screeningSummaryTable")
21+
self.record_disclaimer_sent_button = page.get_by_role(
22+
"button", name="Record Disclaimer Letter Sent"
23+
)
24+
self.confirm_disclaimer_sent_button = page.get_by_role("button", name="Confirm")
25+
self.record_return_disclaimer_button = page.get_by_role(
26+
"button", name="Record Return of Disclaimer Letter"
27+
)
28+
self.notes_field = page.get_by_label("Notes")
29+
30+
def click_request_cease(self) -> None:
31+
"""Clicks the 'Request Cease' button."""
32+
self.click(self.request_cease_button)
33+
34+
def select_cease_reason(self, reason: str) -> None:
35+
"""Selects a given cease reason from the dropdown.
36+
Args:
37+
reason (str): The reason to select from the dropdown.
38+
"""
39+
self.cease_reason_dropdown.select_option(label=reason)
40+
41+
def click_save_request_cease(self) -> None:
42+
"""Clicks the 'Save Request Cease' button."""
43+
self.click(self.save_request_cease_button)
44+
45+
def record_disclaimer_sent(self) -> None:
46+
"""Clicks the 'Record the disclaimer letter has been sent' button."""
47+
self.click(self.record_disclaimer_sent_button)
48+
49+
def confirm_disclaimer_sent(self) -> None:
50+
"""Clicks the 'Confirm disclaimer letter has been sent' button."""
51+
self.click(self.confirm_disclaimer_sent_button)
52+
53+
def record_return_of_disclaimer(self) -> None:
54+
"""Clicks the 'Record the return of the disclaimer letter' button."""
55+
self.click(self.record_return_disclaimer_button)
56+
57+
def fill_notes_and_date(self, notes: str = "AUTO TEST: notes") -> None:
58+
"""Fills in notes and today's date in the form.
59+
Args:
60+
notes (str): Notes to fill in the text box.
61+
"""
62+
self.notes_field.fill(notes)
63+
today_str = datetime.today().strftime("%d/%m/%Y")
64+
self.date_confirmed_field.fill(today_str)
65+
66+
def confirm_cease(self) -> None:
67+
"""Clicks the 'Confirm cease' button and accepts dialog."""
68+
self.safe_accept_dialog(self.confirm_cease_button)
69+
70+
def fill_notes_if_visible(self, notes: str = "AUTO TEST: notes") -> None:
71+
"""Fills in notes if the notes textbox is visible.
72+
Args:
73+
notes (str): Notes to fill in the text box.
74+
"""
75+
if self.notes_textbox.is_visible():
76+
self.notes_textbox.fill(notes)
77+
78+
def fill_date_if_visible(self, date_str: str) -> None:
79+
"""Fills in today's date if the date confirmed field is visible.
80+
Args:
81+
date_str (str): Date to fill in the date confirmed field.
82+
"""
83+
if self.date_confirmed_field.is_visible():
84+
self.date_confirmed_field.fill(date_str)
85+
86+
def confirm_or_save_cease(self) -> None:
87+
"""Confirms the cease action via either 'Confirm Cease' or 'Save Request Cease' button."""
88+
if self.confirm_cease_button.is_visible():
89+
BasePage(self.page).safe_accept_dialog(self.confirm_cease_button)
90+
91+
elif self.save_request_cease_button.is_visible():
92+
self.click(self.save_request_cease_button)
93+
else:
94+
logging.error("No cease confirmation button found!")
95+
raise RuntimeError("Cease button not found on the page")

pytest.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ markers =
4545
subject_search: tests that are part of the subject search test suite
4646
investigation_dataset_tests: tests that are part of the investigation dataset test suite
4747
skip_before_test: tests that will not use the before_test fixture
48+
manual_cease_tests: tests that are part of the manual cease test suite
4849
bcss_additional_tests: tests that are part of the BCSS additional tests test suite
4950
colonoscopy_dataset_tests: tests that are part of the colonoscopy datasets test suite
5051
fobt_diagnosis_date_entry_tests: tests that are part of fobt subject episodes record diagnosis date

0 commit comments

Comments
 (0)