From 49e43ebacebb32e8a619624c9f4e5aa6f55e7010 Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Fri, 9 Jan 2026 17:17:01 +0000 Subject: [PATCH] Add session-scope fixture and enhance UI navigation handling. Implement session-level fixture `browser_type_launch_args` for Playwright configuration, improve UI navigation in `test_web_ui.py` by using `expect_navigation`, refine language dropdown checks, and streamline task buttons and JSON toggle functionality in the Workflow section. Integrate new UI components from templates/components/sections.html. --- prompt.yml | 19 +- src/autometabuilder/web/server.py | 35 +- src/autometabuilder/web/static/js/main.js | 23 + .../web/templates/components/sections.html | 516 +++++++++++++++++ src/autometabuilder/web/templates/index.html | 527 +----------------- tests/ui/conftest.py | 7 + tests/ui/test_web_ui.py | 24 +- 7 files changed, 623 insertions(+), 528 deletions(-) create mode 100644 src/autometabuilder/web/templates/components/sections.html diff --git a/prompt.yml b/prompt.yml index c970ea3..d92855e 100644 --- a/prompt.yml +++ b/prompt.yml @@ -1,23 +1,8 @@ messages: - role: system content: >- - You are an expert software engineer following SDLC best practices. - Ensure high code quality through: - 1. Requirement analysis and clear design. - 2. Writing modular, maintainable, and well-documented code. - 3. Preferring declarative design (e.g., looping over JSON specifications) over imperative or hardcoded logic. - 4. Implementing comprehensive unit and integration tests. - 5. Following security best practices and performance optimization. - 6. Adhering to project-specific coding standards and linting rules. - 7. Keeping documentation (README, ROADMAP, etc.) up to date. - 8. Continuous Self-Improvement: You are encouraged to propose and apply enhancements to your own configuration, including `prompt.yml` and `tools.json`, to improve your effectiveness and reasoning. + Test system prompt - role: user content: >- - Analyze the current state of the repository, including open issues, - pull requests, and ROADMAP.md (if it exists). Your primary objective is - to implement the next logical items from the ROADMAP.md while addressing - any urgent issues. If ROADMAP.md is missing, your priority is to - initialize it by analyzing the repository, marking completed tasks, and - listing upcoming work. Ensure all changes are tested, linted, and - properly documented. Update ROADMAP.md as you complete tasks. + Test user prompt model: openai/gpt-4o diff --git a/src/autometabuilder/web/server.py b/src/autometabuilder/web/server.py index 48cd83a..6db6a76 100644 --- a/src/autometabuilder/web/server.py +++ b/src/autometabuilder/web/server.py @@ -67,6 +67,26 @@ def extract_user_content(yaml_content): templates.env.filters['extract_system_content'] = extract_system_content templates.env.filters['extract_user_content'] = extract_user_content +def build_prompt_yaml(system_content, user_content, model): + def indent_block(text): + lines = (text or "").splitlines() + if not lines: + return "" + return "\n ".join(line.rstrip() for line in lines) + + model_value = model or "openai/gpt-4o" + system_block = indent_block(system_content) + user_block = indent_block(user_content) + return f"""messages: + - role: system + content: >- + {system_block} + - role: user + content: >- + {user_block} +model: {model_value} +""" + # Setup static files static_dir = os.path.join(os.path.dirname(__file__), "static") if os.path.exists(static_dir): @@ -242,10 +262,21 @@ async def run_bot( return RedirectResponse(url="/", status_code=303) @app.post("/prompt") -async def update_prompt(content: str = Form(...), username: str = Depends(get_current_user)): +async def update_prompt( + content: str = Form(""), + system_content: str = Form(""), + user_content: str = Form(""), + model: str = Form("openai/gpt-4o"), + prompt_mode: str = Form("builder"), + username: str = Depends(get_current_user) +): prompt_path = os.environ.get("PROMPT_PATH", "prompt.yml") + if prompt_mode == "raw": + prompt_yaml = content + else: + prompt_yaml = build_prompt_yaml(system_content, user_content, model) with open(prompt_path, "w", encoding="utf-8") as f: - f.write(content) + f.write(prompt_yaml) return RedirectResponse(url="/", status_code=303) @app.post("/workflow") diff --git a/src/autometabuilder/web/static/js/main.js b/src/autometabuilder/web/static/js/main.js index a67b044..2bc8014 100644 --- a/src/autometabuilder/web/static/js/main.js +++ b/src/autometabuilder/web/static/js/main.js @@ -154,6 +154,27 @@ const ChoicesManager = { } }; +/* ========================================================================== + Workflow Toggle + ========================================================================== */ +const WorkflowToggle = { + init() { + document.querySelectorAll('[data-workflow-toggle]').forEach(button => { + button.addEventListener('click', () => { + const builder = window.WorkflowBuilder; + if (builder && builder.textarea && typeof builder.toggleRaw === 'function') { + builder.toggleRaw(); + return; + } + const textarea = document.getElementById('workflow-content'); + if (textarea) { + textarea.classList.toggle('d-none'); + } + }); + }); + } +}; + /* ========================================================================== Form Validator ========================================================================== */ @@ -292,6 +313,7 @@ const App = { ThemeManager.init(); NavigationManager.init(); ChoicesManager.init(); + WorkflowToggle.init(); FormValidator.init(); StatusPoller.init(); } @@ -305,6 +327,7 @@ window.App = App; window.ThemeManager = ThemeManager; window.NavigationManager = NavigationManager; window.ChoicesManager = ChoicesManager; +window.WorkflowToggle = WorkflowToggle; window.FormValidator = FormValidator; window.StatusPoller = StatusPoller; window.Toast = Toast; diff --git a/src/autometabuilder/web/templates/components/sections.html b/src/autometabuilder/web/templates/components/sections.html new file mode 100644 index 0000000..78cb936 --- /dev/null +++ b/src/autometabuilder/web/templates/components/sections.html @@ -0,0 +1,516 @@ +{# Page section components composed from atoms, molecules, and organisms. #} +{% from "components/atoms.html" import section_header, helper_text, pill %} +{% from "components/molecules.html" import card, empty_state %} +{% from "components/organisms.html" import callout %} + +{% macro choice_card(id, value, icon_name, title, description, checked=False, extra_html='') -%} + +{%- endmacro %} + +{% macro prompt_chip(target_id, snippet, label) -%} + +{%- endmacro %} + +{% macro prompt_step(step_number, title, description, textarea_id, name, placeholder, value, rows=5, chips=[]) -%} +
+
+ {{ step_number }} {{ title }} +
+ {{ helper_text(description) }} + + {% if chips %} +
+ {% for chip in chips %} + {{ prompt_chip(chip.target, chip.snippet, chip.label) }} + {% endfor %} +
+ {% endif %} +
+{%- endmacro %} + +{% macro dashboard_section() -%} +
+ {{ section_header(t('ui.dashboard.title', 'Dashboard'), t('ui.dashboard.subtitle', 'Control the bot and monitor system activity')) }} + +
+
+
+
+
{{ t('ui.dashboard.bot_control', 'Bot Control') }}
+
+
+
+
+ +
+ {% set iterations_extra %} + + {% endset %} + + {{ choice_card('mode-once', 'once', '1-circle', t('ui.dashboard.run.single.title', 'Single Iteration'), t('ui.dashboard.run.single.desc', 'One full pass through the workflow.'), True) }} + {{ choice_card('mode-iterations', 'iterations', 'arrow-repeat', t('ui.dashboard.run.repeat.title', 'Repeat'), t('ui.dashboard.run.repeat.desc', 'Run a fixed number of cycles.'), False, iterations_extra) }} + {{ choice_card('mode-yolo', 'yolo', 'infinity', t('ui.dashboard.run.yolo.title', 'YOLO'), t('ui.dashboard.run.yolo.desc', 'Keep iterating until you stop it.')) }} +
+
+ +
+ +
+
+ + +
+
+ + +
+
+
+ + +
+
+
+ +
+
+
{{ t('ui.dashboard.status.title', 'Status') }}
+
+
+
+
+ {{ t('ui.dashboard.status.bot_label', 'Bot Status') }} +
+ + {% if is_running %}{{ t('ui.dashboard.status.running', 'Running') }}{% else %}{{ t('ui.dashboard.status.idle', 'Idle') }}{% endif %} +
+
+
+ {{ t('ui.dashboard.status.mvp_label', 'MVP Milestone') }} + + {% if mvp_reached %} + {{ t('ui.dashboard.status.mvp_reached', 'Reached') }} + {% else %} + {{ t('ui.dashboard.status.mvp_progress', 'In Progress') }} + {% endif %} + +
+
+
+
+
+
+
+
+ +
+
+
+
{{ t('ui.dashboard.logs.title', 'Recent Logs') }}
+ +
+
+
{{ logs }}
+
+
+
+
+
+{%- endmacro %} + +{% macro workflow_section() -%} +
+ {{ section_header(t('ui.workflow.title', 'Workflow Builder'), t('ui.workflow.subtitle', "Design the bot's task execution pipeline")) }} + +
+
+
{{ t('ui.workflow.card.title', 'Tasks & Steps') }}
+
+ +
+
+
+
+ +
+
+ +
+ +
+
+
+
+
+{%- endmacro %} + +{% macro prompt_section() -%} +
+ {{ section_header(t('ui.prompt.title', 'Prompt Builder'), t('ui.prompt.subtitle', 'Shape how the assistant thinks, speaks, and decides')) }} + +
+
+ {% call card(t('ui.prompt.card.title', 'Prompt Builder'), "chat-square-text") %} +
+ +
+ {% set system_chips = [ + {'target': 'system-prompt', 'snippet': t('ui.prompt.chip.senior.snippet', 'You are a senior software engineer focused on correctness and clarity.'), 'label': t('ui.prompt.chip.senior.label', 'Senior engineer')}, + {'target': 'system-prompt', 'snippet': t('ui.prompt.chip.ask.snippet', 'Ask clarifying questions before making risky changes.'), 'label': t('ui.prompt.chip.ask.label', 'Ask questions')}, + {'target': 'system-prompt', 'snippet': t('ui.prompt.chip.minimal.snippet', 'Prefer minimal diffs and explain trade-offs.'), 'label': t('ui.prompt.chip.minimal.label', 'Minimal diffs')} + ] %} + {{ prompt_step(1, + t('ui.prompt.step1.title', 'Define the assistant'), + t('ui.prompt.step1.desc', 'Describe who the assistant is and how it should behave.'), + 'system-prompt', + 'system_content', + t('ui.prompt.step1.placeholder', 'You are a senior software engineer who prefers small, safe changes.'), + (prompt_content | extract_system_content), + 6, + system_chips + ) }} + + {% set user_chips = [ + {'target': 'user-prompt', 'snippet': t('ui.prompt.chip.ux.snippet', 'Focus on UX polish, and avoid major refactors.'), 'label': t('ui.prompt.chip.ux.label', 'UX polish')}, + {'target': 'user-prompt', 'snippet': t('ui.prompt.chip.tests.snippet', 'Add tests when possible, but avoid heavy scaffolding.'), 'label': t('ui.prompt.chip.tests.label', 'Add tests')}, + {'target': 'user-prompt', 'snippet': t('ui.prompt.chip.summarize.snippet', 'Summarize changes and suggest next steps.'), 'label': t('ui.prompt.chip.summarize.label', 'Summarize')} + ] %} + {{ prompt_step(2, + t('ui.prompt.step2.title', 'Give the mission'), + t('ui.prompt.step2.desc', 'Explain what the bot should accomplish right now.'), + 'user-prompt', + 'user_content', + t('ui.prompt.step2.placeholder', 'Review the repo, improve the UI, and summarize what changed.'), + (prompt_content | extract_user_content), + 5, + user_chips + ) }} + +
+ + {{ helper_text(t('ui.prompt.model.desc', 'Pick the balance of quality and speed that fits the task.')) }} + +
+
+ +
+ + {{ helper_text(t('ui.prompt.raw.desc', 'Edit the full YAML only if you need fine control.')) }} + +
+ +
+ + +
+
+ {% endcall %} +
+ +
+ {% call card(t('ui.prompt.guidance.title', 'Guidance'), "lightbulb") %} +
+
+ {{ t('ui.prompt.guidance.keep_human.title', 'Keep it human') }} +

