From dc22f5ff348bbaefc99124b0e9ef6b913c9d37e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mirek=20D=C5=82ugosz?= Date: Wed, 18 Dec 2024 13:40:45 +0100 Subject: [PATCH] UI: test downloading report through both kebab menu and modal --- camayoc/tests/qpc/ui/test_scans.py | 41 +++++++++++++++++ camayoc/ui/enums.py | 10 +++++ camayoc/ui/models/pages/scans.py | 70 ++++++++++++++++++++++++++---- 3 files changed, 113 insertions(+), 8 deletions(-) diff --git a/camayoc/tests/qpc/ui/test_scans.py b/camayoc/tests/qpc/ui/test_scans.py index e3f7ae2f..b7621777 100644 --- a/camayoc/tests/qpc/ui/test_scans.py +++ b/camayoc/tests/qpc/ui/test_scans.py @@ -16,7 +16,9 @@ from camayoc.tests.qpc.utils import assert_sha256sums from camayoc.ui import Client from camayoc.ui import data_factories +from camayoc.ui.enums import ColumnOrdering from camayoc.ui.enums import MainMenuPages +from camayoc.ui.enums import ScansPopupTableColumns def has_network_source(scan_name): @@ -26,6 +28,7 @@ def has_network_source(scan_name): continue scan_sources = set(scan_definition.sources) return bool(network_sources.intersection(scan_sources)) + return False def scan_names(): @@ -64,3 +67,41 @@ def test_download_scan(tmp_path, scans, ui_client: Client, scan_name): tarfile.open(downloaded_report.path()).extractall(tmp_path) assert_sha256sums(tmp_path) assert_ansible_logs(tmp_path, is_network_scan) + + +@pytest.mark.nightly_only +@pytest.mark.parametrize("scan_name", scan_names()) +def test_download_scan_modal(tmp_path, scans, ui_client: Client, scan_name): + """Download finished scan using modal and verify basic content properties. + + :id: 00fc7f4f-6f7a-4947-869e-749060a2285f + :description: This is almost exact copy of test_download_scan, except + this one clicks button in "Last scanned" column and uses modal + to download the scan results. + :steps: + 1) Log into the UI. + 2) Download finished scan report using the modal. + 3) Verify downloaded file looks like a valid scan. + 4) Log out. + :expectedresults: Report is downloaded. User is logged out. + """ + finished_scan = scans.with_name(scan_name) + ( + ui_client.begin() + .login(data_factories.LoginFormDTOFactory()) + .navigate_to(MainMenuPages.SCANS) + .download_scan_modal( + finished_scan.definition.name, + ScansPopupTableColumns.SCAN_TIME, + ColumnOrdering.DESCENDING, + 0, + ) + .logout() + ) + + is_network_scan = has_network_source(scan_name) + downloaded_report = ui_client.downloaded_files[-1] + + tarfile.open(downloaded_report.path()).extractall(tmp_path) + assert_sha256sums(tmp_path) + assert_ansible_logs(tmp_path, is_network_scan) diff --git a/camayoc/ui/enums.py b/camayoc/ui/enums.py index 8c82f00b..e6bb526a 100644 --- a/camayoc/ui/enums.py +++ b/camayoc/ui/enums.py @@ -72,3 +72,13 @@ class SourceConnectionTypes(StrEnum): TLS11 = "TLSv1.1" TLS12 = "TLSv1.2" DISABLE = "Disable SSL" + + +class ScansPopupTableColumns(StrEnum): + SCAN_TIME = "header_scan_time" + SCAN_RESULT = "header_scan_result" + + +class ColumnOrdering(StrEnum): + ASCENDING = "ascending" + DESCENDING = "descending" diff --git a/camayoc/ui/models/pages/scans.py b/camayoc/ui/models/pages/scans.py index 05a37939..8ce573c6 100644 --- a/camayoc/ui/models/pages/scans.py +++ b/camayoc/ui/models/pages/scans.py @@ -9,13 +9,17 @@ from camayoc.ui.decorators import creates_toast from camayoc.ui.decorators import record_action from camayoc.ui.decorators import service +from camayoc.ui.enums import ColumnOrdering from camayoc.ui.enums import Pages +from camayoc.ui.enums import ScansPopupTableColumns from ..components.items_list import AbstractListItem from ..components.popup import PopUp from ..mixins import MainPageMixin from .abstract_page import AbstractPage +REFRESH_BUTTON_LOCATOR = "div[class*=-c-toolbar] button[data-ouia-component-id=refresh]" + class ScanHistoryPopup(PopUp, AbstractPage): SAVE_LOCATOR = None @@ -23,11 +27,25 @@ class ScanHistoryPopup(PopUp, AbstractPage): SAVE_RESULT_CLASS = None CANCEL_RESULT_CLASS = Pages.SCANS - def get_last_download(self) -> Download: + def sort_table(self, sort_by: ScansPopupTableColumns, ordering: ColumnOrdering) -> None: + tries = 0 + table_header_locator = ( + "table[data-ouia-component-id=scan_jobs_table] thead " + f"th[data-ouia-component-id={sort_by.value}]" + ) + while 3 >= tries: + header = self._driver.locator(table_header_locator) + if header.get_attribute("aria-sort") == ordering.value: + return + header.click() + tries += 1 + raise RuntimeError("Failed to sort table") + + def get_nth_download(self, item: int) -> Download: table_row_locator = "table[data-ouia-component-id=scan_jobs_table] tbody tr" download_button_locator = "td[data-label=Download] button" - last_download = self._driver.locator(table_row_locator).last + last_download = self._driver.locator(table_row_locator).nth(item) with self._driver.expect_download() as download_info: last_download.locator(download_button_locator).click(timeout=10_000) download = download_info.value @@ -37,22 +55,47 @@ def get_last_download(self) -> Download: class ScanListElem(AbstractListItem): def download_scan(self) -> Download: + timeout_start = time.monotonic() + timeout = self._client._camayoc_config.camayoc.scan_timeout + download_locator = ( + "button[data-ouia-component-id=action_menu_toggle] " + "~ div *[data-ouia-component-id=download] button" + ) + while timeout > (time.monotonic() - timeout_start): + try: + self._toggle_kebab() + with self._client.driver.expect_download() as download_info: + self.locator.locator(download_locator).click(timeout=10_000) + download = download_info.value + download.path() # blocks the script while file is downloaded + self._toggle_kebab() + return download + except PlaywrightTimeoutError: + self._client.driver.locator(REFRESH_BUTTON_LOCATOR).click() + raise FailedScanException("Scan could not be downloaded") + + def download_scan_modal( + self, sort_by: ScansPopupTableColumns, ordering: ColumnOrdering, item: int + ) -> Download: timeout_start = time.monotonic() timeout = self._client._camayoc_config.camayoc.scan_timeout while timeout > (time.monotonic() - timeout_start): try: - scans_popup = self._open_scans() - download = scans_popup.get_last_download() + scans_popup = self._open_scans_popup() + scans_popup.sort_table(sort_by, ordering) + download = scans_popup.get_nth_download(item) scans_popup.cancel() return download except PlaywrightTimeoutError: scans_popup.cancel() - self._client.driver.locator( - "div[class*=-c-toolbar] button[data-ouia-component-id=refresh]" - ).click() + self._client.driver.locator(REFRESH_BUTTON_LOCATOR).click() raise FailedScanException("Scan could not be downloaded") - def _open_scans(self) -> ScanHistoryPopup: + def _toggle_kebab(self) -> None: + kebab_menu_locator = "button[data-ouia-component-id=action_menu_toggle]" + self.locator.locator(kebab_menu_locator).click() + + def _open_scans_popup(self) -> ScanHistoryPopup: last_scanned_locator = "td[data-label='Last scanned'] button" self.locator.locator(last_scanned_locator).click() return ScanHistoryPopup(client=self._client) @@ -69,3 +112,14 @@ def download_scan(self, scan_name: str) -> ScansMainPage: downloaded_report = scan.download_scan() self._client.downloaded_files.append(downloaded_report) return self + + @creates_toast + @service + @record_action + def download_scan_modal( + self, scan_name: str, sort_by: ScansPopupTableColumns, ordering: ColumnOrdering, item: int + ) -> ScansMainPage: + scan: ScanListElem = self._get_item(scan_name) + downloaded_report = scan.download_scan_modal(sort_by, ordering, item) + self._client.downloaded_files.append(downloaded_report) + return self