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