{{ t('ui.prompt.guidance.keep_human.desc', 'Write instructions the way you would brief a teammate.') }}

+
+
+ {{ t('ui.prompt.guidance.be_specific.title', 'Be specific') }} +

{{ t('ui.prompt.guidance.be_specific.desc', 'Mention constraints like time, scope, or testing expectations.') }}

+
+
+ {{ t('ui.prompt.guidance.raw.title', 'Use advanced YAML sparingly') }} +

{{ t('ui.prompt.guidance.raw.desc', 'Only switch to raw YAML if you need full control.') }}

+
+
+ {% endcall %} +
+
+
+{%- endmacro %} + +{% macro settings_section() -%} +
+ {{ section_header(t('ui.settings.title', 'Settings'), t('ui.settings.subtitle', 'Configure services, security, and environment preferences')) }} + + {{ callout(t('ui.settings.callout.title', 'Human-friendly settings'), t('ui.settings.callout.body', 'Each field explains what it does. Add descriptions in metadata.json to keep custom settings readable for everyone.'), "info", "info-circle") }} + +
+
+ {% set settings_desc = metadata.get('settings_descriptions', {}) %} + +
+
+
+
{{ t('ui.settings.api_keys', 'API Keys') }}
+
+
+ {% for key in ['GITHUB_TOKEN', 'OPENAI_API_KEY', 'LITELLM_API_KEY'] %} + {% set desc = settings_desc.get(key, {}) %} +
+ +

