diff --git a/libs/__init__.py b/libs/__init__.py index 10e9957ce92..bb32b2cdeaa 100644 --- a/libs/__init__.py +++ b/libs/__init__.py @@ -10,24 +10,24 @@ class CurrentExecution: page = None browser = None - service_url: str | None = "" - base_auth_username: str | None = "" - base_auth_password: str | None = "" - current_browser_name: str | None = "" - headless_mode: bool | None = False - session_screenshots_dir: str | None = "" + service_url: str = "" + base_auth_username: str = "" + base_auth_password: str = "" + current_browser_name: str = "" + headless_mode: bool = False + session_screenshots_dir: str = "" - capture_screenshot_flag: bool | None = False - nurse_username: str | None = "" - nurse_password: str | None = "" - superuser_username: str | None = "" - superuser_password: str | None = "" - admin_username: str | None = "" - admin_password: str | None = "" - reset_endpoint: str | None = "" - api_token: str | None = "" - reset_env_before_execution: bool | None = False - slow_motion: int | None = 0 + capture_screenshot_flag: bool = False + nurse_username: str = "" + nurse_password: str = "" + superuser_username: str = "" + superuser_password: str = "" + admin_username: str = "" + admin_password: str = "" + reset_endpoint: str = "" + api_token: str = "" + reset_env_before_execution: bool = False + slow_motion: int = 0 screenshot_sequence: int = 0 child_list: list[str] = [] @@ -37,21 +37,27 @@ class CurrentExecution: @staticmethod def get_env_values(): load_dotenv() - CurrentExecution.service_url = os.getenv("BASE_URL") - CurrentExecution.base_auth_username = os.getenv("BASIC_AUTH_USERNAME") - CurrentExecution.base_auth_password = os.getenv("BASIC_AUTH_PASSWORD") - CurrentExecution.nurse_username = os.getenv("NURSE_USERNAME") - CurrentExecution.nurse_password = os.getenv("NURSE_PASSWORD") - CurrentExecution.superuser_username = os.getenv("SUPERUSER_USERNAME") - CurrentExecution.superuser_password = os.getenv("SUPERUSER_PASSWORD") - CurrentExecution.admin_username = os.getenv("ADMIN_USERNAME") - CurrentExecution.admin_password = os.getenv("ADMIN_PASSWORD") - CurrentExecution.headless_mode = os.getenv("HEADLESS", "True").lower() == "true" - CurrentExecution.capture_screenshot_flag = os.getenv("CAPTURE_SCREENSHOTS", "True").lower() == "true" - CurrentExecution.reset_endpoint = f"{CurrentExecution.service_url}{os.getenv('RESET_ENDPOINT')}" - CurrentExecution.api_token = os.getenv("API_TOKEN") - CurrentExecution.reset_env_before_execution = os.getenv("RESET_ENV_BEFORE_EXECUTION", "True").lower() == "true" - CurrentExecution.slow_motion = int(os.getenv("SLOW_MOTION", 0)) + CurrentExecution.service_url = CurrentExecution.get_env_value(var_name="BASE_URL") + CurrentExecution.base_auth_username = CurrentExecution.get_env_value(var_name="BASIC_AUTH_USERNAME") + CurrentExecution.base_auth_password = CurrentExecution.get_env_value(var_name="BASIC_AUTH_PASSWORD") + CurrentExecution.nurse_username = CurrentExecution.get_env_value(var_name="NURSE_USERNAME") + CurrentExecution.nurse_password = CurrentExecution.get_env_value(var_name="NURSE_PASSWORD") + CurrentExecution.superuser_username = CurrentExecution.get_env_value(var_name="SUPERUSER_USERNAME") + CurrentExecution.superuser_password = CurrentExecution.get_env_value(var_name="SUPERUSER_PASSWORD") + CurrentExecution.admin_username = CurrentExecution.get_env_value(var_name="ADMIN_USERNAME") + CurrentExecution.admin_password = CurrentExecution.get_env_value(var_name="ADMIN_PASSWORD") + CurrentExecution.headless_mode = CurrentExecution.get_env_value(var_name="HEADLESS").lower() == "true" + CurrentExecution.capture_screenshot_flag = ( + CurrentExecution.get_env_value(var_name="CAPTURE_SCREENSHOTS").lower() == "true" + ) + CurrentExecution.reset_endpoint = ( + f"{CurrentExecution.service_url}{CurrentExecution.get_env_value(var_name='RESET_ENDPOINT')}" + ) + CurrentExecution.api_token = CurrentExecution.get_env_value(var_name="API_TOKEN") + CurrentExecution.reset_env_before_execution = ( + CurrentExecution.get_env_value(var_name="RESET_ENV_BEFORE_EXECUTION").lower() == "true" + ) + CurrentExecution.slow_motion = int(CurrentExecution.get_env_value(var_name="SLOW_MOTION")) @staticmethod def reset_environment(): @@ -84,3 +90,11 @@ def set_session_id(session_id: str): @staticmethod def get_session_id() -> str: return CurrentExecution.session_id + + @staticmethod + def get_env_value(var_name: str) -> str: + _val = os.getenv(var_name) + if _val is None: + return "" + else: + return _val diff --git a/libs/playwright_ops.py b/libs/playwright_ops.py index 5dfc06dc9b6..6e2e55a2eb3 100644 --- a/libs/playwright_ops.py +++ b/libs/playwright_ops.py @@ -1,6 +1,9 @@ import os import re from itertools import chain +from socket import timeout + +import allure from libs import CurrentExecution from libs.generic_constants import ( @@ -169,14 +172,15 @@ def _click_link(self, locator: str, exact: bool, index: int): exact (bool): Whether to match the link text exactly. index (int): Index of the link if multiple matches are found. """ - if escape_characters.SEPARATOR_CHAR in locator: - _location = locator.split(escape_characters.SEPARATOR_CHAR)[0] - _locator = locator.split(escape_characters.SEPARATOR_CHAR)[1] - elem = self.ce.page.get_by_role(_location, name=_locator, exact=exact).nth(index) - else: - elem = self.ce.page.get_by_role(aria_roles.LINK, name=locator, exact=exact).nth(index) - elem.click() - self._check_for_app_crash(locator_info=locator) + with allure.step(title=f"Clicking [{locator}]"): + if escape_characters.SEPARATOR_CHAR in locator: + _location = locator.split(escape_characters.SEPARATOR_CHAR)[0] + _locator = locator.split(escape_characters.SEPARATOR_CHAR)[1] + elem = self.ce.page.get_by_role(_location, name=_locator, exact=exact).nth(index) + else: + elem = self.ce.page.get_by_role(aria_roles.LINK, name=locator, exact=exact).nth(index) + elem.click() + self._check_for_app_crash(locator_info=locator) def _click_button(self, locator: str, exact: bool, index: int): """ @@ -187,14 +191,15 @@ def _click_button(self, locator: str, exact: bool, index: int): exact (bool): Whether to match the button text exactly. index (int): Index of the button if multiple matches are found. """ - if escape_characters.SEPARATOR_CHAR in locator: - _location = locator.split(escape_characters.SEPARATOR_CHAR)[0] - _locator = locator.split(escape_characters.SEPARATOR_CHAR)[1] - elem = self.ce.page.get_by_role(_location, name=_locator).nth(index) - else: - elem = self.ce.page.get_by_role(aria_roles.BUTTON, name=locator, exact=exact).nth(index) - elem.click() - self._check_for_app_crash(locator_info=locator) + with allure.step(title=f"Clicking [{locator}]"): + if escape_characters.SEPARATOR_CHAR in locator: + _location = locator.split(escape_characters.SEPARATOR_CHAR)[0] + _locator = locator.split(escape_characters.SEPARATOR_CHAR)[1] + elem = self.ce.page.get_by_role(_location, name=_locator).nth(index) + else: + elem = self.ce.page.get_by_role(aria_roles.BUTTON, name=locator, exact=exact).nth(index) + elem.click() + self._check_for_app_crash(locator_info=locator) def _click_label(self, locator: str, exact: bool, index: int): """ @@ -205,13 +210,14 @@ def _click_label(self, locator: str, exact: bool, index: int): exact (bool): Whether to match the label text exactly. index (int): Index of the label if multiple matches are found. """ - if escape_characters.SEPARATOR_CHAR in locator: - _location = locator.split(escape_characters.SEPARATOR_CHAR)[0] - _locator = locator.split(escape_characters.SEPARATOR_CHAR)[1] - elem = self.ce.page.get_by_role(_location, name=_locator).nth(index) - else: - elem = self.ce.page.get_by_label(locator, exact=exact).nth(index) - elem.click() + with allure.step(title=f"Clicking [{locator}]"): + if escape_characters.SEPARATOR_CHAR in locator: + _location = locator.split(escape_characters.SEPARATOR_CHAR)[0] + _locator = locator.split(escape_characters.SEPARATOR_CHAR)[1] + elem = self.ce.page.get_by_role(_location, name=_locator).nth(index) + else: + elem = self.ce.page.get_by_label(locator, exact=exact).nth(index) + elem.click() def _click_text(self, locator: str, exact: bool, index: int): """ @@ -222,13 +228,14 @@ def _click_text(self, locator: str, exact: bool, index: int): exact (bool): Whether to match the text exactly. index (int): Index of the text if multiple matches are found. """ - if escape_characters.SEPARATOR_CHAR in locator: - _location = locator.split(escape_characters.SEPARATOR_CHAR)[0] - _locator = locator.split(escape_characters.SEPARATOR_CHAR)[1] - elem = self.ce.page.get_by_text(_location, name=_locator).nth(index) - else: - elem = self.ce.page.get_by_text(locator, exact=exact).nth(index) - elem.click() + with allure.step(title=f"Clicking [{locator}]"): + if escape_characters.SEPARATOR_CHAR in locator: + _location = locator.split(escape_characters.SEPARATOR_CHAR)[0] + _locator = locator.split(escape_characters.SEPARATOR_CHAR)[1] + elem = self.ce.page.get_by_text(_location, name=_locator).nth(index) + else: + elem = self.ce.page.get_by_text(locator, exact=exact).nth(index) + elem.click() def _fill(self, locator: str, value: str, exact: bool, index: int): """ @@ -240,15 +247,16 @@ def _fill(self, locator: str, value: str, exact: bool, index: int): exact (bool): Whether to match the input field label exactly. index (int): Index of the input field if multiple matches are found. """ - if escape_characters.SEPARATOR_CHAR in locator: - _location = locator.split(escape_characters.SEPARATOR_CHAR)[0] - _locator = locator.split(escape_characters.SEPARATOR_CHAR)[1] - elem = self.ce.page.get_by_role(_location, name=_locator).nth(index) - else: - elem = self.ce.page.get_by_label(locator, exact=exact).nth(index) - elem.click() - if value != test_data_values.EMPTY: - elem.fill(value) + with allure.step(title=f"Typing [{value}] in [{locator}]"): + if escape_characters.SEPARATOR_CHAR in locator: + _location = locator.split(escape_characters.SEPARATOR_CHAR)[0] + _locator = locator.split(escape_characters.SEPARATOR_CHAR)[1] + elem = self.ce.page.get_by_role(_location, name=_locator).nth(index) + else: + elem = self.ce.page.get_by_label(locator, exact=exact).nth(index) + elem.click() + if value != test_data_values.EMPTY: + elem.fill(value) def _radio_button_select(self, locator: str, exact: bool, index: int): """ @@ -259,13 +267,14 @@ def _radio_button_select(self, locator: str, exact: bool, index: int): exact (bool): Whether to match the radio button label exactly. index (int): Index of the radio button if multiple matches are found. """ - if escape_characters.SEPARATOR_CHAR in locator: - _location = locator.split(escape_characters.SEPARATOR_CHAR)[0] - _locator = locator.split(escape_characters.SEPARATOR_CHAR)[1] - elem = self.ce.page.get_by_role(_location, name=_locator).nth(index) - else: - elem = self.ce.page.get_by_label(locator, exact=exact).nth(index) - elem.click() + with allure.step(title=f"Selecting radio button [{locator}]"): + if escape_characters.SEPARATOR_CHAR in locator: + _location = locator.split(escape_characters.SEPARATOR_CHAR)[0] + _locator = locator.split(escape_characters.SEPARATOR_CHAR)[1] + elem = self.ce.page.get_by_role(_location, name=_locator).nth(index) + else: + elem = self.ce.page.get_by_label(locator, exact=exact).nth(index) + elem.click() def _select_file(self, locator: str, value: str, exact: bool, index: int): """ @@ -277,13 +286,14 @@ def _select_file(self, locator: str, value: str, exact: bool, index: int): exact (bool): Whether to match the file input label exactly. index (int): Index of the file input element if multiple matches are found. """ - if escape_characters.SEPARATOR_CHAR in locator: - _location = locator.split(escape_characters.SEPARATOR_CHAR)[0] - _locator = locator.split(escape_characters.SEPARATOR_CHAR)[1] - elem = self.ce.page.get_by_role(_location, name=_locator).nth(index) - else: - elem = self.ce.page.get_by_label(locator, exact=exact).nth(index) - elem.set_input_files(value) + with allure.step(title=f"Uploading file [{value}] to [{locator}]"): + if escape_characters.SEPARATOR_CHAR in locator: + _location = locator.split(escape_characters.SEPARATOR_CHAR)[0] + _locator = locator.split(escape_characters.SEPARATOR_CHAR)[1] + elem = self.ce.page.get_by_role(_location, name=_locator).nth(index) + else: + elem = self.ce.page.get_by_label(locator, exact=exact).nth(index) + elem.set_input_files(value) def _select_from_list(self, locator: str, value: str, index: int): """ @@ -294,15 +304,16 @@ def _select_from_list(self, locator: str, value: str, index: int): value (str): Value to select from the list. index (int): Index of the dropdown list if multiple matches are found. """ - self._fill(locator=locator, value=value, exact=False, index=index) - self._wait(time_out=wait_time.MIN) - if escape_characters.SEPARATOR_CHAR in locator: - _location = locator.split(escape_characters.SEPARATOR_CHAR)[0] - _locator = locator.split(escape_characters.SEPARATOR_CHAR)[1] - elem = self.ce.page.get_by_role(_location, name=_locator).nth(index) - else: - elem = self.ce.page.get_by_role(aria_roles.OPTION, name=value) - elem.click() + with allure.step(title=f"Selecting [{value}] within list [{locator}]"): + self._fill(locator=locator, value=value, exact=False, index=index) + self._wait(time_out=wait_time.MIN) + if escape_characters.SEPARATOR_CHAR in locator: + _location = locator.split(escape_characters.SEPARATOR_CHAR)[0] + _locator = locator.split(escape_characters.SEPARATOR_CHAR)[1] + elem = self.ce.page.get_by_role(_location, name=_locator).nth(index) + else: + elem = self.ce.page.get_by_role(aria_roles.OPTION, name=value) + elem.click() def _checkbox_check(self, locator: str, index: int): """ @@ -312,13 +323,14 @@ def _checkbox_check(self, locator: str, index: int): locator (str): Locator of the checkbox. index (int): Index of the checkbox if multiple matches are found. """ - if escape_characters.SEPARATOR_CHAR in locator: - _location = locator.split(escape_characters.SEPARATOR_CHAR)[0] - _locator = locator.split(escape_characters.SEPARATOR_CHAR)[1] - elem = self.ce.page.get_by_role(_location, name=_locator).nth(index) - else: - elem = self.ce.page.get_by_label(locator).nth(index) - elem.check() + with allure.step(title=f"Ticking checkbox [{locator}]"): + if escape_characters.SEPARATOR_CHAR in locator: + _location = locator.split(escape_characters.SEPARATOR_CHAR)[0] + _locator = locator.split(escape_characters.SEPARATOR_CHAR)[1] + elem = self.ce.page.get_by_role(_location, name=_locator).nth(index) + else: + elem = self.ce.page.get_by_label(locator).nth(index) + elem.check() def _checkbox_uncheck(self, locator: str, index: int): """ @@ -328,13 +340,14 @@ def _checkbox_uncheck(self, locator: str, index: int): locator (str): Locator of the checkbox. index (int): Index of the checkbox if multiple matches are found. """ - if escape_characters.SEPARATOR_CHAR in locator: - _location = locator.split(escape_characters.SEPARATOR_CHAR)[0] - _locator = locator.split(escape_characters.SEPARATOR_CHAR)[1] - elem = self.ce.page.get_by_role(_location, name=_locator).nth(index) - else: - elem = self.ce.page.get_by_label(locator).nth(0) - elem.uncheck() + with allure.step(title=f"Un-checking checkbox [{locator}]"): + if escape_characters.SEPARATOR_CHAR in locator: + _location = locator.split(escape_characters.SEPARATOR_CHAR)[0] + _locator = locator.split(escape_characters.SEPARATOR_CHAR)[1] + elem = self.ce.page.get_by_role(_location, name=_locator).nth(index) + else: + elem = self.ce.page.get_by_label(locator).nth(0) + elem.uncheck() def _click_index_for_row(self, locator: str, value: str, index: int): """ @@ -345,21 +358,22 @@ def _click_index_for_row(self, locator: str, value: str, index: int): value (str): Value to identify the link in the row. index (int): Index of the row if multiple matches are found. """ - if escape_characters.SEPARATOR_CHAR in locator: - _location = locator.split(escape_characters.SEPARATOR_CHAR)[0] - _locator = locator.split(escape_characters.SEPARATOR_CHAR)[1] - elem = self.ce.page.get_by_role(_location, name=_locator).nth(index) - else: - elem = ( - self.ce.page.get_by_role( - aria_roles.ROW, - name=locator, + with allure.step(title=f"Clicking index [{index}] in table [{locator}]"): + if escape_characters.SEPARATOR_CHAR in locator: + _location = locator.split(escape_characters.SEPARATOR_CHAR)[0] + _locator = locator.split(escape_characters.SEPARATOR_CHAR)[1] + elem = self.ce.page.get_by_role(_location, name=_locator).nth(index) + else: + elem = ( + self.ce.page.get_by_role( + aria_roles.ROW, + name=locator, + ) + .get_by_role(aria_roles.LINK) + .nth(index) ) - .get_by_role(aria_roles.LINK) - .nth(index) - ) - elem.scroll_into_view_if_needed() - elem.click() + elem.scroll_into_view_if_needed() + elem.click() def _download_file_using_link(self, locator: str, value: str, index: int): """ @@ -370,11 +384,12 @@ def _download_file_using_link(self, locator: str, value: str, index: int): value (str): Path to save the downloaded file. index (int): Index of the link if multiple matches are found. """ - with self.ce.page.expect_download() as download_info: - self.act(locator=locator, action=framework_actions.CLICK_LINK, index=index) - download = download_info.value - download.save_as(value) - self._check_for_app_crash(locator_info=locator) + with allure.step(title=f"Downloading file [{value}] from [{locator}]"): + with self.ce.page.expect_download() as download_info: + self.act(locator=locator, action=framework_actions.CLICK_LINK, index=index) + download = download_info.value + download.save_as(value) + self._check_for_app_crash(locator_info=locator) def _download_file_using_button(self, locator: str, value: str, index: int): """ @@ -385,11 +400,12 @@ def _download_file_using_button(self, locator: str, value: str, index: int): value (str): Path to save the downloaded file. index (int): Index of the button if multiple matches are found. """ - with self.ce.page.expect_download() as download_info: - self.act(locator=locator, action=framework_actions.CLICK_BUTTON, index=index) - download = download_info.value - download.save_as(value) - self._check_for_app_crash(locator_info=locator) + with allure.step(title=f"Downloading file [{value}] from [{locator}]"): + with self.ce.page.expect_download() as download_info: + self.act(locator=locator, action=framework_actions.CLICK_BUTTON, index=index) + download = download_info.value + download.save_as(value) + self._check_for_app_crash(locator_info=locator) def _verify_text(self, locator: str, expected_value: str, actual_value: str, exact: bool): """ @@ -401,31 +417,34 @@ def _verify_text(self, locator: str, expected_value: str, actual_value: str, exa actual_value (str): Actual text value. exact (bool): Whether to match the text exactly. """ - if expected_value.startswith(escape_characters.COMMENT_OPERATOR): # Skip this check - return - if exact: - _passed: bool = True if expected_value == actual_value else False - if _passed: - self.capture_screenshot(identifier=locator, action=screenshot_actions.VERIFY_TEXT_PASSED) - else: - self.capture_screenshot(identifier=locator, action=screenshot_actions.VERIFY_TEXT_FAILED) - assert _passed, f"Exact match failed. Expected: '{expected_value}' but actual: '{actual_value}'." - else: - if expected_value.startswith(escape_characters.NOT_OPERATOR): - expected_value = expected_value.removeprefix(escape_characters.NOT_OPERATOR) - _passed: bool = True if clean_text(text=expected_value) not in clean_text(text=actual_value) else False + with allure.step(title=f"Verifying text [{expected_value}]"): + if expected_value.startswith(escape_characters.COMMENT_OPERATOR): # Skip this check + return + if exact: + _passed: bool = True if expected_value == actual_value else False if _passed: self.capture_screenshot(identifier=locator, action=screenshot_actions.VERIFY_TEXT_PASSED) else: self.capture_screenshot(identifier=locator, action=screenshot_actions.VERIFY_TEXT_FAILED) - assert _passed, f"Text '{expected_value}' not expected but found in '{actual_value}'." + assert _passed, f"Exact match failed. Expected: '{expected_value}' but actual: '{actual_value}'." else: - _passed: bool = True if clean_text(text=expected_value) in clean_text(text=actual_value) else False - if _passed: - self.capture_screenshot(identifier=locator, action=screenshot_actions.VERIFY_TEXT_PASSED) + if expected_value.startswith(escape_characters.NOT_OPERATOR): + expected_value = expected_value.removeprefix(escape_characters.NOT_OPERATOR) + _passed: bool = ( + True if clean_text(text=expected_value) not in clean_text(text=actual_value) else False + ) + if _passed: + self.capture_screenshot(identifier=locator, action=screenshot_actions.VERIFY_TEXT_PASSED) + else: + self.capture_screenshot(identifier=locator, action=screenshot_actions.VERIFY_TEXT_FAILED) + assert _passed, f"Text '{expected_value}' not expected but found in '{actual_value}'." else: - self.capture_screenshot(identifier=locator, action=screenshot_actions.VERIFY_TEXT_FAILED) - assert _passed, f"Text '{expected_value}' not found in '{actual_value}'." + _passed: bool = True if clean_text(text=expected_value) in clean_text(text=actual_value) else False + if _passed: + self.capture_screenshot(identifier=locator, action=screenshot_actions.VERIFY_TEXT_PASSED) + else: + self.capture_screenshot(identifier=locator, action=screenshot_actions.VERIFY_TEXT_FAILED) + assert _passed, f"Text '{expected_value}' not found in '{actual_value}'." def _verify_visibility(self, locator: str, expected_value: str, actual_value: str): """ @@ -436,12 +455,13 @@ def _verify_visibility(self, locator: str, expected_value: str, actual_value: st expected_value (str): Expected visibility state. actual_value (str): Actual visibility state. """ - _passed: bool = True if actual_value == expected_value else False - if _passed: - self.capture_screenshot(identifier=locator, action=screenshot_actions.VERIFY_VISIBILITY_PASSED) - else: - self.capture_screenshot(identifier=locator, action=screenshot_actions.VERIFY_VISIBILITY_FAILED) - assert _passed, f"{locator} is not visible." + with allure.step(title=f"Verifying visibility [{locator}]"): + _passed: bool = True if actual_value == expected_value else False + if _passed: + self.capture_screenshot(identifier=locator, action=screenshot_actions.VERIFY_VISIBILITY_PASSED) + else: + self.capture_screenshot(identifier=locator, action=screenshot_actions.VERIFY_VISIBILITY_FAILED) + assert _passed, f"{locator} is not visible." def _verify_checkbox(self, locator: str, expected_value: str, actual_value: str): """ @@ -452,13 +472,14 @@ def _verify_checkbox(self, locator: str, expected_value: str, actual_value: str) expected_value (str): Expected checked state. actual_value (str): Actual checked state. """ - _passed: bool = True if bool(actual_value) == bool(expected_value) else False - _checked: str = "" if bool(actual_value) else "not" - if _passed: - self.capture_screenshot(identifier=locator, action=screenshot_actions.VERIFY_CHECKED_PASSED) - else: - self.capture_screenshot(identifier=locator, action=screenshot_actions.VERIFY_CHECKED_FAILED) - assert _passed, f"{locator} is {_checked} checked." + with allure.step(title=f"Verifying checkbox [{locator}]"): + _passed: bool = True if bool(actual_value) == bool(expected_value) else False + _checked: str = "" if bool(actual_value) else "not" + if _passed: + self.capture_screenshot(identifier=locator, action=screenshot_actions.VERIFY_CHECKED_PASSED) + else: + self.capture_screenshot(identifier=locator, action=screenshot_actions.VERIFY_CHECKED_FAILED) + assert _passed, f"{locator} is {_checked} checked." def _get_element_text(self, locator: str, index: int, by_test_id: bool, chain_locator: bool) -> str: """ @@ -557,7 +578,7 @@ def _get_element_href(self, locator: str, index: int, chain_locator: bool) -> st elem = eval(f"{self.PAGE_ELEMENT_PATH}{locator}") else: elem = self.ce.page.get_by_role(aria_roles.LINK, name=locator).nth(index) - return elem.get_attribute(element_properties.HREF) + return elem.get_attribute(element_properties.HREF.name) def _wait(self, time_out: str): """ @@ -566,8 +587,9 @@ def _wait(self, time_out: str): Args: time_out (str): Time to wait (e.g., "1s", "1m"). """ - _seconds = convert_time_units_to_seconds(time_unit=time_out) - time.sleep(_seconds) + with allure.step(title=f"Waiting {time_out}"): + _seconds = convert_time_units_to_seconds(time_unit=time_out) + time.sleep(_seconds) def _check_for_app_crash(self, locator_info: str): """ diff --git a/libs/testdata_ops.py b/libs/testdata_ops.py index 936255f9baf..37968ae18d9 100644 --- a/libs/testdata_ops.py +++ b/libs/testdata_ops.py @@ -142,7 +142,7 @@ def get_file_paths(self, file_paths: str) -> tuple[str, str]: ) return _input_file_path, _output_template_path - def create_child_list_from_file(self, file_path: str, file_type: str): + def create_child_list_from_file(self, file_path: str, file_type: mavis_file_types): """ Create a list of child names from a file. @@ -153,18 +153,21 @@ def create_child_list_from_file(self, file_path: str, file_type: str): list: List of child names. """ _file_df = self.fo.read_csv_to_df(file_path=file_path) + # _file_df.replace("\xa0", " ", regex=False, inplace=True) # NBSP + # _file_df.replace("\u200d", " ", regex=False, inplace=True) # ZWJ match file_type: case mavis_file_types.CHILD_LIST | mavis_file_types.COHORT | mavis_file_types.CLASS_LIST: _child_list = _file_df[["CHILD_FIRST_NAME", "CHILD_LAST_NAME"]] - return _child_list["CHILD_LAST_NAME"] + ", " + _child_list["CHILD_FIRST_NAME"].values.tolist() + _list = _child_list["CHILD_LAST_NAME"] + ", " + _child_list["CHILD_FIRST_NAME"].values.tolist() case mavis_file_types.VACCS_MAVIS: _child_list = _file_df[["PERSON_FORENAME", "PERSON_SURNAME"]] - return _child_list["PERSON_SURNAME"] + ", " + _child_list["PERSON_FIRSTNAME"].values.tolist() + _list = _child_list["PERSON_SURNAME"] + ", " + _child_list["PERSON_FORENAME"].values.tolist() case mavis_file_types.VACCS_SYSTMONE: _child_list = _file_df[["First name", "Surname"]] - return _child_list["Surname"] + ", " + _child_list["First name"].values.tolist() - case _: - return None + _list = _child_list["Surname"] + ", " + _child_list["First name"].values.tolist() + _list = _list.replace(" ", " ") + _list = _list.replace("\u200d", " ") + return _list def get_session_id(self, excel_path: str) -> str: """ diff --git a/pages/pg_children.py b/pages/pg_children.py index 2a9d77110cc..959c6311ed2 100644 --- a/pages/pg_children.py +++ b/pages/pg_children.py @@ -49,7 +49,9 @@ def verify_child_has_been_uploaded(self, child_list) -> None: self.dashboard_page.go_to_dashboard() self.dashboard_page.click_children() for _child_name in child_list: - _cn = _child_name.strip() + _cn: str = _child_name.strip() + _cn = _cn.replace("\xa0", " ") + _cn = _cn.replace("\u200d", " ") self.search_for_a_child(child_name=_cn) # self.po.act(locator=self.LNK_CLEAR_FILTERS, action=framework_actions.CLICK_LINK) diff --git a/pages/pg_import_records.py b/pages/pg_import_records.py index 99e3e084abf..2241e331e1d 100644 --- a/pages/pg_import_records.py +++ b/pages/pg_import_records.py @@ -112,8 +112,15 @@ def import_class_list_records_from_school_session(self, file_paths: str): self._click_uploaded_file_datetime(truncated=True) self._verify_upload_output(file_path=_output_file_path) - def import_vaccination_records(self, file_paths: str): + def import_vaccination_records( + self, + file_paths: str, + file_type: mavis_file_types = mavis_file_types.VACCS_MAVIS, + verify_on_children_page: bool = False, + ): _input_file_path, _output_file_path = self.tdo.get_file_paths(file_paths=file_paths) + if verify_on_children_page: + _cl = self.tdo.create_child_list_from_file(file_path=_input_file_path, file_type=file_type) self.po.act(locator=self.LNK_IMPORT_RECORDS, action=framework_actions.CLICK_LINK) self.po.act(locator=self.RDO_VACCINATION_RECORDS, action=framework_actions.RADIO_BUTTON_SELECT) self.po.act(locator=self.BTN_CONTINUE, action=framework_actions.CLICK_BUTTON) @@ -128,6 +135,8 @@ def import_vaccination_records(self, file_paths: str): if self.ce.get_file_record_count() > record_limit.FILE_RECORD_MAX_THRESHOLD: self._click_uploaded_file_datetime(truncated=True) self._verify_upload_output(file_path=_output_file_path) + if verify_on_children_page: + self.children_page.verify_child_has_been_uploaded(child_list=_cl) def _record_upload_time(self): self.upload_time = get_link_formatted_date_time() diff --git a/pages/pg_programmes.py b/pages/pg_programmes.py index d7736cae541..b86ce234f92 100644 --- a/pages/pg_programmes.py +++ b/pages/pg_programmes.py @@ -247,7 +247,13 @@ def _download_and_verify_report_headers(self, expected_headers: str): self.po.act(locator=self.BTN_CONTINUE, action=framework_actions.DOWNLOAD_FILE_USING_BUTTON, value=_file_path) _actual_df = self.fo.read_csv_to_df(file_path=_file_path) actual_headers = ",".join(_actual_df.columns.tolist()) - assert expected_headers == actual_headers, "Report headers do not match" + # assert expected_headers == actual_headers, "Report headers do not match" + _e_not_a = [h for h in expected_headers.split(",") if h not in actual_headers.split(",")] + _a_not_e = [h for h in actual_headers.split(",") if h not in expected_headers.split(",")] + if len(_e_not_a) > 0 or len(_a_not_e) > 0: + assert ( + False + ), f"Expected field(s) not found in actual: {_e_not_a}. Actual report contains extra field(s): {_a_not_e}." def verify_mav_965(self): """ diff --git a/pytest.ini b/pytest.ini index 3d25fa86621..007ecc38bee 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,16 +1,9 @@ [pytest] - -addopts = -vs -rf --alluredir=./reports/ -#-n auto --dist=loadfile - log_file = logs/pytest.log -log_cli = true log_file_date_format = %Y-%m-%d %H:%M:%S log_file_format = %(asctime)s - %(name)s %(levelname)s %(message)s -log_file_level = INFO - +addopts = -vs -rf --alluredir=./reports/ testpaths = tests - markers = mobile smoke diff --git a/test_data/vaccs/i_mav_1080.csv b/test_data/vaccs/i_mav_1080.csv index 27f31413c86..03d61559b29 100644 --- a/test_data/vaccs/i_mav_1080.csv +++ b/test_data/vaccs/i_mav_1080.csv @@ -1,5 +1,5 @@ TEST_DESC_IGNORED,ORGANISATION_CODE,SCHOOL_URN,SCHOOL_NAME,NHS_NUMBER,PERSON_FORENAME,PERSON_SURNAME,PERSON_DOB,PERSON_GENDER_CODE,PERSON_POSTCODE,DATE_OF_VACCINATION,VACCINE_GIVEN,BATCH_NUMBER,BATCH_EXPIRY_DATE,ANATOMICAL_SITE,DOSE_SEQUENCE,VACCINATED,CARE_SETTING,PERFORMING_PROFESSIONAL_FORENAME,PERFORMING_PROFESSIONAL_SURNAME,PERFORMING_PROFESSIONAL_EMAIL,CLINIC_NAME,TIME_OF_VACCINATION,REASON_NOT_VACCINATED,SESSION_ID,PROGRAMME TwoSpaces,<> , <> , <> , <> , <> , <> , <> , Male , DN9 1PB , <> , Gardasil9 , AutoBatch1 , 20301231 , Left Thigh , 1 , Y , 1 , , , nurse.joy@example.com , Clinic , 00:01 , , <> , HPV Tabs,<> , <> , <> , <> , <> , <> , <> , Male , DN9 1PB , <> , Gardasil , AutoBatch1 , 20301231 , Right Thigh , 2 , Y , 1 , , , nurse.joy@example.com , Clinic , 00:01 , , <> , HPV -NBSP,<>,<>,<>,<>,<>,<>,<>,Male,DN9 1PB,<>,Cer varix,AutoBatch1,20301231,left upper arm,3,Y,1,,,nurse.joy@example.com,Clinic,00:01,,<>,HPV -ZWJ,<>,<>,<>,<>,<>,<>,<>,Male,ZZ99 3VZ,<>,Gard‍asil9,AutoBatch1,20301231,left arm (upper position),1,Y,1,,,nurse.joy@example.com,Clinic,00:01,,<>,HPV +NBSP,<>,<>,<>,<>,NBSP <>,<>,<>,Male,DN9 1PB,<>,Cervarix,AutoBatch1,20301231,left upper arm,3,Y,1,,,nurse.joy@example.com,Clinic,00:01,,<>,HPV +ZWJ,<>,<>,<>,<>,ZWJ‍<>,<>,<>,Male,ZZ99 3VZ,<>,Gardasil9,AutoBatch1,20301231,left arm (upper position),1,Y,1,,,nurse.joy@example.com,Clinic,00:01,,<>,HPV diff --git a/test_data/vaccs/i_systmone_mav_1080.csv b/test_data/vaccs/i_systmone_mav_1080.csv index fd8d64e66d4..65d01c2de2b 100644 --- a/test_data/vaccs/i_systmone_mav_1080.csv +++ b/test_data/vaccs/i_systmone_mav_1080.csv @@ -1,9 +1,9 @@ TEST_DESC_IGNORED,Date of birth,NHS number,Vaccination area code,Vaccination batch number,Vaccination reason,Vaccination type,First name,Postcode,Sex,Surname,Event date,Event location type,Event time,Organisation ID,School,School code,Patient Count TwoSpaces, <> , <> , , 123013325 , , Cervarix 1 , C<> , LE3 2DA , Female , C<> , <> , School , <> , <> , <> , <> , Tabs,<> , <> , , 123013325 , , Gardasil9 1 , C<> , LE3 2DA , Female , C<> , <> , School , <> , <> , <> , <> , -NBSP,<>,<>,,123013325,,Gard asil9 1,C<>,LE3 2DA,Female,C<>,<>,School,<>,<>,<>,<>, -ZWJ,<>,<>,,123013325,,Gar‍dasil9 1,C<>,LE3 2DA,Female,C<>,<>,School,<>,<>,<>,<>, +NBSP,<>,<>,,123013325,,Gardasil9 1,NBSP <>,LE3 2DA,Female,C<>,<>,School,<>,<>,<>,<>, +ZWJ,<>,<>,,123013325,,Gardasil9 1,ZWJ‍<>,LE3 2DA,Female,C<>,<>,School,<>,<>,<>,<>, HistoricalTwoSpaces, <> , <> , , 123013325 , , Cervarix 1 , C<> , LE3 2DA , Female , C<> , <> , School , <> , <> , <> , <> , HistoricalTabs,<> , <> , , 123013325 , , Gardasil9 1 , C<> , LE3 2DA , Female , C<> , <> , School , <> , <> , <> , <> , -HistoricalNBSP,<>,<>,,123013325,,Gard asil9 1,C<>,LE3 2DA,Female,C<>,<>,School,<>,<>,<>,<>, -HistoricalZWJ,<>,<>,,123013325,,Gar‍dasil9 1,C<>,LE3 2DA,Female,C<>,<>,School,<>,<>,<>,<>, +HistoricalNBSP,<>,<>,,123013325,,Gardasil9 1,NBSP <>,LE3 2DA,Female,C<>,<>,School,<>,<>,<>,<>, +HistoricalZWJ,<>,<>,,123013325,,Gardasil9 1,ZWJ‍<>,LE3 2DA,Female,C<>,<>,School,<>,<>,<>,<>, diff --git a/tests/test_03_import_records.py b/tests/test_03_import_records.py index d29a517a571..52ddada7738 100644 --- a/tests/test_03_import_records.py +++ b/tests/test_03_import_records.py @@ -1,6 +1,10 @@ import pytest -from libs.mavis_constants import child_year_group, test_data_file_paths +from libs.mavis_constants import ( + child_year_group, + mavis_file_types, + test_data_file_paths, +) from pages import pg_dashboard, pg_import_records, pg_login, pg_sessions @@ -93,7 +97,7 @@ def test_child_list_empty_file(self, setup_child_list): @pytest.mark.childlist @pytest.mark.order(306) - def test_child_list_space_normalisation(self, setup_child_list): + def test_child_list_space_normalization(self, setup_child_list): self.import_records_page.import_child_records( file_paths=test_data_file_paths.CHILD_MAV_1080, verify_on_children_page=True ) @@ -144,50 +148,70 @@ def test_class_list_space_normalization(self, setup_class_list: None): @pytest.mark.vaccinations @pytest.mark.order(351) def test_vaccs_positive_file_upload(self, setup_vaccs): - self.import_records_page.import_vaccination_records(file_paths=test_data_file_paths.VACCS_POSITIVE) + self.import_records_page.import_vaccination_records( + file_paths=test_data_file_paths.VACCS_POSITIVE, file_type=mavis_file_types.VACCS_MAVIS + ) @pytest.mark.vaccinations @pytest.mark.order(352) def test_vaccs_negative_file_upload(self, setup_vaccs): - self.import_records_page.import_vaccination_records(file_paths=test_data_file_paths.VACCS_NEGATIVE) + self.import_records_page.import_vaccination_records( + file_paths=test_data_file_paths.VACCS_NEGATIVE, file_type=mavis_file_types.VACCS_MAVIS + ) @pytest.mark.vaccinations @pytest.mark.order(353) def test_vaccs_duplicate_record_upload(self, setup_vaccs): - self.import_records_page.import_vaccination_records(file_paths=test_data_file_paths.VACCS_DUP_1) + self.import_records_page.import_vaccination_records( + file_paths=test_data_file_paths.VACCS_DUP_1, file_type=mavis_file_types.VACCS_MAVIS + ) self.dashboard_page.go_to_dashboard() self.dashboard_page.click_import_records() - self.import_records_page.import_vaccination_records(file_paths=test_data_file_paths.VACCS_DUP_2) + self.import_records_page.import_vaccination_records( + file_paths=test_data_file_paths.VACCS_DUP_2, file_type=mavis_file_types.VACCS_MAVIS + ) @pytest.mark.vaccinations @pytest.mark.order(354) def test_vaccs_file_structure(self, setup_vaccs): - self.import_records_page.import_vaccination_records(file_paths=test_data_file_paths.VACCS_INVALID_STRUCTURE) + self.import_records_page.import_vaccination_records( + file_paths=test_data_file_paths.VACCS_INVALID_STRUCTURE, file_type=mavis_file_types.VACCS_MAVIS + ) @pytest.mark.vaccinations @pytest.mark.order(355) def test_vaccs_no_record(self, setup_vaccs): - self.import_records_page.import_vaccination_records(file_paths=test_data_file_paths.VACCS_HEADER_ONLY) + self.import_records_page.import_vaccination_records( + file_paths=test_data_file_paths.VACCS_HEADER_ONLY, file_type=mavis_file_types.VACCS_MAVIS + ) @pytest.mark.vaccinations @pytest.mark.order(356) def test_vaccs_empty_file(self, setup_vaccs): - self.import_records_page.import_vaccination_records(file_paths=test_data_file_paths.VACCS_EMPTY_FILE) + self.import_records_page.import_vaccination_records( + file_paths=test_data_file_paths.VACCS_EMPTY_FILE, file_type=mavis_file_types.VACCS_MAVIS + ) @pytest.mark.vaccinations @pytest.mark.order(357) def test_vaccs_historic_positive_file_upload(self, setup_vaccs): - self.import_records_page.import_vaccination_records(file_paths=test_data_file_paths.VACCS_HIST_POSITIVE) + self.import_records_page.import_vaccination_records( + file_paths=test_data_file_paths.VACCS_HIST_POSITIVE, file_type=mavis_file_types.VACCS_MAVIS + ) @pytest.mark.vaccinations @pytest.mark.order(358) def test_vaccs_historic_negative_file_upload(self, setup_vaccs): - self.import_records_page.import_vaccination_records(file_paths=test_data_file_paths.VACCS_HIST_NEGATIVE) + self.import_records_page.import_vaccination_records( + file_paths=test_data_file_paths.VACCS_HIST_NEGATIVE, file_type=mavis_file_types.VACCS_MAVIS + ) @pytest.mark.vaccinations @pytest.mark.order(359) def test_vaccs_historic_no_urn_mav_855(self, setup_vaccs): - self.import_records_page.import_vaccination_records(file_paths=test_data_file_paths.VACCS_HPV_MAV_855) + self.import_records_page.import_vaccination_records( + file_paths=test_data_file_paths.VACCS_HPV_MAV_855, file_type=mavis_file_types.VACCS_MAVIS + ) self.dashboard_page.go_to_dashboard() self.dashboard_page.click_children() self.import_records_page.verify_mav_855() @@ -195,26 +219,38 @@ def test_vaccs_historic_no_urn_mav_855(self, setup_vaccs): @pytest.mark.vaccinations @pytest.mark.order(360) def test_vaccs_systmone_positive_file_upload(self, setup_vaccs_systmone): - self.import_records_page.import_vaccination_records(file_paths=test_data_file_paths.VACCS_SYSTMONE_POSITIVE) + self.import_records_page.import_vaccination_records( + file_paths=test_data_file_paths.VACCS_SYSTMONE_POSITIVE, file_type=mavis_file_types.VACCS_SYSTMONE + ) @pytest.mark.vaccinations @pytest.mark.order(361) def test_vaccs_systmone_negative_file_upload(self, setup_vaccs_systmone): - self.import_records_page.import_vaccination_records(file_paths=test_data_file_paths.VACCS_SYSTMONE_NEGATIVE) + self.import_records_page.import_vaccination_records( + file_paths=test_data_file_paths.VACCS_SYSTMONE_NEGATIVE, file_type=mavis_file_types.VACCS_SYSTMONE + ) @pytest.mark.vaccinations @pytest.mark.order(362) def test_vaccs_systmone_negative_historical_file_upload(self, setup_vaccs_systmone): self.import_records_page.import_vaccination_records( - file_paths=test_data_file_paths.VACCS_SYSTMONE_HIST_NEGATIVE + file_paths=test_data_file_paths.VACCS_SYSTMONE_HIST_NEGATIVE, file_type=mavis_file_types.VACCS_SYSTMONE ) @pytest.mark.vaccinations @pytest.mark.order(363) def test_vaccs_hpv_space_normalization(self, setup_vaccs): - self.import_records_page.import_vaccination_records(file_paths=test_data_file_paths.VACCS_MAV_1080) + self.import_records_page.import_vaccination_records( + file_paths=test_data_file_paths.VACCS_MAV_1080, + verify_on_children_page=True, + file_type=mavis_file_types.VACCS_MAVIS, + ) @pytest.mark.vaccinations @pytest.mark.order(364) def test_vaccs_systmone_space_normalization(self, setup_vaccs_systmone): - self.import_records_page.import_vaccination_records(file_paths=test_data_file_paths.VACCS_SYSTMONE_POSITIVE) + self.import_records_page.import_vaccination_records( + file_paths=test_data_file_paths.VACCS_SYSTMONE_MAV_1080, + verify_on_children_page=False, + file_type=mavis_file_types.VACCS_SYSTMONE, + ) diff --git a/tests/test_05_programmes.py b/tests/test_05_programmes.py index 275ae1d7af9..2edd333170d 100644 --- a/tests/test_05_programmes.py +++ b/tests/test_05_programmes.py @@ -1,6 +1,11 @@ import pytest -from libs.mavis_constants import programmes, test_data_file_paths, vaccines +from libs.mavis_constants import ( + mavis_file_types, + programmes, + test_data_file_paths, + vaccines, +) from pages import ( pg_dashboard, pg_import_records, @@ -54,7 +59,9 @@ def setup_mavis_1729(self, start_mavis: None): self.sessions_page.save_session_id_from_offline_excel() self.dashboard_page.go_to_dashboard() self.dashboard_page.click_import_records() - self.import_records_page.import_vaccination_records(file_paths=test_data_file_paths.VACCS_HPV_DOSE_TWO) + self.import_records_page.import_vaccination_records( + file_paths=test_data_file_paths.VACCS_HPV_DOSE_TWO, file_type=mavis_file_types.VACCS_MAVIS + ) self.dashboard_page.go_to_dashboard() self.dashboard_page.click_programmes() yield diff --git a/tests/test_07_children.py b/tests/test_07_children.py index f5b4328393e..5032b5627b2 100644 --- a/tests/test_07_children.py +++ b/tests/test_07_children.py @@ -1,6 +1,6 @@ import pytest -from libs.mavis_constants import test_data_file_paths +from libs.mavis_constants import mavis_file_types, test_data_file_paths from pages import ( pg_children, pg_dashboard, @@ -74,7 +74,9 @@ def setup_mav_853(self, setup_tests: None): self.programmes_page.upload_cohorts(file_paths=test_data_file_paths.COHORTS_MAV_853) self.dashboard_page.go_to_dashboard() self.dashboard_page.click_import_records() - self.import_records_page.import_vaccination_records(file_paths=test_data_file_paths.VACCS_MAV_853) + self.import_records_page.import_vaccination_records( + file_paths=test_data_file_paths.VACCS_MAV_853, file_type=mavis_file_types.VACCS_MAVIS + ) self.dashboard_page.go_to_dashboard() self.dashboard_page.click_children() yield diff --git a/utils/001_cleanup.py b/utils/001_cleanup.py index f83565e26b4..075d8157694 100644 --- a/utils/001_cleanup.py +++ b/utils/001_cleanup.py @@ -1,7 +1,7 @@ import os # This script is designed to be run manually to clear down the 'working', 'reports' and 'screenshots' directories. -folders_to_clean = ["working", "reports", "screenshots"] +folders_to_clean = ["working", "screenshots"] # "reports" def cleanup() -> None: