diff --git a/setup.py b/setup.py index 6f36dadc..2e7e0cbc 100644 --- a/setup.py +++ b/setup.py @@ -77,6 +77,7 @@ def get_long_description() -> str: "TablePlotter = webviz_config.generic_plugins._table_plotter:TablePlotter", "PivotTable = webviz_config.generic_plugins._pivot_table:PivotTable", ], + "pytest11": ["webviz = webviz_config.testing._plugin"], }, install_requires=[ "bleach[css]>=5", diff --git a/tests/test_example_wlf_plugin.py b/tests/test_example_wlf_plugin.py new file mode 100644 index 00000000..cf0f5660 --- /dev/null +++ b/tests/test_example_wlf_plugin.py @@ -0,0 +1,22 @@ +import webviz_config +from webviz_config.generic_plugins._example_wlf_plugin import ExampleWlfPlugin2 + + +def test_example_wlf_plugin2( + _webviz_duo: webviz_config.testing._composite.WebvizComposite, +) -> None: + + plugin = ExampleWlfPlugin2(title="hello") + + _webviz_duo.start_server(plugin) + + _webviz_duo.toggle_webviz_drawer() + + component_id = _webviz_duo.view_settings_group_unique_component_id( + view_id="plot-view", + settings_group_id="plot-settings", + component_unique_id="coordinates-selector", + ) + + _webviz_duo.wait_for_contains_text(component_id, "x - y") + assert _webviz_duo.get_logs() == [] diff --git a/webviz_config/testing/__init__.py b/webviz_config/testing/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/webviz_config/testing/_composite.py b/webviz_config/testing/_composite.py new file mode 100644 index 00000000..d7e30d11 --- /dev/null +++ b/webviz_config/testing/_composite.py @@ -0,0 +1,96 @@ +from typing import Any + +import pathlib + +from dash.testing.composite import Browser +import dash +import webviz_core_components as wcc + +from webviz_config.common_cache import CACHE +from webviz_config.themes import default_theme +from webviz_config.webviz_factory_registry import WEBVIZ_FACTORY_REGISTRY +from webviz_config.webviz_instance_info import WEBVIZ_INSTANCE_INFO, WebvizRunMode +from webviz_config import WebvizPluginABC + +from ._webviz_ids import WebvizIds + + +class WebvizComposite(Browser): + def __init__(self, server: Any, **kwargs: Any) -> None: + super().__init__(**kwargs) + self.app = dash.Dash(__name__) + self.server = server + self.plugin: WebvizPluginABC + self.init_app() + + def init_app(self) -> None: + WEBVIZ_INSTANCE_INFO.initialize( + dash_app=self.app, + run_mode=WebvizRunMode.NON_PORTABLE, + theme=default_theme, + storage_folder=pathlib.Path(__file__).resolve().parent, + ) + try: + WEBVIZ_FACTORY_REGISTRY.initialize(None) + except RuntimeError: + pass + + self.app.css.config.serve_locally = True + self.app.scripts.config.serve_locally = True + self.app.config.suppress_callback_exceptions = True + CACHE.init_app(self.app.server) + + def start_server(self, plugin: WebvizPluginABC, **kwargs: Any) -> None: + """Start the local server with app.""" + + self.app.layout = dash.html.Div( + className=WebvizIds.LAYOUT_WRAPPER, + children=[ + wcc.WebvizContentManager( + id=WebvizIds.CONTENT_MANAGER, + children=[ + wcc.WebvizSettingsDrawer( + id=WebvizIds.SETTINGS_DRAWER, + children=plugin.get_all_settings(), + ), + wcc.WebvizPluginsWrapper( + id=WebvizIds.PLUGINS_WRAPPER, + children=plugin.plugin_layout(), + ), + ], + ), + ], + ) + self.plugin = plugin + # start server with app and pass Dash arguments + self.server(self.app, **kwargs) + + # set the default server_url, it implicitly call wait_for_page + self.server_url = self.server.url + + def toggle_webviz_drawer(self) -> None: + """Open the plugin settings drawer""" + self.wait_for_element(WebvizIds.SETTINGS_DRAWER_TOGGLE_OPEN).click() + + def shared_settings_group_unique_component_id( + self, settings_group_id: str, component_unique_id: str + ) -> str: + """Returns the element id of a component in a shared settings group""" + unique_id = ( + self.plugin.shared_settings_group(settings_group_id) + .component_unique_id(component_unique_id) + .to_string() + ) + return f"#{unique_id}" + + def view_settings_group_unique_component_id( + self, view_id: str, settings_group_id: str, component_unique_id: str + ) -> str: + """Returns the element id of a component in a view settings group""" + unique_id = ( + self.plugin.view(view_id) + .settings_group(settings_group_id) + .component_unique_id(component_unique_id) + .to_string() + ) + return f"#{unique_id}" diff --git a/webviz_config/testing/_plugin.py b/webviz_config/testing/_plugin.py new file mode 100644 index 00000000..71269ce2 --- /dev/null +++ b/webviz_config/testing/_plugin.py @@ -0,0 +1,35 @@ +from typing import Any, Generator + +import pytest + +# pylint: disable=too-few-public-methods +class MissingWebvizTesting: + def __init__(self, **kwargs: Any) -> None: + raise RuntimeError( + "webviz_config[tests] was not installed. " + "Please install to use the webviz testing fixtures." + ) + + +try: + from webviz_config.testing._composite import WebvizComposite +except ImportError: + + WebvizComposite = MissingWebvizTesting # type: ignore + + +@pytest.fixture +def _webviz_duo(request: Any, dash_thread_server: Any, tmpdir: Any) -> Generator: + with WebvizComposite( + dash_thread_server, + browser=request.config.getoption("webdriver"), + remote=request.config.getoption("remote"), + remote_url=request.config.getoption("remote_url"), + headless=request.config.getoption("headless"), + options=request.config.hook.pytest_setup_options(), + download_path=tmpdir.mkdir("download").strpath, + percy_assets_root=request.config.getoption("percy_assets"), + percy_finalize=request.config.getoption("nopercyfinalize"), + pause=request.config.getoption("pause"), + ) as duo: + yield duo diff --git a/webviz_config/testing/_webviz_ids.py b/webviz_config/testing/_webviz_ids.py new file mode 100644 index 00000000..c7f76335 --- /dev/null +++ b/webviz_config/testing/_webviz_ids.py @@ -0,0 +1,9 @@ +from enum import Enum + + +class WebvizIds(str, Enum): + LAYOUT_WRAPPER = "layoutWrapper" + CONTENT_MANAGER = "webviz-content-manager" + SETTINGS_DRAWER = "settings-drawer" + PLUGINS_WRAPPER = "plugins-wrapper" + SETTINGS_DRAWER_TOGGLE_OPEN = ".WebvizSettingsDrawer__ToggleOpen"