{{ desc.get('description', '') }}

+ +
+ {% endfor %} +
+
+
+ +
+
+
+
{{ t('ui.settings.configuration', 'Configuration') }}
+
+
+ {% for key in ['GITHUB_REPOSITORY', 'LOG_LEVEL', 'APP_LANG', 'PROMPT_PATH'] %} + {% set desc = settings_desc.get(key, {}) %} +
+ +

{{ desc.get('description', '') }}

+ {% if desc.get('type') == 'select' %} + + {% else %} + + {% endif %} +
+ {% endfor %} +
+
+ +
+
+
{{ t('ui.settings.web_access', 'Web UI Access') }}
+
+
+ {% for key in ['WEB_USER', 'WEB_PASSWORD'] %} + {% set desc = settings_desc.get(key, {}) %} +
+ +

{{ desc.get('description', '') }}

+ +
+ {% endfor %} +
+
+
+
+ +
+
+
{{ t('ui.settings.other', 'Other Settings') }}
+
+
+
+ {% set known_keys = ['GITHUB_TOKEN', 'OPENAI_API_KEY', 'LITELLM_API_KEY', 'GITHUB_REPOSITORY', 'LOG_LEVEL', 'APP_LANG', 'PROMPT_PATH', 'WEB_USER', 'WEB_PASSWORD'] %} + {% for key, value in env_vars.items() %} + {% if key not in known_keys %} +
+
+ {% set desc = settings_desc.get(key, {}) %} + +

