From ff84cc1e9ab62f859960d29bddf5693eeee43371 Mon Sep 17 00:00:00 2001 From: Rodja Trappe Date: Wed, 15 Oct 2025 17:50:20 +0200 Subject: [PATCH] ensure proper cleanup and error handling in test fixtures (screen & user) - Updated `screen` and `user` fixtures to use `try...finally` for resource cleanup, ensuring environment variables are reset and servers are stopped even if an error occurs. - Improved error logging by checking for unexpected ERROR logs after yielding the fixture. - reactivate tests from `test_main_file_marker.py` (which failed before) This change enhances the reliability of the testing framework by ensuring that resources are properly managed and that errors are logged appropriately. --- nicegui/testing/screen_plugin.py | 25 +++++++++-------- nicegui/testing/user_plugin.py | 47 +++++++++++++++++--------------- tests/test_main_file_marker.py | 2 -- 3 files changed, 39 insertions(+), 35 deletions(-) diff --git a/nicegui/testing/screen_plugin.py b/nicegui/testing/screen_plugin.py index c9a43fcf6..41e10cdc3 100644 --- a/nicegui/testing/screen_plugin.py +++ b/nicegui/testing/screen_plugin.py @@ -82,14 +82,17 @@ def screen(nicegui_reset_globals, # noqa: F811, pylint: disable=unused-argument """Create a new SeleniumScreen fixture.""" os.environ['NICEGUI_SCREEN_TEST_PORT'] = str(Screen.PORT) screen_ = Screen(nicegui_driver, caplog, request) - yield screen_ - os.environ.pop('NICEGUI_SCREEN_TEST_PORT', None) - logs = [record for record in screen_.caplog.get_records('call') if record.levelname == 'ERROR'] - if screen_.is_open: - test_failed = hasattr(request.node, 'rep_call') and request.node.rep_call.failed - screen_.shot(request.node.name, failed=test_failed or bool(logs)) - screen_.stop_server() - if DOWNLOAD_DIR.exists(): - shutil.rmtree(DOWNLOAD_DIR) - if logs: - pytest.fail('There were unexpected ERROR logs.', pytrace=False) + try: + yield screen_ + + logs = [record for record in screen_.caplog.get_records('call') if record.levelname == 'ERROR'] + if screen_.is_open: + test_failed = hasattr(request.node, 'rep_call') and request.node.rep_call.failed + screen_.shot(request.node.name, failed=test_failed or bool(logs)) + if logs: + pytest.fail('There were unexpected ERROR logs.', pytrace=False) + finally: + os.environ.pop('NICEGUI_SCREEN_TEST_PORT', None) + screen_.stop_server() + if DOWNLOAD_DIR.exists(): + shutil.rmtree(DOWNLOAD_DIR) diff --git a/nicegui/testing/user_plugin.py b/nicegui/testing/user_plugin.py index e92f86055..7b6cfb38f 100644 --- a/nicegui/testing/user_plugin.py +++ b/nicegui/testing/user_plugin.py @@ -30,33 +30,36 @@ async def user(nicegui_reset_globals, # noqa: F811, pylint: disable=unused-argu ) -> AsyncGenerator[User, None]: """Create a new user fixture.""" os.environ['NICEGUI_USER_SIMULATION'] = 'true' - main_path = get_path_to_main_file(request) - if main_path is None: - prepare_simulation() - ui.run(storage_secret='simulated secret') - else: - runpy.run_path(str(main_path), run_name='__main__') + try: + main_path = get_path_to_main_file(request) + if main_path is None: + prepare_simulation() + ui.run(storage_secret='simulated secret') + else: + runpy.run_path(str(main_path), run_name='__main__') - async with core.app.router.lifespan_context(core.app): - async with httpx.AsyncClient(transport=httpx.ASGITransport(core.app), base_url='http://test') as client: - yield User(client) + async with core.app.router.lifespan_context(core.app): + async with httpx.AsyncClient(transport=httpx.ASGITransport(core.app), base_url='http://test') as client: + yield User(client) - os.environ.pop('NICEGUI_USER_SIMULATION', None) - ui.navigate = Navigate() - ui.notify = notify - ui.download = download - - logs = [record for record in caplog.get_records('call') if record.levelname == 'ERROR'] - if logs: - pytest.fail('There were unexpected ERROR logs.', pytrace=False) + logs = [record for record in caplog.get_records('call') if record.levelname == 'ERROR'] + if logs: + pytest.fail('There were unexpected ERROR logs.', pytrace=False) + finally: + os.environ.pop('NICEGUI_USER_SIMULATION', None) + ui.navigate = Navigate() + ui.notify = notify + ui.download = download @pytest.fixture async def create_user(user: User) -> AsyncGenerator[Callable[[], User], None]: # pylint: disable=unused-argument """Create a fixture for building new users.""" prepare_simulation() - async with core.app.router.lifespan_context(core.app): - yield lambda: User(httpx.AsyncClient(transport=httpx.ASGITransport(core.app), base_url='http://test')) - ui.navigate = Navigate() - ui.notify = notify - ui.download = download + try: + async with core.app.router.lifespan_context(core.app): + yield lambda: User(httpx.AsyncClient(transport=httpx.ASGITransport(core.app), base_url='http://test')) + finally: + ui.navigate = Navigate() + ui.notify = notify + ui.download = download diff --git a/tests/test_main_file_marker.py b/tests/test_main_file_marker.py index be7e0dac8..0e9e12cdb 100644 --- a/tests/test_main_file_marker.py +++ b/tests/test_main_file_marker.py @@ -3,14 +3,12 @@ from nicegui.testing import Screen, User -@pytest.mark.skip('This test breaks all following tests') @pytest.mark.xfail(raises=FileNotFoundError, strict=True) @pytest.mark.nicegui_main_file('non_existent_file.py') async def test_marker_injects_main_file_for_user_plugin(user: User): await user.open('/') -@pytest.mark.skip('This test breaks all following tests') @pytest.mark.xfail(raises=FileNotFoundError, strict=True) @pytest.mark.nicegui_main_file('non_existent_file.py') def test_marker_injects_main_file_for_screen_plugin(screen: Screen):