diff --git a/backend/autometabuilder/data/server.py b/backend/autometabuilder/data/server.py
new file mode 100644
index 0000000..8eb686d
--- /dev/null
+++ b/backend/autometabuilder/data/server.py
@@ -0,0 +1,184 @@
+"""Server module for UI tests - creates a minimal Flask app for testing."""
+import os
+import logging
+from flask import Flask, send_from_directory, jsonify
+from asgiref.wsgi import WsgiToAsgi
+from autometabuilder.workflow.plugin_registry import PluginRegistry, load_plugin_map
+from autometabuilder.workflow.runtime import WorkflowRuntime
+
+# Configure logging
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+
+class _SimpleLogger:
+ """Minimal logger for plugin execution."""
+ def info(self, *args, **kwargs):
+ logger.info(*args, **kwargs)
+
+ def debug(self, *args, **kwargs):
+ logger.debug(*args, **kwargs)
+
+ def error(self, *args, **kwargs):
+ logger.error(*args, **kwargs)
+
+
+def create_app():
+ """Create and configure the Flask application for testing."""
+ # Create Flask app
+ app = Flask(__name__, static_folder=None)
+ app.config['JSON_SORT_KEYS'] = False
+
+ # Create runtime for plugin execution
+ runtime = WorkflowRuntime(
+ context={},
+ store={},
+ tool_runner=None,
+ logger=_SimpleLogger()
+ )
+
+ # Store Flask app in runtime context
+ runtime.context["flask_app"] = app
+
+ # Load plugins
+ plugin_map = load_plugin_map()
+ registry = PluginRegistry(plugin_map)
+
+ # Check if we're in mock mode (for testing)
+ mock_mode = os.environ.get("MOCK_WEB_UI", "false").lower() == "true"
+
+ if mock_mode:
+ # Create minimal mock routes for testing
+ @app.route('/')
+ def index():
+ # Return a minimal HTML page for testing
+ return '''
+
+
AutoMetabuilder
+
+
+
Dashboard
+
+
Ready
+
+
+
+
+
+
+
+
+
+
+
+
+''', 200
+
+ @app.route('/')
+ def serve_static(path):
+ # Redirect to index for all routes in mock mode
+ return index()
+
+ @app.route('/api/context')
+ def api_context():
+ from autometabuilder.utils import load_metadata
+ return jsonify({
+ "logs": [],
+ "env_vars": {},
+ "translations": ["en"],
+ "metadata": load_metadata(),
+ "navigation": [],
+ "prompt_content": "",
+ "workflow_content": "",
+ "workflow_packages": [],
+ "workflow_packages_raw": [],
+ "messages": {},
+ "lang": os.environ.get("APP_LANG", "en"),
+ "status": {
+ "is_running": False,
+ "mvp_reached": False,
+ "config": {}
+ }
+ }), 200
+
+ @app.route('/api/status')
+ def api_status():
+ return jsonify({
+ "is_running": False,
+ "mvp_reached": False,
+ "config": {}
+ }), 200
+
+ @app.route('/api/run', methods=['POST'])
+ def api_run():
+ return jsonify({"success": True, "message": "Mock run"}), 200
+
+ else:
+ # Create routes using workflow plugins
+ try:
+ # Create context routes
+ context_result = registry.get("web.route_context")(runtime, {})
+ context_bp = context_result.get("result")
+ if context_bp:
+ app.register_blueprint(context_bp)
+
+ # Create run routes
+ run_result = registry.get("web.route_run")(runtime, {})
+ run_bp = run_result.get("result")
+ if run_bp:
+ app.register_blueprint(run_bp)
+
+ # Create prompt routes
+ prompt_result = registry.get("web.route_prompt")(runtime, {})
+ prompt_bp = prompt_result.get("result")
+ if prompt_bp:
+ app.register_blueprint(prompt_bp)
+
+ # Create settings routes
+ settings_result = registry.get("web.route_settings")(runtime, {})
+ settings_bp = settings_result.get("result")
+ if settings_bp:
+ app.register_blueprint(settings_bp)
+
+ # Create translations routes
+ translations_result = registry.get("web.route_translations")(runtime, {})
+ translations_bp = translations_result.get("result")
+ if translations_bp:
+ app.register_blueprint(translations_bp)
+
+ # Create navigation routes
+ navigation_result = registry.get("web.route_navigation")(runtime, {})
+ navigation_bp = navigation_result.get("result")
+ if navigation_bp:
+ app.register_blueprint(navigation_bp)
+
+ # Serve static files
+ from pathlib import Path
+ frontend_dist = Path(__file__).resolve().parent.parent.parent.parent / 'frontend' / 'dist'
+
+ @app.route('/')
+ def index():
+ return send_from_directory(frontend_dist, 'index.html')
+
+ @app.route('/')
+ def serve_static(path):
+ try:
+ return send_from_directory(frontend_dist, path)
+ except (FileNotFoundError, OSError):
+ # Fallback to index.html for SPA routing
+ return send_from_directory(frontend_dist, 'index.html')
+
+ except Exception as e:
+ logger.error(f"Failed to register routes: {e}")
+ # Fall back to basic routes
+ @app.route('/')
+ def index():
+ return "AutoMetabuilder Server", 200
+
+ return app
+
+
+# Create the app instance for imports
+flask_app = create_app()
+# Wrap Flask app for ASGI compatibility (needed for uvicorn)
+app = WsgiToAsgi(flask_app)
diff --git a/backend/tests/ui/__init__.py b/backend/tests/ui/__init__.py
new file mode 100644
index 0000000..be16b8c
--- /dev/null
+++ b/backend/tests/ui/__init__.py
@@ -0,0 +1 @@
+"""UI tests package."""
diff --git a/backend/tests/ui/test_ui_translations.py b/backend/tests/ui/test_ui_translations.py
index 3ece659..fd34f79 100644
--- a/backend/tests/ui/test_ui_translations.py
+++ b/backend/tests/ui/test_ui_translations.py
@@ -1,6 +1,6 @@
from playwright.sync_api import Page, expect
-from autometabuilder.metadata_loader import load_metadata
+from autometabuilder.utils import load_metadata
from .helpers import wait_for_nav
diff --git a/poetry.lock b/poetry.lock
index 080b8c5..223d526 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -539,7 +539,7 @@ version = "8.3.1"
description = "Composable command line interface toolkit"
optional = false
python-versions = ">=3.10"
-groups = ["main"]
+groups = ["main", "dev"]
files = [
{file = "click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6"},
{file = "click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a"},
@@ -559,7 +559,7 @@ files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
-markers = {main = "platform_system == \"Windows\"", dev = "sys_platform == \"win32\""}
+markers = {main = "platform_system == \"Windows\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\""}
[[package]]
name = "cryptography"
@@ -924,7 +924,7 @@ version = "0.16.0"
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
optional = false
python-versions = ">=3.8"
-groups = ["main"]
+groups = ["main", "dev"]
files = [
{file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"},
{file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"},
@@ -2451,6 +2451,26 @@ h2 = ["h2 (>=4,<5)"]
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""]
+[[package]]
+name = "uvicorn"
+version = "0.40.0"
+description = "The lightning-fast ASGI server."
+optional = false
+python-versions = ">=3.10"
+groups = ["dev"]
+files = [
+ {file = "uvicorn-0.40.0-py3-none-any.whl", hash = "sha256:c6c8f55bc8bf13eb6fa9ff87ad62308bbbc33d0b67f84293151efe87e0d5f2ee"},
+ {file = "uvicorn-0.40.0.tar.gz", hash = "sha256:839676675e87e73694518b5574fd0f24c9d97b46bea16df7b8c05ea1a51071ea"},
+]
+
+[package.dependencies]
+click = ">=7.0"
+h11 = ">=0.8"
+typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""}
+
+[package.extras]
+standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.13)", "websockets (>=10.4)"]
+
[[package]]
name = "werkzeug"
version = "3.1.5"
diff --git a/pyproject.toml b/pyproject.toml
index 1ddaed7..799a0cc 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -30,5 +30,6 @@ validate-workflows = "autometabuilder.tools.validate_workflows:main"
[dependency-groups]
dev = [
"playwright (>=1.57.0,<2.0.0)",
- "pytest-playwright (>=0.7.2,<0.8.0)"
+ "pytest-playwright (>=0.7.2,<0.8.0)",
+ "uvicorn (>=0.27.0,<1.0.0)"
]