diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 00000000..db07c4a1
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1 @@
+* @NHSDigital/bs-select-test-automation-code-owners
diff --git a/.github/actions/run-playwright-tests/action.yml b/.github/actions/run-unit-tests/action.yml
similarity index 59%
rename from .github/actions/run-playwright-tests/action.yml
rename to .github/actions/run-unit-tests/action.yml
index 03cf714c..bf6770c5 100644
--- a/.github/actions/run-playwright-tests/action.yml
+++ b/.github/actions/run-unit-tests/action.yml
@@ -1,4 +1,4 @@
-name: "Run Util & Example Tests"
+name: "Run Util Tests"
runs-on: ubuntu-latest
timeout-minutes: 3
runs:
@@ -13,9 +13,6 @@ runs:
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- - name: Ensure browsers are installed
- shell: bash
- run: python -m playwright install --with-deps
- name: Run util tests
shell: bash
run: pytest -m "utils" --ignore=tests/
@@ -25,12 +22,3 @@ runs:
name: result-output-utils
path: test-results/
retention-days: 3
- - name: Run example tests
- shell: bash
- run: pytest --ignore=tests_utils/
- - uses: actions/upload-artifact@v4
- if: ${{ !cancelled() }}
- with:
- name: result-output-example
- path: test-results/
- retention-days: 3
diff --git a/.github/workflows/stage-2-test.yaml b/.github/workflows/stage-2-test.yaml
index 48a47ca6..4249e42d 100644
--- a/.github/workflows/stage-2-test.yaml
+++ b/.github/workflows/stage-2-test.yaml
@@ -30,11 +30,11 @@ on:
jobs:
run-tests:
- name: "Run Util & Example Tests"
+ name: "Run Util Tests"
runs-on: ubuntu-latest
timeout-minutes: 3
steps:
- name: "Checkout code"
uses: actions/checkout@v4
- - name: "Run Playwright Tests"
- uses: ./.github/actions/run-playwright-tests
+ - name: "Run Util Tests"
+ uses: ./.github/actions/run-unit-tests
diff --git a/.gitignore b/.gitignore
index cf6c86b8..b96516f2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,3 +19,12 @@ axe-reports/
local.env
# Please, add your custom content below!
+
+__pycache__/
+.pytest_cache/
+.venv/
+screenshot/
+tmp/
+*.dump
+.env
+.pgpass
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 9ccb82cc..7df57148 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -28,5 +28,9 @@
"."
],
"python.testing.unittestEnabled": false,
- "python.testing.pytestEnabled": true
+ "python.testing.pytestEnabled": true,
+ "[python]": {
+ "editor.defaultFormatter": "ms-python.black-formatter",
+ "editor.formatOnSave": true
+ }
}
diff --git a/buildBase.dockerfile b/buildBase.dockerfile
index 7df1219b..f600393e 100644
--- a/buildBase.dockerfile
+++ b/buildBase.dockerfile
@@ -1,18 +1,35 @@
FROM python:3.13-slim
+RUN addgroup --system nonroot \
+ && adduser --system --home /home/nonroot nonroot --ingroup nonroot
+
WORKDIR /test
+# Install dependencies
+# Try posgres client install
+RUN apt-get update && apt-get --no-install-recommends install -y libpq-dev && rm -rf /var/lib/apt/lists/*
+
+ENV HOME=/home/nonroot
+ENV PATH="$HOME/.local/bin:$PATH"
+
# Install dependencies
COPY ./requirements.txt ./requirements.txt
-RUN pip install --no-cache-dir -r requirements.txt
-RUN playwright install --with-deps
-RUN playwright install chrome
+RUN pip install --no-cache-dir -r requirements.txt && \
+ playwright install --with-deps && \
+ playwright install chrome && \
+ mkdir -p /tests/ && \
+ mkdir -p /utils/ && \
+ mkdir -p /pages/
-RUN mkdir -p /tests/
COPY ./tests/ ./tests/
-RUN mkdir -p /utils/
COPY ./utils/ ./utils/
+COPY ./pages/ ./pages/
+COPY ./conftest.py ./conftest.py
COPY ./pytest.ini ./pytest.ini
COPY ./run_tests.sh ./run_tests.sh
+COPY ./users.json ./users.json
+
+RUN chmod +x ./run_tests.sh \
+ && chown -R nonroot:nonroot /test
-RUN chmod +x ./run_tests.sh
+USER nonroot
diff --git a/conftest.py b/conftest.py
index 99c9ee54..3ba907bf 100644
--- a/conftest.py
+++ b/conftest.py
@@ -9,7 +9,24 @@
from dotenv import load_dotenv
from pathlib import Path
-LOCAL_ENV_PATH = Path(os.getcwd()) / 'local.env'
+from pages.ni_ri_sp_batch_page import NiRiSpBatchPage
+
+# from utils.db_util import DbUtil
+import logging
+from playwright.sync_api import sync_playwright
+
+# from utils.db_restore import DbRestore
+from utils.user_tools import UserTools
+
+logger = logging.getLogger(__name__)
+from playwright.sync_api import Page
+from pages.rlp_cohort_list_page import CohortListPage
+from pages.rlp_location_list_page import ScreeningLocationListPage
+from pages.rlp_unit_list_page import ScreeningUnitListPage
+from pages.main_menu import MainMenuPage
+
+
+LOCAL_ENV_PATH = Path(os.getcwd()) / "local.env"
@pytest.fixture(autouse=True, scope="session")
@@ -24,3 +41,144 @@ def import_local_env_file() -> None:
"""
if Path.is_file(LOCAL_ENV_PATH):
load_dotenv(LOCAL_ENV_PATH, override=False)
+
+
+@pytest.fixture(scope="session")
+def user_tools() -> UserTools:
+ return UserTools()
+
+
+@pytest.fixture
+def main_menu(page: Page) -> MainMenuPage:
+ return MainMenuPage(page)
+
+
+@pytest.fixture
+def rlp_location_list_page(page: Page) -> ScreeningLocationListPage:
+ return ScreeningLocationListPage(page)
+
+
+@pytest.fixture
+def rlp_cohort_list_page(page: Page) -> CohortListPage:
+ return CohortListPage(page)
+
+
+@pytest.fixture
+def rlp_unit_list_page(page: Page) -> ScreeningUnitListPage:
+ return ScreeningUnitListPage(page)
+
+
+@pytest.fixture
+def ni_ri_sp_batch_page(page: Page) -> NiRiSpBatchPage:
+ return NiRiSpBatchPage(page)
+
+
+# ## Fixture for ci-infra
+# @pytest.fixture
+# def db_util():
+# db = DbUtil(host = os.getenv("CI_INFRA_DB_HOST"),
+# port=os.getenv("CI_INFRA_DB_PORT"),
+# dbname=os.getenv("CI_INFRA_DBNAME"),
+# user=os.getenv("CI_INFRA_DB_USER"),
+# password=os.getenv("CI_INFRA_DB_PASSWORD"))
+# return db
+
+# ## Fixture is for VM local database
+# @pytest.fixture
+# def db_util_local():
+# db = DbUtil(host = os.getenv("LOCAL_DB_HOST"),
+# port=os.getenv("LOCAL_DB_PORT"),
+# dbname=os.getenv("LOCAL_DBNAME"),
+# user=os.getenv("LOCAL_DB_USER"),
+# password=os.getenv("LOCAL_DB_PASSWORD"))
+# return db
+
+# # @pytest.fixture(scope="session", autouse=True)
+# def db_restore():
+# DbRestore().full_db_restore()
+
+# This variable is used for JSON reporting only
+ENVIRONMENT_DATA = None
+
+
+@pytest.fixture(autouse=True, scope="session")
+def environment_info(metadata: object, base_url: str) -> None:
+
+ def filter_result(results: dict, key: str) -> str:
+ """This is for tidying up the response for the HTML report"""
+ string_to_return = ""
+ for item in results[key]:
+ if string_to_return != "":
+ string_to_return += "
"
+ string_to_return += f"{item}: {results[key][item]}"
+ return string_to_return
+
+ if base_url is not None:
+ try: # Try to get metadata first using a playwright object, but don't fail if it can't retrieve it
+ with sync_playwright() as playwright:
+ browser = playwright.chromium.launch(headless=True)
+ context = browser.new_context(base_url=base_url)
+ result = context.request.get(
+ "/bss/info",
+ headers={
+ "Host": f"{base_url}".replace("https://", "").replace("/", ""),
+ "Accept": "*/*",
+ "Accept-Encoding": "gzip, deflate, br",
+ },
+ ignore_https_errors=True,
+ )
+ metadata["Application Details"] = filter_result(
+ result.json(), "Application Details"
+ )
+ metadata["Database Details"] = filter_result(
+ result.json(), "Database Details"
+ )
+ global ENVIRONMENT_DATA
+ ENVIRONMENT_DATA = result.json()
+ context.close()
+ browser.close()
+
+ except Exception as ex:
+ logger.warning("Not been able to capture environment data for this run.")
+ logger.warning(f"Exception: {ex}")
+ metadata["Application Details"] = "Unable to retrieve"
+ metadata["Database Details"] = "Unable to retrieve"
+
+
+# --- JSON Report Generation ---
+
+
+@pytest.hookimpl(optionalhook=True)
+def pytest_json_runtest_metadata(item: object) -> dict:
+ formatted_description = str(item.function.__doc__).replace("\n", "")
+ return {"description": " ".join(formatted_description.split())}
+
+
+@pytest.hookimpl(optionalhook=True)
+def pytest_json_modifyreport(json_report: object) -> None:
+ # Add env data to json report if present
+ if ENVIRONMENT_DATA != None:
+ json_report["environment_data"] = ENVIRONMENT_DATA
+
+
+# --- HTML Report Generation ---
+
+
+def pytest_html_report_title(report: object) -> None:
+ report.title = "BS-Select Test Automation Report"
+
+
+def pytest_html_results_table_header(cells: list) -> None:
+ cells.insert(2, "
Description | ")
+
+
+def pytest_html_results_table_row(report: object, cells: list) -> None:
+ description = getattr(report, "description", "N/A")
+ cells.insert(2, f"{description} | ")
+
+
+@pytest.hookimpl(hookwrapper=True)
+def pytest_runtest_makereport(item: object) -> object:
+ outcome = yield
+ report = outcome.get_result()
+ report.description = str(item.function.__doc__)
diff --git a/pages/base_page.py b/pages/base_page.py
new file mode 100644
index 00000000..5c1a4f41
--- /dev/null
+++ b/pages/base_page.py
@@ -0,0 +1,23 @@
+import logging
+from playwright.sync_api import Page, expect
+
+
+class BasePage:
+
+ def __init__(self, page: Page) -> None:
+ self.page = page
+
+ def verify_header(self, header: str) -> None:
+ expect(self.page.get_by_role("heading")).to_contain_text(header)
+
+ def select_menu_option(self, menu_option: str, sub_menu_option: str = "") -> None:
+ if sub_menu_option == "":
+ self.page.get_by_role("link", name=menu_option).click()
+ logging.info(f"Navigated to: {menu_option}")
+ else:
+ self.page.get_by_text(menu_option).hover()
+ self.page.get_by_role("link", name=sub_menu_option).click()
+ logging.info(f"Navigated to: {menu_option} -> {sub_menu_option}")
+
+ def logout(self) -> None:
+ self.page.get_by_role("link", name="Logout").click()
diff --git a/pages/bso_mapping/gp_practice_list.py b/pages/bso_mapping/gp_practice_list.py
new file mode 100644
index 00000000..cf8a9792
--- /dev/null
+++ b/pages/bso_mapping/gp_practice_list.py
@@ -0,0 +1,17 @@
+import logging
+from playwright.sync_api import Page, expect
+from pages.report_page import ReportPage
+
+
+class GPListPage(ReportPage):
+
+ # Selectable Options
+
+ HEADER = "GP Practice List"
+
+ def __init__(self, page: Page) -> None:
+ ReportPage.__init__(self, page)
+ self.page = page
+
+ def verify_header(self) -> None:
+ super().verify_header(self.HEADER)
diff --git a/pages/login/cognito_authentication.py b/pages/login/cognito_authentication.py
new file mode 100644
index 00000000..85cbbe56
--- /dev/null
+++ b/pages/login/cognito_authentication.py
@@ -0,0 +1,14 @@
+import logging
+from playwright.sync_api import Page, expect
+
+
+class CognitoAuthenticationPage:
+
+ def __init__(self, page: Page) -> None:
+ self.page = page
+
+ def cognito_login(self, username: str, password: str) -> None:
+ logging.info(f"Logging in as: {username}")
+ self.page.get_by_role("textbox", name="Username").fill(username)
+ self.page.get_by_role("textbox", name="Password").fill(password)
+ self.page.get_by_role("button", name="submit").click()
diff --git a/pages/login/org_selection.py b/pages/login/org_selection.py
new file mode 100644
index 00000000..305469f8
--- /dev/null
+++ b/pages/login/org_selection.py
@@ -0,0 +1,33 @@
+import logging
+from playwright.sync_api import Page, expect
+from pages.base_page import BasePage
+
+
+PAGE_HEADER = "Choose Organisation"
+
+
+class OrgSelectionPage(BasePage):
+
+ def __init__(self, page: Page) -> None:
+ BasePage.__init__(self, page)
+ self.page = page
+
+ def org_selection(self, role: str = None) -> None:
+ expect(self.page.get_by_text("Breast Screening Select")).to_be_visible()
+ if self.page.url.endswith("/bss/orgChoice"):
+ self.verify_header()
+ if role is not None:
+ logging.info(f"Selecting role: {role}")
+
+ options = self.page.locator("#chosenOrgCode option").all()
+ for option in options:
+ if option.get_attribute("value") == role:
+ self.page.locator("#chosenOrgCode").select_option(role)
+ break
+ else:
+ raise AssertionError(f"Role '{role}' not found on /orgChoice screen.")
+
+ self.page.get_by_role("button", name="Select Organisation").click()
+
+ def verify_header(self) -> None:
+ return super().verify_header(PAGE_HEADER)
diff --git a/pages/main_menu.py b/pages/main_menu.py
new file mode 100644
index 00000000..25ca298d
--- /dev/null
+++ b/pages/main_menu.py
@@ -0,0 +1,15 @@
+import logging
+from playwright.sync_api import Page
+from pages.base_page import BasePage
+
+
+HEADER = "Welcome to Breast Screening Select"
+
+
+class MainMenuPage(BasePage):
+
+ def __init__(self, page: Page) -> None:
+ self.page = page
+
+ def verify_header(self) -> None:
+ return super().verify_header(HEADER)
diff --git a/pages/monitoring_reports/ceased_unceased.py b/pages/monitoring_reports/ceased_unceased.py
new file mode 100644
index 00000000..7dc9c354
--- /dev/null
+++ b/pages/monitoring_reports/ceased_unceased.py
@@ -0,0 +1,116 @@
+import logging
+from playwright.sync_api import Page, expect
+from pages.report_page import ReportPage
+
+
+class CeasedUnceasedPage(ReportPage):
+
+ # Selectable Options
+
+ HEADER = "Ceased/Unceased Subject List"
+ AGE_OPTIONS=["All", "In BSO Age Range"]
+ REASON_OPTIONS=["All", "Informed Choice", "Bilateral Mastectomy", "Mental Capacity Act", "Personal Welfare"],
+ TABLE_ID="#subjectCeasingList"
+ TABLE_FIRST_ROW=f"{TABLE_ID} > tbody > tr:nth-child(1)"
+ TABLE_FIRST_NHS_NUMBER=f"{TABLE_FIRST_ROW} > td:nth-child(3)"
+ TABLE_FIRST_FAMILY_NAME=f"{TABLE_FIRST_ROW} > td:nth-child(7)"
+ TABLE_FIRST_FIRST_NAME=f"{TABLE_FIRST_ROW} > td:nth-child(8)"
+ API_REQUEST="**/bss/report/ceasing/search**"
+
+ def __init__(self, page: Page) -> None:
+ ReportPage.__init__(self, page)
+ self.page = page
+
+ def verify_header(self) -> None:
+ super().verify_header(self.HEADER)
+
+ def search_both(self, run_search: bool = True) -> None:
+ self.page.get_by_label("Both").check()
+ if run_search:
+ self.press_search()
+
+ def search_ceased(self, run_search: bool = True) -> None:
+ self.page.get_by_label("Ceased Subjects").nth(0).check()
+ if run_search:
+ self.press_search()
+
+ def search_unceased(self, run_search: bool = True) -> None:
+ self.page.get_by_label("Unceased Subjects").check()
+ if run_search:
+ self.press_search()
+
+ def press_search(self) -> None:
+ self.page.get_by_role("button", name=" Search").click()
+
+ def both_date(self) -> None:
+ self.page.get_by_label("Both").check()
+ self.page.get_by_label("Ceased/Unceased From").click()
+ self.page.get_by_label("Ceased/Unceased From").fill("01/01/2015")
+ self.page.get_by_label("Ceased/Unceased Until").click()
+ self.page.get_by_role("cell", name="14").click()
+ self.page.get_by_role("button", name=" Search").click()
+
+ def ceased_only_date(self) -> None:
+ self.page.get_by_label("Ceased Subjects", exact=True).check()
+ self.page.get_by_label("Ceased From").click()
+ self.page.get_by_label("Ceased From").fill("01/01/2015")
+ self.page.get_by_label("Ceased Until").click()
+ self.page.get_by_role("cell", name="14").click()
+ self.page.get_by_role("button", name=" Search").click()
+
+ def unceased_only_date(self) -> None:
+ self.page.get_by_label("Unceased Subjects").check()
+ self.page.get_by_label("Unceased From").click()
+ self.page.get_by_label("Unceased From").fill("01/01/2015")
+ self.page.get_by_label("Unceased Until").click()
+ self.page.get_by_role("cell", name="14").click()
+ self.page.get_by_role("button", name=" Search").click()
+
+ def set_done_drop_down(self, value: str) -> None:
+ self.page.locator("#actionList").select_option(value)
+ self.page.wait_for_timeout(5000)
+
+ def enter_nhs_number(self, selected_nhs: str) -> None:
+ self.page.locator("#nhsNumberFilter").get_by_role("textbox").fill(selected_nhs)
+ with self.page.expect_response(self.API_REQUEST) as response:
+ pass
+
+ def sort_date_added_to_BSO(self) -> None:
+ self.page.locator("#actionList").select_option("")
+ self.page.get_by_label("Date Added To BSO: activate").click()
+ self.page.wait_for_timeout(5000)
+
+ def sort_born(self) -> None:
+ self.page.locator("#actionList").select_option("")
+ self.page.get_by_label("Born: activate to sort column").click()
+ self.page.wait_for_timeout(5000)
+
+ def table_filtered_by_age(self, selected_age: str) -> None:
+ self.page.locator("#ageTodayList").select_option(selected_age)
+ with self.page.expect_response(self.API_REQUEST) as response:
+ pass
+
+ def enter_family_name(self, selected_family_name: str) -> None:
+ self.page.locator("#familyNameFilter").get_by_role("textbox").fill(selected_family_name)
+ with self.page.expect_response(self.API_REQUEST) as response:
+ pass
+
+ def enter_first_name(self, selected_first_name: str) -> None:
+ self.page.locator("#firstGivenNameFilter").get_by_role("textbox").fill(selected_first_name)
+ with self.page.expect_response(self.API_REQUEST) as response:
+ pass
+
+ def sort_date_ceased(self) -> None:
+ self.page.locator("#actionList").select_option("")
+ self.page.get_by_label("Date Ceased: activate to sort").click()
+ self.page.wait_for_timeout(5000)
+
+ def sort_date_unceased(self) -> None:
+ self.page.locator("#actionList").select_option("")
+ self.page.get_by_label("Date Unceased: activate to").click()
+ self.page.wait_for_timeout(5000)
+
+ def reason_selected(self, selected_reason: str) -> None:
+ self.page.locator("#reasonList").select_option(selected_reason)
+ with self.page.expect_response(self.API_REQUEST) as response:
+ pass
diff --git a/pages/monitoring_reports/sspi_update_warnings_action.py b/pages/monitoring_reports/sspi_update_warnings_action.py
new file mode 100644
index 00000000..d78308fd
--- /dev/null
+++ b/pages/monitoring_reports/sspi_update_warnings_action.py
@@ -0,0 +1,105 @@
+import logging
+from playwright.sync_api import Page
+from pages.monitoring_reports.sspi_update_warnings_base import (
+ SSPIUpdateWarningsBasePage,
+)
+
+
+class SSPIUpdateWarningsActionPage(SSPIUpdateWarningsBasePage):
+
+ # Selectable Options
+
+ AGE_OPTIONS = ["All", "Under 80", "80 and over"]
+ REASON_OPTIONS = [
+ "Date of birth changed",
+ "Date of death set",
+ "Date of death cleared",
+ "Gender changed",
+ "NHS number changed",
+ "NHS number superseded",
+ "Subject joined BSO",
+ "Subject left BSO",
+ ]
+ WARNING_OPTIONS = [
+ "Age now inside programme",
+ "Age now inside programme - Open episode",
+ "Age now outside programme",
+ "Age now outside programme - Open episode",
+ "Date of death cleared",
+ "Gender is Indeterminate",
+ "Male with SC end code",
+ "Male with no history",
+ "Male with screening events",
+ "No open episodes",
+ "Open episode closed",
+ "Subject has open episode",
+ "Subject has HR status",
+ "Subject has screening events",
+ "Subject is ceased",
+ "Under 44 with batch episodes",
+ "Was below age limit now above",
+ "Was below age limit now above - Open episode",
+ "Was previously male",
+ ]
+ TABLE_ID = "#sspiUpdateWarningList"
+ TABLE_FIRST_ROW = f"{TABLE_ID} > tbody > tr:nth-child(1)"
+ TABLE_FIRST_NHS_NUMBER = f"{TABLE_FIRST_ROW} > td:nth-child(3)"
+ TABLE_FIRST_FAMILY_NAME = f"{TABLE_FIRST_ROW} > td:nth-child(4)"
+ TABLE_FIRST_FIRST_NAME = f"{TABLE_FIRST_ROW} > td:nth-child(5)"
+
+ def __init__(self, page: Page) -> None:
+ SSPIUpdateWarningsBasePage.__init__(self, page)
+ self.API_REQUEST = "**/bss/report/sspiUpdateWarnings/action/search**"
+ self.HEADER = "SSPI Update Warnings - Action"
+ # self.page = page
+
+ # def verify_header(self) -> None:
+ # super().verify_header(self.HEADER)
+
+ # def set_done_drop_down(self, value: str) -> None:
+ # self.page.locator("#actionList").select_option(value)
+ # self.page.wait_for_timeout(5000)
+
+ # def enter_nhs_number(self, selected_nhs: str) -> None:
+ # self.page.locator("#nhsNumberFilter").get_by_role("textbox").fill(selected_nhs)
+ # with self.page.expect_response(self.API_REQUEST) as response:
+ # pass
+
+ # def enter_family_name(self, selected_family_name: str) -> None:
+ # self.page.locator("#familyNameFilter").get_by_role("textbox").fill(
+ # selected_family_name
+ # )
+ # with self.page.expect_response(self.API_REQUEST) as response:
+ # pass
+
+ # def enter_first_name(self, selected_first_name: str) -> None:
+ # self.page.locator("#firstGivenNameFilter").get_by_role("textbox").fill(
+ # selected_first_name
+ # )
+ # with self.page.expect_response(self.API_REQUEST) as response:
+ # pass
+
+ # def sort_received(self) -> None:
+ # self.page.locator("#actionList").select_option("")
+ # self.page.get_by_label("Received: activate to sort").click()
+ # self.page.wait_for_timeout(5000)
+
+ # def table_filtered_by_age(self, selected_age: str) -> None:
+ # self.page.locator("#ageTodayList").select_option(selected_age)
+ # with self.page.expect_response(self.API_REQUEST) as response:
+ # pass
+
+ # def event_selected(self, selected_reason: str) -> None:
+ # self.page.locator("#eventList").select_option(selected_reason)
+ # with self.page.expect_response(self.API_REQUEST) as response:
+ # pass
+
+ # def warning_selected(self, selected_warning: str) -> None:
+ # self.page.locator("#reasonList").select_option(selected_warning)
+ # with self.page.expect_response(self.API_REQUEST) as response:
+ # pass
+
+ # def sort_add_info(self) -> None:
+ # self.page.locator("#actionList").select_option("")
+ # self.page.get_by_label("Additional Info: activate to").click()
+ # self.page.wait_for_timeout(5000)
diff --git a/pages/monitoring_reports/sspi_update_warnings_base.py b/pages/monitoring_reports/sspi_update_warnings_base.py
new file mode 100644
index 00000000..2136d2a6
--- /dev/null
+++ b/pages/monitoring_reports/sspi_update_warnings_base.py
@@ -0,0 +1,70 @@
+import logging
+from playwright.sync_api import Page
+from pages.report_page import ReportPage
+
+
+class SSPIUpdateWarningsBasePage(ReportPage):
+
+ def __init__(self, page: Page) -> None:
+ ReportPage.__init__(self, page)
+ self.action_list = page.locator("#actionList")
+ self.API_REQUEST = "" # This is set by specific action or information page
+ self.HEADER = "" # This is set by specific action or information page
+
+ def verify_header(self) -> None:
+ super().verify_header(self.HEADER)
+
+ def set_done_drop_down(self, value: str) -> None:
+ self.action_list.select_option(value)
+ self.page.wait_for_timeout(5000)
+
+ def enter_nhs_number(self, selected_nhs: str) -> None:
+ self.page.locator("#nhsNumberFilter").get_by_role("textbox").fill(selected_nhs)
+ self.await_api_response()
+
+ def enter_family_name(self, selected_family_name: str) -> None:
+ self.page.locator("#familyNameFilter").get_by_role("textbox").fill(
+ selected_family_name
+ )
+ self.await_api_response()
+
+ def enter_first_name(self, selected_first_name: str) -> None:
+ self.page.locator("#firstGivenNameFilter").get_by_role("textbox").fill(
+ selected_first_name
+ )
+ self.await_api_response()
+
+ def sort_received(self) -> None:
+ self.action_list.select_option("")
+ self.page.get_by_label("Received: activate to sort").click()
+ self.page.wait_for_timeout(5000)
+
+ def table_filtered_by_age(self, selected_age: str) -> None:
+ self.page.locator("#ageTodayList").select_option(selected_age)
+ self.await_api_response()
+
+ def event_selected(self, selected_reason: str) -> None:
+ self.page.locator("#eventList").select_option(selected_reason)
+ self.await_api_response()
+
+ def warning_selected(self, selected_warning: str) -> None:
+ self.page.locator("#reasonList").select_option(selected_warning)
+ self.await_api_response()
+
+ def sort_add_info(self) -> None:
+ self.action_list.select_option("")
+ self.page.get_by_label("Additional Info: activate to").click()
+ self.page.wait_for_timeout(5000)
+
+ def await_api_response(self) -> None:
+ """
+ This method waits for the API response to complete after a filter or action is applied.
+ It is useful to ensure that the page has updated before proceeding with further actions.
+ """
+ with self.page.expect_response(self.API_REQUEST) as response:
+ # Response captured here is not used, but we wait for the request to complete
+ logging.debug(
+ "response received from {} = {}".format(
+ self.API_REQUEST, response.value
+ )
+ )
diff --git a/pages/monitoring_reports/sspi_update_warnings_information.py b/pages/monitoring_reports/sspi_update_warnings_information.py
new file mode 100644
index 00000000..a1f19890
--- /dev/null
+++ b/pages/monitoring_reports/sspi_update_warnings_information.py
@@ -0,0 +1,37 @@
+import logging
+from playwright.sync_api import Page
+from pages.monitoring_reports.sspi_update_warnings_base import (
+ SSPIUpdateWarningsBasePage,
+)
+
+
+class SSPIUpdateWarningsInformationPage(SSPIUpdateWarningsBasePage):
+
+ # Selectable Options
+
+ HEADER = "SSPI Update Warnings - Information"
+ AGE_OPTIONS = ["All", "Under 80", "80 and over"]
+ REASONI_OPTIONS = ["Removal", "Subject joined BSO", "Subject left BSO"]
+ WARNINGI_OPTIONS = [
+ "Adoption",
+ "Armed Services",
+ "Embarkation",
+ "Mental Hospital",
+ "No open episodes",
+ "No open or changed episodes",
+ "Not provided",
+ "Other reason",
+ "Previous end code changed",
+ "Removal",
+ "Service dependent",
+ ]
+ TABLE_ID = "#sspiUpdateWarningList"
+ TABLE_FIRST_ROW = f"{TABLE_ID} > tbody > tr:nth-child(1)"
+ TABLE_FIRST_NHS_NUMBER = f"{TABLE_FIRST_ROW} > td:nth-child(3)"
+ TABLE_FIRST_FAMILY_NAME = f"{TABLE_FIRST_ROW} > td:nth-child(4)"
+ TABLE_FIRST_FIRST_NAME = f"{TABLE_FIRST_ROW} > td:nth-child(5)"
+
+ def __init__(self, page: Page) -> None:
+ SSPIUpdateWarningsBasePage.__init__(self, page)
+ self.HEADER = "SSPI Update Warnings - Information"
+ self.API_REQUEST = "**/bss/report/sspiUpdateWarnings/information/search**"
diff --git a/pages/monitoring_reports/subjects_never_invited.py b/pages/monitoring_reports/subjects_never_invited.py
new file mode 100644
index 00000000..7799fff4
--- /dev/null
+++ b/pages/monitoring_reports/subjects_never_invited.py
@@ -0,0 +1,20 @@
+import logging
+from playwright.sync_api import Page, expect
+from pages.report_page import ReportPage
+
+
+class SubjectsNeverInvitedPage(ReportPage):
+
+ # Selectable Options
+
+ HEADER = "Subjects Never Invited For Screening"
+ TABLE_ID = "#subjectsNeverInvitedList"
+ TABLE_FIRST_ROW = f"{TABLE_ID} > tbody > tr:nth-child(1)"
+ TABLE_FIRST_NHS_NUMBER = f"{TABLE_FIRST_ROW} > td:nth-child(2)"
+
+ def __init__(self, page: Page) -> None:
+ ReportPage.__init__(self, page)
+ self.page = page
+
+ def verify_header(self) -> None:
+ super().verify_header(self.HEADER)
diff --git a/pages/ni_ri_sp_batch_page.py b/pages/ni_ri_sp_batch_page.py
new file mode 100644
index 00000000..9e8a98fa
--- /dev/null
+++ b/pages/ni_ri_sp_batch_page.py
@@ -0,0 +1,278 @@
+from __future__ import annotations
+from datetime import datetime, timedelta
+from playwright.sync_api import Page, expect
+
+
+class NiRiSpBatchPage:
+
+ def __init__(self, page: Page) -> None:
+
+ self.page = page
+ self.bso_batch_id_input = page.locator("#bsoBatchId")
+ self.selection_date_input = page.locator("#rispSelectionDate")
+ self.year_of_birth_from_input = page.locator("#earliestBirthYear")
+ self.year_of_birth_to_input = page.locator("#latestBirthYear")
+ self.month_of_birth_to_input = page.locator("#latestBirthMonth")
+ self.count_button = page.locator("#countButtonText")
+ self.page_heading = page.locator("h1.bss-page-title")
+ self.include_specified_practices_radio_btn = page.locator(
+ "#include_specified_practices"
+ )
+ self.include_specified_practice_groups_radio_btn = page.locator(
+ "#include_specified_practice_groups"
+ )
+ self.include_specified_outcodes_radio_btn = page.locator(
+ "#include_specified_outcodes"
+ )
+ self.include_specified_outcode_groups_radio_btn = page.locator(
+ "#include_specified_outcode_groups"
+ )
+ self.excluded_gp_practices_filter = page.locator(
+ "#gpPracticeSelection div.box1 input.filter"
+ )
+ self.gp_practices_select_move_single_arrow = page.locator(
+ "#gpPracticeSelection div.box1 button.move"
+ )
+ self.outcode_select_move_single_arrow = page.locator(
+ "#outcodeSelection div.box1 button.move"
+ )
+ self.excluded_outcode_filter = page.locator(
+ "#outcodeSelection div.box1 input.filter"
+ )
+ self.excluded_gp_practice_groups_arrow = page.locator(
+ "#gpPracticeGroupSelection div.box1 button.move"
+ )
+ self.excluded_outcode_groups_arrow = page.locator(
+ "#outcodeGroupSelection div.box1 button.move"
+ )
+ self.excluded_gp_practices_list = page.locator(
+ "select#bootstrap-duallistbox-nonselected-list_selectedGpPracticeCodes"
+ )
+ self.excluded_outcode_list = page.locator(
+ "select#bootstrap-duallistbox-nonselected-list_selectedOutcodes"
+ )
+ self.excluded_gp_practice_groups = page.locator(
+ "select#bootstrap-duallistbox-nonselected-list_selectedGpPracticeGroups"
+ )
+ self.excluded_outcode_groups = page.locator(
+ "select#bootstrap-duallistbox-nonselected-list_selectedOutcodeGroups"
+ )
+ self.batch_title = page.locator("input#title")
+ self.bso_batch_id_filter_text_box = page.locator(
+ "#batchIdFilter input[type='text']"
+ )
+ self.specify_by_gp_practice_group_radio_btn = page.locator(
+ "#include_specified_practice_groups"
+ )
+ self.specify_by_outcode_group_radio_btn = page.locator(
+ "#include_specified_outcode_groups"
+ )
+
+ def enter_bso_batch_id(self, bso_batch_id: str) -> NiRiSpBatchPage:
+ self.bso_batch_id_input.fill(bso_batch_id)
+
+ def select_specify_by_gp_practice_group(self) -> NiRiSpBatchPage:
+ self.specify_by_gp_practice_group_radio_btn.check()
+
+ def select_specify_by_outcode_group(self) -> NiRiSpBatchPage:
+ self.specify_by_outcode_group_radio_btn.check()
+
+ def enter_batch_title(self, batch_title: str) -> NiRiSpBatchPage:
+ self.batch_title.fill(batch_title)
+
+ def enter_excluded_gp_practices_filter(self, text: str) -> NiRiSpBatchPage:
+ self.excluded_gp_practices_filter.type(text)
+
+ def select_excluded_gp_practices_from_list(self, gp_code: str) -> NiRiSpBatchPage:
+ self.excluded_gp_practices_list.locator(f'option[value="{gp_code}"]').click()
+
+ def select_excluded_outcodes_from_list(self, outcode: str) -> NiRiSpBatchPage:
+ self.excluded_outcode_list.locator(f'option[value="{outcode}"]').click()
+
+ def select_excluded_gp_practice_groups(self, gp_code: str) -> NiRiSpBatchPage:
+ self.excluded_gp_practice_groups.locator(f'option[value="{gp_code}"]').click()
+
+ def select_excluded_outcode_groups(self, outcode: str) -> NiRiSpBatchPage:
+ self.excluded_outcode_groups.locator(f'option[value="{outcode}"]').click()
+
+ def click_gp_practices_select_move(self) -> NiRiSpBatchPage:
+ self.gp_practices_select_move_single_arrow.click()
+
+ def click_gp_practice_groups_select_move(self) -> NiRiSpBatchPage:
+ self.excluded_gp_practice_groups_arrow.click()
+
+ def click_outcode_groups_select_move(self) -> NiRiSpBatchPage:
+ self.excluded_outcode_groups_arrow.click()
+
+ def enter_excluded_outcodes_filter(self, text: str) -> NiRiSpBatchPage:
+ self.excluded_outcode_filter.fill(text)
+
+ def click_outcodes_select_move(self) -> NiRiSpBatchPage:
+ self.outcode_select_move_single_arrow.click()
+
+ def enter_date_for_selection(self, days_offset: int) -> NiRiSpBatchPage:
+ target_date = (datetime.today() + timedelta(days=days_offset)).strftime(
+ "%d-%b-%Y"
+ )
+ self.selection_date_input.fill(target_date)
+
+ def select_ntd_end_date(self, days_offset: int) -> NiRiSpBatchPage:
+ target_date = (datetime.today() + timedelta(days=days_offset)).strftime(
+ "%d-%b-%Y"
+ )
+ self.page.locator("#ntdEndDate").fill(target_date)
+
+ def select_range_by_age(self) -> NiRiSpBatchPage:
+ self.page.locator("input#include_age").check()
+
+ def select_include_specified_practices(self) -> NiRiSpBatchPage:
+ self.include_specified_practices_radio_btn.check()
+
+ def select_include_specified_practice_groups(self) -> NiRiSpBatchPage:
+ self.include_specified_practice_groups_radio_btn.check()
+
+ def select_include_specified_outcodes(self) -> NiRiSpBatchPage:
+ self.include_specified_outcodes_radio_btn.check()
+
+ def select_include_specified_outcode_groups(self) -> NiRiSpBatchPage:
+ self.include_specified_outcode_groups_radio_btn.check()
+
+ def enter_include_year_of_birth_from(
+ self, year_of_birth_from: str
+ ) -> NiRiSpBatchPage:
+ self.year_of_birth_from_input.fill(year_of_birth_from)
+
+ def enter_ntdd_start_age_year(self, start_age: str) -> NiRiSpBatchPage:
+ self.page.locator("#startAgeYears").fill(start_age)
+
+ def enter_ntdd_end_age_year(self, end_age: str) -> NiRiSpBatchPage:
+ self.page.locator("#endAgeYears").fill(end_age)
+
+ def enter_ntdd_start_age_month(self, start_age: str) -> NiRiSpBatchPage:
+ self.page.locator("#startAgeMonths").fill(start_age)
+
+ def enter_ntdd_end_age_month(self, end_age: str) -> NiRiSpBatchPage:
+ self.page.locator("#endAgeMonths").fill(end_age)
+
+ def check_include_younger_women(self) -> NiRiSpBatchPage:
+ check_box = self.page.locator("input#includeYoungerSubjects")
+ check_box.check()
+ assert check_box.is_checked()
+
+ def enter_include_year_of_birth_to(self, year_of_birth_to: str) -> NiRiSpBatchPage:
+ self.year_of_birth_to_input.fill(year_of_birth_to)
+
+ def enter_include_month_of_birth_to(
+ self, month_of_birth_to: str
+ ) -> NiRiSpBatchPage:
+ self.month_of_birth_to_input.fill(month_of_birth_to)
+
+ def click_count_button(self) -> NiRiSpBatchPage:
+ self.count_button.click()
+ self.page.wait_for_timeout(3000)
+
+ def assert_text_visible(self, text: str) -> NiRiSpBatchPage:
+ locator = self.page.locator(f"p:has-text('{text}')")
+ locator.scroll_into_view_if_needed()
+ expect(locator).to_be_visible()
+
+ def assert_page_header(self, expected_header: str) -> NiRiSpBatchPage:
+ expect(self.page_heading).to_have_text(expected_header)
+
+ def assert_global_error(self, expected_error: str) -> NiRiSpBatchPage:
+ error_locator = self.page.locator("#globalErrorMessages li")
+ expect(error_locator).to_contain_text(expected_error)
+
+ def assert_entered_bso_batch_id_and_filterd_row_value(
+ self, bso_batch_id: str
+ ) -> NiRiSpBatchPage:
+ self.bso_batch_id_filter_text_box.fill(bso_batch_id)
+ self.page.wait_for_timeout(3000)
+ first_row_second_cell_value = self.page.locator(
+ "//tbody/tr[1]/td[2]"
+ ).text_content()
+ assert bso_batch_id == first_row_second_cell_value
+
+ def search_by_batch_title(self, batch_title: str) -> NiRiSpBatchPage:
+ self.page.locator("#batchTitleFilter input[type='text']").fill(batch_title)
+ self.page.wait_for_timeout(2000)
+ filtered_cell_value = self.page.locator("//tbody/tr[1]/td[11]").text_content()
+ assert batch_title == filtered_cell_value
+
+ def search_by_bso_batch_id_and_batch_title(
+ self, bso_batch_id: str, batch_title: str
+ ) -> NiRiSpBatchPage:
+ self.bso_batch_id_filter_text_box.fill(bso_batch_id)
+ self.search_by_batch_title(batch_title)
+ self.page.wait_for_timeout(3000)
+ first_row_second_cell_value = self.page.locator(
+ "//tbody/tr[1]/td[2]"
+ ).text_content()
+ assert bso_batch_id == first_row_second_cell_value
+
+ def assert_select_date_cell_value_is_not_null(
+ self, cell_value: str
+ ) -> NiRiSpBatchPage:
+ self.page.wait_for_timeout(2000)
+ select_date_cell_value = self.page.locator("//tbody/tr[1]/td[6]").text_content()
+ assert select_date_cell_value.strip() != cell_value
+
+ def assert_select_date_cell_value(self, cell_value: str) -> NiRiSpBatchPage:
+ self.page.wait_for_timeout(2000)
+ select_date_cell_value = self.page.locator("//tbody/tr[1]/td[6]").text_content()
+ assert select_date_cell_value == cell_value
+
+ def select_ri_sp_yob_from_drop_down(self) -> NiRiSpBatchPage:
+ self.page.locator("#batchTypeList").select_option(label="RISP by Year of Birth")
+
+ def select_no_from_failsafe_flag_drop_down(self) -> NiRiSpBatchPage:
+ self.page.locator("#failsafeFlagList").select_option(label="No")
+
+ def click_count_select_button(self) -> NiRiSpBatchPage:
+ select_btn = self.page.locator("#selectButton")
+ confirm_btn_pop_up = self.page.locator("#confirmButtonInSelectPopupText")
+ select_btn.click()
+ confirm_btn_pop_up.click()
+ self.page.wait_for_timeout(3000)
+
+ def assert_selected_cell_value(self, cell_value: str) -> NiRiSpBatchPage:
+ self.page.wait_for_timeout(2000)
+ selected_cell_value = self.page.locator("//tbody/tr[1]/td[7]").text_content()
+ assert selected_cell_value == cell_value
+
+ def assert_selected_cell_value_is_not_null(
+ self, cell_value: str
+ ) -> NiRiSpBatchPage:
+ self.page.wait_for_timeout(2000)
+ selected_cell_value = self.page.locator("//tbody/tr[1]/td[7]").text_content()
+ assert selected_cell_value.strip() != cell_value
+
+ def assert_rejected_cell_value(self, cell_value: str) -> NiRiSpBatchPage:
+ self.page.wait_for_timeout(2000)
+ rejected_cell_value = self.page.locator("//tbody/tr[1]/td[8]").text_content()
+ assert rejected_cell_value == cell_value
+
+ def assert_rejected_cell_value_is_not_null(
+ self, cell_value: str
+ ) -> NiRiSpBatchPage:
+ self.page.wait_for_timeout(2000)
+ rejected_cell_value = self.page.locator("//tbody/tr[1]/td[8]").text_content()
+ assert rejected_cell_value.strip() != cell_value
+
+ def validate_batch_id_error(self, batch_id: str, expected_error: str) -> None:
+ self.enter_bso_batch_id(batch_id)
+ self.enter_date_for_selection(5)
+ self.click_count_button()
+ if "already exists" in expected_error:
+ self.assert_global_error(expected_error)
+ else:
+ self.assert_text_visible(expected_error)
+
+ def enter_year_range_and_count(
+ self, from_year: str, to_year: str
+ ) -> NiRiSpBatchPage:
+ self.enter_bso_batch_id("PMA916865R")
+ self.enter_date_for_selection(5)
+ self.enter_include_year_of_birth_from(from_year)
+ self.enter_include_year_of_birth_to(to_year)
+ self.click_count_button()
diff --git a/pages/report_page.py b/pages/report_page.py
new file mode 100644
index 00000000..8160c464
--- /dev/null
+++ b/pages/report_page.py
@@ -0,0 +1,22 @@
+import logging
+from pathlib import Path
+from playwright.sync_api import Page, expect
+from pages.base_page import BasePage
+
+DOWNLOAD_PATH = Path(__file__).parent.parent / "test-results"
+
+
+class ReportPage(BasePage):
+
+ def __init__(self, page: Page) -> None:
+ BasePage.__init__(self, page)
+ self.page = page
+
+ def download_csv(self) -> Path:
+ with self.page.expect_download() as download_info:
+ # Perform the action that initiates download
+ self.page.get_by_text("Download to CSV").click()
+ download = download_info.value
+ # Wait for the download process to complete and save the downloaded file somewhere
+ download.save_as(DOWNLOAD_PATH / download.suggested_filename)
+ return DOWNLOAD_PATH / download.suggested_filename
diff --git a/pages/rlp_cohort_list_page.py b/pages/rlp_cohort_list_page.py
new file mode 100644
index 00000000..eba30433
--- /dev/null
+++ b/pages/rlp_cohort_list_page.py
@@ -0,0 +1,377 @@
+from __future__ import annotations
+import re
+import playwright
+from playwright.sync_api import Page, expect
+
+
+class CohortListPage:
+
+ def __init__(self, page: Page) -> None:
+ self.page = page
+
+ self.create_screening_cohort_by_gp_practice_btn = page.locator(
+ "#addCohortByPracticeButtonText"
+ )
+ self.screening_cohort_name_txtbox = page.locator("//input[@id='description']")
+ self.default_screening_location_dropdown = page.locator(
+ "select#defaultLocation"
+ )
+ self.default_screening_unit_dropdown = page.locator("select#defaultUnit")
+ self.select_gp_practices_btn = page.locator("button#selectElementsButton.btn")
+ self.create_screening_cohort_save_btn = page.locator("#saveButton")
+ self.gp_practice_name_field = page.locator("th#nameFilter > input")
+ self.gp_code_field = page.locator("th#codeFilter > input")
+ self.add_gp_practices_to_include = page.locator("//button[text()='Add']")
+ self.cancel_creating_screening_cohort = page.locator("a#cancelButton")
+ self.expected_attendance_rate = page.locator("input#uptakePercentage")
+ self.done_btn_gp_practices_incluse_popup = page.locator(
+ "button#cancelButtonInAmendCohortPopup"
+ )
+ self.screening_cohort_name_filter = page.locator("th#descriptionFilter > input")
+ self.screening_location_filter = page.locator(
+ "th#defaultLocationFilter > input"
+ )
+ self.screening_unit_filter = page.locator("th#defaultUnitFilter> input")
+ self.location_paging_info = page.locator("#screeningLocationList_info")
+ self.unit_paging_info = page.locator("#screeningUnitList_info")
+ self.cohort_paging_info = page.locator("#screeningCohortList_info")
+ self.remove_btn_included_gp_practices = page.locator("button#deleteBtn_301")
+ self.filtered_cohort_name = page.locator("//tbody/tr/td[2]")
+ self.filtered_cohort_pencil_icon = page.locator("tbody tr td .glyphicon-pencil")
+ self.amend_cohort_cancel_button = page.locator("a#cancelButton")
+ self.amend_screening_cohort_name_txtbox = page.locator("input#description")
+ self.amend_attendance_rate_txtbox = page.locator("input#uptakePercentage")
+ self.amend_screening_location_dropdown = page.locator("select#defaultLocation")
+ self.amend_screening_unit_dropdown = page.locator("select#defaultUnit")
+ self.amend_select_gp_practices_btn = page.locator(
+ "span#selectElementsButtonText"
+ )
+ self.amend_save_btn = page.locator("span#saveButtonText")
+ self.amend_gp_code_field = page.locator("th#codeFilter > input")
+ self.amend_add_btn_select_gp_practices = page.get_by_role("button", name="Add")
+ self.amend_done_btn_gp_practices = page.locator(
+ "button#cancelButtonInAmendCohortPopup"
+ )
+ self.location_dropdown_count = page.locator("select#defaultLocation option")
+ self.unit_dropdown_count = page.locator("select#defaultUnit option")
+ self.error_message_locator = page.locator("p#error_unitNameText")
+ self.create_screening_cohort_by_outcode_btn = page.locator(
+ "button#addCohortByOutcodeButton"
+ )
+ self.cancel_cohort_by_outcode_btn = page.locator("a#cancelButton")
+ self.save_cohort_by_outcode_btn = page.locator("button#saveButton")
+ self.select_outcodes_btn = page.locator("button#selectElementsButton")
+ self.outcode_filter = page.locator("#nameFilter > input")
+
+ def click_create_screening_cohort_by_gp_practice_btn(self) -> CohortListPage:
+ self.create_screening_cohort_by_gp_practice_btn.click()
+ return self
+
+ def click_create_screening_cohort_by_outcode_btn(self) -> CohortListPage:
+ self.create_screening_cohort_by_outcode_btn.click()
+ return self
+
+ def enter_outcode_filter(self, out_code: str) -> CohortListPage:
+ self.outcode_filter.fill(out_code)
+ return self
+
+ def click_cancel_cohort_by_outcode_btn(self) -> CohortListPage:
+ self.cancel_cohort_by_outcode_btn.click()
+ return self
+
+ def click_save_cohort_by_outcode_btn(self) -> CohortListPage:
+ self.save_cohort_by_outcode_btn.click()
+ return self
+
+ def click_select_outcodes_btn(self) -> CohortListPage:
+ self.select_outcodes_btn.click()
+ return self
+
+ def number_of_location_dropdown_count(self) -> CohortListPage:
+ self.page.wait_for_timeout(5000)
+ return self.location_dropdown_count.count() - 1 # excluding empty option
+
+ def number_of_unit_dropdown_count(self) -> CohortListPage:
+ return self.unit_dropdown_count.count() - 1 # excluding empty option
+
+ def select_amend_screening_location_dropdown(
+ self, location_name: str
+ ) -> CohortListPage:
+ self.amend_screening_location_dropdown.select_option(location_name)
+ return self
+
+ def select_amend_screening_unit_dropdown(self, unit_name: str) -> CohortListPage:
+ self.amend_screening_unit_dropdown.select_option(unit_name)
+ return self
+
+ def enter_amend_gp_code_field(self, gp_code: str) -> CohortListPage:
+ self.amend_gp_code_field.fill(gp_code)
+ return self
+
+ def click_amend_select_gp_practices_btn(self) -> CohortListPage:
+ self.amend_select_gp_practices_btn.click()
+ return self
+
+ def click_amend_save_btn(self) -> CohortListPage:
+ self.amend_save_btn.click()
+ return self
+
+ def click_amend_add_btn_select_gp_practices(self) -> CohortListPage:
+ self.amend_add_btn_select_gp_practices.click()
+ return self
+
+ def click_amend_done_btn_gp_practices(self) -> CohortListPage:
+ self.amend_done_btn_gp_practices.click()
+ return self
+
+ def enter_amend_expected_attendance_rate(
+ self, attendance_rate: int
+ ) -> CohortListPage:
+ self.amend_attendance_rate_txtbox.fill(attendance_rate)
+ return self
+
+ def enter_amend_screening_cohort_name(self, amend_name: str) -> CohortListPage:
+ self.amend_screening_cohort_name_txtbox.fill(amend_name)
+ return self
+
+ def click_filtered_cohort_pencil_icon(self) -> CohortListPage:
+ self.filtered_cohort_pencil_icon.click()
+ return self
+
+ def click_amend_cohort_cancel_button(self) -> CohortListPage:
+ self.amend_cohort_cancel_button.click()
+ return self
+
+ def enter_screening_cohort_name_field(self, cohort_name: str) -> CohortListPage:
+ self.screening_cohort_name_txtbox.fill(cohort_name)
+ return self
+
+ def select_default_screening_location_dropdown(
+ self, location_name: str
+ ) -> CohortListPage:
+ self.default_screening_location_dropdown.select_option(location_name)
+ return self
+
+ def select_default_screening_unit_dropdown(self, unit_name: str) -> CohortListPage:
+ self.default_screening_unit_dropdown.select_option(unit_name)
+ return self
+
+ def click_select_gp_practices_btn(self) -> CohortListPage:
+ self.select_gp_practices_btn.click()
+ return self
+
+ def click_create_screening_cohort_save_btn(self) -> CohortListPage:
+ self.create_screening_cohort_save_btn.click()
+ return self
+
+ def enter_gp_practice_name_field(self, gp_name: str) -> CohortListPage:
+ self.gp_practice_name_field.fill(gp_name)
+ return self
+
+ def enter_gp_code_field(self, gp_code: str) -> CohortListPage:
+ self.gp_code_field.fill(gp_code)
+ return self
+
+ def click_on_cancel_creating_screening_cohort(self) -> CohortListPage:
+ self.cancel_creating_screening_cohort.click()
+ return self
+
+ def click_add_btn_gp_practices_to_include(self) -> CohortListPage:
+ self.add_gp_practices_to_include.click()
+ return self
+
+ def verify_add_btn_gp_practices_not_to_be_present(self) -> CohortListPage:
+ expect(self.add_gp_practices_to_include).not_to_be_visible()
+ return self
+
+ def enter_expected_attendance_rate(self, attendance_rate: int) -> CohortListPage:
+ self.expected_attendance_rate.fill(attendance_rate)
+ return self
+
+ def click_add_btn_to_select_outcode(self, outcode) -> CohortListPage:
+ outcode_add_btn = self.page.locator(
+ f"//tr[td[1][normalize-space()='{outcode}']]//button[text()='Add']"
+ )
+ outcode_add_btn.click()
+ return self
+
+ def click_remove_button_by_gp_code(self, gp_code) -> CohortListPage:
+ # Use an XPath selector to find the "Remove" button in the same row where the first column text matches the gp_code
+ remove_button = self.page.locator(
+ f"//table[@id='practicesToIncludeList']//tr[td[normalize-space()='{gp_code}']]//button[text()='Remove']"
+ )
+ remove_button.click()
+ return self
+
+ def create_cohort_user2_bso2(self, cohort_name, location_name) -> CohortListPage:
+ self.click_create_screening_cohort_by_gp_practice_btn()
+ self.enter_screening_cohort_name_field(
+ cohort_name
+ ).enter_expected_attendance_rate("25")
+ self.select_default_screening_location_dropdown(
+ location_name
+ ).select_default_screening_unit_dropdown("Batman")
+ self.click_select_gp_practices_btn().enter_gp_code_field(
+ "A00009"
+ ).click_add_btn_gp_practices_to_include().click_done_btn_gp_practices_include_popup()
+ self.click_create_screening_cohort_save_btn()
+ self.page.wait_for_timeout(3000)
+ return self
+
+ def create_cohort(self, cohort_name, location_name) -> CohortListPage:
+ self.click_create_screening_cohort_by_gp_practice_btn()
+ self.enter_screening_cohort_name_field(
+ cohort_name
+ ).enter_expected_attendance_rate("25")
+ self.select_default_screening_location_dropdown(
+ location_name
+ ).select_default_screening_unit_dropdown("Batman")
+ self.click_select_gp_practices_btn().enter_gp_code_field(
+ "A00005"
+ ).click_add_btn_gp_practices_to_include().click_done_btn_gp_practices_include_popup()
+ self.click_create_screening_cohort_save_btn()
+ self.page.wait_for_timeout(3000)
+ return self
+
+ def create_cohort_without_gp(
+ self, cohort_name, location_name, unit_name
+ ) -> CohortListPage:
+ self.click_create_screening_cohort_by_gp_practice_btn()
+ self.enter_screening_cohort_name_field(
+ cohort_name
+ ).enter_expected_attendance_rate("25")
+ self.select_default_screening_location_dropdown(
+ location_name
+ ).select_default_screening_unit_dropdown(unit_name)
+ self.click_create_screening_cohort_save_btn()
+ self.page.wait_for_timeout(3000)
+ return self
+
+ def create_cohort_outcode_without_gp(
+ self, cohort_name, attendance_rate, location_name, unit_name
+ ) -> CohortListPage:
+ self.click_create_screening_cohort_by_outcode_btn()
+ self.enter_screening_cohort_name_field(
+ cohort_name
+ ).enter_expected_attendance_rate(attendance_rate)
+ self.select_default_screening_location_dropdown(
+ location_name
+ ).select_default_screening_unit_dropdown(unit_name)
+ self.click_save_cohort_by_outcode_btn()
+ self.page.wait_for_timeout(3000)
+ return self
+
+ def value_of_filtered_cohort_name(self):
+ filterd_value = self.filtered_cohort_name.text_content()
+ self.page.wait_for_timeout(4000)
+ return filterd_value
+
+ def value_of_filtered_attendance(self):
+ filtered_value = self.amend_attendance_rate_txtbox.input_value()
+ self.page.wait_for_timeout(4000)
+ return filtered_value
+
+ def click_done_btn_gp_practices_include_popup(self) -> CohortListPage:
+ self.done_btn_gp_practices_incluse_popup.click()
+ return self
+
+ def enter_screening_cohort_name_filter(self, cohort_name: str) -> CohortListPage:
+ self.screening_cohort_name_filter.fill(cohort_name)
+ self.page.wait_for_timeout(4000)
+ return self
+
+ def enter_screening_location_filter(self, location_name: str) -> CohortListPage:
+ self.screening_location_filter.fill(location_name)
+ self.page.wait_for_timeout(3000)
+ return self
+
+ def enter_screening_unit_filter(self, unit_name: str) -> CohortListPage:
+ self.screening_unit_filter.fill(unit_name)
+ self.page.wait_for_timeout(3000)
+ return self
+
+ def select_cohort_type_dropdown(self, cohort_type: str) -> CohortListPage:
+ self.page.locator("select#cohortTypeList").select_option(label=cohort_type)
+ self.page.wait_for_timeout(3000)
+ return self
+
+ def dbl_click_on_filtered_cohort(self) -> CohortListPage:
+ self.filtered_cohort_name.dblclick()
+ return self
+
+ def extract_cohort_paging_info(self) -> int:
+ self.cohort_paging_info.scroll_into_view_if_needed()
+ paging_info_text = self.cohort_paging_info.text_content()
+ self.page.wait_for_timeout(3000)
+ re_search_result = re.search(
+ "Showing (\d+) to (\d+) of (\d+) entries", paging_info_text
+ )
+ return int(re_search_result.group(3))
+
+ def screening_cohorts_count_in_db(self, db_util) -> int:
+ result = db_util.get_results(
+ """select count(1)
+ from rlp_cohorts where bso_organisation_id in(
+ select bso_organisation_id from bso_organisations where bso_organisation_code = 'LAV')
+ """
+ )
+ return result["count"][0]
+
+ def extract_location_paging_list_count(self) -> int:
+ self.location_paging_info.scroll_into_view_if_needed()
+ paging_info_text = self.location_paging_info.text_content()
+ self.page.wait_for_timeout(5000)
+ re_search_result = re.search(
+ "Showing (\d+) to (\d+) of (\d+) entries", paging_info_text
+ )
+ return int(re_search_result.group(3))
+
+ def extract_paging_unit_list_count(self) -> int:
+ self.page.locator("#screeningStatusList").select_option(label="All")
+ self.unit_paging_info.scroll_into_view_if_needed()
+ paging_info_text = self.unit_paging_info.text_content()
+ self.page.wait_for_timeout(5000)
+ re_search_result = re.search(
+ "Showing (\d+) to (\d+) of (\d+) entries", paging_info_text
+ )
+ return int(re_search_result.group(3))
+
+ def extract_paging_unit_list_count_active_only(self) -> int:
+ self.page.locator("#screeningStatusList").select_option(label="Active")
+ self.unit_paging_info.scroll_into_view_if_needed()
+ paging_info_text = self.unit_paging_info.text_content()
+ self.page.wait_for_timeout(5000)
+ re_search_result = re.search(
+ "Showing (\d+) to (\d+) of (\d+) entries", paging_info_text
+ )
+ return int(re_search_result.group(3))
+
+ # Method to directly attempt to create the unit
+ def create_unit_if_not_exists(self, unit_name: str) -> None:
+ self.page.get_by_role("button", name="Add Screening Unit").click()
+ self.page.locator("//input[@id='unitNameText']").fill(unit_name)
+ self.page.wait_for_selector(
+ f"//input[@id='unitTypeText' and @type='radio' and @value='MOBILE']"
+ ).check()
+ self.page.locator("#addButtonInAddUnitPopup").click()
+ # Check for the error message indicating a duplicate unit
+ try:
+ self.page.wait_for_selector("p#error_unitNameText", timeout=3000)
+ if (
+ "Name is already in use by another unit"
+ in self.error_message_locator.inner_text()
+ ):
+ # If the error message appears, click cancel to close the popup
+ self.page.wait_for_selector("#cancelButtonInAddUnitPopup").click()
+ except playwright._impl._errors.TimeoutError:
+ # If no error message appears, assume the Unit was added successfully
+ pass
+ self.page.wait_for_timeout(5000)
+
+ def create_unit_for_test_data(self, unit_name: str) -> None:
+ self.page.get_by_role("button", name="Add Screening Unit").click()
+ self.page.locator("//input[@id='unitNameText']").fill(unit_name)
+ self.page.wait_for_selector(
+ f"//input[@id='unitTypeText' and @type='radio' and @value='MOBILE']"
+ ).check()
+ self.page.locator("#addButtonInAddUnitPopup").click()
diff --git a/pages/rlp_location_list_page.py b/pages/rlp_location_list_page.py
new file mode 100644
index 00000000..99fdb151
--- /dev/null
+++ b/pages/rlp_location_list_page.py
@@ -0,0 +1,193 @@
+from __future__ import annotations
+import re
+import playwright
+from playwright.sync_api import Page, expect
+
+
+class ScreeningLocationListPage:
+
+ def __init__(self, page: Page) -> None:
+
+ self.page = page
+ self.add_screening_location_btn = page.locator("button#addLocationButton")
+ self.screening_location_name_textbox = page.locator("input#locationNameText")
+ self.add_screening_location_btn_on_popup = page.locator(
+ "#addButtonInAddLocationPopupText"
+ )
+ self.cancel_add_screening_location_btn = page.locator(
+ "#cancelButtonInAddLocationPopup"
+ )
+ self.paging_info = page.locator("#screeningLocationList_info")
+ self.location_name_filter = page.locator("th#nameFilter > input")
+ self.filtered_location = page.locator("//tbody/tr/td[2]")
+ self.amend_screening_location_name = page.locator("#locationNameAmendText")
+ self.amend_screening_location_btn = page.locator(
+ "#amendButtonInAmendLocationPopupText"
+ )
+ self.cancel_amend_location_btn = page.locator(
+ "#cancelButtonInAmendLocationPopup"
+ )
+ self.log_out_btn = page.locator("#logoutButton")
+ self.create_screening_cohort_by_gp_practice_btn = page.locator(
+ "#addCohortByPracticeButtonText"
+ )
+ self.screening_cohort_name_textbox = page.locator("//input[@id='description']")
+ self.default_screening_location_dropdown = page.locator(
+ "//select[@id='defaultLocation']"
+ )
+ self.default_screening_unit_dropdown = page.locator(
+ "//select[@id='defaultUnit']"
+ )
+ self.select_gp_practices_btn = page.locator("button#selectElementsButton.btn")
+ self.create_screening_cohort_save_btn = page.locator("#saveButton")
+ self.gp_practice_name_field = page.locator("th#nameFilter > input")
+ self.gp_code_field = page.locator("th#codeFilter > input")
+ self.add_gp_practices_to_include = page.locator("//button[text()='Add']")
+ self.expected_attendance_rate = page.locator("input#uptakePercentage")
+ self.error_message_locator = page.locator("p#error_locationNameText")
+
+ def click_add_screening_location_btn(self) -> ScreeningLocationListPage:
+ self.add_screening_location_btn.click()
+ return self
+
+ def enter_screening_location_name(
+ self, location_name: str
+ ) -> ScreeningLocationListPage:
+ self.screening_location_name_textbox.fill(location_name)
+ return self
+
+ def click_add_screening_location_btn_on_popup(self) -> ScreeningLocationListPage:
+ self.add_screening_location_btn_on_popup.click()
+ self.page.wait_for_timeout(3000)
+ return self
+
+ def click_cancel_add_screening_location_btn(self) -> ScreeningLocationListPage:
+ self.cancel_add_screening_location_btn.click()
+ return self
+
+ def enter_screening_location_filter_textbox(
+ self, location_name: str
+ ) -> ScreeningLocationListPage:
+ self.location_name_filter.fill(location_name)
+ self.page.wait_for_timeout(4000)
+ return self
+
+ def extract_paging_info(self) -> int:
+ self.paging_info.scroll_into_view_if_needed()
+ paging_info_text = self.paging_info.text_content()
+ self.page.wait_for_timeout(5000)
+ re_search_result = re.search(r"\b(\d{1,5}) entries\b", paging_info_text)
+ return int(re_search_result.group(1))
+
+ def invoke_filtered_screening_location(self) -> None:
+ self.page.wait_for_timeout(4000)
+ self.filtered_location.dblclick()
+
+ def enter_amend_screening_location_name_fn(self) -> ScreeningLocationListPage:
+ existing_value = self.amend_screening_location_name.input_value()
+ new_name = f"{existing_value}+ Amend"
+ self.amend_screening_location_name.fill(new_name)
+ return new_name
+
+ def enter_amend_screening_location_name(
+ self, amend_name: str
+ ) -> ScreeningLocationListPage:
+ self.amend_screening_location_name.fill(amend_name)
+ return self
+
+ def screening_location_count_in_db(self, db_util) -> int:
+ result = db_util.get_results(
+ """select count(1)
+ from rlp_locations where bso_organisation_id in(
+ select bso_organisation_id from bso_organisations where bso_organisation_code = 'LAV')
+ """
+ )
+ return result["count"][0]
+
+ def click_amend_screening_location_btn(self) -> None:
+ self.amend_screening_location_btn.click()
+
+ def click_cancel_amend_location_btn(self) -> None:
+ self.cancel_amend_location_btn.click()
+
+ def value_of_filtered_location_name(self):
+ filtered_value = self.filtered_location.text_content()
+ self.page.wait_for_timeout(4000)
+ return filtered_value
+
+ def click_log_out_btn(self) -> None:
+ self.log_out_btn.click()
+
+ def click_create_screening_cohort_by_gp_practice_btn(
+ self,
+ ) -> ScreeningLocationListPage:
+ self.create_screening_cohort_by_gp_practice_btn.click()
+ return self
+
+ def enter_screening_cohort_name_textbox(
+ self, cohort_name: str
+ ) -> ScreeningLocationListPage:
+ self.screening_cohort_name_textbox.fill(cohort_name)
+ return self
+
+ def select_default_screening_location_dropdown(
+ self, location_name: str
+ ) -> ScreeningLocationListPage:
+ self.default_screening_location_dropdown.select_option(location_name)
+ return self
+
+ def select_default_screening_unit_dropdown(
+ self, unit_name: str
+ ) -> ScreeningLocationListPage:
+ self.default_screening_unit_dropdown.select_option(unit_name)
+ return self
+
+ def click_select_gp_practices_btn(self) -> ScreeningLocationListPage:
+ self.select_gp_practices_btn.click()
+ return self
+
+ def click_create_screening_cohort_save_btn(self) -> ScreeningLocationListPage:
+ self.create_screening_cohort_save_btn.click()
+ return self
+
+ def enter_gp_practice_name_field(self, gp_name: str) -> ScreeningLocationListPage:
+ self.gp_practice_name_field.fill(gp_name)
+ return self
+
+ def enter_gp_code_filed(self, gp_code: str) -> ScreeningLocationListPage:
+ self.gp_code_field.fill(gp_code)
+ return self
+
+ def click_add_btn_gp_practices_to_include(self) -> ScreeningLocationListPage:
+ self.add_gp_practices_to_include.click()
+ return self
+
+ def enter_expected_attendance_rate(self) -> ScreeningLocationListPage:
+ self.expected_attendance_rate.click()
+ return self
+
+ def create_screening_location(self, location_name) -> None:
+ self.click_add_screening_location_btn()
+ self.enter_screening_location_name(location_name)
+ self.click_add_screening_location_btn_on_popup()
+
+ # Method to directly attempt to create the location
+ def create_location_if_not_exists(self, location_name: str):
+ self.click_add_screening_location_btn()
+ self.enter_screening_location_name(location_name)
+ self.click_add_screening_location_btn_on_popup()
+ # Check for the error message indicating a duplicate location
+ try:
+ self.page.wait_for_selector(
+ "p#error_locationNameText", timeout=3000
+ ) # Adjust timeout as needed
+ if (
+ "Name is already in use by another location"
+ in self.error_message_locator.inner_text()
+ ):
+ # If the error message appears, click cancel to close the popup
+ self.click_cancel_add_screening_location_btn()
+ except playwright._impl._errors.TimeoutError:
+ # If no error message appears, assume the location was added successfully
+ pass
+ self.page.wait_for_timeout(500)
diff --git a/pages/rlp_unit_list_page.py b/pages/rlp_unit_list_page.py
new file mode 100644
index 00000000..1ea863ba
--- /dev/null
+++ b/pages/rlp_unit_list_page.py
@@ -0,0 +1,258 @@
+from __future__ import annotations
+import re
+from playwright.sync_api import Page, expect, playwright
+import pytest
+
+
+class ScreeningUnitListPage:
+
+ def __init__(self, page: Page) -> None:
+ self.page = page
+ self.screening_unit_name_txt_box = page.locator("input#unitNameText")
+ self.add_screening_unit_btn_on_pop_up_window = page.locator(
+ "#addButtonInAddUnitPopup"
+ )
+ self.unit_type_inactive_radio_btn = page.locator(
+ "//input[@id='statusText' and @type='radio' and @value='INACTIVE']"
+ )
+ self.unit_type_active_radio_btn = page.locator(
+ "//input[@id='statusText' and @type='radio' and @value='ACTIVE']"
+ )
+ self.status_dropdown = page.locator("#screeningStatusList")
+ self.unit_paging_info = page.locator("#screeningUnitList_info")
+ self.input_name_filter = page.locator("#nameFilter > input")
+ self.add_screening_unit_btn = page.locator("button#addUnitButton")
+ self.cancel_btn_on_pop_up = page.locator("#cancelButtonInAddUnitPopup")
+ self.no_matching_records_found_msg = page.locator(
+ "//td[text()='No matching records found']"
+ )
+ self.notes_txt_box = page.locator("textarea#notesText")
+ self.filterd_unit_name = page.locator("//tr//td[4]")
+ self.monday_appointment_value = page.locator("input#mondaySlotsAmendText")
+
+ def enter_screening_unit_name_txt_box(
+ self, unit_name: dict
+ ) -> ScreeningUnitListPage:
+ self.screening_unit_name_txt_box.fill(unit_name)
+ return self
+
+ def dbl_click_on_filtered_unit_name(self) -> ScreeningUnitListPage:
+ self.filterd_unit_name.dblclick()
+ return self
+
+ def select_status_dropdown(self, status_value: str) -> ScreeningUnitListPage:
+ self.status_dropdown.select_option(label=status_value)
+ return self
+
+ def click_add_screening_unit_btn_on_pop_up_window(self) -> ScreeningUnitListPage:
+ self.add_screening_unit_btn_on_pop_up_window.click()
+ return self
+
+ def select_unit_status_radio_btn(self, field_value: str) -> ScreeningUnitListPage:
+ self.page.wait_for_selector(
+ f"//input[@id='statusText' and @type='radio' and @value='{field_value}']"
+ ).check()
+ return self
+
+ def select_unit_type_radio_btn(self, field_value: str) -> ScreeningUnitListPage:
+ self.page.wait_for_selector(
+ f"//input[@id='unitTypeText' and @type='radio' and @value='{field_value}']"
+ ).check()
+ return self
+
+ def select_amend_unit_status_radio_btn(
+ self, field_value: str
+ ) -> ScreeningUnitListPage:
+ self.page.wait_for_selector(
+ f"//input[@id='statusAmendText' and @type='radio' and @value='{field_value}']"
+ ).check()
+ return self
+
+ def select_amend_unit_type_radio_btn(
+ self, field_value: str
+ ) -> ScreeningUnitListPage:
+ self.page.wait_for_selector(
+ f"//input[@id='unitTypeAmendText' and @type='radio' and @value='{field_value}']"
+ ).check()
+ return self
+
+ def select_unit_type_inactive_radio_btn(self) -> ScreeningUnitListPage:
+ self.unit_type_inactive_radio_btn.check()
+ return self
+
+ def select_unit_type_active_radio_btn(self) -> ScreeningUnitListPage:
+ self.unit_type_active_radio_btn.check()
+ return self
+
+ def select_status_mobile_radio_btn(self) -> ScreeningUnitListPage:
+ self.select_unit_type_radio_btn("MOBILE")
+ return self
+
+ def select_status_static_radio_btn(self) -> ScreeningUnitListPage:
+ self.select_unit_type_radio_btn("STATIC")
+ return self
+
+ def enter_notes_text_box(self, notes: str) -> ScreeningUnitListPage:
+ self.notes_txt_box.fill(notes)
+ return self
+
+ def click_cancel_btn_on_pop_up_window(self) -> ScreeningUnitListPage:
+ self.cancel_btn_on_pop_up.click()
+ return self
+
+ def verify_unit_has_no_matching_records_available_in_the_table(
+ self, unit_name: str
+ ) -> ScreeningUnitListPage:
+ self.status_dropdown.select_option(label="All")
+ self.input_name_filter.fill(unit_name)
+ self.page.wait_for_timeout(3000)
+ self.no_matching_records_found_msg.is_visible()
+ return self
+
+ def expect_no_matching_records_found_msg(self) -> ScreeningUnitListPage:
+ self.no_matching_records_found_msg.is_visible()
+ return self
+
+ def get_day_appointment_value(self) -> dict:
+ days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
+ # Using dictionary comprehension to get values for all days
+ return {
+ day: self.page.get_by_role("textbox", name=day).input_value()
+ for day in days
+ }
+
+ def enter_usual_number_of_appontments_text_box(
+ self, unit_data: dict
+ ) -> ScreeningUnitListPage:
+ if "mon" in unit_data:
+ self.page.get_by_role("textbox", name="Mon").fill(unit_data["mon"])
+ if "tue" in unit_data:
+ self.page.get_by_role("textbox", name="Tue").fill(unit_data["tue"])
+ if "wed" in unit_data:
+ self.page.get_by_role("textbox", name="Wed").fill(unit_data["wed"])
+ if "thu" in unit_data:
+ self.page.get_by_role("textbox", name="Thu").fill(unit_data["thu"])
+ if "fri" in unit_data:
+ self.page.get_by_role("textbox", name="Fri").fill(unit_data["fri"])
+ if "sat" in unit_data:
+ self.page.get_by_role("textbox", name="Sat").fill(unit_data["sat"])
+ if "sun" in unit_data:
+ self.page.get_by_role("textbox", name="Sun").fill(unit_data["sun"])
+
+ def enter_amend_screening_unit_name(self, text: str) -> ScreeningUnitListPage:
+ self.page.locator("input#unitNameAmendText").fill(text)
+ return self
+
+ def enter_amend_screening_unit_notes(self, text: str) -> ScreeningUnitListPage:
+ self.page.locator("textarea#notesAmendText").fill(text)
+ return self
+
+ def clear_name_and_status_filter(self) -> None:
+ self.page.wait_for_selector("#screeningStatusList").select_option("")
+ self.page.wait_for_selector("#nameFilter input").fill("")
+
+ def click_amend_screening_unit_btn_on_pop_up_window(self) -> None:
+ self.page.locator("//span[@id='amendButtonInAmendUnitPopupText']").click()
+
+ def click_cancel_btn_on_amend_screening_unit_pop_up_window(self) -> None:
+ self.page.locator("//span[@id='amendButtonInAmendUnitPopupText']").click()
+
+ def filter_unit_by_name(self, unit_name) -> ScreeningUnitListPage:
+ self.status_dropdown.select_option(label="All")
+ self.input_name_filter.fill(unit_name)
+ self.page.wait_for_timeout(5000)
+ return self
+
+ def verify_screening_unit_by_name(self, expected_notes) -> ScreeningUnitListPage:
+ self.status_dropdown.select_option(label="All")
+ self.input_name_filter.fill(expected_notes)
+ self.page.wait_for_timeout(3000)
+ note_values = self.page.wait_for_selector("//tr//td[4]").text_content()
+ self.page.wait_for_timeout(3000)
+ assert note_values == expected_notes
+ return self
+
+ def click_add_screening_unit_btn(self) -> ScreeningUnitListPage:
+ self.add_screening_unit_btn.click()
+ return self
+
+ # Method to directly attempt to create the Unit
+ def create_unit(self, unit_name: str) -> ScreeningUnitListPage:
+ self.click_add_screening_unit_btn()
+ self.enter_screening_unit_name_txt_box(unit_name)
+ self.select_status_mobile_radio_btn()
+ self.click_add_screening_unit_btn_on_pop_up_window()
+ self.page.wait_for_timeout(3000)
+ return self
+
+ def add_screening_unit(
+ self, unit_data: dict, unit_type: str = "MOBILE", unit_status: str = "ACTIVE"
+ ):
+ # Adds a new screening unit using the provided unit data
+ self.add_screening_unit_btn.click()
+ self.screening_unit_name_txt_box.fill(unit_data["unit_name"])
+ # Unit Type radio button
+ if unit_type:
+ self.select_unit_type_radio_btn(unit_type)
+ if unit_status:
+ self.select_unit_status_radio_btn(unit_status)
+ self.enter_usual_number_of_appontments_text_box(unit_data)
+ if "notes" in unit_data:
+ self.page.locator("textarea#notesText").fill(unit_data["notes"])
+
+ def amend_screening_unit(self, unit_data: dict) -> None:
+ self.enter_amend_screening_unit_name(unit_data["unit_name"])
+ self.enter_amend_usual_number_of_appointments_text_box(unit_data)
+ self.enter_amend_screening_unit_notes(unit_data["notes"])
+ self.click_amend_screening_unit_btn_on_pop_up_window()
+
+ def screening_unit_list_count_in_db(self, db_util) -> int:
+ result = db_util.get_results(
+ """select count(1)
+ from rlp_units where bso_organisation_id in(
+ select bso_organisation_id from bso_organisations where bso_organisation_code = 'LAV')
+ """
+ )
+ return result["count"][0]
+
+ def extract_paging_unit_list_count(self):
+ self.input_name_filter.fill("")
+ self.status_dropdown.select_option(label="All")
+ self.page.wait_for_timeout(4000)
+ self.unit_paging_info.scroll_into_view_if_needed()
+ paging_info_text = self.unit_paging_info.text_content()
+ re_search_result = re.search(r"\b(\d{1,5}) entries\b", paging_info_text)
+ return int(re_search_result.group(1))
+
+ def enter_amend_usual_number_of_appointments_text_box(
+ self, unit_data: dict
+ ) -> ScreeningUnitListPage:
+ if "mon" in unit_data:
+ self.page.locator("//input[@id='mondaySlotsAmendText']").fill(
+ unit_data["mon"]
+ )
+ if "tue" in unit_data:
+ self.page.locator("//input[@id='tuesdaySlotsAmendText']").fill(
+ unit_data["tue"]
+ )
+ if "wed" in unit_data:
+ self.page.locator("//input[@id='wednesdaySlotsAmendText']").fill(
+ unit_data["wed"]
+ )
+ if "thu" in unit_data:
+ self.page.locator("//input[@id='thursdaySlotsAmendText']").fill(
+ unit_data["thu"]
+ )
+ if "fri" in unit_data:
+ self.page.locator("//input[@id='fridaySlotsAmendText']").fill(
+ unit_data["fri"]
+ )
+ if "sat" in unit_data:
+ self.page.locator("//input[@id='saturdaySlotsAmendText']").fill(
+ unit_data["sat"]
+ )
+ if "sun" in unit_data:
+ self.page.locator("//input[@id='sundaySlotsAmendText']").fill(
+ unit_data["sun"]
+ )
+ return self
diff --git a/pytest.ini b/pytest.ini
index 40fa9d53..f9580cea 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -1,25 +1,57 @@
[pytest]
-# Enable python logging during tests, at info level by default
log_cli = True
log_cli_level = INFO
-# Default options for pytest to use each execution
addopts =
- -m "not utils"
+ --base-url=https://ci-infra.nonprod.breast-screening-select.nhs.uk
--html=test-results/report.html
--self-contained-html
--json-report
--json-report-file=test-results/results.json
- --json-report-omit=collectors
--tracing=retain-on-failure
+ -m "not specific_requirement"
# Allows pytest to identify the base of this project as the pythonpath
pythonpath = .
-# These are the tags that pytest will recognise when using @pytest.mark
markers =
- example: tests used for example purposes by this blueprint
- utils: tests for utility classes provided by this blueprint
+ # Stages
+ smoke: tests that prove the ui has loaded correctly
branch: tests designed to run at a branch level
main: tests designed to run against the main branch
release: tests designed to run specifically against a release branch
+
+ # Utils
+ utils: tests for utils (unit tests)
+
+ # Business areas
+ subjects: tests for subject-based scenarios
+ sspi: tests for sspi-based scenarios
+ ceased: tests for ceased-based scenarios
+ invited: tests for subjects never invited-based scenarios
+ gp_practice_list: api tests for gp practice list-based scenario
+ gp_practices_assigned_to_bso: api tests for gp practices assigned to bso-based scenarios
+ geographic_outcode_list: api tests for geographic outcode list-based scenarios
+ bso_contact_list: api tests for bso contact list-based scenarios
+ subject_search: api tests for subject search-based scenarios
+ batch_list: api tests for batch list-based scenarios
+ outcome_list: api tests for outcome list-based scenarios
+ gp_practice_group_list: api tests for gp practice group list-based scenarios
+ outcode_group_list: api tests for outcode group list-based scenarios
+ sspi_action: api tests for sspi action report-based scenarios
+ sspi_information: api tests for sspi information report-based scenarios
+ ceased_unceased: api tests for ceased/unceased subjects report-based scenarios
+ subjects_never_invited: api tests for subjects never invited report-based scenarios
+ subject_demographic: api tests for subject demographic report-based scenarios
+ subjects_overdue: api tests for subjects overdue invitation-based scenarios
+ ceasing_instances: api tests for ceasing instances with no documentation-based scenarios
+ search_batches: api tests for search batches-based scenarios
+
+ # Other testing types
+ specific_requirement: tests which serve special purposes and should not run by default
+ accessibility: tests designed to run for accessibility scanning
+ instana: tests for accessing performance information from instana (experimental)
+ uiapi: ui api tests for testing the interaction with apis
+
+ # For testing / debugging
+ only: only run specific test (for local use only)
diff --git a/requirements.txt b/requirements.txt
index 2d819a10..ff1c318e 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,3 +3,6 @@ pytest-html>=4.1.1
pytest-json-report>=1.5.0
pytest-playwright-axe>=4.10.3
python-dotenv>=1.1.0
+pandas==2.2.*
+psycopg==3.2.*
+boto3==1.37.*
diff --git a/run_tests.sh b/run_tests.sh
index 5cec7116..d3fabd35 100755
--- a/run_tests.sh
+++ b/run_tests.sh
@@ -1,3 +1,7 @@
#!/bin/sh
-pytest
+BASE_URL=${1:-${BASE_URL}}
+TAG_TO_USE=${2:=${TAG_TO_USE}}
+export COGNITO_USER_PASSWORD=${3:=${COGNITO_USER_PASSWORD}}
+
+pytest --tracing retain-on-failure --base-url $1 -m $2
diff --git a/scripts/docker/examples/python/.tool-versions.example b/scripts/docker/examples/python/.tool-versions.example
deleted file mode 100644
index 92093116..00000000
--- a/scripts/docker/examples/python/.tool-versions.example
+++ /dev/null
@@ -1,2 +0,0 @@
-# python, SEE: https://hub.docker.com/_/python/tags
-# docker/python 3.11.4-alpine3.18@sha256:0135ae6442d1269379860b361760ad2cf6ab7c403d21935a8015b48d5bf78a86
diff --git a/scripts/docker/examples/python/Dockerfile b/scripts/docker/examples/python/Dockerfile
deleted file mode 100644
index d0780aa4..00000000
--- a/scripts/docker/examples/python/Dockerfile
+++ /dev/null
@@ -1,33 +0,0 @@
-# `*:latest` will be replaced with a corresponding version stored in the '.tool-versions' file
-# hadolint ignore=DL3007
-FROM python:latest as base
-
-# === Builder ==================================================================
-
-FROM base AS builder
-COPY ./assets/hello_world/requirements.txt /requirements.txt
-WORKDIR /packages
-RUN set -eux; \
- \
- # Install dependencies
- pip install \
- --requirement /requirements.txt \
- --prefix=/packages \
- --no-warn-script-location \
- --no-cache-dir
-
-# === Runtime ==================================================================
-
-FROM base
-ENV \
- LANG="C.UTF-8" \
- LC_ALL="C.UTF-8" \
- PYTHONDONTWRITEBYTECODE="1" \
- PYTHONUNBUFFERED="1" \
- TZ="UTC"
-COPY --from=builder /packages /usr/local
-COPY ./assets/hello_world /hello_world
-WORKDIR /hello_world
-USER nobody
-CMD [ "python", "app.py" ]
-EXPOSE 8000
diff --git a/scripts/docker/examples/python/Dockerfile.effective b/scripts/docker/examples/python/Dockerfile.effective
deleted file mode 100644
index 3f1ea6b0..00000000
--- a/scripts/docker/examples/python/Dockerfile.effective
+++ /dev/null
@@ -1,54 +0,0 @@
-# `*:latest` will be replaced with a corresponding version stored in the '.tool-versions' file
-FROM python:3.11.4-alpine3.18@sha256:0135ae6442d1269379860b361760ad2cf6ab7c403d21935a8015b48d5bf78a86 as base
-
-# === Builder ==================================================================
-
-FROM base AS builder
-COPY ./assets/hello_world/requirements.txt /requirements.txt
-WORKDIR /packages
-RUN set -eux; \
- \
- # Install dependencies
- pip install \
- --requirement /requirements.txt \
- --prefix=/packages \
- --no-warn-script-location \
- --no-cache-dir
-
-# === Runtime ==================================================================
-
-FROM base
-ENV \
- LANG="C.UTF-8" \
- LC_ALL="C.UTF-8" \
- PYTHONDONTWRITEBYTECODE="1" \
- PYTHONUNBUFFERED="1" \
- TZ="UTC"
-COPY --from=builder /packages /usr/local
-COPY ./assets/hello_world /hello_world
-WORKDIR /hello_world
-USER nobody
-CMD [ "python", "app.py" ]
-EXPOSE 8000
-
-# === Metadata =================================================================
-
-ARG IMAGE
-ARG TITLE
-ARG DESCRIPTION
-ARG LICENCE
-ARG GIT_URL
-ARG GIT_BRANCH
-ARG GIT_COMMIT_HASH
-ARG BUILD_DATE
-ARG BUILD_VERSION
-LABEL \
- org.opencontainers.image.base.name=$IMAGE \
- org.opencontainers.image.title="$TITLE" \
- org.opencontainers.image.description="$DESCRIPTION" \
- org.opencontainers.image.licenses="$LICENCE" \
- org.opencontainers.image.url=$GIT_URL \
- org.opencontainers.image.ref.name=$GIT_BRANCH \
- org.opencontainers.image.revision=$GIT_COMMIT_HASH \
- org.opencontainers.image.created=$BUILD_DATE \
- org.opencontainers.image.version=$BUILD_VERSION
diff --git a/scripts/docker/examples/python/VERSION b/scripts/docker/examples/python/VERSION
deleted file mode 100644
index 8acdd82b..00000000
--- a/scripts/docker/examples/python/VERSION
+++ /dev/null
@@ -1 +0,0 @@
-0.0.1
diff --git a/scripts/docker/examples/python/assets/hello_world/app.py b/scripts/docker/examples/python/assets/hello_world/app.py
deleted file mode 100644
index 4844e89c..00000000
--- a/scripts/docker/examples/python/assets/hello_world/app.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from flask import Flask
-from flask_wtf.csrf import CSRFProtect
-
-app = Flask(__name__)
-csrf = CSRFProtect()
-csrf.init_app(app)
-
-@app.route("/")
-def index():
- return "Hello World!"
-
-app.run(host='0.0.0.0', port=8000)
diff --git a/scripts/docker/examples/python/assets/hello_world/requirements.txt b/scripts/docker/examples/python/assets/hello_world/requirements.txt
deleted file mode 100644
index ed285bb8..00000000
--- a/scripts/docker/examples/python/assets/hello_world/requirements.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-blinker==1.6.2
-click==8.1.7
-Flask-WTF==1.2.0
-Flask==2.3.3
-itsdangerous==2.1.2
-Jinja2==3.1.6
-MarkupSafe==2.1.3
-pip==23.3
-setuptools==78.1.1
-Werkzeug==3.0.6
-wheel==0.41.1
-WTForms==3.0.1
diff --git a/scripts/docker/examples/python/tests/goss.yaml b/scripts/docker/examples/python/tests/goss.yaml
deleted file mode 100644
index 589db37b..00000000
--- a/scripts/docker/examples/python/tests/goss.yaml
+++ /dev/null
@@ -1,8 +0,0 @@
-package:
- python:
- installed: true
-
-command:
- pip list | grep -i flask:
- exit-status: 0
- timeout: 60000
diff --git a/setup_env_file.py b/setup_env_file.py
index e36e2fc6..8ee38bd8 100644
--- a/setup_env_file.py
+++ b/setup_env_file.py
@@ -11,19 +11,33 @@
import os
from pathlib import Path
-REQUIRED_KEYS = ["USER_PASS"]
-DEFAULT_LOCAL_ENV_PATH = Path(os.getcwd()) / 'local.env'
+REQUIRED_KEYS = [
+ "COGNITO_USER_PASSWORD",
+ "DB_HOST",
+ "DB_PORT",
+ "DB_NAME",
+ "DB_USER",
+ "DB_PASSWORD",
+]
+DEFAULT_LOCAL_ENV_PATH = Path(os.getcwd()) / "local.env"
+
def create_env_file():
"""
Create a local.env file with the required keys.
"""
- with open(DEFAULT_LOCAL_ENV_PATH, 'w') as f:
- f.write("# Use this file to populate secrets without committing them to the codebase (as this file is set in .gitignore).\n")
- f.write("# To retrieve values as part of your tests, use os.getenv('VARIABLE_NAME').\n")
- f.write("# Note: When running in a pipeline or workflow, you should pass these variables in at runtime.\n\n")
+ with open(DEFAULT_LOCAL_ENV_PATH, "w") as f:
+ f.write(
+ "# Use this file to populate secrets without committing them to the codebase (as this file is set in .gitignore).\n"
+ )
+ f.write(
+ "# To retrieve values as part of your tests, use os.getenv('VARIABLE_NAME').\n"
+ )
+ f.write(
+ "# Note: When running in a pipeline or workflow, you should pass these variables in at runtime.\n\n"
+ )
for key in REQUIRED_KEYS:
- f.write(f'{key}=\n')
+ f.write(f"{key}=\n")
if __name__ == "__main__":
diff --git a/tests/accessibility/test_accessibility.py b/tests/accessibility/test_accessibility.py
new file mode 100644
index 00000000..13600163
--- /dev/null
+++ b/tests/accessibility/test_accessibility.py
@@ -0,0 +1,52 @@
+"""
+Accessibility Tests: This covers accessibility scanning using axe-core for each url in the LIST_OF_PAGES.
+"""
+
+import pytest
+from playwright.sync_api import Page
+from utils.axe import Axe
+from utils.user_tools import UserTools
+
+
+LIST_OF_PAGES = [
+ "home",
+ # Subjects
+ "subjects",
+ "subjects/9300000001",
+ # Batch Management
+ "batch/list",
+ "fsBatch/create",
+ "rispAgexBatch/create",
+ "ntddBatch/create",
+ # Outcome List
+ "outcome/list",
+ # Parameters
+ "gpPracticeGroup/list",
+ "gpPracticeGroup/create",
+ "outcodeGroup/list",
+ "outcodeGroup/create",
+ "rispDefaults",
+ "bsoDefaults",
+ "failsafeParameters",
+ # BSO Mapping
+ "gpPractice/list",
+ "gpPractice/A12345",
+ "assignedGpPractice/list",
+ "outcode/list",
+ "outcodeView/EX1",
+ # BSO Contact List
+ "bso/list",
+ "bso/AGA",
+]
+
+
+@pytest.mark.specific_requirement
+@pytest.mark.accessibility
+def test_accessibility_sweep(page: Page, user_tools: UserTools) -> None:
+ """This test will loop through each page on the list of pages, and run Axe to generate an accessibility report."""
+
+ user_tools.user_login(page, "BSO User - BS1")
+
+ for url in LIST_OF_PAGES:
+ page.goto(f"/bss/{url}")
+ Axe.run(page, filename=url.replace("/", "__"))
diff --git a/tests/api/batch_management/test_batch_list_api.py b/tests/api/batch_management/test_batch_list_api.py
new file mode 100644
index 00000000..acbeec13
--- /dev/null
+++ b/tests/api/batch_management/test_batch_list_api.py
@@ -0,0 +1,188 @@
+"""
+Batch List Tests: These tests cover the Batch List, accessed from the Batch Management tab.
+"""
+
+import pytest
+from utils.api_utils import ApiUtils
+from playwright.sync_api import BrowserContext
+
+
+pytestmark = [pytest.mark.batch_list, pytest.mark.uiapi]
+
+API_URL = "/bss/batch/search"
+
+
+def test_batch_list_search_all(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search Batch List on all batches
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSortDirectionWithOrder[0countDateTime]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 10
+ for batch in response_data["results"]:
+ # Have to exclude RISP due to test data showing "RISP1" etc
+ if batch["description"] != "RISP":
+ assert len(batch["bsoBatchId"]) == 10
+ assert batch["bsoCode"] == batch["bsoBatchId"][:3]
+
+
+def test_bso_search_national_all(api_national_user_session: BrowserContext) -> None:
+ """
+ API test to check search Batch List on all BSO's
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[batchType]": "RISP_AGEX",
+ "columnSortDirectionWithOrder[0countDateTime]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_national_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 2
+ for batch in response_data["results"]:
+ if batch["description"] == "RISP_AGEX":
+ assert len(batch["bsoBatchId"]) == 10
+ assert batch["bsoCode"] == batch["bsoBatchId"][:3]
+
+
+def test_invalid_helpdesk_user(api_helpdesk_session: BrowserContext) -> None:
+ """
+ API test to check an invaild user doesn't have access to the BSO Contact List, so returns a 403 error.
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[code]": "BS1",
+ "columnSortDirectionWithOrder[0code]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_helpdesk_session, API_URL).get_request(data, False)
+ assert response_data == 403
+
+
+def test_batch_list_search_batch_type_risp_agex(
+ api_bso_user_session: BrowserContext,
+) -> None:
+ """
+ API test to check search Batch Type on all RISP_AGEX batches
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[batchType]": "RISP_AGEX",
+ "columnSortDirectionWithOrder[0countDateTime]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 10
+ for batch in response_data["results"]:
+ if batch["description"] == "RISP_AGEX":
+ assert len(batch["bsoBatchId"]) == 10
+ assert batch["bsoCode"] == batch["bsoBatchId"][:3]
+
+
+def test_batch_list_search_batch_type_ntdd(
+ api_bso_user_session: BrowserContext,
+) -> None:
+ """
+ API test to check search Batch Type on all NTDD batches
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[batchType]": "NTDD",
+ "columnSortDirectionWithOrder[0countDateTime]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 10
+ for batch in response_data["results"]:
+ if batch["description"] == "NTDD":
+ assert len(batch["bsoBatchId"]) == 10
+ assert batch["bsoCode"] == batch["bsoBatchId"][:3]
+
+
+def test_batch_list_search_batch_type_routine_failsafe(
+ api_bso_user_session: BrowserContext,
+) -> None:
+ """
+ API test to check search Batch Type on all NTDD batches
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[batchType]": "FS",
+ "columnSortDirectionWithOrder[0countDateTime]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 10
+ for batch in response_data["results"]:
+ if batch["description"] == "FS":
+ assert len(batch["bsoBatchId"]) == 10
+ assert batch["bsoCode"] == batch["bsoBatchId"][:3]
+
+
+def test_batch_list_search_failsafe_flag(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search Failsafe Flag on all batches
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[includeYoungerSubjects]": "NO",
+ "columnSortDirectionWithOrder[0countDateTime]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 10
+ for batch in response_data["results"]:
+ # Have to exclude RISP due to test data showing "RISP1" etc
+ if batch["description"] != "RISP":
+ assert len(batch["bsoBatchId"]) == 10
+ assert batch["bsoCode"] == batch["bsoBatchId"][:3]
+
+
+def test_batch_list_search_batch_title(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search Batch Title for "Perform" on all batches
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[title]": "Perform",
+ "columnSortDirectionWithOrder[0countDateTime]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 10
+ for batch in response_data["results"]:
+ assert str(batch["title"]).startswith("Perform")
diff --git a/tests/api/bso_contact_list/test_bso_contact_list_api.py b/tests/api/bso_contact_list/test_bso_contact_list_api.py
new file mode 100644
index 00000000..da02d628
--- /dev/null
+++ b/tests/api/bso_contact_list/test_bso_contact_list_api.py
@@ -0,0 +1,183 @@
+"""
+BSO Contact List Tests: These tests cover the BSO Contact List, accessed from the BSO Contact List tab.
+"""
+
+import pytest
+from utils.api_utils import ApiUtils
+from playwright.sync_api import BrowserContext
+
+
+pytestmark = [pytest.mark.bso_contact_list, pytest.mark.uiapi]
+
+API_URL = "/bss/bso/search"
+
+
+def test_bso_search_all(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on all BSO's
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSortDirectionWithOrder[0code]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 10
+ for bso in response_data["results"]:
+ assert len(bso["code"]) == 3
+
+
+def test_bso_search_national_all(api_national_user_session: BrowserContext) -> None:
+ """
+ API test to check search on all BSO's
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSortDirectionWithOrder[0code]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_national_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 10
+ for bso in response_data["results"]:
+ assert len(bso["code"]) == 3
+
+
+def test_invalid_user(api_helpdesk_session: BrowserContext) -> None:
+ """
+ API test to check an invaild user doesn't have access to the BSO Contact List, so returns a 403 error.
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[code]": "BS1",
+ "columnSortDirectionWithOrder[0code]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_helpdesk_session, API_URL).get_request(data, False)
+ assert response_data == 403
+
+
+def test_bso_search_bso(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on BSO AGA
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[code]": "AGA",
+ "columnSortDirectionWithOrder[0code]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 1
+ for bso in response_data["results"]:
+ assert bso["code"] == "AGA"
+
+
+def test_bso_search_bso_name(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on BSO Name containing "BSO"
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "2",
+ "searchText": "",
+ "columnSearchText[name]": "BSO",
+ "columnSortDirectionWithOrder[0code]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 2
+ for name in response_data["results"]:
+ assert str(name["name"]).startswith("BSO")
+
+
+def test_bso_search_sqas_region_north(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on SQAS Region "North"
+ """
+ data = {
+ "draw": "5",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[bsoRegionName]": "NORTH",
+ "columnSortDirectionWithOrder[0code]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 5
+ assert len(response_data["results"]) == 10
+ assert response_data["results"][0]["code"] == "BYO"
+
+
+def test_bso_search_sqas_region_south(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on SQAS Region "South"
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[bsoRegionName]": "SOUTH",
+ "columnSortDirectionWithOrder[0code]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 10
+ assert response_data["results"][0]["code"] == "BS2"
+
+
+def test_bso_search_status_active(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on Status "Active"
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[active]": "true",
+ "columnSortDirectionWithOrder[0code]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 2
+ assert response_data["results"][0]["code"] == "BS1"
+
+
+def test_bso_search_status_inactive(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on Status "Inactive"
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[active]": "false",
+ "columnSortDirectionWithOrder[0code]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 10
+ assert response_data["results"][0]["code"] == "AGA"
diff --git a/tests/api/bso_mapping/test_assigned_bsos_api.py b/tests/api/bso_mapping/test_assigned_bsos_api.py
new file mode 100644
index 00000000..28926241
--- /dev/null
+++ b/tests/api/bso_mapping/test_assigned_bsos_api.py
@@ -0,0 +1,109 @@
+"""
+GP Practices Assigned to BSO Tests: These tests cover the GP Practices Assigned to BSO, accessed from the BSO Mapping tab.
+"""
+
+import pytest
+from utils.api_utils import ApiUtils
+from playwright.sync_api import BrowserContext
+
+
+pytestmark = [pytest.mark.gp_practices_assigned_to_bso, pytest.mark.uiapi]
+
+API_URL = "/bss/assignedGpPractice/search"
+
+
+def test_gp_practices_assigned_to_bso_search_all(
+ api_bso_user_session: BrowserContext,
+) -> None:
+ """
+ API test to check search on all GP Practices
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSortDirectionWithOrder[0lastUpdatedOn]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 10
+ assert response_data["results"][0]["code"] == "A12345"
+
+
+def test_invalid_national_user(api_national_user_session: BrowserContext) -> None:
+ """
+ API test to check an invaild user (National user) doesn't have access to the GP Practices Assigned to BSO, so returns a 403 error.
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSortDirectionWithOrder[0lastUpdatedOn]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_national_user_session, API_URL).get_request(
+ data, False
+ )
+ assert response_data == 403
+
+
+def test_invalid_helpdesk_user(api_helpdesk_session: BrowserContext) -> None:
+ """
+ API test to check an invaild user (Helpdesk user) doesn't have access to the GP Practices Assigned to BSO, so returns a 403 error.
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSortDirectionWithOrder[0lastUpdatedOn]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_helpdesk_session, API_URL).get_request(data, False)
+ assert response_data == 403
+
+
+def test_gp_practices_assigned_to_bso_search_practice(
+ api_bso_user_session: BrowserContext,
+) -> None:
+ """
+ API test to check search on a specific GP Practice
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[code]": "EX0007",
+ "columnSortDirectionWithOrder[0lastUpdatedOn]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 1
+ assert response_data["results"][0]["code"] == "EX0007"
+
+
+def test_gp_practices_assigned_to_bso_search_name(
+ api_bso_user_session: BrowserContext,
+) -> None:
+ """
+ API test to check search on GP Practice Name containing "Gold"
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[name]": "Gold",
+ "columnSortDirectionWithOrder[0lastUpdatedOn]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 1
+ for name in response_data["results"]:
+ assert str(name["name"]).startswith("Gold")
diff --git a/tests/api/bso_mapping/test_geographic_outcode_list_api.py b/tests/api/bso_mapping/test_geographic_outcode_list_api.py
new file mode 100644
index 00000000..e8ab7425
--- /dev/null
+++ b/tests/api/bso_mapping/test_geographic_outcode_list_api.py
@@ -0,0 +1,134 @@
+"""
+Geographic Outcode List Tests: These tests cover the Geographic Outcode List, accessed from the BSO Mapping tab.
+"""
+
+import pytest
+from utils.api_utils import ApiUtils
+from playwright.sync_api import BrowserContext
+
+
+pytestmark = [pytest.mark.geographic_outcode_list, pytest.mark.uiapi]
+
+API_URL = "/bss/outcode/search"
+
+
+def test_geo_outcode_search_all(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on all BSO's
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSortDirectionWithOrder[0bso.code]": "asc",
+ "columnSortDirectionWithOrder[1outcode]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 10
+ for bso in response_data["results"]:
+ assert len(bso["bso"]["code"]) == 3
+
+
+def test_geo_outcode_search_national_all(
+ api_national_user_session: BrowserContext,
+) -> None:
+ """
+ API test logged in as a National user to check search on all BSO's
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSortDirectionWithOrder[0bso.code]": "asc",
+ "columnSortDirectionWithOrder[1outcode]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_national_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 10
+ for bso in response_data["results"]:
+ assert len(bso["bso"]["code"]) == 3
+
+
+def test_invalid_helpdesk_user(api_helpdesk_session: BrowserContext) -> None:
+ """
+ API test to check an invaild user (Helpdesk user) doesn't have access to the Geographic Outcode List, so returns a 403 error.
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSortDirectionWithOrder[0bso.code]": "asc",
+ "columnSortDirectionWithOrder[1outcode]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_helpdesk_session, API_URL).get_request(data, False)
+ assert response_data == 403
+
+
+def test_geo_outcode_search_bso(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on BSO AGA
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[bso.code]": "aga",
+ "columnSortDirectionWithOrder[0bso.code]": "asc",
+ "columnSortDirectionWithOrder[1outcode]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 1
+ for bso in response_data["results"]:
+ assert bso["bso"]["code"] == "AGA"
+
+
+def test_geo_outcode_search_bso_name(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on BSO Name
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[bso.name]": "New",
+ "columnSortDirectionWithOrder[0bso.code]": "asc",
+ "columnSortDirectionWithOrder[1outcode]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 1
+ for name in response_data["results"]:
+ assert str(name["bso"]["name"]).startswith("New")
+
+
+def test_geo_outcode_search_bso_outcode(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on Outcode "EX4"
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[outcode]": "EX4",
+ "columnSortDirectionWithOrder[0bso.code]": "asc",
+ "columnSortDirectionWithOrder[1outcode]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 1
+ for outcode in response_data["results"]:
+ assert outcode["outcode"] == "EX4"
diff --git a/tests/api/bso_mapping/test_gp_practice_list_api.py b/tests/api/bso_mapping/test_gp_practice_list_api.py
new file mode 100644
index 00000000..6fc85c83
--- /dev/null
+++ b/tests/api/bso_mapping/test_gp_practice_list_api.py
@@ -0,0 +1,158 @@
+"""
+GP Practice List Tests: These tests cover the GP Practice List, accessed from the BSO Mapping tab.
+"""
+
+import pytest
+from utils.api_utils import ApiUtils
+from playwright.sync_api import BrowserContext
+
+
+pytestmark = [pytest.mark.gp_practice_list, pytest.mark.uiapi]
+
+API_URL = "/bss/gpPractice/search"
+
+
+def test_gp_practice_search_BS1(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on BSO Code BS1
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[bso.code]": "BS1",
+ "columnSortDirectionWithOrder[0bso.code]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 10
+ for bso in response_data["results"]:
+ assert bso["bso"]["code"] == "BS1"
+
+
+def test_gp_practice_search_national_BS1(
+ api_national_user_session: BrowserContext,
+) -> None:
+ """
+ API test logged in as a National user to check search on BSO Code BS1
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[bso.code]": "BS1",
+ "columnSortDirectionWithOrder[0bso.code]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_national_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 10
+ for bso in response_data["results"]:
+ assert bso["bso"]["code"] == "BS1"
+
+
+def test_invalid_user(api_helpdesk_session: BrowserContext) -> None:
+ """
+ API test to check an invaild user (Helpdesk user) doesn't have access to the GP Practice List, so returns a 403 error.
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[bso.code]": "BS1",
+ "columnSortDirectionWithOrder[0bso.code]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_helpdesk_session, API_URL).get_request(data, False)
+ assert response_data == 403
+
+
+def test_gp_practice_search_A12345(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on GP Practice A12345
+ """
+ data = {
+ "draw": "2",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[bso.code]": "BS1",
+ "columnSearchText[code]": "A12345",
+ "columnSearchText[name]": "MEGA",
+ "columnSortDirectionWithOrder[0bso.code]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 2
+ assert len(response_data["results"]) == 1
+ assert response_data["results"][0]["bso"]["code"] == "BS1"
+ assert response_data["results"][0]["name"] == "Mega Practice"
+ assert response_data["results"][0]["code"] == "A12345"
+
+
+def test_gp_practice_search_A82(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check GP Practice search on A82
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "3",
+ "searchText": "",
+ "columnSearchText[code]": "A82",
+ "columnSortDirectionWithOrder[0bso.code]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 3
+ for practice in response_data["results"]:
+ assert str(practice["code"]).startswith("A82")
+
+
+def test_gp_practice_search_included_in_group_yes(
+ api_bso_user_session: BrowserContext,
+) -> None:
+ """
+ API test to check search on Included in Group status is "Yes"
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[bso.code]": "BS1",
+ "columnSearchText[includedInGroup]": "YES",
+ "columnSortDirectionWithOrder[0bso.code]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 10
+ assert response_data["results"][0]["bso"]["code"] == "BS1"
+
+
+def test_gp_practice_search_included_in_group_no(
+ api_bso_user_session: BrowserContext,
+) -> None:
+ """
+ API test to check search on Included in Group status is "No"
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[bso.code]": "BS1",
+ "columnSearchText[includedInGroup]": "NO",
+ "columnSortDirectionWithOrder[0bso.code]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 7
+ assert response_data["results"][0]["bso"]["code"] == "BS1"
diff --git a/tests/api/conftest.py b/tests/api/conftest.py
new file mode 100644
index 00000000..36ee79ba
--- /dev/null
+++ b/tests/api/conftest.py
@@ -0,0 +1,34 @@
+import pytest
+from utils.user_tools import UserTools
+from playwright.sync_api import Playwright, BrowserContext
+
+
+def persist_browser_context(playwright: Playwright, base_url: str) -> BrowserContext:
+ return playwright.chromium.launch_persistent_context("", base_url=base_url)
+
+
+@pytest.fixture(scope="session")
+def api_bso_user_session(
+ user_tools: UserTools, playwright: Playwright, base_url: str
+) -> BrowserContext:
+ context = persist_browser_context(playwright, base_url)
+ user_tools.user_login(context.new_page(), "BSO User - BS1")
+ return context
+
+
+@pytest.fixture(scope="session")
+def api_national_user_session(
+ user_tools: UserTools, playwright: Playwright, base_url: str
+) -> BrowserContext:
+ context = persist_browser_context(playwright, base_url)
+ user_tools.user_login(context.new_page(), "National User")
+ return context
+
+
+@pytest.fixture(scope="session")
+def api_helpdesk_session(
+ user_tools: UserTools, playwright: Playwright, base_url: str
+) -> BrowserContext:
+ context = persist_browser_context(playwright, base_url)
+ user_tools.user_login(context.new_page(), "Helpdesk User")
+ return context
diff --git a/tests/api/failsafe_reports/test_search_batches_api.py b/tests/api/failsafe_reports/test_search_batches_api.py
new file mode 100644
index 00000000..3a9ca949
--- /dev/null
+++ b/tests/api/failsafe_reports/test_search_batches_api.py
@@ -0,0 +1,80 @@
+"""
+Search Batches Tests: These tests cover the Search Batches, accessed from the Failsafe Reports tab.
+"""
+
+import pytest
+from utils.api_utils import ApiUtils
+from playwright.sync_api import BrowserContext
+
+
+pytestmark = [pytest.mark.search_batches, pytest.mark.uiapi]
+
+API_URL = "/bss/selectedBatch/search"
+
+
+def test_invalid_national_user(api_national_user_session: BrowserContext) -> None:
+ """
+ API test to check an invaild user (National user) doesn't have access to the Search Batches, so returns a 403 error.
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSortDirectionWithOrder[0selectDateTime]": "desc",
+ "searchSpecification[gpPracticeCode]": "",
+ "searchSpecification[gpPracticeGroupName]": "",
+ "searchSpecification[outcode]": "",
+ "searchSpecification[outcodeGroupName]": "",
+ "searchSpecification[startDate]": "25-Jul-2016",
+ "searchSpecification[endDate]": "27-May-2025",
+ }
+ response_data = ApiUtils(api_national_user_session, API_URL).get_request(
+ data, False
+ )
+ assert response_data == 403
+
+
+def test_invalid_helpdesk_user(api_helpdesk_session: BrowserContext) -> None:
+ """
+ API test to check an invaild user (Helpdesk user) doesn't have access to the Search Batches, so returns a 403 error.
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSortDirectionWithOrder[0selectDateTime]": "desc",
+ "searchSpecification[gpPracticeCode]": "",
+ "searchSpecification[gpPracticeGroupName]": "",
+ "searchSpecification[outcode]": "",
+ "searchSpecification[outcodeGroupName]": "",
+ "searchSpecification[startDate]": "25-Jul-2016",
+ "searchSpecification[endDate]": "27-May-2025",
+ }
+ response_data = ApiUtils(api_helpdesk_session, API_URL).get_request(data, False)
+ assert response_data == 403
+
+
+def test_search_batches_all(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on All entries on Search Batches
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSortDirectionWithOrder[0selectDateTime]": "desc",
+ "searchSpecification[gpPracticeCode]": "",
+ "searchSpecification[gpPracticeGroupName]": "",
+ "searchSpecification[outcode]": "",
+ "searchSpecification[outcodeGroupName]": "",
+ "searchSpecification[startDate]": "25-Jul-2016",
+ "searchSpecification[endDate]": "27-May-2025",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 5
+ for name in response_data["results"]:
+ assert name["bsoCode"] == "BS1"
diff --git a/tests/api/monitoring_reports/test_ceased_unceased_api.py b/tests/api/monitoring_reports/test_ceased_unceased_api.py
new file mode 100644
index 00000000..147fd869
--- /dev/null
+++ b/tests/api/monitoring_reports/test_ceased_unceased_api.py
@@ -0,0 +1,92 @@
+"""
+Ceased/Unceased Subjects report Tests: These tests cover the Ceased/Unceased Subjects report, accessed from the Monitoring Reports tab.
+"""
+
+import pytest
+from utils.api_utils import ApiUtils
+from playwright.sync_api import BrowserContext
+
+
+pytestmark = [pytest.mark.ceased_unceased, pytest.mark.uiapi]
+
+API_URL = "/bss/report/ceasing/search"
+
+
+def test_ceased_unceased_default(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on all entries on the Ceased/Unceased Subjects report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[actioned]": "NOT_ACTIONED",
+ "searchSpecification[searchFor]": "CEASED",
+ "searchSpecification[startDate]": "",
+ "searchSpecification[endDate]": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 0
+ for code in response_data["results"]:
+ assert code["bso"]["code"] == "BS1"
+
+
+def test_invalid_national_user(api_national_user_session: BrowserContext) -> None:
+ """
+ API test to check an invaild user (National user) doesn't have access to the Ceased/Unceased Subjects report, so returns a 403 error.
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[actioned]": "NOT_ACTIONED",
+ "searchSpecification[searchFor]": "CEASED",
+ "searchSpecification[startDate]": "",
+ "searchSpecification[endDate]": "",
+ }
+ response_data = ApiUtils(api_national_user_session, API_URL).get_request(
+ data, False
+ )
+ assert response_data == 403
+
+
+def test_invalid_helpdesk_user(api_helpdesk_session: BrowserContext) -> None:
+ """
+ API test to check an invaild user (Helpdesk user) doesn't have access to the Ceased/Unceased Subjects report, so returns a 403 error.
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[actioned]": "NOT_ACTIONED",
+ "searchSpecification[searchFor]": "CEASED",
+ "searchSpecification[startDate]": "",
+ "searchSpecification[endDate]": "",
+ }
+ response_data = ApiUtils(api_helpdesk_session, API_URL).get_request(data, False)
+ assert response_data == 403
+
+
+def test_ceased_unceased_both(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on Both selected on the Ceased/Unceased Subjects report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[actioned]": "NOT_ACTIONED",
+ "searchSpecification[searchFor]": "BOTH",
+ "searchSpecification[startDate]": "",
+ "searchSpecification[endDate]": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 1
+ for code in response_data["results"]:
+ assert code["subject"]["bso"]["code"] == "BS1"
diff --git a/tests/api/monitoring_reports/test_ceasing_instances_api.py b/tests/api/monitoring_reports/test_ceasing_instances_api.py
new file mode 100644
index 00000000..1117ab6d
--- /dev/null
+++ b/tests/api/monitoring_reports/test_ceasing_instances_api.py
@@ -0,0 +1,188 @@
+"""
+Ceasing Instances with No Documentation report Tests: These tests cover the Ceasing Instances with No Documentation report, accessed from the Monitoring Reports tab.
+"""
+
+import pytest
+from utils.api_utils import ApiUtils
+from playwright.sync_api import BrowserContext
+
+
+pytestmark = [pytest.mark.ceasing_instances, pytest.mark.uiapi]
+
+API_URL = "/bss/report/outstandingCeasingDocumentation/search"
+
+
+def test_ceasing_instances_current(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on current entries on the Ceasing Instances with No Documentation report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[subject.ageInYears]": "73",
+ "columnSearchText[dateTimeOfUnceasing]": "open",
+ "columnSortDirectionWithOrder[0dateTimeOfCeasing]": "asc",
+ "columnSortDirectionWithOrder[1subject.nhsNumber]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 3
+ for code in response_data["results"]:
+ assert code["subject"]["bso"]["code"] == "BS1"
+
+
+def test_ceasing_instances_all(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on all entries on the Ceasing Instances with No Documentation report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[subject.ageInYears]": "73",
+ "columnSortDirectionWithOrder[0dateTimeOfCeasing]": "asc",
+ "columnSortDirectionWithOrder[1subject.nhsNumber]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 5
+ for code in response_data["results"]:
+ assert code["subject"]["bso"]["code"] == "BS1"
+
+
+def test_ceasing_instances_historic(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on all entries on the Ceasing Instances with No Documentation report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[subject.ageInYears]": "73",
+ "columnSearchText[dateTimeOfUnceasing]": "closed",
+ "columnSortDirectionWithOrder[0dateTimeOfCeasing]": "asc",
+ "columnSortDirectionWithOrder[1subject.nhsNumber]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 2
+ for code in response_data["results"]:
+ assert code["subject"]["bso"]["code"] == "BS1"
+
+
+def test_invalid_national_user(api_national_user_session: BrowserContext) -> None:
+ """
+ API test to check an invaild user (National user) doesn't have access to the Ceasing Instances with No Documentation report, so returns a 403 error.
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[subject.ageInYears]": "73",
+ "columnSearchText[dateTimeOfUnceasing]": "open",
+ "columnSortDirectionWithOrder[0dateTimeOfCeasing]": "asc",
+ "columnSortDirectionWithOrder[1subject.nhsNumber]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_national_user_session, API_URL).get_request(
+ data, False
+ )
+ assert response_data == 403
+
+
+def test_invalid_helpdesk_user(api_helpdesk_session: BrowserContext) -> None:
+ """
+ API test to check an invaild user (Helpdesk user) doesn't have access to the Ceasing Instances with No Documentation report, so returns a 403 error.
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[subject.ageInYears]": "73",
+ "columnSearchText[dateTimeOfUnceasing]": "open",
+ "columnSortDirectionWithOrder[0dateTimeOfCeasing]": "asc",
+ "columnSortDirectionWithOrder[1subject.nhsNumber]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_helpdesk_session, API_URL).get_request(data, False)
+ assert response_data == 403
+
+
+def test_ceasing_instances_nhs_number(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on NHS Number on the Ceasing Instances with No Documentation report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[subject.nhsNumber]": "930 000 0015",
+ "columnSearchText[subject.ageInYears]": "73",
+ "columnSearchText[dateTimeOfUnceasing]": "open",
+ "columnSortDirectionWithOrder[0dateTimeOfCeasing]": "asc",
+ "columnSortDirectionWithOrder[1subject.nhsNumber]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 1
+ for nhs in response_data["results"]:
+ assert len(nhs["subject"]["nhsNumber"]) == 10
+
+
+def test_ceasing_instances_family_name(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on Family Name on the Ceasing Instances with No Documentation report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[subject.ageInYears]": "73",
+ "columnSearchText[subject.familyName]": "PERFORMANCE",
+ "columnSearchText[dateTimeOfUnceasing]": "open",
+ "columnSortDirectionWithOrder[0dateTimeOfCeasing]": "asc",
+ "columnSortDirectionWithOrder[1subject.nhsNumber]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 2
+ for name in response_data["results"]:
+ assert str(name["subject"]["familyName"]).startswith("PERFORMANCE")
+
+
+def test_ceasing_instances_first_given_name(
+ api_bso_user_session: BrowserContext,
+) -> None:
+ """
+ API test to check search on First Given Name "India" on the Ceasing Instances with No Documentation report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[subject.ageInYears]": "73",
+ "columnSearchText[subject.firstNames]": "India",
+ "columnSearchText[dateTimeOfUnceasing]": "open",
+ "columnSortDirectionWithOrder[0dateTimeOfCeasing]": "asc",
+ "columnSortDirectionWithOrder[1subject.nhsNumber]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 2
+ for name in response_data["results"]:
+ assert str(name["subject"]["firstNames"]).startswith("India")
diff --git a/tests/api/monitoring_reports/test_sspi_action_api.py b/tests/api/monitoring_reports/test_sspi_action_api.py
new file mode 100644
index 00000000..961cfc89
--- /dev/null
+++ b/tests/api/monitoring_reports/test_sspi_action_api.py
@@ -0,0 +1,213 @@
+"""
+SSPI Update Warnings Action report Tests: These tests cover the SSPI Update Warnings Action report, accessed from the Monitoring Reports tab.
+"""
+
+import pytest
+from utils.api_utils import ApiUtils
+from playwright.sync_api import BrowserContext
+
+
+pytestmark = [pytest.mark.sspi_action, pytest.mark.uiapi]
+
+API_URL = "/bss/report/sspiUpdateWarnings/action/search"
+
+
+def test_sspi_action_default(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on all entries on the SSPI Update Warnings Action report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[actioned]": "NOT_ACTIONED",
+ "columnSortDirectionWithOrder[0receivedDateTime]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 3
+ for name in response_data["results"]:
+ assert name["bsoCode"] == "BS1"
+
+
+def test_invalid_national_user(api_national_user_session: BrowserContext) -> None:
+ """
+ API test to check an invaild user (National user) doesn't have access to the SSPI Update Warnings Action report, so returns a 403 error.
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[actioned]": "NOT_ACTIONED",
+ "columnSortDirectionWithOrder[0receivedDateTime]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_national_user_session, API_URL).get_request(
+ data, False
+ )
+ assert response_data == 403
+
+
+def test_invalid_helpdesk_user(api_helpdesk_session: BrowserContext) -> None:
+ """
+ API test to check an invaild user (Helpdesk user) doesn't have access to the SSPI Update Warnings Action report, so returns a 403 error.
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[actioned]": "NOT_ACTIONED",
+ "columnSortDirectionWithOrder[0receivedDateTime]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_helpdesk_session, API_URL).get_request(data, False)
+ assert response_data == 403
+
+
+def test_sspi_action_all(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on All entries on the SSPI Update Warnings Action report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSortDirectionWithOrder[0receivedDateTime]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 7
+ for name in response_data["results"]:
+ assert name["bsoCode"] == "BS1"
+
+
+def test_sspi_action_nhs_number(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on NHS Number on the SSPI Update Warnings Action report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[actioned]": "NOT_ACTIONED",
+ "columnSearchText[nhsNumber]": "930 000 0002",
+ "columnSortDirectionWithOrder[0receivedDateTime]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 1
+ for nhs in response_data["results"]:
+ assert len(nhs["nhsNumber"]) == 10
+
+
+def test_sspi_action_first_given_name(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on First Given Name "Coleen" entries on the SSPI Update Warnings Action report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[actioned]": "NOT_ACTIONED",
+ "columnSearchText[firstNames]": "coleen",
+ "columnSortDirectionWithOrder[0receivedDateTime]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 1
+ for name in response_data["results"]:
+ assert str(name["firstNames"]).startswith("Coleen")
+
+
+def test_sspi_action_family_name(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on Family Name "Smith" entries on the SSPI Update Warnings Action report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[actioned]": "NOT_ACTIONED",
+ "columnSearchText[familyName]": "smith",
+ "columnSortDirectionWithOrder[0receivedDateTime]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 1
+ for name in response_data["results"]:
+ assert str(name["familyName"]).startswith("SMITH")
+
+
+def test_sspi_action_age_today(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on Age Today "Under 80" entries on the SSPI Update Warnings Action report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[actioned]": "NOT_ACTIONED",
+ "columnSearchText[ageInYears]": "under80",
+ "columnSortDirectionWithOrder[0receivedDateTime]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 3
+ for age in response_data["results"]:
+ assert age["bsoCode"] == "BS1"
+
+
+def test_sspi_action_event(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on Event entries on the SSPI Update Warnings Action report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[actioned]": "NOT_ACTIONED",
+ "columnSearchText[event]": "DATE_OF_DEATH_SET",
+ "columnSortDirectionWithOrder[0nhsNumber]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 1
+ for event in response_data["results"]:
+ assert event["event"]["description"] == "Date of death set"
+
+
+def test_sspi_action_warning(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on Warning entries on the SSPI Update Warnings Action report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[actioned]": "NOT_ACTIONED",
+ "columnSearchText[reason]": "SUBJECT_IS_HR",
+ "columnSortDirectionWithOrder[0receivedDateTime]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 1
+ for warning in response_data["results"]:
+ assert warning["reason"]["description"] == "Subject has HR status"
diff --git a/tests/api/monitoring_reports/test_sspi_information_api.py b/tests/api/monitoring_reports/test_sspi_information_api.py
new file mode 100644
index 00000000..b058c0bf
--- /dev/null
+++ b/tests/api/monitoring_reports/test_sspi_information_api.py
@@ -0,0 +1,215 @@
+"""
+SSPI Update Warnings Information report Tests: These tests cover the SSPI Update Warnings Information report, accessed from the Monitoring Reports tab.
+"""
+
+import pytest
+from utils.api_utils import ApiUtils
+from playwright.sync_api import BrowserContext
+
+
+pytestmark = [pytest.mark.sspi_information, pytest.mark.uiapi]
+
+API_URL = "/bss/report/sspiUpdateWarnings/information/search"
+
+
+def test_sspi_information_default(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on all entries on the SSPI Update Warnings Information report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[actioned]": "NOT_ACTIONED",
+ "columnSortDirectionWithOrder[0receivedDateTime]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 2
+ for name in response_data["results"]:
+ assert name["bsoCode"] == "BS1"
+
+
+def test_invalid_national_user(api_national_user_session: BrowserContext) -> None:
+ """
+ API test to check an invaild user (National user) doesn't have access to the SSPI Information Warnings Action report, so returns a 403 error.
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[actioned]": "NOT_ACTIONED",
+ "columnSortDirectionWithOrder[0receivedDateTime]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_national_user_session, API_URL).get_request(
+ data, False
+ )
+ assert response_data == 403
+
+
+def test_invalid_helpdesk_user(api_helpdesk_session: BrowserContext) -> None:
+ """
+ API test to check an invaild user (Helpdesk user) doesn't have access to the SSPI Update Warnings Information report, so returns a 403 error.
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[actioned]": "NOT_ACTIONED",
+ "columnSortDirectionWithOrder[0receivedDateTime]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_helpdesk_session, API_URL).get_request(data, False)
+ assert response_data == 403
+
+
+def test_sspi_information_all(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on All entries on the SSPI Update Warnings Information report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSortDirectionWithOrder[0receivedDateTime]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 4
+ for name in response_data["results"]:
+ assert name["bsoCode"] == "BS1"
+
+
+def test_sspi_information_nhs_number(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on NHS Number on the SSPI Update Warnings Information report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[actioned]": "NOT_ACTIONED",
+ "columnSearchText[nhsNumber]": "930 000 0020",
+ "columnSortDirectionWithOrder[0receivedDateTime]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 1
+ for nhs in response_data["results"]:
+ assert len(nhs["nhsNumber"]) == 10
+
+
+def test_sspi_information_first_given_name(
+ api_bso_user_session: BrowserContext,
+) -> None:
+ """
+ API test to check search on First Given Name "Priscilla" entries on the SSPI Update Warnings Information report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[actioned]": "NOT_ACTIONED",
+ "columnSearchText[firstNames]": "priscilla",
+ "columnSortDirectionWithOrder[0receivedDateTime]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 1
+ for name in response_data["results"]:
+ assert str(name["firstNames"]).startswith("Priscilla")
+
+
+def test_sspi_information_family_name(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on Family Name "Jones" entries on the SSPI Update Warnings Information report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[actioned]": "NOT_ACTIONED",
+ "columnSearchText[familyName]": "jones",
+ "columnSortDirectionWithOrder[0receivedDateTime]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 1
+ for name in response_data["results"]:
+ assert str(name["familyName"]).startswith("JONES")
+
+
+def test_sspi_information_age_today(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on Age Today "Under 80" entries on the SSPI Update Warnings Information report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[actioned]": "NOT_ACTIONED",
+ "columnSearchText[ageInYears]": "under80",
+ "columnSortDirectionWithOrder[0receivedDateTime]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 2
+ for age in response_data["results"]:
+ assert age["bsoCode"] == "BS1"
+
+
+def test_sspi_information_event(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on Event entries on the SSPI Update Warnings Information report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[actioned]": "NOT_ACTIONED",
+ "columnSearchText[event]": "REMOVAL",
+ "columnSortDirectionWithOrder[0nhsNumber]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 1
+ for event in response_data["results"]:
+ assert event["event"]["description"] == "Removal"
+
+
+def test_sspi_information_warning(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on Warning entries on the SSPI Update Warnings Information report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[actioned]": "NOT_ACTIONED",
+ "columnSearchText[reason]": "NO_OPEN_EPISODES",
+ "columnSortDirectionWithOrder[0receivedDateTime]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 1
+ for warning in response_data["results"]:
+ assert warning["reason"]["description"] == "No open episodes"
diff --git a/tests/api/monitoring_reports/test_subject_demographic_api.py b/tests/api/monitoring_reports/test_subject_demographic_api.py
new file mode 100644
index 00000000..29a57adb
--- /dev/null
+++ b/tests/api/monitoring_reports/test_subject_demographic_api.py
@@ -0,0 +1,127 @@
+"""
+Pending Subject Demographic Changes List report Tests: These tests cover the Pending Subject Demographic Changes List report, accessed from the Monitoring Reports tab.
+"""
+
+import pytest
+from utils.api_utils import ApiUtils
+from playwright.sync_api import BrowserContext
+
+
+pytestmark = [pytest.mark.subject_demographic, pytest.mark.uiapi]
+
+API_URL = "/bss/report/pendingDemographicChanges/search"
+
+
+def test_subject_demographic_default(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on all entries on the Pending Subject Demographic Changes List report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSortDirectionWithOrder[0changeReceivedDateTime]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 4
+ for nhs in response_data["results"]:
+ assert len(nhs["subject"]["nhsNumber"]) == 10
+
+
+def test_invalid_national_user(api_national_user_session: BrowserContext) -> None:
+ """
+ API test to check an invaild user (National user) doesn't have access to the Pending Subject Demographic Changes List report, so returns a 403 error.
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSortDirectionWithOrder[0changeReceivedDateTime]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_national_user_session, API_URL).get_request(
+ data, False
+ )
+ assert response_data == 403
+
+
+def test_invalid_helpdesk_user(api_helpdesk_session: BrowserContext) -> None:
+ """
+ API test to check an invaild user (Helpdesk user) doesn't have access to thePending Subject Demographic Changes List report, so returns a 403 error.
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSortDirectionWithOrder[0dateOfBirth]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_helpdesk_session, API_URL).get_request(data, False)
+ assert response_data == 403
+
+
+def test_subject_demographic_nhs_number(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on NHS Number on the Pending Subject Demographic Changes List report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[subject.nhsNumber]": "930 000 0002",
+ "columnSortDirectionWithOrder[0changeReceivedDateTime]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 1
+ for nhs in response_data["results"]:
+ assert len(nhs["subject"]["nhsNumber"]) == 10
+
+
+def test_subject_demographic_family_name(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on Family Name on the Pending Subject Demographic Changes List report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[subject.familyName]": "performance",
+ "columnSortDirectionWithOrder[0changeReceivedDateTime]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 2
+ for name in response_data["results"]:
+ assert str(name["subject"]["familyName"]).startswith("PERFORMANCE")
+
+
+def test_subjects_never_invited_first_given_name(
+ api_bso_user_session: BrowserContext,
+) -> None:
+ """
+ API test to check search on First Given Name "Audrey" on the Pending Subject Demographic Changes List report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[subject.firstNames]": "Audrey",
+ "columnSortDirectionWithOrder[0changeReceivedDateTime]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 1
+ for name in response_data["results"]:
+ assert str(name["subject"]["firstNames"]).startswith("Audrey")
diff --git a/tests/api/monitoring_reports/test_subjects_never_invited_api.py b/tests/api/monitoring_reports/test_subjects_never_invited_api.py
new file mode 100644
index 00000000..ec26cb1b
--- /dev/null
+++ b/tests/api/monitoring_reports/test_subjects_never_invited_api.py
@@ -0,0 +1,175 @@
+"""
+Subjects Never Invited for Screening report Tests: These tests cover the Subjects Never Invited for Screening report, accessed from the Monitoring Reports tab.
+"""
+
+import pytest
+from utils.api_utils import ApiUtils
+from playwright.sync_api import BrowserContext
+
+
+pytestmark = [pytest.mark.subjects_never_invited, pytest.mark.uiapi]
+
+API_URL = "/bss/report/subjectsNeverInvited/search"
+
+
+def test_subjects_never_invited_default(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on all entries on the Subjects Never Invited for Screening report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSortDirectionWithOrder[0dateOfBirth]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 5
+ for code in response_data["results"]:
+ assert code["bso"]["code"] == "BS1"
+
+
+def test_invalid_national_user(api_national_user_session: BrowserContext) -> None:
+ """
+ API test to check an invaild user (National user) doesn't have access to the Subjects Never Invited for Screening report, so returns a 403 error.
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSortDirectionWithOrder[0dateOfBirth]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_national_user_session, API_URL).get_request(
+ data, False
+ )
+ assert response_data == 403
+
+
+def test_invalid_helpdesk_user(api_helpdesk_session: BrowserContext) -> None:
+ """
+ API test to check an invaild user (Helpdesk user) doesn't have access to the Subjects Never Invited for Screening report, so returns a 403 error.
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSortDirectionWithOrder[0dateOfBirth]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_helpdesk_session, API_URL).get_request(data, False)
+ assert response_data == 403
+
+
+def test_subjects_never_invited_nhs_number(
+ api_bso_user_session: BrowserContext,
+) -> None:
+ """
+ API test to check search on NHS Number on the Subjects Never Invited for Screening report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[nhsNumber]": "930 000 0022",
+ "columnSortDirectionWithOrder[0dateOfBirth]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 1
+ for nhs in response_data["results"]:
+ assert len(nhs["nhsNumber"]) == 10
+
+
+def test_subjects_never_invited_family_name(
+ api_bso_user_session: BrowserContext,
+) -> None:
+ """
+ API test to check search on Family Name on the Subjects Never Invited for Screening report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[familyName]": "AFAKENAME",
+ "columnSortDirectionWithOrder[0dateOfBirth]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 1
+ for name in response_data["results"]:
+ assert str(name["familyName"]).startswith("AFAKENAME")
+
+
+def test_subjects_never_invited_first_given_name(
+ api_bso_user_session: BrowserContext,
+) -> None:
+ """
+ API test to check search on First Given Name "Judy" on the Subjects Never Invited for Screening report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[firstNames]": "Judy",
+ "columnSortDirectionWithOrder[0dateOfBirth]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 1
+ for name in response_data["results"]:
+ assert str(name["firstNames"]).startswith("Judy")
+
+
+def test_subjects_never_invited_first_given_names(
+ api_bso_user_session: BrowserContext,
+) -> None:
+ """
+ API test to check search on First Given Name "Jen" on the Subjects Never Invited for Screening report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[firstNames]": "Jen",
+ "columnSortDirectionWithOrder[0dateOfBirth]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 2
+ for name in response_data["results"]:
+ assert str(name["firstNames"]).startswith("Jen")
+
+
+def test_subjects_never_invited_gp_practice_code(
+ api_bso_user_session: BrowserContext,
+) -> None:
+ """
+ API test to check search on GP Practice Code "GP4" on the Subjects Never Invited for Screening report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[gpPracticeSummary.code]": "gp4",
+ "columnSortDirectionWithOrder[0dateOfBirth]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 4
+ for name in response_data["results"]:
+ assert str(name["gpPracticeSummary"]["code"]).startswith("GP4")
diff --git a/tests/api/monitoring_reports/test_subjects_overdue_invitation_api.py b/tests/api/monitoring_reports/test_subjects_overdue_invitation_api.py
new file mode 100644
index 00000000..cafe6cb4
--- /dev/null
+++ b/tests/api/monitoring_reports/test_subjects_overdue_invitation_api.py
@@ -0,0 +1,171 @@
+"""
+Subjects Overdue Invitation report Tests: These tests cover the Subjects Overdue Invitation report, accessed from the Monitoring Reports tab.
+"""
+
+import pytest
+from utils.api_utils import ApiUtils
+from playwright.sync_api import BrowserContext
+
+
+pytestmark = [pytest.mark.subjects_overdue, pytest.mark.uiapi]
+
+API_URL = "/bss/report/subjectsOverdueInvitation/search"
+
+
+def test_subjects_never_invited_default(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on all entries on the Subjects Overdue Invitation report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSortDirectionWithOrder[0latestInvitationDate]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 3
+ for code in response_data["results"]:
+ assert code["bso"]["code"] == "BS1"
+
+
+def test_invalid_national_user(api_national_user_session: BrowserContext) -> None:
+ """
+ API test to check an invaild user (National user) doesn't have access to the Subjects Overdue Invitation report, so returns a 403 error.
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSortDirectionWithOrder[0latestInvitationDate]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_national_user_session, API_URL).get_request(
+ data, False
+ )
+ assert response_data == 403
+
+
+def test_invalid_helpdesk_user(api_helpdesk_session: BrowserContext) -> None:
+ """
+ API test to check an invaild user (Helpdesk user) doesn't have access to the Subjects Overdue Invitation report, so returns a 403 error.
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSortDirectionWithOrder[0latestInvitationDate]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_helpdesk_session, API_URL).get_request(data, False)
+ assert response_data == 403
+
+
+def test_subjects_overdue_nhs_number(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on NHS Number on the Subjects Overdue Invitation report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[nhsNumber]": "930 000 0025",
+ "columnSortDirectionWithOrder[0latestInvitationDate]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 1
+ for nhs in response_data["results"]:
+ assert len(nhs["nhsNumber"]) == 10
+
+
+def test_subjects_overdue_family_name(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on Family Name on the Subjects Never Invited for Screening report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[familyName]": "PERFORMANCE",
+ "columnSortDirectionWithOrder[0latestInvitationDate]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 2
+ for name in response_data["results"]:
+ assert str(name["familyName"]).startswith("PERFORMANCE")
+
+
+def test_subjects_overdue_first_given_name(
+ api_bso_user_session: BrowserContext,
+) -> None:
+ """
+ API test to check search on First Given Name "Janet" on the Subjects Never Invited for Screening report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[firstNames]": "Janet",
+ "columnSortDirectionWithOrder[0latestInvitationDate]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 1
+ for name in response_data["results"]:
+ assert str(name["firstNames"]).startswith("Janet")
+
+
+def test_subjects_overdue_gp_practice_code(
+ api_bso_user_session: BrowserContext,
+) -> None:
+ """
+ API test to check search on GP Practice Code "GP3" on the Subjects Overdue Invitation report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[gpPracticeSummary.code]": "gp3",
+ "columnSortDirectionWithOrder[0latestInvitationDate]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 2
+ for name in response_data["results"]:
+ assert str(name["gpPracticeSummary"]["code"]).startswith("GP3")
+
+
+def test_subjects_overdue_months_since_invite(
+ api_bso_user_session: BrowserContext,
+) -> None:
+ """
+ API test to check search on Months Since Invitation "40 or more" on the Subjects Overdue Invitation report
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[monthsSinceInvitation]": "40",
+ "columnSortDirectionWithOrder[0latestInvitationDate]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 3
+ for code in response_data["results"]:
+ assert code["bso"]["code"] == "BS1"
diff --git a/tests/api/outcome_list/test_outcome_list_api.py b/tests/api/outcome_list/test_outcome_list_api.py
new file mode 100644
index 00000000..52e19d3c
--- /dev/null
+++ b/tests/api/outcome_list/test_outcome_list_api.py
@@ -0,0 +1,86 @@
+"""
+Outcome List Tests: These tests cover the Outcome List, accessed from the Outcome List tab.
+"""
+
+import pytest
+from utils.api_utils import ApiUtils
+from playwright.sync_api import BrowserContext
+
+
+pytestmark = [pytest.mark.outcome_list, pytest.mark.uiapi]
+
+API_URL = "/bss/outcome/search"
+
+
+def test_outcome_list_search_all(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on all Outcome List
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[typeDescription]": "NBR",
+ "columnSortDirectionWithOrder[0transferDateTime]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 1
+ for description in response_data["results"]:
+ assert str(description["typeDescription"]).startswith("NBR")
+
+
+def test_invalid_national_user(api_national_user_session: BrowserContext) -> None:
+ """
+ API test to check an invaild user (National user) doesn't have access to the Outcome List, so returns a 403 error.
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSortDirectionWithOrder[0transferDateTime]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_national_user_session, API_URL).get_request(
+ data, False
+ )
+ assert response_data == 403
+
+
+def test_invalid_helpdesk_user(api_helpdesk_session: BrowserContext) -> None:
+ """
+ API test to check an invaild user (Helpdesk user) doesn't have access to the Outcome List, so returns a 403 error.
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSortDirectionWithOrder[0transferDateTime]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_helpdesk_session, API_URL).get_request(data, False)
+ assert response_data == 403
+
+
+def test_outcome_list_search_data_type(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on Batch 121 on the Outcome List
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[typeDescription]": "BATCH",
+ "columnSortDirectionWithOrder[0transferDateTime]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 2
+ for description in response_data["results"]:
+ assert str(description["typeDescription"]).startswith("Batch 121")
diff --git a/tests/api/parameters/test_gp_practice_group_list_api.py b/tests/api/parameters/test_gp_practice_group_list_api.py
new file mode 100644
index 00000000..d367cf06
--- /dev/null
+++ b/tests/api/parameters/test_gp_practice_group_list_api.py
@@ -0,0 +1,112 @@
+"""
+GP Practice Group List Tests: These tests cover the GP Practice Group List, accessed from the Parameters tab.
+"""
+
+import pytest
+from utils.api_utils import ApiUtils
+from playwright.sync_api import BrowserContext
+
+
+pytestmark = [pytest.mark.gp_practice_group_list, pytest.mark.uiapi]
+
+API_URL = "/bss/gpPracticeGroup/search"
+
+
+def test_gp_practice_group_list_search_all(
+ api_bso_user_session: BrowserContext,
+) -> None:
+ """
+ API test to check search on all GP Practice Group List
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[active]": "true",
+ "columnSortDirectionWithOrder[0active]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 9
+ for name in response_data["results"]:
+ assert name["bsoCode"] == "BS1"
+
+
+def test_invalid_national_user(api_national_user_session: BrowserContext) -> None:
+ """
+ API test to check an invaild user (National user) doesn't have access to the GP Practice Group List, so returns a 403 error.
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[active]": "true",
+ "columnSortDirectionWithOrder[0active]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_national_user_session, API_URL).get_request(
+ data, False
+ )
+ assert response_data == 403
+
+
+def test_invalid_helpdesk_user(api_helpdesk_session: BrowserContext) -> None:
+ """
+ API test to check an invaild user (Helpdesk user) doesn't have access to the GP Practice Group List, so returns a 403 error.
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[active]": "true",
+ "columnSortDirectionWithOrder[0active]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_helpdesk_session, API_URL).get_request(data, False)
+ assert response_data == 403
+
+
+def test_gp_practice_group_list_search_BS1(
+ api_bso_user_session: BrowserContext,
+) -> None:
+ """
+ API test to check search on Group "BS1" on the GP Practice Group List
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[groupName]": "bs1",
+ "columnSearchText[active]": "true",
+ "columnSortDirectionWithOrder[0active]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 7
+ for name in response_data["results"]:
+ assert str(name["groupName"]).startswith("BS1")
+
+
+def test_gp_practice_group_status_all(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on Status "All"
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSortDirectionWithOrder[0active]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 9
+ for name in response_data["results"]:
+ assert name["bsoCode"] == "BS1"
diff --git a/tests/api/parameters/test_outcode_group_list_api.py b/tests/api/parameters/test_outcode_group_list_api.py
new file mode 100644
index 00000000..c00f6a35
--- /dev/null
+++ b/tests/api/parameters/test_outcode_group_list_api.py
@@ -0,0 +1,108 @@
+"""
+Outcode Group List Tests: These tests cover the Outcode Group List, accessed from the Parameters tab.
+"""
+
+import pytest
+from utils.api_utils import ApiUtils
+from playwright.sync_api import BrowserContext
+
+
+pytestmark = [pytest.mark.outcode_group_list, pytest.mark.uiapi]
+
+API_URL = "/bss/outcodeGroup/search"
+
+
+def test_outcode_group_list_search_all(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on all Outcode Group List
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[active]": "true",
+ "columnSortDirectionWithOrder[0active]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 9
+ for name in response_data["results"]:
+ assert name["bsoCode"] == "BS1"
+
+
+def test_invalid_national_user(api_national_user_session: BrowserContext) -> None:
+ """
+ API test to check an invaild user (National user) doesn't have access to the Outcode Group List, so returns a 403 error.
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[active]": "true",
+ "columnSortDirectionWithOrder[0active]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_national_user_session, API_URL).get_request(
+ data, False
+ )
+ assert response_data == 403
+
+
+def test_invalid_helpdesk_user(api_helpdesk_session: BrowserContext) -> None:
+ """
+ API test to check an invaild user (Helpdesk user) doesn't have access to the Outcode Group List, so returns a 403 error.
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[active]": "true",
+ "columnSortDirectionWithOrder[0active]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_helpdesk_session, API_URL).get_request(data, False)
+ assert response_data == 403
+
+
+def test_outcode_group_list_search_zone(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on "Zone" in the Outcode Group List
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[groupName]": "zone",
+ "columnSortDirectionWithOrder[0active]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 3
+ for name in response_data["results"]:
+ assert str(name["groupName"]).startswith("ZONE")
+
+
+def test_outcode_group_status_all(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check search on Status "All"
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "10",
+ "searchText": "",
+ "columnSearchText[active]": "true",
+ "columnSortDirectionWithOrder[0active]": "desc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 9
+ for name in response_data["results"]:
+ assert name["bsoCode"] == "BS1"
diff --git a/tests/api/subject_search/test_subject_seach_api.py b/tests/api/subject_search/test_subject_seach_api.py
new file mode 100644
index 00000000..241c151a
--- /dev/null
+++ b/tests/api/subject_search/test_subject_seach_api.py
@@ -0,0 +1,35 @@
+"""
+Subject Search Tests: These tests cover the Subject Search, accessed from the Subject Search tab.
+"""
+
+import pytest
+from utils.api_utils import ApiUtils
+from playwright.sync_api import BrowserContext
+
+
+pytestmark = [pytest.mark.subject_search, pytest.mark.uiapi]
+
+API_URL = "/bss/subject/search"
+
+
+@pytest.mark.only
+def test_subject_search_all(api_bso_user_session: BrowserContext) -> None:
+ """
+ API test to check Subject Search
+ """
+ data = {
+ "draw": "1",
+ "start": "0",
+ "length": "50",
+ "searchText": "",
+ "columnSearchText[familyName]": "performance",
+ "columnSearchText[bso.code]": "BS1",
+ "columnSortDirectionWithOrder[0familyName]": "asc",
+ "columnSortDirectionWithOrder[1firstNames]": "asc",
+ "searchSpecification": "",
+ }
+ response_data = ApiUtils(api_bso_user_session, API_URL).get_request(data)
+ assert response_data["draw"] == 1
+ assert len(response_data["results"]) == 16
+ for bso in response_data["results"]:
+ assert len(bso["code"]) == 3
diff --git a/tests/test_example.py b/tests/test_example.py
deleted file mode 100644
index a6b86845..00000000
--- a/tests/test_example.py
+++ /dev/null
@@ -1,78 +0,0 @@
-"""
-This file provides a very basic test to confirm how to get started with test execution, and also
-a way to prove that the blueprint has been copied and built correctly for teams getting stated.
-
-You can invoke this test once the blueprint has been installed by using the following command
-to see the test executing and producing a trace report:
- pytest --tracing on --headed
-"""
-
-import pytest
-from playwright.sync_api import Page, expect
-
-
-@pytest.fixture(autouse=True)
-def initial_navigation(page: Page) -> None:
- '''
- This fixture (or hook) is used for each test in this file to navigate to this repository before
- each test, to reduce the need for repeated code within the tests directly.
-
- This specific fixture has been designated to run for every test by setting autouse=True.
- '''
-
- # Navigate to page
- page.goto("https://github.com/nhs-england-tools/playwright-python-blueprint")
-
-
-@pytest.mark.example
-def test_basic_example(page: Page) -> None:
- '''
- This test demonstrates how to quickly get started using Playwright Python, which runs using pytest.
-
- This example starts with @pytest.mark.example, which indicates this test has been tagged
- with the term "example", to demonstrate how tests can be independently tagged.
-
- When running using the pytest command, Playwright automatically instantiates certain objects
- available for use, including the Page object (which is how Playwright interacts with the
- system under test).
-
- This test does the following:
- 1) Navigates to this repository (via the initial_navigation fixture above)
- 2) Asserts that the README contents rendered by GitHub contains the text "Playwright Python Blueprint"
- 3) Asserts that the main section of the page contains the topic label "playwright-python"
- '''
-
- # Assert repo text is present
- expect(page.get_by_role("article")).to_contain_text("Playwright Python Blueprint")
-
- # Assert the page loaded contains a reference to the playwright-python topic page
- expect(page.get_by_role("main")).to_contain_text("playwright-python")
-
-
-@pytest.mark.example
-def test_textbox_example(page: Page) -> None:
- """
- This test demonstrates another example of quickly getting started using Playwright Python.
-
- This is specifically designed to outline some of the principals that Playwright uses, for
- example when looking for a specific textbox to enter information into, rather than using a
- direct HTML or CSS reference, you can use attributes of the field (in this case the placeholder
- text) to find the element as a user would navigating your application. You can also use
- locators to find specific HTML or CSS elements as required (in this case the locator for the
- assertion).
-
- This test does the following:
- 1) Navigates to this repository (via the initial_navigation fixture above)
- 2) Uses the "Go to file" textbox and searches for this file, "text_example.py"
- 3) Selects the label for the dropdown element presented for the search results and clicks
- 4) Asserts that the filename for the now selected file is "test_example.py"
- """
-
- # Select the "Go to file" textbox and search for this file
- page.get_by_placeholder("Go to file").fill("test_example.py")
-
- # Click the file name presented in the dropdown
- page.get_by_label("tests/test_example.").click()
-
- # Confirm we are viewing the correct file
- expect(page.locator("#file-name-id-wide")).to_contain_text("test_example.py")
diff --git a/tests/ui/cohort_manager/cohort_manager_util.py b/tests/ui/cohort_manager/cohort_manager_util.py
new file mode 100644
index 00000000..dacae83b
--- /dev/null
+++ b/tests/ui/cohort_manager/cohort_manager_util.py
@@ -0,0 +1,21 @@
+def subject_count_by_nhs_number(db_util, nhs_number, table_name):
+ subject_search = (
+ f"""select count(1) as count from {table_name} where nhs_number = %s """
+ )
+ df = db_util.get_results(subject_search, [nhs_number])
+ return df["count"][0]
+
+
+def fetch_all_audit_subjects(db_util, nhs_number):
+ import pandas as pd
+
+ subject_search = f"""select * from audit_subjects where nhs_number = %s order by transaction_db_date_time desc"""
+ results = db_util.get_results(subject_search, [nhs_number])
+ return pd.DataFrame(results)
+
+
+def fetch_subject_column_value(db_util, nhs_number, table_column):
+ subject_search = f"""select {table_column} from subjects where nhs_number = %s """
+ results = db_util.get_results(subject_search, [nhs_number])
+ assert len(results) == 1, f"Expected only 1 but returned {len(results)}"
+ return results[table_column][0]
diff --git a/tests/ui/cohort_manager/test_cohort_manager_lambda_integration.py b/tests/ui/cohort_manager/test_cohort_manager_lambda_integration.py
new file mode 100644
index 00000000..87b49dc7
--- /dev/null
+++ b/tests/ui/cohort_manager/test_cohort_manager_lambda_integration.py
@@ -0,0 +1,957 @@
+import logging
+import boto3
+import json
+
+from pandas import DataFrame
+import pytest
+import time
+from dateutil.parser import parse
+from playwright.sync_api import expect, Page
+from tests.ui.cohort_manager.cohort_manager_util import (
+ fetch_subject_column_value,
+ subject_count_by_nhs_number,
+)
+from utils.db_util import DbUtil
+from utils.user_tools import UserTools
+
+
+logging.getLogger("botocore").setLevel(logging.WARNING)
+
+## Run this cmd before running these tests - aws sso login --profile bs-select-rw-user-730319765130
+
+
+################ CM Lambda Positive tests ####################
+# TC-9
+@pytest.mark.cm1
+def test_status_204(db_util: DbUtil) -> None:
+ """
+ trigger_lambda_with_python and assert the status of 204
+ """
+ message_id = "ffffffff-ffff-ffff-ffff-fffffffff204"
+ insert_data(db_util, message_id)
+ trigger_lambda_and_verify_success(0)
+
+
+# TC-2, this test covers TC-4 as well
+@pytest.mark.cm2
+def test_validate_max_field_length_in_db_pi_changes(db_util: DbUtil) -> None:
+ """
+ Test to validate cohort_manager max field length in db pi_changes table
+ """
+ # Insert data
+ message_id = "ffffffff-ffff-ffff-ffff-ffffffffffcc"
+ insert_data(db_util, message_id)
+ trigger_lambda_and_verify_success(1)
+
+ stub_data = {
+ "request_id": "6d649f7d-e36f-475e-886b-60ff12d4ddea",
+ "nhs_number": "9470082060",
+ "name_prefix": "Dr Professor Jonathan WilliamsonXYZ",
+ "family_name": "Montgomery Featherstonehaugh ABCXYZ",
+ "given_name": "Alexander Jonathan Williamson ABXYZ",
+ "other_given_names": "ChristopherEdwardNathanielBenedictAndersonSmithJackson WilliamsRobertJohnsonThompsonSusanLouiseGrogu",
+ "previous_family_name": "Montgomery Featherstonehaugh ABC XY",
+ "birth_date": "19670101",
+ "death_date": "",
+ "gender_code": 2,
+ "address_line_1": "1234 Greenwood AvenueApartmentSuite 5678",
+ "address_line_2": "5678 OakwoodStreetBuilding Number 234567",
+ "address_line_3": "91011 MapleLaneResidentialBlock CUnit 56",
+ "address_line_4": "1415 PineHillRoadBusinessDistrict Floor7",
+ "address_line_5": "1617 CedarGroveDriveLakeviewApartment 3B",
+ "postcode": "EU13 9NG",
+ "primary_care_provider": "A00002",
+ "reason_for_removal": "",
+ "reason_removal_eff_from_date": "",
+ "superseded_by_nhs_number": "",
+ "telephone_number_home": "12345678901234567890123456789012",
+ "telephone_number_mobile": "12345678901234567890123456789012",
+ "email_address_home": "JonathanWilliamsonAlexanderChristopherEdwardNathanielBenedictSmithWiliamsonJoe@example.com",
+ "preferred_language": "En",
+ "interpreter_required": "1",
+ "usual_address_eff_from_date": "20201201",
+ "telephone_number_home_eff_from_date": "20201231",
+ "telephone_number_mobile_eff_from_date": "20201231",
+ "email_address_home_eff_from_date": "20201231",
+ }
+ # Retrieve data from the DB using the request_id from stub data
+ db_result = get_latest_record_by_request_id(db_util, stub_data["request_id"])
+ inserted = db_result.to_dict("records")[0]
+
+ # Assertion to compare stub data and DB data
+ assert (
+ str(inserted["message_id"]) == stub_data["request_id"]
+ ), "field not matched: request_id"
+ nhs_number = "9470082060"
+ wait_for_assertion(
+ lambda: fetch_pi_changes_column_value(db_util, nhs_number, "state")
+ == "PROCESSED"
+ )
+ assert inserted["nhs_number"] == stub_data["nhs_number"]
+ assert inserted["name_prefix"] == stub_data["name_prefix"]
+ assert inserted["family_name"] == stub_data["family_name"]
+ assert inserted["first_name"] == stub_data["given_name"]
+ assert inserted["other_names"] == stub_data["other_given_names"]
+ assert inserted["previous_family_name"] == stub_data["previous_family_name"]
+ assert inserted["date_of_birth"].strftime("%Y%m%d") == stub_data["birth_date"]
+ assert inserted["gender_code"] == stub_data["gender_code"]
+ assert inserted["address_line_1"] == stub_data["address_line_1"]
+ assert inserted["address_line_2"] == stub_data["address_line_2"]
+ assert inserted["address_line_3"] == stub_data["address_line_3"]
+ assert inserted["address_line_4"] == stub_data["address_line_4"]
+ assert inserted["address_line_5"] == stub_data["address_line_5"]
+ assert inserted["postcode"] == stub_data["postcode"]
+ assert inserted["gp_practice_code"] == stub_data["primary_care_provider"]
+ assert inserted["processed_date_time"] == stub_data.get("processed_date_time")
+ assert inserted["person_application_feed_id"] == stub_data.get(
+ "person_application_feed_id"
+ )
+ assert inserted["transaction_id"] == stub_data.get("transaction_id")
+ assert inserted["transaction_user_org_role_id"] == stub_data.get(
+ "transaction_user_org_role_id"
+ )
+ assert inserted["telephone_number_home"] == stub_data.get("telephone_number_home")
+ assert inserted["telephone_number_mobile"] == stub_data.get(
+ "telephone_number_mobile"
+ )
+ assert inserted["email_address_home"] == stub_data.get("email_address_home")
+ assert (
+ inserted["preferred_language"].upper()
+ == stub_data["preferred_language"].upper()
+ )
+ assert inserted["interpreter_required"] == bool(stub_data["interpreter_required"])
+ assert (
+ inserted["usual_address_eff_from_date"].strftime("%Y%m%d")
+ == stub_data["usual_address_eff_from_date"]
+ )
+ assert (
+ inserted["tel_number_home_eff_from_date"].strftime("%Y%m%d")
+ == stub_data["telephone_number_home_eff_from_date"]
+ )
+ assert (
+ inserted["tel_number_mob_eff_from_date"].strftime("%Y%m%d")
+ == stub_data["telephone_number_mobile_eff_from_date"]
+ )
+ assert (
+ inserted["email_addr_home_eff_from_date"].strftime("%Y%m%d")
+ == stub_data["email_address_home_eff_from_date"]
+ )
+
+
+# TC-3
+@pytest.mark.cm3
+def test_validate_greater_than_max_field_length_of_nhs_number_in_db_pi_changes(
+ db_util: DbUtil,
+) -> None:
+ """
+ Negative test to validate cohort_manager greater than max field length of nhs_number in db pi_changes table
+ """
+ stub_request_id = "6d649f7d-e36f-475e-846b-60ff12d4bdea"
+
+ # Insert data
+ message_id = "ffffffc3-ffff-ffff-ffff-fffffffffff1"
+ insert_data(db_util, message_id)
+ trigger_lambda_and_verify_error(
+ "Invalid response from cohort manager: attribute: nhs_number should be of length 10 but was 11"
+ )
+
+ # verify db has the latest request_id
+ db_result = get_latest_record_by_request_id(db_util, stub_request_id)
+ assert len(db_result) == 0
+
+
+# TC-3
+def test_validate_greater_than_max_field_length_of_family_name_in_db_pi_changes(
+ db_util: DbUtil,
+) -> None:
+ """
+ Negative test to validate cohort_manager greater than max field length of family_name in db pi_changes table
+ """
+ stub_request_id = "6d649f7d-e36f-475e-846b-60cd12d4bdea"
+
+ # Insert data
+ message_id = "ffffffc3-ffff-ffff-ffff-fffffffffff2"
+ insert_data(db_util, message_id)
+ trigger_lambda_and_verify_error(
+ "Invalid response from cohort manager: attribute: family_name should be of maximum length 35 but was 37"
+ )
+
+ # verify db has the latest request_id
+ db_result = get_latest_record_by_request_id(db_util, stub_request_id)
+ assert len(db_result) == 0
+
+
+# TC-8 test covers TC-6 & TC-7
+@pytest.mark.tc8
+def test_to_add_and_update_participant_in_the_pi_changes(db_util: DbUtil) -> None:
+ """
+ Test to add and update the participant and assert the added and updaded values
+ """
+ # Insert data
+ nhs_number = "9011000042"
+ message_id = "ffffffff-ffff-ffff-ffff-fffffffffc8f"
+ insert_data(db_util, message_id)
+ trigger_lambda_and_verify_success(1)
+
+ stub_data = {
+ "request_id": "2f153e68-b4f3-45fb-a85d-14b0d1eb22bb",
+ "nhs_number": "9011000042",
+ "name_prefix": "Dr.",
+ "family_name": "Bednar",
+ "given_name": "Miguelina",
+ "other_given_names": "Raul",
+ "previous_family_name": "Olson",
+ "birth_date": "19700702",
+ "death_date": "",
+ "gender_code": 2,
+ "address_line_1": "Apt. 825",
+ "address_line_2": "71354 Monahan Squares",
+ "address_line_3": "Lake Jamieside",
+ "address_line_4": "Arkansas",
+ "address_line_5": "Burundi",
+ "postcode": "EU1 8LN",
+ "primary_care_provider": "A00001",
+ "reason_for_removal": "",
+ "reason_removal_eff_from_date": "",
+ "superseded_by_nhs_number": "",
+ "telephone_number_home": "40846 280 931",
+ "telephone_number_mobile": "40103 024 754",
+ "email_address_home": "dltlhagqn3229@test.com",
+ "preferred_language": "en",
+ "interpreter_required": 1,
+ "usual_address_eff_from_date": "20000101",
+ "telephone_number_home_eff_from_date": "20000101",
+ "telephone_number_mobile_eff_from_date": "20000101",
+ "email_address_home_eff_from_date": "20000101",
+ "primary_care_provider_eff_from_date": "20000101",
+ }
+
+ # Retrieve data from the DB using the request_id from stub data
+ db_result = get_latest_record_by_request_id(db_util, stub_data["request_id"])
+ inserted = db_result.to_dict("records")[0]
+
+ # Perform the assertion to compare stub data and DB data
+ assert (
+ str(inserted["message_id"]) == stub_data["request_id"]
+ ), "field not matched: request_id"
+ wait_for_assertion(
+ lambda: fetch_pi_changes_column_value(db_util, nhs_number, "state")
+ == "PROCESSED"
+ )
+ assert inserted["address_line_1"] == stub_data["address_line_1"]
+ assert inserted["telephone_number_home"] == stub_data.get("telephone_number_home")
+ assert inserted["telephone_number_mobile"] == stub_data.get(
+ "telephone_number_mobile"
+ )
+ # Insert data for add and update
+ insert_data(db_util, "ffffffff-ffff-ffff-ffff-ffffffffc8ff")
+ trigger_lambda_and_verify_success(2)
+
+ stub_data_update = [
+ {
+ "request_id": "2f153e68-b4f3-45f2-a85d-14b0d1eb22cc",
+ "nhs_number": "9000000041",
+ "superseded_by_nhs_number": "",
+ "primary_care_provider": "A00001",
+ "primary_care_provider_eff_from_date": "",
+ "name_prefix": "MS",
+ "given_name": "ADDIEN",
+ "other_given_names": "PADERAU",
+ "family_name": "DIMOCK",
+ "previous_family_name": "",
+ "birth_date": "19801209",
+ "gender_code": 2,
+ "address_line_1": "1 NEWSTEAD AVENUE",
+ "address_line_2": "NEWARK",
+ "address_line_3": "NOTTS",
+ "address_line_4": "",
+ "address_line_5": "",
+ "postcode": "NG2 1ND",
+ "usual_address_eff_from_date": "20070723",
+ "death_date": "",
+ "telephone_number_home": "",
+ "telephone_number_home_eff_from_date": "",
+ "telephone_number_mobile": "",
+ "telephone_number_mobile_eff_from_date": "",
+ "email_address_home": "",
+ "email_address_home_eff_from_date": "",
+ "preferred_language": "",
+ "interpreter_required": 0,
+ "reason_for_removal": "",
+ "reason_removal_eff_from_date": "",
+ },
+ {
+ "request_id": "2f153e68-b4f3-45f2-a85d-14b0d1eb22cc",
+ "nhs_number": "9011000042",
+ "superseded_by_nhs_number": "",
+ "primary_care_provider": "A00001",
+ "primary_care_provider_eff_from_date": "20000101",
+ "name_prefix": "Dr.",
+ "given_name": "Miguelina",
+ "other_given_names": "Raul",
+ "family_name": "Bednar",
+ "previous_family_name": "Olson",
+ "birth_date": "19700702",
+ "gender_code": 2,
+ "address_line_1": "825",
+ "address_line_2": "71354 Monahan Squares",
+ "address_line_3": "Lake Jamieside",
+ "address_line_4": "Arkansas",
+ "address_line_5": "Burundi",
+ "postcode": "EU1 8LN",
+ "usual_address_eff_from_date": "20000101",
+ "death_date": "",
+ "telephone_number_home": "70846 280 941",
+ "telephone_number_home_eff_from_date": "20000101",
+ "telephone_number_mobile": "70103 024 555",
+ "telephone_number_mobile_eff_from_date": "20000101",
+ "email_address_home": "dltlhagqn3229@test.com",
+ "email_address_home_eff_from_date": "20000101",
+ "preferred_language": "en",
+ "interpreter_required": 1,
+ "reason_for_removal": "",
+ "reason_removal_eff_from_date": "",
+ },
+ ]
+ # Retrieve data from the DB using the request_id from stub data
+ expected = stub_data_update[1]
+ db_result = get_latest_records_by_nhs_number(db_util, expected["nhs_number"], 2)
+ updated = db_result.to_dict("records")[0]
+
+ assert (
+ str(updated["message_id"]) == expected["request_id"]
+ ), "field not matched: request_id"
+ wait_for_assertion(
+ lambda: fetch_pi_changes_column_value(db_util, nhs_number, "state")
+ == "PROCESSED"
+ )
+ assert updated["address_line_1"] == expected["address_line_1"]
+ assert updated["telephone_number_home"] == expected.get("telephone_number_home")
+ assert updated["telephone_number_mobile"] == expected.get("telephone_number_mobile")
+
+
+# TC-11
+@pytest.mark.tc11
+def test_where_nhs_num_exists_superseded_nhs_num_does_not_exists(
+ db_util: DbUtil, page: Page, user_tools: UserTools
+) -> None:
+ """
+ Test to when the subjects record for the NHS number is updated with the superseded NHS number received within the message,
+ No new subject record is created
+ """
+ nhs_number_before = "9007007228"
+ superseded_nhs_number = "9011100042"
+ assert subject_count_by_nhs_number(db_util, nhs_number_before, "subjects") == 1
+ assert subject_count_by_nhs_number(db_util, superseded_nhs_number, "subjects") == 0
+ prev_subject_audit_count = subject_count_by_nhs_number(
+ db_util, superseded_nhs_number, "audit_subjects"
+ )
+
+ # Insert data
+ message_id = "ffffffff-ffff-ffff-ffff-ffffffffff11"
+ insert_data(db_util, message_id)
+
+ trigger_lambda_and_verify_success(1)
+
+ stub_data = {
+ "request_id": "33ccaa03-9e8e-4aad-a14d-f25d697dcb3a",
+ "participant_id": 11,
+ "nhs_number": "9007007228",
+ "superseded_by_nhs_number": "9011100042",
+ "primary_care_provider": "A00020",
+ "primary_care_provider_eff_from_date": "",
+ "name_prefix": "Mrs",
+ "given_name": "Harriet",
+ "other_given_names": "",
+ "family_name": "COLE",
+ "previous_family_name": "",
+ "birth_date": "19491003",
+ "gender_code": 2,
+ "address_line_1": "56",
+ "address_line_2": "Eastcliffe Road",
+ "address_line_3": "Eastcliffe Crescent",
+ "address_line_4": "Bristol",
+ "address_line_5": "Avon",
+ "postcode": "BR20 4RD",
+ "usual_address_eff_from_date": "20070723",
+ "death_date": "",
+ "telephone_number_home": "",
+ "telephone_number_home_eff_from_date": "",
+ "telephone_number_mobile": "",
+ "telephone_number_mobile_eff_from_date": "",
+ "email_address_home": "",
+ "email_address_home_eff_from_date": "",
+ "preferred_language": "",
+ "interpreter_required": 0,
+ "reason_for_removal": "",
+ "reason_removal_eff_from_date": "",
+ }
+
+ db_result = get_latest_record_by_request_id(db_util, stub_data["request_id"])
+ inserted = db_result.to_dict("records")[0]
+ # Perform the assertion to compare stub data and DB data
+ assert (
+ str(inserted["message_id"]) == stub_data["request_id"]
+ ), "field not matched: request_id"
+ wait_for_assertion(
+ lambda: fetch_pi_changes_column_value(db_util, nhs_number_before, "state")
+ == "PROCESSED"
+ )
+ wait_for_assertion(
+ lambda: subject_count_by_nhs_number(db_util, nhs_number_before, "subjects") == 0
+ )
+ wait_for_assertion(
+ lambda: subject_count_by_nhs_number(db_util, superseded_nhs_number, "subjects")
+ == 1
+ )
+ wait_for_assertion(
+ lambda: subject_count_by_nhs_number(
+ db_util, superseded_nhs_number, "audit_subjects"
+ )
+ == prev_subject_audit_count + 1
+ )
+ # UI assertions
+ # Logged into BSS_SO1
+ user_tools.user_login(page, "BSO User1 - BS1")
+ page.goto("/bss/subjects", wait_until="domcontentloaded")
+ page.locator("//a[text()='Subject Search']").click()
+ page.locator("#nhsNumberFilter input").fill(nhs_number_before)
+ page.locator("#nhsNumberFilter input").press("Enter")
+ expect(page.locator("text=No matching records found")).to_be_visible()
+ page.locator("#nhsNumberFilter input").fill(superseded_nhs_number)
+ page.locator("#nhsNumberFilter input").press("Enter")
+ expect(page.locator("//td[text()='901 110 0042']")).to_be_visible()
+
+
+# TC-12
+# @pytest.mark.tc12
+def test_where_nhs_num_and_superseded_nhs_num_both_does_exists(
+ db_util: DbUtil, page: Page, user_tools: UserTools
+) -> None:
+ """
+ Test for when nhs_number and superseded_by_nhs number both exists in the Subjects table
+ """
+ nhs_number_before = "9007007227"
+ assert subject_count_by_nhs_number(db_util, nhs_number_before, "subjects") == 1
+
+ superseded_nhs_number = "9100070464"
+ assert subject_count_by_nhs_number(db_util, superseded_nhs_number, "subjects") == 1
+ prev_subject_audit_count = subject_count_by_nhs_number(
+ db_util, superseded_nhs_number, "audit_subjects"
+ )
+ prev_subject = fetch_latest_record_by_nhs_number(
+ db_util, superseded_nhs_number, "subjects", "transaction_db_date_time"
+ )
+
+ # Insert data
+ message_id = "ffffffff-ffff-ffff-ffff-ffffffffff12"
+ insert_data(db_util, message_id)
+ trigger_lambda_and_verify_success(1)
+
+ stub_data = {
+ "request_id": "33ccaa03-9e8e-4abd-a14d-f25d697dbb3b",
+ "participant_id": 12,
+ "nhs_number": "9007007227",
+ "superseded_by_nhs_number": "9100070464",
+ "primary_care_provider": "A00020",
+ "primary_care_provider_eff_from_date": "20000101",
+ "name_prefix": "Mrs",
+ "given_name": "Felicity ",
+ "other_given_names": "",
+ "family_name": "COLE",
+ "previous_family_name": "",
+ "birth_date": "19500403",
+ "gender_code": 2,
+ "address_line_1": "55",
+ "address_line_2": "Eastcliffe Road",
+ "address_line_3": "Eastcliffe Crescent",
+ "address_line_4": "Bristol",
+ "address_line_5": "Avon",
+ "postcode": "BR20 4RD",
+ "usual_address_eff_from_date": "",
+ "death_date": "",
+ "telephone_number_home": "",
+ "telephone_number_home_eff_from_date": "",
+ "telephone_number_mobile": "",
+ "telephone_number_mobile_eff_from_date": "",
+ "email_address_home": "",
+ "email_address_home_eff_from_date": "",
+ "preferred_language": "en",
+ "interpreter_required": 1,
+ "reason_for_removal": "",
+ "reason_removal_eff_from_date": "",
+ }
+
+ db_result = get_latest_record_by_request_id(db_util, stub_data["request_id"])
+ inserted = db_result.to_dict("records")[0]
+ # Perform the assertion to compare stub data and DB data
+ assert (
+ str(inserted["message_id"]) == stub_data["request_id"]
+ ), "field not matched: request_id"
+ wait_for_assertion(
+ lambda: fetch_pi_changes_column_value(db_util, nhs_number_before, "state")
+ == "PROCESSED"
+ )
+ wait_for_assertion(
+ lambda: subject_count_by_nhs_number(db_util, nhs_number_before, "subjects") == 1
+ )
+ wait_for_assertion(
+ lambda: fetch_latest_removal_reason(db_util, nhs_number_before, "subjects")
+ == "NOT_PROVIDED"
+ )
+ wait_for_assertion(
+ lambda: fetch_latest_removal_reason(db_util, superseded_nhs_number, "subjects")
+ is None
+ )
+ wait_for_assertion(
+ lambda: subject_count_by_nhs_number(db_util, superseded_nhs_number, "subjects")
+ == 1
+ )
+ wait_for_assertion(
+ lambda: subject_count_by_nhs_number(
+ db_util, superseded_nhs_number, "audit_subjects"
+ )
+ == prev_subject_audit_count
+ )
+ # Assering no change in the superseded_nhs participant data
+ after_subject = fetch_latest_record_by_nhs_number(
+ db_util, superseded_nhs_number, "subjects", "transaction_db_date_time"
+ )
+ assert after_subject.equals(prev_subject)
+ # UI assertions
+ # Logged into BSS_SO1
+ user_tools.user_login(page, "BSO User1 - BS1")
+ page.goto("/bss/subjects", wait_until="domcontentloaded")
+ page.locator("//a[text()='Subject Search']").click()
+ page.locator("#nhsNumberFilter input").fill(nhs_number_before)
+ page.locator("#nhsNumberFilter input").press("Enter")
+ expect(page.locator("//td[text()='900 700 7227']")).to_be_visible()
+ page.locator("#nhsNumberFilter input").fill(superseded_nhs_number)
+ page.locator('//th[@id="bsoFilter"]//input').fill("")
+ page.locator("#nhsNumberFilter input").press("Enter")
+ expect(page.locator("//td[text()='910 007 0464']")).to_be_visible()
+
+
+# TC-13
+@pytest.mark.tc13
+def test_where_nhs_num_and_superseded_nhs_num_both_does_not_exists(
+ db_util: DbUtil, page: Page, user_tools: UserTools
+) -> None:
+ """
+ Test for when nhs_number and superseded_by_nhs number both does NOT exists in the Subjects table
+ """
+ nhs_number_before = "9007117227"
+ assert subject_count_by_nhs_number(db_util, nhs_number_before, "subjects") == 0
+
+ superseded_nhs_number = "9006116227"
+ assert subject_count_by_nhs_number(db_util, superseded_nhs_number, "subjects") == 0
+ prev_subject_audit_count = subject_count_by_nhs_number(
+ db_util, superseded_nhs_number, "audit_subjects"
+ )
+
+ # Insert data
+ message_id = "ffffffff-ffff-ffff-ffff-ffffffffff13"
+ insert_data(db_util, message_id)
+ trigger_lambda_and_verify_success(1)
+
+ stub_data = {
+ "request_id": "33ccaa03-9e8e-4bbd-a14d-f25d697dbb3c",
+ "participant_id": 13,
+ "nhs_number": "9007117227",
+ "superseded_by_nhs_number": "9006116227",
+ "primary_care_provider": "A00002",
+ "primary_care_provider_eff_from_date": "",
+ "name_prefix": "Mrs",
+ "given_name": "Tina",
+ "other_given_names": "",
+ "family_name": "Test",
+ "previous_family_name": "",
+ "birth_date": "19701201",
+ "gender_code": 2,
+ "address_line_1": "The House",
+ "address_line_2": "Bakerstreet",
+ "address_line_3": "London",
+ "address_line_4": "UK",
+ "address_line_5": "",
+ "postcode": "EX8 1AA",
+ "usual_address_eff_from_date": "20070723",
+ "death_date": "",
+ "telephone_number_home": "",
+ "telephone_number_home_eff_from_date": "",
+ "telephone_number_mobile": "",
+ "telephone_number_mobile_eff_from_date": "",
+ "email_address_home": "",
+ "email_address_home_eff_from_date": "",
+ "preferred_language": "",
+ "interpreter_required": 0,
+ "reason_for_removal": "",
+ "reason_removal_eff_from_date": "",
+ }
+
+ db_result = get_latest_record_by_request_id(db_util, stub_data["request_id"])
+ inserted = db_result.to_dict("records")[0]
+ # Perform the assertion to compare stub data and DB data
+ assert (
+ str(inserted["message_id"]) == stub_data["request_id"]
+ ), "field not matched: request_id"
+ wait_for_assertion(
+ lambda: fetch_pi_changes_column_value(db_util, nhs_number_before, "state")
+ == "PROCESSED"
+ )
+ wait_for_assertion(
+ lambda: subject_count_by_nhs_number(db_util, nhs_number_before, "subjects") == 0
+ )
+ wait_for_assertion(
+ lambda: subject_count_by_nhs_number(db_util, superseded_nhs_number, "subjects")
+ == 1
+ )
+ wait_for_assertion(
+ lambda: subject_count_by_nhs_number(
+ db_util, superseded_nhs_number, "audit_subjects"
+ )
+ == prev_subject_audit_count + 1
+ )
+ # UI assertions
+ # Logged into BSS_SO1
+ user_tools.user_login(page, "BSO User1 - BS1")
+ page.goto("/bss/subjects", wait_until="domcontentloaded")
+ page.locator("//a[text()='Subject Search']").click()
+ page.locator("#nhsNumberFilter input").fill(nhs_number_before)
+ page.locator("#nhsNumberFilter input").press("Enter")
+ expect(page.locator("text=No matching records found")).to_be_visible()
+ page.locator("#nhsNumberFilter input").fill(superseded_nhs_number)
+ page.locator("#nhsNumberFilter input").press("Enter")
+ expect(page.locator("//td[text()='900 611 6227']")).to_be_visible()
+
+
+# TC-14
+# @pytest.mark.tc14
+def test_where_nhs_num_does_not_exists_superseded_nhs_num_exists(
+ db_util: DbUtil, page: Page, user_tools: UserTools
+) -> None:
+ """
+ Test for when nhs_number does NOT and superseded_by_nhs number both exists in the Subjects table
+ """
+ nhs_number_before = "9005114227"
+ assert subject_count_by_nhs_number(db_util, nhs_number_before, "subjects") == 0
+
+ superseded_nhs_number = "9007007226"
+ assert subject_count_by_nhs_number(db_util, superseded_nhs_number, "subjects") == 1
+ prev_subject_audit_count = subject_count_by_nhs_number(
+ db_util, superseded_nhs_number, "audit_subjects"
+ )
+
+ # Insert data
+ message_id = "ffffffff-ffff-ffff-ffff-ffffffffff14"
+ insert_data(db_util, message_id)
+ trigger_lambda_and_verify_success(1)
+
+ stub_data = {
+ "request_id": "33ccaa03-9e8e-4aad-a14d-f25d697dbb3d",
+ "participant_id": 14,
+ "nhs_number": "9005114227",
+ "superseded_by_nhs_number": "9007007226",
+ "primary_care_provider": "A00002",
+ "primary_care_provider_eff_from_date": "",
+ "name_prefix": "Mrs",
+ "given_name": "Emily",
+ "other_given_names": "",
+ "family_name": "Test",
+ "previous_family_name": "",
+ "birth_date": "19771111",
+ "gender_code": 2,
+ "address_line_1": "Emily's House",
+ "address_line_2": "Bakerstreet",
+ "address_line_3": "London",
+ "address_line_4": "UK",
+ "address_line_5": "",
+ "postcode": "EX8 1AA",
+ "usual_address_eff_from_date": "20070723",
+ "death_date": "",
+ "telephone_number_home": "",
+ "telephone_number_home_eff_from_date": "",
+ "telephone_number_mobile": "",
+ "telephone_number_mobile_eff_from_date": "",
+ "email_address_home": "",
+ "email_address_home_eff_from_date": "",
+ "preferred_language": "",
+ "interpreter_required": 0,
+ "reason_for_removal": "",
+ "reason_removal_eff_from_date": "",
+ }
+
+ db_result = get_latest_record_by_request_id(db_util, stub_data["request_id"])
+ inserted = db_result.to_dict("records")[0]
+ # Perform the assertion to compare stub data and DB data
+ assert (
+ str(inserted["message_id"]) == stub_data["request_id"]
+ ), "field not matched: request_id"
+ wait_for_assertion(
+ lambda: fetch_pi_changes_column_value(db_util, nhs_number_before, "state")
+ == "PROCESSED"
+ )
+ wait_for_assertion(
+ lambda: subject_count_by_nhs_number(db_util, nhs_number_before, "subjects") == 0
+ )
+ wait_for_assertion(
+ lambda: subject_count_by_nhs_number(db_util, superseded_nhs_number, "subjects")
+ == 1
+ )
+ wait_for_assertion(
+ lambda: subject_count_by_nhs_number(
+ db_util, superseded_nhs_number, "audit_subjects"
+ )
+ == prev_subject_audit_count
+ )
+ # UI assertions
+ # Logged into BSS_SO1
+ user_tools.user_login(page, "BSO User1 - BS1")
+ page.goto("/bss/subjects", wait_until="domcontentloaded")
+ page.locator("//a[text()='Subject Search']").click()
+ page.locator("#nhsNumberFilter input").fill(nhs_number_before)
+ page.locator("#nhsNumberFilter input").press("Enter")
+ expect(page.locator("text=No matching records found")).to_be_visible()
+ page.locator("#nhsNumberFilter input").fill(superseded_nhs_number)
+ page.locator("#nhsNumberFilter input").press("Enter")
+ expect(page.locator("//td[text()='900 700 7226']")).to_be_visible()
+
+
+# TC-15, 16, 17
+@pytest.mark.tc15
+def test_death_date_and_reason_for_reamoval_populated_using_dummy_GP_practice_code_pi_changes_gp_practice_code_is_Null_with_reason_for_removal(
+ db_util: DbUtil,
+) -> None:
+ """
+ 15 = Test to verify death_date and reason_for_reamoval is populated
+ 16 = Test using dummy GP_practice_code = ZZZLED
+ 17 = Test with where pi_changes.gp_practice_code = Null and with a reason_for_removal
+ """
+ nhs_number_15 = "9000019463"
+ nhs_number_16 = "9007007216"
+ nhs_number_17 = "9000018196"
+
+ assert fetch_subject_column_value(db_util, nhs_number_15, "removal_reason") is None
+ assert fetch_subject_column_value(db_util, nhs_number_16, "gp_practice_id") == 20
+ assert fetch_subject_column_value(db_util, nhs_number_17, "removal_reason") is None
+
+ # Inserted reason_for_removal data
+ message_id = "ffffff15-ff16-ff17-ffff-fffffffffff2"
+ insert_data(db_util, message_id)
+ trigger_lambda_and_verify_success(3)
+
+ stub_data_with_reasons = [
+ {
+ "request_id": "24fec7a0-98eb-4cf1-a4bd-a9bc60105f51",
+ "participant_id": 15,
+ "nhs_number": nhs_number_15,
+ "death_date": "20090909",
+ "reason_for_removal": "D",
+ "reason_removal_eff_from_date": "20090909",
+ },
+ {
+ "request_id": "24fec7a0-98eb-4cf1-a4bd-a9bc60105f51",
+ "participant_id": 16,
+ "nhs_number": nhs_number_16,
+ "primary_care_provider": "ZZZLED",
+ },
+ {
+ "request_id": "24fec7a0-98eb-4cf1-a4bd-a9bc60105f51",
+ "participant_id": 17,
+ "nhs_number": nhs_number_17,
+ "primary_care_provider": "",
+ "reason_for_removal": "R",
+ },
+ ]
+ db_result = get_latest_record_by_request_id(
+ db_util, stub_data_with_reasons[0]["request_id"]
+ )
+ inserted_data = db_result.to_dict("records")[0]
+ # Perform the assertion to compare stub data and DB data
+ assert (
+ str(inserted_data["message_id"]) == stub_data_with_reasons[0]["request_id"]
+ ), "field not matched: request_id"
+ wait_for_assertion(
+ lambda: fetch_pi_changes_column_value(db_util, nhs_number_15, "state")
+ == "PROCESSED"
+ )
+ wait_for_assertion(
+ lambda: fetch_pi_changes_column_value(db_util, nhs_number_16, "state")
+ == "PROCESSED"
+ )
+ wait_for_assertion(
+ lambda: fetch_pi_changes_column_value(db_util, nhs_number_17, "state")
+ == "PROCESSED"
+ )
+
+ assert (
+ fetch_subject_column_value(db_util, nhs_number_15, "removal_reason") == "DEATH"
+ )
+ assert (
+ fetch_subject_column_value(db_util, nhs_number_16, "gp_practice_id") == 100032
+ ) # todo add UI assertions
+ assert (
+ fetch_subject_column_value(db_util, nhs_number_17, "removal_reason")
+ == "REMOVAL"
+ )
+ assert fetch_subject_column_value(db_util, nhs_number_17, "gp_practice_id") is None
+
+
+#################### CM Lambda Neagtive/NFR tests ####################
+# TC-16
+def test_status_401(db_util: DbUtil) -> None:
+ """
+ trigger_lambda_with_python and assert the status of 401
+ """
+ # Insert data & verify the error message(lambda response)
+ message_id = "ffffffff-ffff-ffff-ffff-fffffffff401"
+ insert_data(db_util, message_id)
+ trigger_lambda_and_verify_error(
+ "The request to https://bss2-1381.nonprod.breast-screening-select.nhs.uk/bss/cohortManager had a HTTP error: 401 Client Error: for url: https://bss2-1381.nonprod.breast-screening-select.nhs.uk/bss/cohortManager?screeningServiceId=1&rowCount=500&requestId=ffffffff-ffff-ffff-ffff-fffffffff401"
+ )
+
+
+# TC-17
+def test_status_403(db_util: DbUtil) -> None:
+ """
+ trigger_lambda_with_python and assert the status of 403
+ """
+ # Insert data & verify the error message(lambda response)
+ message_id = "ffffffff-ffff-ffff-ffff-fffffffff403"
+ insert_data(db_util, message_id)
+ trigger_lambda_and_verify_error(
+ "The request to https://bss2-1381.nonprod.breast-screening-select.nhs.uk/bss/cohortManager had a HTTP error: 403 Client Error: for url: https://bss2-1381.nonprod.breast-screening-select.nhs.uk/bss/cohortManager?screeningServiceId=1&rowCount=500&requestId=ffffffff-ffff-ffff-ffff-fffffffff403"
+ )
+
+
+# TC-18
+def test_status_404(db_util: DbUtil) -> None:
+ """
+ trigger_lambda_with_python and assert the status of 404
+ """
+ # Insert data & verify the error message(lambda response)
+ message_id = "ffffffff-ffff-ffff-ffff-fffffffff404"
+ insert_data(db_util, message_id)
+ trigger_lambda_and_verify_error(
+ "The request to https://bss2-1381.nonprod.breast-screening-select.nhs.uk/bss/cohortManager had a HTTP error: 404 Client Error: for url: https://bss2-1381.nonprod.breast-screening-select.nhs.uk/bss/cohortManager?screeningServiceId=1&rowCount=500&requestId=ffffffff-ffff-ffff-ffff-fffffffff404"
+ )
+
+
+# TC-20
+def test_status_500(db_util: DbUtil) -> None:
+ """
+ trigger_lambda_with_python and assert the status of 500
+ """
+ # Insert data & verify the error message(lambda response)
+ message_id = "ffffffff-ffff-ffff-ffff-fffffffff500"
+ insert_data(db_util, message_id)
+ trigger_lambda_and_verify_error(
+ "The request to https://bss2-1381.nonprod.breast-screening-select.nhs.uk/bss/cohortManager had a HTTP error: 500 Server Error: for url: https://bss2-1381.nonprod.breast-screening-select.nhs.uk/bss/cohortManager?screeningServiceId=1&rowCount=500&requestId=ffffffff-ffff-ffff-ffff-fffffffff500"
+ )
+
+
+#####################################################
+
+# Methods #
+
+
+#####################################################
+# Function to invoke AWS Lambda
+def invoke_lambda(function_name, payload, region="eu-west-2") -> dict:
+ """
+ Invokes an AWS Lambda function and validates the response.
+ """
+ # Initialize the Lambda
+ session = boto3.Session(profile_name="bs-select-rw-user-730319765130")
+ lambda_client = session.client("lambda", region_name=region)
+ response = lambda_client.invoke(
+ FunctionName=function_name,
+ InvocationType="RequestResponse",
+ Payload=json.dumps(payload),
+ )
+ response_payload = json.loads(response["Payload"].read())
+ return response_payload
+
+
+def insert_data(db_util, message_id) -> None:
+ insert_query = """INSERT INTO pi_changes (inserted_date_time, message_id) values (current_timestamp, %s)"""
+ params = (message_id,)
+ db_util.insert(insert_query, params)
+
+
+def trigger_lambda_and_verify_success(inserted) -> None:
+ trigger_lambda_and_verify_status("success", str(inserted))
+
+
+def trigger_lambda_and_verify_error(message) -> None:
+ trigger_lambda_and_verify_status("error", status_message=message)
+
+
+def trigger_lambda_and_verify_status(
+ status_text, inserted="", status_message=""
+) -> None:
+ lambda_function_name = "bs-select-cohort-bss-cm-integration" # Lambda function name
+ lambda_payload = {}
+ response = invoke_lambda(lambda_function_name, lambda_payload)
+ assert (
+ response.get("status") == status_text
+ ), "Lambda did not return expected status!"
+ if inserted:
+ assert (
+ str(response.get("inserted")) == inserted
+ ), "Lambda did not return expected inserted count!"
+ if status_message:
+ assert (
+ str(response.get("message")) == status_message
+ ), "Lambda did not return expected status!"
+
+
+# Retrieve data from the DB based on the request_id
+def get_latest_record_by_request_id(db_conn, message_id) -> DataFrame | None:
+ query = """SELECT message_id, nhs_number, state, name_prefix, family_name, first_name, other_names, previous_family_name, date_of_birth, date_of_death,
+ gender_code, address_line_1, address_line_2, address_line_3, address_line_4, address_line_5, postcode, gp_practice_code, nhais_cipher,
+ nhais_deduction_reason, nhais_deduction_date, replaced_nhs_number, superseded_by_nhs_number, processed_date_time, person_application_feed_id,
+ transaction_id, transaction_app_date_time, transaction_user_org_role_id, telephone_number_home, telephone_number_mobile,
+ email_address_home, preferred_language, interpreter_required, usual_address_eff_from_date, tel_number_home_eff_from_date, tel_number_mob_eff_from_date,
+ email_addr_home_eff_from_date FROM pi_changes WHERE message_id = %s ORDER BY inserted_date_time DESC limit 1"""
+ return db_conn.get_results(query, [message_id])
+
+
+def get_latest_records_by_nhs_number(db_conn, nhs_number, limit) -> DataFrame | None:
+ query = """SELECT * FROM pi_changes WHERE nhs_number=%s ORDER BY inserted_date_time DESC limit %s"""
+ return db_conn.get_results(query, [nhs_number, limit])
+
+
+def wait_for_assertion(assert_func, timeout=90, interval=3):
+ end_time = time.time() + timeout
+
+ counter = 0
+ while time.time() < end_time:
+ counter += 1
+ if assert_func():
+ return
+ time.sleep(interval)
+
+ result = assert_func()
+ assert result, "Expected TRUE"
+
+
+def fetch_latest_removal_reason(db_util, nhs_number, table_name):
+ return fetch_latest_record_by_nhs_number(
+ db_util, nhs_number, table_name, "transaction_db_date_time"
+ )["removal_reason"][0]
+
+
+def fetch_latest_record_by_nhs_number(
+ db_util, nhs_number, table_name, order_by_field
+) -> DataFrame:
+ query = f"""SELECT * FROM {table_name} WHERE nhs_number = %s order by {order_by_field} desc limit 1"""
+ df = db_util.get_results(query, [nhs_number])
+ return df
+
+
+def fetch_pi_changes_column_value(db_util, nhs_number, table_column):
+ query = f"""SELECT {table_column} FROM pi_changes WHERE nhs_number = %s order by inserted_date_time desc limit 1"""
+ df = db_util.get_results(query, [nhs_number])
+ # Extract the column value
+ column_value = df[table_column][0]
+ return column_value
diff --git a/tests/ui/ni_ri_sp_batch/test_ni_ri_sp_batch_additional_testing.py b/tests/ui/ni_ri_sp_batch/test_ni_ri_sp_batch_additional_testing.py
new file mode 100644
index 00000000..f81c5499
--- /dev/null
+++ b/tests/ui/ni_ri_sp_batch/test_ni_ri_sp_batch_additional_testing.py
@@ -0,0 +1,98 @@
+from itertools import count
+import playwright
+import pytest
+from pages.main_menu import MainMenuPage
+from playwright.sync_api import expect, Page, Playwright
+from pages.ni_ri_sp_batch_page import NiRiSpBatchPage
+from utils import test_helpers
+from utils.CheckDigitGenerator import CheckDigitGenerator
+from utils.user_tools import UserTools
+from utils.screenshot_tool import ScreenshotTool
+
+
+# TC-1
+@pytest.mark.tc1
+@pytest.mark.ni3
+def test_count_display_for_ri_sp_batch_with_blank_selected_date_selected_and_rejected_fields(
+ page: Page, ni_ri_sp_batch_page: NiRiSpBatchPage
+) -> None:
+ """verify RI/SP Batch by Year of Birth has been counted and counted is not selected assert Select Date, Selected, and Rejected fields are blank"""
+ # Logged into BSO3 as user3(PMA) and select 'Create RI/SP Batch' from the 'batch management' dropdown
+ UserTools().user_login(page, "Ni Only BSO User3 - BS3")
+ MainMenuPage(page).select_menu_option("Batch Management", "Create RI/SP Batch")
+ ni_ri_sp_batch_page.assert_page_header("Create RI/SP Batch by Year of Birth")
+ batch_title = test_helpers.generate_random_string(10)
+ check_digit = CheckDigitGenerator().generate_check_digit()
+ ni_ri_sp_batch_page.enter_bso_batch_id(check_digit)
+ ni_ri_sp_batch_page.enter_batch_title(batch_title)
+ ni_ri_sp_batch_page.enter_date_for_selection(5)
+ ni_ri_sp_batch_page.click_count_button()
+ ScreenshotTool(page).take_screenshot("additional_testing_TC_1")
+ ni_ri_sp_batch_page.assert_page_header("Amend RI/SP Batch by Year of Birth")
+ MainMenuPage(page).select_menu_option("Batch Management", "Batch List")
+ ni_ri_sp_batch_page.assert_page_header("Batch List")
+ ni_ri_sp_batch_page.assert_entered_bso_batch_id_and_filterd_row_value(check_digit)
+ ni_ri_sp_batch_page.assert_select_date_cell_value("")
+ ni_ri_sp_batch_page.assert_selected_cell_value("")
+ ni_ri_sp_batch_page.assert_rejected_cell_value("")
+ ScreenshotTool(page).take_screenshot("additional_testing_TC_1.1")
+
+
+# TC-2, TC-3
+@pytest.mark.tc2
+@pytest.mark.ni3
+def test_count_display_for_ri_sp_batch_with_selected_date_selected_and_rejected_fields(
+ page: Page, ni_ri_sp_batch_page: NiRiSpBatchPage
+) -> None:
+ """verify RI/SP Batch by Year of Birth has been counted and counted is selected assert Select Date, Selected, and Rejected fields has a value"""
+ # Logged into BSO3 as user3(PMA) and select 'Create RI/SP Batch' from the 'batch management' dropdown and 'No' from Failsafe Flag drop down
+ UserTools().user_login(page, "Ni Only BSO User3 - BS3")
+ MainMenuPage(page).select_menu_option("Batch Management", "Create RI/SP Batch")
+ ni_ri_sp_batch_page.assert_page_header("Create RI/SP Batch by Year of Birth")
+ batch_title = test_helpers.generate_random_string(10)
+ check_digit = CheckDigitGenerator().generate_check_digit()
+ ni_ri_sp_batch_page.enter_bso_batch_id(check_digit)
+ ni_ri_sp_batch_page.enter_batch_title(batch_title)
+ ni_ri_sp_batch_page.enter_date_for_selection(5)
+ ni_ri_sp_batch_page.click_count_button()
+ ScreenshotTool(page).take_screenshot("additional_testing_TC_2")
+ ni_ri_sp_batch_page.assert_page_header("Amend RI/SP Batch by Year of Birth")
+ ni_ri_sp_batch_page.click_count_select_button()
+ MainMenuPage(page).select_menu_option("Batch Management", "Batch List")
+ ni_ri_sp_batch_page.assert_page_header("Batch List")
+ ni_ri_sp_batch_page.select_ri_sp_yob_from_drop_down()
+ ni_ri_sp_batch_page.select_no_from_failsafe_flag_drop_down()
+ ScreenshotTool(page).take_screenshot("additional_testing_TC_2.1")
+ ni_ri_sp_batch_page.assert_entered_bso_batch_id_and_filterd_row_value(check_digit)
+ ScreenshotTool(page).take_screenshot("additional_testing_TC_2.2")
+ ni_ri_sp_batch_page.assert_select_date_cell_value_is_not_null("")
+ ni_ri_sp_batch_page.assert_selected_cell_value_is_not_null("")
+ ni_ri_sp_batch_page.assert_rejected_cell_value_is_not_null("")
+
+
+# TC-4
+@pytest.mark.tc4
+@pytest.mark.ni3
+def test_count_display_for_ri_sp_batch_with_selected_date_selected_and_rejected_fields_search_by_bso_batch_id_and_batch_title(
+ page: Page, ni_ri_sp_batch_page: NiRiSpBatchPage
+) -> None:
+ """verify RI/SP Batch by Year of Birth has been counted and counted is selected assert Select Date, Selected, and Rejected fields has a value and
+ search by bso batch id and batch title
+ """
+ # Logged into BSO3 as user3(PMA) and select 'Create RI/SP Batch' from the 'batch management' dropdown
+ UserTools().user_login(page, "Ni Only BSO User3 - BS3")
+ MainMenuPage(page).select_menu_option("Batch Management", "Create RI/SP Batch")
+ ni_ri_sp_batch_page.assert_page_header("Create RI/SP Batch by Year of Birth")
+ batch_title = test_helpers.generate_random_string(10)
+ check_digit = CheckDigitGenerator().generate_check_digit()
+ ni_ri_sp_batch_page.enter_bso_batch_id(check_digit)
+ ni_ri_sp_batch_page.enter_batch_title(batch_title)
+ ni_ri_sp_batch_page.enter_date_for_selection(5)
+ ni_ri_sp_batch_page.click_count_button()
+ ScreenshotTool(page).take_screenshot("additional_testing_TC_4")
+ ni_ri_sp_batch_page.assert_page_header("Amend RI/SP Batch by Year of Birth")
+ ni_ri_sp_batch_page.click_count_select_button()
+ MainMenuPage(page).select_menu_option("Batch Management", "Batch List")
+ ni_ri_sp_batch_page.assert_page_header("Batch List")
+ ni_ri_sp_batch_page.search_by_bso_batch_id_and_batch_title(check_digit, batch_title)
+ ScreenshotTool(page).take_screenshot("additional_testing_TC_4.1")
diff --git a/tests/ui/ni_ri_sp_batch/test_ni_ri_sp_batch_parameters.py b/tests/ui/ni_ri_sp_batch/test_ni_ri_sp_batch_parameters.py
new file mode 100644
index 00000000..71812c5a
--- /dev/null
+++ b/tests/ui/ni_ri_sp_batch/test_ni_ri_sp_batch_parameters.py
@@ -0,0 +1,160 @@
+from itertools import count
+import playwright
+import pytest
+
+# from conftest import ni_ri_sp_batch_page
+from pages.main_menu import MainMenuPage
+from playwright.sync_api import expect, Page, Playwright
+from pages.ni_ri_sp_batch_page import NiRiSpBatchPage
+from utils import test_helpers
+from utils.CheckDigitGenerator import CheckDigitGenerator
+from utils.user_tools import UserTools
+from utils.screenshot_tool import ScreenshotTool
+
+
+# TC-1
+@pytest.mark.ni2
+def test_create_ri_sp_batch_using_selected_gp_practice_codes_and_all_outcodes(
+ page: Page, ni_ri_sp_batch_page: NiRiSpBatchPage
+) -> None:
+ """Test to create ri/sp batch using "specify by gp practice" and "all outcodes" """
+ # Logged into BSO3 as user3(PMA) and select 'Create RI/SP Batch' from the 'batch management' dropdown
+ UserTools().user_login(page, "Ni Only BSO User3 - BS3")
+ MainMenuPage(page).select_menu_option("Batch Management", "Create RI/SP Batch")
+ ni_ri_sp_batch_page.assert_page_header("Create RI/SP Batch by Year of Birth")
+ batch_title = test_helpers.generate_random_string(10)
+ check_digit = CheckDigitGenerator().generate_check_digit()
+ ni_ri_sp_batch_page.enter_bso_batch_id(check_digit)
+ ni_ri_sp_batch_page.enter_batch_title(batch_title)
+ ni_ri_sp_batch_page.enter_date_for_selection(5)
+ ni_ri_sp_batch_page.enter_include_year_of_birth_from("1955")
+ ni_ri_sp_batch_page.enter_include_year_of_birth_to("1975")
+ ni_ri_sp_batch_page.select_include_specified_practices()
+ ni_ri_sp_batch_page.enter_excluded_gp_practices_filter("N00005")
+ ni_ri_sp_batch_page.select_excluded_gp_practices_from_list("N00005")
+ ni_ri_sp_batch_page.click_gp_practices_select_move()
+ ni_ri_sp_batch_page.click_count_button()
+ ScreenshotTool(page).take_screenshot("batch_parameters_TC_1")
+ ni_ri_sp_batch_page.assert_page_header("Amend RI/SP Batch by Year of Birth")
+ MainMenuPage(page).select_menu_option("Batch Management", "Batch List")
+ ni_ri_sp_batch_page.assert_page_header("Batch List")
+ ni_ri_sp_batch_page.assert_entered_bso_batch_id_and_filterd_row_value(check_digit)
+ ScreenshotTool(page).take_screenshot("batch_parameters_TC_1.1")
+
+
+# TC-2
+@pytest.mark.ni2
+def test_create_ri_sp_batch_using_selected_gp_practice_groups_and_all_outcodes(
+ page: Page, ni_ri_sp_batch_page: NiRiSpBatchPage
+) -> None:
+ """Test to create ri/sp batch using "selected gp practice groups" and "all outcodes" """
+ # Logged into BSO3 as user3(PMA) and select 'Create RI/SP Batch' from the 'batch management' dropdown
+ UserTools().user_login(page, "Ni Only BSO User3 - BS3")
+ MainMenuPage(page).select_menu_option("Batch Management", "Create RI/SP Batch")
+ ni_ri_sp_batch_page.assert_page_header("Create RI/SP Batch by Year of Birth")
+ batch_title = test_helpers.generate_random_string(10)
+ check_digit = CheckDigitGenerator().generate_check_digit()
+ ni_ri_sp_batch_page.enter_bso_batch_id(check_digit)
+ ni_ri_sp_batch_page.enter_batch_title(batch_title)
+ ni_ri_sp_batch_page.enter_date_for_selection(5)
+ ni_ri_sp_batch_page.select_specify_by_gp_practice_group()
+ ni_ri_sp_batch_page.select_excluded_gp_practice_groups("GP PRACTICE GROUP 1")
+ ni_ri_sp_batch_page.click_gp_practice_groups_select_move()
+ ni_ri_sp_batch_page.click_count_button()
+ ScreenshotTool(page).take_screenshot("batch_parameters_TC_2")
+ ni_ri_sp_batch_page.assert_page_header("Amend RI/SP Batch by Year of Birth")
+ MainMenuPage(page).select_menu_option("Batch Management", "Batch List")
+ ni_ri_sp_batch_page.assert_page_header("Batch List")
+ ni_ri_sp_batch_page.assert_entered_bso_batch_id_and_filterd_row_value(check_digit)
+ ScreenshotTool(page).take_screenshot("batch_parameters_TC_2.1")
+
+
+# TC-3
+@pytest.mark.ni2
+def test_create_ri_sp_batch_using_all_gp_practices_and_selected_outcodes(
+ page: Page, ni_ri_sp_batch_page: NiRiSpBatchPage
+) -> None:
+ """Test to create ri/sp batch using "include all gp practices" and "specify by outcode" """
+ # Logged into BSO3 as user3(PMA) and select 'Create RI/SP Batch' from the 'batch management' dropdown
+ UserTools().user_login(page, "Ni Only BSO User3 - BS3")
+ MainMenuPage(page).select_menu_option("Batch Management", "Create RI/SP Batch")
+ ni_ri_sp_batch_page.assert_page_header("Create RI/SP Batch by Year of Birth")
+ batch_title = test_helpers.generate_random_string(10)
+ check_digit = CheckDigitGenerator().generate_check_digit()
+ ni_ri_sp_batch_page.enter_bso_batch_id(check_digit)
+ ni_ri_sp_batch_page.enter_batch_title(batch_title)
+ ni_ri_sp_batch_page.enter_date_for_selection(5)
+ ni_ri_sp_batch_page.enter_include_year_of_birth_from("1955")
+ ni_ri_sp_batch_page.enter_include_year_of_birth_to("1975")
+ ni_ri_sp_batch_page.select_include_specified_outcodes()
+ ni_ri_sp_batch_page.enter_excluded_outcodes_filter("M12")
+ ni_ri_sp_batch_page.select_excluded_outcodes_from_list("M12")
+ ni_ri_sp_batch_page.click_outcodes_select_move()
+ ni_ri_sp_batch_page.click_count_button()
+ ScreenshotTool(page).take_screenshot("batch_parameters_TC_3")
+ ni_ri_sp_batch_page.assert_page_header("Amend RI/SP Batch by Year of Birth")
+ MainMenuPage(page).select_menu_option("Batch Management", "Batch List")
+ ni_ri_sp_batch_page.assert_page_header("Batch List")
+ ni_ri_sp_batch_page.assert_entered_bso_batch_id_and_filterd_row_value(check_digit)
+ ScreenshotTool(page).take_screenshot("batch_parameters_TC_3.1")
+
+
+# TC-4
+@pytest.mark.ni2
+def test_create_ri_sp_batch_using_include_all_gp_practices_and_selected_outcode_groups(
+ page: Page, ni_ri_sp_batch_page: NiRiSpBatchPage
+) -> None:
+ """Test to create ri/sp batch using "include all gp practices" and "selected outcode groups" """
+ # Logged into BSO3 as user3(PMA) and select 'Create RI/SP Batch' from the 'batch management' dropdown
+ UserTools().user_login(page, "Ni Only BSO User3 - BS3")
+ MainMenuPage(page).select_menu_option("Batch Management", "Create RI/SP Batch")
+ ni_ri_sp_batch_page.assert_page_header("Create RI/SP Batch by Year of Birth")
+ batch_title = test_helpers.generate_random_string(10)
+ check_digit = CheckDigitGenerator().generate_check_digit()
+ ni_ri_sp_batch_page.enter_bso_batch_id(check_digit)
+ ni_ri_sp_batch_page.enter_batch_title(batch_title)
+ ni_ri_sp_batch_page.enter_date_for_selection(5)
+ ni_ri_sp_batch_page.select_specify_by_outcode_group()
+ ni_ri_sp_batch_page.select_excluded_outcode_groups("OUTCODE GROUP 1")
+ ni_ri_sp_batch_page.click_outcode_groups_select_move()
+ ni_ri_sp_batch_page.click_count_button()
+ ScreenshotTool(page).take_screenshot("batch_parameters_TC_4")
+ ni_ri_sp_batch_page.assert_page_header("Amend RI/SP Batch by Year of Birth")
+ MainMenuPage(page).select_menu_option("Batch Management", "Batch List")
+ ni_ri_sp_batch_page.assert_page_header("Batch List")
+ ni_ri_sp_batch_page.assert_entered_bso_batch_id_and_filterd_row_value(check_digit)
+ ScreenshotTool(page).take_screenshot("batch_parameters_TC_4.1")
+
+
+# TC-5
+@pytest.mark.ni2
+def test_create_ri_sp_batch_using_selected_out_codes_and_selected_gp_practices(
+ page: Page, ni_ri_sp_batch_page: NiRiSpBatchPage
+) -> None:
+ """Test to create ri/sp batch using "specify by gp practices" and "specify by outcode" """
+ # Logged into BSO3 as user3(PMA) and select 'Create RI/SP Batch' from the 'batch management' dropdown
+ UserTools().user_login(page, "Ni Only BSO User3 - BS3")
+ MainMenuPage(page).select_menu_option("Batch Management", "Create RI/SP Batch")
+ ni_ri_sp_batch_page.assert_page_header("Create RI/SP Batch by Year of Birth")
+ batch_title = test_helpers.generate_random_string(10)
+ check_digit = CheckDigitGenerator().generate_check_digit()
+ ni_ri_sp_batch_page.enter_bso_batch_id(check_digit)
+ ni_ri_sp_batch_page.enter_batch_title(batch_title)
+ ni_ri_sp_batch_page.enter_date_for_selection(5)
+ ni_ri_sp_batch_page.enter_include_year_of_birth_from("1955")
+ ni_ri_sp_batch_page.enter_include_year_of_birth_to("1975")
+ ni_ri_sp_batch_page.select_include_specified_practices()
+ ni_ri_sp_batch_page.enter_excluded_gp_practices_filter("N00007")
+ ni_ri_sp_batch_page.select_excluded_gp_practices_from_list("N00007")
+ ni_ri_sp_batch_page.click_gp_practices_select_move()
+ ni_ri_sp_batch_page.select_include_specified_outcodes()
+ ni_ri_sp_batch_page.enter_excluded_outcodes_filter("M11")
+ ni_ri_sp_batch_page.select_excluded_outcodes_from_list("M11")
+ ni_ri_sp_batch_page.click_outcodes_select_move()
+ ni_ri_sp_batch_page.click_count_button()
+ ScreenshotTool(page).take_screenshot("batch_parameters_TC_5")
+ ni_ri_sp_batch_page.assert_page_header("Amend RI/SP Batch by Year of Birth")
+ MainMenuPage(page).select_menu_option("Batch Management", "Batch List")
+ ni_ri_sp_batch_page.assert_page_header("Batch List")
+ ni_ri_sp_batch_page.assert_entered_bso_batch_id_and_filterd_row_value(check_digit)
+ ScreenshotTool(page).take_screenshot("batch_parameters_TC_5.1")
diff --git a/tests/ui/ni_ri_sp_batch/test_ni_ri_sp_batch_ui_validation.py b/tests/ui/ni_ri_sp_batch/test_ni_ri_sp_batch_ui_validation.py
new file mode 100644
index 00000000..95a16a0c
--- /dev/null
+++ b/tests/ui/ni_ri_sp_batch/test_ni_ri_sp_batch_ui_validation.py
@@ -0,0 +1,275 @@
+import playwright
+import pytest
+from pages.main_menu import MainMenuPage
+from playwright.sync_api import expect, Page, Playwright
+from pages.ni_ri_sp_batch_page import NiRiSpBatchPage
+from utils.user_tools import UserTools
+from utils.screenshot_tool import ScreenshotTool
+
+
+# TC-1
+@pytest.mark.ni
+def test_ni_bso_user_cannot_create_ri_sp_batch_when_yob_parameter_not_set(
+ page: Page, ni_ri_sp_batch_page: NiRiSpBatchPage
+) -> None:
+ """This test verifies that a BSS NI BSO user is unable to create an RI/SP Batch by Year of Birth
+ when the corresponding BSO parameter 'RI/SP Batch by Year of Birth' is not set.
+ """
+ # Logged into BSO3 as user3(PMA) and select 'Create RI/SP Batch' from the 'batch management' dropdown
+ UserTools().user_login(page, "Ni Only BSO User3 - BS3")
+ MainMenuPage(page).select_menu_option("Batch Management", "Create RI/SP Batch")
+ ni_ri_sp_batch_page.assert_page_header("Create RI/SP Batch by Year of Birth")
+ ni_ri_sp_batch_page.enter_bso_batch_id("PMA106376K")
+ ni_ri_sp_batch_page.enter_date_for_selection(5)
+ ni_ri_sp_batch_page.enter_include_year_of_birth_from("")
+ ni_ri_sp_batch_page.enter_include_year_of_birth_to("")
+ ni_ri_sp_batch_page.click_count_button()
+ # Validating the error message
+ ni_ri_sp_batch_page.assert_text_visible("Earliest Birth Year must be populated")
+ ni_ri_sp_batch_page.assert_text_visible("Latest Birth Year must be populated")
+ ScreenshotTool(page).take_screenshot(
+ "user_cannot_create_ri_sp_batch_when_yob_parameter_not_set"
+ )
+
+
+# TC-2.1
+@pytest.mark.ni
+def test_eng_bso_user_cannot_create_ri_sp_batch_by_year_of_birth(
+ page: Page, ni_ri_sp_batch_page: NiRiSpBatchPage
+) -> None:
+ """Verify 'ENG' user attempt to create ri/sp batch by YOB."""
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Batch Management", "Create RI/SP Batch")
+ ni_ri_sp_batch_page.assert_page_header("Create RI/SP Batch by Year of Birth")
+ ni_ri_sp_batch_page.enter_bso_batch_id("BA1542274F")
+ ni_ri_sp_batch_page.enter_date_for_selection(5)
+ ni_ri_sp_batch_page.enter_include_year_of_birth_from("1955")
+ ni_ri_sp_batch_page.enter_include_year_of_birth_to("1975")
+ ni_ri_sp_batch_page.click_count_button()
+ ni_ri_sp_batch_page.assert_text_visible("Invalid format for BSO Batch ID")
+ ScreenshotTool(page).take_screenshot(
+ "eng_bso_user_cannot_create_ri_sp_batch_by_yob"
+ )
+
+
+# TC-2.2
+@pytest.mark.ni
+def test_ni_bso_user_is_able_to_create_ri_sp_batch_by_year_of_birth(
+ page: Page, ni_ri_sp_batch_page: NiRiSpBatchPage
+) -> None:
+ """Verify ni bso user is able to create ri/sp batch by YOB."""
+ UserTools().user_login(page, "Ni Only BSO User3 - BS3")
+ MainMenuPage(page).select_menu_option("Batch Management", "Create RI/SP Batch")
+ ni_ri_sp_batch_page.assert_page_header("Create RI/SP Batch by Year of Birth")
+ ni_ri_sp_batch_page.enter_bso_batch_id("PMA106376K")
+ ni_ri_sp_batch_page.enter_date_for_selection(5)
+ ni_ri_sp_batch_page.click_count_button()
+ ni_ri_sp_batch_page.assert_page_header("Amend RI/SP Batch by Year of Birth")
+ ScreenshotTool(page).take_screenshot(
+ "ni_bso_user_able_to_create_ri_sp_batch_by_yob"
+ )
+ page.locator("#deleteButton").click()
+ page.wait_for_timeout(3000)
+ page.locator("#confirmButtonInDeletePopupText").click()
+
+
+# TC-3.1
+@pytest.mark.ni
+def test_ni_bso_batch_id_not_unique_warning(
+ page: Page, ni_ri_sp_batch_page: NiRiSpBatchPage
+) -> None:
+ """This test verifies that a BSS NI BSO user sees appropriate warnings
+ when entering an invalid BSO Batch ID that is not unique.
+ """
+ # Logged into BSO3 as user3(PMA) and select 'Create RI/SP Batch' from the 'batch management' dropdown
+ UserTools().user_login(page, "Ni Only BSO User3 - BS3")
+ MainMenuPage(page).select_menu_option("Batch Management", "Create RI/SP Batch")
+ ni_ri_sp_batch_page.assert_page_header("Create RI/SP Batch by Year of Birth")
+ ni_ri_sp_batch_page.validate_batch_id_error(
+ batch_id="PMA999582G",
+ expected_error="This Batch ID already exists on the system.",
+ )
+ ScreenshotTool(page).take_screenshot("batch_id_already_exists_on_the_system")
+
+
+# TC-3.2
+@pytest.mark.ni
+def test_ni_bso_batch_id_invalid_format_warning(
+ page: Page, ni_ri_sp_batch_page: NiRiSpBatchPage
+) -> None:
+ """This test verifies that a BSS NI BSO user sees appropriate warnings
+ when entering an invalid BSO Batch ID that is incorrect format.
+ """
+ # Logged into BSO3 as user3(PMA) and select 'Create RI/SP Batch' from the 'batch management' dropdown
+ UserTools().user_login(page, "Ni Only BSO User3 - BS3")
+ MainMenuPage(page).select_menu_option("Batch Management", "Create RI/SP Batch")
+ ni_ri_sp_batch_page.assert_page_header("Create RI/SP Batch by Year of Birth")
+ ni_ri_sp_batch_page.validate_batch_id_error(
+ batch_id="123INVALID", expected_error="Invalid format for BSO Batch ID"
+ )
+ ScreenshotTool(page).take_screenshot("batch_id_invalid_format_warning")
+
+
+# TC-3.3
+@pytest.mark.ni
+def test_ni_bso_batch_id_check_digit_warning(
+ page: Page, ni_ri_sp_batch_page: NiRiSpBatchPage
+) -> None:
+ """This test verifies that a BSS NI BSO user sees appropriate warnings
+ when entering an invalid BSO Batch ID that is Check digit is incorrect.
+ """
+ # Logged into BSO3 as user3(PMA) and select 'Create RI/SP Batch' from the 'batch management' dropdown
+ UserTools().user_login(page, "Ni Only BSO User3 - BS3")
+ MainMenuPage(page).select_menu_option("Batch Management", "Create RI/SP Batch")
+ ni_ri_sp_batch_page.assert_page_header("Create RI/SP Batch by Year of Birth")
+ ni_ri_sp_batch_page.validate_batch_id_error(
+ batch_id="PMA9168656", expected_error="Invalid format for BSO Batch ID"
+ )
+ ScreenshotTool(page).take_screenshot("batch_id_incorrect_check_digit_warning")
+
+
+# TC-4.1
+@pytest.mark.ni
+def test_ni_date_for_selection_today_shows_warning(
+ page: Page, ni_ri_sp_batch_page: NiRiSpBatchPage
+) -> None:
+ """Verifies that a warning is shown when 'Date for Selection' is set to today's date."""
+ # Logged into BSO3 as user3(PMA) and select 'Create RI/SP Batch' from the 'batch management' dropdown
+ UserTools().user_login(page, "Ni Only BSO User3 - BS3")
+ MainMenuPage(page).select_menu_option("Batch Management", "Create RI/SP Batch")
+ ni_ri_sp_batch_page.assert_page_header("Create RI/SP Batch by Year of Birth")
+ ni_ri_sp_batch_page.enter_bso_batch_id("PMA916865R")
+ ni_ri_sp_batch_page.enter_date_for_selection(0)
+ ni_ri_sp_batch_page.click_count_button()
+ ni_ri_sp_batch_page.assert_text_visible("Date for Selection must be in the future")
+ ScreenshotTool(page).take_screenshot("batch_id_date_for_selection_today_warning")
+
+
+# TC-4.2
+@pytest.mark.ni
+def test_ni_date_for_selection_in_past_shows_warning(
+ page: Page, ni_ri_sp_batch_page: NiRiSpBatchPage
+) -> None:
+ """Verifies that a warning is shown when 'Date for Selection' is set to past date."""
+ # Logged into BSO3 as user3(PMA) and select 'Create RI/SP Batch' from the 'batch management' dropdown
+ UserTools().user_login(page, "Ni Only BSO User3 - BS3")
+ MainMenuPage(page).select_menu_option("Batch Management", "Create RI/SP Batch")
+ ni_ri_sp_batch_page.assert_page_header("Create RI/SP Batch by Year of Birth")
+ ni_ri_sp_batch_page.enter_bso_batch_id("PMA916865R")
+ ni_ri_sp_batch_page.enter_date_for_selection(-5)
+ ni_ri_sp_batch_page.click_count_button()
+ ni_ri_sp_batch_page.assert_text_visible("Date for Selection must be in the future")
+ ScreenshotTool(page).take_screenshot(
+ "batch_id_date_for_selection_must_be_in_the_future_warning"
+ )
+
+
+# TC-5.1
+@pytest.mark.ni
+def test_ni_bso_user_receives_warning_for_invalid_yob_range_1954_1975(
+ page: Page, ni_ri_sp_batch_page: NiRiSpBatchPage
+) -> None:
+ """Verify that a warning is shown when YOB From = 1954 and To = 1975 (71 to 50 years ago)."""
+ UserTools().user_login(page, "Ni Only BSO User3 - BS3")
+ MainMenuPage(page).select_menu_option("Batch Management", "Create RI/SP Batch")
+ ni_ri_sp_batch_page.assert_page_header("Create RI/SP Batch by Year of Birth")
+ ni_ri_sp_batch_page.enter_year_range_and_count("1954", "1975")
+ ni_ri_sp_batch_page.assert_text_visible(
+ "Earliest Birth Year must be between 1955 and 1975"
+ )
+ ScreenshotTool(page).take_screenshot(
+ "Earliest_warning_for_invalid_yob_range_1954_1975"
+ )
+
+
+# TC-5.2
+@pytest.mark.ni
+def test_ni_bso_user_receives_warning_for_invalid_yob_range_1954_1974(
+ page: Page, ni_ri_sp_batch_page: NiRiSpBatchPage
+) -> None:
+ """Verify that a warning is shown when YOB From = 1954 and To = 1974 (71 to 51 years ago)."""
+ UserTools().user_login(page, "Ni Only BSO User3 - BS3")
+ MainMenuPage(page).select_menu_option("Batch Management", "Create RI/SP Batch")
+ ni_ri_sp_batch_page.assert_page_header("Create RI/SP Batch by Year of Birth")
+ ni_ri_sp_batch_page.enter_year_range_and_count("1954", "1974")
+ ni_ri_sp_batch_page.assert_text_visible(
+ "Earliest Birth Year must be between 1955 and 1975"
+ )
+ ScreenshotTool(page).take_screenshot(
+ "Earliest_warning_for_invalid_yob_range_1954_1974"
+ )
+
+
+# TC-6.1
+@pytest.mark.ni
+def test_ni_bso_user_receives_warning_for_invalid_yob_range_1955_1976(
+ page: Page, ni_ri_sp_batch_page: NiRiSpBatchPage
+) -> None:
+ """Verify that a warning is shown when YOB From = 1955 and To = 1976 (70 to 49 years ago)."""
+ UserTools().user_login(page, "Ni Only BSO User3 - BS3")
+ MainMenuPage(page).select_menu_option("Batch Management", "Create RI/SP Batch")
+ ni_ri_sp_batch_page.assert_page_header("Create RI/SP Batch by Year of Birth")
+ ni_ri_sp_batch_page.enter_year_range_and_count("1955", "1976")
+ ni_ri_sp_batch_page.assert_text_visible(
+ "Latest Birth Year must be between 1955 and 1975"
+ )
+ ScreenshotTool(page).take_screenshot(
+ "Latest_warning_for_invalid_yob_range_1955_1976"
+ )
+
+
+# TC-6.2
+@pytest.mark.ni
+def test_ni_bso_user_receives_warning_for_invalid_yob_range_1956_1976(
+ page: Page, ni_ri_sp_batch_page: NiRiSpBatchPage
+) -> None:
+ """Verify that a warning is shown when YOB From = 1956 and To = 1976 (69 to 49 years ago)."""
+ UserTools().user_login(page, "Ni Only BSO User3 - BS3")
+ MainMenuPage(page).select_menu_option("Batch Management", "Create RI/SP Batch")
+ ni_ri_sp_batch_page.assert_page_header("Create RI/SP Batch by Year of Birth")
+ ni_ri_sp_batch_page.enter_year_range_and_count("1956", "1976")
+ ni_ri_sp_batch_page.assert_text_visible(
+ "Latest Birth Year must be between 1955 and 1975"
+ )
+ ScreenshotTool(page).take_screenshot(
+ "Latest_warning_for_invalid_yob_range_1956_1976"
+ )
+
+
+# TC-7
+@pytest.mark.ni
+@pytest.mark.nitest
+def test_ni_bso_user_receives_warning_for_invalid_yob_range_1955_1954(
+ page: Page, ni_ri_sp_batch_page: NiRiSpBatchPage
+) -> None:
+ """Verify that a warning is shown when YOB From = 1955 and To = 1954 (70 to 69 years ago)."""
+ UserTools().user_login(page, "Ni Only BSO User3 - BS3")
+ MainMenuPage(page).select_menu_option("Batch Management", "Create RI/SP Batch")
+ ni_ri_sp_batch_page.assert_page_header("Create RI/SP Batch by Year of Birth")
+ ni_ri_sp_batch_page.enter_year_range_and_count("1955", "1954")
+ ni_ri_sp_batch_page.assert_text_visible(
+ "Earliest Birth Year must not be after the Latest Birth Year"
+ )
+ ni_ri_sp_batch_page.assert_text_visible(
+ "Latest Birth Year must be between 1955 and 1975"
+ )
+ ScreenshotTool(page).take_screenshot("warning_for_invalid_yob_range_1955_1954")
+
+
+# TC-8
+@pytest.mark.ni
+def test_ni_bso_user_receives_warning_for_invalid_month_of_birth_range(
+ page: Page, ni_ri_sp_batch_page: NiRiSpBatchPage
+) -> None:
+ """Verify that a warning is shown when 'Include Month Of Birth' value is set to 13."""
+ UserTools().user_login(page, "Ni Only BSO User3 - BS3")
+ MainMenuPage(page).select_menu_option("Batch Management", "Create RI/SP Batch")
+ ni_ri_sp_batch_page.assert_page_header("Create RI/SP Batch by Year of Birth")
+ ni_ri_sp_batch_page.enter_bso_batch_id("PMA916865R")
+ ni_ri_sp_batch_page.enter_date_for_selection(5)
+ ni_ri_sp_batch_page.enter_include_month_of_birth_to("13")
+ ni_ri_sp_batch_page.click_count_button()
+ ni_ri_sp_batch_page.assert_text_visible(
+ "Latest Birth Month must be between 1 and 12"
+ )
+ ScreenshotTool(page).take_screenshot("warning_for_invalid_month_of_birth_range")
diff --git a/tests/ui/rlp_screening_cohort_list/test_amend_rlp_screening_cohort_list_by_gp.py b/tests/ui/rlp_screening_cohort_list/test_amend_rlp_screening_cohort_list_by_gp.py
new file mode 100644
index 00000000..da75efb4
--- /dev/null
+++ b/tests/ui/rlp_screening_cohort_list/test_amend_rlp_screening_cohort_list_by_gp.py
@@ -0,0 +1,408 @@
+import playwright
+import pytest
+
+# from conftest import db_util
+from pages.main_menu import MainMenuPage
+from pages.rlp_cohort_list_page import CohortListPage
+from playwright.sync_api import expect, Page, Playwright
+from datetime import datetime
+from pages.rlp_location_list_page import ScreeningLocationListPage
+from utils.test_helpers import generate_random_string
+from utils import test_helpers
+from utils.user_tools import UserTools
+
+
+# test to create the unit test data
+@pytest.mark.amendcohortgp
+def test_check_and_create_unit_test_data(
+ page: Page, rlp_cohort_list_page: CohortListPage
+) -> None:
+ """creating unit test data for User2 BS2"""
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "Read Only BSO User2 - BS2")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Unit List")
+ unit_names = ["Batman", "Captain"]
+ for unit_name in unit_names:
+ rlp_cohort_list_page.create_unit_if_not_exists(unit_name)
+
+
+#### Test_15
+@pytest.mark.amendcohortgp
+def test_try_amend_cohort_by_dblclick_and_invoke_pencil_icon(
+ page: Page, rlp_cohort_list_page: CohortListPage
+) -> None:
+ """User invokes the Edit Cohort functionality by Double click list entry and Invoke Pencil Icon"""
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+
+ # creating cohort using method with hardcoded attendence and screening unit values
+ cohort_name = f"cohort_name-{datetime.now()}"
+ location_name = "Poundland Car Park - Alberta Retail Park"
+ rlp_cohort_list_page.create_cohort(cohort_name, location_name)
+
+ # Filter the newly created cohort and double clicking on the cohort to amend(testing double click)
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(cohort_name)
+ rlp_cohort_list_page.dbl_click_on_filtered_cohort()
+ expect(page.get_by_text("Amend Screening Cohort")).to_be_visible()
+ # cancelling the amend
+ rlp_cohort_list_page.click_amend_cohort_cancel_button()
+
+ # Filter the newly created cohort and clicking on the cohort pencil to amend(testing pencil icon)
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(cohort_name)
+ rlp_cohort_list_page.click_filtered_cohort_pencil_icon()
+ expect(page.get_by_text("Amend Screening Cohort")).to_be_visible()
+
+
+#### Test_16 positive data validation
+@pytest.mark.parametrize("input_length", [3, 100])
+@pytest.mark.amendcohortgp
+def test_amend_cohort_name_with_valid_data(
+ page: Page, rlp_cohort_list_page: CohortListPage, input_length
+) -> None:
+ """User amends data in the Screening Cohort field with valid data"""
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ # creating cohort
+ cohort_name = f"cohort_name-{datetime.now()}"
+ location_name = "Poundland Car Park - Alberta Retail Park"
+ unit_name = "Batman"
+ rlp_cohort_list_page.create_cohort_without_gp(cohort_name, location_name, unit_name)
+ # Filter the newly created cohort and double clicking on the cohort to amend(testing double click)
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(cohort_name)
+ rlp_cohort_list_page.dbl_click_on_filtered_cohort()
+ # Amending cohort name field
+ amend_cohort_name = generate_random_string(input_length)
+ rlp_cohort_list_page.enter_amend_screening_cohort_name(amend_cohort_name)
+ rlp_cohort_list_page.click_amend_save_btn()
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(amend_cohort_name)
+ filterd_amend_name = rlp_cohort_list_page.value_of_filtered_cohort_name()
+ assert amend_cohort_name == filterd_amend_name
+
+
+## Test_16 negative field data validation
+@pytest.mark.amendcohortgp
+@pytest.mark.parametrize(
+ "amend_name, expected_message",
+ [
+ ("$%&@", "Screening Cohort Name contains invalid characters"),
+ ("cd", "The Screening Cohort Name you entered is too short"),
+ (" ", "Screening Cohort Name must be populated"),
+ ("Hadley", "Screening Cohort Name is already in use by another cohort"),
+ ],
+)
+def test_amend_screening_cohort_with_invalid_data(
+ page: Page, rlp_cohort_list_page: CohortListPage, amend_name, expected_message
+) -> None:
+ """Negative test - User amends data in the Screening Cohort field with inalidv data"""
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ # creating cohort
+ cohort_name = f"cohort_name-{datetime.now()}"
+ location_name = "Poundland Car Park - Alberta Retail Park"
+ unit_name = "Batman"
+ rlp_cohort_list_page.create_cohort_without_gp(cohort_name, location_name, unit_name)
+ # Filter the newly created cohort and double clicking on the cohort to amend(testing double click)
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(cohort_name)
+ rlp_cohort_list_page.dbl_click_on_filtered_cohort()
+ # Amending cohort name field
+ rlp_cohort_list_page.enter_amend_screening_cohort_name(amend_name)
+ rlp_cohort_list_page.click_amend_save_btn()
+ # Assert that the correct error message is displayed based on invalid_data
+ expect(page.get_by_text(expected_message)).to_be_visible()
+
+
+#### Test_17 positive field data validation for amend Expected Attendance Rate
+@pytest.mark.amendcohortgp
+@pytest.mark.parametrize("input_value", ["0.0", "100.0"])
+def test_amend_expected_attendance_rate_valid_data(
+ page: Page, rlp_cohort_list_page: CohortListPage, input_value
+) -> None:
+ """Positive test - The User is able to select and commit a change to Expected Attendance Rate - integer values 0.00 - 100.0"""
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ cohort_name = f"amend_attendance-{datetime.now()}"
+ location_name = "Poundland Car Park - Alberta Retail Park"
+ rlp_cohort_list_page.create_cohort(cohort_name, location_name)
+ # Filter the newly created cohort and double clicking on the cohort to amend(testing double click)
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(cohort_name)
+ rlp_cohort_list_page.dbl_click_on_filtered_cohort()
+ # Test data
+ rlp_cohort_list_page.enter_amend_expected_attendance_rate(input_value)
+ rlp_cohort_list_page.click_amend_save_btn()
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(cohort_name)
+ rlp_cohort_list_page.dbl_click_on_filtered_cohort()
+ filtered_amend_attendance = rlp_cohort_list_page.value_of_filtered_attendance()
+ assert input_value == filtered_amend_attendance
+
+
+#### Test_17 negative test for amend Expected Attendance Rate field
+@pytest.mark.amendcohortgp
+@pytest.mark.parametrize(
+ "amend_attendance_rate, expected_message",
+ [
+ ("cd", "Invalid value"),
+ (" ", "Expected Attendance Rate must be between 0 and 100"),
+ ],
+)
+def test_amend_expected_attendance_rate_invalid_data(
+ page: Page,
+ rlp_cohort_list_page: CohortListPage,
+ amend_attendance_rate,
+ expected_message,
+) -> None:
+ """Negative test - User amends data in the Expected Attendance Rate (%) field - Non integer value and Null"""
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ cohort_name = f"amend_attendance-{datetime.now()}"
+ location_name = "Poundland Car Park - Alberta Retail Park"
+ rlp_cohort_list_page.create_cohort(cohort_name, location_name)
+ # Filter the newly created cohort and double clicking on the cohort to amend(testing double click)
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(cohort_name)
+ rlp_cohort_list_page.dbl_click_on_filtered_cohort()
+ rlp_cohort_list_page.enter_amend_expected_attendance_rate(amend_attendance_rate)
+ rlp_cohort_list_page.click_amend_save_btn()
+ # Assert that the correct error message is displayed based on invalid_data
+ expect(page.get_by_text(expected_message)).to_be_visible()
+
+
+#### Test_18
+@pytest.mark.amendcohortgp
+def test_amend_cohort_location(
+ page: Page, rlp_cohort_list_page: CohortListPage
+) -> None:
+ """The correct list of Locations available to this user in this BSO, are displayed correctly,
+ The User is able to select and commit a change to Location"""
+ # Logged into BSS_SO1 location_list to create location data
+
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Location List")
+ location_name = f"cohort_location-{datetime.now()}"
+ ScreeningLocationListPage(page).create_screening_location(location_name)
+
+ # extracting the location count
+ location_list_count = rlp_cohort_list_page.extract_location_paging_list_count()
+
+ # Logged into BSS_SO1
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ cohort_name = f"cohort_name-{datetime.now()}"
+ unit_name = "Batman"
+ rlp_cohort_list_page.create_cohort_without_gp(cohort_name, location_name, unit_name)
+ page.wait_for_timeout(3000)
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(cohort_name)
+ rlp_cohort_list_page.dbl_click_on_filtered_cohort()
+
+ # extracting the drop down location count
+ dropdown_count = rlp_cohort_list_page.number_of_location_dropdown_count()
+ assert location_list_count == dropdown_count
+ amend_location_name = "Aldi - Caldecott County Retail Park"
+ rlp_cohort_list_page.select_amend_screening_location_dropdown(amend_location_name)
+ rlp_cohort_list_page.click_amend_save_btn()
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(cohort_name)
+ expect(page.get_by_text("Aldi - Caldecott County Retail Park")).to_be_visible()
+
+
+#### Test_19
+@pytest.mark.amendcohortgp
+def test_amend_cohort_unit_list(
+ page: Page, rlp_cohort_list_page: CohortListPage
+) -> None:
+ """The correct list of Active only Units available to this user in this BSO, are displayed correctly,
+ The User is able to select and commit a change to Default Unit"""
+ # Logged into BSS_SO1 unit_list to create unit data
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Unit List")
+ unit_name = f"cohort_unit-{datetime.now()}"
+ rlp_cohort_list_page.create_unit_for_test_data(unit_name)
+ # extracting the unit count
+ unit_list_count = rlp_cohort_list_page.extract_paging_unit_list_count_active_only()
+
+ # Logged into BSS_SO1
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ cohort_name = f"cohort_name-{datetime.now()}"
+ location_name = "Aldi - Caldecott County Retail Park"
+ rlp_cohort_list_page.create_cohort_without_gp(cohort_name, location_name, unit_name)
+ page.wait_for_timeout(3000)
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(cohort_name)
+ rlp_cohort_list_page.dbl_click_on_filtered_cohort()
+
+ # extracting the drop down location count
+ dropdown_count = rlp_cohort_list_page.number_of_unit_dropdown_count()
+ assert unit_list_count == dropdown_count
+ amend_unit_name = "Batman"
+ rlp_cohort_list_page.select_amend_screening_unit_dropdown(amend_unit_name)
+ rlp_cohort_list_page.click_amend_save_btn()
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(cohort_name)
+ expect(page.get_by_text("Batman")).to_be_visible()
+
+
+#### Test_20
+@pytest.mark.amendcohortgp
+def test_amend_added_gp_practices_are_visible(
+ page: Page, rlp_cohort_list_page: CohortListPage
+) -> None:
+ """Amend test - User Selects another GP Practice from the 'All available GP Practices' List by invoking the 'Add' PB"""
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+
+ cohort_name = f"cohort_name-{datetime.now()}"
+ location_name = "Aldi - Caldecott County Retail Park"
+ rlp_cohort_list_page.create_cohort(cohort_name, location_name)
+
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(cohort_name)
+ rlp_cohort_list_page.dbl_click_on_filtered_cohort()
+ page.wait_for_timeout(3000)
+ # including gp practice
+ rlp_cohort_list_page.click_select_gp_practices_btn()
+ rlp_cohort_list_page.enter_gp_code_field("A00002")
+ rlp_cohort_list_page.click_add_btn_gp_practices_to_include()
+ rlp_cohort_list_page.click_done_btn_gp_practices_include_popup()
+ rlp_cohort_list_page.click_select_gp_practices_btn()
+ rlp_cohort_list_page.enter_gp_code_field("A00003")
+ rlp_cohort_list_page.click_add_btn_gp_practices_to_include()
+ rlp_cohort_list_page.click_done_btn_gp_practices_include_popup()
+ rlp_cohort_list_page.click_create_screening_cohort_save_btn()
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(cohort_name)
+ rlp_cohort_list_page.dbl_click_on_filtered_cohort()
+ included_gp_practices = page.locator(
+ "//table[@id='practicesToIncludeList']//tr//td[2]"
+ ).count()
+ assert included_gp_practices == 3
+ # Locate all GP practice codes in the table
+ gp_practice_codes = page.locator(
+ "//div[@id='practicesToIncludeList_wrapper']//tr//td[2]"
+ ).all_inner_texts()
+ # Assert that "A00002" and "A00003" are in the list of GP practice codes
+ assert (
+ "A00002" in gp_practice_codes
+ ), "A00002 was not found in the included GP practices."
+ assert (
+ "A00003" in gp_practice_codes
+ ), "A00002 was not found in the included GP practices."
+ assert (
+ "A00005" in gp_practice_codes
+ ), "A00003 was not found in the included GP practices."
+
+
+#### Test_21
+@pytest.mark.amendcohortgp
+def test_amend_remove_gp_practices(
+ page: Page, rlp_cohort_list_page: CohortListPage
+) -> None:
+ """
+ The 'Amend Screening Cohort' screen is displayed for the correct Cohort
+ Cohort has GP Practices selected in the 'Included GP Practices List'
+ User then selects to Remove GP Practices by invoking the 'Remove' button
+ """
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+
+ cohort_name = f"cohort_name-{datetime.now()}"
+ location_name = "Aldi - Caldecott County Retail Park"
+ rlp_cohort_list_page.create_cohort(cohort_name, location_name)
+
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(cohort_name)
+ rlp_cohort_list_page.dbl_click_on_filtered_cohort()
+ page.wait_for_timeout(3000)
+ # including gp practice
+ rlp_cohort_list_page.click_select_gp_practices_btn()
+ rlp_cohort_list_page.enter_gp_code_field("A00002")
+ rlp_cohort_list_page.click_add_btn_gp_practices_to_include()
+ rlp_cohort_list_page.click_done_btn_gp_practices_include_popup()
+ rlp_cohort_list_page.click_select_gp_practices_btn()
+ rlp_cohort_list_page.enter_gp_code_field("A00003")
+ rlp_cohort_list_page.click_add_btn_gp_practices_to_include()
+ rlp_cohort_list_page.click_done_btn_gp_practices_include_popup()
+ rlp_cohort_list_page.click_create_screening_cohort_save_btn()
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(cohort_name)
+ rlp_cohort_list_page.dbl_click_on_filtered_cohort()
+ included_gp_practices = page.locator(
+ "//table[@id='practicesToIncludeList']//tr//td[2]"
+ ).count()
+ assert included_gp_practices == 3
+
+ # removing one included gp practice
+ rlp_cohort_list_page.click_remove_button_by_gp_code("A00002")
+ removed_included_gp_practices = page.locator(
+ "//table[@id='practicesToIncludeList']//tr//td[2]"
+ ).count()
+ assert removed_included_gp_practices == 2
+
+ # removing one included gp practice
+ rlp_cohort_list_page.click_remove_button_by_gp_code("A00003")
+ removed_included_gp_practices = page.locator(
+ "//table[@id='practicesToIncludeList']//tr//td[2]"
+ ).count()
+ assert removed_included_gp_practices == 1
+
+ # removing last of the included gp practice
+ rlp_cohort_list_page.click_remove_button_by_gp_code("A00005")
+ removed_included_gp_practices = page.locator(
+ "//table[@id='practicesToIncludeList']//tr//td[2]"
+ ).count()
+ assert removed_included_gp_practices == 0
+
+
+#### Test_22
+@pytest.mark.amendcohortgp
+def test_amend_cancel_adding_gp_practices(
+ page: Page, rlp_cohort_list_page: CohortListPage
+) -> None:
+ """
+ Screening Cohort List is displayed,
+ User invokes 'Add by Practice' pb and invokes 'Cancel' pb
+ User is returned to the 'Screening Cohort List' Screen
+ """
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+
+ cohort_name = f"cohort_name-{datetime.now()}"
+ location_name = "Aldi - Caldecott County Retail Park"
+ unit_name = "Batman"
+ rlp_cohort_list_page.create_cohort_without_gp(cohort_name, location_name, unit_name)
+
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(cohort_name)
+ rlp_cohort_list_page.dbl_click_on_filtered_cohort()
+ page.wait_for_timeout(3000)
+ # clicking on amend page cancel button
+ rlp_cohort_list_page.click_amend_cohort_cancel_button()
+ expect(page.get_by_text("Screening cohort list", exact=True)).to_be_visible()
+
+
+#### Test_23
+@pytest.mark.amendcohortgp
+def test_amend_cohort_name_avilable_for_user2(
+ page: Page, rlp_cohort_list_page: CohortListPage, context
+) -> None:
+ """BSO specific GP Practice Cohort amendments are available to the other Users within the same BSO"""
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+
+ # creating cohort using method with hardcoded attendence
+ cohort_name = f"cohort_name-{datetime.now()}"
+ location_name = "Poundland Car Park - Alberta Retail Park"
+ unit_name = "Batman"
+ rlp_cohort_list_page.create_cohort_without_gp(cohort_name, location_name, unit_name)
+ # Filter the newly created cohort and double clicking on the cohort to amend(testing double click)
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(cohort_name)
+ rlp_cohort_list_page.dbl_click_on_filtered_cohort()
+
+ # Test data to amend cohort name
+ amend_cohort_name = f"amend_user2-{datetime.now()}"
+ rlp_cohort_list_page.enter_amend_screening_cohort_name(amend_cohort_name)
+ rlp_cohort_list_page.click_amend_save_btn()
+ context.clear_cookies()
+ # Logged into BSS_SO1 as user2
+ UserTools().user_login(page, "BSO User2 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(amend_cohort_name)
+ filterd_amend_name = rlp_cohort_list_page.value_of_filtered_cohort_name()
+ assert amend_cohort_name == filterd_amend_name
diff --git a/tests/ui/rlp_screening_cohort_list/test_amend_rlp_screening_cohort_list_by_outcode.py b/tests/ui/rlp_screening_cohort_list/test_amend_rlp_screening_cohort_list_by_outcode.py
new file mode 100644
index 00000000..a0b86a6c
--- /dev/null
+++ b/tests/ui/rlp_screening_cohort_list/test_amend_rlp_screening_cohort_list_by_outcode.py
@@ -0,0 +1,336 @@
+import playwright
+import pytest
+
+# from conftest import db_util
+from pages.main_menu import MainMenuPage
+from pages.rlp_cohort_list_page import CohortListPage
+from playwright.sync_api import expect, Page, Playwright
+from datetime import datetime
+from pages.rlp_location_list_page import ScreeningLocationListPage
+from pages.rlp_unit_list_page import ScreeningUnitListPage
+from utils.test_helpers import generate_random_string
+from utils import test_helpers
+from utils.user_tools import UserTools
+
+
+# test to create the unit test data
+@pytest.mark.amendcohortoutcode
+def test_check_and_create_unit_test_data(
+ page: Page, rlp_cohort_list_page: CohortListPage
+):
+ """creating unit test data for User2 BS2"""
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "Read Only BSO User2 - BS2")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Unit List")
+ unit_names = ["Batman", "Captain"]
+ for unit_name in unit_names:
+ rlp_cohort_list_page.create_unit_if_not_exists(unit_name)
+
+
+# test to create the location data
+@pytest.mark.amendcohortoutcode
+def test_check_and_create_location_test_data_for_outcode(
+ page: Page, rlp_cohort_list_page: CohortListPage
+):
+ """
+ Random test to generate location test data for User2 BS2
+ """
+ # Logged into BSS_SO2 User2
+ UserTools().user_login(page, "Read Only BSO User2 - BS2")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Location List")
+ locations = [
+ "Aldi - Caldecott County Retail Park",
+ "Poundland Car Park - Alberta Retail Park",
+ ]
+ for location in locations:
+ ScreeningLocationListPage(page).create_location_if_not_exists(location)
+
+
+# creating cohort for below test
+@pytest.mark.amendcohortoutcode
+def test_create_screening_cohort_outcode_test_data(
+ page: Page, rlp_cohort_list_page: CohortListPage
+) -> None:
+ """
+ Test to create a test data
+ """
+ # Logged into BSS_SO2 user2
+ UserTools().user_login(page, "Read Only BSO User2 - BS2")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ # Test data
+ cohort_name = "Hadley"
+ attendance_rate = "25"
+ location_name = "Poundland Car Park - Alberta Retail Park"
+ unit_name = "Batman"
+ # creating cohort using create cohort method
+ rlp_cohort_list_page.create_cohort_outcode_without_gp(
+ cohort_name, str(attendance_rate), location_name, unit_name
+ )
+
+
+## Test_35
+@pytest.mark.amendcohortoutcode
+def test_outcode_try_amend_cohort_by_dblclick_and_invoke_pencil_icon(
+ page: Page, rlp_cohort_list_page: CohortListPage
+):
+ """
+ Trying to amend cohort using the methods - double clicking on the cohort and invoking the pencil icon
+ """
+ # Logged into BSS_SO2 user2
+ UserTools().user_login(page, "Read Only BSO User2 - BS2")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ # Test data
+ cohort_name = f"cohort_name-{datetime.now()}"
+ location_name = "Poundland Car Park - Alberta Retail Park"
+ unit_name = "Batman"
+ attendance_rate = "25"
+ # creating cohort using create cohort method
+ rlp_cohort_list_page.create_cohort_outcode_without_gp(
+ cohort_name, str(attendance_rate), location_name, unit_name
+ )
+
+ # Filter the newly created cohort and double clicking on the cohort to amend(testing double click)
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(cohort_name)
+ rlp_cohort_list_page.dbl_click_on_filtered_cohort()
+ expect(page.get_by_text("Amend Screening Cohort")).to_be_visible()
+ # cancelling the amend
+ rlp_cohort_list_page.click_amend_cohort_cancel_button()
+
+ # Filter the newly created cohort and clicking on the cohort pencil to amend(testing pencil icon)
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(cohort_name)
+ rlp_cohort_list_page.click_filtered_cohort_pencil_icon()
+ expect(page.get_by_text("Amend Screening Cohort")).to_be_visible()
+
+
+## Test_36 positive data validation
+@pytest.mark.amendcohortoutcode
+@pytest.mark.parametrize("input_length", [3, 100])
+def test_outcode_amend_cohort_name_with_valid_data(
+ page: Page, rlp_cohort_list_page: CohortListPage, input_length
+):
+ """
+ Creating a cohort using outcode to amend the name field using the min and max length
+ """
+ # Logged into BSS_SO2 user2
+ UserTools().user_login(page, "Read Only BSO User2 - BS2")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ # creating cohort
+ cohort_name = f"cohort_name-{datetime.now()}"
+ attendance_rate = 25
+ location_name = "Poundland Car Park - Alberta Retail Park"
+ unit_name = "Batman"
+ rlp_cohort_list_page.create_cohort_outcode_without_gp(
+ cohort_name, str(attendance_rate), location_name, unit_name
+ )
+ # Filter the newly created cohort and double clicking on the cohort to amend(testing double click)
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(cohort_name)
+ rlp_cohort_list_page.dbl_click_on_filtered_cohort()
+ # Amending the cohort name
+ amend_cohort_name = generate_random_string(input_length)
+ rlp_cohort_list_page.enter_amend_screening_cohort_name(amend_cohort_name)
+ rlp_cohort_list_page.click_amend_save_btn()
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(amend_cohort_name)
+ filterd_amend_name = rlp_cohort_list_page.value_of_filtered_cohort_name()
+ assert amend_cohort_name == filterd_amend_name
+
+
+## Test_36 negative field data validation
+@pytest.mark.amendcohortoutcode
+@pytest.mark.parametrize(
+ "amend_name, expected_message",
+ [
+ ("$%&@", "Screening Cohort Name contains invalid characters"),
+ ("cd", "The Screening Cohort Name you entered is too short"),
+ (" ", "Screening Cohort Name must be populated"),
+ ("Hadley", "Screening Cohort Name is already in use by another cohort"),
+ ],
+)
+def test_outcode_amend_screening_cohort_with_invalid_data(
+ page: Page, rlp_cohort_list_page: CohortListPage, amend_name, expected_message
+):
+ """Test to verify error messages for outcode amend cohort with invalid data "$%&@", " "-empty string, Name is already in use, too short"""
+ # Logged into BSS_SO2 user2
+ UserTools().user_login(page, "Read Only BSO User2 - BS2")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ # creating cohort
+ cohort_name = f"cohort_name-{datetime.now()}"
+ attendance_rate = 25
+ location_name = "Poundland Car Park - Alberta Retail Park"
+ unit_name = "Batman"
+ rlp_cohort_list_page.create_cohort_outcode_without_gp(
+ cohort_name, str(attendance_rate), location_name, unit_name
+ )
+ # Filter the newly created cohort and double clicking on the cohort to amend(testing double click)
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(cohort_name)
+ rlp_cohort_list_page.dbl_click_on_filtered_cohort()
+ # Amending cohort name field
+ rlp_cohort_list_page.enter_amend_screening_cohort_name(amend_name)
+ rlp_cohort_list_page.click_amend_save_btn()
+ # Assert that the correct error message is displayed based on invalid_data
+ expect(page.get_by_text(expected_message)).to_be_visible()
+
+
+## Test_37 positive field data validation for amend Expected Attendance Rate
+@pytest.mark.amendcohortoutcode
+@pytest.mark.parametrize("input_value", ["0.0", "100.0"])
+def test_outcode_amend_expected_attendance_rate_valid_data(
+ page: Page, rlp_cohort_list_page: CohortListPage, input_value
+) -> None:
+ """outcode amend expected attendence rate valid data"""
+ # Logged into BSS_SO2 user2
+ UserTools().user_login(page, "Read Only BSO User2 - BS2")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ # creating cohort
+ cohort_name = f"cohort_name-{datetime.now()}"
+ attendance_rate = 25
+ location_name = "Poundland Car Park - Alberta Retail Park"
+ unit_name = "Batman"
+ rlp_cohort_list_page.create_cohort_outcode_without_gp(
+ cohort_name, str(attendance_rate), location_name, unit_name
+ )
+ # Filter the newly created cohort and double clicking on the cohort to amend(testing double click)
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(cohort_name)
+ rlp_cohort_list_page.dbl_click_on_filtered_cohort()
+ # Amend attendace rate
+ rlp_cohort_list_page.enter_amend_expected_attendance_rate(input_value)
+ rlp_cohort_list_page.click_amend_save_btn()
+
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(cohort_name)
+ rlp_cohort_list_page.dbl_click_on_filtered_cohort()
+ filtered_amend_attendance = rlp_cohort_list_page.value_of_filtered_attendance()
+ assert input_value == filtered_amend_attendance
+
+
+#### Test_37 negative test for amend Expected Attendance Rate field
+@pytest.mark.amendcohortoutcode
+@pytest.mark.parametrize(
+ "amend_attendance_rate, expected_message",
+ [
+ ("cd", "Invalid value"),
+ (" ", "Expected Attendance Rate must be between 0 and 100"),
+ ],
+)
+def test_outcode_amend_expected_attendance_rate_invalid_data(
+ page: Page,
+ rlp_cohort_list_page: CohortListPage,
+ amend_attendance_rate,
+ expected_message,
+):
+ """Test to verify error messages for outcode amend cohort with invalid data "$%&@", " "-empty string, Name is already in use, too short"""
+ # Logged into BSS_SO2 user2
+ UserTools().user_login(page, "Read Only BSO User2 - BS2")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ # creating cohort
+ cohort_name = f"cohort_name-{datetime.now()}"
+ attendance_rate = 25
+ location_name = "Poundland Car Park - Alberta Retail Park"
+ unit_name = "Batman"
+ rlp_cohort_list_page.create_cohort_outcode_without_gp(
+ cohort_name, str(attendance_rate), location_name, unit_name
+ )
+ # Filter the newly created cohort and double clicking on the cohort to amend(testing double click)
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(cohort_name)
+ rlp_cohort_list_page.dbl_click_on_filtered_cohort()
+ # Amend attendace rate
+ rlp_cohort_list_page.enter_amend_expected_attendance_rate(amend_attendance_rate)
+ rlp_cohort_list_page.click_amend_save_btn()
+ # Assert that the correct error message is displayed based on invalid_data
+ expect(page.get_by_text(expected_message)).to_be_visible()
+
+
+#### Test_40
+@pytest.mark.amendcohortoutcode
+def test_outcode_amend_included_outcodes_are_visible(
+ page: Page, rlp_cohort_list_page: CohortListPage
+):
+ """created a cohort, amened the cohort by including the outcodes"""
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "Read Only BSO User2 - BS2")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ # creating cohort
+ cohort_name = f"cohort_name-{datetime.now()}"
+ attendance_rate = 25
+ location_name = "Poundland Car Park - Alberta Retail Park"
+ unit_name = "Batman"
+ out_code1 = "EX1"
+ out_code2 = "EX2"
+ rlp_cohort_list_page.create_cohort_outcode_without_gp(
+ cohort_name, str(attendance_rate), location_name, unit_name
+ )
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(cohort_name)
+ rlp_cohort_list_page.dbl_click_on_filtered_cohort()
+ page.wait_for_timeout(3000)
+ rlp_cohort_list_page.click_select_outcodes_btn()
+ rlp_cohort_list_page.enter_outcode_filter(out_code1)
+ rlp_cohort_list_page.click_add_btn_to_select_outcode(out_code1)
+ rlp_cohort_list_page.click_done_btn_gp_practices_include_popup()
+ included_outcodes = page.locator(
+ "//table[@id='practicesToIncludeList']//tr//td[2]"
+ ).count()
+ assert included_outcodes == 1
+ rlp_cohort_list_page.click_select_outcodes_btn()
+ rlp_cohort_list_page.enter_outcode_filter(out_code2)
+ rlp_cohort_list_page.click_add_btn_to_select_outcode(out_code2)
+ rlp_cohort_list_page.click_done_btn_gp_practices_include_popup()
+ included_outcodes = page.locator(
+ "//table[@id='practicesToIncludeList']//tr//td[2]"
+ ).count()
+ assert included_outcodes == 2
+
+
+## Test_41
+@pytest.mark.amendcohortoutcode
+def test_outcode_amend_remove_added_outcodes(
+ page: Page, rlp_cohort_list_page: CohortListPage
+):
+ """
+ created a cohort, amened the cohort by including the outcodes, and removed the outcodes
+ """
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "Read Only BSO User2 - BS2")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ # creating cohort
+ cohort_name = f"cohort_name-{datetime.now()}"
+ attendance_rate = 25
+ location_name = "Poundland Car Park - Alberta Retail Park"
+ unit_name = "Batman"
+ out_code1 = "EX1"
+ out_code2 = "EX2"
+ out_code3 = "EX3"
+ rlp_cohort_list_page.create_cohort_outcode_without_gp(
+ cohort_name, str(attendance_rate), location_name, unit_name
+ )
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(cohort_name)
+ rlp_cohort_list_page.dbl_click_on_filtered_cohort()
+ page.wait_for_timeout(3000)
+ rlp_cohort_list_page.click_select_outcodes_btn()
+ rlp_cohort_list_page.enter_outcode_filter(out_code1)
+ rlp_cohort_list_page.click_add_btn_to_select_outcode(out_code1)
+ rlp_cohort_list_page.enter_outcode_filter(out_code2)
+ rlp_cohort_list_page.click_add_btn_to_select_outcode(out_code2)
+ rlp_cohort_list_page.enter_outcode_filter(out_code3)
+ rlp_cohort_list_page.click_add_btn_to_select_outcode(out_code3)
+ rlp_cohort_list_page.click_done_btn_gp_practices_include_popup()
+ included_outcodes = page.locator(
+ "//table[@id='practicesToIncludeList']//tr//td[2]"
+ ).count()
+ assert included_outcodes == 3
+
+ # removing one included gp practice
+ rlp_cohort_list_page.click_remove_button_by_gp_code("EX1")
+ removed_included_outcodes = page.locator(
+ "//table[@id='practicesToIncludeList']//tr//td[2]"
+ ).count()
+ assert removed_included_outcodes == 2
+ # removing one included gp practice
+ rlp_cohort_list_page.click_remove_button_by_gp_code("EX2")
+ removed_included_outcodes = page.locator(
+ "//table[@id='practicesToIncludeList']//tr//td[2]"
+ ).count()
+ assert removed_included_outcodes == 1
+ # removing last of the included gp practice
+ rlp_cohort_list_page.click_remove_button_by_gp_code("EX3")
+ removed_included_outcodes = page.locator(
+ "//table[@id='practicesToIncludeList']//tr//td[2]"
+ ).count()
+ assert removed_included_outcodes == 0
diff --git a/tests/ui/rlp_screening_cohort_list/test_rlp_screening_cohort_list_by_gp.py b/tests/ui/rlp_screening_cohort_list/test_rlp_screening_cohort_list_by_gp.py
new file mode 100644
index 00000000..2b3116c3
--- /dev/null
+++ b/tests/ui/rlp_screening_cohort_list/test_rlp_screening_cohort_list_by_gp.py
@@ -0,0 +1,486 @@
+import playwright
+import pytest
+
+# from conftest import db_util
+from pages.main_menu import MainMenuPage
+from pages.rlp_cohort_list_page import CohortListPage
+from playwright.sync_api import expect, Page, Playwright
+from datetime import datetime
+from pages.rlp_location_list_page import ScreeningLocationListPage
+from pages.rlp_unit_list_page import ScreeningUnitListPage
+from utils.test_helpers import generate_random_string
+from utils import test_helpers
+from utils.user_tools import UserTools
+
+
+# test to create the unit test data
+@pytest.mark.cohortgp
+def test_check_and_create_unit_test_data(
+ page: Page, rlp_cohort_list_page: CohortListPage
+):
+ """creating unit test data for User2 BS2"""
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "Read Only BSO User2 - BS2")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Unit List")
+ unit_names = ["Batman", "Captain"]
+ for unit_name in unit_names:
+ rlp_cohort_list_page.create_unit_if_not_exists(unit_name)
+
+
+# test to create the location data
+@pytest.mark.cohortgp
+def test_check_and_create_location_test_data_for_outcode(
+ page: Page, rlp_cohort_list_page: CohortListPage
+):
+ """
+ Random test to generate location test data for User2 BS2
+ """
+ # Logged into BSS_SO2 User2
+ UserTools().user_login(page, "Read Only BSO User2 - BS2")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Location List")
+ locations = [
+ "Aldi - Caldecott County Retail Park",
+ "Poundland Car Park - Alberta Retail Park",
+ ]
+ for location in locations:
+ ScreeningLocationListPage(page).create_location_if_not_exists(location)
+
+
+## Test_01
+## Test_02
+@pytest.mark.cohortgp
+def test_only_default_BSO_Cohort_visible(
+ page: Page, rlp_cohort_list_page: CohortListPage
+) -> None:
+ """
+ User Selects 'Screening Cohorts' from the drop down list,
+ User has no saved Cohorts,
+ 'Screening Cohort List' Screen is displayed correctly, with only the default BSO Cohort visible
+ """
+ # Logged into BSS_SO2_User2 and select screening cohort list from the roundplanning drop down
+ UserTools().user_login(page, "Read Only BSO User2 - BS2")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ expect(page.get_by_text("Screening cohort list", exact=True)).to_be_visible()
+ paging_info = rlp_cohort_list_page.extract_cohort_paging_info()
+ assert paging_info == 1
+ cell_data = page.locator("//tbody/tr/td[6]").inner_text()
+ assert cell_data == "Default"
+
+
+## Test_03
+@pytest.mark.cohortgp
+def test_paging_of_cohort_list(
+ page: Page, rlp_cohort_list_page: CohortListPage, db_util
+) -> None:
+ """
+ Test to compare the UI cohort row count and db row count
+ """
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ # Checking the paging info
+ ui_row_count = rlp_cohort_list_page.extract_cohort_paging_info()
+ db_row_count = rlp_cohort_list_page.screening_cohorts_count_in_db(db_util)
+ assert db_row_count == int(ui_row_count)
+
+
+## Test_04
+@pytest.mark.cohortgp
+def test_defaults_are_set_and_displayed_correctly(
+ page: Page, rlp_cohort_list_page: CohortListPage
+) -> None:
+ """
+ Test to verify the cohort defaults are displayed correctly
+ """
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+
+ # Create Screening Cohort screen is displayed
+ rlp_cohort_list_page.click_create_screening_cohort_by_gp_practice_btn()
+ page.wait_for_timeout(3000)
+ expect(page.get_by_text("Create Screening Cohort")).to_be_visible()
+
+ # All defaults are set & displayed correctly
+ expect(page.locator("input#description")).to_be_empty() # screening cohort name
+ expect(
+ page.locator("input#uptakePercentage")
+ ).to_be_empty() # Expected Attendance Rate (%)
+ assert (
+ page.locator("select#defaultLocation").input_value() == ""
+ ) # Default Screening Location
+ assert (
+ page.locator("select#defaultUnit").input_value() == ""
+ ) # Default Screening Unit
+ expect(page.locator("td.dataTables_empty")).to_be_visible() # Included GP Practices
+
+
+## Test_05
+@pytest.mark.cohortgp
+def test_invoke_cancel_btn_return_to_screening_cohort_list(
+ page: Page, rlp_cohort_list_page: CohortListPage
+) -> None:
+ """
+ Test to invoke cancel cohort button and retuen to the screening cohort list page
+ """
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+
+ # Create Screening Cohort screen is displayed
+ rlp_cohort_list_page.click_create_screening_cohort_by_gp_practice_btn()
+ page.wait_for_timeout(3000)
+
+ rlp_cohort_list_page.click_on_cancel_creating_screening_cohort()
+ page.wait_for_timeout(5000)
+ expect(page.get_by_text("Screening cohort list", exact=True)).to_be_visible()
+
+
+## Test_06
+@pytest.mark.cohortgp
+@pytest.mark.parametrize("input_length", [3, 100])
+def test_create_screening_cohort_valid_data(
+ page: Page, rlp_cohort_list_page: CohortListPage, input_length
+) -> None:
+ """User enters valid data into Screening Cohort field"""
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ # Test data
+ cohort_name = generate_random_string(
+ input_length
+ ) # this method will generate string between 3 & 100 which will be unique each time
+ location_name = "Aldi - Caldecott County Retail Park"
+ # checking uniqueness of the name
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(cohort_name)
+ expect(page.locator("td.dataTables_empty")).to_be_visible()
+ # creating cohort using create cohort method
+ rlp_cohort_list_page.create_cohort(cohort_name, location_name)
+ # filtering name and storing in filterd_name
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(cohort_name)
+ filterd_name = page.locator("//tr//td[2]").text_content()
+ assert cohort_name == filterd_name
+
+
+## Test_06 negative field data validation
+@pytest.mark.cohortgp
+@pytest.mark.parametrize(
+ "cohort_name, expected_message",
+ [
+ ("$%&@", "Screening Cohort Name contains invalid characters"),
+ ("cd", "The Screening Cohort Name you entered is too short"),
+ (" ", "Screening Cohort Name must be populated"),
+ ("Hadley", "Screening Cohort Name is already in use by another cohort"),
+ ],
+)
+def test_try_to_create_screening_cohort_with_invalid_data(
+ page: Page, rlp_cohort_list_page: CohortListPage, cohort_name, expected_message
+):
+ """
+ Negative test - User enters invalid data into Screening Cohort field
+ """
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ # try to create cohort using create cohort method with invalid data
+ rlp_cohort_list_page.click_create_screening_cohort_by_gp_practice_btn()
+ page.wait_for_timeout(3000)
+ rlp_cohort_list_page.enter_screening_cohort_name_field(cohort_name)
+ rlp_cohort_list_page.enter_expected_attendance_rate("25")
+ rlp_cohort_list_page.select_default_screening_location_dropdown(
+ "Aldi - Caldecott County Retail Park"
+ )
+ rlp_cohort_list_page.select_default_screening_unit_dropdown("Batman")
+ rlp_cohort_list_page.click_create_screening_cohort_save_btn()
+
+ # Assert that the correct error message is displayed based on invalid_data
+ expect(page.get_by_text(expected_message)).to_be_visible()
+
+
+## Test_07 positive field data validation for Expected Attendance Rate
+@pytest.mark.cohortgp
+@pytest.mark.parametrize("input_value", [0, 100])
+def test_expected_attendance_rate_valid_data(
+ page: Page, rlp_cohort_list_page: CohortListPage, input_value
+) -> None:
+ """
+ User enters valid data in the Expected Attendance Rate (%) field
+ """
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ cohort_name = f"cohort_name-{datetime.now()}"
+ rlp_cohort_list_page.click_create_screening_cohort_by_gp_practice_btn()
+ page.wait_for_timeout(3000)
+ rlp_cohort_list_page.enter_screening_cohort_name_field(cohort_name)
+ rlp_cohort_list_page.enter_expected_attendance_rate(str(input_value))
+ rlp_cohort_list_page.select_default_screening_location_dropdown(
+ "Aldi - Caldecott County Retail Park"
+ )
+ rlp_cohort_list_page.select_default_screening_unit_dropdown("Batman")
+ rlp_cohort_list_page.click_create_screening_cohort_save_btn()
+
+ # filtering name and storing in filterd_name
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(cohort_name)
+ rlp_cohort_list_page.dbl_click_on_filtered_cohort()
+ assert float(page.locator("input#uptakePercentage").input_value()) == input_value
+
+
+#### Test_07 negative test for Expected Attendance Rate field
+@pytest.mark.cohortgp
+@pytest.mark.parametrize(
+ "attendance_rate, expected_message",
+ [
+ ("cd", "Invalid value"),
+ (" ", "Expected Attendance Rate must be between 0 and 100"),
+ ],
+)
+def test_expected_attendance_rate_invalid_data(
+ page: Page, rlp_cohort_list_page: CohortListPage, attendance_rate, expected_message
+):
+ """Negative test - User enters data in the Expected Attendance Rate (%) field - Non integer value and Null"""
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+
+ # try to create cohort using invalid attendance rate
+ cohort_name = f"cohort_name-{datetime.now()}"
+ rlp_cohort_list_page.click_create_screening_cohort_by_gp_practice_btn()
+ page.wait_for_timeout(3000)
+ rlp_cohort_list_page.enter_screening_cohort_name_field(cohort_name)
+ rlp_cohort_list_page.enter_expected_attendance_rate(attendance_rate)
+ rlp_cohort_list_page.select_default_screening_location_dropdown(
+ "Aldi - Caldecott County Retail Park"
+ )
+ rlp_cohort_list_page.select_default_screening_unit_dropdown("Batman")
+ rlp_cohort_list_page.click_create_screening_cohort_save_btn()
+ # Assert that the correct error message is displayed based on invalid_data
+ expect(page.get_by_text(expected_message)).to_be_visible()
+
+
+#### Test_08
+@pytest.mark.cohortgp
+def test_default_location_dropdown(page: Page, rlp_cohort_list_page: CohortListPage):
+ """
+ The correct list of Locations available to this user in this BSO, are displayed correctly
+ """
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ rlp_cohort_list_page.click_create_screening_cohort_by_gp_practice_btn()
+ page.wait_for_timeout(3000)
+ # extracting the drop down location count
+ rlp_cohort_list_page.select_default_screening_location_dropdown(None)
+ dropdown_count = rlp_cohort_list_page.number_of_location_dropdown_count()
+
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Location List")
+ # extracting the location count
+ location_list_count = rlp_cohort_list_page.extract_location_paging_list_count()
+ assert dropdown_count == location_list_count
+
+
+#### Test_09
+@pytest.mark.cohortgp
+def test_default_unit_dropdown(page: Page, rlp_cohort_list_page: CohortListPage):
+ """
+ The correct list of units available to this user in this BSO, are displayed correctly
+ """
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ rlp_cohort_list_page.click_create_screening_cohort_by_gp_practice_btn()
+ page.wait_for_timeout(3000)
+ # extracting the drop down location count
+ rlp_cohort_list_page.select_default_screening_unit_dropdown(None)
+ dropdown_count = rlp_cohort_list_page.number_of_unit_dropdown_count()
+
+ # Logged into BSS_SO1 location list
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Unit List")
+ # extracting the location count
+ unit_list_count = rlp_cohort_list_page.extract_paging_unit_list_count_active_only()
+ assert dropdown_count == unit_list_count
+
+
+#### Test_10.1.2, 10.2.1
+@pytest.mark.cohortgp
+def test_added_gp_practices_are_visible(
+ page: Page, rlp_cohort_list_page: CohortListPage
+):
+ """
+ Test to add gp practices and covers negative test of adding the same gp practice
+ """
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ rlp_cohort_list_page.click_create_screening_cohort_by_gp_practice_btn()
+ page.wait_for_timeout(3000)
+ # including gp practice
+ rlp_cohort_list_page.click_select_gp_practices_btn()
+ rlp_cohort_list_page.enter_gp_code_field("A00002")
+ rlp_cohort_list_page.click_add_btn_gp_practices_to_include()
+ rlp_cohort_list_page.click_done_btn_gp_practices_include_popup()
+ rlp_cohort_list_page.click_select_gp_practices_btn()
+ rlp_cohort_list_page.enter_gp_code_field("A00003")
+ rlp_cohort_list_page.click_add_btn_gp_practices_to_include()
+ rlp_cohort_list_page.click_done_btn_gp_practices_include_popup()
+ included_gp_practices = page.locator(
+ "//table[@id='practicesToIncludeList']//tr//td[2]"
+ ).count()
+ assert included_gp_practices == 2
+ # try including the already added gp_practices - negative test
+ rlp_cohort_list_page.click_select_gp_practices_btn()
+ rlp_cohort_list_page.enter_gp_code_field("A00002")
+ rlp_cohort_list_page.verify_add_btn_gp_practices_not_to_be_present()
+
+
+#### Test_11
+@pytest.mark.cohortgp
+def test_gp_practices_removed_from_included_gp_practices(
+ page: Page, rlp_cohort_list_page: CohortListPage
+):
+ """
+ User add and remove the gp practices
+ """
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ rlp_cohort_list_page.click_create_screening_cohort_by_gp_practice_btn()
+ page.wait_for_timeout(3000)
+ # including gp practice
+ rlp_cohort_list_page.click_select_gp_practices_btn()
+ rlp_cohort_list_page.enter_gp_code_field("A00002")
+ rlp_cohort_list_page.click_add_btn_gp_practices_to_include()
+ rlp_cohort_list_page.click_done_btn_gp_practices_include_popup()
+ rlp_cohort_list_page.click_select_gp_practices_btn()
+ rlp_cohort_list_page.enter_gp_code_field("A00003")
+ rlp_cohort_list_page.click_add_btn_gp_practices_to_include()
+ rlp_cohort_list_page.click_done_btn_gp_practices_include_popup()
+ rlp_cohort_list_page.click_select_gp_practices_btn()
+ rlp_cohort_list_page.enter_gp_code_field("A00004")
+ rlp_cohort_list_page.click_add_btn_gp_practices_to_include()
+ rlp_cohort_list_page.click_done_btn_gp_practices_include_popup()
+ included_gp_practices = page.locator(
+ "//table[@id='practicesToIncludeList']//tr//td[2]"
+ ).count()
+ assert included_gp_practices == 3
+ # removing one included gp practice
+ rlp_cohort_list_page.click_remove_button_by_gp_code("A00002")
+ removed_included_gp_practices = page.locator(
+ "//table[@id='practicesToIncludeList']//tr//td[2]"
+ ).count()
+ assert removed_included_gp_practices == 2
+ # removing one included gp practice
+ rlp_cohort_list_page.click_remove_button_by_gp_code("A00003")
+ removed_included_gp_practices = page.locator(
+ "//table[@id='practicesToIncludeList']//tr//td[2]"
+ ).count()
+ assert removed_included_gp_practices == 1
+ # removing last of the included gp practice
+ rlp_cohort_list_page.click_remove_button_by_gp_code("A00004")
+ removed_included_gp_practices = page.locator(
+ "//table[@id='practicesToIncludeList']//tr//td[2]"
+ ).count()
+ assert removed_included_gp_practices == 0
+
+
+#### Test_12
+@pytest.mark.cohortgp
+def test_click_save_without_filling_all_mandatory_fields(
+ page: Page, rlp_cohort_list_page: CohortListPage
+):
+ """User invoke save button without filling all the mandatory fields and validate the response"""
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+
+ rlp_cohort_list_page.click_create_screening_cohort_by_gp_practice_btn()
+ page.wait_for_timeout(3000)
+ rlp_cohort_list_page.click_create_screening_cohort_save_btn()
+ # expected error messages to be visible
+ expect(page.get_by_text("Screening Cohort Name must be populated")).to_be_visible
+ expect(
+ page.get_by_text("Expected Attendance Rate must be between 0 and 100")
+ ).to_be_visible
+ expect(
+ page.get_by_text("Default Screening Location must be populated")
+ ).to_be_visible
+ expect(page.get_by_text("Default Screening Unit must be populated")).to_be_visible
+
+
+#### Test_13
+@pytest.mark.cohortgp
+def test_another_user_logs_into_BS_select(
+ page: Page, rlp_cohort_list_page: CohortListPage, context
+):
+ """Other Users are not able to create Cohort with same details of other existing Cohort within the same BSO, failing validation"""
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ # creating cohort using method with hardcoded attendence and screening unit
+ cohort_name = f"cohort_name-{datetime.now()}"
+ location_name = "Poundland Car Park - Alberta Retail Park"
+ rlp_cohort_list_page.create_cohort(cohort_name, location_name)
+ context.clear_cookies()
+
+ # Logged into BSS_SO1_User2
+ UserTools().user_login(page, "BSO User2 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ # User2 is filtering the cohort by name which is creatd by User1
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(cohort_name)
+ filterd_name = rlp_cohort_list_page.value_of_filtered_cohort_name()
+ assert cohort_name == filterd_name
+ # User2 tries to create cohort with same detailes test should fail
+ rlp_cohort_list_page.create_cohort(cohort_name, location_name)
+ expect(
+ page.get_by_text("Screening Cohort Name is already in use by another cohort")
+ ).to_be_visible()
+
+
+## Test_47
+@pytest.mark.cohortgp
+def test_gp_practice_exist_outcode_does_not_exist(
+ page: Page, rlp_cohort_list_page: CohortListPage
+):
+ """
+ Test to verify when GP practice exists the outcode will be disabled
+ """
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ assert page.locator(
+ "button:has-text('Create screening cohort by GP practice')"
+ ).is_enabled(), "'Create screening cohort by GP practice' button is not enabled"
+ assert page.locator(
+ "button:has-text('Create screening cohort by outcode')"
+ ).is_disabled(), "'Create screening cohort by outcode' button is enabled"
+
+ info_icon = page.locator("button#addCohortByOutcodeButton + span")
+ assert info_icon.get_attribute("data-toggle") == "tooltip"
+ tooltip_text = info_icon.get_attribute("data-original-title")
+ expected_text = "Cohorts have already been defined by GP practice/Sub practice, defining more cohorts by outcode is not permitted.
This is to prevent subjects from being included in the demand figures for multiple cohort types."
+ assert tooltip_text == expected_text
+
+
+#### Test_48
+@pytest.mark.cohortgp
+def test_gp_practice_does_not_exist_outcode_exist(
+ page: Page, rlp_cohort_list_page: CohortListPage
+):
+ """
+ Test to verify when outcode exists the GP practice will be disabled
+ """
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "Read Only BSO User2 - BS2")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ assert page.locator(
+ "button:has-text('Create screening cohort by GP practice')"
+ ).is_disabled(), "'Create screening cohort by GP practice' button is enabled"
+ assert page.locator(
+ "button:has-text('Create screening cohort by outcode')"
+ ).is_enabled(), "'Create screening cohort by outcode' button is not enabled"
+
+ info_icon = page.locator("button#addCohortByPracticeButton + span")
+ assert info_icon.get_attribute("data-toggle") == "tooltip"
+ tooltip_text = info_icon.get_attribute("data-original-title")
+ expected_text = "Cohorts have already been defined by outcode, defining more cohorts by GP practice/sub practice is not permitted.
This is to prevent subjects from being included in the demand figures for multiple cohort types."
+ assert tooltip_text == expected_text
diff --git a/tests/ui/rlp_screening_cohort_list/test_rlp_screening_cohort_list_by_outcode.py b/tests/ui/rlp_screening_cohort_list/test_rlp_screening_cohort_list_by_outcode.py
new file mode 100644
index 00000000..1c8dba7c
--- /dev/null
+++ b/tests/ui/rlp_screening_cohort_list/test_rlp_screening_cohort_list_by_outcode.py
@@ -0,0 +1,539 @@
+import playwright
+import pytest
+
+# from conftest import db_util
+from pages.main_menu import MainMenuPage
+from pages.rlp_cohort_list_page import CohortListPage
+from playwright.sync_api import expect, Page, Playwright
+from datetime import datetime
+from pages.rlp_location_list_page import ScreeningLocationListPage
+from pages.rlp_unit_list_page import ScreeningUnitListPage
+from utils.test_helpers import generate_random_string
+from utils import test_helpers
+from utils.user_tools import UserTools
+
+
+# test to create the unit test data
+def test_check_and_create_unit_test_data(
+ page: Page, rlp_cohort_list_page: CohortListPage
+):
+ """creating unit test data for User2 BS2"""
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "Read Only BSO User2 - BS2")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Unit List")
+ unit_names = ["Batman", "Captain"]
+ for unit_name in unit_names:
+ rlp_cohort_list_page.create_unit_if_not_exists(unit_name)
+
+
+# test to create the location data
+def test_check_and_create_location_test_data_for_outcode(
+ page: Page, rlp_cohort_list_page: CohortListPage
+):
+ """
+ Random test to generate location test data for User2 BS2
+ """
+ # Logged into BSS_SO2 User2
+ UserTools().user_login(page, "Read Only BSO User2 - BS2")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Location List")
+ locations = [
+ "Aldi - Caldecott County Retail Park",
+ "Poundland Car Park - Alberta Retail Park",
+ ]
+ for location in locations:
+ ScreeningLocationListPage(page).create_location_if_not_exists(location)
+
+
+#### Test_45
+@pytest.mark.cohortoutcode
+def test_to_verify_gp_practice_and_outcode_buttons_are_enable(
+ page: Page, rlp_cohort_list_page: CohortListPage
+):
+ """
+ Test to verify add By Practice pushbutton is enabled and
+ add By Outcode pushbutton is enabled
+ """
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "Read Only BSO User2 - BS2")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ assert page.locator(
+ "button:has-text('Create screening cohort by GP practice')"
+ ).is_enabled(), "'Create screening cohort by GP practice' button is not enabled"
+ assert page.locator(
+ "button:has-text('Create screening cohort by outcode')"
+ ).is_enabled(), "'Create screening cohort by outcode' button is not enabled"
+
+
+## Test_24
+@pytest.mark.cohortoutcode
+def test_for_outcode_defaults_are_set_and_displayed_correctly(
+ page: Page, rlp_cohort_list_page: CohortListPage
+) -> None:
+ """test 'Create Screening Cohort' screen is displayed correctly
+ all defaults are set & displayed correctly"""
+ # Logged into BSS_SO2 user2
+ UserTools().user_login(page, "Read Only BSO User2 - BS2")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+
+ # Create Screening Cohort screen is displayed
+ rlp_cohort_list_page.click_create_screening_cohort_by_outcode_btn()
+ page.wait_for_timeout(3000)
+ expect(page.get_by_text("Create screening cohort", exact=True)).to_be_visible()
+
+ # All defaults are set & displayed correctly
+ expect(page.locator("input#description")).to_be_empty() # screening cohort name
+ expect(
+ page.locator("input#uptakePercentage")
+ ).to_be_empty() # Expected Attendance Rate (%)
+ assert (
+ page.locator("select#defaultLocation").input_value() == ""
+ ) # Default Screening Location
+ assert (
+ page.locator("select#defaultUnit").input_value() == ""
+ ) # Default Screening Unit
+ expect(page.locator("td.dataTables_empty")).to_be_visible() # Included GP Practices
+
+
+# Test_25
+@pytest.mark.cohortoutcode
+def test_for_outcode_cancel_function(page: Page, rlp_cohort_list_page: CohortListPage):
+ """
+ User is able to cancel Cohort without saving and able to retuen to cohort home page
+ """
+ # Logged into BSS_SO2 user2
+ UserTools().user_login(page, "Read Only BSO User2 - BS2")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ rlp_cohort_list_page.click_create_screening_cohort_by_outcode_btn()
+ page.wait_for_timeout(3000)
+ rlp_cohort_list_page.click_cancel_cohort_by_outcode_btn()
+ page.wait_for_timeout(3000)
+ expect(page.get_by_text("Screening cohort list", exact=True)).to_be_visible()
+
+
+## Test_26
+@pytest.mark.cohortoutcode
+@pytest.mark.parametrize("input_length", [3, 100])
+def test_create_screening_cohort_outcode_valid_data(
+ page: Page, rlp_cohort_list_page: CohortListPage, input_length
+) -> None:
+ """
+ Test for creating screening Cohort outcode using the valid field length of 3 and 100 char,
+ asserting created value and the actual value
+ """
+ # Logged into BSS_SO2 user2
+ UserTools().user_login(page, "Read Only BSO User2 - BS2")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ # Test data
+ cohort_name = generate_random_string(
+ input_length
+ ) # generate_randon_string method will generate string between 3 & 100 which will be unique each time
+ location_name = "Poundland Car Park - Alberta Retail Park"
+ unit_name = "Batman"
+ attendance_rate = "25"
+ # checking uniqueness of the name
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(cohort_name)
+ page.wait_for_timeout(3000)
+ expect(page.locator("td.dataTables_empty")).to_be_visible()
+ # creating cohort using create cohort method
+ rlp_cohort_list_page.create_cohort_outcode_without_gp(
+ cohort_name, str(attendance_rate), location_name, unit_name
+ )
+ # filtering name and storing in filterd_name
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(cohort_name)
+ filterd_name = page.locator("//tr//td[2]").text_content()
+ assert cohort_name == filterd_name
+
+
+# creating cohort for below test
+@pytest.mark.cohortoutcode
+def test_create_screening_cohort_outcode_test_data(
+ page: Page, rlp_cohort_list_page: CohortListPage
+) -> None:
+ """
+ Test to create a test data
+ """
+ # Logged into BSS_SO2 user2
+ UserTools().user_login(page, "Read Only BSO User2 - BS2")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ # Test data
+ cohort_name = "Hadley"
+ attendance_rate = "25"
+ location_name = "Poundland Car Park - Alberta Retail Park"
+ unit_name = "Batman"
+ # creating cohort using create cohort method
+ rlp_cohort_list_page.create_cohort_outcode_without_gp(
+ cohort_name, str(attendance_rate), location_name, unit_name
+ )
+
+
+## Test_26 negative test
+@pytest.mark.cohortoutcode
+@pytest.mark.parametrize(
+ "cohort_name, expected_message",
+ [
+ ("$%&@", "Screening Cohort Name contains invalid characters"),
+ ("cd", "The Screening Cohort Name you entered is too short"),
+ (" ", "Screening Cohort Name must be populated"),
+ ("Hadley", "Screening Cohort Name is already in use by another cohort"),
+ ],
+)
+def test_try_to_create_screening_cohort_outcode_with_invalid_data(
+ page: Page, rlp_cohort_list_page: CohortListPage, cohort_name, expected_message
+):
+ """
+ Test for creating screening Cohort outcode using the invalid field values
+ capturing the error messages
+ """
+ # Logged into BSS_SO2 user2
+ UserTools().user_login(page, "Read Only BSO User2 - BS2")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ # Test data
+ attendance_rate = "25"
+ location_name = "Poundland Car Park - Alberta Retail Park"
+ unit_name = "Batman"
+ # try creating cohort using create cohort method
+ rlp_cohort_list_page.create_cohort_outcode_without_gp(
+ cohort_name, str(attendance_rate), location_name, unit_name
+ )
+ # Assert that the correct error message is displayed based on invalid_data
+ expect(page.get_by_text(expected_message)).to_be_visible()
+
+
+## Test_27
+@pytest.mark.cohortoutcode
+@pytest.mark.parametrize("attendance_rate", [0, 100])
+def test_outcode_expected_attendance_rate_valid_data(
+ page: Page, rlp_cohort_list_page: CohortListPage, attendance_rate
+) -> None:
+ """User enters valid data into Expected Attendance Rate (%) field"""
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "Read Only BSO User2 - BS2")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ cohort_name = f"cohort_name-{datetime.now()}"
+ location_name = "Poundland Car Park - Alberta Retail Park"
+ unit_name = "Batman"
+ rlp_cohort_list_page.create_cohort_outcode_without_gp(
+ cohort_name, str(attendance_rate), location_name, unit_name
+ )
+ # filtering name and storing in filterd_name
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(cohort_name)
+ rlp_cohort_list_page.dbl_click_on_filtered_cohort()
+ assert (
+ float(page.locator("input#uptakePercentage").input_value()) == attendance_rate
+ )
+
+
+## Test_27 negative test for Expected Attendance Rate field
+@pytest.mark.cohortoutcode
+@pytest.mark.parametrize(
+ "attendance_rate, expected_message",
+ [
+ ("cd", "Invalid value"),
+ (" ", "Expected Attendance Rate must be between 0 and 100"),
+ ],
+)
+def test_outcode_expected_attendance_rate_invalid_data(
+ page: Page, rlp_cohort_list_page: CohortListPage, attendance_rate, expected_message
+):
+ """Negative test - User enters inalid data into Expected Attendance Rate (%) field"""
+ # Logged into BSS_SO2 user2
+ UserTools().user_login(page, "Read Only BSO User2 - BS2")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ # try to create cohort using invalid attendance rate
+ cohort_name = f"cohort_name-{datetime.now()}"
+ location_name = "Poundland Car Park - Alberta Retail Park"
+ unit_name = "Batman"
+ rlp_cohort_list_page.create_cohort_outcode_without_gp(
+ cohort_name, attendance_rate, location_name, unit_name
+ )
+ # Assert that the correct error message is displayed based on invalid_data
+ expect(page.get_by_text(expected_message)).to_be_visible()
+
+
+## Test_28
+@pytest.mark.cohortoutcode
+def test_outcode_default_location_dropdown(
+ page: Page, rlp_cohort_list_page: CohortListPage
+):
+ """The correct list of Locations available to this user in this BSO are displayed correctly"""
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "Read Only BSO User2 - BS2")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+
+ rlp_cohort_list_page.click_create_screening_cohort_by_outcode_btn()
+ page.wait_for_timeout(3000)
+ # extracting the drop down location count
+ rlp_cohort_list_page.select_default_screening_location_dropdown(None)
+ dropdown_count = rlp_cohort_list_page.number_of_location_dropdown_count()
+
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Location List")
+ # extracting the location count
+ location_list_count = rlp_cohort_list_page.extract_location_paging_list_count()
+ assert dropdown_count == location_list_count
+
+
+## Test_29
+@pytest.mark.cohortoutcode
+def test_outcode_default_unit_dropdown(
+ page: Page, rlp_cohort_list_page: CohortListPage
+):
+ """Test the correct list of Active only Units available to this user in this BSO are displayed correctly"""
+ # Logged into BSS_SO2 user2
+ UserTools().user_login(page, "Read Only BSO User2 - BS2")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+
+ rlp_cohort_list_page.click_create_screening_cohort_by_outcode_btn()
+ page.wait_for_timeout(3000)
+ rlp_cohort_list_page.select_default_screening_unit_dropdown(None)
+ dropdown_count = rlp_cohort_list_page.number_of_unit_dropdown_count()
+
+ # Logged into BSS_SO2 location list
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Unit List")
+ # extracting the location count
+ unit_list_count = rlp_cohort_list_page.extract_paging_unit_list_count()
+ assert dropdown_count == unit_list_count
+
+
+## Test_30
+@pytest.mark.cohortoutcode
+def test_outcode_added_gp_practices_are_visible(
+ page: Page, rlp_cohort_list_page: CohortListPage
+):
+ """1. Selects Outcode from the 'All available Outcodes' List, the correct Outcodes details are now visible in the Included Outcodes List
+ Negative test - 2. Attempt to add same Outcode"""
+ # Logged into BSS_SO2 user2
+ UserTools().user_login(page, "Read Only BSO User2 - BS2")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+
+ rlp_cohort_list_page.click_create_screening_cohort_by_outcode_btn()
+ page.wait_for_timeout(3000)
+ # including outcode
+ rlp_cohort_list_page.click_select_outcodes_btn()
+ rlp_cohort_list_page.enter_outcode_filter("EX1")
+ rlp_cohort_list_page.click_add_btn_to_select_outcode("EX1")
+ rlp_cohort_list_page.click_done_btn_gp_practices_include_popup()
+ page.wait_for_timeout(3000)
+ included_outcodes = page.locator(
+ "//table[@id='practicesToIncludeList']//tr//td[2]"
+ ).count()
+ assert included_outcodes == 1
+
+ # attempt to add the same outcode the add btn is not present 2nd time - negative test
+ rlp_cohort_list_page.click_select_outcodes_btn()
+ rlp_cohort_list_page.enter_outcode_filter("EX1")
+ expect(
+ page.locator("//tr[td[1][normalize-space()='EX1']]//button[text()='Add']")
+ ).not_to_be_visible()
+
+
+## Test_31
+@pytest.mark.cohortoutcode
+def test_selects_to_remove_outcodes(page: Page, rlp_cohort_list_page: CohortListPage):
+ """User selects to Remove Outcodes - Removes 1 Outcode, Removes 2 Outcode, Removes all Outcodes"""
+ # Logged into BSS_SO2 user2
+ UserTools().user_login(page, "Read Only BSO User2 - BS2")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+
+ rlp_cohort_list_page.click_create_screening_cohort_by_outcode_btn()
+ page.wait_for_timeout(3000)
+ # including outcodes
+ rlp_cohort_list_page.click_select_outcodes_btn()
+ rlp_cohort_list_page.enter_outcode_filter("EX1")
+ rlp_cohort_list_page.click_add_btn_to_select_outcode("EX1")
+ rlp_cohort_list_page.click_done_btn_gp_practices_include_popup()
+ rlp_cohort_list_page.click_select_outcodes_btn()
+ rlp_cohort_list_page.enter_outcode_filter("EX2")
+ rlp_cohort_list_page.click_add_btn_to_select_outcode("EX2")
+ rlp_cohort_list_page.click_done_btn_gp_practices_include_popup()
+ rlp_cohort_list_page.click_select_outcodes_btn()
+ rlp_cohort_list_page.enter_outcode_filter("EX3")
+ rlp_cohort_list_page.click_add_btn_to_select_outcode("EX3")
+ rlp_cohort_list_page.click_done_btn_gp_practices_include_popup()
+ included_outcodes = page.locator(
+ "//table[@id='practicesToIncludeList']//tr//td[2]"
+ ).count()
+ assert included_outcodes == 3
+ # removing one included gp practice
+ rlp_cohort_list_page.click_remove_button_by_gp_code("EX1")
+ removed_included_outcodes = page.locator(
+ "//table[@id='practicesToIncludeList']//tr//td[2]"
+ ).count()
+ assert removed_included_outcodes == 2
+ # removing one included gp practice
+ rlp_cohort_list_page.click_remove_button_by_gp_code("EX2")
+ removed_included_outcodes = page.locator(
+ "//table[@id='practicesToIncludeList']//tr//td[2]"
+ ).count()
+ assert removed_included_outcodes == 1
+ # removing last of the included gp practice
+ rlp_cohort_list_page.click_remove_button_by_gp_code("EX3")
+ removed_included_outcodes = page.locator(
+ "//table[@id='practicesToIncludeList']//tr//td[2]"
+ ).count()
+ assert removed_included_outcodes == 0
+
+
+## Test_32
+@pytest.mark.cohortoutcode
+def test_outcode_click_save_without_filling_all_mandatory_fields(
+ page: Page, rlp_cohort_list_page: CohortListPage
+):
+ """Invoke add button without filling out all the mandatory fiels and validate the error messages"""
+ # Logged into BSS_SO2 user2
+ UserTools().user_login(page, "Read Only BSO User2 - BS2")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ rlp_cohort_list_page.click_create_screening_cohort_by_outcode_btn()
+ page.wait_for_timeout(3000)
+ rlp_cohort_list_page.click_create_screening_cohort_save_btn()
+ # expected error messages to be visible
+ expect(page.get_by_text("Screening Cohort Name must be populated")).to_be_visible
+ expect(
+ page.get_by_text("Expected Attendance Rate must be between 0 and 100")
+ ).to_be_visible
+ expect(
+ page.get_by_text("Default Screening Location must be populated")
+ ).to_be_visible
+ expect(page.get_by_text("Default Screening Unit must be populated")).to_be_visible
+
+
+#### Test_44.1.1
+@pytest.mark.cohortoutcode
+@pytest.mark.parametrize("search_term", ["Cohort", "had"])
+def test_outcode_search_feature_using_description(
+ page: Page, rlp_cohort_list_page: CohortListPage, search_term: str
+):
+ """
+ Test to validate the search feature using description
+ """
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "Read Only BSO User2 - BS2")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ # search by description
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(search_term)
+ filtered_values = page.locator("//tbody/tr/td[2]").all_text_contents()
+ # Assert that each value contains "cohort"
+ for text in filtered_values:
+ assert (
+ search_term.lower() in text.lower()
+ ), f"Value '{text}' does not contain '{search_term}'."
+
+
+#### Test_44.1.2
+@pytest.mark.cohortoutcode
+@pytest.mark.parametrize("search_term", ["Pound", "Park"])
+def test_outcode_search_feature_using_location(
+ page: Page, rlp_cohort_list_page: CohortListPage, search_term: str
+):
+ """
+ Test to validate the search feature using location
+ """
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "Read Only BSO User2 - BS2")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ # search by description
+ rlp_cohort_list_page.enter_screening_location_filter(search_term)
+ filtered_values = page.locator("//tbody/tr/td[4]").all_text_contents()
+ # Assert that each value contains "cohort"
+ for text in filtered_values:
+ assert (
+ search_term.lower() in text.lower()
+ ), f"Value '{text}' does not contain '{search_term}'."
+
+
+#### Test_44.1.3
+@pytest.mark.cohortoutcode
+@pytest.mark.parametrize("search_term", ["Bat", "Cap"])
+def test_outcode_search_feature_using_unit_name(
+ page: Page, rlp_cohort_list_page: CohortListPage, search_term: str
+):
+ """
+ Test to validate the search feature using unit name
+ """
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "Read Only BSO User2 - BS2")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ # search by description
+ rlp_cohort_list_page.enter_screening_unit_filter(search_term)
+ filtered_values = page.locator("//tbody/tr/td[5]").all_text_contents()
+ # Assert that each value contains "cohort"
+ for text in filtered_values:
+ assert (
+ search_term.lower() in text.lower()
+ ), f"Value '{text}' does not contain '{search_term}'."
+
+
+#### Test_44.1.4
+@pytest.mark.cohortoutcode
+@pytest.mark.parametrize("search_term", ["All", "Outcode", "Default"])
+def test_outcode_search_feature_using_cohort_type(
+ page: Page, rlp_cohort_list_page: CohortListPage, search_term: str
+):
+ """
+ Test to validate the search feature using cohort type
+ """
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "Read Only BSO User2 - BS2")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ # search by description
+ ui_row_count_before = rlp_cohort_list_page.extract_cohort_paging_info()
+ rlp_cohort_list_page.select_cohort_type_dropdown(search_term)
+ filtered_values = page.locator("//tbody/tr/td[6]").all_text_contents()
+ # Assert that each value contains "cohort"
+ if search_term is "All":
+ ui_row_count_after = rlp_cohort_list_page.extract_cohort_paging_info()
+ assert ui_row_count_before == ui_row_count_after
+ else:
+ for text in filtered_values:
+ assert (
+ search_term.lower() in text.lower()
+ ), f"Value '{text}' does not contain '{search_term}'."
+
+
+## Test_47
+@pytest.mark.cohortoutcode
+def test_gp_practice_exist_outcode_does_not_exist(
+ page: Page, rlp_cohort_list_page: CohortListPage
+):
+ """
+ Test to verify when GP practice exists the outcode will be disabled
+ """
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ assert page.locator(
+ "button:has-text('Create screening cohort by GP practice')"
+ ).is_enabled(), "'Create screening cohort by GP practice' button is not enabled"
+ assert page.locator(
+ "button:has-text('Create screening cohort by outcode')"
+ ).is_disabled(), "'Create screening cohort by outcode' button is enabled"
+
+ info_icon = page.locator("button#addCohortByOutcodeButton + span")
+ assert info_icon.get_attribute("data-toggle") == "tooltip"
+ tooltip_text = info_icon.get_attribute("data-original-title")
+ expected_text = "Cohorts have already been defined by GP practice/Sub practice, defining more cohorts by outcode is not permitted.
This is to prevent subjects from being included in the demand figures for multiple cohort types."
+ assert tooltip_text == expected_text
+
+
+#### Test_48
+@pytest.mark.cohortoutcode
+def test_gp_practice_does_not_exist_outcode_exist(
+ page: Page, rlp_cohort_list_page: CohortListPage
+):
+ """
+ Test to verify when outcode exists the GP practice will be disabled
+ """
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "Read Only BSO User2 - BS2")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+ assert page.locator(
+ "button:has-text('Create screening cohort by GP practice')"
+ ).is_disabled(), "'Create screening cohort by GP practice' button is enabled"
+ assert page.locator(
+ "button:has-text('Create screening cohort by outcode')"
+ ).is_enabled(), "'Create screening cohort by outcode' button is not enabled"
+
+ info_icon = page.locator("button#addCohortByPracticeButton + span")
+ assert info_icon.get_attribute("data-toggle") == "tooltip"
+ tooltip_text = info_icon.get_attribute("data-original-title")
+ expected_text = "Cohorts have already been defined by outcode, defining more cohorts by GP practice/sub practice is not permitted.
This is to prevent subjects from being included in the demand figures for multiple cohort types."
+ assert tooltip_text == expected_text
diff --git a/tests/ui/rlp_screening_unit_list/test_rlp_screening_amend_unit.py b/tests/ui/rlp_screening_unit_list/test_rlp_screening_amend_unit.py
new file mode 100644
index 00000000..27f3b3f0
--- /dev/null
+++ b/tests/ui/rlp_screening_unit_list/test_rlp_screening_amend_unit.py
@@ -0,0 +1,268 @@
+import random
+import secrets
+import re
+import string
+import pytest
+from playwright.sync_api import expect, Page, Playwright
+from datetime import datetime
+from pages.rlp_unit_list_page import ScreeningUnitListPage
+from utils.test_helpers import generate_random_string
+from utils import test_helpers
+from utils.screenshot_tool import ScreenshotTool
+from utils.user_tools import UserTools
+from pages.main_menu import MainMenuPage
+
+
+## Scenario 4 - 16.1.3, 18.1.1, 18.1.4, 21.1.2 ##
+@pytest.mark.rlpunit
+def test_amend_screeing_unit_name_verify_the_paging_count(
+ page: Page, rlp_screening_unit_list_page: ScreeningUnitListPage, db_util
+) -> None:
+ """
+ Amend screening unit name and assert the unit list count on the UI and DB
+ """
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Unit List")
+ rlp_screening_unit_list_page.select_status_dropdown("All")
+ page.wait_for_timeout(3000)
+ db_row_count = rlp_screening_unit_list_page.screening_unit_list_count_in_db(db_util)
+ ui_row_count = rlp_screening_unit_list_page.extract_paging_unit_list_count()
+ assert db_row_count == int(ui_row_count)
+ unit_name = f"unit_name-{datetime.now()}"
+ rlp_screening_unit_list_page.create_unit(unit_name)
+ ScreenshotTool(page).take_screenshot("rlp_unit_amend_tc_16_18_21")
+ rlp_screening_unit_list_page.filter_unit_by_name(unit_name)
+ rlp_screening_unit_list_page.dbl_click_on_filtered_unit_name()
+ amend_unit_name = f"amend_name-{datetime.now()}"
+ rlp_screening_unit_list_page.enter_amend_screening_unit_name(amend_unit_name)
+ rlp_screening_unit_list_page.click_amend_screening_unit_btn_on_pop_up_window()
+ page.wait_for_timeout(3000)
+ ScreenshotTool(page).take_screenshot("rlp_unit_amend_tc_16_18_21.1")
+ amend_db_row_count = rlp_screening_unit_list_page.screening_unit_list_count_in_db(
+ db_util
+ )
+ amend_ui_row_count = rlp_screening_unit_list_page.extract_paging_unit_list_count()
+ assert amend_db_row_count == int(amend_ui_row_count)
+
+
+## Scenario 4 - 18.1.2, 18.1.3, 19.1.1, 20.1.2 ##
+@pytest.mark.rlpunit
+@pytest.mark.parametrize("input_length", [3, 50])
+def test_amend_screeing_unit_name_using_min_max_char_length(
+ page: Page, rlp_screening_unit_list_page: ScreeningUnitListPage, input_length
+) -> None:
+ """Amend screening unit name, unit_status and unt_type and assert the values in the table"""
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Unit List")
+ rlp_screening_unit_list_page.select_status_dropdown("All")
+ page.wait_for_timeout(3000)
+ unit_name = f"unit_name-{datetime.now()}"
+ rlp_screening_unit_list_page.create_unit(unit_name)
+ ScreenshotTool(page).take_screenshot("rlp_unit_amend_tc_18_19_20")
+ rlp_screening_unit_list_page.filter_unit_by_name(unit_name)
+ rlp_screening_unit_list_page.dbl_click_on_filtered_unit_name()
+ amend_unit_name = generate_random_string(input_length)
+ # Amended the unit name, status and type
+ rlp_screening_unit_list_page.enter_amend_screening_unit_name(amend_unit_name)
+ rlp_screening_unit_list_page.select_amend_unit_status_radio_btn("INACTIVE")
+ rlp_screening_unit_list_page.select_amend_unit_type_radio_btn("STATIC")
+ rlp_screening_unit_list_page.click_amend_screening_unit_btn_on_pop_up_window()
+ page.wait_for_timeout(3000)
+ rlp_screening_unit_list_page.filter_unit_by_name(amend_unit_name)
+ ScreenshotTool(page).take_screenshot("rlp_unit_amend_tc_18_19_20.1")
+ assert page.wait_for_selector("//tr//td[4]").text_content() == amend_unit_name
+ assert page.wait_for_selector("//tr//td[5]").text_content() == "Static"
+ assert page.wait_for_selector("//tr//td[6]").text_content() == "Inactive"
+ # verify_screening_unit_by_name() - will assert the actual amend_unit_name and created value in the table
+ rlp_screening_unit_list_page.verify_screening_unit_by_name(amend_unit_name)
+
+
+## Scenario 4 - 16.2.3 ##
+@pytest.mark.rlpunit
+def test_cancel_amend_screeing_unit(
+ page: Page, rlp_screening_unit_list_page: ScreeningUnitListPage, db_util
+) -> None:
+ """Cancel amend screening unit and assert the unit list count on the UI and DB should be the same"""
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Unit List")
+ rlp_screening_unit_list_page.select_status_dropdown("All")
+ page.wait_for_timeout(3000)
+ db_row_count = rlp_screening_unit_list_page.screening_unit_list_count_in_db(db_util)
+ ui_row_count = rlp_screening_unit_list_page.extract_paging_unit_list_count()
+ assert db_row_count == int(ui_row_count)
+ unit_name = f"unit_name-{datetime.now()}"
+ rlp_screening_unit_list_page.create_unit(unit_name)
+ rlp_screening_unit_list_page.filter_unit_by_name(unit_name)
+ rlp_screening_unit_list_page.dbl_click_on_filtered_unit_name()
+ amend_unit_name = f"amend_name-{datetime.now()}"
+ rlp_screening_unit_list_page.enter_amend_screening_unit_name(amend_unit_name)
+ rlp_screening_unit_list_page.click_cancel_btn_on_amend_screening_unit_pop_up_window()
+ page.wait_for_timeout(3000)
+ ScreenshotTool(page).take_screenshot("rlp_unit_amend_tc_16")
+ amend_db_row_count = rlp_screening_unit_list_page.screening_unit_list_count_in_db(
+ db_util
+ )
+ amend_ui_row_count = rlp_screening_unit_list_page.extract_paging_unit_list_count()
+ assert amend_db_row_count == int(amend_ui_row_count)
+
+
+## Scenario 4, 21.1.1, 24.1.2, 24.1.3, 29.1.5 ##
+@pytest.mark.rlpunit
+def test_amend_screeing_unit_days_and_notes(
+ page: Page, rlp_screening_unit_list_page: ScreeningUnitListPage
+) -> None:
+ """Amend screening unit name and usual no of appointments and notes"""
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Unit List")
+ unit_name = f"unit_name-{datetime.now()}"
+ rlp_screening_unit_list_page.create_unit(unit_name)
+ rlp_screening_unit_list_page.filter_unit_by_name(unit_name)
+ rlp_screening_unit_list_page.dbl_click_on_filtered_unit_name()
+ amend_unit_name = f"amend_name-{datetime.now()}"
+ amend_notes = generate_random_string(250)
+ unit_data = {
+ "amend_unit_name": amend_unit_name,
+ "amend_unit_notes": amend_notes,
+ **{
+ day: str(secrets.randbelow(999))
+ for day in ["sun", "mon", "tue", "wed", "thu", "fri", "sat"]
+ },
+ }
+ rlp_screening_unit_list_page.enter_amend_screening_unit_name(amend_unit_name)
+ rlp_screening_unit_list_page.enter_amend_usual_number_of_appointments_text_box(
+ unit_data
+ )
+ rlp_screening_unit_list_page.enter_amend_screening_unit_notes(amend_notes)
+ rlp_screening_unit_list_page.click_amend_screening_unit_btn_on_pop_up_window()
+ page.wait_for_timeout(3000)
+ rlp_screening_unit_list_page.filter_unit_by_name(amend_unit_name)
+ rlp_screening_unit_list_page.dbl_click_on_filtered_unit_name()
+ amended_day_values = rlp_screening_unit_list_page.get_day_appointment_value()
+ field_amend_notes = page.locator("textarea#notesAmendText").input_value()
+ assert field_amend_notes == amend_notes
+ # Assert that the UI data matches the expected data
+ for day, expected_value in unit_data.items():
+ if not day.startswith("amend_unit"):
+ day_value = amended_day_values[day.capitalize()]
+ assert (
+ day_value == expected_value
+ ), f"Mismatch for {day}: expected {expected_value}, got {day_value}"
+
+
+## Scenario 4 - 18.2.1, 18.2.2, 18.2.4, 18.2.5, 21.2.1, 21.2.2, 24.2.1, 30.2.1, 30.2.3 ##
+@pytest.mark.rlpunit
+@pytest.mark.parametrize("amend_unit_name", ["#$%&", "CA", " ", "Batman"])
+def test_negative_amend_screeing_unit_name(
+ page: Page, rlp_screening_unit_list_page: ScreeningUnitListPage, amend_unit_name
+) -> None:
+ """
+ Amend screening unit name using invalid data and capturing the error messages
+ """
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Unit List")
+ unit_name = f"unit_name-{datetime.now()}"
+ rlp_screening_unit_list_page.create_unit(unit_name)
+ rlp_screening_unit_list_page.filter_unit_by_name(unit_name)
+ rlp_screening_unit_list_page.dbl_click_on_filtered_unit_name()
+ notes = "negative invalid char # % $ & test"
+ unit_data = {
+ "amend_unit_name": amend_unit_name,
+ "thu": "",
+ "notes": notes,
+ **{
+ day: "".join(secrets.choice(string.ascii_letters) for _ in range(2))
+ for day in ["sun", "mon", "tue", "wed", "fri", "sat"]
+ },
+ }
+ rlp_screening_unit_list_page.amend_screening_unit(unit_data)
+
+ errors = {
+ bool(
+ re.search(r"[#$%<>&]", amend_unit_name)
+ ): "Name contains invalid characters",
+ len(amend_unit_name) < 3: "The Name you entered is too short",
+ amend_unit_name == "Batman": "Name is already in use by another unit",
+ amend_unit_name.strip() == "": "Unit Type must be populated",
+ notes
+ == "negative invalid char # % $ & test": "Notes contains invalid characters",
+ }
+
+ for condition, message in errors.items():
+ if condition:
+ expect(page.get_by_text(message)).to_be_visible()
+ break
+
+
+## Scenario 4 - 21.2.1, 21.2.2, 24.2.1, 28.2.1, 30.2.2 ##
+@pytest.mark.rlpunit
+def test_negative_amend_screeing_unit_name(
+ page: Page, rlp_screening_unit_list_page: ScreeningUnitListPage
+) -> None:
+ """
+ Amend screening unit name using invalid data and capturing the error messages
+ """
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Unit List")
+ unit_name = f"unit_name-{datetime.now()}"
+ rlp_screening_unit_list_page.create_unit(unit_name)
+ rlp_screening_unit_list_page.filter_unit_by_name(unit_name)
+ rlp_screening_unit_list_page.dbl_click_on_filtered_unit_name()
+ notes = "negative invalid char # % $ & test"
+ unit_data = {
+ "unit_name": unit_name,
+ "thu": "",
+ "notes": notes,
+ **{
+ day: "".join(secrets.choice(string.ascii_letters) for _ in range(2))
+ for day in ["sun", "mon", "tue", "wed", "fri", "sat"]
+ },
+ }
+ rlp_screening_unit_list_page.amend_screening_unit(unit_data)
+ for day in [
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday",
+ "Sunday",
+ ]:
+ expect(
+ page.get_by_text(f"{day} Appointments must be between 0 and 999")
+ ).to_be_visible()
+ expect(page.get_by_text("Notes contains invalid characters")).to_be_visible()
+ ScreenshotTool(page).take_screenshot("rlp_unit_amend_tc_21_24_28_30")
+
+
+## Test_31 ##
+@pytest.mark.rlpunit
+def test_user2_from_same_bso_can_access_the_user1_amended_data(
+ page: Page, rlp_screening_unit_list_page: ScreeningUnitListPage, context
+) -> None:
+ """
+ Amended screening unit is available for another user from same BSO
+ """
+ # Logged into BSS_SO1 user1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Unit List")
+ unit_name = f"user1-{datetime.now()}"
+ rlp_screening_unit_list_page.create_unit(unit_name)
+ rlp_screening_unit_list_page.filter_unit_by_name(unit_name)
+ rlp_screening_unit_list_page.dbl_click_on_filtered_unit_name()
+ amend_unit_name = f"amend_name-{datetime.now()}"
+ rlp_screening_unit_list_page.enter_amend_screening_unit_name(amend_unit_name)
+ rlp_screening_unit_list_page.click_amend_screening_unit_btn_on_pop_up_window()
+ context.clear_cookies()
+ page.wait_for_timeout(500)
+ # Logged on as BSS_SO1 user2
+ UserTools().user_login(page, "BSO User2 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Unit List")
+ rlp_screening_unit_list_page.filter_unit_by_name(amend_unit_name)
+ ScreenshotTool(page).take_screenshot("rlp_unit_amend_tc_31")
+ assert page.wait_for_selector(f"//tr//td[4]").text_content() == amend_unit_name
diff --git a/tests/ui/rlp_screening_unit_list/test_rlp_screening_unit.py b/tests/ui/rlp_screening_unit_list/test_rlp_screening_unit.py
new file mode 100644
index 00000000..bf5a8ac8
--- /dev/null
+++ b/tests/ui/rlp_screening_unit_list/test_rlp_screening_unit.py
@@ -0,0 +1,290 @@
+import random
+import secrets
+import re
+import string
+import pytest
+from playwright.sync_api import expect, Page, Playwright
+from datetime import datetime
+from pages.rlp_unit_list_page import ScreeningUnitListPage
+from utils import test_helpers
+from utils.screenshot_tool import ScreenshotTool
+from utils.user_tools import UserTools
+from pages.main_menu import MainMenuPage
+
+
+## Scenario_01 ## Test_01, Test_3.1.4 ##
+@pytest.mark.rlpunit
+def test_all_data_loaded(
+ page: Page, rlp_screening_unit_list_page: ScreeningUnitListPage, db_util
+) -> None:
+ """test to verify the correct number of 'Screening units' are displayed on the DB and UI"""
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Unit List")
+ expect(page.get_by_text("Screening unit list", exact=True)).to_be_visible()
+ db_row_count = rlp_screening_unit_list_page.screening_unit_list_count_in_db(db_util)
+ ui_row_count = rlp_screening_unit_list_page.extract_paging_unit_list_count()
+ assert db_row_count == int(ui_row_count)
+
+
+## Scenario_02 ## Test 4.1.4, 7.1.1, 7.1.2, 8.1.1, 8.1.2, 9.1.1, 10.1.2, 10.1.3 ##
+@pytest.mark.rlpunit
+@pytest.mark.parametrize("unit_type", ["MOBILE", "STATIC"])
+@pytest.mark.parametrize("unit_status", ["ACTIVE", "INACTIVE"])
+def test_screening_unit_creation(
+ page: Page,
+ rlp_screening_unit_list_page: ScreeningUnitListPage,
+ db_util,
+ unit_type,
+ unit_status,
+) -> None:
+ """User adds a Screening unit + assert the new entry is present in the UI table and added to the DB
+ and the test will use unit_type and unit_status in all 4 variations
+ """
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Unit List")
+
+ count_before = rlp_screening_unit_list_page.screening_unit_list_count_in_db(db_util)
+
+ unit_name = f"Unit_name-{datetime.now()}"
+ notes = test_helpers.generate_random_string(250)
+ unit_data = {
+ "unit_name": unit_name,
+ "notes": notes,
+ **{
+ day: str(secrets.randbelow(999))
+ for day in ["sun", "mon", "tue", "wed", "thu", "fri", "sat"]
+ },
+ }
+ rlp_screening_unit_list_page.add_screening_unit(unit_data, unit_type, unit_status)
+ rlp_screening_unit_list_page.click_add_screening_unit_btn_on_pop_up_window()
+ page.wait_for_timeout(3000)
+ rlp_screening_unit_list_page.verify_screening_unit_by_name(unit_name)
+ ScreenshotTool(page).take_screenshot("rlp_unit_tc_4_7_8_9_10")
+ count_after = rlp_screening_unit_list_page.screening_unit_list_count_in_db(db_util)
+ assert count_after == count_before + 1
+
+
+## Scenario 2 ### Test 4.2.3 - Negative ##
+@pytest.mark.rlpunit
+def test_screening_unit_cancellation(
+ page: Page, rlp_screening_unit_list_page: ScreeningUnitListPage, db_util
+) -> None:
+ """Previously saved units = x, cancels adding 1 unit, returns back to list view of x"""
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Unit List")
+ count_before = rlp_screening_unit_list_page.screening_unit_list_count_in_db(db_util)
+
+ unit_name = f"Unit_cancel-{datetime.now()}"
+ rlp_screening_unit_list_page.click_add_screening_unit_btn()
+ rlp_screening_unit_list_page.enter_screening_unit_name_txt_box(unit_name)
+ rlp_screening_unit_list_page.select_status_mobile_radio_btn()
+ rlp_screening_unit_list_page.click_cancel_btn_on_pop_up_window()
+ page.wait_for_timeout(3000)
+ rlp_screening_unit_list_page.verify_unit_has_no_matching_records_available_in_the_table(
+ unit_name
+ )
+ rlp_screening_unit_list_page.expect_no_matching_records_found_msg()
+ ScreenshotTool(page).take_screenshot("rlp_unit_tc_4_negative")
+ count_after = rlp_screening_unit_list_page.screening_unit_list_count_in_db(db_util)
+ assert count_after == count_before
+
+
+## Scenario 2 ## Test 5, 7.1.3, 8.1.3, 9.1.2 ##
+@pytest.mark.rlpunit1
+def test_defaults_are_set_correctly(
+ page: Page, rlp_screening_unit_list_page: ScreeningUnitListPage
+) -> None:
+ """All defaults are set & displayed correctly"""
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Unit List")
+ rlp_screening_unit_list_page.click_add_screening_unit_btn()
+ expect(page.locator("input#unitNameText")).to_be_empty()
+ expect(
+ page.locator(
+ "//input[@name='statusText' and @type='radio' and @value='ACTIVE']"
+ )
+ ).to_be_checked()
+ expect(
+ page.locator(
+ "//input[@name='statusText' and @type='radio' and @value='INACTIVE']"
+ )
+ ).not_to_be_checked()
+ expect(page.get_by_role("radio", name="Mobile")).not_to_be_checked()
+ expect(page.get_by_role("radio", name="Static")).not_to_be_checked()
+ for day in ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]:
+ expect(page.get_by_role("textbox", name=day)).to_have_value("0")
+ expect(page.locator("#notesText")).to_be_empty()
+ ScreenshotTool(page).take_screenshot("rlp_unit_tc_5_7_8_9")
+
+
+## Scenario 2 ## Test 6.1.1, 6.1.2, 6.1.3, 6.1.4 ##
+@pytest.mark.rlpunit
+@pytest.mark.parametrize("input_length", [3, 50])
+def test_validate_add_new_unit_list_name_field(
+ page: Page, rlp_screening_unit_list_page: ScreeningUnitListPage, input_length
+) -> None:
+ """Enters data into Screening Unit Name field on the 'Add a new unit' screen using input name lengths are 3 and 50"""
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Unit List")
+
+ unit_name = test_helpers.generate_random_string(
+ input_length
+ ) # this method will generate string between 3 & 100 which will be unique each time
+ rlp_screening_unit_list_page.verify_unit_has_no_matching_records_available_in_the_table(
+ unit_name
+ )
+
+ rlp_screening_unit_list_page.create_unit(unit_name)
+ rlp_screening_unit_list_page.verify_screening_unit_by_name(unit_name)
+ ScreenshotTool(page).take_screenshot("rlp_unit_tc_6")
+
+
+## Scenario 1&2 ## Test 7.2.1, 9.2.1, 9.2.2 ##
+@pytest.mark.rlpunit
+def test_negative_non_integer_values_and_empty_unit_type(
+ page: Page, rlp_screening_unit_list_page: ScreeningUnitListPage
+) -> None:
+ """Null unit_name, unit_type and days are null & non integer"""
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Unit List")
+ unit_data = {
+ "unit_name": "",
+ "thu": "",
+ **{
+ day: "".join(secrets.choice(string.ascii_letters) for _ in range(2))
+ for day in ["sun", "mon", "tue", "wed", "fri", "sat"]
+ },
+ }
+ rlp_screening_unit_list_page.add_screening_unit(
+ unit_data, unit_type="", unit_status=""
+ )
+ rlp_screening_unit_list_page.click_add_screening_unit_btn_on_pop_up_window()
+ expect(page.get_by_text("Name must be populated")).to_be_visible()
+ expect(page.get_by_text("Unit Type must be populated")).to_be_visible()
+ for day in ["Sun", "Mon", "Tues", "Wednes", "Thurs", "Fri", "Satur"]:
+ expect(
+ page.get_by_text(f"{day}day Appointments must be between 0 and 999")
+ ).to_be_visible()
+ ScreenshotTool(page).take_screenshot("rlp_unit_tc_7_9")
+
+
+## Scenario 3 ## negative-Test 6.2.1, 6.2.2, 6.2.3, 6.2.4, 6.2.5, 14.2.1, 15.2.1 ##
+@pytest.mark.rlpunit
+@pytest.mark.parametrize("invalid_name", ["$#%&", "CA", "Batman", " "])
+def test_negative_non_integer_values_and_empty_unit_type(
+ page: Page, rlp_screening_unit_list_page: ScreeningUnitListPage, invalid_name
+) -> None:
+ """Negative test with invalid data for unit_name filed"""
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Unit List")
+
+ unit_name = invalid_name
+ rlp_screening_unit_list_page.create_unit(unit_name)
+ # Asserting expected error messages for invalid names
+ invalid_char_pattern = re.compile("[#$%<>&]")
+ if invalid_char_pattern.findall(unit_name):
+ expect(page.get_by_text("Name contains invalid characters")).to_be_visible()
+ elif len(unit_name) < 3:
+ expect(page.get_by_text("The Name you entered is too short")).to_be_visible()
+ elif unit_name == "Batman":
+ expect(
+ page.get_by_text("Name is already in use by another unit")
+ ).to_be_visible()
+ elif unit_name == " ":
+ expect(page.get_by_text("Unit Type must be populated")).to_be_visible
+ ScreenshotTool(page).take_screenshot("rlp_unit_tc_6_14_15")
+
+
+## Scenario 1 ##Test 10.2.1 ##
+@pytest.mark.rlpunit
+def test_notes_shows_err_msg_for_invalid_chars(
+ page: Page, rlp_screening_unit_list_page: ScreeningUnitListPage
+) -> None:
+ """Negative test with invalid data for notes filed"""
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Unit List")
+
+ unitname = f"ZZZZZZ-notes-{datetime.now()}"
+ unit_data = {"unit_name": unitname, "notes": "#$%&"}
+ rlp_screening_unit_list_page.add_screening_unit(unit_data)
+ rlp_screening_unit_list_page.click_add_screening_unit_btn_on_pop_up_window()
+ expect(
+ page.locator(
+ "//p[@id='error_notesText' and text()='Notes contains invalid characters']"
+ )
+ ).to_be_visible()
+ ScreenshotTool(page).take_screenshot("rlp_unit_tc_10")
+
+
+## Scenario 2 ## Test 14.1.1, 14.1.2, 14.1.3, 14.1.4, 14.1.5, 10.1.1, 15.1.1, 15.1.2, 15.1.3, 15.1.4 ##
+@pytest.mark.rlpunit
+def test_add_screeing_unit_with_defaults(
+ page: Page, rlp_screening_unit_list_page: ScreeningUnitListPage
+) -> None:
+ """Creating screeing unit with defaults and mandatory fields(unit_name, unit_type and days)"""
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Unit List")
+ unit_name = f"Unit_name-{datetime.now()}"
+ rlp_screening_unit_list_page.create_unit(unit_name)
+
+ rlp_screening_unit_list_page.verify_screening_unit_by_name(unit_name)
+ assert page.wait_for_selector("//tr//td[5]").text_content() == "Mobile"
+ assert page.wait_for_selector("//tr//td[6]").text_content() == "Active"
+ for col in range(7, 14):
+ assert page.wait_for_selector(f"//tr//td[{col}]").text_content() == "0"
+ ScreenshotTool(page).take_screenshot("rlp_unit_tc_14_15")
+
+
+## Test_11 ##
+@pytest.mark.rlpunit
+def test_another_user_from_another_bso_creates_the_unit(
+ page: Page, rlp_screening_unit_list_page: ScreeningUnitListPage, context
+) -> None:
+ """Added screening unit is not available for another user from different BSO, and creates the unit with the same data"""
+ # Logged into BSS_SO1 user1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Unit List")
+ unit_name = f"user1-{datetime.now()}"
+ rlp_screening_unit_list_page.create_unit(unit_name)
+ context.clear_cookies()
+ # Logged on as BSS_02 user2
+ UserTools().user_login(page, "Read Only BSO User2 - BS2")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Unit List")
+ rlp_screening_unit_list_page.filter_unit_by_name(unit_name)
+ expect(page.get_by_text("No matching records found")).to_be_visible()
+
+ # user2 is creating the unit in BSO2 with the same data used by user1 in BSO1
+ rlp_screening_unit_list_page.create_unit(unit_name)
+ rlp_screening_unit_list_page.verify_screening_unit_by_name(unit_name)
+ ScreenshotTool(page).take_screenshot("rlp_unit_tc_11")
+
+
+## Test_12 ##
+@pytest.mark.rlpunit
+def test_user2_from_same_bso_can_access_the_user1_data(
+ page: Page, rlp_screening_unit_list_page: ScreeningUnitListPage, context
+) -> None:
+ """Added screening unit is available for another user from same BSO, and tried to create the unit with the same data, it'll throw an error"""
+ # Logged into BSS_SO1 user1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Unit List")
+ unit_name = f"user1-{datetime.now()}"
+ rlp_screening_unit_list_page.create_unit(unit_name)
+ context.clear_cookies()
+ # Logged on as BSS_SO1 user2
+ UserTools().user_login(page, "BSO User2 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Unit List")
+ rlp_screening_unit_list_page.verify_screening_unit_by_name(unit_name)
+ # user2 tries to create the unit with existing unit name, it'll display an error msg
+ rlp_screening_unit_list_page.create_unit(unit_name)
+ ScreenshotTool(page).take_screenshot("rlp_unit_tc_12")
+ expect(page.get_by_text("Name is already in use by another unit")).to_be_visible()
diff --git a/tests/ui/test_ceased.py b/tests/ui/test_ceased.py
new file mode 100644
index 00000000..48bfbefa
--- /dev/null
+++ b/tests/ui/test_ceased.py
@@ -0,0 +1,252 @@
+"""
+Ceased Tests: These tests cover the Ceased subject monitoring reports.
+"""
+
+import pytest
+from pages.main_menu import MainMenuPage
+from utils.user_tools import UserTools
+from playwright.sync_api import Page, expect
+from pages.monitoring_reports.ceased_unceased import CeasedUnceasedPage
+from utils.screenshot_tool import ScreenshotTool
+
+
+pytestmark = [pytest.mark.ceased]
+
+# Fixtures
+
+
+@pytest.fixture(autouse=True)
+def log_in(user_tools: UserTools, page: Page) -> None:
+ user_tools.user_login(page, "BSO User - BS1")
+
+
+@pytest.fixture
+def ceased_page(page: Page) -> CeasedUnceasedPage:
+ MainMenuPage(page).select_menu_option(
+ "Monitoring Reports", "Ceased/Unceased Subject List"
+ )
+ ceased_page = CeasedUnceasedPage(page)
+ ceased_page.verify_header()
+ return ceased_page
+
+
+# Tests
+
+
+def test_ceased_both_view_subject(page: Page, ceased_page: CeasedUnceasedPage) -> None:
+ """
+ Search Both on the Ceased/Unceased Subject List report
+ """
+ ceased_page.search_both()
+ expect(page.locator("#subjectCeasingList")).to_be_visible()
+ ScreenshotTool(page).take_screenshot("ceased_both_view_subject")
+
+
+def test_ceased_view_subject(page: Page, ceased_page: CeasedUnceasedPage) -> None:
+ """
+ Select Ceased on the Ceased/Unceased Subject List report
+ """
+ ceased_page.search_unceased(False)
+ ceased_page.search_ceased()
+ expect(page.locator("#subjectCeasingList")).to_be_visible()
+ ScreenshotTool(page).take_screenshot("ceased_view_subject")
+
+
+def test_unceased_view_subject(page: Page, ceased_page: CeasedUnceasedPage) -> None:
+ """
+ Select Unceased on the Ceased/Unceased Subject List report
+ """
+ ceased_page.search_unceased()
+ expect(page.locator("#subjectCeasingList")).to_be_visible()
+ ScreenshotTool(page).take_screenshot("unceased_view_subject")
+
+
+def test_both_date(page: Page, ceased_page: CeasedUnceasedPage) -> None:
+ """
+ Select Date from the Date Picker and Both on the Ceased/Unceased Subject List report
+ """
+ ceased_page.both_date()
+ expect(page.locator("#subjectCeasingList")).to_be_visible()
+ ScreenshotTool(page).take_screenshot("ceased_both_date")
+
+
+def test_ceased_only_date(page: Page, ceased_page: CeasedUnceasedPage) -> None:
+ """
+ Select Date from the Date Picker and Ceased on the Ceased/Unceased Subject List report
+ """
+ ceased_page.ceased_only_date()
+ expect(page.locator("#subjectCeasingList")).to_be_visible()
+ ScreenshotTool(page).take_screenshot("ceased_only_date")
+
+
+def test_unceased_only_date(page: Page, ceased_page: CeasedUnceasedPage) -> None:
+ """
+ Select Date from the Date Picker and Uneased on the Ceased/Unceased Subject List report
+ """
+ ceased_page.unceased_only_date()
+ expect(page.locator("#subjectCeasingList")).to_be_visible()
+ ScreenshotTool(page).take_screenshot("unceased_only_date")
+
+
+def test_ceased_both_done(page: Page, ceased_page: CeasedUnceasedPage) -> None:
+ """
+ Select Both and All from the Done drop down menu on the Ceased/Unceased Subject List report
+ """
+ ceased_page.search_both()
+ expect(page.locator("#subjectCeasingList")).to_be_visible()
+ ceased_page.set_done_drop_down("All")
+ ScreenshotTool(page).take_screenshot("ceased_both_done")
+
+
+def test_ceased_both_nhs_number(page: Page, ceased_page: CeasedUnceasedPage) -> None:
+ """
+ Select Both and enter NHS Number in the filter field on the Ceased/Unceased Subject List report
+ """
+ ceased_page.search_both()
+ expect(page.locator("#subjectCeasingList")).to_be_visible()
+ nhs_number = page.locator(ceased_page.TABLE_FIRST_NHS_NUMBER).text_content()
+ ceased_page.enter_nhs_number(nhs_number)
+ expect(page.locator(ceased_page.TABLE_FIRST_NHS_NUMBER)).to_have_text(nhs_number)
+ ScreenshotTool(page).take_screenshot("ceased_both_nhs_number")
+
+
+def test_ceased_both_date_added_to_BSO(
+ page: Page, ceased_page: CeasedUnceasedPage
+) -> None:
+ """
+ Select Both and select sorting on Date added to BSO on the Ceased/Unceased Subject List report
+ """
+ ceased_page.search_both()
+ expect(page.locator("#subjectCeasingList")).to_be_visible()
+ ceased_page.sort_date_added_to_BSO()
+ expect(page.locator("#columnHeaderNhsNumber").nth(1)).to_have_attribute(
+ "aria-sort", "ascending"
+ )
+ ScreenshotTool(page).take_screenshot("ceased_both_date_added_to_BSO")
+
+
+def test_ceased_both_born(page: Page, ceased_page: CeasedUnceasedPage) -> None:
+ """
+ Select Both and select sorting on Born on the Ceased/Unceased Subject List report
+ """
+ ceased_page.search_both()
+ expect(page.locator("#subjectCeasingList")).to_be_visible()
+ ceased_page.sort_born()
+ expect(page.locator("#columnHeaderNhsNumber").nth(2)).to_have_attribute(
+ "aria-sort", "ascending"
+ )
+ ScreenshotTool(page).take_screenshot("ceased_both_born")
+
+
+def test_ceased_both_age_today(page: Page, ceased_page: CeasedUnceasedPage) -> None:
+ """
+ Select Both and select Age "In BSO Age Range" from the drop down menu on the Ceased/Unceased Subject List report
+ """
+ ceased_page.search_both()
+ expect(page.locator("#subjectCeasingList")).to_be_visible()
+ selected_age = "In BSO Age Range"
+ ceased_page.table_filtered_by_age(selected_age)
+ for age in ceased_page.AGE_OPTIONS:
+ if age != selected_age:
+ expect(page.locator("tbody")).not_to_contain_text(age)
+ ScreenshotTool(page).take_screenshot("ceased_both_age_today")
+
+
+def test_ceased_both_done_age_today(
+ page: Page, ceased_page: CeasedUnceasedPage
+) -> None:
+ """
+ Select Both, select All from the Done drop down menu and select Age "In BSO Age Range" from the drop down menu on the Ceased/Unceased Subject List report
+ """
+ ceased_page.search_both()
+ expect(page.locator("#subjectCeasingList")).to_be_visible()
+ ceased_page.set_done_drop_down("All")
+ selected_age = "In BSO Age Range"
+ ceased_page.table_filtered_by_age(selected_age)
+ for age in ceased_page.AGE_OPTIONS:
+ if age != selected_age:
+ expect(page.locator("tbody")).not_to_contain_text(age)
+ ScreenshotTool(page).take_screenshot("ceased_both_done_age_today")
+
+
+def test_ceased_both_done_all_age_today(
+ page: Page, ceased_page: CeasedUnceasedPage
+) -> None:
+ """
+ Select Both, select All from the Done drop down menu and select Age "All" from the drop down menu on the Ceased/Unceased Subject List report
+ """
+ ceased_page.search_both()
+ expect(page.locator("#subjectCeasingList")).to_be_visible()
+ ceased_page.set_done_drop_down("All")
+ selected_age = "In BSO Age Range"
+ ceased_page.table_filtered_by_age(selected_age)
+ for age in ceased_page.AGE_OPTIONS:
+ if age != selected_age:
+ expect(page.locator("tbody")).not_to_contain_text(age)
+ ScreenshotTool(page).take_screenshot("ceased_both_done_all_age_today")
+
+
+def test_ceased_both_family_name(page: Page, ceased_page: CeasedUnceasedPage) -> None:
+ """
+ Select Both and enter Family Name in the filter field on the Ceased/Unceased Subject List report
+ """
+ ceased_page.search_both()
+ expect(page.locator("#subjectCeasingList")).to_be_visible()
+ family_name = page.locator(ceased_page.TABLE_FIRST_FAMILY_NAME).text_content()
+ ceased_page.enter_family_name(family_name)
+ expect(page.locator(ceased_page.TABLE_FIRST_FAMILY_NAME)).to_have_text(family_name)
+ ScreenshotTool(page).take_screenshot("ceased_both_family_name")
+
+
+def test_ceased_first_name(page: Page, ceased_page: CeasedUnceasedPage) -> None:
+ """
+ Select Both and enter First Given Name in the filter field on the Ceased/Unceased Subject List report
+ """
+ ceased_page.search_both()
+ expect(page.locator("#subjectCeasingList")).to_be_visible()
+ first_name = page.locator(ceased_page.TABLE_FIRST_FIRST_NAME).text_content()
+ ceased_page.enter_first_name(first_name)
+ expect(page.locator(ceased_page.TABLE_FIRST_FIRST_NAME)).to_have_text(first_name)
+ ScreenshotTool(page).take_screenshot("ceased_first_name")
+
+
+def test_ceased_both_date_ceased(page: Page, ceased_page: CeasedUnceasedPage) -> None:
+ """
+ Select Both, All from the Done drop down menu and select sorting on Date Ceased on the Ceased/Unceased Subject List report
+ """
+ ceased_page.search_both()
+ expect(page.locator("#subjectCeasingList")).to_be_visible()
+ ceased_page.set_done_drop_down("All")
+ ceased_page.sort_date_ceased()
+ expect(page.locator("#columnHeaderDateCeased")).to_have_attribute(
+ "aria-sort", "ascending"
+ )
+ ScreenshotTool(page).take_screenshot("ceased_both_date_ceased")
+
+
+def test_ceased_both_date_unceased(page: Page, ceased_page: CeasedUnceasedPage) -> None:
+ """
+ Select Both, All from the Done drop down menu and select sorting on Date Unceased on the Ceased/Unceased Subject List report
+ """
+ ceased_page.search_both()
+ expect(page.locator("#subjectCeasingList")).to_be_visible()
+ ceased_page.set_done_drop_down("All")
+ ceased_page.sort_date_unceased()
+ expect(page.locator("#columnHeaderDateUnceased")).to_have_attribute(
+ "aria-sort", "ascending"
+ )
+ ScreenshotTool(page).take_screenshot("ceased_both_date_unceased")
+
+
+def test_ceased_both_reason(page: Page, ceased_page: CeasedUnceasedPage) -> None:
+ """
+ Select Both and Reason "Informed Choice" on the Ceased/Unceased Subject List report
+ """
+ ceased_page.search_both()
+ expect(page.locator("#subjectCeasingList")).to_be_visible()
+ selected_reason = "Informed Choice"
+ ceased_page.reason_selected(selected_reason)
+ for reason in ceased_page.REASON_OPTIONS:
+ if reason != selected_reason:
+ expect(page.locator("tbody")).not_to_contain_text(reason)
+ ScreenshotTool(page).take_screenshot("ceased_both_reason")
diff --git a/tests/ui/test_gp_practice_list.py b/tests/ui/test_gp_practice_list.py
new file mode 100644
index 00000000..241a1104
--- /dev/null
+++ b/tests/ui/test_gp_practice_list.py
@@ -0,0 +1,31 @@
+"""
+GP Practice List Tests: These tests cover the GP Practice List, accessed from the BSO Mapping tab.
+"""
+
+import csv
+import pytest
+from pages.main_menu import MainMenuPage
+from utils.nhs_number_tools import NHSNumberTools
+from utils.user_tools import UserTools
+from utils.table_utils import TableUtils
+from playwright.sync_api import Page, expect
+from pages.bso_mapping.gp_practice_list import GPListPage
+from utils.screenshot_tool import ScreenshotTool
+
+
+pytestmark = [pytest.mark.gppracticelist]
+
+# Fixtures
+
+
+@pytest.fixture(autouse=True)
+def log_in(user_tools: UserTools, page: Page) -> None:
+ user_tools.user_login(page, "BSO User - BS1")
+
+
+@pytest.fixture
+def gplist_page(page: Page) -> GPListPage:
+ MainMenuPage(page).select_menu_option("BSO Mapping", "GP Practice List")
+ gplist_page = GPListPage(page)
+ gplist_page.verify_header()
+ return gplist_page
diff --git a/tests/ui/test_ntdd_additional_testing.py b/tests/ui/test_ntdd_additional_testing.py
new file mode 100644
index 00000000..a439539e
--- /dev/null
+++ b/tests/ui/test_ntdd_additional_testing.py
@@ -0,0 +1,126 @@
+from itertools import count
+import playwright
+import pytest
+
+# from conftest import ni_ri_sp_batch_page
+from pages.main_menu import MainMenuPage
+from playwright.sync_api import expect, Page, Playwright
+from datetime import datetime
+from pages.ni_ri_sp_batch_page import NiRiSpBatchPage
+from utils import test_helpers
+from utils.CheckDigitGenerator import CheckDigitGenerator
+from utils.user_tools import UserTools
+from utils.screenshot_tool import ScreenshotTool
+
+
+@pytest.fixture()
+def login_and_create_ntdd_batch(
+ page: Page, ni_ri_sp_batch_page: NiRiSpBatchPage
+) -> dict:
+ """Fixture to log in and create a NTDD batch."""
+ # Logged into BSO3 as user3(PMA) and select 'Create NTDD Batch' from the 'batch management' dropdown
+ UserTools().user_login(page, "Ni Only BSO User3 - BS3")
+ MainMenuPage(page).select_menu_option("Batch Management", "Create NTDD Batch")
+ ni_ri_sp_batch_page.assert_page_header("Create NTDD Batch")
+ batch_title = f"ntdd-{datetime.now()}"
+ check_digit = CheckDigitGenerator().generate_check_digit()
+ ni_ri_sp_batch_page.enter_bso_batch_id(check_digit)
+ ni_ri_sp_batch_page.enter_batch_title(batch_title)
+ ni_ri_sp_batch_page.select_ntd_end_date(5)
+ ni_ri_sp_batch_page.select_range_by_age()
+ ni_ri_sp_batch_page.enter_ntdd_start_age_year("50")
+ ni_ri_sp_batch_page.enter_ntdd_start_age_month("1")
+ ni_ri_sp_batch_page.enter_ntdd_end_age_year("70")
+ ni_ri_sp_batch_page.enter_ntdd_end_age_month("11")
+ ni_ri_sp_batch_page.check_include_younger_women()
+ ni_ri_sp_batch_page.click_count_button()
+ return {"batch_title": batch_title, "check_digit": check_digit}
+
+
+# TC-6
+@pytest.mark.tc6
+@pytest.mark.ntdd
+def test_count_display_for_ntdd_with_blank_selected_date_selected_and_rejected_fields(
+ page: Page, ni_ri_sp_batch_page: NiRiSpBatchPage, login_and_create_ntdd_batch: dict
+) -> None:
+ """verify NTDD Batch by Year of Birth has been counted and counted is not selected assert Select Date, Selected, and Rejected fields are blank"""
+ # Logged into BSO3 as user3(PMA) and select 'Create RI/SP Batch' from the 'batch management' dropdown
+ ScreenshotTool(page).take_screenshot("additional_testing_TC_6")
+ ni_ri_sp_batch_page.assert_page_header("Amend NTDD Batch")
+ MainMenuPage(page).select_menu_option("Batch Management", "Batch List")
+ ni_ri_sp_batch_page.assert_page_header("Batch List")
+ ni_ri_sp_batch_page.assert_entered_bso_batch_id_and_filterd_row_value(
+ login_and_create_ntdd_batch["check_digit"]
+ )
+ ni_ri_sp_batch_page.assert_select_date_cell_value("")
+ ni_ri_sp_batch_page.assert_selected_cell_value("")
+ ni_ri_sp_batch_page.assert_rejected_cell_value("")
+ ScreenshotTool(page).take_screenshot("additional_testing_TC_6.1")
+
+
+# TC-7
+@pytest.mark.tc7
+@pytest.mark.ntdd
+def test_count_display_for_ntdd_with_selected_date_selected_and_rejected_fields(
+ page: Page, ni_ri_sp_batch_page: NiRiSpBatchPage, login_and_create_ntdd_batch: dict
+) -> None:
+ """verify NTDD Batch by Year of Birth has been counted and counted is selected assert Select Date, Selected, and Rejected fields should not be blank"""
+ # Logged into BSO3 as user3(PMA) and select 'Create RI/SP Batch' from the 'batch management' dropdown
+ ScreenshotTool(page).take_screenshot("additional_testing_TC_7")
+ ni_ri_sp_batch_page.assert_page_header("Amend NTDD Batch")
+ ni_ri_sp_batch_page.click_count_select_button()
+ MainMenuPage(page).select_menu_option("Batch Management", "Batch List")
+ ni_ri_sp_batch_page.assert_page_header("Batch List")
+ ni_ri_sp_batch_page.assert_entered_bso_batch_id_and_filterd_row_value(
+ login_and_create_ntdd_batch["check_digit"]
+ )
+ ni_ri_sp_batch_page.assert_select_date_cell_value_is_not_null("")
+ ni_ri_sp_batch_page.assert_selected_cell_value_is_not_null("")
+ ni_ri_sp_batch_page.assert_rejected_cell_value_is_not_null("")
+ ScreenshotTool(page).take_screenshot("additional_testing_TC_7.1")
+
+
+# TC-8
+# TODO
+@pytest.mark.tc8
+@pytest.mark.ntdd
+def test_count_display_for_ntdd_with_selected_date_selected_and_rejected_fields(
+ page: Page, ni_ri_sp_batch_page: NiRiSpBatchPage, login_and_create_ntdd_batch: dict
+) -> None:
+ """verify NTDD Batch by Year of Birth has been counted and counted is selected assert Select Date, Selected, and Rejected fields should not be blank"""
+ # Logged into BSO3 as user3(PMA) and select 'Create RI/SP Batch' from the 'batch management' dropdown
+ ScreenshotTool(page).take_screenshot("additional_testing_TC_8")
+ ni_ri_sp_batch_page.assert_page_header("Amend NTDD Batch")
+ ni_ri_sp_batch_page.click_count_select_button()
+ MainMenuPage(page).select_menu_option("Batch Management", "Batch List")
+ ni_ri_sp_batch_page.assert_page_header("Batch List")
+ ni_ri_sp_batch_page.assert_entered_bso_batch_id_and_filterd_row_value(
+ login_and_create_ntdd_batch["check_digit"]
+ )
+ ni_ri_sp_batch_page.assert_select_date_cell_value_is_not_null("")
+ ni_ri_sp_batch_page.assert_selected_cell_value_is_not_null("")
+ ni_ri_sp_batch_page.assert_rejected_cell_value_is_not_null("")
+ ScreenshotTool(page).take_screenshot("additional_testing_TC_8.1")
+
+
+# TC-9
+@pytest.mark.tc9
+@pytest.mark.ntdd
+def test_count_display_for_ntdd_batch_with_selected_date_selected_and_rejected_fields_search_by_bso_batch_id_and_batch_title(
+ page: Page, ni_ri_sp_batch_page: NiRiSpBatchPage, login_and_create_ntdd_batch: dict
+) -> None:
+ """verify NTDD Batch by Year of Birth has been counted and counted is selected assert Select Date, Selected, and Rejected fields has a value and
+ search by bso batch id and batch title
+ """
+ # Logged into BSO3 as user3(PMA) and select 'Create RI/SP Batch' from the 'batch management' dropdown
+ ScreenshotTool(page).take_screenshot("additional_testing_TC_9")
+ ni_ri_sp_batch_page.assert_page_header("Amend NTDD Batch")
+ ni_ri_sp_batch_page.click_count_select_button()
+ ni_ri_sp_batch_page.click_count_select_button()
+ MainMenuPage(page).select_menu_option("Batch Management", "Batch List")
+ ni_ri_sp_batch_page.assert_page_header("Batch List")
+ ni_ri_sp_batch_page.search_by_bso_batch_id_and_batch_title(
+ login_and_create_ntdd_batch["check_digit"],
+ login_and_create_ntdd_batch["batch_title"],
+ )
+ ScreenshotTool(page).take_screenshot("additional_testing_TC_9.1")
diff --git a/tests/ui/test_rlp_screening_location_list.py b/tests/ui/test_rlp_screening_location_list.py
new file mode 100644
index 00000000..76fd1f9a
--- /dev/null
+++ b/tests/ui/test_rlp_screening_location_list.py
@@ -0,0 +1,375 @@
+import pytest
+from pages.main_menu import MainMenuPage
+from pages.rlp_location_list_page import ScreeningLocationListPage
+from pages.rlp_cohort_list_page import CohortListPage
+from playwright.sync_api import expect, Page
+from datetime import datetime
+from pages.rlp_unit_list_page import ScreeningUnitListPage
+from utils.test_helpers import generate_random_string
+from utils.screenshot_tool import ScreenshotTool
+from utils.user_tools import UserTools
+
+
+#### Test_01
+#### Test_03
+def test_paging_of_screening_location_list(
+ page: Page, rlp_location_list_page: ScreeningLocationListPage, db_util
+) -> None:
+ """
+ Test to check the paging of the location list
+ """
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User1 - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Location List")
+ # Expected to see the screening location page
+ expect(page.get_by_text("Screening location list", exact=True)).to_be_visible()
+ # Checking the paging info
+ db_row_count = rlp_location_list_page.screening_location_count_in_db(db_util)
+ ui_row_count = rlp_location_list_page.extract_paging_info()
+ assert db_row_count == int(ui_row_count)
+
+
+#### Test_04
+def test_add_screening_location(
+ page: Page, rlp_location_list_page: ScreeningLocationListPage
+) -> None:
+ """
+ Test to add location to the location list
+ """
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Location List")
+ # Adding new screening location
+ location_name = f"Location_name-{datetime.now()}"
+ rlp_location_list_page.click_add_screening_location_btn()
+ rlp_location_list_page.enter_screening_location_name(location_name)
+ rlp_location_list_page.click_add_screening_location_btn_on_popup()
+ # Entering the newly added location name in the search box
+ rlp_location_list_page.enter_screening_location_filter_textbox(location_name)
+ # Capturing the search value and stored it in the search_value
+ search_value = page.locator("//tbody/tr/td[2]").text_content()
+ page.wait_for_timeout(4000)
+ ScreenshotTool(page).take_screenshot("rlp_unit_tc_4")
+ # asserting the location_name and search_value
+ assert location_name == search_value
+
+
+#### Test_04 Negative test
+def test_cancel_screening_location(
+ page: Page, rlp_location_list_page: ScreeningLocationListPage
+) -> None:
+ """
+ Test to cancel the screening location and asserting the values of before and after the cancellation,
+ both values should be the same
+ """
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Location List")
+ # Extracting page_info before cancellation
+ before_cancellation = rlp_location_list_page.extract_paging_info()
+ # Cancel add screening location
+ location_name = f"Location_name-{datetime.now()}"
+ rlp_location_list_page.click_add_screening_location_btn()
+ rlp_location_list_page.enter_screening_location_name(location_name)
+ rlp_location_list_page.click_cancel_add_screening_location_btn()
+ # Extracting page_info after cancellation
+ after_cancellation = rlp_location_list_page.extract_paging_info()
+ # Asserting page number before and after the cancellation
+ assert before_cancellation == after_cancellation
+
+
+#### Test_05
+def test_default_screening_location_values(
+ page: Page, rlp_location_list_page: ScreeningLocationListPage
+) -> None:
+ """
+ Test to verify the default location field, field should be empty
+ """
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Location List")
+ # Expected default values should be empty
+ rlp_location_list_page.click_add_screening_location_btn()
+ expect(page.locator("#locationNameText")).to_be_empty()
+
+
+#### Test_06 valid_data
+@pytest.mark.parametrize("input_length", [3, 100])
+def test_create_location_valid_data_positive(
+ page: Page, rlp_location_list_page: ScreeningLocationListPage, input_length
+) -> None:
+ """
+ Test for creating screening location using the valid field length of 3 and 100 char,
+ asserting created value and the actual value
+ """
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Location List")
+ location_name = generate_random_string(input_length)
+ rlp_location_list_page.click_add_screening_location_btn()
+ rlp_location_list_page.enter_screening_location_name(location_name)
+ rlp_location_list_page.click_add_screening_location_btn_on_popup()
+ rlp_location_list_page.enter_screening_location_filter_textbox(location_name)
+ page.wait_for_timeout(4000)
+ # Capturing the search value and stored it in the search_value
+ search_value = page.locator("//tbody/tr/td[2]").text_content()
+ # asserting the location_name and search_value
+ assert location_name == search_value
+
+
+#### Test_06 negative invalid_data
+@pytest.mark.parametrize(
+ "invalid_data, expected_message",
+ [
+ ("$%&@", "Name contains invalid characters"),
+ ("cd", "The Name you entered is too short"),
+ (" ", "Name must be populated"),
+ (
+ "Aldi - Caldecott County Retail Park",
+ "Name is already in use by another location",
+ ),
+ ],
+)
+def test_create_location_invalid_data_negative(
+ page: Page,
+ rlp_location_list_page: ScreeningLocationListPage,
+ invalid_data,
+ expected_message,
+) -> None:
+ """
+ Test for try creating the screening location with invalid data "$%&@","cd", " ", already existing name
+ and expecting the error values
+ """
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Location List")
+ # Enter invalid_data in the location name txt box
+ rlp_location_list_page.click_add_screening_location_btn()
+ rlp_location_list_page.enter_screening_location_name(invalid_data)
+ rlp_location_list_page.click_add_screening_location_btn_on_popup()
+ # Assert that the correct error message is displayed based on invalid_data
+ expect(page.get_by_text(expected_message)).to_be_visible()
+
+
+#### Test_07
+def test_check_availability_of_locations_different_user_same_bso(
+ page: Page, rlp_location_list_page: ScreeningLocationListPage, context
+) -> None:
+ """
+ under same bso, location was created by user_1 and user_2 from same bso can have access to the location
+ """
+ # Logged into BSS_SO1_User1
+ UserTools().user_login(page, "BSO User - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Location List")
+ # Adding new screening location
+ location_name = f"location_name-{datetime.now()}"
+ rlp_location_list_page.click_add_screening_location_btn()
+ rlp_location_list_page.enter_screening_location_name(location_name)
+ rlp_location_list_page.click_add_screening_location_btn_on_popup()
+ context.clear_cookies()
+ # Logged into BSS_SO1_User2
+ UserTools().user_login(page, "BSO User - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Location List")
+ # Searching for the screening_location created by User 1 while logged in as User 2
+ rlp_location_list_page.enter_screening_location_filter_textbox(location_name)
+ # storing the User_2 filtered location_name in filtered_name
+ filtered_name = rlp_location_list_page.value_of_filtered_location_name()
+ # asserting the the location name created by User_1 and filtered by User_2
+ assert location_name == filtered_name
+ # User_2 is trying to create a location already created by User_1
+ rlp_location_list_page.click_add_screening_location_btn()
+ rlp_location_list_page.enter_screening_location_name(location_name)
+ rlp_location_list_page.click_add_screening_location_btn_on_popup()
+ # Test should throw an error message
+ expect(
+ page.get_by_text("Name is already in use by another location")
+ ).to_be_visible()
+
+
+#### Test_08
+def test_check_availability_of_locations_different_user_different_bso(
+ page: Page, rlp_location_list_page: ScreeningLocationListPage, context
+) -> None:
+ """
+ Location was created by user_1, user_2 from different bso can't have access to the location
+ user 2 from different bso try to search for the location created by user 1 will get a response as "No matching records found"
+ """
+ # Logged into BSS_SO1_User1
+ UserTools().user_login(page, "BSO User - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Location List")
+ # Adding new screening location
+ location_name = f"Location_name-{datetime.now()}"
+ rlp_location_list_page.click_add_screening_location_btn()
+ rlp_location_list_page.enter_screening_location_name(location_name)
+ rlp_location_list_page.click_add_screening_location_btn_on_popup()
+ rlp_location_list_page.click_log_out_btn()
+ context.clear_cookies()
+ # Logged into BSS_SO2_User2
+ UserTools().user_login(page, "Read Only BSO User - BS2")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Location List")
+ # BSO specific Locations are not available to other Users within other BSOs
+ rlp_location_list_page.enter_screening_location_filter_textbox(location_name)
+ expect(page.get_by_text("No matching records found")).to_be_visible()
+ rlp_location_list_page.click_add_screening_location_btn()
+ rlp_location_list_page.enter_screening_location_name(location_name)
+ rlp_location_list_page.click_add_screening_location_btn_on_popup()
+ # storing the User_2 created and filtered location_name in filtered_name
+ rlp_location_list_page.enter_screening_location_filter_textbox(location_name)
+ filtered_name = rlp_location_list_page.value_of_filtered_location_name()
+ # asserting the the location name created and filtered by User_2
+ assert location_name == filtered_name
+
+
+#### Test covers Test_09, Test_10 and Test_11(valid_data_set)
+@pytest.mark.parametrize("input_length", [3, 100])
+def test_amend_screening_location(
+ page: Page, rlp_location_list_page: ScreeningLocationListPage, input_length
+) -> None:
+ """
+ Test for amending screening location, in here I created the location and amended the location using the valid field length of 3 and 100 char,
+ asserting the amend_name vs actual amended value
+ """
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Location List")
+ # Adding new screening location
+ location_name = f"location_name-{datetime.now()}"
+ rlp_location_list_page.click_add_screening_location_btn()
+ rlp_location_list_page.enter_screening_location_name(location_name)
+ rlp_location_list_page.click_add_screening_location_btn_on_popup()
+ # Edit the newly added location
+ rlp_location_list_page.enter_screening_location_filter_textbox(location_name)
+ rlp_location_list_page.invoke_filtered_screening_location()
+ expect(
+ page.locator("#amendButtonInAmendLocationPopupText")
+ ).to_be_visible() # Test 9 verification
+ # Amending the newly added location using generate_random string method and storing in the "amend_name" and clicking amend_button on the pop_up window
+ amend_name = generate_random_string(input_length)
+ rlp_location_list_page.enter_amend_screening_location_name(amend_name)
+ rlp_location_list_page.click_amend_screening_location_btn()
+ # entering Amend_name in the search box
+ rlp_location_list_page.enter_screening_location_filter_textbox(amend_name)
+ # storing the filtered location value in the "filtered_amend_name"
+ filtered_amend_name = rlp_location_list_page.value_of_filtered_location_name()
+ # asserting the amend_value and filtered_amend_value
+ assert amend_name == filtered_amend_name
+
+
+## Test_11 negative test with invalid_data
+@pytest.mark.parametrize(
+ "invalid_data, expected_message",
+ [
+ ("$%&@", "Name contains invalid characters"),
+ ("cd", "The Name you entered is too short"),
+ (" ", "Name must be populated"),
+ (
+ "Aldi - Caldecott County Retail Park",
+ "Name is already in use by another location",
+ ),
+ ],
+)
+def test_invalid_amend_screening_location(
+ page: Page,
+ rlp_location_list_page: ScreeningLocationListPage,
+ invalid_data,
+ expected_message,
+) -> None:
+ """
+ Test for amending screening location with invalid data "$%&@","cd", " ", already existing name
+ and expecting the error values
+ """
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Location List")
+ # Adding new screening location
+ location_name = f"location_name-{datetime.now()}"
+ rlp_location_list_page.click_add_screening_location_btn()
+ rlp_location_list_page.enter_screening_location_name(location_name)
+ rlp_location_list_page.click_add_screening_location_btn_on_popup()
+ # Edit the pre existing location
+ rlp_location_list_page.enter_screening_location_filter_textbox(location_name)
+ rlp_location_list_page.invoke_filtered_screening_location()
+ # Enter the invalid amend data
+ rlp_location_list_page.enter_amend_screening_location_name(invalid_data)
+ rlp_location_list_page.click_amend_screening_location_btn()
+ # Assert that the correct error message is displayed based on invalid_data
+ expect(page.get_by_text(expected_message)).to_be_visible()
+
+
+## Test_12
+def test_amend_screening_location_available_for_other_user_within_same_bso(
+ page: Page, rlp_location_list_page: ScreeningLocationListPage, context
+) -> None:
+ """
+ Test for amending screening location by user2 with in the same bso which is created by user1
+ asserting the amended value vs actual amended value
+ """
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Location List")
+ # Adding new screening location
+ location_name = f"Location_name-{datetime.now()}"
+ rlp_location_list_page.click_add_screening_location_btn()
+ rlp_location_list_page.enter_screening_location_name(location_name)
+ rlp_location_list_page.click_add_screening_location_btn_on_popup()
+ # Edit the newly added location
+ rlp_location_list_page.enter_screening_location_filter_textbox(location_name)
+ rlp_location_list_page.invoke_filtered_screening_location()
+
+ # Amending the newly added location using generate_random string method and storing in the "amend_name" and clicking amend_button on the pop_up window
+ amend_name = f"amend_location-{datetime.now()}"
+ rlp_location_list_page.enter_amend_screening_location_name(amend_name)
+ rlp_location_list_page.click_amend_screening_location_btn()
+ rlp_location_list_page.click_log_out_btn()
+ context.clear_cookies()
+
+ # Logged into BSS_SO1_User2
+ UserTools().user_login(page, "BSO User - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Location List")
+ # Searching for the screening_location created by User 1 while logged in as User 2
+ rlp_location_list_page.enter_screening_location_filter_textbox(amend_name)
+ # storing the User_2 filtered location_name in filtered_name
+ filtered_name = rlp_location_list_page.value_of_filtered_location_name()
+ # asserting the the location name created by User_1 and filtered by User_2
+ assert amend_name == filtered_name
+
+
+## Test_13
+def test_location_linked_to_multiple_cohorts(
+ page: Page, rlp_cohort_list_page: CohortListPage, context
+) -> None:
+ """
+ created 2 cohorts and 1 location, linked 2 cohorts to 1 location
+ """
+ # create unit for test data
+ UserTools().user_login(page, "BSO User - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Unit List")
+ ScreeningUnitListPage(page).create_unit("Batman")
+ context.clear_cookies()
+ # Logged into BSS_SO1
+ UserTools().user_login(page, "BSO User - BS1")
+ MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
+
+ # create cohort inner function
+ def create_cohort(location_name, cohort_name):
+ rlp_cohort_list_page.click_create_screening_cohort_by_gp_practice_btn()
+ rlp_cohort_list_page.enter_screening_cohort_name_field(cohort_name)
+ rlp_cohort_list_page.enter_expected_attendance_rate("25")
+ rlp_cohort_list_page.select_default_screening_location_dropdown(location_name)
+ rlp_cohort_list_page.select_default_screening_unit_dropdown("Batman")
+ rlp_cohort_list_page.click_create_screening_cohort_save_btn()
+ page.wait_for_timeout(3000)
+
+ location_name = "Aldi - Caldecott County Retail Park"
+ cohort_name = f"Multiple_cohorts-{datetime.now()}"
+ input_names = [f"{cohort_name} - Test 1", f"{cohort_name} - Test 2"]
+
+ # creating multiple(2) cohorts in a loop
+ for name in input_names:
+ create_cohort(location_name, name)
+
+ # filtering name and location
+ rlp_cohort_list_page.enter_screening_cohort_name_filter(cohort_name)
+ rlp_cohort_list_page.enter_screening_location_filter(location_name)
+ filtered_names = page.locator("//tr//td[2]").all_text_contents()
+ assert input_names == filtered_names
diff --git a/tests/ui/test_smoke.py b/tests/ui/test_smoke.py
new file mode 100644
index 00000000..611e43e5
--- /dev/null
+++ b/tests/ui/test_smoke.py
@@ -0,0 +1,109 @@
+"""
+Smoke Tests: These are the smoke tests for the BS-Select pipeline.
+"""
+
+import pytest
+from pages.main_menu import MainMenuPage
+from utils.user_tools import UserTools
+from playwright.sync_api import Page, expect
+
+
+pytestmark = [pytest.mark.smoke]
+
+# Fixtures
+
+
+@pytest.fixture(autouse=True)
+def log_in(user_tools: UserTools, page: Page) -> None:
+ user_tools.user_login(page, "BSO User - BS1")
+
+
+# Tests
+
+
+def test_subject_search(page: Page) -> None:
+ """
+ From the main menu, navigate to Subject Search and confirm the header has rendered correctly.
+ """
+ MainMenuPage(page).select_menu_option("Subject Search")
+ expect(page.get_by_role("heading")).to_contain_text("Subject Search")
+
+
+def test_batch_list(page: Page) -> None:
+ """
+ From the main menu, navigate to Batch Management -> Batch List and confirm the header has rendered correctly.
+ """
+ MainMenuPage(page).select_menu_option("Batch Management", "Batch List")
+ expect(page.get_by_role("heading")).to_contain_text("Batch List")
+
+
+def test_outcome_list(page: Page) -> None:
+ """
+ From the main menu, navigate to Outcome List and confirm the header has rendered correctly.
+ """
+ MainMenuPage(page).select_menu_option("Outcome List")
+ expect(page.get_by_role("heading")).to_contain_text("Outcome List")
+
+
+def test_parameters(page: Page) -> None:
+ """
+ From the main menu, navigate to Parameters -> Monthly Failsafe Report and confirm the header has rendered correctly.
+ """
+ MainMenuPage(page).select_menu_option("Parameters", "Monthly Failsafe Report")
+ expect(page.get_by_role("heading")).to_contain_text(
+ "Monthly Failsafe Report Parameters"
+ )
+
+
+def test_bso_mapping(page: Page) -> None:
+ """
+ From the main menu, navigate to BSO Mapping -> GP Practice List and confirm the header has rendered correctly.
+ """
+ MainMenuPage(page).select_menu_option("BSO Mapping", "GP Practice List")
+ expect(page.get_by_role("heading")).to_contain_text("GP Practice List")
+
+
+def test_bso_contact_list(page: Page) -> None:
+ """
+ From the main menu, navigate to BSO Contact List and confirm the header has rendered correctly.
+ """
+ MainMenuPage(page).select_menu_option("BSO Contact List")
+ expect(page.get_by_role("heading")).to_contain_text("BSO Contact List")
+
+
+def test_monitoring_reports(page: Page) -> None:
+ """
+ From the main menu, navigate to Monitoring Reports -> SSPI Update Warnings and confirm the header has rendered correctly.
+ """
+ MainMenuPage(page).select_menu_option(
+ "Monitoring Reports", "SSPI Update Warnings - Action"
+ )
+ expect(page.get_by_role("heading")).to_contain_text("SSPI Update Warnings - Action")
+
+
+def test_failsafe_reports(page: Page) -> None:
+ """
+ From the main menu, navigate to Failsafe Reports -> Batch Analysis Report List and confirm the header has rendered correctly.
+ """
+ MainMenuPage(page).select_menu_option(
+ "Failsafe Reports", "Batch Analysis Report List"
+ )
+ expect(page.get_by_role("heading")).to_contain_text("Batch Analysis Report List")
+
+
+def test_estimating(page: Page) -> None:
+ """
+ From the main menu, navigate to Estimating -> NTDD Screening Estimate List and confirm the header has rendered correctly.
+ """
+ MainMenuPage(page).select_menu_option("Estimating", "NTDD Screening Estimate List")
+ expect(page.get_by_role("heading")).to_contain_text("NTDD Screening Estimate List")
+
+
+def test_round_planning(page: Page) -> None:
+ """
+ From the main menu, navigate to Round Planning -> Current and Next Visit List and confirm the header has rendered correctly.
+ """
+ MainMenuPage(page).select_menu_option(
+ "Round Planning", "Current and Next Visit List"
+ )
+ expect(page.get_by_role("heading")).to_contain_text("Current and next visits")
diff --git a/tests/ui/test_sspi.py b/tests/ui/test_sspi.py
new file mode 100644
index 00000000..3c454376
--- /dev/null
+++ b/tests/ui/test_sspi.py
@@ -0,0 +1,331 @@
+"""
+SSPI Tests: These tests cover the SSPI warning reports.
+"""
+
+import pytest
+import csv
+from pages.main_menu import MainMenuPage
+from utils.nhs_number_tools import NHSNumberTools
+from utils.user_tools import UserTools
+from utils.table_utils import TableUtils
+from playwright.sync_api import Page, expect
+from pages.monitoring_reports.sspi_update_warnings_action import (
+ SSPIUpdateWarningsActionPage,
+)
+from pages.monitoring_reports.sspi_update_warnings_information import (
+ SSPIUpdateWarningsInformationPage,
+)
+from utils.screenshot_tool import ScreenshotTool
+
+
+pytestmark = [pytest.mark.sspi]
+
+# Fixtures
+
+
+@pytest.fixture(autouse=True)
+def log_in(user_tools: UserTools, page: Page) -> None:
+ user_tools.user_login(page, "BSO User - BS1")
+
+
+@pytest.fixture
+def action_page(page: Page) -> SSPIUpdateWarningsActionPage:
+ MainMenuPage(page).select_menu_option(
+ "Monitoring Reports", "SSPI Update Warnings - Action"
+ )
+ action_page = SSPIUpdateWarningsActionPage(page)
+ action_page.verify_header()
+ return action_page
+
+
+@pytest.fixture
+def information_page(page: Page) -> SSPIUpdateWarningsInformationPage:
+ MainMenuPage(page).select_menu_option(
+ "Monitoring Reports", "SSPI Update Warnings - Information"
+ )
+ information_page = SSPIUpdateWarningsInformationPage(page)
+ information_page.verify_header()
+ return information_page
+
+
+# Tests
+
+
+def test_sspi_update_warnings_action_csv_download(
+ page: Page, action_page: SSPIUpdateWarningsActionPage
+) -> None:
+ """
+ Select Done from the drop down menu on the SSPI Update Warnings Action report and click the Download to CSV button to download the file
+ """
+ action_page.set_done_drop_down("All")
+ downloaded_file = action_page.download_csv()
+ warning_table = TableUtils(page, action_page.TABLE_ID)
+ row_number = 0
+ with open(downloaded_file) as file:
+ csv_file = csv.DictReader(file)
+ for lines in csv_file:
+ expect(warning_table.pick_row(row_number)).to_contain_text(
+ NHSNumberTools().spaced_nhs_number(lines["NHS Number"])
+ )
+ row_number += 1
+
+ if row_number >= warning_table.get_row_count():
+ # Stop if we have processed all rows in the table visible on the UI
+ break
+
+
+def test_sspi_update_warnings_action_done(
+ page: Page, action_page: SSPIUpdateWarningsActionPage
+) -> None:
+ """
+ Select Done from the drop down menu on the SSPI Update Warnings Action report
+ """
+ action_page.set_done_drop_down("All")
+ ScreenshotTool(page).take_screenshot("sspi_update_warnings_action_done")
+
+
+def test_sspi_update_warnings_action_nhs_number(
+ page: Page, action_page: SSPIUpdateWarningsActionPage
+) -> None:
+ """
+ Enter NHS Number in the filter field on the SSPI Update Warnings Action report
+ """
+ nhs_number = page.locator(action_page.TABLE_FIRST_NHS_NUMBER).text_content()
+ action_page.enter_nhs_number(nhs_number)
+ expect(page.locator(action_page.TABLE_FIRST_NHS_NUMBER)).to_have_text(nhs_number)
+ ScreenshotTool(page).take_screenshot("sspi_update_warnings_action_nhs_number")
+
+
+def test_sspi_update_warnings_action_family_name(
+ page: Page, action_page: SSPIUpdateWarningsActionPage
+) -> None:
+ """
+ Enter Family Name in the filter field on the SSPI Update Warnings Action report
+ """
+ family_name = page.locator(action_page.TABLE_FIRST_FAMILY_NAME).text_content()
+ action_page.enter_family_name(family_name)
+ expect(page.locator(action_page.TABLE_FIRST_FAMILY_NAME)).to_have_text(family_name)
+ ScreenshotTool(page).take_screenshot("sspi_update_warnings_action_family_name")
+
+
+def test_sspi_update_warnings_action_first_name(
+ page: Page, action_page: SSPIUpdateWarningsActionPage
+) -> None:
+ """
+ Enter First Given Name in the filter field on the SSPI Update Warnings Action report
+ """
+ first_name = page.locator(action_page.TABLE_FIRST_FIRST_NAME).text_content()
+ action_page.enter_first_name(first_name)
+ expect(page.locator(action_page.TABLE_FIRST_FIRST_NAME)).to_have_text(first_name)
+ ScreenshotTool(page).take_screenshot("sspi_update_warnings_action_first_name")
+
+
+def test_sspi_update_warnings_action_age(
+ page: Page, action_page: SSPIUpdateWarningsActionPage
+) -> None:
+ """
+ Select Age "Under 80" from the drop down menu on the SSPI Update Warnings Action report
+ """
+ selected_age = "Under 80"
+ action_page.table_filtered_by_age(selected_age)
+ for age in action_page.AGE_OPTIONS:
+ if age != selected_age:
+ expect(page.locator("tbody")).not_to_contain_text(age)
+ ScreenshotTool(page).take_screenshot("sspi_update_warnings_action_age")
+
+
+def test_sspi_update_warnings_action_received(
+ page: Page, action_page: SSPIUpdateWarningsActionPage
+) -> None:
+ """
+ Select the sorting Received on the SSPI Update Warnings Action report
+ """
+ action_page.sort_received()
+ expect(page.locator("#columnHeaderReceived")).to_have_attribute(
+ "aria-sort", "ascending"
+ )
+ ScreenshotTool(page).take_screenshot("sspi_update_warnings_action_received")
+
+
+def test_sspi_update_warnings_action_event(
+ page: Page, action_page: SSPIUpdateWarningsActionPage
+) -> None:
+ """
+ Select Event "Date of death set" on the SSPI Update Warnings Action report
+ """
+ selected_reason = "Date of death set"
+ action_page.event_selected(selected_reason)
+ for reason in action_page.REASON_OPTIONS:
+ if reason != selected_reason:
+ expect(page.locator("tbody")).not_to_contain_text(reason)
+ ScreenshotTool(page).take_screenshot("sspi_update_warnings_action_event")
+
+
+def test_sspi_update_warnings_action_warning(
+ page: Page, action_page: SSPIUpdateWarningsActionPage
+) -> None:
+ """
+ Select Warning "Subject has open episode" on the SSPI Update Warnings Action report
+ """
+ selected_warning = "Subject has open episode"
+ action_page.warning_selected(selected_warning)
+ for warning in action_page.WARNING_OPTIONS:
+ if warning != selected_warning:
+ expect(page.locator("tbody")).not_to_contain_text(warning)
+ ScreenshotTool(page).take_screenshot("sspi_update_warnings_action_warning")
+
+
+def test_sspi_update_warnings_action_add_info(
+ page: Page, action_page: SSPIUpdateWarningsActionPage
+) -> None:
+ """
+ Select the sorting Add Information on the SSPI Update Warnings Action report
+ """
+ action_page.sort_add_info()
+ expect(page.locator("#columnHeaderAdditionalInfo")).to_have_attribute(
+ "aria-sort", "ascending"
+ )
+ ScreenshotTool(page).take_screenshot("sspi_update_warnings_action_add_info")
+
+
+def test_sspi_update_warnings_information_csv_download(
+ page: Page, information_page: SSPIUpdateWarningsInformationPage
+) -> None:
+ """
+ Select Done from the drop down menu on the SSPI Update Warnings Information report and click the Download to CSV button to download the file
+ """
+ information_page.set_done_drop_down("All")
+ downloaded_file = information_page.download_csv()
+ warning_table = TableUtils(page, information_page.TABLE_ID)
+ row_number = 0
+ with open(downloaded_file) as file:
+ csv_file = csv.DictReader(file)
+ for lines in csv_file:
+ expect(warning_table.pick_row(row_number)).to_contain_text(
+ NHSNumberTools().spaced_nhs_number(lines["NHS Number"])
+ )
+ row_number += 1
+
+ if row_number >= warning_table.get_row_count():
+ # Stop if we have processed all rows in the table visible on the UI
+ break
+
+
+def test_sspi_update_warnings_information_done(
+ page: Page, information_page: SSPIUpdateWarningsInformationPage
+) -> None:
+ """
+ Select Done from the drop down menu on the SSPI Update Warnings Information report
+ """
+ information_page.set_done_drop_down("All")
+ ScreenshotTool(page).take_screenshot("sspi_update_warnings_information_done")
+
+
+def test_sspi_update_warnings_information_nhs_number(
+ page: Page, information_page: SSPIUpdateWarningsInformationPage
+) -> None:
+ """
+ Enter NHS Number in the filter field on the SSPI Update Warnings Information report
+ """
+ nhs_number = page.locator(information_page.TABLE_FIRST_NHS_NUMBER).text_content()
+ information_page.enter_nhs_number(nhs_number)
+ expect(page.locator(information_page.TABLE_FIRST_NHS_NUMBER)).to_have_text(
+ nhs_number
+ )
+ ScreenshotTool(page).take_screenshot("sspi_update_warnings_information_nhs_number")
+
+
+def test_sspi_update_warnings_information_family_name(
+ page: Page, information_page: SSPIUpdateWarningsInformationPage
+) -> None:
+ """
+ Enter Family Name in the filter field on the SSPI Update Warnings Information report
+ """
+ family_name = page.locator(information_page.TABLE_FIRST_FAMILY_NAME).text_content()
+ information_page.enter_family_name(family_name)
+ expect(page.locator(information_page.TABLE_FIRST_FAMILY_NAME)).to_have_text(
+ family_name
+ )
+ ScreenshotTool(page).take_screenshot("sspi_update_warnings_information_family_name")
+
+
+def test_sspi_update_warnings_information_first_name(
+ page: Page, information_page: SSPIUpdateWarningsInformationPage
+) -> None:
+ """
+ Enter First Name in the filter field on the SSPI Update Warnings Information report
+ """
+ first_name = page.locator(information_page.TABLE_FIRST_FIRST_NAME).text_content()
+ information_page.enter_first_name(first_name)
+ expect(page.locator(information_page.TABLE_FIRST_FIRST_NAME)).to_have_text(
+ first_name
+ )
+ ScreenshotTool(page).take_screenshot("sspi_update_warnings_information_first_name")
+
+
+def test_sspi_update_warnings_information_age(
+ page: Page, information_page: SSPIUpdateWarningsInformationPage
+) -> None:
+ """
+ Select Age "Under 80" from the drop down menu on the SSPI Update Warnings Information report
+ """
+ selected_age = "Under 80"
+ information_page.table_filtered_by_age(selected_age)
+ for age in information_page.AGE_OPTIONS:
+ if age != selected_age:
+ expect(page.locator("tbody")).not_to_contain_text(age)
+ ScreenshotTool(page).take_screenshot("sspi_update_warnings_information_age")
+
+
+def test_sspi_update_warnings_information_received(
+ page: Page, information_page: SSPIUpdateWarningsInformationPage
+) -> None:
+ """
+ Select the sorting Received on the SSPI Update Warnings Information report
+ """
+ information_page.sort_received()
+ expect(page.locator("#columnHeaderReceived")).to_have_attribute(
+ "aria-sort", "ascending"
+ )
+ ScreenshotTool(page).take_screenshot("sspi_update_warnings_information_received")
+
+
+def test_sspi_update_warnings_information_event(
+ page: Page, information_page: SSPIUpdateWarningsInformationPage
+) -> None:
+ """
+ Select Event "Subject joined BSO" on the SSPI Update Warnings Information report
+ """
+ selected_reason = "Subject joined BSO"
+ information_page.event_selected(selected_reason)
+ for reason in information_page.REASONI_OPTIONS:
+ if reason != selected_reason:
+ expect(page.locator("tbody")).not_to_contain_text(reason)
+ ScreenshotTool(page).take_screenshot("sspi_update_warnings_information_event")
+
+
+def test_sspi_update_warnings_information_warning(
+ page: Page, information_page: SSPIUpdateWarningsInformationPage
+) -> None:
+ """
+ Select Warning "No open episodes" on the SSPI Update Warnings Information report
+ """
+ selected_warning = "No open episodes"
+ information_page.warning_selected(selected_warning)
+ for warning in information_page.WARNINGI_OPTIONS:
+ if warning != selected_warning:
+ expect(page.locator("tbody")).not_to_contain_text(warning)
+ ScreenshotTool(page).take_screenshot("sspi_update_warnings_information_warning")
+
+
+def test_sspi_update_warnings_information_add_info(
+ page: Page, information_page: SSPIUpdateWarningsInformationPage
+) -> None:
+ """
+ Select the sorting Add Information on the SSPI Update Warnings Information report
+ """
+ information_page.sort_add_info()
+ expect(page.locator("#columnHeaderAdditionalInfo")).to_have_attribute(
+ "aria-sort", "ascending"
+ )
+ ScreenshotTool(page).take_screenshot("sspi_update_warnings_information_add_info")
diff --git a/tests/ui/test_subjects.py b/tests/ui/test_subjects.py
new file mode 100644
index 00000000..a7cd8e35
--- /dev/null
+++ b/tests/ui/test_subjects.py
@@ -0,0 +1,46 @@
+"""
+Subjects Tests: These tests cover viewing subjects via Subject Search.
+"""
+
+import pytest
+from secrets import randbelow
+from pages.main_menu import MainMenuPage
+from utils.user_tools import UserTools
+from utils.nhs_number_tools import NHSNumberTools
+from playwright.sync_api import Page, expect
+
+# Fixtures
+
+
+@pytest.fixture(autouse=True)
+def log_in(user_tools: UserTools, page: Page) -> None:
+ user_tools.user_login(page, "BSO User - BS1")
+ MainMenuPage(page).select_menu_option("Subject Search")
+
+
+# Tests
+
+
+@pytest.mark.subjects
+def test_subject_search(page: Page) -> None:
+ """
+ Navigate to subject search, search for a subject family name starting with the letter A, select a random result and
+ confirm the correct Subject Details page is loaded.
+ """
+ # Wait for the JSON database request to complete and search on letter A
+ with page.expect_response("**/bss/subjects/search**") as response:
+ # Await initial API load, but don't need to do anything with response at this stage
+ pass
+ page.locator("#familyNameFilter").get_by_role("textbox").fill("A")
+
+ # Wait for the JSON database request to complete, select a random result from the JSON and select
+ with page.expect_response("**/bss/subjects/search**") as response:
+ selected_number = randbelow(len(response.value.json()["results"]))
+ nhs_number = NHSNumberTools().spaced_nhs_number(
+ response.value.json()["results"][selected_number]["nhsNumber"]
+ )
+ page.get_by_text(nhs_number).dblclick()
+
+ # Show subject details and confirm correct page is present
+ expect(page.get_by_text("Subject Details")).to_be_visible()
+ expect(page.get_by_text(nhs_number)).to_be_visible()
diff --git a/tests/ui/test_subjects_never_invited.py b/tests/ui/test_subjects_never_invited.py
new file mode 100644
index 00000000..aefd95d3
--- /dev/null
+++ b/tests/ui/test_subjects_never_invited.py
@@ -0,0 +1,53 @@
+"""
+Subjects Never Invited For Screening Tests: These tests cover the Subjects Never Invited For Screening report.
+"""
+
+import pytest
+import csv
+from pages.main_menu import MainMenuPage
+from utils.nhs_number_tools import NHSNumberTools
+from utils.user_tools import UserTools
+from utils.table_utils import TableUtils
+from playwright.sync_api import Page, expect
+from pages.monitoring_reports.subjects_never_invited import SubjectsNeverInvitedPage
+
+
+pytestmark = [pytest.mark.invited]
+
+# Fixtures
+
+
+@pytest.fixture(autouse=True)
+def log_in(user_tools: UserTools, page: Page) -> None:
+ user_tools.user_login(page, "BSO User - BS1")
+
+
+@pytest.fixture
+def subjectsnotinvited_page(page: Page) -> SubjectsNeverInvitedPage:
+ MainMenuPage(page).select_menu_option(
+ "Monitoring Reports", "Subjects Never Invited For Screening"
+ )
+ neverinvited_page = SubjectsNeverInvitedPage(page)
+ neverinvited_page.verify_header()
+ return neverinvited_page
+
+
+# Tests
+
+
+def test_subjects_never_invited_csv_download(
+ page: Page, neverinvited_page: SubjectsNeverInvitedPage
+) -> None:
+ """
+ Click the Download to CSV button to download the file
+ """
+ downloaded_file = neverinvited_page.download_csv()
+ warning_table = TableUtils(page, neverinvited_page.TABLE_ID)
+ row_number = 0
+ with open(downloaded_file) as file:
+ csv_file = csv.DictReader(file)
+ for lines in csv_file:
+ expect(warning_table.pick_row(row_number)).to_contain_text(
+ NHSNumberTools().spaced_nhs_number(lines["NHS Number"])
+ )
+ row_number += 1
diff --git a/tests_utils/test_date_time_utils.py b/tests_utils/test_date_time_utils.py
index a7a2af0e..22eb0987 100644
--- a/tests_utils/test_date_time_utils.py
+++ b/tests_utils/test_date_time_utils.py
@@ -5,12 +5,17 @@
pytestmark = [pytest.mark.utils]
+
def test_current_datetime():
dtu = utils.date_time_utils.DateTimeUtils()
current_date = datetime.now()
assert dtu.current_datetime() == current_date.strftime("%d/%m/%Y %H:%M")
- assert dtu.current_datetime("%Y-%m-%d %H:%M") == current_date.strftime("%Y-%m-%d %H:%M")
- assert dtu.current_datetime("%d %B %Y %H:%M") == current_date.strftime("%d %B %Y %H:%M")
+ assert dtu.current_datetime("%Y-%m-%d %H:%M") == current_date.strftime(
+ "%Y-%m-%d %H:%M"
+ )
+ assert dtu.current_datetime("%d %B %Y %H:%M") == current_date.strftime(
+ "%d %B %Y %H:%M"
+ )
def test_format_date():
@@ -32,11 +37,27 @@ def test_get_day_of_week_for_today():
dtu = utils.date_time_utils.DateTimeUtils()
date = datetime.now()
day_of_week = dtu.get_a_day_of_week(date)
- assert day_of_week in ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
+ assert day_of_week in [
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday",
+ "Sunday",
+ ]
def test_get_a_day_of_week():
dtu = utils.date_time_utils.DateTimeUtils()
date = datetime(2023, 11, 8)
day_of_week = dtu.get_a_day_of_week(date)
- assert day_of_week in ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
+ assert day_of_week in [
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday",
+ "Sunday",
+ ]
diff --git a/tests_utils/test_nhs_number_tools.py b/tests_utils/test_nhs_number_tools.py
index e7b7c38e..bdb57c90 100644
--- a/tests_utils/test_nhs_number_tools.py
+++ b/tests_utils/test_nhs_number_tools.py
@@ -4,15 +4,22 @@
pytestmark = [pytest.mark.utils]
+
def test_nhs_number_checks() -> None:
assert NHSNumberTools._nhs_number_checks("1234567890") == None
- with pytest.raises(Exception, match=r'The NHS number provided \(A234567890\) is not numeric.'):
+ with pytest.raises(
+ Exception, match=r"The NHS number provided \(A234567890\) is not numeric."
+ ):
NHSNumberTools._nhs_number_checks("A234567890")
- with pytest.raises(NHSNumberToolsException, match=r'The NHS number provided \(123\) is not 10 digits'):
+ with pytest.raises(
+ NHSNumberToolsException,
+ match=r"The NHS number provided \(123\) is not 10 digits",
+ ):
NHSNumberTools._nhs_number_checks("123")
+
def test_spaced_nhs_number() -> None:
assert NHSNumberTools.spaced_nhs_number("1234567890") == "123 456 7890"
assert NHSNumberTools.spaced_nhs_number(3216549870) == "321 654 9870"
diff --git a/tests_utils/test_user_tools.py b/tests_utils/test_user_tools.py
index 014c870a..bf752978 100644
--- a/tests_utils/test_user_tools.py
+++ b/tests_utils/test_user_tools.py
@@ -6,8 +6,13 @@
pytestmark = [pytest.mark.utils]
+
def test_retrieve_user(monkeypatch: object) -> None:
- monkeypatch.setattr(utils.user_tools, "USERS_FILE", Path(__file__).parent / "resources" / "test_users.json")
+ monkeypatch.setattr(
+ utils.user_tools,
+ "USERS_FILE",
+ Path(__file__).parent / "resources" / "test_users.json",
+ )
test_user = UserTools.retrieve_user("Test User")
assert test_user["username"] == "TEST_USER1"
@@ -17,5 +22,7 @@ def test_retrieve_user(monkeypatch: object) -> None:
assert test_user2["username"] == "TEST_USER2"
assert test_user2["test_key"] == "TEST B"
- with pytest.raises(UserToolsException, match=r'User \[Invalid User\] is not present in users.json'):
+ with pytest.raises(
+ UserToolsException, match=r"User \[Invalid User\] is not present in users.json"
+ ):
UserTools.retrieve_user("Invalid User")
diff --git a/users.json b/users.json
index 2fdbee1b..35e65dbc 100644
--- a/users.json
+++ b/users.json
@@ -1,11 +1,49 @@
{
- "_comment": "This file can be used to store data on users for your application, and then pulled through using the utils.user_tools UserTools utility. The documentation for this utility explains how this file is read.",
- "Example User 1": {
- "username": "EXAMPLE_USER1",
- "roles": ["Example Role A"]
- },
- "Example User 2": {
- "username": "EXAMPLE_USER2",
- "roles": ["Example Role B", "Example Role C"]
+ "BSO User - BS1": {
+ "username": "BSS_USER1",
+ "role_to_select": "BSS_SO1",
+ "role_type": "BSO User",
+ "hub": "BS1",
+ "uuid":"555033739104"
+ },
+ "BSO User - BS2": {
+ "username": "BSS_USER1",
+ "role_to_select": "BSS_SO2",
+ "role_type": "BSO User",
+ "hub": "BS2",
+ "uuid":"555033739104"
+ },
+ "Read Only BSO User - BS2": {
+ "username": "BSS_USER2",
+ "role_to_select": "BSS_SO2",
+ "role_type": "Read Only BSO User",
+ "hub": "BS2",
+ "uuid":"555033740107"
+ },
+ "National User": {
+ "username": "BSS_USER1",
+ "role_to_select": "BSS_NAT",
+ "role_type": "National User",
+ "uuid":"555033739104"
+ },
+ "Helpdesk User": {
+ "username": "BSS_USER2",
+ "role_to_select": "BSS_NAT",
+ "role_type": "Helpdesk User",
+ "uuid":"555033740107"
+ },
+ "BSO User1 - BS1": {
+ "username": "BSS_USER2",
+ "role_to_select": "BSS_SO1",
+ "role_type": "BSO User",
+ "hub": "BS1",
+ "uuid":"555033740107"
+ },
+ "Ni Only BSO User - BS3": {
+ "username": "BSS_USER3",
+ "role_to_select": "BSS_SO3",
+ "role_type": "BSO User",
+ "hub": "BS3",
+ "uuid":"555033741108"
}
}
diff --git a/utils/CheckDigitGenerator.py b/utils/CheckDigitGenerator.py
new file mode 100644
index 00000000..cdff9b53
--- /dev/null
+++ b/utils/CheckDigitGenerator.py
@@ -0,0 +1,49 @@
+import secrets
+
+
+class CheckDigitGenerator:
+ def generate_check_digit(self) -> dict:
+ return self.calculate("PMA")["batch_id"]
+
+ # This function calculates the check digit
+ def check_digit(self, bso_code: str, sequence: str) -> str:
+ base = int(sequence)
+ for char in bso_code:
+ base += ord(char)
+
+ check_digit_sequence = "ACDEFGHJKLMNPQRTUWX"
+ check_digit_index = base % len(check_digit_sequence)
+ return check_digit_sequence[check_digit_index]
+
+ # This function generates the batch ID
+ def calculate(self, bso_code: str) -> dict:
+ bso_code = bso_code.strip().upper()
+ if len(bso_code) == 3:
+ random_sequence = str(secrets.randbelow(900000) + 100000)
+ check = self.check_digit(bso_code, random_sequence)
+ batch_id = f"{bso_code}{random_sequence}{check}"
+ return {
+ "sequence": random_sequence,
+ "check_digit": check,
+ "batch_id": batch_id,
+ }
+ else:
+ return {"sequence": "", "check_digit": "", "batch_id": ""}
+
+ # This function validates a given batch ID
+ def validate(self, bso_batch_id: str) -> str:
+ if len(bso_batch_id) != 10:
+ return f"Expected 10 length, but was {len(bso_batch_id)}"
+
+ bso_code = bso_batch_id[:3]
+ sequence = bso_batch_id[3:9]
+ actual_check_digit = bso_batch_id[9]
+
+ if not sequence.isnumeric():
+ return f"Invalid sequence number: {sequence}"
+
+ expected_check_digit = self.check_digit(bso_code, sequence)
+ if expected_check_digit != actual_check_digit:
+ return f"Expected check digit: {expected_check_digit}, but was {actual_check_digit}"
+
+ return "Valid"
diff --git a/utils/api_utils.py b/utils/api_utils.py
new file mode 100644
index 00000000..7c204226
--- /dev/null
+++ b/utils/api_utils.py
@@ -0,0 +1,31 @@
+import logging
+import json
+
+logger = logging.getLogger(__name__)
+from playwright.sync_api import BrowserContext, Page, expect
+
+
+class ApiUtils:
+ """
+ A utility class providing functionality for making API requests.
+ """
+
+ def __init__(self, api_session: BrowserContext, api_to_call: str) -> None:
+ self.api_session = api_session
+ self.api_to_call = api_to_call
+
+ def get_request(self, parameters: dict, result_ok: bool = True) -> dict | int:
+ """
+ This will send a Get request using the parameters specified.
+
+ Args:
+ parameters (dict): The parameters to give to the Get request.
+ result_ok (bool): Expect the result to be successful, if True.
+
+ Returns:
+ dict or int: A dictionary representation of the API response or the error code if response is not okay.
+
+ """
+ result = self.api_session.request.get(self.api_to_call, params=parameters)
+ assert result.ok == result_ok
+ return json.loads(result.body()) if result.ok else result.status
diff --git a/utils/axe.py b/utils/axe.py
index 689535c6..abb0438f 100644
--- a/utils/axe.py
+++ b/utils/axe.py
@@ -9,7 +9,7 @@
PATH_FOR_REPORT = Path(os.getcwd()) / "axe-reports"
-class Axe():
+class Axe:
"""
This utility allows for interaction with axe-core, to allow for accessibility scanning of pages
under test to identify any accessibility concerns.
diff --git a/utils/db_restore.py b/utils/db_restore.py
new file mode 100644
index 00000000..6ee362d1
--- /dev/null
+++ b/utils/db_restore.py
@@ -0,0 +1,106 @@
+import os
+import boto3
+import psycopg2
+import time
+import subprocess
+
+
+class DbRestore:
+ def __init__(self):
+ self.conn = None
+ self.s3_bucket = os.getenv("S3_BUCKET_NAME") # Name of the S3 bucket
+ self.s3_backup_key = os.getenv("S3_BACKUP_KEY") # Object key (file path in S3)
+ self.local_backup_path = (
+ "./tmp/db_backup.dump" # Local path to store downloaded backup
+ )
+
+ def connect(self):
+ self.conn = self.create_connection()
+ self.conn.autocommit = True
+
+ def create_connection(self, super: bool = False):
+ return psycopg2.connect(
+ host=os.getenv("PG_HOST"),
+ port=os.getenv("PG_PORT"),
+ user=os.getenv("PG_SUPERUSER") if super else os.getenv("PG_USER"),
+ password=os.getenv("PG_SUPERPASS") if super else os.getenv("PG_PASS"),
+ dbname="postgres" if super else os.getenv("PG_DBNAME"),
+ )
+
+ def recreate_db(self):
+ db_name = os.getenv("PG_DBNAME")
+ with self.conn.cursor() as cur:
+ cur.execute(f'DROP DATABASE IF EXISTS "{db_name}"')
+ cur.execute(f'CREATE DATABASE "{db_name}"')
+
+ def disconnect(self):
+ """Close the database connection."""
+ if self.conn:
+ try:
+ self.conn.close()
+ except Exception as e:
+ print(f"NO connection found to disconnect from! - {e}")
+
+ def download_backup_from_s3(self):
+ """Download the database backup from S3 to a local temporary file."""
+
+ try:
+ session = boto3.Session(profile_name="bs-select-rw-user-730319765130")
+ s3 = session.client("s3")
+ s3.download_file(self.s3_bucket, self.s3_backup_key, self.local_backup_path)
+ except Exception as e:
+ raise
+
+ def restore_backup(self):
+ """Restore the database from the downloaded backup."""
+ os.environ["PGPASSWORD"] = os.getenv("PG_PASS")
+ subprocess.run(
+ [
+ "pg_restore",
+ "--clean",
+ "-h",
+ os.getenv("PG_HOST"),
+ "-p",
+ os.getenv("PG_PORT"),
+ "-U",
+ os.getenv("PG_USER"),
+ "-d",
+ os.getenv("PG_DBNAME"),
+ "-j",
+ os.getenv("J_VALUE"),
+ self.local_backup_path,
+ ],
+ check=False,
+ )
+
+ def kill_all_db_sessions(self):
+ """Terminate all active sessions for the target database."""
+ dbname = os.getenv("PG_DBNAME")
+
+ try:
+ self.conn = self.create_connection(super=True)
+ with self.conn.cursor() as cur:
+ cur.execute(
+ f"""
+ SELECT pg_terminate_backend(pg_stat_activity.pid)
+ FROM pg_stat_activity
+ WHERE datname = '{dbname}' AND pid <> pg_backend_pid();"""
+ )
+ self.conn.commit()
+ print("Deleted all connections")
+ except Exception as e:
+ print(e)
+ print(
+ "Could not connect to DB. Check if other connections are present or if DB exists."
+ )
+ finally:
+ self.disconnect()
+
+ def full_db_restore(self):
+ # self.connect()
+ # self.recreate_db()
+ # self.disconnect()
+ start_time = time.time()
+ self.restore_backup()
+ end_time = time.time()
+ elapsed = end_time - start_time
diff --git a/utils/db_util.py b/utils/db_util.py
new file mode 100644
index 00000000..2f158519
--- /dev/null
+++ b/utils/db_util.py
@@ -0,0 +1,25 @@
+import psycopg
+import pandas as pd
+
+
+class DbUtil:
+ conn = None
+
+ def __init__(self, **conn_params) -> None:
+ self.conn = psycopg.connect(**conn_params)
+
+ def get_results(self, query: str, params: list[any] = []):
+ if self.conn:
+ df = pd.read_sql_query(query, self.conn, params=params)
+ return df
+ else:
+ return None
+
+ def insert(self, query: str, params: tuple = None):
+ """
+ Executes an INSERT query and commits the transaction.
+ """
+ if self.conn:
+ with self.conn.cursor() as cursor:
+ cursor.execute(query, params)
+ self.conn.commit()
diff --git a/utils/nhs_number_tools.py b/utils/nhs_number_tools.py
index 2b55ae0c..2134d0d3 100644
--- a/utils/nhs_number_tools.py
+++ b/utils/nhs_number_tools.py
@@ -18,9 +18,13 @@ def _nhs_number_checks(nhs_number: str) -> None:
nhs_number (str): The NHS number to check.
"""
if not nhs_number.isnumeric():
- raise NHSNumberToolsException("The NHS number provided ({}) is not numeric.".format(nhs_number))
+ raise NHSNumberToolsException(
+ "The NHS number provided ({}) is not numeric.".format(nhs_number)
+ )
if len(nhs_number) != 10:
- raise NHSNumberToolsException("The NHS number provided ({}) is not 10 digits.".format(nhs_number))
+ raise NHSNumberToolsException(
+ "The NHS number provided ({}) is not 10 digits.".format(nhs_number)
+ )
@staticmethod
def spaced_nhs_number(nhs_number: int | str) -> str:
diff --git a/utils/screenshot_tool.py b/utils/screenshot_tool.py
new file mode 100644
index 00000000..830da01c
--- /dev/null
+++ b/utils/screenshot_tool.py
@@ -0,0 +1,25 @@
+import logging
+
+logger = logging.getLogger(__name__)
+from playwright.sync_api import Page, expect
+
+
+class ScreenshotTool:
+ """
+ A utility class providing functionality for taking a screenshot.
+ """
+
+ def __init__(self, page: Page) -> None:
+ self.page = page
+
+ def take_screenshot(self, filename: str) -> None:
+ """
+ This will take a screenshot of the test which is passed into the class.
+
+ Args:
+ filename (str): Name given to the screenshot.
+
+ """
+ self.page.screenshot(
+ path=f"test-results/screenshot/{filename}.png", full_page=True
+ )
diff --git a/utils/table_utils.py b/utils/table_utils.py
new file mode 100644
index 00000000..48ca4888
--- /dev/null
+++ b/utils/table_utils.py
@@ -0,0 +1,143 @@
+import logging
+from secrets import randbelow
+from playwright.sync_api import Page, expect, Locator
+
+logger = logging.getLogger(__name__)
+
+
+class TableUtils:
+ """
+ A utility class providing functionality around tables in BS-Select.
+ """
+
+ def __init__(self, page: Page, table_locator: str) -> None:
+ """
+ Initializer for TableUtils.
+
+ Args:
+ page (playwright.sync_api.Page): The page the table is on.
+ table_locator (str): The locator value to use to find the table.
+
+ Returns:
+ A TableUtils object ready to use.
+ """
+ self.page = page
+ self.table_id = table_locator
+
+ def _format_inner_text(self, data: str) -> dict:
+ """
+ This formats the inner text of a row to make it easier to manage
+
+ Args:
+ data (str): The .inner_text() of a table row.
+
+ Returns:
+ A dict with each column item from the row identified with its position.
+ """
+ dict_to_return = {}
+ split_rows = data.split("\t")
+ pos = 1
+ for item in split_rows:
+ dict_to_return[pos] = item
+ pos += 1
+ return dict_to_return
+
+ def get_table_headers(self) -> dict:
+ """
+ This retrieves the headers from the table.
+
+ Returns:
+ A dict with each column item from the header row identified with its position.
+ """
+ headers = self.page.locator(f"{self.table_id} > thead tr").nth(0).inner_text()
+ return self._format_inner_text(headers)
+
+ def get_row_count(self) -> int:
+ """
+ This returns the total rows visible on the table (on the screen currently)
+
+ Returns:
+ An int with the total row count.
+ """
+ return self.page.locator(f"{self.table_id} > tbody tr").count()
+
+ def pick_row(self, row_number: int) -> Locator:
+ """
+ This picks a selected row from table
+
+ Args:
+ row_id (str): The row number of the row to select.
+
+ Returns:
+ A playwright.sync_api.Locator with the row object.
+ """
+ return self.page.locator(f"{self.table_id} > tbody tr").nth(row_number)
+
+ def pick_random_row(self) -> Locator:
+ """
+ This picks a random row from the visible rows in the table (full row)
+
+ Returns:
+ A playwright.sync_api.Locator with the row object.
+ """
+ return self.page.locator(f"{self.table_id} > tbody tr").nth(
+ randbelow(self.get_row_count())
+ )
+
+ def pick_random_row_number(self) -> int:
+ """
+ This picks a random row from the table in BS-Select and returns its position
+
+ Returns:
+ An int representing a random row on the table.
+ """
+ return randbelow(self.get_row_count())
+
+ def get_row_data_with_headers(self, row_number: int) -> dict:
+ """
+ This picks a selected row from table
+
+ Args:
+ row_number (str): The row number of the row to select.
+
+ Returns:
+ A dict object with keys representing the headers, and values representing the row contents.
+ """
+ headers = self.get_table_headers()
+ row_data = self._format_inner_text(
+ self.page.locator(f"{self.table_id} > tbody tr")
+ .nth(row_number)
+ .inner_text()
+ )
+ results = {}
+
+ for key in headers:
+ results[headers[key]] = row_data[key]
+
+ return results
+
+ def get_full_table_with_headers(self) -> dict:
+ """
+ This returns the full table as a dict of rows, with each entry having a header key / value pair.
+ NOTE: The row count starts from 1 to represent the first row, not 0.
+
+ Returns:
+ A dict object with keys representing the rows, with values being a dict representing a header key / column value pair.
+ """
+ full_results = {}
+ for row in range(self.get_row_count()):
+ full_results[row + 1] = self.get_row_data_with_headers(row)
+ return full_results
+
+ def wait_for_table_to_populate(self) -> None:
+ """
+ This checks that the following phrases are no longer present in the body of the table:
+ - "Waiting for typing to finish..."
+ - "Searching..."
+ """
+ expect(self.page.locator(self.table_id)).not_to_contain_text(
+ "Waiting for typing to finish..."
+ )
+ expect(self.page.locator(self.table_id)).not_to_contain_text(
+ "Searching...", timeout=10000
+ )
diff --git a/utils/test_helpers.py b/utils/test_helpers.py
new file mode 100644
index 00000000..639cd94b
--- /dev/null
+++ b/utils/test_helpers.py
@@ -0,0 +1,7 @@
+import secrets
+import string
+
+
+def generate_random_string(length: int = secrets.randbelow(98) + 3) -> str:
+ characters = string.ascii_uppercase + string.digits + string.ascii_lowercase
+ return "".join(secrets.choice(characters) for _ in range(length))
diff --git a/utils/user_tools.py b/utils/user_tools.py
index f8026717..243ba276 100644
--- a/utils/user_tools.py
+++ b/utils/user_tools.py
@@ -2,7 +2,9 @@
import os
import logging
from pathlib import Path
-
+from playwright.sync_api import Page
+from pages.login.cognito_authentication import CognitoAuthenticationPage
+from pages.login.org_selection import OrgSelectionPage
logger = logging.getLogger(__name__)
USERS_FILE = Path(os.getcwd()) / "users.json"
@@ -13,6 +15,30 @@ class UserTools:
A utility class for retrieving and doing common actions with users.
"""
+ def user_login(self, page: Page, user: str) -> None:
+ """
+ Logs into the BS-Select application and selects the applicable org (if required).
+
+ Args:
+ page (playwright.sync_api.Page): The Playwright page object to interact with.
+ user (str): The user details required, in the format "Role Type" or "Role Type - Organisation".
+ """
+ page.goto("/bss")
+ user = self.retrieve_user(user)
+
+ if "cognito" in page.url:
+ CognitoAuthenticationPage(page).cognito_login(
+ user["username"], os.getenv("COGNITO_USER_PASSWORD")
+ )
+ else:
+ # CIS2 Simple Realm
+ page.locator("//input[@data-vv-as='User Name']").fill(user["uuid"])
+ page.locator("//input[@data-vv-as='Password']").fill(
+ os.getenv("USER_PASSWORD")
+ )
+ page.locator("//button[@class='nhsuk-button']").click()
+ OrgSelectionPage(page).org_selection(user["role_to_select"])
+
@staticmethod
def retrieve_user(user: str) -> dict:
"""
@@ -24,7 +50,7 @@ def retrieve_user(user: str) -> dict:
Returns:
dict: A Python dictionary with the details of the user requested, if present.
"""
- with open(USERS_FILE, 'r') as file:
+ with open(USERS_FILE, "r") as file:
user_data = json.loads(file.read())
if user not in user_data: