Skip to content

Commit b094b55

Browse files
committed
Added test_headings_on_active_batch_list_screen and updated utils / pom accordingly
1 parent 7ca6abd commit b094b55

File tree

3 files changed

+108
-71
lines changed

3 files changed

+108
-71
lines changed

pages/communication_production/batch_list_page.py

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -128,33 +128,42 @@ class ActiveBatchListPage(BatchListPage):
128128
def __init__(self, page):
129129
super().__init__(page)
130130

131+
def assert_column_present(self, column_name: str) -> None:
132+
"""Asserts that the specified column is present in the table header."""
133+
headers = list(
134+
TableUtils(self.page, "table#batchList").get_table_headers().values()
135+
)
136+
assert (
137+
column_name in headers
138+
), f"Column '{column_name}' not found in table headers"
139+
140+
def assert_column_sortable(self, column_name: str) -> None:
141+
"""Asserts that the specified column is marked as sortable in the UI."""
142+
sort_locator = self.page.locator(
143+
"table#batchList thead tr:first-child th span.dt-column-title",
144+
has_text=column_name,
145+
)
146+
assert (
147+
sort_locator.count() > 0
148+
), f"Sortable header not found for column '{column_name}'"
149+
assert (
150+
sort_locator.first.get_attribute("role") == "button"
151+
), f"Column '{column_name}' is not sortable"
152+
153+
def assert_column_filterable(self, column_name: str) -> None:
154+
"""Asserts that the specified column has a filter control in the second header row."""
155+
table = TableUtils(self.page, "table#batchList")
156+
column_index = table.get_column_index(column_name) - 1 # Convert 1-based to 0-based
157+
158+
filter_locator = (
159+
self.page.locator("table#batchList thead tr:nth-child(2) th")
160+
.nth(column_index)
161+
.locator("input, select, div.input-group")
162+
)
163+
assert (
164+
filter_locator.count() > 0
165+
), f"Filter control not found for column '{column_name}'"
131166

132-
def verify_sortable_and_filterable_columns(self) -> None:
133-
"""
134-
Validates the presence of expected columns in the Active Batch List table.
135-
"""
136-
table = TableUtils(
137-
self.page, "table.active-batch-list"
138-
) # Adjust selector as needed
139-
expected_columns = [
140-
"ID",
141-
"Type",
142-
"Original",
143-
"Event Code",
144-
"Description",
145-
"Batch Split By",
146-
"Screening Centre",
147-
"Status",
148-
"Priority",
149-
"Deadline",
150-
"Count",
151-
]
152-
153-
for column in expected_columns:
154-
column_index = table.get_column_index(column)
155-
assert (
156-
column_index != -1
157-
), f"Column '{column}' not found in the batch list table"
158167

159168

160169
class ArchivedBatchListPage(BatchListPage):

tests/regression/communications_production/test_basic_active_batch_list_functionality_regression.py

Lines changed: 38 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import pytest
2-
from playwright.sync_api import Page, expect
2+
from playwright.sync_api import Page
33
from pages.base_page import BasePage
44
from pages.communication_production.communications_production_page import (
55
CommunicationsProductionPage,
66
)
77
from pages.communication_production.batch_list_page import ActiveBatchListPage
88
from utils.user_tools import UserTools
9-
from utils.table_util import TableUtils
109

1110

1211
@pytest.fixture(scope="function", autouse=True)
@@ -22,7 +21,43 @@ def before_each(page: Page):
2221
BasePage(page).go_to_communications_production_page()
2322
CommunicationsProductionPage(page).go_to_active_batch_list_page()
2423

25-
# @BCSSAdditionalTests @LettersTests
24+
25+
@pytest.mark.regression
26+
def test_headings_on_active_batch_list_screen(page: Page) -> None:
27+
"""
28+
Scenario: Check headings on Active Batch List Screen
29+
Given I log in to BCSS "England" as user role "HubManager"
30+
When I view the active batch list
31+
Then the table contains a sortable and filterable column for "<Column Name>"
32+
"""
33+
batch_list_page = ActiveBatchListPage(page)
34+
35+
expected_columns = [
36+
"ID",
37+
"Type",
38+
"Original",
39+
"Event Code",
40+
"Description",
41+
"Batch Split By",
42+
"Screening Centre",
43+
"Status",
44+
"Priority",
45+
"Deadline",
46+
"Count",
47+
]
48+
49+
for column in expected_columns:
50+
# Step 1: Ensure the column is present
51+
batch_list_page.assert_column_present(column)
52+
53+
# Step 2: Assert sortable UI attribute is present
54+
batch_list_page.assert_column_sortable(column)
55+
56+
# Step 3: Assert filterable control is rendered
57+
batch_list_page.assert_column_filterable(column)
58+
59+
60+
# TODO: @BCSSAdditionalTests @LettersTests
2661
# Feature: Basic Active Batch List functionality
2762