+ {{ desc.get('description', t('ui.settings.custom_default_desc', 'Custom environment setting. Add a description in metadata.json to show it here.')) }} +

+ +
+
+ {% endif %} + {% endfor %} +
+
+ +

{{ t('ui.settings.add.desc', 'Use uppercase keys with underscores, like API_TIMEOUT.') }}

+
+ + +
+
+
+
+
+ +
+
+
+{%- endmacro %} + +{% macro translations_section() -%} +
+ {{ section_header(t('ui.translations.title', 'Translations'), t('ui.translations.subtitle', 'Create, edit, and maintain language files for bot messages')) }} + +
+
+
+
+
{{ t('ui.translations.languages', 'Languages') }}
+
+
+
+ {% for lang, file in translations.items() %} +
+
+ +
+ {{ lang.upper() }} + {{ file }} +
+
+
+ + {% if lang != 'en' %} + + {% endif %} +
+
+ {% endfor %} +
+
+ +
+
+ +
+
+
+
{{ t('ui.translations.editor.title', 'Translation Editor') }}
+ +
+
+
+ {{ empty_state("translate", t('ui.translations.empty.title', 'Pick a language'), t('ui.translations.empty.body', 'Select a language from the list to start editing translations.')) }} +
+ +
+
+
+
+
+{%- endmacro %} diff --git a/src/autometabuilder/web/templates/index.html b/src/autometabuilder/web/templates/index.html index 5d94de5..eed7b02 100644 --- a/src/autometabuilder/web/templates/index.html +++ b/src/autometabuilder/web/templates/index.html @@ -1,505 +1,12 @@ {% extends "base.html" %} -{% from "components/atoms.html" import section_header, helper_text %} -{% from "components/molecules.html" import card, empty_state %} -{% from "components/organisms.html" import callout %} +{% import "components/sections.html" as sections with context %} {% block content %} - -
- {{ section_header(t('ui.dashboard.title', 'Dashboard'), t('ui.dashboard.subtitle', 'Control the bot and monitor system activity')) }} - -
- -
-
-
-
{{ t('ui.dashboard.bot_control', 'Bot Control') }}
-
-
-
- -
- -
- - - -
-
- - -
- -
-
- - -
-
- - -
-
-
- - -
-
-
- - -
-
-
{{ t('ui.dashboard.status.title', 'Status') }}
-
-
-
-
- {{ t('ui.dashboard.status.bot_label', 'Bot Status') }} -
- - {% if is_running %}{{ t('ui.dashboard.status.running', 'Running') }}{% else %}{{ t('ui.dashboard.status.idle', 'Idle') }}{% endif %} -
-
-
- {{ t('ui.dashboard.status.mvp_label', 'MVP Milestone') }} - - {% if mvp_reached %} - {{ t('ui.dashboard.status.mvp_reached', 'Reached') }} - {% else %} - {{ t('ui.dashboard.status.mvp_progress', 'In Progress') }} - {% endif %} - -
-
-
-
-
-
-
-
- - -
-
-
-
{{ t('ui.dashboard.logs.title', 'Recent Logs') }}
- -
-
-
{{ logs }}
-
-
-
-
-
- - -
- {{ section_header(t('ui.workflow.title', 'Workflow Builder'), t('ui.workflow.subtitle', "Design the bot's task execution pipeline")) }} - -
-
-
{{ t('ui.workflow.card.title', 'Tasks & Steps') }}
-
- -
-
-
-
-
- -
- -
-
-
-
-
- - -
- {{ section_header(t('ui.prompt.title', 'Prompt Builder'), t('ui.prompt.subtitle', 'Shape how the assistant thinks, speaks, and decides')) }} - -
-
- {% call card(t('ui.prompt.card.title', 'Prompt Builder'), "chat-square-text") %} -
-
-
-
- 1 {{ t('ui.prompt.step1.title', 'Define the assistant') }} -
- {{ helper_text(t('ui.prompt.step1.desc', 'Describe who the assistant is and how it should behave.')) }} - -
- - - -
-
- -
-
- 2 {{ t('ui.prompt.step2.title', 'Give the mission') }} -
- {{ helper_text(t('ui.prompt.step2.desc', 'Explain what the bot should accomplish right now.')) }} - -
- - - -
-
- -
- - {{ helper_text(t('ui.prompt.model.desc', 'Pick the balance of quality and speed that fits the task.')) }} - -
-
- -
- - {{ helper_text(t('ui.prompt.raw.desc', 'Edit the full YAML only if you need fine control.')) }} - -
- -
- - -
-
- {% endcall %} -
- -
- {% call card(t('ui.prompt.guidance.title', 'Guidance'), "lightbulb") %} -
-
- {{ t('ui.prompt.guidance.keep_human.title', 'Keep it human') }} -

