#!/usr/bin/python3
# Run this with --help to see available options for tracing and debugging
# See https://github.com/cockpit-project/cockpit/blob/master/test/common/testlib.py
# "class Browser" and "class MachineCase" for the available API.

import os
import sys

# import Cockpit's machinery for test VMs and its browser test API
TEST_DIR = os.path.dirname(__file__)
sys.path.append(os.path.join(TEST_DIR, "common"))
sys.path.append(os.path.join(os.path.dirname(TEST_DIR), "bots/machine"))

from machine_core import ssh_connection  # noqa
import testlib  # noqa

REGISTRIES_CONF = """
[registries.search]
registries = ['localhost:5000', 'localhost:6000']

[registries.insecure]
registries = ['localhost:5000', 'localhost:6000']
"""

NOT_RUNNING = ["exited", "stopped"]


def checkImage(browser, name, owner):
    browser.wait_visible("#containers-images table")
    browser.wait_js_func("""(function (first, last) {
        let items = ph_select("#containers-images table tbody");
        for (i = 0; i < items.length; i++)
            if (items[i].innerText.trim().startsWith(first) && items[i].innerText.trim().endsWith(last))
                return true;
        return false;
        })""", name, owner)


class TestApplication(testlib.MachineCase):

    def setUp(self):
        super().setUp()
        self.machine.execute("systemctl stop podman.service; systemctl --now enable podman.socket")

        # backup/restore pristine podman state, so that tests can run on existing testbeds
        self.restore_dir("/var/lib/containers", reboot_safe=True)

        self.addCleanup(self.machine.execute, "systemctl stop podman.service")

        # HACK: sometimes podman leaks mounts
        self.addCleanup(self.machine.execute, "podman rm --force --all && "
                        "findmnt --list -otarget | grep /var/lib/containers/. | xargs -r umount || true")

        # Create admin session
        self.machine.execute("""
            mkdir /home/admin/.ssh
            cp /root/.ssh/* /home/admin/.ssh
            chown -R admin:admin /home/admin/.ssh
            chmod -R go-wx /home/admin/.ssh
            """)
        self.admin_s = ssh_connection.SSHConnection(user="admin",
                                                    address=self.machine.ssh_address,
                                                    ssh_port=self.machine.ssh_port,
                                                    identity_file=self.machine.identity_file)

        # Enable user service as well
        self.admin_s.execute("systemctl --user stop podman.service; systemctl --now --user enable podman.socket")
        self.restore_dir("/home/admin/.local/share/containers", reboot_safe=True)
        self.addCleanup(self.admin_s.execute, "podman rm --force --all; systemctl --user stop podman.service podman.socket")

        # But disable it globally so that "systemctl --user disable" does what we expect
        self.machine.execute("systemctl --global disable podman.socket")

        self.addCleanup(self.machine.execute, "killall -9 podman || true")

        # HACK: https://github.com/containers/podman/issues/8762
        self.admin_s.execute("timeout 5 curl --unix-socket $XDG_RUNTIME_DIR/podman/podman.sock http://d/v1.24/libpod/info || true")

        self.allow_journal_messages("/run.*/podman/podman: couldn't connect.*")
        self.allow_journal_messages(".*/run.*/podman/podman.*Connection reset by peer")

        self.has_criu = self.machine.image not in ["debian-testing", "ubuntu-stable"]
        self.has_selinux = self.machine.image not in ["debian-testing", "ubuntu-stable"]

    def cgroupsV2(self):
        return self.machine.image not in ["ubuntu-stable", "rhel-8-3", "rhel-8-4"]

    def execute(self, system, cmd):
        if system:
            return self.machine.execute(cmd)
        else:
            return self.admin_s.execute(cmd)

    def waitPodRow(self, podName, present=False):
        if present:
            self.browser.wait_visible("#table-" + podName)
        else:
            self.browser.wait_not_present("#table-" + podName)

    def waitPodContainer(self, podName, containerList):
        if len(containerList):
            for container in containerList:
                self.waitContainer(container["id"], True, name=container["name"], image=container["image"],
                                   cmd=container["command"], state=container["state"], pod=podName)
        else:
            if self.browser.val("#containers-containers-filter") == "all":
                self.browser.wait_in_text("#table-" + podName + " .ct-table-empty", "No containers in this pod ")
            else:
                self.browser.wait_in_text("#table-" + podName + " .ct-table-empty", "No running containers in this pod ")

    def performPodAction(self, podName, podOwner, action):
        b = self.browser

        b.click("#pod-{0}-{1}-action-toggle".format(podName, podOwner))
        b.click("ul[aria-labelledby=pod-{1}-{2}-action-toggle] li > button.pod-action-{0}".format(action.lower(), podName, podOwner))
        b.wait_not_present("ul[aria-labelledby=pod-{0}-{1}-action-toggle]".format(podName, podOwner))

    @testlib.nondestructive
    def testPods(self):
        b = self.browser
        m = self.machine

        self.login_and_go("/podman")
        b.wait_visible("#app")

        self.browser.wait_in_text("#containers-containers .ct-table-empty", "No running containers")

        # Run a pods as system
        self.machine.execute("podman pod create --infra=false --name pod-1")

        self.waitPodRow("pod-1", False)
        self.filter_containers("all")
        self.waitPodContainer("pod-1", [])

        containerId = self.machine.execute("podman run -d --pod pod-1 --name test-pod-1-system alpine sleep 100").strip()
        self.waitPodContainer("pod-1", [{"name": "test-pod-1-system", "image": "alpine", "command": "sleep 100", "state": "running", "id": containerId}])
        self.machine.execute("podman pod stop pod-1")
        self.waitPodContainer("pod-1", [{"name": "test-pod-1-system", "image": "alpine", "command": "sleep 100", "state": NOT_RUNNING, "id": containerId}])
        self.filter_containers("running")
        self.waitPodRow("pod-1", False)

        self.filter_containers("all")
        b.set_input_text('#containers-filter', 'pod-1')
        self.waitPodContainer("pod-1", [{"name": "test-pod-1-system", "image": "alpine", "command": "sleep 100", "state": NOT_RUNNING, "id": containerId}])
        b.set_input_text('#containers-filter', 'test-pod-1-system')
        self.waitPodContainer("pod-1", [{"name": "test-pod-1-system", "image": "alpine", "command": "sleep 100", "state": NOT_RUNNING, "id": containerId}])

        # Check Pod Actions
        self.performPodAction("pod-1", "system", "Start")
        self.waitPodContainer("pod-1", [{"name": "test-pod-1-system", "image": "alpine", "command": "sleep 100", "state": "running", "id": containerId}])

        self.performPodAction("pod-1", "system", "Stop")
        self.waitPodContainer("pod-1", [{"name": "test-pod-1-system", "image": "alpine", "command": "sleep 100", "state": NOT_RUNNING, "id": containerId}])

        self.machine.execute("podman pod start pod-1")
        self.waitPodContainer("pod-1", [{"name": "test-pod-1-system", "image": "alpine", "command": "sleep 100", "state": "running", "id": containerId}])

        old_pid = m.execute("podman inspect --format '{{.State.Pid}}' test-pod-1-system")
        self.performPodAction("pod-1", "system", "Restart")
        b.wait(lambda: old_pid != m.execute("podman inspect --format '{{.State.Pid}}' test-pod-1-system".strip()))

        self.performPodAction("pod-1", "system", "Delete")
        b.click(".pf-c-modal-box button:contains(Delete)")
        b.wait_in_text(".pf-c-modal-box__body", "running or paused containers cannot be removed without force")
        b.click(".pf-c-modal-box button:contains('Force delete')")
        self.waitPodRow("pod-1", False)

    @testlib.nondestructive
    def testBasicSystem(self):
        self._testBasic(True)

        b = self.browser

        # Test dropping and gaining privileges
        b.set_val("#containers-containers-owner", "all")
        self.filter_containers("all")
        self.execute(False, "podman pod create --infra=false --name pod_user")
        self.execute(True, "podman pod create --infra=false --name pod_system")

        checkImage(b, "quay.io/cockpit/registry:2", "system")
        checkImage(b, "quay.io/cockpit/registry:2", "admin")
        b.wait_visible("#containers-containers .pod-name:contains('pod_user')")
        b.wait_visible("#containers-containers .pod-name:contains('pod_system')")
        b.wait_visible("#containers-containers .container-name:contains('a')")
        b.wait_visible("#containers-containers .container-name:contains('b')")

        if self.system_before(231):  # Changed in #14740
            modal_sel = ".modal-dialog"
        else:
            modal_sel = ".pf-c-modal-box"

        # Drop privileges
        b.switch_to_top()
        b.click("#super-user-indicator button")
        b.click("%s:contains('Switch to limited access') button:contains('Limit access')" % modal_sel)
        b.wait_not_present("%s:contains('Switch to limited access')" % modal_sel)
        b.wait_text("#super-user-indicator", "Limited access")
        b.switch_to_frame("cockpit1:localhost/podman")

        # All system things should disappear
        b.wait_not_present("#containers-containers .pod-name:contains('pod_system')")
        b.wait_not_present("#containers-containers .container-name:contains('a')")
        b.wait_visible("#containers-containers .pod-name:contains('pod_user')")
        b.wait_visible("#containers-containers .container-name:contains('b')")
        # Checking images is harder but if there would be more than one this would fail
        b.wait_visible("#containers-images:contains('quay.io/cockpit/registry:2')")

        # Owner select should disappear
        b.wait_not_present("#containers-containers-owner")

        # Also user selection in image download should not be visible
        b.click("button:contains(Get new image)")
        b.wait_visible('div.pf-c-modal-box header:contains("Search for an image")')
        b.wait_visible("div.pf-c-modal-box footer button:contains(Download):disabled")
        b.wait_not_present("#as-user")
        b.click(".pf-c-modal-box button:contains('Cancel')")
        b.wait_not_present('div.pf-c-modal-box header:contains("Search for an image")')

        # Gain privileges
        b.switch_to_top()
        b.click("#super-user-indicator button")
        b.wait_in_text("%s:contains('Administrative access')" % modal_sel, "Please authenticate")
        b.set_input_text("%s:contains('Administrative access') input" % modal_sel, "foobar")
        b.click("%s button:contains('Authenticate')" % modal_sel)
        b.wait_not_present("%s:contains('Administrative access')" % modal_sel)
        b.wait_text("#super-user-indicator", "Administrative access")
        b.switch_to_frame("cockpit1:localhost/podman")

        # We are notified that we can also start the system one
        b.wait_in_text("#overview div.pf-c-alert .pf-c-alert__title", "System Podman service is also available")
        b.click("#overview div.pf-c-alert .pf-c-alert__action > button:contains(Start)")
        b.wait_not_present("#overview div.pf-c-alert .pf-c-alert__title")

        checkImage(b, "quay.io/cockpit/registry:2", "system")
        checkImage(b, "quay.io/cockpit/registry:2", "admin")
        b.wait_visible("#containers-containers .pod-name:contains('pod_user')")
        b.wait_visible("#containers-containers .pod-name:contains('pod_system')")
        b.wait_visible("#containers-containers .container-name:contains('a')")
        b.wait_visible("#containers-containers .container-name:contains('b')")

        # Owner select should appear
        b.wait_visible("#containers-containers-owner")

        # Also user selection in image download should be visible
        b.click("button:contains(Get new image)")
        b.wait_visible('div.pf-c-modal-box header:contains("Search for an image")')
        b.wait_visible("div.pf-c-modal-box footer button:contains(Download):disabled")
        b.wait_visible("#as-user")
        b.click(".pf-c-modal-box button:contains('Cancel')")
        b.wait_not_present('div.pf-c-modal-box header:contains("Search for an image")')

        # Check that when we filter only system stuff an then drop privileges that we show user stuff
        b.set_val("#containers-containers-owner", "system")
        b.wait_not_present("#containers-containers .pod-name:contains('pod_user')")
        b.wait_not_present("#containers-containers .container-name:contains('b')")
        # Checking images is harder but if there would be more than one this would fail
        b.wait_visible("#containers-images:contains('quay.io/cockpit/registry:2')")

        b.switch_to_top()
        b.click("#super-user-indicator button")
        b.click("%s:contains('Switch to limited access') button:contains('Limit access')" % modal_sel)
        b.wait_not_present("%s:contains('Switch to limited access')" % modal_sel)
        b.wait_text("#super-user-indicator", "Limited access")
        b.switch_to_frame("cockpit1:localhost/podman")

        b.wait_visible("#containers-containers .pod-name:contains('pod_user')")
        b.wait_visible("#containers-containers .container-name:contains('b')")
        # Checking images is harder but if there would be more than one this would fail
        b.wait_visible("#containers-images:contains('quay.io/cockpit/registry:2')")

        # Test that when root is logged in we don't present "user" and "system"
        b.logout()
        self.login_and_go("/podman", user="root")
        b.wait_visible("#app")

        # `User Service is also available` banner should not be present
        b.wait_not_present("#overview div.pf-c-alert")
        # There should not be any duplicate images listed
        b.wait_js_func("ph_count_check", "#containers-images tbody", 1)
        # There should not be 'owner' selector
        b.wait_not_present("#containers-containers-owner")

    @testlib.nondestructive
    def testBasicUser(self):
        self._testBasic(False)

    def _testBasic(self, auth):
        b = self.browser

        if not auth:
            self.allow_authorize_journal_messages()
            self.allow_browser_errors("Failed to start system podman.socket.*")

        self.login_and_go("/podman", superuser=auth)
        b.wait_visible("#app")

        b.wait_visible(".content-filter input")

        # Check all containers
        if auth:
            checkImage(b, "quay.io/libpod/busybox:latest", "system")
            checkImage(b, "quay.io/libpod/alpine:latest", "system")
            checkImage(b, "quay.io/cockpit/registry:2", "system")

        checkImage(b, "quay.io/libpod/busybox:latest", "admin")
        checkImage(b, "quay.io/libpod/alpine:latest", "admin")
        checkImage(b, "quay.io/cockpit/registry:2", "admin")

        # Check order of images
        text = b.text("#containers-images table")
        if auth:
            # all user images before all system images
            self.assertRegex(text, ".*admin.*system.*")
            self.assertNotRegex(text, ".*system.*admin.*")
        else:
            self.assertNotIn("system", text)
        # images are sorted alphabetically
        self.assertRegex(text, ".*cockpit/registry.*libpod/alpine.*libpod/busybox")

        # prepare image ids - much easier to pick a specific container
        images = {}
        for image in self.execute(auth, "podman images --noheading --no-trunc").strip().split("\n"):
            # <name> <tag> sha256:<sha> <other things>
            items = image.split()
            images["{0}:{1}".format(items[0], items[1])] = items[2].split(":")[-1]

        # show image listing toggle
        busybox_sel = "#containers-images tbody tr[data-row-id={0}{1}]".format(images["quay.io/libpod/busybox:latest"], auth).lower()
        b.wait_visible(busybox_sel)
        b.click(busybox_sel + " td.pf-c-table__toggle button")
        b.wait_visible(busybox_sel + " + tr button.btn-delete")
        b.wait_in_text("#containers-images tbody.pf-m-expanded tr .image-details:first-child:contains('busybox:latest')", "Commandsh")
        b.click(busybox_sel + " td.pf-c-table__toggle button")

        # make sure no running containers shown
        self.filter_containers('running')
        b.wait_in_text("#containers-containers", "No running containers")

        if auth:
            # Run two containers as system (first exits immediately)
            self.execute(auth, "podman run -d --name test-sh-system alpine:latest sh")
            self.execute(auth, "podman run -d --name swamped-crate-system busybox:latest sleep 1000")

        # Run two containers as admin (first exits immediately)
        self.execute(False, "podman run -d --name test-sh-user alpine:latest sh")
        self.execute(False, "podman run -d --name swamped-crate-user busybox:latest sleep 1000")

        # Test owner filtering
        if auth:
            b.wait_js_func("ph_count_check", "#containers-images tbody", 6)
            b.wait_js_func("ph_count_check", "#containers-containers tbody", 2)

            b.set_val("#containers-containers-owner", "system")
            b.wait_js_func("ph_count_check", "#containers-images tbody", 3)
            b.wait_in_text("#containers-images", "system")
            b.wait_js_func("ph_count_check", "#containers-containers tbody", 1)
            b.wait_in_text("#containers-containers", "system")

            b.set_val("#containers-containers-owner", "user")
            b.wait_js_func("ph_count_check", "#containers-images tbody", 3)
            b.wait_in_text("#containers-images", "admin")
            b.wait_js_func("ph_count_check", "#containers-containers tbody", 1)
            b.wait_in_text("#containers-containers", "admin")

            b.set_val("#containers-containers-owner", "all")
            b.wait_js_func("ph_count_check", "#containers-images tbody", 6)
            b.wait_js_func("ph_count_check", "#containers-containers tbody", 2)
        else:  # No 'owner' selector when not privileged
            b.wait_not_present("#containers-containers-owner")

        user_containers = {}
        system_containers = {}
        for container in self.execute(True, "podman ps --all --no-trunc").strip().split("\n")[1:]:
            # <sha> <other things> <name>
            items = container.split()
            system_containers[items[-1]] = items[0]
        for container in self.execute(False, "podman ps --all --no-trunc").strip().split("\n")[1:]:
            # <sha> <other things> <name>
            items = container.split()
            user_containers[items[-1]] = items[0]

        # running busybox shown
        if auth:
            b.wait_visible("#containers-containers .container-name:contains('swamped-crate-system')")
            self.waitContainer(system_containers["swamped-crate-system"], True, name='swamped-crate-system',
                               image='busybox:latest', cmd="sleep 1000", state='running')

        b.wait_visible("#containers-containers .container-name:contains('swamped-crate-user')")
        self.waitContainer(user_containers["swamped-crate-user"], False, name='swamped-crate-user',
                           image='busybox:latest', cmd="sleep 1000", state='running')

        # exited alpine not shown
        b.wait_not_in_text("#containers-containers", "alpine:latest")

        # show all containers and check status
        self.filter_containers('all')

        # exited alpine under everything list
        b.wait_visible("#containers-containers")
        if auth:
            self.waitContainer(system_containers["test-sh-system"], True, name='test-sh-system', image='alpine:latest',
                               cmd='sh', state=NOT_RUNNING)

        self.waitContainer(user_containers["test-sh-user"], False, name='test-sh-user', image='alpine:latest',
                           cmd='sh', state=NOT_RUNNING)

        b.click('#containers-containers tbody tr:contains("swamped-crate-user") td.pf-c-table__toggle button')
        b.wait_visible('#containers-containers tbody tr:contains("swamped-crate-user") + tr button.btn-delete')

        if auth:
            b.click('#containers-containers tbody tr:contains("swamped-crate-system") td.pf-c-table__toggle button')
            b.wait_visible('#containers-containers tbody tr:contains("swamped-crate-system") + tr button.btn-delete')

        # Checked order of containers
        if auth:
            b.wait_collected_text("#containers-containers .container-name", "swamped-crate-usertest-sh-userswamped-crate-systemtest-sh-system")
        else:
            b.wait_collected_text("#containers-containers .container-name", "swamped-crate-usertest-sh-user")

        # show running container
        self.filter_containers('running')
        if auth:
            self.waitContainer(system_containers["swamped-crate-system"], True, name='swamped-crate-system',
                               image='busybox:latest', cmd="sleep 1000", state='running')
        self.waitContainer(user_containers["swamped-crate-user"], False, name='swamped-crate-user',
                           image='busybox:latest', cmd="sleep 1000", state='running')
        # check exited alpine not in running list
        b.wait_not_in_text("#containers-containers", "alpine:latest")

        # delete running container busybox using force delete
        if auth:
            b.click('#containers-containers tbody tr:contains("swamped-crate-system") + tr button.btn-delete')
            self.confirm_modal("Force delete")
            b.wait_not_in_text("#containers-containers", "swamped-crate-system")

        self.filter_containers("all")

        b.click('#containers-containers tbody tr:contains("swamped-crate-user") + tr button.btn-delete')
        self.confirm_modal("Force delete")
        b.wait_not_in_text("#containers-containers", "swamped-crate-user")

        b.wait_visible('#containers-containers td[data-label="Container"]:contains("test-sh-user")')
        b.click('#containers-containers tbody tr:contains("test-sh-user") td.pf-c-table__toggle button')
        b.click('#containers-containers tbody tr:contains("test-sh-user") + tr button.btn-delete')
        self.confirm_modal("Delete")
        b.wait_not_in_text("#containers-containers", "test-sh-user")

        if auth:
            b.wait_visible('#containers-containers td[data-label="Container"]:contains("test-sh-system")')
            b.click('#containers-containers tbody tr:contains("test-sh-system") td.pf-c-table__toggle button')
            b.click('#containers-containers tbody tr:contains("test-sh-system") + tr button.btn-delete')
            self.confirm_modal("Delete")
            b.wait_not_in_text("#containers-containers", "test-sh-system")

        def container_commit(container_name, image_name="testimg", image_tag="testtag", image_author="tester", image_command="sleep 6000", owner="system"):
            self.filter_containers("all")
            b.wait_visible('#containers-containers td[data-label="Container"]:contains({0})'.format(container_name))
            b.click('#containers-containers tbody tr:contains({0}) td.pf-c-table__toggle button'.format(container_name))
            # open commit modal and close it using cancel
            b.click('#containers-containers tbody tr:contains({0}) + tr button.btn-commit'.format(container_name))
            self.confirm_modal("Cancel")
            # open commit modal and create an image
            b.click('#containers-containers tbody tr:contains({0}) + tr button.btn-commit'.format(container_name))
            b.wait_visible(".pf-c-modal-box")
            # set commit parameters
            b.set_input_text("#commit-dialog-image-name", image_name)
            b.set_input_text("#commit-dialog-image-tag", image_tag)
            b.set_input_text("#commit-dialog-author", image_author)
            b.set_input_text("#commit-dialog-command", image_command)
            b.click(".pf-c-modal-box footer .btn-ctr-commit")
            b.wait_not_present(".pf-c-modal-box")
            b.wait_visible('#containers-images td[data-label="Name"]:contains("{0}:{1}")'.format(image_name, image_tag))
            checkImage(b, "localhost/{0}:{1}".format(image_name, image_tag), owner)
            # open the listing toggle of testimg and check the commit paramerters
            toggle_sel = '#containers-images tbody tr:contains("{0}:{1}") td.pf-c-table__toggle button'.format(image_name, image_tag)
            b.click(toggle_sel)
            b.wait_visible('#containers-images tbody tr:contains("{0}:{1}"):has(dd:contains("localhost/{0}:{1}"))'.format(image_name, image_tag))
            b.wait_in_text('#containers-images tbody tr:contains("{0}:{1}") dd:nth-child(8)'.format(image_name, image_tag), image_command)
            b.wait_visible('#containers-images tbody tr:contains("{0}:{1}"):has(dd:contains({2}))'.format(image_name, image_tag, image_author))
            # close listing toggle again
            b.click(toggle_sel)

        # run a container (will exit immediately) and test the display of commit modal
        if auth:
            self.execute(True, "podman run -d --name test-sh0 alpine sh")
            container_commit("test-sh0")
            checkImage(b, "localhost/testimg:testtag", "system")
            self.execute(True, "podman rm -f test-sh0; podman rmi testimg:testtag")

        self.execute(False, "podman run -d --name test-sh1 alpine sh")
        container_commit("test-sh1", owner="admin")
        checkImage(b, "localhost/testimg:testtag", "admin")
        self.execute(False, "podman rm -f test-sh1; podman rmi testimg:testtag")

        # test commit of a running container
        if auth:
            self.execute(True, "podman run -d --name test-sh2 busybox sleep 1000")
            container_commit("test-sh2", image_command=r'sh -c "while echo \"Hello World\"; do sleep 1; done "')
            self.execute(True, "podman rm -f test-sh2; podman rmi testimg:testtag")

        # HACK - this does not work, see https://github.com/containers/libpod/issues/3970
        # self.execute(False, "podman run -d --name test-sh3 alpine sleep 1000")
        # container_commit("test-sh3", owner="admin")
        # self.execute(False, "podman rm -f test-sh3; podman rmi testimg:testtag")

        self.execute(auth, "podman run -d --name test-sh4 alpine sh")
        b.wait_visible('#containers-containers td[data-label="Container"]:contains("test-sh4")')
        b.click('#containers-containers tbody tr:contains("test-sh4") td.pf-c-table__toggle button')
        # open commit modal and check error modal
        b.click('#containers-containers tbody tr:contains("test-sh4") + tr button.btn-commit')
        # check required field error
        b.click(".pf-c-modal-box footer .btn-ctr-commit")
        b.wait_visible('.pf-c-modal-box:contains("Image name is required")')
        b.click(".pf-c-modal-box .pf-c-alert .pf-c-button")
        b.wait_not_present(".pf-c-modal-box .alert")

        # check error
        b.set_input_text("#commit-dialog-image-name", "TEST")
        b.click(".pf-c-modal-box footer .btn-ctr-commit")
        b.wait_visible('.pf-c-modal-box:contains("Failed to commit container")')
        b.click(".pf-c-modal-box .pf-c-alert .pf-c-button")
        b.wait_not_present(".pf-c-modal-box .alert")
        b.wait_visible(".pf-c-modal-box footer .btn-ctr-commit:not([disabled])")

        b.click(".btn-ctr-cancel-commit")

        # delete image busybox that hasn't been used
        # First try to just untag and then remove with more tags
        self.execute(auth, "podman tag quay.io/libpod/busybox:latest quay.io/libpod/busybox:1")
        self.execute(auth, "podman tag quay.io/libpod/busybox:latest quay.io/libpod/busybox:2")
        self.execute(auth, "podman tag quay.io/libpod/busybox:latest quay.io/libpod/busybox:3")
        self.execute(auth, "podman tag quay.io/libpod/busybox:latest quay.io/libpod/busybox:4")

        b.wait_visible(busybox_sel)
        b.click(busybox_sel + " td.pf-c-table__toggle button")

        b.wait_in_text(busybox_sel + " + tr", "quay.io/libpod/busybox:1")
        b.wait_in_text(busybox_sel + " + tr", "quay.io/libpod/busybox:2")
        b.wait_in_text(busybox_sel + " + tr", "quay.io/libpod/busybox:3")
        b.wait_in_text(busybox_sel + " + tr", "quay.io/libpod/busybox:4")

        b.click(busybox_sel + " + tr button.btn-delete")
        self.assertTrue(b.get_checked(".pf-c-check__input[aria-label='quay.io/libpod/busybox:latest']"))
        b.set_checked(".pf-c-check__input[aria-label='quay.io/libpod/busybox:1']", True)
        b.set_checked(".pf-c-check__input[aria-label='quay.io/libpod/busybox:3']", True)
        b.set_checked(".pf-c-check__input[aria-label='quay.io/libpod/busybox:latest']", False)
        self.confirm_modal("Delete")
        b.wait_in_text(busybox_sel + " + tr", "quay.io/libpod/busybox:latest")
        b.wait_in_text(busybox_sel + " + tr", "quay.io/libpod/busybox:2")
        b.wait_not_in_text(busybox_sel + " + tr", "quay.io/libpod/busybox:1")
        b.wait_not_in_text(busybox_sel + " + tr", "quay.io/libpod/busybox:3")

        b.click(busybox_sel + " + tr button.btn-delete")
        b.click("button:contains('select all')")
        self.assertTrue(b.get_checked(".pf-c-check__input[aria-label='quay.io/libpod/busybox:latest']"))
        self.assertTrue(b.get_checked(".pf-c-check__input[aria-label='quay.io/libpod/busybox:2']"))
        self.assertTrue(b.get_checked(".pf-c-check__input[aria-label='quay.io/libpod/busybox:4']"))
        self.confirm_modal("Delete")
        self.confirm_modal("Force delete")
        b.wait_not_in_text("#containers-images", busybox_sel)

        # Check that we correctly show networking information
        # Rootless don't have this info
        if auth:
            # Remove current container for easier selectors
            self.execute(auth, "podman rm -f test-sh4")
            b.wait_not_present(".container-details")

            self.execute(auth, "podman run -dt --name net_check alpine")
            b.click('#containers-containers tbody tr:contains("net_check") td.pf-c-table__toggle button')
            b.wait_in_text(".container-details", self.execute(auth, "podman inspect --format '{{.NetworkSettings.Gateway}}' net_check").strip())
            b.wait_in_text(".container-details", self.execute(auth, "podman inspect --format '{{.NetworkSettings.IPAddress}}' net_check").strip())
            b.wait_in_text(".container-details", self.execute(auth, "podman inspect --format '{{.NetworkSettings.IPPrefixLen}}' net_check").strip())
            b.wait_in_text(".container-details", self.execute(auth, "podman inspect --format '{{.NetworkSettings.MacAddress}}' net_check").strip())

        # delete image alpine that has been used by a container
        alpine_sel = "#containers-images tbody tr[data-row-id={0}{1}]".format(images["quay.io/libpod/alpine:latest"], auth).lower()
        b.wait_visible(alpine_sel)
        b.click(alpine_sel + " td.pf-c-table__toggle button")
        b.click(alpine_sel + " + tr button.btn-delete")
        self.confirm_modal("Delete")
        self.confirm_modal("Force delete")
        b.wait_not_in_text("#containers-images", alpine_sel)

        b.wait_collected_text("#containers-containers .container-name", "")
        self.execute(auth, "podman run -d --name c registry:2 sh")
        b.wait_collected_text("#containers-containers .container-name", "c")
        self.execute(auth, "podman run -d --name a registry:2 sh")
        b.wait_collected_text("#containers-containers .container-name", "ac")

        self.execute(False, "podman run -d --name b registry:2 sh")
        if auth:
            b.wait_collected_text("#containers-containers .container-name", "bac")
            self.execute(False, "podman run -d --name doremi registry:2 sh")
            b.wait_collected_text("#containers-containers .container-name", "bdoremiac")
            b.wait(lambda: b.text('#containers-containers tr:contains(doremi) td:nth-of-type(6)') in NOT_RUNNING)
        else:
            b.wait_collected_text("#containers-containers .container-name", "abc")

        # Test intermediate images
        b.wait_not_present(".listing-action")
        tmpdir = self.execute(auth, "mktemp -d").strip()
        self.execute(auth, "echo 'FROM quay.io/cockpit/registry:2\nRUN ls' > {0}/Dockerfile".format(tmpdir))
        self.execute(auth, "podman build {0}".format(tmpdir))

        b.wait_not_in_text("#containers-images", "<none>:<none>")
        b.click(".listing-action button:contains('Show intermediate images')")
        b.wait_in_text("#containers-images", "<none>:<none>")
        b.wait_in_text("#containers-images tbody:last-child td:nth-child(3)", "Today at")

        b.click(".listing-action button:contains('Hide intermediate images')")
        b.wait_not_in_text("#containers-images", "<none>:<none>")

    @testlib.nondestructive
    def testDownloadImage(self):
        b = self.browser
        execute = self.execute

        def prepare():
            # Create and start registry containers
            self.execute(True, "podman run -d -p 5000:5000 --name registry registry:2")
            self.execute(True, "podman run -d -p 6000:5000 --name registry_alt registry:2")
            # Add local insecure registry into registries conf
            self.execute(True, "echo \"{0}\" > /etc/containers/registries.conf && systemctl stop podman.service".format(REGISTRIES_CONF))
            # Push busybox image to the local registries
            self.execute(True, "podman tag quay.io/libpod/busybox localhost:5000/my-busybox && podman push localhost:5000/my-busybox")
            self.execute(True, "podman tag quay.io/libpod/busybox localhost:6000/my-busybox && podman push localhost:6000/my-busybox")
            # Untag busybox image which duplicates the image we are about to download
            self.execute(True, "podman rmi -f quay.io/libpod/busybox localhost:5000/my-busybox localhost:6000/my-busybox")

        class DownloadImageDialog:
            def __init__(self, imageName, imageTag=None, user="system"):
                self.imageName = imageName
                self.imageTag = imageTag
                self.user = user
                self.imageSha = ""

            def openDialog(self):
                # Open get new image modal
                b.click("button:contains(Get new image)")
                b.wait_visible('div.pf-c-modal-box header:contains("Search for an image")')
                b.wait_visible("div.pf-c-modal-box footer button:contains(Download):disabled")

                return self

            def fillDialog(self):
                # Search for image specified with self.imageName and self.imageTag
                b.click("#{0}".format(self.user))
                b.set_val('#registry-select', "localhost:5000")
                # HACK: Sometimes the value is not shown fully. FIXME
                b.set_input_text("#search-image-dialog-name", self.imageName, value_check=False)
                if self.imageTag:
                    b.set_input_text(".image-tag-entry", self.imageTag)

                return self

            def selectImageAndDownload(self):
                # Select and download the self.imageName image
                b.wait_visible(".pf-c-data-list .image-name:contains({0})".format(self.imageName))
                b.click(".pf-c-data-list .image-name:contains({0})".format(self.imageName))
                b.wait_visible("div.pf-c-modal-box footer button:contains(Download):not([disabled])")
                b.click("div.pf-c-modal-box footer button:contains(Download)")

                return self

            def expectDownloadErrorForNonExistingTag(self):
                b.wait_not_present('div.pf-c-modal-box')
                b.wait_visible('h4.pf-c-alert__title:contains("Danger alert:Failed to download image localhost:5000/{0}:{1}")'.format(self.imageName, self.imageTag))

                return self

            def expectSearchErrorForNotExistingImage(self):
                b.wait_visible(".pf-c-modal-box__body:contains(No results for {0})".format(self.imageName))

                return self

            def expectDownloadSuccess(self):
                # Confirm that the modal dialog is not open anymore
                b.wait_not_present('div.pf-c-modal-box')
                # Confirm that the image got downloaded
                b.wait_visible('#containers-images td[data-label="Name"]:contains("{0}")'.format(self.imageName))
                checkImage(b, "localhost:5000/{0}:{1}".format(self.imageName, self.imageTag or "latest"), "system" if self.user == "system" else "admin")

                # Confirm that no error has happened
                b.wait_not_present('h4.pf-c-alert__title:contains("Failed to download image")')

                # Find out this image ID
                self.imageSha = execute(self.user == "system", "podman inspect --format '{{{{.Id}}}}' localhost:5000/{0}:{1}".format(self.imageName, self.imageTag or "latest")).strip()

                return self

            def deleteImage(self, force=False, another=None):
                imageTagSuffix = ":" + (self.imageTag or 'latest')

                # Select the image row

                # show image listing toggle
                sel = "#containers-images tbody tr[data-row-id={0}{1}]".format(self.imageSha, "true" if self.user == "system" else "false")
                b.wait_visible(sel)
                b.click(sel + " td.pf-c-table__toggle button")

                # Click the delete icon on the image row
                b.wait_visible(sel + ' + tr button.btn-delete')
                b.click(sel + ' + tr button.btn-delete')

                b.set_checked(".pf-c-check__input[aria-label='localhost:5000/{0}{1}']".format(self.imageName, imageTagSuffix), True)
                if another:
                    b.set_checked(".pf-c-check__input[aria-label='{0}']".format(another), True)

                # Confirm deletion in the delete dialog
                b.click(".pf-c-modal-box #btn-img-delete")

                if force:
                    # Confirm force delete
                    b.click(".pf-c-modal-box button:contains('Force delete')")

                b.wait_not_present(sel)

                return self

        prepare()

        self.login_and_go("/podman", superuser=True)
        b.wait_visible("#app")

        # Test registries
        b.click("button:contains(Get new image)")
        b.wait_visible('div.pf-c-modal-box header:contains("Search for an image")')
        # HACK: Sometimes the value is not shown fully. FIXME
        b.set_input_text("#search-image-dialog-name", "my-busybox", value_check=False)

        b.wait_visible(".pf-c-data-list .image-name:contains('localhost:5000/my-busybox')")
        b.wait_visible(".pf-c-data-list .image-name:contains('localhost:6000/my-busybox')")

        b.set_val('#registry-select', "localhost:6000")
        b.wait_not_present(".pf-c-data-list .image-name:contains('localhost:5000/my-busybox')")
        b.wait_visible(".pf-c-data-list .image-name:contains('localhost:6000/my-busybox')")
        b.click(".pf-c-modal-box button:contains('Cancel')")
        b.wait_not_present('div.pf-c-modal-box')

        dialog0 = DownloadImageDialog('my-busybox', user="system")
        dialog0.openDialog() \
            .fillDialog() \
            .selectImageAndDownload() \
            .expectDownloadSuccess()

        dialog1 = DownloadImageDialog('my-busybox', user="user")
        dialog1.openDialog() \
            .fillDialog() \
            .selectImageAndDownload() \
            .expectDownloadSuccess()
        dialog1.deleteImage(True, another="quay.io/libpod/busybox:latest")

        dialog0.deleteImage()

        dialog = DownloadImageDialog('my-busybox', 'latest', user="system")
        dialog.openDialog() \
              .fillDialog() \
              .selectImageAndDownload() \
              .expectDownloadSuccess() \
              .deleteImage()

        dialog = DownloadImageDialog('foobar')
        dialog.openDialog() \
              .fillDialog() \
              .expectSearchErrorForNotExistingImage()

        dialog = DownloadImageDialog('my-busybox', 'foobar')
        dialog.openDialog() \
              .fillDialog() \
              .selectImageAndDownload() \
              .expectDownloadErrorForNonExistingTag()

    @testlib.nondestructive
    def testLifecycleOperationsUser(self):
        self._testLifecycleOperations(False)

    @testlib.nondestructive
    def testLifecycleOperationsSystem(self):
        self._testLifecycleOperations(True)

    def _testLifecycleOperations(self, auth):
        b = self.browser

        if not auth:
            self.allow_authorize_journal_messages()
            self.allow_browser_errors("Failed to start system podman.socket.*")

        self.login_and_go("/podman", superuser=auth)
        b.wait_visible("#app")
        self.filter_containers('all')

        # run a container
        self.execute(auth, "podman run -dit --name swamped-crate busybox:latest sh; podman stop swamped-crate")
        b.wait(lambda: self.execute(auth, "podman ps --all | grep -e swamped-crate -e Exited"))

        b.wait_visible("#containers-containers")
        b.wait_visible('#containers-containers td[data-label="Container"]:contains("swamped-crate")')
        b.click('#containers-containers tbody tr:contains("busybox:latest") td.pf-c-table__toggle button')

        if not auth:
            # Checkpoint/restore is not supported on user containers yet - the related buttons should not be shown
            # Check that the restore option is not present (i.e. start is a regular button)
            b.wait_visible('#containers-containers tbody tr:contains("busybox:latest") + tr button.pf-c-button:contains(Start)')

        # Start the container
        b.click('#containers-containers tbody tr:contains("busybox:latest") + tr button:contains(Start)')

        container_sha = self.execute(auth, "podman inspect --format '{{.Id}}' swamped-crate").strip()

        with b.wait_timeout(5):
            self.waitContainer(container_sha, auth, name='swamped-crate', image='busybox:latest', cmd='sh',
                               state='running', owner="system" if auth else "admin")
        # Check we show usage
        b.wait(lambda: b.text("#containers-containers tbody tr:contains('busybox:latest') > td:nth-child(3)") != "")
        cpu = b.text("#containers-containers tbody tr:contains('busybox:latest') > td:nth-child(3)")
        memory = b.text("#containers-containers tbody tr:contains('busybox:latest') > td:nth-child(4)")
        if auth or self.cgroupsV2():
            self.assertIn('%', cpu)
            num = cpu[:-1]
            self.assertTrue(num.replace('.', '', 1).isdigit())

            self.assertIn('/', memory)
            numbers = memory.split('/')
            self.assertTrue(numbers[0].strip().replace('.', '', 1).isdigit())
            full = numbers[1].strip().split()
            self.assertTrue(full[0].replace('.', '', 1).isdigit())
            self.assertIn(full[1], ["GiB", "MiB"])

            # Test that the value is updated dynamically
            self.execute(auth, "podman exec -i swamped-crate sh -c 'dd bs=1024 count=1000000 < /dev/urandom > /dev/null'")
            new_cpu = b.text("#containers-containers tbody tr:contains('busybox:latest') > td:nth-child(3)")
            self.assertIn('%', new_cpu)
            new_num = new_cpu[:-1]
            self.assertTrue(new_num.replace('.', '', 1).isdigit())
            self.assertTrue(new_num > num)
        else:
            # No support for CGroupsV2
            self.assertEqual(cpu, "n/a")
            self.assertEqual(memory, "n/a")

        # Restart the container
        old_pid = self.execute(auth, "podman inspect --format '{{.State.Pid}}' swamped-crate")
        b.click('#containers-containers tbody tr:contains("busybox:latest") + tr button:contains(Restart) + button')
        b.click('#containers-containers tbody tr:contains("busybox:latest") + tr ul.pf-c-dropdown__menu li a:contains(Force restart)')
        b.wait(lambda: old_pid != self.execute(auth, "podman inspect --format '{{.State.Pid}}' swamped-crate".strip()))

        with b.wait_timeout(5):
            self.waitContainer(container_sha, auth, name='swamped-crate', image='busybox:latest', cmd='sh', state='running')

        self.filter_containers('all')
        b.wait_visible("#containers-containers")

        # Stop the container
        b.wait_visible('#containers-containers td[data-label="Container"]:contains("busybox:latest")')
        b.click('#containers-containers tbody tr:contains("busybox:latest") + tr button:contains(Stop) + button')
        if not auth:
            # Check that the checkpoint option is not present for rootless
            b.wait_visible('#containers-containers tr:contains("busybox:latest") + tr a:contains("Force stop")')
            b.wait_not_present('#containers-containers tr:contains("busybox:latest") + tr #Stop-dropdown ul li:has(a:contains(Checkpoint))')
        b.click('#containers-containers tbody tr:contains("busybox:latest") + tr ul.pf-c-dropdown__menu li a:contains(Force stop)')

        self.waitContainer(container_sha, auth, name='swamped-crate', image='busybox:latest', cmd='sh')
        b.wait(lambda: b.text('#containers-containers tr:contains(swamped-crate) td:nth-of-type(6)') in NOT_RUNNING)
        b.wait_text("#containers-containers tbody tr:contains('busybox:latest') > td:nth-child(3)", "")
        b.wait_text("#containers-containers tbody tr:contains('busybox:latest') > td:nth-child(4)", "")

    @testlib.nondestructive
    def testCheckpointRestoreCGroupsV2(self):
        b = self.browser
        m = self.machine

        if not self.cgroupsV2():
            return

        # Only Fedora works with CGroupsV2
        if m.image.startswith('fedora'):
            return self._testCheckpointRestore()

        # On other systems just check that we get expected error messages

        self.login_and_go("/podman", superuser=True)
        b.wait_visible("#app")
        self.filter_containers('all')

        # Run a container
        self.execute(True, "podman run -dit --name swamped-crate busybox:latest sh")
        b.wait(lambda: self.execute(True, "podman ps --all | grep -e swamped-crate"))
        b.click('#containers-containers tbody tr:contains("busybox:latest") td.pf-c-table__toggle button')

        # Checkpoint the container
        b.click('#containers-containers tr:contains("busybox:latest") + tr button:contains(Stop) + button')
        b.click('#containers-containers tr:contains("busybox:latest") + tr #Stop-dropdown ul li:has(a:contains(Checkpoint))')
        b.set_checked('.pf-c-modal-box input#checkpoint-dialog-keep', True)
        b.set_checked('.pf-c-modal-box input#checkpoint-dialog-tcpEstablished', True)
        b.set_checked('.pf-c-modal-box input#checkpoint-dialog-ignoreRootFS', True)
        b.click('.pf-c-modal-box button:contains(Checkpoint)')
        b.wait_not_present('.modal_dialog')

        if self.has_criu:
            b.wait_in_text('.pf-c-alert.pf-m-danger .pf-c-alert__description', 'Configured runtime does not support checkpoint/restore')
        else:
            b.wait_in_text(".pf-c-alert.pf-m-danger", "Checkpoint/Restore requires at least CRIU")

    def testCheckpointRestoreCGroupsV1(self):
        m = self.machine

        # Set machine to cgroup1 and reboot; Debian uses hybrid mode and does not have grubby
        if not m.image.startswith("debian") and not m.image.startswith("ubuntu"):
            self.execute(True, 'grubby --update-kernel=ALL --args="systemd.unified_cgroup_hierarchy=0"')
            self.machine.spawn("sleep 0.1 && reboot", "reboot")
            m.wait_reboot()

        self._testCheckpointRestore()

    def _testCheckpointRestore(self):
        b = self.browser
        m = self.machine

        self.login_and_go("/podman", superuser=True)
        b.wait_visible("#app")
        self.filter_containers('all')

        # Run a container
        self.execute(True, "podman run -dit --name swamped-crate busybox:latest sh; podman stop swamped-crate")
        b.wait(lambda: self.execute(True, "podman ps --all | grep -e swamped-crate -e Exited"))
        b.click('#containers-containers tbody tr:contains(swamped-crate) td.pf-c-table__toggle button')

        # Check that the restore option is not present (i.e. start is a regular button)
        b.wait_visible('#containers-containers tbody tr:contains(swamped-crate) + tr button.pf-c-button:contains(Start)')

        # Start the container
        b.click('#containers-containers tbody tr:contains(swamped-crate) + tr button:contains(Start)')
        b.wait_in_text('#containers-containers tr:contains(swamped-crate)', 'running')

        # Checkpoint the container
        b.click('#containers-containers tr:contains(swamped-crate) + tr button:contains(Stop) + button')
        b.click('#containers-containers tr:contains(swamped-crate) + tr #Stop-dropdown ul li:has(a:contains(Checkpoint))')
        b.set_checked('.pf-c-modal-box input#checkpoint-dialog-keep', True)
        b.set_checked('.pf-c-modal-box input#checkpoint-dialog-tcpEstablished', True)
        b.set_checked('.pf-c-modal-box input#checkpoint-dialog-ignoreRootFS', True)
        b.click('.pf-c-modal-box button:contains(Checkpoint)')

        with b.wait_timeout(300):
            b.wait_not_present(".pf-c-modal-box")

        if self.has_criu:
            b.wait(lambda: b.text('#containers-containers tr:contains(swamped-crate) td:nth-of-type(6)') in NOT_RUNNING)
        else:
            # expect proper error message
            b.wait_in_text(".pf-c-alert.pf-m-danger", "Failed to checkpoint container swamped-crate")
            b.wait_in_text(".pf-c-alert.pf-m-danger", "Checkpoint/Restore requires at least CRIU")
            return

        # Restore the container
        b.wait_visible('#containers-containers td[data-label="Container"]:contains(swamped-crate)')
        b.click('#containers-containers tr:contains(swamped-crate) + tr button:contains(Start) + button')
        b.click('#containers-containers tr:contains(swamped-crate) + tr #Start-dropdown ul li:has(a:contains(Restore))')
        b.set_checked('.pf-c-modal-box input#restore-dialog-keep', True)
        b.set_checked('.pf-c-modal-box input#restore-dialog-tcpEstablished', True)
        b.set_checked('.pf-c-modal-box input#restore-dialog-ignoreStaticIP', True)
        b.set_checked('.pf-c-modal-box input#restore-dialog-ignoreStaticMAC', True)
        b.click('.pf-c-modal-box button:contains(Restore)')
        b.wait(lambda: b.text('#containers-containers tr:contains(swamped-crate) td:nth-of-type(6)') == 'running')

        # Checkpoint the container without stopping
        b.wait_visible('#containers-containers td[data-label="Container"]:contains(swamped-crate)')
        b.click('#containers-containers tr:contains(swamped-crate) + tr button:contains(Stop) + button')
        b.click('#containers-containers tr:contains(swamped-crate) + tr #Stop-dropdown ul li:has(a:contains(Checkpoint))')
        b.set_checked('.pf-c-modal-box input#checkpoint-dialog-leaveRunning', True)
        b.click('.pf-c-modal-box button:contains(Checkpoint)')
        b.wait_not_present('.modal_dialog')

        # Stop the container
        m.execute("podman stop swamped-crate")
        b.wait(lambda: b.text('#containers-containers tr:contains(swamped-crate) td:nth-of-type(6)') in NOT_RUNNING)

        # Restore the container
        b.click('#containers-containers tr:contains(swamped-crate) + tr button:contains(Start) + button')
        b.click('#containers-containers tr:contains(swamped-crate) + tr #Start-dropdown ul li:has(a:contains(Restore))')
        b.click('.pf-c-modal-box button:contains(Restore)')
        b.wait(lambda: b.text('#containers-containers tr:contains(swamped-crate) td:nth-of-type(6)') == 'running')

    @testlib.nondestructive
    def testNotRunning(self):
        b = self.browser

        def disable_system():
            self.execute(True, "systemctl disable --now podman.socket")
            self.execute(True, "killall podman || true")

        def enable_system():
            self.execute(True, "systemctl enable --now podman.socket")

        def enable_user():
            self.execute(False, "systemctl --user enable --now podman.socket")

        def disable_user():
            self.execute(False, "systemctl --user disable --now podman.socket")
            # HACK: user podman.service leaks a "podman pause" outside of the unit: https://github.com/containers/podman/issues/7180
            self.execute(False, "pkill -u $USER -e podman; while pgrep -u $USER -a podman; do sleep 1; done")

        def is_active_system(string):
            b.wait(lambda: self.execute(True, "systemctl is-active podman.socket || true").strip() == string)

        def is_enabled_system(string):
            b.wait(lambda: self.execute(True, "systemctl is-enabled podman.socket || true").strip() == string)

        def is_active_user(string):
            b.wait(lambda: self.execute(False, "systemctl --user is-active podman.socket || true").strip() == string)

        def is_enabled_user(string):
            b.wait(lambda: self.execute(False, "systemctl --user is-enabled podman.socket || true").strip() == string)

        disable_system()
        disable_user()
        self.login_and_go("/podman")

        # Troubleshoot action
        b.click("#app .pf-c-empty-state button.pf-m-link")
        b.enter_page("/system/services")
        b.wait_in_text("#service-details", "podman.socket")

        # Start action, with enabling (by default)
        b.go("/podman")
        b.enter_page("/podman")
        b.click("#app .pf-c-empty-state button.pf-m-primary")

        b.wait_visible("#containers-containers")
        b.wait_not_present("#overview > div.pf-c-alert.pf-m-info")

        is_active_system("active")
        is_active_user("active")
        is_enabled_system("enabled")
        is_enabled_user("enabled")

        # Start action, without enabling
        disable_system()
        disable_user()
        b.click("#app .pf-c-empty-state input[type=checkbox]")
        b.click("#app .pf-c-empty-state button.pf-m-primary")

        b.wait_visible("#containers-containers")
        is_enabled_system("disabled")
        is_enabled_user("disabled")
        is_active_system("active")
        is_active_user("active")

        b.logout()
        disable_system()
        # HACK: Due to https://github.com/containers/podman/issues/7180, avoid user podman.service to time out; make sure to start it afresh
        disable_user()
        enable_user()
        self.login_and_go("/podman")
        b.wait_in_text("#overview div.pf-c-alert .pf-c-alert__title", "System Podman service is also available")
        b.click("#overview div.pf-c-alert .pf-c-alert__action > button:contains(Start)")
        b.wait_not_present("#overview div.pf-c-alert")
        is_active_system("active")
        is_active_user("active")
        is_enabled_user("enabled")
        is_enabled_system("enabled")

        b.logout()
        disable_user()
        enable_system()
        self.login_and_go("/podman")
        b.wait_in_text("#overview div.pf-c-alert .pf-c-alert__title", "User Podman service is also available")
        b.click("#overview div.pf-c-alert .pf-c-alert__action > button:contains(Start)")
        b.wait_not_present("#overview > div.pf-c-alert")
        is_active_system("active")
        is_active_user("active")
        is_enabled_user("enabled")
        is_enabled_system("enabled")

        b.logout()
        disable_user()
        disable_system()
        self.login_and_go("/podman", superuser=False)
        b.click("#app .pf-c-empty-state button.pf-m-primary")

        # HACK: https://github.com/containers/libpod/issues/6660
        # Try to click on the button bunch of times due to the aforementioned bug
        with b.wait_timeout(8):
            for _ in range(5):
                try:
                    b.wait_visible("#containers-containers")
                    b.wait_not_present("#overview div.pf-c-alert")
                    break
                except testlib.Error:
                    b.click("#app .pf-c-empty-state button.pf-m-primary")

        is_active_system("inactive")
        is_active_user("active")
        is_enabled_user("enabled")
        is_enabled_system("disabled")
        b.logout()

        # no Troubleshoot action without cockpit-system package
        disable_system()
        disable_user()
        self.restore_file("/usr/share/cockpit/systemd/manifest.json")
        self.machine.execute("rm /usr/share/cockpit/systemd/manifest.json")
        self.login_and_go("/podman")
        b.wait_visible("#app .pf-c-empty-state button.pf-m-primary")
        self.assertFalse(b.is_present("#app .pf-c-empty-state button.pf-m-link"))
        # starting still works
        b.click("#app .pf-c-empty-state button.pf-m-primary")
        b.wait_visible("#containers-containers")

        self.allow_restart_journal_messages()
        self.allow_authorize_journal_messages()
        self.allow_journal_messages(".*podman/podman.sock/.*: couldn't connect:.*")
        self.allow_journal_messages(".*podman/podman.sock/.*/events.*: received truncated HTTP response.*")

    @testlib.nondestructive
    def testRunImageSystem(self):
        self._testRunImage(True)

    @testlib.nondestructive
    def testRunImageUser(self):
        self.allow_authorize_journal_messages()
        self._testRunImage(False)

    def _testRunImage(self, auth):
        b = self.browser
        m = self.machine

        # Just drop user images so we can use simpler selectors
        if auth:
            self.execute(False, "podman rmi alpine busybox registry:2")

        self.login_and_go("/podman", superuser=auth)

        b.wait_in_text("#containers-images", "busybox:latest")
        b.wait_in_text("#containers-images", "alpine:latest")
        if auth:
            b.wait_not_in_text("#containers-images", "admin")

        # Check command in alpine
        b.wait_visible('#containers-images td[data-label="Name"]:contains("alpine:latest")')
        b.click('#containers-images tbody tr:contains("alpine:latest") td > button > .pf-m-start')
        b.wait_visible('div.pf-c-modal-box header:contains("Run image")')
        b.wait_visible("#run-image-dialog-command[value='/bin/sh']")
        b.click(".btn-cancel")

        # Open run image dialog
        b.wait_visible('#containers-images td[data-label="Name"]:contains("busybox:latest")')
        b.click('#containers-images tbody tr:contains("busybox:latest") td > button > .pf-m-start')
        b.wait_visible('div.pf-c-modal-box header:contains("Run image")')

        # Inspect and fill modal dialog
        b.wait_in_text("#run-image-dialog-image", "busybox:latest")

        # Check that there is autogenerated name and then overwrite it
        b.wait_not_val("#run-image-dialog-name", "")
        b.set_input_text("#run-image-dialog-name", "busybox-with-tty")

        b.wait_visible("#run-image-dialog-command[value='sh']")

        # Check memory configuration
        # Only works with CGroupsV2
        if auth or self.cgroupsV2():
            b.set_checked("#run-image-dialog-memory-limit-checkbox", True)
            b.wait_visible("#run-image-dialog-memory-limit-checkbox:checked")
            b.wait_visible('#run-image-dialog-memory-limit input[value="512"]')
            b.set_input_text("#run-image-dialog-memory-limit input[type=number]", "0.5")
            b.set_val('#memory-unit-select', "GiB")

        # CPU shares work only with system containers
        if auth:
            b.set_checked("#run-image-dialog-cpu-priority-checkbox", True)
            b.wait_visible("#run-image-dialog-cpu-priority-checkbox:checked")
            b.wait_visible('#run-image-dialog-cpu-priority input[value=""]')
            b.set_input_text("#run-image-dialog-cpu-priority input[type=number]", "512")
        else:
            b.wait_not_present("#run-image-dialog-cpu-priority-checkbox")

        # Enable tty
        b.set_checked("#run-image-dialog-tty", True)

        # Set up command line
        b.set_input_text('#run-image-dialog-command', "sh -c 'for i in $(seq 20); do sleep 1; echo $i; done; sleep infinity'")

        # Configure published ports
        b.set_input_text('.publish-port-form[data-key="0"] input:nth-child(2)', '6000')
        b.set_input_text('.publish-port-form[data-key="0"] input:nth-child(3)', '5000')
        b.click('.publish-port-form[data-key="0"] .btn-add')
        b.set_input_text('.publish-port-form[data-key="1"] input:first-child', '127.0.0.1')
        b.set_input_text('.publish-port-form[data-key="1"] input:nth-child(2)', '6001')
        b.set_input_text('.publish-port-form[data-key="1"] input:nth-child(3)', '5001')
        b.set_val('.publish-port-form[data-key="1"] select', "udp")
        b.click('.publish-port-form[data-key="1"] .btn-add')
        b.set_input_text('.publish-port-form[data-key="2"] input:first-child', '7001')
        b.set_input_text('.publish-port-form[data-key="2"] input:nth-child(2)', '7001')
        b.click('.publish-port-form[data-key="2"] .btn-close')
        b.click('.publish-port-form[data-key="1"] .btn-add')
        b.set_input_text('.publish-port-form[data-key="3"] input:nth-child(3)', '8001')
        b.click('.publish-port-form[data-key="3"] .btn-add')
        b.set_input_text('.publish-port-form[data-key="4"] input:first-child', '127.0.0.2')
        b.set_input_text('.publish-port-form[data-key="4"] input:nth-child(3)', '9001')

        # Configure env
        b.set_input_text('.env-form[data-key="0"] input:first-child', 'APPLE')
        b.set_input_text('.env-form[data-key="0"] input:nth-child(2)', 'ORANGE')
        b.click('.env-form[data-key="0"] .btn-add')
        b.set_input_text('.env-form[data-key="1"] input:first-child', 'PEAR')
        b.set_input_text('.env-form[data-key="1"] input:nth-child(2)', 'BANANA')
        b.click('.env-form[data-key="1"] .btn-add')
        b.set_input_text('.env-form[data-key="2"] input:first-child', 'MELON')
        b.set_input_text('.env-form[data-key="2"] input:nth-child(2)', 'GRAPE')
        b.click('.env-form[data-key="2"] .btn-close')
        b.click('.env-form[data-key="1"] .btn-add')
        b.set_input_text('.env-form[data-key="3"] input:first-child', 'RHUBARB')
        b.set_input_text('.env-form[data-key="3"] input:nth-child(2)', 'STRAWBERRY')

        # Configure volumes
        rodir, rwdir = m.execute("mktemp; mktemp").split('\n')[:2]
        m.execute("chown admin:admin {0}".format(rodir))
        m.execute("chown admin:admin {0}".format(rwdir))
        b.set_val('.volume-form[data-key="0"] select:first-of-type', "ro")

        if self.has_selinux:
            b.set_val('.volume-form[data-key="0"] select:nth-of-type(2)', "z")
        else:
            b.wait_not_present('.volume-form[data-key="0"] select:nth-of-type(2)')

        b.set_input_text('.volume-form[data-key="0"] .pf-m-typeahead input', rodir)
        b.key_press(["\r"])
        b.set_input_text('.volume-form[data-key="0"] > div > input', '/tmp/ro')
        ro_label = m.execute("ls -dZ {0}".format(rodir)).split(" ")[0]
        b.set_input_text('.volume-form[data-key="0"] .pf-m-typeahead input', rodir)
        b.key_press(["\r"])
        b.click('.volume-form[data-key="0"] .btn-add')
        b.click('.volume-form[data-key="1"] .btn-close')
        b.wait_not_present('.volume-form[data-key="1"]')
        b.click('.volume-form[data-key="0"] .btn-add')

        if self.has_selinux:
            b.set_val('.volume-form[data-key="2"] select:nth-of-type(2)', "Z")
        else:
            b.wait_not_present('.volume-form[data-key="0"] select:nth-of-type(2)')

        b.set_input_text('.volume-form[data-key="2"] .pf-m-typeahead input', rwdir)
        b.key_press(["\r"])
        b.set_input_text('.volume-form[data-key="2"] > div > input', '/tmp/rw')
        b.set_input_text('.volume-form[data-key="2"] .pf-m-typeahead input', rwdir)
        b.key_press(["\r"])
        rw_label = m.execute("ls -dZ {0}".format(rwdir)).split(" ")[0]

        b.click('.pf-c-modal-box__footer button:contains("Run")')
        b.wait_not_present("div.pf-c-modal-box")
        b.wait_visible('#containers-containers td[data-label="Container"]:contains("busybox:latest")')
        sha = self.execute(auth, "podman inspect --format '{{.Id}}' busybox-with-tty").strip()
        self.waitContainer(sha, auth, name='busybox-with-tty', image='busybox:latest',
                           cmd='sh -c "for i in $(seq 20); do sleep 1; echo $i; done; sleep infinity"',
                           state='running', owner="system" if auth else "admin")
        hasTTY = self.execute(auth, "podman inspect --format '{{.Config.Tty}}' busybox-with-tty").strip()
        self.assertEqual(hasTTY, 'true')
        # Only works with CGroupsV2
        if auth or self.cgroupsV2():
            memory = self.execute(auth, "podman inspect --format '{{.HostConfig.Memory}}' busybox-with-tty").strip()
            self.assertEqual(memory, '536870912')

        if auth:
            cpuShares = self.execute(auth, "podman inspect --format '{{.HostConfig.CpuShares}}' busybox-with-tty || podman inspect --format '{{.HostConfig.CPUShares}}' busybox-with-tty").strip()
            self.assertEqual(cpuShares, '512')

        b.wait(lambda: "3" in self.execute(auth, "podman logs busybox-with-tty"))

        b.click('#containers-containers tbody tr:contains("busybox:latest") td.pf-c-table__toggle button')

        b.wait_in_text('#containers-containers tr:contains("busybox:latest") dt:contains("Ports") + dd', '0.0.0.0:6000 \u2192 5000/tcp')
        b.wait_in_text('#containers-containers tr:contains("busybox:latest") dt:contains("Ports") + dd', '127.0.0.1:6001 \u2192 5001/udp')
        b.wait_in_text('#containers-containers tr:contains("busybox:latest") dt:contains("Ports") + dd', '127.0.0.2:')
        b.wait_in_text('#containers-containers tr:contains("busybox:latest") dt:contains("Ports") + dd', ' \u2192 8001/tcp')
        b.wait_not_in_text('#containers-containers tr:contains("busybox:latest") dt:contains("Ports") + dd', '7001/tcp')
        ports = self.execute(auth, "podman inspect --format '{{.NetworkSettings.Ports}}' busybox-with-tty")

        b.wait_in_text('#containers-containers tr:contains("busybox:latest") dt:contains("Created") + dd', 'Today at')

        self.assertIn('5000/tcp:[{ 6000}]', ports)
        self.assertIn('5001/udp:[{127.0.0.1 6001}]', ports)
        self.assertIn('8001/tcp:[{ ', ports)
        self.assertIn('9001/tcp:[{127.0.0.2 ', ports)
        self.assertNotIn('7001/tcp', ports)

        env = self.execute(auth, "podman exec busybox-with-tty env")
        self.assertIn('APPLE=ORANGE', env)
        self.assertIn('PEAR=BANANA', env)
        self.assertIn('RHUBARB=STRAWBERRY', env)
        self.assertNotIn('MELON=GRAPE', env)

        romnt = self.execute(auth, "podman exec busybox-with-tty cat /proc/self/mountinfo | grep /tmp/ro")
        self.assertIn('ro', romnt)
        self.assertIn(rodir[4:], romnt)
        rwmnt = self.execute(auth, "podman exec busybox-with-tty cat /proc/self/mountinfo | grep /tmp/rw")
        self.assertIn('rw', rwmnt)
        self.assertIn(rwdir[4:], rwmnt)

        if self.has_selinux:
            # rw was set to :Z so it should change, but not be shared
            rw_label_new = m.execute("ls -dZ {0}".format(rwdir)).split(" ")[0]
            self.assertNotEqual(rw_label, rw_label_new)
            self.assertRegex(rw_label_new, r"container_file_t:s0:c\d*,c\d*$")

            # ro was set to :z to it should change and be shared
            ro_label_new = m.execute("ls -dZ {0}".format(rodir)).split(" ")[0]
            self.assertNotEqual(ro_label, ro_label_new)
            self.assertRegex(ro_label_new, "container_file_t:s0$")

        def get_int(n):
            try:
                return int(n)
            except ValueError:
                return 0

        b.click("a:contains('Logs')")
        b.wait_text(".container-logs .xterm-accessibility-tree > div:nth-child(1)", "1")
        b.wait_text(".container-logs .xterm-accessibility-tree > div:nth-child(6)", "6")

        b.click("a:contains('Console')")
        b.wait(lambda: get_int(b.text(".container-terminal .xterm-accessibility-tree > div:nth-child(3)")) > 7)

        # HACK: Stop this container because of https://github.com/containers/libpod/issues/3454
        b.click('#containers-containers tbody tr:contains("busybox-with-tty") + tr button:contains(Stop) + button')
        b.click("div.pf-c-dropdown.pf-m-expanded ul li:nth-child(1) a")

        # Create another instance without port publishing
        b.wait_visible('#containers-images td[data-label="Name"]:contains("busybox:latest")')
        b.click('#containers-containers tbody tr:contains("busybox:latest") td.pf-c-table__toggle button')
        b.click('#containers-images tbody tr:contains("busybox:latest") td > button > .pf-m-start')
        b.wait_visible('div.pf-c-modal-box header:contains("Run image")')

        b.wait_in_text("#run-image-dialog-image", "busybox:latest")
        b.set_input_text("#run-image-dialog-name", "busybox-without-publish")

        # Set up command line
        b.set_input_text('#run-image-dialog-command', '/bin/sh -c "sleep 1000m"')

        # Run without tty, console should be able to `exec`
        b.set_checked("#run-image-dialog-tty", False)

        b.click('.pf-c-modal-box__footer button:contains("Run")')
        b.wait_not_present("div.pf-c-modal-box")

        b.wait_visible('#containers-containers td[data-label="Container"]:contains("busybox-without-publish")')
        b.click('#containers-containers tbody tr:contains("busybox-without-publish") td.pf-c-table__toggle button')
        b.wait_visible('#containers-containers tbody tr:contains("busybox-without-publish") + tr dt:contains("Ports")')
        b.wait_text('#containers-containers tr:contains("busybox-without-publish") + tr dt:contains("Ports") + dd', "")

        # Rootless only works with CGroupsV2
        if auth or self.cgroupsV2():
            cpuShares = self.execute(auth, "podman inspect --format '{{.HostConfig.CpuShares}}' busybox-without-publish").strip()
            # podman ≥ 1.8 translates 0 default into actual value
            self.assertIn(cpuShares, ['0', '1024'])

        b.set_val("#containers-containers-filter", "all")

        b.click(".pf-m-expanded a:contains('Console')")
        b.wait_text(".pf-m-expanded .xterm-accessibility-tree > div:nth-child(1)", "/ # ")
        b.focus(".pf-m-expanded .xterm-helper-textarea")
        b.key_press('echo hello\r')
        b.wait_text(".pf-m-expanded .xterm-accessibility-tree > div:nth-child(2)", "hello")
        b.wait_text(".pf-m-expanded .xterm-accessibility-tree > div:nth-child(3)", "/ # ")
        b.wait_text(".pf-m-expanded .xterm-accessibility-tree > div:nth-child(1)", "/ # echo hello")
        b.click('#containers-containers tbody tr:contains("busybox-without-publish") + tr button:contains(Stop) + button')
        b.click("div.pf-c-dropdown.pf-m-expanded ul li:nth-child(1) a")
        b.wait_text(".pf-m-expanded .xterm-accessibility-tree > div:nth-child(3)", "/ #  disconnected ")
        sha = self.execute(auth, "podman inspect --format '{{.Id}}' busybox-without-publish").strip()
        self.waitContainer(sha, auth, name='busybox-without-publish', image='busybox:latest', state=NOT_RUNNING)
        b.click('#containers-containers tbody tr:contains("busybox-without-publish") + tr button:contains(Start)')
        self.waitContainer(sha, auth, state='running')
        b.wait_text(".pf-m-expanded .xterm-accessibility-tree > div:nth-child(1)", "/ # ")
        b.click('#containers-containers tbody tr:contains("busybox-without-publish") + tr button:contains(Stop) + button')
        b.click("div.pf-c-dropdown.pf-m-expanded ul li:nth-child(1) a")
        self.waitContainer(sha, auth, state=NOT_RUNNING)
        b.click('#containers-containers tr:contains("busybox-without-publish")')

        b.set_input_text('#containers-filter', 'tty')
        self.check_containers(["busybox-with-tty"], ["busybox-without-publish"])
        self.check_images([], ["busybox:latest", "alpine:latest", "registry:2"])
        b.set_input_text('#containers-filter', 'busy')
        self.check_containers(["busybox-with-tty", "busybox-without-publish"], [])
        self.check_images(["busybox:latest"], ["alpine:latest", "registry:2"])
        b.set_input_text('#containers-filter', 'alpine')
        self.check_containers([], ["busybox-with-tty", "busybox-without-publish"])
        self.check_images(["alpine:latest"], ["busybox:latest", "registry:2"])
        b.set_input_text('#containers-filter', '')
        self.check_containers(["busybox-with-tty", "busybox-without-publish"], [])
        self.check_images(["busybox:latest", "alpine:latest", "registry:2"], [])

        b.set_val("#containers-containers-filter", "all")
        self.waitContainer(sha, auth, name='busybox-without-publish', image='busybox:latest', state=NOT_RUNNING)
        b.click('#containers-containers tr:contains("busybox-without-publish") .pf-c-table__toggle button')
        b.click(".pf-m-expanded a:contains('Console')")
        b.wait_text(".pf-m-expanded .pf-c-title", "Container is not running")

        b.click('#containers-images tbody tr:contains("busybox:latest") td.pf-c-table__toggle button')
        b.click("#containers-images tbody tr:contains('busybox:latest') a:contains('Used by')")
        b.wait_visible("#containers-images tbody tr:contains('busybox:latest') + tr div.ct-listing-panel-body tbody tr:contains('busybox-without-publish')")
        b.wait_visible("#containers-images tbody tr:contains('busybox:latest') + tr div.ct-listing-panel-body tbody tr:contains('busybox-with-tty')")

        b.click('#containers-images tbody tr:contains("alpine:latest") td.pf-c-table__toggle button')
        b.click("#containers-images tbody tr:contains('alpine:latest') a:contains('Used by')")
        b.wait_visible("#containers-images tbody tr:contains('alpine:latest') + tr div.ct-listing-panel-body thead.ct-table-empty td:contains('No containers are using this image')")

        b.set_input_text('#containers-filter', 'foobar')
        b.wait_visible('#containers-containers thead tr td:contains("No containers that match the current filter")')
        b.wait_visible('#containers-images thead tr td:contains("No images that match the current filter")')
        b.set_input_text('#containers-filter', '')
        self.execute(auth, "podman rmi -af")
        b.wait_visible('#containers-containers thead tr td:contains("No containers")')
        b.set_val("#containers-containers-filter", "running")
        b.wait_visible('#containers-containers thead tr td:contains("No running containers")')
        b.wait_visible('#containers-images thead tr td:contains("No images")')

    def check_content(self, type, present, not_present):
        b = self.browser
        for item in present:
            b.wait_visible('#containers-{0} tbody tr:first-child:contains({1})'.format(type, item))
        for item in not_present:
            b.wait_not_present('#containers-{0} tbody tr:first-child:contains({1})'.format(type, item))

    def check_containers(self, present, not_present):
        self.check_content("containers", present, not_present)

    def check_images(self, present, not_present):
        self.check_content("images", present, not_present)

    def waitContainer(self, row_id, auth, name="", image="", cmd="", owner="", state=[], pod="no-pod"):
        """Check the container with row_name has the expected values
            "image" can be substring, "state" might be string or array of possible states, other are
            checked for exact match.
        """
        sel = "#containers-containers #table-" + pod + " tbody tr[data-row-id={0}{1}]".format(row_id, auth).lower()
        b = self.browser
        if name:
            b.wait_text(sel + " .container-name", name)
        if image:
            b.wait_in_text(sel + " .container-block small:nth-child(2)", image)
        if cmd:
            b.wait_text(sel + " .container-block small:last-child", cmd)
        if owner:
            b.wait_text(sel + " td[data-label=Owner]", owner)
        if state:
            if not isinstance(state, list):
                state = [state]
            b.wait(lambda: b.text(sel + " td[data-label=State]") in state)

    def filter_containers(self, value):
        """Use dropdown menu in the header to filter containers"""
        b = self.browser
        b.set_val("#containers-containers-filter", value)

    def confirm_modal(self, text):
        """Wait for the pop up window and click the button with text"""
        b = self.browser
        b.click(".pf-c-modal-box footer button:contains(%s)" % text)
        b.wait_not_present(".pf-c-modal-box footer button:contains(%s)" % text)


if __name__ == '__main__':
    testlib.test_main()
