From 36dc174bfedf212532658b84f8ab64971863d292 Mon Sep 17 00:00:00 2001 From: appel_c Date: Wed, 16 Apr 2025 17:27:41 +0200 Subject: [PATCH] test: add function scoped rpc_widgets e2e test; closes #510 --- tests/end-2-end/conftest.py | 3 +- .../end-2-end/test_plotting_framework_e2e.py | 7 +- tests/end-2-end/test_rpc_widgets_e2e.py | 88 +++++++++++++++++++ 3 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 tests/end-2-end/test_rpc_widgets_e2e.py diff --git a/tests/end-2-end/conftest.py b/tests/end-2-end/conftest.py index 9866cf98..6bc56ffe 100644 --- a/tests/end-2-end/conftest.py +++ b/tests/end-2-end/conftest.py @@ -29,7 +29,7 @@ def gui_id(): @pytest.fixture -def connected_client_gui_obj(gui_id, bec_client_lib): +def connected_client_gui_obj(qtbot, gui_id, bec_client_lib): """ Fixture to create a new BECGuiClient object and start a server in the background. @@ -38,6 +38,7 @@ def connected_client_gui_obj(gui_id, bec_client_lib): gui = BECGuiClient(gui_id=gui_id) try: gui.start(wait=True) + qtbot.waitUntil(lambda: hasattr(gui, "bec"), timeout=5000) yield gui finally: gui.kill_server() diff --git a/tests/end-2-end/test_plotting_framework_e2e.py b/tests/end-2-end/test_plotting_framework_e2e.py index c09ef82d..6d2fc2dd 100644 --- a/tests/end-2-end/test_plotting_framework_e2e.py +++ b/tests/end-2-end/test_plotting_framework_e2e.py @@ -110,6 +110,7 @@ def test_rpc_waveform_scan(qtbot, bec_client_lib, connected_client_gui_obj): assert plt_data["bpm4d-bpm4d"]["y"] == last_scan_data["bpm4d"]["bpm4d"].val +@pytest.mark.timeout(100) def test_async_plotting(qtbot, bec_client_lib, connected_client_gui_obj): gui = connected_client_gui_obj dock = gui.bec @@ -122,12 +123,12 @@ def test_async_plotting(qtbot, bec_client_lib, connected_client_gui_obj): # Test add dev.waveform.sim.select_model("GaussianModel") dev.waveform.sim.params = {"amplitude": 1000, "center": 4000, "sigma": 300} - dev.waveform.async_update.put("add") - dev.waveform.waveform_shape.put(10000) + dev.waveform.async_update.set("add").wait() + dev.waveform.waveform_shape.set(1000).wait() wf = dock.new("wf_dock").new("Waveform") curve = wf.plot(y_name="waveform") - status = scans.line_scan(dev.samx, -5, 5, steps=10, exp_time=0.05, relative=False) + status = scans.line_scan(dev.samx, -5, 5, steps=5, exp_time=0.05, relative=False) status.wait() # Wait for the scan to finish and the data to be available in history diff --git a/tests/end-2-end/test_rpc_widgets_e2e.py b/tests/end-2-end/test_rpc_widgets_e2e.py new file mode 100644 index 00000000..712f29c0 --- /dev/null +++ b/tests/end-2-end/test_rpc_widgets_e2e.py @@ -0,0 +1,88 @@ +from typing import TYPE_CHECKING + +import pytest + +from bec_widgets.cli.rpc.rpc_base import RPCBase, RPCReference + +# pylint: disable=protected-access +# pylint: disable=used-before-assignment + + +def wait_for_namespace_change( + qtbot, + gui: RPCBase, + parent_widget: RPCBase | RPCReference, + object_name: str, + widget_gui_id: str, + timeout: int = 10000, + exists: bool = True, +): + """Utility method to wait for the namespace to be created in the widget.""" + # GUI object is not registered in the registry (yet) + if parent_widget is gui: + + def check_reference_registered(): + # Check that the widget is in ipython registry + obj = gui._ipython_registry.get(widget_gui_id, None) + if obj is None: + if not exists: + return True + return False + # _rpc_references do not exist on BECGuiClient class somehow.. + + else: + + def check_reference_registered(): + obj = gui._ipython_registry.get(widget_gui_id, None) + if obj is None: + if not exists: + return True + return False + ref = parent_widget._rpc_references.get(widget_gui_id, None) + if exists: + return ref is not None + return ref is None + + try: + qtbot.waitUntil(check_reference_registered, timeout=timeout) + except Exception as e: + raise RuntimeError( + f"Timeout waiting for {parent_widget.object_name}.{object_name} to be created." + ) from e + + +def create_widget( + qtbot, gui: RPCBase, dock_area: RPCReference, widget_cls_name: str +) -> tuple[RPCReference, RPCReference, RPCReference]: + """Utility method to create a widget and wait for the namespaces to be created.""" + dock = dock_area.new(widget=widget_cls_name) + wait_for_namespace_change(qtbot, gui, dock_area, dock.object_name, dock._gui_id) + widget = dock.element_list[-1] + wait_for_namespace_change(qtbot, gui, dock, widget.object_name, widget._gui_id) + return dock, widget + + +@pytest.mark.timeout(100) +def test_available_widgets(qtbot, connected_client_gui_obj): + """This test checks that all widgets that are available via gui.available_widgets can be created and removed.""" + gui = connected_client_gui_obj + dock_area = gui.bec + for object_name in gui.available_widgets.__dict__: + # Skip private attributes + if object_name.startswith("_"): + continue + # Skip VSCode widget as Code server is not available in the Docker image + if object_name == "VSCodeEditor": + continue + # Create widget the widget + dock, widget = create_widget( + qtbot, gui, dock_area, getattr(gui.available_widgets, object_name) + ) + # The create_widget method already waits for the widget to be created + # and added to the ipython registry. We can here assert if the dock_area + # has the dock and the widget + assert gui._ipython_registry.get(widget._gui_id, None) is not None + assert hasattr(dock_area, dock.object_name) + assert hasattr(dock, widget.object_name) + # Now we remove the widget again + dock_area.delete(dock.object_name)