🏡 index : quine-global/quine-anaconda-webui.git

#!/usr/bin/python3
#
# Copyright (C) 2021 Red Hat, Inc.
# SPDX-License-Identifier: LGPL-2.1-or-later

from anacondalib import VirtInstallMachineCase, pixel_tests_ignore
from bugzilla_mock import (
    install_mock_bugzilla,
    set_mock_bugzilla_bugs,
    uninstall_mock_bugzilla,
)
from installer import Installer
from testlib import nondestructive, test_main  # pylint: disable=import-error
from utils import get_pretty_name


@nondestructive
class TestBasic(VirtInstallMachineCase):

    def testNavigation(self):
        """
        Description:
            Verifies that all steps in the installer are accessible via 'Next' button

        Expected results:
            - All steps are reachable by clicking on the 'Next' button
        """
        b = self.browser
        m = self.machine
        i = Installer(b, m)

        i.open()

        self.assertIn(
            b.attr("#app .pf-v6-c-page", "data-debug").lower(),
            ["false", "true"]
        )

        i.check_prerelease_info()
        # Do not start the installation in non-destructive tests as this performs non revertible changes
        # with the pages basically empty of common elements (as those are provided by the top-level installer widget)
        # we at least iterate over them to check this works as expected
        i.reach(i.steps.REVIEW)

        # Ensure that the 'actual' UI process is running/
        self.assertIn("/usr/libexec/anaconda/webui-desktop", m.execute("ps aux"))

    def testSidebarNavigation(self):
        """
        Description:
            Verifies navigation to previous steps using the sidebar

        Expected results:
            - All previous steps are reachable by clicking on the sidebar
            - Later steps are disabled in the sidebar
        """

        b = self.browser
        m = self.machine
        i = Installer(b, m)

        i.open()

        i.check_prerelease_info()

        # Test that clicking on current step does not break navigation
        i.click_step_on_sidebar()

        i.reach(i.steps.REVIEW)

        # Test going back
        steps = [
            i.steps.ACCOUNTS,
            i.steps.STORAGE_CONFIGURATION,
            i.steps.INSTALLATION_METHOD,
            i.steps.LANGUAGE,
        ]

        for step in steps:
            if step not in i.steps.hidden_steps:
                i.click_step_on_sidebar(step)

        i.reach(i.steps.REVIEW)

        # Check disabling of later steps when going back
        i.reach_on_sidebar(i.steps.LANGUAGE)
        for step in [
            i.steps.ACCOUNTS,
            i.steps.STORAGE_CONFIGURATION,
            i.steps.INSTALLATION_METHOD,
            i.steps.REVIEW,
        ]:
            i.check_sidebar_step_disabled(step)

    def testAboutModal(self):
        """
        Description:
            Test the About modal

        Expected results:
            - The About modal is shown when clicking on the "About" item in the kebab menu
            - The About modal contains the correct information
            - The About modal can be closed
        """
        b = self.browser
        m = self.machine
        i = Installer(b, m)

        i.open()

        # Obtain pretty name and versions from the CLI
        pretty_name = get_pretty_name(m)
        backend_version = m.execute("anaconda --version | cut -d ' ' -f 2 | tail -1").strip()
        webui_version = m.execute("cat /usr/local/share/cockpit/anaconda-webui/VERSION.txt").strip().split()[0]

        # Click on the kebab
        b.click("#toggle-kebab")

        # Click on the "About" item from the dropdown
        b.click("#about-modal-dropdown-item-about")

        # Expect PRETTY_NAME to be shown in modal title
        b.wait_in_text("#about-modal-title", pretty_name)

        # Expect "Powered by Anaconda" to be shown in modal subtitle
        b.wait_in_text("#about-modal-subtitle", "Powered by Anaconda")

        # Expect About modal body to be shown
        b.wait_in_text("#backend-dt", "Anaconda")
        b.wait_in_text("#backend-dd", backend_version)
        b.wait_in_text("#webui-dt", "Anaconda WebUI")
        b.wait_in_text("#webui-dd", webui_version)

        # Expect button link for Anaconda project page to be shown
        b.wait_in_text("#anaconda-page-button", "Anaconda project page")

        # Pixel test the language step
        b.assert_pixels(
            ".pf-v6-c-about-modal-box",
            "about-modal",
            ignore=[*pixel_tests_ignore, "#backend-dd","#webui-dd", "#about-modal-title"],
        )

        # Close about modal
        b.click(".pf-v6-c-button[aria-label='Close Dialog']")
        b.wait_not_present("#about-modal")

    def testReportIssue(self):
        """
        Description:
            Test the Report Issue modal

        Expected results:
            - The Report Issue modal is shown when clicking on the "Report issue" item in the kebab menu
            - The Report Issue modal contains the correct information
            - The Report Issue modal can be closed
        """
        b = self.browser
        m = self.machine
        i = Installer(b, m)

        i.open()

        b.wait_not_present("#user-issue-bz-report-modal")

        b.click("#toggle-kebab")
        b.click("#about-modal-dropdown-item-report")
        b.wait_visible("#user-issue-bz-report-modal")
        b.click("#user-issue-dialog-cancel-btn")
        b.wait_not_present("#user-issue-bz-report-modal")

    def testJsErrorHandling(self):
        """
        Description:
            Test that the Error modal is shown when a JS error occurs

        Setup:
            - Trigger a JS error

        Expected results:
            - The Error modal is shown
            - The Error modal contains the correct information
            - The Journal contains the exception
        """

        b = self.browser
        m = self.machine
        i = Installer(b, m)

        i.open()

        b.wait_not_present("#critical-error-bz-report-modal")

        b.eval_js("window.setTimeout(function() { myNonExistingFunction()}, 0);")
        b.wait_in_text("#critical-error-bz-report-modal-details", "myNonExistingFunction is not defined")

        b.wait_visible("button:contains('Next'):disabled")
        b.assert_pixels(
            "#critical-error-bz-report-modal",
            "js-error-modal",
            ignore=["#critical-error-bz-report-modal-review-log"]
        )

        # Check the exception is present in the journal
        self.assertIn(
            "myNonExistingFunction is not defined",
            m.execute("journalctl -b -t anaconda-webui")
        )

    def _open_critical_error_report_step1(self):
        b = self.browser
        i = Installer(b, self.machine)

        i.open()
        b.wait_not_present("#critical-error-bz-report-modal")
        b.eval_js("window.setTimeout(function() { myNonExistingFunction()}, 0);")
        b.wait_in_text("#critical-error-bz-report-modal-details", "myNonExistingFunction is not defined")
        b.wait_visible("#critical-error-bugzilla-apikey")

    def testBugzillaDuplicateDetection(self):
        """
        Description:
            Test duplicate detection in the Bugzilla reporting flow

        Setup:
            - Install a mock python-bugzilla package that returns canned open bugs
            - Trigger a JS error to open the critical-error report modal

        Expected results:
            - After API key validation, potential duplicates are shown
            - Users can continue to create a new report
            - Back navigation returns to the duplicate list when matches exist
            - When no duplicates are found, the duplicates step is skipped
        """
        b = self.browser
        m = self.machine
        dialog = "#critical-error-bz-report-modal"
        mock_bugs = [
            {
                "id": 2428210,
                "summary": "WebUI: org.fedoraproject.Anaconda.BootloaderInstallationError: boot loader install failed",
                "status": "NEW",
            },
            {
                "id": 2491005,
                "summary": (
                    "Installation of the system failed: Installing boot loader"
                    " org.fedoraproject.Anaconda.BootloaderInstallationError:"
                    " boot loader install failed"
                ),
                "status": "NEW",
            },
            {
                "id": 2485664,
                "summary": (
                    "Installation of the system failed: Installing boot loader"
                    " org.fedoraproject.Anaconda.BootloaderInstallationError:"
                    " boot loader install failed"
                ),
                "status": "NEW",
            },
        ]

        install_mock_bugzilla(m, mock_bugs)
        self.addCleanup(uninstall_mock_bugzilla, m)

        self._open_critical_error_report_step1()

        # Step 1: enter API key and continue
        b.set_input_text("#critical-error-bugzilla-apikey", "fake-valid-api-key")
        b.wait_visible(f"{dialog} button:contains('Next'):not([disabled])")
        b.click(f"{dialog} button:contains('Next')")

        # Step 2: duplicates list
        b.wait_visible("#critical-error-duplicates-alert")
        b.wait_in_text("#critical-error-duplicates-alert", "Potential duplicate bugs found")
        b.wait_visible("#critical-error-duplicates-list")
        b.wait_in_text("#critical-error-duplicate-2428210", "#2428210")
        b.wait_in_text("#critical-error-duplicate-2428210", "[NEW]")
        b.wait_in_text("#critical-error-duplicate-2491005", "#2491005")
        b.wait_in_text("#critical-error-duplicate-2485664", "#2485664")
        b.wait_visible("#critical-error-continue-new-btn")

        # Continue to create a new report (step 3)
        b.click("#critical-error-continue-new-btn")
        b.wait_visible("#critical-error-bug-summary")
        b.wait_not_present("#critical-error-duplicates-list")
        b.wait_visible(f"{dialog} button:contains('Report issue')")

        # Back returns to the duplicates step when matches exist
        b.click(f"{dialog} button:contains('Back')")
        b.wait_visible("#critical-error-duplicates-list")
        b.wait_not_present("#critical-error-bug-summary")

        # Back from duplicates returns to API key entry
        b.click(f"{dialog} button:contains('Back')")
        b.wait_visible("#critical-error-bugzilla-apikey")
        b.wait_not_present("#critical-error-duplicates-list")

        # No duplicates: skip straight to bug details
        set_mock_bugzilla_bugs(m, [])
        b.set_input_text("#critical-error-bugzilla-apikey", "fake-valid-api-key")
        b.click(f"{dialog} button:contains('Next')")
        b.wait_visible("#critical-error-bug-summary")
        b.wait_not_present("#critical-error-duplicates-list")
        b.wait_visible(f"{dialog} button:contains('Report issue')")

        # Back skips the empty duplicates step and returns to API key entry
        b.click(f"{dialog} button:contains('Back')")
        b.wait_visible("#critical-error-bugzilla-apikey")
        b.wait_not_present("#critical-error-bug-summary")


if __name__ == '__main__':
    test_main()