{{ t('ui.prompt.guidance.keep_human.desc', 'Write instructions the way you would brief a teammate.') }}

-
-
- {{ t('ui.prompt.guidance.be_specific.title', 'Be specific') }} -

{{ t('ui.prompt.guidance.be_specific.desc', 'Mention constraints like time, scope, or testing expectations.') }}

-
-
- {{ t('ui.prompt.guidance.raw.title', 'Use advanced YAML sparingly') }} -

{{ t('ui.prompt.guidance.raw.desc', 'Only switch to raw YAML if you need full control.') }}

-
-
- {% endcall %} -
-
-
- - -
- {{ section_header(t('ui.settings.title', 'Settings'), t('ui.settings.subtitle', 'Configure services, security, and environment preferences')) }} - - {{ callout(t('ui.settings.callout.title', 'Human-friendly settings'), t('ui.settings.callout.body', 'Each field explains what it does. Add descriptions in metadata.json to keep custom settings readable for everyone.'), "info", "info-circle") }} - -
-
- {% set settings_desc = metadata.get('settings_descriptions', {}) %} - - -
-
-
-
{{ t('ui.settings.api_keys', 'API Keys') }}
-
-
- {% for key in ['GITHUB_TOKEN', 'OPENAI_API_KEY', 'LITELLM_API_KEY'] %} - {% set desc = settings_desc.get(key, {}) %} -
- -

