Source code for univention.testing.browser.process_overview
#!/usr/bin/python3
# SPDX-FileCopyrightText: 2023-2025 Univention GmbH
# SPDX-License-Identifier: AGPL-3.0-only
import re
import subprocess
import time
from typing import Literal
from playwright.sync_api import Page, expect
from univention.config_registry import ucr
from univention.lib.i18n import Translation
from univention.testing.browser.lib import UMCBrowserTest
_ = Translation('ucs-test-framework').translate
available_categories = Literal['All', 'User', 'PID', 'Command']
[docs]
class ProcessOverview:
def __init__(self, tester: UMCBrowserTest):
self.tester: UMCBrowserTest = tester
self.page: Page = tester.page
self.module_name = _('Process overview')
self.grid_load_url = re.compile('.*univention/command/top/query.*')
[docs]
def navigate(self, username=ucr.get('tests/domainadmin/username', 'Administrator'), password=ucr.get('tests/domainadmin/pwd', 'univention')):
self.tester.login(username, password)
self.tester.open_module(self.module_name, self.grid_load_url)
[docs]
def search(self, category: available_categories, text: str):
"""
Run a search
:param category: the category to search for. NOTICE: this argument gets translated in the function
:param text: the text to search for
"""
# usually caller is responsible for translating but here it makes more sense to translate here
# so we can have nice typing
category = _(category)
text_boxes = self.page.get_by_role('textbox')
category_textbox = self.page.get_by_label('Category')
search_textbox = text_boxes.nth(2)
category_textbox.click()
expect(category_textbox).to_be_enabled()
category_textbox.clear()
category_textbox.fill(category)
search_textbox.fill(text)
with self.page.expect_response(self.grid_load_url):
search_textbox.press('Enter')
[docs]
def ensure_process(self, process: subprocess.Popen, category: available_categories):
"""
Ensures that a process is running, either by PID or Command
:param process: the process to search for. If searching by name the process args are joined together
if searching by pid, process.pid is used
:param category: the category to search for the process by. Currently only PID and Command are supported
"""
process_name = ' '.join(process.args)
if category == 'PID':
self.search('PID', str(process.pid))
else:
self.search('Command', process_name)
[docs]
def kill_process(self, process: subprocess.Popen, force: bool):
"""
Kills the process given by process and ensures that is was actually killed
:param process: the process to kill
:param force: if false sends SIGTERM to the process by pressing the Terminate button
if true sends SIGKILL to the process by pressing the 'Force termination' button
"""
process_pid = str(process.pid)
self.search('PID', process_pid)
self.page.get_by_role('gridcell', name=str(process_pid), exact=True).click()
button = self.page.get_by_role('button', name=f"{_('Force termination') if force else _('Terminate')}")
button.click()
# without this sleep the button sometimes doesn't get clicked correctly
time.sleep(1)
confirmation_dialog = self.page.get_by_role('dialog')
confirmation_dialog.get_by_role('button', name='Ok').click()
expect(confirmation_dialog).to_be_hidden()
with self.page.expect_response(self.grid_load_url):
pass
expected_return_code = -9 if force else -15
return_code = process.poll()
assert expected_return_code == return_code, f'Expected return code to be {expected_return_code} but got {return_code}'
self.search('PID', process_pid)
cell = self.page.get_by_role('gridcell', name=process_pid)
expect(cell).to_be_hidden()