Skip to content

Commit 99f2cb1

Browse files
committed
Addressed PR comments
1 parent 3cf339c commit 99f2cb1

File tree

12 files changed

+159
-136
lines changed

12 files changed

+159
-136
lines changed

docs/utility-guides/Oracle.md

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
# Utility Guide: Oracle
22

3-
The Oracle Utility provides an easy way to interact with an Oracle database directly from your Playwright test suite.It can be used to run SQL queries or stored procedures on the Oracle database.
3+
The Oracle Utility provides an easy way to interact with an Oracle database directly from your Playwright test suite. It can be used to run SQL queries or stored procedures on the Oracle database.
44

55
## When and Why to Use the Oracle Utility
66

7-
You might need to use this utility in scenarios such as:
7+
You might use this utility for:
88

9-
- To run SQL queries or stored procedures on the Oracle database.
10-
- Verifying that data is correctly written to or updated in the database after a workflow is completed in your application.
9+
- Running SQL queries or stored procedures on the Oracle database
10+
11+
- Validating application workflows against live database records
12+
13+
- Creating test data dynamically via helper functions
14+
15+
- Verifying data integrity after system events
1116

1217
## Table of Contents
1318

@@ -25,6 +30,9 @@ You might need to use this utility in scenarios such as:
2530
## Using the Oracle Utility
2631

2732
To use the Oracle Utility, import the 'OracleDB' class into your test file and then call the OracleDB methods from within your tests, as required.
33+
```python
34+
from utils.oracle.oracle import OracleDB
35+
```
2836

2937
## Required arguments
3038

@@ -42,6 +50,7 @@ The docstrings also specify when arguments are optional, and what the default va
4250
- **execute_stored_procedure(self, `procedure_name`, params=None)**: Executes a named stored procedure with optional parameters.
4351
- **exec_bcss_timed_events(self, nhs_number_df)**: Runs the `bcss_timed_events` stored procedure for each NHS number provided in a DataFrame.
4452
- **get_subject_id_from_nhs_number(self, nhs_number)**: Retrieves the `subject_screening_id` for a given NHS number.
53+
- **create_subjects_via_sspi(...)**: Creates synthetic subjects using stored procedure `PKG_SSPI.p_process_pi_subject`
4554

4655
For full implementation details, see utils/oracle/oracle.py.
4756

@@ -68,6 +77,16 @@ def test_oracle_query() -> None:
6877

