Skip to content

Commit e679e3c

Browse files
Merge branch 'main' of github.com:NHSDigital/bcss-playwright into feature/BCSS-21165-selenium-to-playwright-endoscopyinvestigation-dataset-scenario-11
2 parents 7b1e882 + 5d405f1 commit e679e3c

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)