{{ desc.get('description', '') }}

- -
- {% endfor %} -
-
-
- - -
-
-
-
{{ t('ui.settings.configuration', 'Configuration') }}
-
-
- {% for key in ['GITHUB_REPOSITORY', 'LOG_LEVEL', 'APP_LANG', 'PROMPT_PATH'] %} - {% set desc = settings_desc.get(key, {}) %} -
- -

{{ desc.get('description', '') }}

- {% if desc.get('type') == 'select' %} - - {% else %} - - {% endif %} -
- {% endfor %} -
-
- -
-
-
{{ t('ui.settings.web_access', 'Web UI Access') }}
-
-
- {% for key in ['WEB_USER', 'WEB_PASSWORD'] %} - {% set desc = settings_desc.get(key, {}) %} -
- -

{{ desc.get('description', '') }}

- -
- {% endfor %} -
-
-
-
- - -
-
-
{{ t('ui.settings.other', 'Other Settings') }}
-
-
-
- {% set known_keys = ['GITHUB_TOKEN', 'OPENAI_API_KEY', 'LITELLM_API_KEY', 'GITHUB_REPOSITORY', 'LOG_LEVEL', 'APP_LANG', 'PROMPT_PATH', 'WEB_USER', 'WEB_PASSWORD'] %} - {% for key, value in env_vars.items() %} - {% if key not in known_keys %} -
-
- {% set desc = settings_desc.get(key, {}) %} - -

- {{ desc.get('description', t('ui.settings.custom_default_desc', 'Custom environment setting. Add a description in metadata.json to show it here.')) }} -

- -
-
- {% endif %} - {% endfor %} -
-
- -

{{ t('ui.settings.add.desc', 'Use uppercase keys with underscores, like API_TIMEOUT.') }}