6978
def run_stored_procedure() -> None:
7079
OracleDB().execute_stored_procedure("bcss_timed_events")
80+
81+
def create_subjects_dynamically() -> None:
82+
OracleDB().create_subjects_via_sspi(
83+
count=5,
84+
screening_centre="BCS01",
85+
base_age=60,
86+
start_offset=-2,
87+
end_offset=4,
88+
nhs_start=9200000000
89+
)
7190
```
7291

7392
---

pages/communication_production/batch_list_page.py

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -104,38 +104,47 @@ def enter_type_filter(self, option_text: str) -> None:
104104
logging.info(f"[FILTER] Type filter set to '{option_text}'")
105105

106106
def enter_original_filter(self, search_text: str) -> None:
107+
"""Enter text in the Original filter and press Enter"""
107108
self.original_filter.fill(search_text)
108109
self.original_filter.press("Enter")
109110

110111
def enter_event_code_filter(self, search_text: str) -> None:
112+
"""Enter text in the Event Code filter and press Enter"""
111113
self.event_code_filter.fill(search_text)
112114
self.event_code_filter.press("Enter")
113115

114116
def enter_description_filter(self, search_text: str) -> None:
117+
"""Enter text in the Description filter and press Enter"""
115118
self.description_filter.fill(search_text)
116119
self.description_filter.press("Enter")
117120

118121
def enter_batch_split_by_filter(self, search_text: str) -> None:
122+
"""Enter text in the 'Batch Split By' filter and press Enter"""
119123
self.batch_split_by_filter.fill(search_text)
120124
self.batch_split_by_filter.press("Enter")
121125

122126
def enter_screening_centre_filter(self, search_text: str) -> None:
127+
"""Enter text in the Screening Centre filter and press Enter"""
123128
self.screening_centre_filter.fill(search_text)
124129
self.screening_centre_filter.press("Enter")
125130

126131
def enter_count_filter(self, search_text: str) -> None:
132+
"""Enter text in the Count filter and press Enter"""
127133
self.count_filter.fill(search_text)
128134
self.count_filter.press("Enter")
129135

130136
def enter_deadline_date_filter(self, date: datetime) -> None:
137+
"""Enter a date in the Deadline Date filter and press Enter"""
131138
self.click(self.deadline_calendar_picker)
132139
CalendarPicker(self.page).v2_calendar_picker(date)
133140

134141
def clear_deadline_filter_date(self) -> None:
142+
"""Clear the date in the Deadline Date filter"""
135143
self.click(self.deadline_calendar_picker)
136144
self.click(self.deadline_date_clear_button)
137145

138146
def verify_deadline_date_filter_input(self, expected_text: str) -> None:
147+
"""Verifies that the Deadline Date filter input has the expected value."""
139148
expect(self.deadline_date_filter_with_input).to_have_value(expected_text)
140149

141150
def open_letter_batch(
@@ -178,6 +187,23 @@ def open_letter_batch(
178187
expect(view_link).to_be_visible()
179188
view_link.click()
180189

190+
def select_first_batch_row(self, table_selector: str, timeout_ms: int = 0) -> None:
191+
"""
192+
Clicks the first batch link in the specified batch list table.
193+
194+
Args:
195+
table_selector (str): CSS selector for the table (e.g., "table#batchList").
196+
timeout_ms (int): Optional timeout to wait before interaction (default: 0).
197+
"""
198+
first_link = self.page.locator(f"{table_selector} tbody tr td.id a").first
199+
if timeout_ms > 0:
200+
first_link.wait_for(timeout=timeout_ms)
201+
202+
assert (
203+
first_link.count() > 0
204+
), f"No batch links found in table '{table_selector}'"
205+
first_link.click()
206+
181207

182208
class ActiveBatchListPage(BatchListPage):
183209
"""Active Batch List Page-specific methods."""
@@ -186,10 +212,8 @@ def __init__(self, page: Page):
186212
super().__init__(page, table_selector=BATCH_LIST_SELECTOR)
187213

188214
def select_first_active_batch(self) -> None:
189-
"""Clicks the first batch ID link in the active batch list."""
190-
first_link = self.page.locator(f"{self.table_selector} tbody tr td.id a").first
191-
assert first_link.count() > 0, "No active batch links found"
192-
first_link.click()
215+
"""Selects the first active batch in the batch list table."""
216+
self.select_first_batch_row(self.table_selector, timeout_ms=10000)
193217

194218
def is_batch_present(self, batch_type: str) -> bool:
195219
"""Checks if a batch of the given type exists in the active batch list."""
@@ -210,13 +234,13 @@ def get_open_original_batch_row(self) -> Locator | None:
210234
table = TableUtils(self.page, BATCH_LIST_SELECTOR)
211235
row_count = table.get_row_count()
212236

213-
for i in range(row_count):
214-
row_data = table.get_row_data_with_headers(i)
237+
for row in range(row_count):
238+
row_data = table.get_row_data_with_headers(row)
215239
if (
216240
row_data.get("Type", "").strip() == "Original"
217241
and row_data.get("Status", "").strip() == "Open"
218242
):
219-
return table.pick_row(i)
243+
return table.pick_row(row)
220244
return None
221245

222246
def assert_s83f_batch_present(self) -> None:
@@ -237,11 +261,8 @@ def __init__(self, page: Page):
237261
super().__init__(page, table_selector=BATCH_LIST_SELECTOR)
238262

239263
def select_first_archived_batch(self) -> None:
240-
"""Clicks the first batch ID link in the archived batch list."""
241-
first_batch_link = self.page.locator("table#batchList tbody tr td.id a").first
242-
first_batch_link.wait_for(timeout=10000)
243-
assert first_batch_link.count() > 0, "No archived batch links found"
244-
first_batch_link.click()
264+
"""Selects the first archived batch in the batch list table."""
265+
self.select_first_batch_row("table#batchList", timeout_ms=10000)
245266

246267
def get_archived_batch_row(
247268
self, batch_type: str, event_code: str, description: str
@@ -268,8 +289,8 @@ def get_archived_batch_row(
268289
if row_count == 0:
269290
return None
270291

271-
for i in range(row_count):
272-
row = rows.nth(i)
292+
for row in range(row_count):
293+
row = rows.nth(row)
273294

274295
try:
275296
type_text = row.locator("td").nth(1).inner_text().strip()

pages/communication_production/letter_library_index_page.py

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,23 @@ class LetterLibraryIndexPage(BasePage):
1111
def __init__(self, page: Page):
1212
super().__init__(page)
1313
self.page = page
14-
self.table_utils = TableUtils(page, DISPLAY_RS_SELECTOR)
14+
self.table_utils = TableUtils(self.page, DISPLAY_RS_SELECTOR)
1515
# Letter Library Index - page locators, methods
1616

17-
self.letter_library_index_table = page.locator(DISPLAY_RS_SELECTOR)
18-
self.define_supplementary_letter_button = page.locator(
17+
self.letter_library_index_table = self.page.locator(DISPLAY_RS_SELECTOR)
18+
self.define_supplementary_letter_button = self.page.locator(
1919
"input.HeaderButtons[value='Define Supplementary Letter']"
2020
)
21+
self.letter_description_input = self.page.locator('input[name="A_C_LETT_DESC"]')
22+
self.destination_dropdown = self.page.locator("#A_C_DESTINATION_ID")
23+
self.priority_dropdown = self.page.locator("#A_C_PRIORITY_ID")
24+
self.signatory_input = self.page.locator("#A_C_SIGNATORY")
25+
self.job_title_input = self.page.locator("#A_C_JOB_TITLE")
26+
self.paragraph_input = self.page.locator("#A_C_PARAGRAPH_1")
27+
self.save_button = self.page.get_by_role("button", name="Save")
28+
self.event_code_input = self.page.get_by_role(
29+
"textbox", name="Enter text to filter the list"
30+
)
2131

2232
def verify_letter_library_index_title(self) -> None:
2333
"""Verify the Letter Library Index page title is displayed as expected"""
@@ -62,16 +72,10 @@ def filter_by_event_code(self, event_code: str) -> None:
6272
Args:
6373
event_code (str): The event code to filter the list (e.g., 'S1')
6474
"""
65-
event_code_input = self.page.get_by_role(
66-
"textbox", name="Enter text to filter the list"
67-
)
68-
expect(event_code_input).to_be_visible()
69-
event_code_input.click()
70-
event_code_input.fill(event_code)
71-
event_code_input.press("Enter")
72-
73-
# Optional: wait for the filtered list to update
74-
self.page.wait_for_timeout(500) # tweak or replace with smart wait
75+
expect(self.event_code_input).to_be_visible()
76+
self.event_code_input.click()
77+
self.event_code_input.fill(event_code)
78+
self.event_code_input.press("Enter")
7579

7680
def click_first_letter_code_link_in_table(self) -> None:
7781
"""Clicks the first link from the Letter Library Index table."""
@@ -108,16 +112,15 @@ def define_supplementary_letter(
108112
job_title (str): Signatory's job title
109113
paragraph_text (str): Main body text of the letter
110114
"""
111-
self.page.locator('input[name="A_C_LETT_DESC"]').fill(description)
112-
self.page.locator("#A_C_DESTINATION_ID").select_option(destination_id)
113-
self.page.locator("#A_C_PRIORITY_ID").select_option(priority_id)
114-
self.page.locator("#A_C_SIGNATORY").fill(signatory)
115-
self.page.locator("#A_C_JOB_TITLE").fill(job_title)
116-
self.page.locator("#A_C_PARAGRAPH_1").fill(paragraph_text)
117-
118-
# Handle the modal popup when saving
119-
self.page.once("dialog", lambda dialog: dialog.accept())
120-
self.page.get_by_role("button", name="Save").click()
115+
self.letter_description_input.fill(description)
116+
self.destination_dropdown.select_option(destination_id)
117+
self.priority_dropdown.select_option(priority_id)
118+
self.signatory_input.fill(signatory)
119+
self.job_title_input.fill(job_title)
120+
self.paragraph_input.fill(paragraph_text)
121+
122+
# Save and accept dialog safely
123+
self.safe_accept_dialog(self.save_button)
121124

122125

123126
class LetterDefinitionDetailPage(BasePage):

pages/communication_production/manage_active_batch_page.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from pages.base_page import BasePage
33
from playwright.sync_api import expect
44
import re
5+
import logging
56

67

78
class ManageActiveBatchPage(BasePage):
@@ -19,6 +20,13 @@ def __init__(self, page: Page):
1920
self.retrieve_button_text = self.page.locator('text="Retrieve"')
2021
self.confirm_button_text = self.page.locator('text="Confirm Printed"')
2122
self.reprepare_batch_text = self.page.locator('text="Re-Prepare Batch"')
23+
self.button = self.page.get_by_role(
24+
"button", name="Retrieve and Confirm Letters"
25+
)
26+
self.message = self.page.locator(
27+
'text="Batch Successfully Archived and Printed"'
28+
)
29+
self.title_text = self.page.locator("#page-title").inner_text()
2230

2331
def click_prepare_button(self) -> None:
2432
"""Click the Prepare Batch button"""
@@ -40,20 +48,17 @@ def assert_active_batch_details_visible(self) -> None:
4048

4149
def retrieve_and_confirm_letters(self) -> None:
4250
"""Clicks the Retrieve and Confirm Letters button."""
43-
button = self.page.get_by_role("button", name="Retrieve and Confirm Letters")
44-
expect(button).to_be_enabled()
45-
button.click()
51+
expect(self.button).to_be_enabled()
52+
self.button.click()
4653

4754
def assert_confirmation_success_message(self) -> None:
4855
"""Verifies the confirmation message is shown after printing."""
49-
message = self.page.locator('text="Batch Successfully Archived and Printed"')
50-
expect(message).to_be_visible()
56+
expect(self.message).to_be_visible()
5157

5258
def get_batch_id(self) -> str:
5359
"""Extracts the batch ID from the page title."""
54-
title_text = self.page.locator("#page-title").inner_text()
55-
print(f"[DEBUG] Page title text: '{title_text}'")
56-
match = re.search(r"\d+", title_text)
60+
logging.debug(f"[BATCH ID] Page title text: '{self.title_text}'")
61+
match = re.search(r"\d+", self.title_text)
5762
if match:
5863
return match.group()
5964
raise ValueError("Batch ID not found in page title.")

pages/communication_production/manage_archived_batch_page.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,24 @@ class ManageArchivedBatchPage(BasePage):
88

99
def __init__(self, page: Page):
1010
super().__init__(page)
11+
self.page = page
12+
self.header = self.page.locator("#page-title")
13+
self.reprint_button = self.page.locator(
14+
"input.ReprintButton[value='Reprint Batch']"
15+
)
16+
self.confirmation_msg = self.page.locator(
17+
'text="Batch Successfully Archived and Printed"'
18+
)
1119

1220
def assert_archived_batch_details_visible(self) -> None:
1321
"""Verifies the Manage Archived Batch page has loaded."""
14-
header = self.page.locator("#page-title")
15-
expect(header).to_have_text("Manage Archived Batch")
22+
expect(self.header).to_have_text("Manage Archived Batch")
1623

1724
def click_reprint_button(self) -> None:
1825
"""Clicks the 'Reprint' button on the Archived Batch details page."""
19-
reprint_button = self.page.locator("input.ReprintButton[value='Reprint Batch']")
20-
expect(reprint_button).to_be_visible()
21-
reprint_button.click()
26+
expect(self.reprint_button).to_be_visible()
27+
self.reprint_button.click()
2228

2329
def confirm_archived_message_visible(self) -> None:
2430
"""Verifies that the batch was successfully archived and confirmation message is shown."""
25-
confirmation_msg = self.page.locator(
26-
'text="Batch Successfully Archived and Printed"'
27-
)
28-
expect(confirmation_msg).to_be_visible()
31+
expect(self.confirmation_msg).to_be_visible()

0 commit comments

Comments
 (0)