From 659d0bfcea3a08799c55e26e0ce1c9684dd02673 Mon Sep 17 00:00:00 2001
From: Dave Harding <35965578+davethepunkyone@users.noreply.github.com>
Date: Tue, 17 Jun 2025 09:30:56 +0100
Subject: [PATCH 1/5] Create CODEOWNERS (#2)
## Description
Create a CODEOWNERS file as outlined in the SEQF and assign the Code
Owners team.
See:
https://github.com/NHSDigital/software-engineering-quality-framework/blob/main/practices/securing-repositories.md#teams-setup
## Context
Follows the guidance for the SEQF around security practices for managing
code for NHSE.
## Type of changes
- [ ] Refactoring (non-breaking change)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would change existing
functionality)
- [ ] Bug fix (non-breaking change which fixes an issue)
## Checklist
- [x] I am familiar with the [contributing
guidelines](https://github.com/nhs-england-tools/playwright-python-blueprint/blob/main/CONTRIBUTING.md)
- [x] I have followed the code style of the project
- [ ] I have added tests to cover my changes (where appropriate)
- [ ] I have updated the documentation accordingly
- [ ] This PR is a result of pair or mob programming
---
## Sensitive Information Declaration
To ensure the utmost confidentiality and protect your and others
privacy, we kindly ask you to NOT including [PII (Personal Identifiable
Information) / PID (Personal Identifiable
Data)](https://digital.nhs.uk/data-and-information/keeping-data-safe-and-benefitting-the-public)
or any other sensitive data in this PR (Pull Request) and the codebase
changes. We will remove any PR that do contain any sensitive
information. We really appreciate your cooperation in this matter.
- [x] I confirm that neither PII/PID nor sensitive data are included in
this PR and the codebase changes.
Signed-off-by: Dave Harding <35965578+davethepunkyone@users.noreply.github.com>
---
.github/CODEOWNERS | 1 +
1 file changed, 1 insertion(+)
create mode 100644 .github/CODEOWNERS
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
From 9202b35617e70f2750879ecc0a0d4ffcf836c31e Mon Sep 17 00:00:00 2001
From: Dave Harding <35965578+davethepunkyone@users.noreply.github.com>
Date: Thu, 19 Jun 2025 16:26:04 +0100
Subject: [PATCH 2/5] Initial Code Transfer from GitLab (#3)
## Description
This transfers the Playwright code from GitLab to GitHub.
## Context
This brings us in line with the NHSE Engineering Red Lines, specifically
SDLC-1 (all code must be on the corp GitHub).
## Type of changes
- [ ] Refactoring (non-breaking change)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would change existing
functionality)
- [ ] Bug fix (non-breaking change which fixes an issue)
## Checklist
- [x] I am familiar with the [contributing
guidelines](https://github.com/nhs-england-tools/playwright-python-blueprint/blob/main/CONTRIBUTING.md)
- [x] I have followed the code style of the project
- [x] I have added tests to cover my changes (where appropriate)
- [ ] I have updated the documentation accordingly
- [ ] This PR is a result of pair or mob programming
---
## Sensitive Information Declaration
To ensure the utmost confidentiality and protect your and others
privacy, we kindly ask you to NOT including [PII (Personal Identifiable
Information) / PID (Personal Identifiable
Data)](https://digital.nhs.uk/data-and-information/keeping-data-safe-and-benefitting-the-public)
or any other sensitive data in this PR (Pull Request) and the codebase
changes. We will remove any PR that do contain any sensitive
information. We really appreciate your cooperation in this matter.
- [x] I confirm that neither PII/PID nor sensitive data are included in
this PR and the codebase changes.
---
.../action.yml | 14 +-
.github/workflows/stage-2-test.yaml | 6 +-
.gitignore | 9 +
.vscode/settings.json | 6 +-
buildBase.dockerfile | 14 +-
conftest.py | 160 ++-
pages/base_page.py | 23 +
pages/bso_mapping/gp_practice_list.py | 17 +
pages/login/cognito_authentication.py | 14 +
pages/login/org_selection.py | 25 +
pages/main_menu.py | 15 +
pages/monitoring_reports/ceased_unceased.py | 116 +++
.../sspi_update_warnings_action.py | 105 ++
.../sspi_update_warnings_base.py | 70 ++
.../sspi_update_warnings_information.py | 37 +
.../subjects_never_invited.py | 20 +
pages/ni_ri_sp_batch_page.py | 278 +++++
pages/report_page.py | 22 +
pages/rlp_cohort_list_page.py | 377 +++++++
pages/rlp_location_list_page.py | 193 ++++
pages/rlp_unit_list_page.py | 258 +++++
pytest.ini | 46 +-
requirements.txt | 3 +
run_tests.sh | 6 +-
setup_env_file.py | 28 +-
tests/accessibility/test_accessibility.py | 52 +
.../batch_management/test_batch_list_api.py | 188 ++++
.../test_bso_contact_list_api.py | 183 ++++
.../api/bso_mapping/test_assigned_bsos_api.py | 109 ++
.../test_geographic_outcode_list_api.py | 134 +++
.../bso_mapping/test_gp_practice_list_api.py | 158 +++
tests/api/conftest.py | 34 +
.../test_search_batches_api.py | 80 ++
.../test_ceased_unceased_api.py | 92 ++
.../test_ceasing_instances_api.py | 188 ++++
.../test_sspi_action_api.py | 213 ++++
.../test_sspi_information_api.py | 215 ++++
.../test_subject_demographic_api.py | 127 +++
.../test_subjects_never_invited_api.py | 175 ++++
.../test_subjects_overdue_invitation_api.py | 171 ++++
.../api/outcome_list/test_outcome_list_api.py | 86 ++
.../test_gp_practice_group_list_api.py | 112 ++
.../parameters/test_outcode_group_list_api.py | 108 ++
.../subject_search/test_subject_seach_api.py | 35 +
tests/test_example.py | 78 --
.../ui/cohort_manager/cohort_manager_util.py | 21 +
.../test_cohort_manager_lambda_integration.py | 957 ++++++++++++++++++
.../test_ni_ri_sp_batch_additional_testing.py | 98 ++
.../test_ni_ri_sp_batch_parameters.py | 160 +++
.../test_ni_ri_sp_batch_ui_validation.py | 275 +++++
...t_amend_rlp_screening_cohort_list_by_gp.py | 408 ++++++++
...nd_rlp_screening_cohort_list_by_outcode.py | 336 ++++++
.../test_rlp_screening_cohort_list_by_gp.py | 486 +++++++++
...st_rlp_screening_cohort_list_by_outcode.py | 539 ++++++++++
.../test_rlp_screening_amend_unit.py | 268 +++++
.../test_rlp_screening_unit.py | 290 ++++++
tests/ui/test_ceased.py | 252 +++++
tests/ui/test_gp_practice_list.py | 31 +
tests/ui/test_ntdd_additional_testing.py | 126 +++
tests/ui/test_rlp_screening_location_list.py | 387 +++++++
tests/ui/test_smoke.py | 109 ++
tests/ui/test_sspi.py | 331 ++++++
tests/ui/test_subjects.py | 46 +
tests/ui/test_subjects_never_invited.py | 53 +
tests_utils/test_date_time_utils.py | 29 +-
tests_utils/test_nhs_number_tools.py | 11 +-
tests_utils/test_user_tools.py | 11 +-
users.json | 54 +-
utils/CheckDigitGenerator.py | 49 +
utils/api_utils.py | 31 +
utils/axe.py | 2 +-
utils/db_restore.py | 106 ++
utils/db_util.py | 25 +
utils/nhs_number_tools.py | 8 +-
utils/screenshot_tool.py | 25 +
utils/table_utils.py | 143 +++
utils/test_helpers.py | 7 +
utils/user_tools.py | 30 +-
78 files changed, 9967 insertions(+), 137 deletions(-)
rename .github/actions/{run-playwright-tests => run-unit-tests}/action.yml (59%)
create mode 100644 pages/base_page.py
create mode 100644 pages/bso_mapping/gp_practice_list.py
create mode 100644 pages/login/cognito_authentication.py
create mode 100644 pages/login/org_selection.py
create mode 100644 pages/main_menu.py
create mode 100644 pages/monitoring_reports/ceased_unceased.py
create mode 100644 pages/monitoring_reports/sspi_update_warnings_action.py
create mode 100644 pages/monitoring_reports/sspi_update_warnings_base.py
create mode 100644 pages/monitoring_reports/sspi_update_warnings_information.py
create mode 100644 pages/monitoring_reports/subjects_never_invited.py
create mode 100644 pages/ni_ri_sp_batch_page.py
create mode 100644 pages/report_page.py
create mode 100644 pages/rlp_cohort_list_page.py
create mode 100644 pages/rlp_location_list_page.py
create mode 100644 pages/rlp_unit_list_page.py
create mode 100644 tests/accessibility/test_accessibility.py
create mode 100644 tests/api/batch_management/test_batch_list_api.py
create mode 100644 tests/api/bso_contact_list/test_bso_contact_list_api.py
create mode 100644 tests/api/bso_mapping/test_assigned_bsos_api.py
create mode 100644 tests/api/bso_mapping/test_geographic_outcode_list_api.py
create mode 100644 tests/api/bso_mapping/test_gp_practice_list_api.py
create mode 100644 tests/api/conftest.py
create mode 100644 tests/api/failsafe_reports/test_search_batches_api.py
create mode 100644 tests/api/monitoring_reports/test_ceased_unceased_api.py
create mode 100644 tests/api/monitoring_reports/test_ceasing_instances_api.py
create mode 100644 tests/api/monitoring_reports/test_sspi_action_api.py
create mode 100644 tests/api/monitoring_reports/test_sspi_information_api.py
create mode 100644 tests/api/monitoring_reports/test_subject_demographic_api.py
create mode 100644 tests/api/monitoring_reports/test_subjects_never_invited_api.py
create mode 100644 tests/api/monitoring_reports/test_subjects_overdue_invitation_api.py
create mode 100644 tests/api/outcome_list/test_outcome_list_api.py
create mode 100644 tests/api/parameters/test_gp_practice_group_list_api.py
create mode 100644 tests/api/parameters/test_outcode_group_list_api.py
create mode 100644 tests/api/subject_search/test_subject_seach_api.py
delete mode 100644 tests/test_example.py
create mode 100644 tests/ui/cohort_manager/cohort_manager_util.py
create mode 100644 tests/ui/cohort_manager/test_cohort_manager_lambda_integration.py
create mode 100644 tests/ui/ni_ri_sp_batch/test_ni_ri_sp_batch_additional_testing.py
create mode 100644 tests/ui/ni_ri_sp_batch/test_ni_ri_sp_batch_parameters.py
create mode 100644 tests/ui/ni_ri_sp_batch/test_ni_ri_sp_batch_ui_validation.py
create mode 100644 tests/ui/rlp_screening_cohort_list/test_amend_rlp_screening_cohort_list_by_gp.py
create mode 100644 tests/ui/rlp_screening_cohort_list/test_amend_rlp_screening_cohort_list_by_outcode.py
create mode 100644 tests/ui/rlp_screening_cohort_list/test_rlp_screening_cohort_list_by_gp.py
create mode 100644 tests/ui/rlp_screening_cohort_list/test_rlp_screening_cohort_list_by_outcode.py
create mode 100644 tests/ui/rlp_screening_unit_list/test_rlp_screening_amend_unit.py
create mode 100644 tests/ui/rlp_screening_unit_list/test_rlp_screening_unit.py
create mode 100644 tests/ui/test_ceased.py
create mode 100644 tests/ui/test_gp_practice_list.py
create mode 100644 tests/ui/test_ntdd_additional_testing.py
create mode 100644 tests/ui/test_rlp_screening_location_list.py
create mode 100644 tests/ui/test_smoke.py
create mode 100644 tests/ui/test_sspi.py
create mode 100644 tests/ui/test_subjects.py
create mode 100644 tests/ui/test_subjects_never_invited.py
create mode 100644 utils/CheckDigitGenerator.py
create mode 100644 utils/api_utils.py
create mode 100644 utils/db_restore.py
create mode 100644 utils/db_util.py
create mode 100644 utils/screenshot_tool.py
create mode 100644 utils/table_utils.py
create mode 100644 utils/test_helpers.py
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..a2045d4e 100644
--- a/buildBase.dockerfile
+++ b/buildBase.dockerfile
@@ -2,15 +2,19 @@ FROM python:3.13-slim
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/*
+
# 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/
-RUN mkdir -p /tests/
COPY ./tests/ ./tests/
-RUN mkdir -p /utils/
COPY ./utils/ ./utils/
COPY ./pytest.ini ./pytest.ini
COPY ./run_tests.sh ./run_tests.sh
diff --git a/conftest.py b/conftest.py
index 99c9ee54..eda4cb00 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_screening_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_screening_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..ef8b233c
--- /dev/null
+++ b/pages/login/org_selection.py
@@ -0,0 +1,25 @@
+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}")
+ self.page.locator("#chosenOrgCode").select_option(role)
+ 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..10b1c6ed
--- /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_txtbox = 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_txtbox = 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_txtbox.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_txtbox(
+ 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_filterd_location_name(self):
+ filterd_value = self.filtered_location.text_content()
+ self.page.wait_for_timeout(4000)
+ return filterd_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_txtbox(
+ self, cohort_name: str
+ ) -> ScreeningLocationListPage:
+ self.screening_cohort_name_txtbox.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/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..0772f452
--- /dev/null
+++ b/tests/ui/test_rlp_screening_location_list.py
@@ -0,0 +1,387 @@
+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
+@pytest.mark.locationlist
+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
+@pytest.mark.locationlist
+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 User1 - 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_txtbox(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
+@pytest.mark.locationlist
+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 User1 - 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()
+ # Assering page number before and after the cancellation
+ assert before_cancellation == after_cancellation
+
+
+#### Test_05
+@pytest.mark.locationlist
+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 User1 - 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.locationlist
+@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 User1 - 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_txtbox(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.locationlist
+@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 User1 - 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
+@pytest.mark.locationlist
+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 User1 - 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 User2 - 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_txtbox(location_name)
+ # storing the User_2 filterd location_name in filterd_name
+ filterd_name = rlp_location_list_page.value_of_filterd_location_name()
+ # assering the the location name created by User_1 and filterd by User_2
+ assert location_name == filterd_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
+@pytest.mark.locationlist
+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 User1 - 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 User2 - 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_txtbox(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 filterd location_name in filterd_name
+ rlp_location_list_page.enter_screening_location_filter_txtbox(location_name)
+ filterd_name = rlp_location_list_page.value_of_filterd_location_name()
+ # assering the the location name created and filterd by User_2
+ assert location_name == filterd_name
+
+
+#### Test coveres Test_09, Test_10 and Test_11(valid_data_set)
+@pytest.mark.locationlist
+@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 User1 - 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_txtbox(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_randon 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_txtbox(amend_name)
+ # storing the filterd location value in the "filterd_amend_name"
+ filterd_amend_name = rlp_location_list_page.value_of_filterd_location_name()
+ # assering the amend_value and filterd_amend_value
+ assert amend_name == filterd_amend_name
+
+
+## Test_11 negatie test with invalid_data
+@pytest.mark.locationlist
+@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 User1 - 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 exising location
+ rlp_location_list_page.enter_screening_location_filter_txtbox(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
+@pytest.mark.locationlist
+def test_amend_screening_location_availabale_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 User1 - 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_txtbox(location_name)
+ rlp_location_list_page.invoke_filtered_screening_location()
+
+ # Amending the newly added location using generate_randon 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 User2 - 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_txtbox(amend_name)
+ # storing the User_2 filterd location_name in filterd_name
+ filterd_name = rlp_location_list_page.value_of_filterd_location_name()
+ # assering the the location name created by User_1 and filterd by User_2
+ assert amend_name == filterd_name
+
+
+## Test_13
+@pytest.mark.locationlist
+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 User1 - 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 User1 - 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)
+ filterd_names = page.locator("//tr//td[2]").all_text_contents()
+ assert input_names == filterd_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:
From dfbb540197d03531816d0da66b61bd0059e8bc1f Mon Sep 17 00:00:00 2001
From: Vipasya
Date: Tue, 24 Jun 2025 15:07:38 +0100
Subject: [PATCH 3/5] BSS2-2304 amend login step to fail faster on org
selection if org is not present (#4)
## Description
In org selection page, checking for presence of role in the available
options,
This makes the tests fail fast (instead of waiting for 30sec timeout)i
if the role isn't in the list of available options.
## Context
## Type of changes
- [ ] Refactoring (non-breaking change)
- [X] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would change existing
functionality)
- [ ] Bug fix (non-breaking change which fixes an issue)
## Checklist
- [x] I am familiar with the [contributing
guidelines](https://github.com/nhs-england-tools/playwright-python-blueprint/blob/main/CONTRIBUTING.md)
- [x] I have followed the code style of the project
- [ ] I have added tests to cover my changes (where appropriate)
- [ ] I have updated the documentation accordingly
- [ ] This PR is a result of pair or mob programming
---
## Sensitive Information Declaration
To ensure the utmost confidentiality and protect your and others
privacy, we kindly ask you to NOT including [PII (Personal Identifiable
Information) / PID (Personal Identifiable
Data)](https://digital.nhs.uk/data-and-information/keeping-data-safe-and-benefitting-the-public)
or any other sensitive data in this PR (Pull Request) and the codebase
changes. We will remove any PR that do contain any sensitive
information. We really appreciate your cooperation in this matter.
- [x] I confirm that neither PII/PID nor sensitive data are included in
this PR and the codebase changes.
---
conftest.py | 4 +-
pages/login/org_selection.py | 10 +-
pages/rlp_location_list_page.py | 18 +--
tests/ui/test_rlp_screening_location_list.py | 112 +++++++++----------
4 files changed, 70 insertions(+), 74 deletions(-)
diff --git a/conftest.py b/conftest.py
index eda4cb00..3ba907bf 100644
--- a/conftest.py
+++ b/conftest.py
@@ -54,7 +54,7 @@ def main_menu(page: Page) -> MainMenuPage:
@pytest.fixture
-def rlp_screening_location_list_page(page: Page) -> ScreeningLocationListPage:
+def rlp_location_list_page(page: Page) -> ScreeningLocationListPage:
return ScreeningLocationListPage(page)
@@ -64,7 +64,7 @@ def rlp_cohort_list_page(page: Page) -> CohortListPage:
@pytest.fixture
-def rlp_screening_unit_list_page(page: Page) -> ScreeningUnitListPage:
+def rlp_unit_list_page(page: Page) -> ScreeningUnitListPage:
return ScreeningUnitListPage(page)
diff --git a/pages/login/org_selection.py b/pages/login/org_selection.py
index ef8b233c..305469f8 100644
--- a/pages/login/org_selection.py
+++ b/pages/login/org_selection.py
@@ -18,7 +18,15 @@ def org_selection(self, role: str = None) -> None:
self.verify_header()
if role is not None:
logging.info(f"Selecting role: {role}")
- self.page.locator("#chosenOrgCode").select_option(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:
diff --git a/pages/rlp_location_list_page.py b/pages/rlp_location_list_page.py
index 10b1c6ed..99fdb151 100644
--- a/pages/rlp_location_list_page.py
+++ b/pages/rlp_location_list_page.py
@@ -10,7 +10,7 @@ def __init__(self, page: Page) -> None:
self.page = page
self.add_screening_location_btn = page.locator("button#addLocationButton")
- self.screening_location_name_txtbox = page.locator("input#locationNameText")
+ self.screening_location_name_textbox = page.locator("input#locationNameText")
self.add_screening_location_btn_on_popup = page.locator(
"#addButtonInAddLocationPopupText"
)
@@ -31,7 +31,7 @@ def __init__(self, page: Page) -> None:
self.create_screening_cohort_by_gp_practice_btn = page.locator(
"#addCohortByPracticeButtonText"
)
- self.screening_cohort_name_txtbox = page.locator("//input[@id='description']")
+ self.screening_cohort_name_textbox = page.locator("//input[@id='description']")
self.default_screening_location_dropdown = page.locator(
"//select[@id='defaultLocation']"
)
@@ -53,7 +53,7 @@ def click_add_screening_location_btn(self) -> ScreeningLocationListPage:
def enter_screening_location_name(
self, location_name: str
) -> ScreeningLocationListPage:
- self.screening_location_name_txtbox.fill(location_name)
+ self.screening_location_name_textbox.fill(location_name)
return self
def click_add_screening_location_btn_on_popup(self) -> ScreeningLocationListPage:
@@ -65,7 +65,7 @@ def click_cancel_add_screening_location_btn(self) -> ScreeningLocationListPage:
self.cancel_add_screening_location_btn.click()
return self
- def enter_screening_location_filter_txtbox(
+ def enter_screening_location_filter_textbox(
self, location_name: str
) -> ScreeningLocationListPage:
self.location_name_filter.fill(location_name)
@@ -110,10 +110,10 @@ def click_amend_screening_location_btn(self) -> None:
def click_cancel_amend_location_btn(self) -> None:
self.cancel_amend_location_btn.click()
- def value_of_filterd_location_name(self):
- filterd_value = self.filtered_location.text_content()
+ def value_of_filtered_location_name(self):
+ filtered_value = self.filtered_location.text_content()
self.page.wait_for_timeout(4000)
- return filterd_value
+ return filtered_value
def click_log_out_btn(self) -> None:
self.log_out_btn.click()
@@ -124,10 +124,10 @@ def click_create_screening_cohort_by_gp_practice_btn(
self.create_screening_cohort_by_gp_practice_btn.click()
return self
- def enter_screening_cohort_name_txtbox(
+ def enter_screening_cohort_name_textbox(
self, cohort_name: str
) -> ScreeningLocationListPage:
- self.screening_cohort_name_txtbox.fill(cohort_name)
+ self.screening_cohort_name_textbox.fill(cohort_name)
return self
def select_default_screening_location_dropdown(
diff --git a/tests/ui/test_rlp_screening_location_list.py b/tests/ui/test_rlp_screening_location_list.py
index 0772f452..76fd1f9a 100644
--- a/tests/ui/test_rlp_screening_location_list.py
+++ b/tests/ui/test_rlp_screening_location_list.py
@@ -12,7 +12,6 @@
#### Test_01
#### Test_03
-@pytest.mark.locationlist
def test_paging_of_screening_location_list(
page: Page, rlp_location_list_page: ScreeningLocationListPage, db_util
) -> None:
@@ -31,7 +30,6 @@ def test_paging_of_screening_location_list(
#### Test_04
-@pytest.mark.locationlist
def test_add_screening_location(
page: Page, rlp_location_list_page: ScreeningLocationListPage
) -> None:
@@ -39,7 +37,7 @@ def test_add_screening_location(
Test to add location to the location list
"""
# Logged into BSS_SO1
- UserTools().user_login(page, "BSO User1 - BS1")
+ 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()}"
@@ -47,7 +45,7 @@ def test_add_screening_location(
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_txtbox(location_name)
+ 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)
@@ -57,7 +55,6 @@ def test_add_screening_location(
#### Test_04 Negative test
-@pytest.mark.locationlist
def test_cancel_screening_location(
page: Page, rlp_location_list_page: ScreeningLocationListPage
) -> None:
@@ -66,7 +63,7 @@ def test_cancel_screening_location(
both values should be the same
"""
# Logged into BSS_SO1
- UserTools().user_login(page, "BSO User1 - BS1")
+ 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()
@@ -77,12 +74,11 @@ def test_cancel_screening_location(
rlp_location_list_page.click_cancel_add_screening_location_btn()
# Extracting page_info after cancellation
after_cancellation = rlp_location_list_page.extract_paging_info()
- # Assering page number before and after the cancellation
+ # Asserting page number before and after the cancellation
assert before_cancellation == after_cancellation
#### Test_05
-@pytest.mark.locationlist
def test_default_screening_location_values(
page: Page, rlp_location_list_page: ScreeningLocationListPage
) -> None:
@@ -90,7 +86,7 @@ def test_default_screening_location_values(
Test to verify the default location field, field should be empty
"""
# Logged into BSS_SO1
- UserTools().user_login(page, "BSO User1 - BS1")
+ 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()
@@ -98,7 +94,6 @@ def test_default_screening_location_values(
#### Test_06 valid_data
-@pytest.mark.locationlist
@pytest.mark.parametrize("input_length", [3, 100])
def test_create_location_valid_data_positive(
page: Page, rlp_location_list_page: ScreeningLocationListPage, input_length
@@ -108,13 +103,13 @@ def test_create_location_valid_data_positive(
asserting created value and the actual value
"""
# Logged into BSS_SO1
- UserTools().user_login(page, "BSO User1 - BS1")
+ 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_txtbox(location_name)
+ 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()
@@ -123,7 +118,6 @@ def test_create_location_valid_data_positive(
#### Test_06 negative invalid_data
-@pytest.mark.locationlist
@pytest.mark.parametrize(
"invalid_data, expected_message",
[
@@ -147,7 +141,7 @@ def test_create_location_invalid_data_negative(
and expecting the error values
"""
# Logged into BSS_SO1
- UserTools().user_login(page, "BSO User1 - BS1")
+ 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()
@@ -158,7 +152,6 @@ def test_create_location_invalid_data_negative(
#### Test_07
-@pytest.mark.locationlist
def test_check_availability_of_locations_different_user_same_bso(
page: Page, rlp_location_list_page: ScreeningLocationListPage, context
) -> None:
@@ -166,7 +159,7 @@ def test_check_availability_of_locations_different_user_same_bso(
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 User1 - BS1")
+ 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()}"
@@ -175,14 +168,14 @@ def test_check_availability_of_locations_different_user_same_bso(
rlp_location_list_page.click_add_screening_location_btn_on_popup()
context.clear_cookies()
# Logged into BSS_SO1_User2
- UserTools().user_login(page, "BSO User2 - BS1")
+ 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_txtbox(location_name)
- # storing the User_2 filterd location_name in filterd_name
- filterd_name = rlp_location_list_page.value_of_filterd_location_name()
- # assering the the location name created by User_1 and filterd by User_2
- assert location_name == filterd_name
+ 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)
@@ -194,7 +187,6 @@ def test_check_availability_of_locations_different_user_same_bso(
#### Test_08
-@pytest.mark.locationlist
def test_check_availability_of_locations_different_user_different_bso(
page: Page, rlp_location_list_page: ScreeningLocationListPage, context
) -> None:
@@ -203,7 +195,7 @@ def test_check_availability_of_locations_different_user_different_bso(
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 User1 - BS1")
+ 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()}"
@@ -213,23 +205,22 @@ def test_check_availability_of_locations_different_user_different_bso(
rlp_location_list_page.click_log_out_btn()
context.clear_cookies()
# Logged into BSS_SO2_User2
- UserTools().user_login(page, "Read Only BSO User2 - BS2")
+ 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_txtbox(location_name)
+ 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 filterd location_name in filterd_name
- rlp_location_list_page.enter_screening_location_filter_txtbox(location_name)
- filterd_name = rlp_location_list_page.value_of_filterd_location_name()
- # assering the the location name created and filterd by User_2
- assert location_name == filterd_name
+ # 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 coveres Test_09, Test_10 and Test_11(valid_data_set)
-@pytest.mark.locationlist
+#### 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
@@ -239,7 +230,7 @@ def test_amend_screening_location(
asserting the amend_name vs actual amended value
"""
# Logged into BSS_SO1
- UserTools().user_login(page, "BSO User1 - BS1")
+ 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()}"
@@ -247,25 +238,24 @@ def test_amend_screening_location(
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_txtbox(location_name)
+ 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_randon string method and storing in the "amend_name" and clicking amend_button on the pop_up window
+ # 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_txtbox(amend_name)
- # storing the filterd location value in the "filterd_amend_name"
- filterd_amend_name = rlp_location_list_page.value_of_filterd_location_name()
- # assering the amend_value and filterd_amend_value
- assert amend_name == filterd_amend_name
+ 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 negatie test with invalid_data
-@pytest.mark.locationlist
+## Test_11 negative test with invalid_data
@pytest.mark.parametrize(
"invalid_data, expected_message",
[
@@ -289,15 +279,15 @@ def test_invalid_amend_screening_location(
and expecting the error values
"""
# Logged into BSS_SO1
- UserTools().user_login(page, "BSO User1 - BS1")
+ 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 exising location
- rlp_location_list_page.enter_screening_location_filter_txtbox(location_name)
+ # 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)
@@ -307,8 +297,7 @@ def test_invalid_amend_screening_location(
## Test_12
-@pytest.mark.locationlist
-def test_amend_screening_location_availabale_for_other_user_within_same_bso(
+def test_amend_screening_location_available_for_other_user_within_same_bso(
page: Page, rlp_location_list_page: ScreeningLocationListPage, context
) -> None:
"""
@@ -316,7 +305,7 @@ def test_amend_screening_location_availabale_for_other_user_within_same_bso(
asserting the amended value vs actual amended value
"""
# Logged into BSS_SO1
- UserTools().user_login(page, "BSO User1 - BS1")
+ 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()}"
@@ -324,10 +313,10 @@ def test_amend_screening_location_availabale_for_other_user_within_same_bso(
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_txtbox(location_name)
+ 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_randon string method and storing in the "amend_name" and clicking amend_button on the pop_up window
+ # 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()
@@ -335,18 +324,17 @@ def test_amend_screening_location_availabale_for_other_user_within_same_bso(
context.clear_cookies()
# Logged into BSS_SO1_User2
- UserTools().user_login(page, "BSO User2 - BS1")
+ 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_txtbox(amend_name)
- # storing the User_2 filterd location_name in filterd_name
- filterd_name = rlp_location_list_page.value_of_filterd_location_name()
- # assering the the location name created by User_1 and filterd by User_2
- assert amend_name == filterd_name
+ 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
-@pytest.mark.locationlist
def test_location_linked_to_multiple_cohorts(
page: Page, rlp_cohort_list_page: CohortListPage, context
) -> None:
@@ -354,12 +342,12 @@ def test_location_linked_to_multiple_cohorts(
created 2 cohorts and 1 location, linked 2 cohorts to 1 location
"""
# create unit for test data
- UserTools().user_login(page, "BSO User1 - BS1")
+ 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 User1 - BS1")
+ UserTools().user_login(page, "BSO User - BS1")
MainMenuPage(page).select_menu_option("Round Planning", "Screening Cohort List")
# create cohort inner function
@@ -383,5 +371,5 @@ def create_cohort(location_name, cohort_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)
- filterd_names = page.locator("//tr//td[2]").all_text_contents()
- assert input_names == filterd_names
+ filtered_names = page.locator("//tr//td[2]").all_text_contents()
+ assert input_names == filtered_names
From 1d99e414c905f73d76287901b8e945239f67a074 Mon Sep 17 00:00:00 2001
From: Dave Harding
Date: Tue, 24 Jun 2025 15:29:00 +0100
Subject: [PATCH 4/5] follow sonarqube amendment, remove example
---
buildBase.dockerfile | 5 ++
.../examples/python/.tool-versions.example | 2 -
scripts/docker/examples/python/Dockerfile | 33 ------------
.../examples/python/Dockerfile.effective | 54 -------------------
scripts/docker/examples/python/VERSION | 1 -
.../examples/python/assets/hello_world/app.py | 12 -----
.../assets/hello_world/requirements.txt | 12 -----
.../docker/examples/python/tests/goss.yaml | 8 ---
8 files changed, 5 insertions(+), 122 deletions(-)
delete mode 100644 scripts/docker/examples/python/.tool-versions.example
delete mode 100644 scripts/docker/examples/python/Dockerfile
delete mode 100644 scripts/docker/examples/python/Dockerfile.effective
delete mode 100644 scripts/docker/examples/python/VERSION
delete mode 100644 scripts/docker/examples/python/assets/hello_world/app.py
delete mode 100644 scripts/docker/examples/python/assets/hello_world/requirements.txt
delete mode 100644 scripts/docker/examples/python/tests/goss.yaml
diff --git a/buildBase.dockerfile b/buildBase.dockerfile
index a2045d4e..a95d5fbe 100644
--- a/buildBase.dockerfile
+++ b/buildBase.dockerfile
@@ -1,5 +1,10 @@
FROM python:3.13-slim
+RUN addgroup -S nonroot \
+ && adduser -S nonroot -G nonroot
+
+USER nonroot
+
WORKDIR /test
# Install dependencies
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
From 19f86a55017cb424dcce2cde68655f900cbe7022 Mon Sep 17 00:00:00 2001
From: Dave Harding
Date: Wed, 25 Jun 2025 08:01:15 +0100
Subject: [PATCH 5/5] Amendment to container and tested as working
---
buildBase.dockerfile | 20 ++++++++++++++------
1 file changed, 14 insertions(+), 6 deletions(-)
diff --git a/buildBase.dockerfile b/buildBase.dockerfile
index a95d5fbe..f600393e 100644
--- a/buildBase.dockerfile
+++ b/buildBase.dockerfile
@@ -1,9 +1,7 @@
FROM python:3.13-slim
-RUN addgroup -S nonroot \
- && adduser -S nonroot -G nonroot
-
-USER nonroot
+RUN addgroup --system nonroot \
+ && adduser --system --home /home/nonroot nonroot --ingroup nonroot
WORKDIR /test
@@ -11,17 +9,27 @@ WORKDIR /test
# 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 && \
playwright install --with-deps && \
playwright install chrome && \
mkdir -p /tests/ && \
- mkdir -p /utils/
+ mkdir -p /utils/ && \
+ mkdir -p /pages/
COPY ./tests/ ./tests/
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
+RUN chmod +x ./run_tests.sh \
+ && chown -R nonroot:nonroot /test
+
+USER nonroot