# SPDX-FileCopyrightText: 2023-2025 Univention GmbH
# SPDX-License-Identifier: AGPL-3.0-only
from __future__ import annotations
import subprocess
import time
from pathlib import Path
from typing import TYPE_CHECKING
import pytest
from playwright.sync_api import Browser, BrowserContext, BrowserType, Page, expect
from univention.config_registry import handler_set, handler_unset
from univention.testing import udm as _udm
from univention.testing.browser import logger
from univention.testing.browser.generic_udm_module import UserModule
from univention.testing.browser.ldap_directory import LDAPDirectory
from univention.testing.browser.lib import UMCBrowserTest
from univention.testing.browser.selfservice import SelfService
from univention.testing.browser.sidemenu import SideMenuLicense, SideMenuUser
from univention.testing.browser.suggestion import AppCenterCacheTest
from univention.testing.browser.univentionconfigurationregistry import UniventionConfigurationRegistry
from . import check_for_backtrace, save_screenshot, save_trace
if TYPE_CHECKING:
from collections.abc import Generator, Iterator
[docs]
@pytest.fixture(scope='session', autouse=True)
def suppress_notifications():
handler_set(['umc/web/hooks/suppress_umc_notifications=suppress_umc_notifications'])
yield
handler_unset(['umc/web/hooks/suppress_umc_notifications'])
phase_report_key = pytest.StashKey[dict[str, pytest.CollectReport]]()
[docs]
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
outcome = yield
rep = outcome.get_result()
item.stash.setdefault(phase_report_key, {})[rep.when] = rep
[docs]
@pytest.fixture(scope='session')
def ucs_browser_context_args(browser_context_args):
return {
**browser_context_args,
'ignore_https_errors': True,
}
[docs]
@pytest.fixture(scope='session')
def ucs_browser_type_launch_args(browser_type_launch_args):
return {
**browser_type_launch_args,
'executable_path': '/usr/bin/chromium',
'args': [
'--disable-gpu',
],
}
[docs]
@pytest.fixture(scope='session')
def browser_context_args(browser_context_args):
return {
**browser_context_args,
'ignore_https_errors': True,
}
[docs]
@pytest.fixture(scope='session')
def browser_type_launch_args(browser_type_launch_args):
return {
**browser_type_launch_args,
'executable_path': '/usr/bin/chromium',
'args': [
'--disable-gpu',
],
}
[docs]
@pytest.fixture(scope='module')
def udm_module_scope() -> Iterator[_udm.UCSTestUDM]:
"""Auto-reverting UDM wrapper."""
with _udm.UCSTestUDM() as udm:
yield udm
[docs]
@pytest.fixture
def ucr_module(umc_browser_test: UMCBrowserTest):
return UniventionConfigurationRegistry(umc_browser_test)
[docs]
@pytest.fixture
def user_module(umc_browser_test: UMCBrowserTest):
return UserModule(umc_browser_test)
[docs]
@pytest.fixture
def self_service(umc_browser_test: UMCBrowserTest) -> SelfService:
return SelfService(umc_browser_test)
[docs]
@pytest.fixture
def ldap_directory(umc_browser_test: UMCBrowserTest) -> LDAPDirectory:
return LDAPDirectory(umc_browser_test)
[docs]
def kill_univention_management_console_module():
try:
subprocess.run(
['pkill', '-f', '/usr/sbin/univention-management-console-module'],
check=True,
)
except subprocess.CalledProcessError as e:
if e.returncode != 1:
logger.exception('failed killing module processes')
raise
[docs]
@pytest.fixture(scope='module')
def kill_module_processes_module():
logger.info('killing module processes')
kill_univention_management_console_module()
[docs]
@pytest.fixture
def kill_module_processes():
logger.info('killing module processes')
kill_univention_management_console_module()
[docs]
def setup_browser_context(context, start_tracing=True):
context.set_default_timeout(30 * 1000)
expect.set_options(timeout=30 * 1000)
if start_tracing:
context.tracing.start(screenshots=True, snapshots=True, sources=True)
page = context.new_page()
return page
[docs]
@pytest.fixture(scope='module')
def context_module_scope(
browser_type: BrowserType,
ucs_browser_type_launch_args: dict,
ucs_browser_context_args: dict,
):
browser = browser_type.launch(**ucs_browser_type_launch_args)
return browser.new_context(**ucs_browser_context_args)
[docs]
@pytest.fixture(scope='module')
def umc_browser_test_module(
context_module_scope: BrowserContext,
kill_module_processes_module,
) -> UMCBrowserTest:
page = setup_browser_context(context_module_scope)
tester = UMCBrowserTest(page)
return tester
[docs]
@pytest.fixture
def umc_browser_test(
browser_type: BrowserType,
ucs_browser_type_launch_args: dict,
ucs_browser_context_args: dict,
request: pytest.FixtureRequest,
kill_module_processes,
ucr,
) -> Generator[UMCBrowserTest, None, None]:
browser = browser_type.launch(**ucs_browser_type_launch_args)
context = browser.new_context(**ucs_browser_context_args)
page = setup_browser_context(context)
tester = UMCBrowserTest(page)
yield tester
teardown_umc_browser_test(request, ucr, page, context, browser)
[docs]
def teardown_umc_browser_test(
request: pytest.FixtureRequest,
ucr,
page: Page | list[Page],
context: BrowserContext,
browser: Browser,
):
try:
report = request.node.stash[phase_report_key]
except KeyError:
logger.warning(
'phase_report_key has not been found in node stash. Skipping trace saving and backtrace checking.',
)
return
if not isinstance(page, list):
page = [page]
try:
if 'call' in report and report['call'].failed:
failure_timestamp = time.time_ns()
save_trace(context, request.node.name, Path('browser').resolve(), ucr, timestamp=failure_timestamp)
for i, p in enumerate(page):
save_screenshot(p, request.node.name, Path('browser'), ucr, i, timestamp=failure_timestamp)
check_for_backtrace(p, page_index=i)
else:
context.tracing.stop()
finally:
context.close()
# close browser instance if requested,
# useful for parameterized test
markers = list(request.node.iter_markers('close_browser'))
if len(markers) == 1:
browser.close()
[docs]
@pytest.fixture
def app_center_cache():
app_center_cache = AppCenterCacheTest()
yield app_center_cache
app_center_cache.restore()