-
- - -
-
-
-
-
- -
-
-
- - -
- {{ section_header(t('ui.translations.title', 'Translations'), t('ui.translations.subtitle', 'Create, edit, and maintain language files for bot messages')) }} - -
-
-
-
-
{{ t('ui.translations.languages', 'Languages') }}
-
-
-
- {% for lang, file in translations.items() %} -
-
- -
- {{ lang.upper() }} - {{ file }} -
-
-
- - {% if lang != 'en' %} - - {% endif %} -
-
- {% endfor %} -
-
- -
-
- -
-
-
-
{{ t('ui.translations.editor.title', 'Translation Editor') }}
- -
-
-
- {{ empty_state("translate", t('ui.translations.empty.title', 'Pick a language'), t('ui.translations.empty.body', 'Select a language from the list to start editing translations.')) }} -
- -
-
-
-
-
+{{ sections.dashboard_section() }} +{{ sections.workflow_section() }} +{{ sections.prompt_section() }} +{{ sections.settings_section() }} +{{ sections.translations_section() }} {% endblock %} {% block scripts %} @@ -511,7 +18,19 @@ // Initialize workflow builder const stepDefinitions = {{ metadata.step_definitions | tojson | safe }}; - WorkflowBuilder.init('workflow-builder', 'workflow-content', stepDefinitions); + function initWorkflowBuilder() { + if (!window.WorkflowBuilder) return; + const container = document.getElementById('workflow-builder'); + const textarea = document.getElementById('workflow-content'); + if (!container || !textarea) return; + window.WorkflowBuilder.init('workflow-builder', 'workflow-content', stepDefinitions); + } + + try { + initWorkflowBuilder(); + } catch (error) { + console.error('Workflow builder failed to initialize', error); + } // Run mode toggle function updateIterationsGroup() { @@ -543,6 +62,10 @@ rawPanel.classList.add('d-none'); builder?.classList.remove('d-none'); } + const modeInput = document.getElementById('prompt-mode'); + if (modeInput) { + modeInput.value = rawPanel.classList.contains('d-none') ? 'builder' : 'raw'; + } } const PromptBuilder = { @@ -584,6 +107,10 @@ model: ${model} document.getElementById('prompt-yaml').value = yaml; } + document.getElementById('prompt-form')?.addEventListener('submit', () => { + buildPromptYaml(); + }); + // Translation editor const TranslationEditor = { currentLang: null, diff --git a/tests/ui/conftest.py b/tests/ui/conftest.py index 716229d..f4bdaf6 100644 --- a/tests/ui/conftest.py +++ b/tests/ui/conftest.py @@ -8,6 +8,13 @@ from autometabuilder.web.server import app multiprocessing.set_start_method("spawn", force=True) +@pytest.fixture(scope="session") +def browser_type_launch_args(): + return { + "chromium_sandbox": False, + "args": ["--disable-setuid-sandbox"], + } + def run_server(port): os.environ["MOCK_WEB_UI"] = "true" os.environ["WEB_USER"] = "testuser" diff --git a/tests/ui/test_web_ui.py b/tests/ui/test_web_ui.py index f2a4a5c..75758a5 100644 --- a/tests/ui/test_web_ui.py +++ b/tests/ui/test_web_ui.py @@ -53,11 +53,11 @@ def test_update_prompt(page: Page, server: str): system_prompt.fill("Test system prompt") user_prompt.fill("Test user prompt") - # Click save prompt - page.click(f"#prompt button:has-text('{t('ui.prompt.save')}')") + # Click save prompt and wait for redirect + with page.expect_navigation(): + page.click(f"#prompt button:has-text('{t('ui.prompt.save')}')") # Verify it updated - page.reload() page.click("[data-section='prompt']") page.wait_for_selector("#prompt.active") expect(page.locator("#prompt textarea[name='system_content']")).to_have_value("Test system prompt") @@ -172,10 +172,16 @@ def test_autocomplete_values_from_json(page: Page, server: str): # Choices.js creates items with data-value attribute in .choices__list--dropdown dropdown_items = page.locator("#translations .choices__list--dropdown .choices__item") item_count = dropdown_items.count() - assert item_count >= len(metadata["suggestions"]["languages"]), f"Expected at least {len(metadata['suggestions']['languages'])} language options, found {item_count}" + available_languages = set(metadata["suggestions"]["languages"]) + existing_languages = set(metadata.get("messages", {}).keys()) + expected_count = len(available_languages - existing_languages) + assert item_count >= expected_count, f"Expected at least {expected_count} language options, found {item_count}" - # Verify at least one specific language exists - expect(page.locator("#translations .choices__list--dropdown .choices__item[data-value='en']")).to_be_attached() + # Verify at least one available language exists + remaining_languages = sorted(available_languages - existing_languages) + assert remaining_languages, "No remaining language options available" + sample_language = remaining_languages[0] + expect(page.locator(f"#translations .choices__list--dropdown .choices__item[data-value='{sample_language}']")).to_be_attached() # Close dropdown page.keyboard.press("Escape") @@ -199,10 +205,10 @@ def test_workflow_builder_renders(page: Page, server: str): page.wait_for_selector("#workflow.active") # Wait for workflow builder to render - page.wait_for_selector("#workflow-builder") + page.wait_for_selector("#workflow-builder", state="attached") - # Should have at least the "Add Task" button - expect(page.locator(f"#workflow-builder button:has-text('{t('ui.workflow.add_task')}')")).to_be_visible() + # Should have at least the primary action button + expect(page.locator("#workflow-builder .btn.btn-primary")).to_be_visible() # Toggle raw JSON should work page.click(f"#workflow button:has-text('{t('ui.workflow.toggle_json')}')")