2863
# Scenario: Check navigation from Active Batch List Screen to Manage Active Batch Screen
@@ -39,29 +74,3 @@ def before_each(page: Page):
3974
# When I navigate to the Communications Production > Active Batch List Page
4075
# And I prepare the Pre-Invitation FIT letter batch
4176
# Then The Pre-Invitation FIT letter batch is no longer listed
42-
43-
@pytest.mark.regression
44-
def test_headings_on_active_batch_list_screen(page: Page) -> None:
45-
"""
46-
Confirms that the active batch list table contains a sortable and filterable column for "ID", "Type", "Original",
47-
"Event Code", "Description", "Batch Split By", "Screening Centre", "Status", "Priority", "Deadline" and "Count"
48-
"""
49-
# Scenario: Check headings on Active Batch List Screen
50-
# Given I log in to BCSS "England" as user role "HubManager"
51-
# When I view the active batch list
52-
# Then the table contains a sortable and filterable column for "ID"
53-
# And the table contains a sortable and filterable column for "Type"
54-
# And the table contains a sortable and filterable column for "Original"
55-
# And the table contains a sortable and filterable column for "Event Code"
56-
# And the table contains a sortable and filterable column for "Description"
57-
# And the table contains a sortable and filterable column for "Batch Split By"
58-
# And the table contains a sortable and filterable column for "Screening Centre"
59-
# And the table contains a sortable and filterable column for "Status"
60-
# And the table contains a sortable and filterable column for "Priority"
61-
# And the table contains a sortable and filterable column for "Deadline"
62-
# And the table contains a sortable and filterable column for "Count"
63-
batch_list_page = ActiveBatchListPage(page)
64-
# 🔍 TEMPORARY DEBUGGING
65-
table = TableUtils(page, "table.active-batch-list")
66-
print("DEBUG - Headers Found:", table.get_table_headers())
67-
batch_list_page.verify_sortable_and_filterable_columns()

utils/table_util.py

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
from playwright.sync_api import Page, Locator, expect
2-
from pages.base_page import BasePage
1+
from playwright.sync_api import Page, Locator
32
import logging
43
import secrets
54

@@ -51,17 +50,21 @@ def get_column_index(self, column_name: str) -> int:
5150
header_texts = headers.evaluate_all("ths => ths.map(th => th.innerText.trim())")
5251
logging.info(f"First Row Headers Found: {header_texts}")
5352

54-
# Extract detailed second-row headers if first-row headers seem generic
53+
# Attempt to extract a second row of headers (commonly used for filters or alternate titles)
5554
second_row_headers = self.table.locator(
5655
"thead tr:nth-child(2) th"
5756
).evaluate_all("ths => ths.map(th => th.innerText.trim())")
58-
# Merge both lists: Prioritize second-row headers if available
59-
if second_row_headers:
57+
58+
# Use the second row only if it contains meaningful text (not filters, dropdowns, or placeholder values)
59+
if second_row_headers and all(
60+
h and not any(c in h.lower() for c in ("input", "all", "select"))
61+
for h in second_row_headers
62+
):
6063
header_texts = second_row_headers
64+
logging.info(f"Second Row Headers Found: {second_row_headers}")
6165

62-
logging.info(f"Second Row Headers Found: {header_texts}")
6366
for index, header in enumerate(header_texts):
64-
if column_name.lower() in header.lower():
67+
if column_name.strip().lower() == header.strip().lower():
6568
return index + 1 # Convert to 1-based index
6669
return -1 # Column not found
6770

@@ -130,36 +133,52 @@ def get_table_headers(self) -> dict:
130133
dict: A mapping of column index (1-based) to header text.
131134
"""
132135
# Strategy 1: Try <thead> with <tr><th><span class="dt-column-title">Header</span></th>
133-
header_spans = self.page.locator(f"{self.table_id} > thead tr:first-child th span.dt-column-title")
136+
header_spans = self.page.locator(
137+
f"{self.table_id} > thead tr:first-child th span.dt-column-title"
138+
)
134139
if header_spans.count():
135140
try:
136-
header_texts = header_spans.evaluate_all("els => els.map(el => el.textContent.trim())")
141+
header_texts = header_spans.evaluate_all(
142+
"els => els.map(el => el.textContent.trim())"
143+
)
144+
print("[DEBUG] Parsed header texts:", repr(header_texts))
137145
return {idx + 1: text for idx, text in enumerate(header_texts)}
138146
except Exception as e:
139-
logging.warning(f"[get_table_headers] span.dt-column-title fallback failed: {e}")
147+
logging.warning(
148+
f"[get_table_headers] span.dt-column-title fallback failed: {e}"
149+
)
140150

141151
# Strategy 2: Fallback to standard <thead> > tr > th inner text
142-
header_cells = self.page.locator(f"{self.table_id} > thead tr").first.locator("th")
152+
header_cells = self.page.locator(f"{self.table_id} > thead tr").first.locator(
153+
"th"
154+
)
143155
if header_cells.count():
144156
try:
145-
header_texts = header_cells.evaluate_all("els => els.map(th => th.innerText.trim())")
157+
header_texts = header_cells.evaluate_all(
158+
"els => els.map(th => th.innerText.trim())"
159+
)
146160
return {idx + 1: text for idx, text in enumerate(header_texts)}
147161
except Exception as e:
148162
logging.warning(f"[get_table_headers] basic <th> fallback failed: {e}")
149163

150164
# Strategy 3: Last resort — try to find header from tbody row (some old tables use <tbody> only)
151-
fallback_row = self.table.locator("tbody tr").filter(has=self.page.locator("th")).first
165+
fallback_row = (
166+
self.table.locator("tbody tr").filter(has=self.page.locator("th")).first
167+
)
152168
if fallback_row.locator("th").count():
153169
try:
154-
header_texts = fallback_row.locator("th").evaluate_all("els => els.map(th => th.innerText.trim())")
170+
header_texts = fallback_row.locator("th").evaluate_all(
171+
"els => els.map(th => th.innerText.trim())"
172+
)
155173
return {idx + 1: text for idx, text in enumerate(header_texts)}
156174
except Exception as e:
157175
logging.warning(f"[get_table_headers] tbody fallback failed: {e}")
158176

159-
logging.warning(f"[get_table_headers] No headers found for table: {self.table_id}")
177+
logging.warning(
178+
f"[get_table_headers] No headers found for table: {self.table_id}"
179+
)
160180
return {}
161181

162-
163182
def get_row_count(self) -> int:
164183
"""
165184
This returns the total rows visible on the table (on the screen currently)

0 commit comments

Comments
 (0)