Source code for univention.testing.pytest_univention_playwright.fixtures

# 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 side_menu_license(umc_browser_test: UMCBrowserTest): return SideMenuLicense(umc_browser_test)
[docs] @pytest.fixture def side_menu_user(umc_browser_test: UMCBrowserTest): return SideMenuUser(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()