Download tests_python/test_gradio_app.py from build-small-hackathon/agent-swarm-workbench: direct link, hf CLI and curl.
- Browser
- Download file 16.2 kB
-
https://huggingface.co/spaces/build-small-hackathon/agent-swarm-workbench/resolve/main/tests_python/test_gradio_app.py
- Command line
-
hf download hf://spaces/build-small-hackathon/agent-swarm-workbench/tests_python/test_gradio_app.py
-
curl -L -o test_gradio_app.py https://huggingface.co/spaces/build-small-hackathon/agent-swarm-workbench/resolve/main/tests_python/test_gradio_app.py
16.2 kB
| from arena.codebase_archive import LocalCodebaseArchiveStore | |
| from arena.gradio_app import GradioArena, build_app | |
| from arena.model_catalog import DEFAULT_LOCAL_MODEL, ProviderModel | |
| from arena.run_store import InMemoryRunStore | |
| from arena.service import ArenaService | |
| from arena.swarm_runtime import SwarmRuntime | |
| from arena.validator_graph import DeterministicValidatorExecutor, ValidatorGraph | |
| from conftest import make_local_session | |
| def make_service(): | |
| store = LocalCodebaseArchiveStore() | |
| return ArenaService( | |
| session_factory=make_local_session, | |
| run_store=InMemoryRunStore(), | |
| swarm_runtime=SwarmRuntime( | |
| session_factory=make_local_session, | |
| archive_store=store, | |
| ), | |
| validator_graph=ValidatorGraph( | |
| executor=DeterministicValidatorExecutor(), | |
| rubric_enabled=False, | |
| ), | |
| archive_store=store, | |
| ) | |
| def create_local_run(arena: GradioArena, prompt: str, criteria: str = "", tests: str = ""): | |
| return arena.create_run(prompt, criteria, tests, "local", DEFAULT_LOCAL_MODEL, "", "") | |
| def test_gradio_local_provider_runs_inside_create_callback() -> None: | |
| service = make_service() | |
| calls: list[str] = [] | |
| original_create_run = service.create_run | |
| original_start_run = service.start_run | |
| def create_run(request): | |
| calls.append("create") | |
| return original_create_run(request) | |
| def start_run(request): | |
| calls.append("start") | |
| return original_start_run(request) | |
| service.create_run = create_run | |
| service.start_run = start_run | |
| arena = GradioArena(service) | |
| create_local_run(arena, "Build a CLI", tests="echo ok") | |
| assert calls == ["create"] | |
| def test_gradio_create_run_returns_state_and_summary() -> None: | |
| arena = GradioArena(make_service()) | |
| state, status_html, run_json, tasks, events, token, download, _, chat = create_local_run( | |
| arena, | |
| "Build a small FastAPI app", | |
| "pytest must pass\ninclude README", | |
| "echo ok", | |
| ) | |
| assert state.role == "run" | |
| assert state.run_id == run_json["id"] | |
| assert isinstance(status_html, str) | |
| assert run_json["prompt"] == "Build a small FastAPI app" | |
| assert [item["text"] for item in run_json["criteria"]] == ["pytest must pass", "include README"] | |
| assert run_json["user_tests"] == ["echo ok"] | |
| assert run_json["status"] in ("working", "validating", "completed") | |
| assert isinstance(run_json.get("codebase_zip_url", ""), str) | |
| assert token == "" | |
| assert state.token == "" | |
| assert "Build a small FastAPI app" in chat | |
| def test_gradio_create_run_accepts_empty_model_dropdown_value() -> None: | |
| arena = GradioArena(make_service()) | |
| state, _, run_json, *_ = create_local_run( | |
| arena, | |
| "Build a small Gradio demo", | |
| "Works as a Gradio app", | |
| "python -m py_compile app.py", | |
| ) | |
| assert state.role == "run" | |
| assert run_json["prompt"] == "Build a small Gradio demo" | |
| def test_gradio_hosted_provider_requires_user_api_key() -> None: | |
| arena = GradioArena(make_service()) | |
| state, status_html, run_json, *rest = arena.create_run( | |
| "Build a small Gradio demo", | |
| "Works as a Gradio app", | |
| "python -m py_compile app.py", | |
| "openrouter", | |
| "qwen/qwen3.5-9b", | |
| "", | |
| "", | |
| ) | |
| assert state.role == "" | |
| assert "enter your own API key" in status_html | |
| assert "enter your own API key" in rest[4] | |
| assert run_json == {} | |
| def test_gradio_refresh_models_accepts_empty_optional_inputs() -> None: | |
| class FakeService: | |
| def refresh_provider_models(self, config): | |
| assert config.provider == "openrouter" | |
| assert config.key_value() == "" | |
| assert config.base_url == "" | |
| return [ | |
| ProviderModel( | |
| id="nvidia/nemotron-3.5-content-safety:free", | |
| label="NVIDIA: Nemotron 3.5 Content Safety", | |
| provider="openrouter", | |
| parameter_count_b=4.0, | |
| eligible=True, | |
| ), | |
| ProviderModel( | |
| id="too-large/model-70b", | |
| label="Too Large 70B", | |
| provider="openrouter", | |
| parameter_count_b=70.0, | |
| eligible=False, | |
| status="too_large", | |
| ), | |
| ] | |
| arena = GradioArena(FakeService()) | |
| dropdown, models = arena.refresh_models("openrouter", None, None) | |
| assert dropdown.choices == [ | |
| ("nvidia/nemotron-3.5-content-safety:free", "nvidia/nemotron-3.5-content-safety:free") | |
| ] | |
| assert dropdown.value == "nvidia/nemotron-3.5-content-safety:free" | |
| assert [model["id"] for model in models] == ["nvidia/nemotron-3.5-content-safety:free"] | |
| assert models[0]["parameter_count_b"] == 4.0 | |
| def test_gradio_send_message_records_followup_in_chat() -> None: | |
| arena = GradioArena(make_service()) | |
| state, *_ = create_local_run(arena, "Build a CLI") | |
| _, _, run_json, _, events, _, _, _, chat = arena.send_message(state, "Can you keep working on this?") | |
| assert run_json["events"][-1]["message"] == "User: Can you keep working on this?" | |
| assert events[-1][1] == "User: Can you keep working on this?" | |
| assert "Can you keep working on this?" in chat | |
| assert 'class="chat-bubble is-user"' in chat | |
| def test_gradio_feedback_records_mom_test_event() -> None: | |
| arena = GradioArena(make_service()) | |
| state, *_ = create_local_run(arena, "Build a CLI") | |
| _, _, run_json, _, events, _, _, _, chat = arena.record_feedback( | |
| state, | |
| "Mom", | |
| "I would use this with buyers.", | |
| "scale", | |
| "Needs Vietnamese copy.", | |
| ) | |
| assert "Mom Test feedback" in run_json["events"][-1]["message"] | |
| assert "Decision: scale" in run_json["events"][-1]["message"] | |
| assert "I would use this with buyers." in events[-1][1] | |
| assert "Needs Vietnamese copy." in chat | |
| def test_gradio_refresh_shows_current_status() -> None: | |
| arena = GradioArena(make_service()) | |
| state, *_ = create_local_run(arena, "Build a CLI", tests="echo ok") | |
| _, status_html, run_json, *_ = arena.refresh(state) | |
| assert run_json["id"] == state.run_id | |
| assert run_json["status"] in ("working", "validating", "completed") | |
| def test_gradio_refresh_shows_completed_tasks_and_events() -> None: | |
| """Bug E: After a run completes, the tasks and events dataframes must show the run's data.""" | |
| import time | |
| service = make_service() | |
| arena = GradioArena(service) | |
| state, *_ = create_local_run(arena, "Build a CLI", tests="test -f README.md") | |
| for _ in range(60): | |
| run = service.get_run(state.run_id) | |
| if run.status == "completed": | |
| break | |
| time.sleep(0.5) | |
| _, _, _, tasks, events, *_ = arena.refresh(state) | |
| assert len(tasks) >= 1, f"Expected at least 1 task, got {tasks}" | |
| assert len(events) >= 1, f"Expected at least 1 event, got {events}" | |
| def test_gradio_status_badge_renders_html() -> None: | |
| arena = GradioArena(make_service()) | |
| state, status_html, *_ = create_local_run(arena, "Build a CLI", tests="echo ok") | |
| assert "<span" in status_html | |
| assert _extract_status_from_html(status_html) in ("working", "validating", "completed") | |
| def test_gradio_app_constructs_without_crashing() -> None: | |
| app = build_app(make_service()) | |
| assert type(app).__name__ == "Blocks" | |
| assert app.title == "Backyard Demo Builder" | |
| def test_gradio_defaults_target_backyard_demo_builder() -> None: | |
| app = build_app(make_service()) | |
| values = "\n".join( | |
| str(getattr(block, "value", "") or "") | |
| for block in app.blocks.values() | |
| if hasattr(block, "value") | |
| ) | |
| assert "real-estate agent" in values | |
| assert "handoff_spec.md" in values | |
| assert "field_notes.md" in values | |
| assert "Backyard Demo Builder" in values | |
| assert "Record feedback" in values or any( | |
| getattr(block, "value", None) == "Record feedback" | |
| for block in app.blocks.values() | |
| ) | |
| def test_gradio_two_consecutive_runs_both_complete() -> None: | |
| """Bug D: A second run on the same service must complete, not hang.""" | |
| import time | |
| service = make_service() | |
| arena = GradioArena(service) | |
| state1, *_ = create_local_run(arena, "Build a CLI", tests="test -f README.md") | |
| for _ in range(60): | |
| if service.get_run(state1.run_id).status == "completed": | |
| break | |
| time.sleep(0.5) | |
| assert service.get_run(state1.run_id).status == "completed", "First run did not complete" | |
| state2, *_ = create_local_run(arena, "Build another CLI", tests="test -f README.md") | |
| for _ in range(60): | |
| if service.get_run(state2.run_id).status == "completed": | |
| break | |
| time.sleep(0.5) | |
| assert service.get_run(state2.run_id).status == "completed", "Second run did not complete" | |
| def test_gradio_outputs_tuple_has_nine_distinct_outputs() -> None: | |
| """Bug F: The 9-tuple outputs list must bind to 9 distinct Gradio components. | |
| Before the fix, position 1 (status bar) and position 7 (status panel with summary) | |
| were both bound to the same gr.HTML element. The status bar was dead code because | |
| the full status panel overwrote it. | |
| """ | |
| app = build_app(make_service()) | |
| for fn in app.fns.values(): | |
| if not fn.outputs or len(fn.outputs) != 9: | |
| continue | |
| ids = [o.elem_id if hasattr(o, "elem_id") and o.elem_id else id(o) for o in fn.outputs] | |
| assert len(ids) == len(set(ids)), ( | |
| f"Duplicate output binding detected in {fn.name}: {ids}. " | |
| "Each output slot must bind to a distinct Gradio component." | |
| ) | |
| def test_gradio_does_not_poll_refresh_while_idle() -> None: | |
| """Idle timer refreshes compete with manual model refresh in Gradio's queue.""" | |
| app = build_app(make_service()) | |
| assert all(fn.name != "refresh_1" for fn in app.fns.values()) | |
| def test_css_has_mobile_breakpoint_with_hero_scaling() -> None: | |
| """Bug G/H: The mobile media query must scale the hero H1 and rebalance the top bar.""" | |
| from arena.gradio_app import CSS | |
| assert "@media" in CSS, "CSS must include responsive media queries for mobile" | |
| assert "max-width: 880px" in CSS, "CSS must include the 880px mobile breakpoint" | |
| start = CSS.index("@media (max-width: 880px)") | |
| rest = CSS[start + 1:] | |
| next_media = rest.find("@media") | |
| mobile_block = rest[:next_media] if next_media != -1 else rest | |
| assert "hero-title" in mobile_block, ( | |
| "Mobile media query must include hero-title rules to prevent H1 wrap on small viewports" | |
| ) | |
| assert "wordmark" in mobile_block, ( | |
| "Mobile media query must rebalance the top bar wordmark so it fits on one line" | |
| ) | |
| def test_gradio_workbench_children_live_inside_workbench_wrapper() -> None: | |
| """Bug I: The workbench Row must render inside the .workbench wrapper, not as a sibling. | |
| Gradio 6 renders gr.Row as a sibling of raw HTML elements, not nested. | |
| If we wrap a gr.Row in custom <div class="workbench">…</div>, the wrapper | |
| collapses to 1px and the form/dispatch land outside it. That 1px wrapper | |
| shows up as a stray horizontal line in the middle of the page. | |
| """ | |
| app = build_app(make_service()) | |
| workbench = None | |
| for block in app.blocks.values(): | |
| if getattr(block, "elem_id", None) is None and "workbench" in (getattr(block, "elem_classes", []) or []): | |
| workbench = block | |
| break | |
| if getattr(block, "elem_id", None) == "run-shell": | |
| pass | |
| app_html = app.fns | |
| assert app_html, "App must have registered event handlers" | |
| blocks_list = list(app.blocks.values()) | |
| workbench_blocks = [b for b in blocks_list if "workbench" in (getattr(b, "elem_classes", []) or [])] | |
| assert workbench_blocks, "App must have at least one workbench-classed block" | |
| workbench_block = workbench_blocks[0] | |
| assert workbench_block is not None | |
| assert workbench_block.elem_classes and "workbench" in workbench_block.elem_classes | |
| assert workbench_block.__class__.__name__ in ("Row", "Group", "Column"), ( | |
| f"Workbench wrapper must be a Gradio Row/Group/Column so the form and dispatch " | |
| f"land inside it, not a raw HTML block. Got type={workbench_block.__class__.__name__!r}." | |
| ) | |
| def test_gradio_run_state_rail_uses_dynamic_status_panel() -> None: | |
| """The rail must not keep stale static 'No active run' text after a run starts.""" | |
| app = build_app(make_service()) | |
| html = "\n".join( | |
| getattr(block, "value", "") or "" | |
| for block in app.blocks.values() | |
| if block.__class__.__name__ == "HTML" | |
| ) | |
| status_panels = [ | |
| block for block in app.blocks.values() | |
| if getattr(block, "elem_id", None) == "status-panel" | |
| ] | |
| assert "No active run. Start one to populate telemetry." not in html | |
| assert len(status_panels) == 1 | |
| def test_css_fills_full_viewport_no_centering_black_hole() -> None: | |
| """Bug J: The .gradio-container must not cap the page width below 100%. | |
| At 1920x1200 the page should fill the frame. A max-width: 1280px | |
| rule leaves a 320px black bar on each side that the user calls | |
| 'a black hole on the right panel' — uncomfortable to look at. | |
| """ | |
| from arena.gradio_app import CSS | |
| container_rule = next( | |
| (rule for rule in CSS.split("}") if ".gradio-container" in rule and "max-width" in rule), | |
| None, | |
| ) | |
| assert container_rule is not None, "Expected an explicit .gradio-container max-width rule" | |
| import re | |
| px_match = re.search(r"max-width:\s*(\d+)px", container_rule) | |
| pct_match = re.search(r"max-width:\s*100%", container_rule) | |
| if pct_match: | |
| return | |
| assert px_match, f"Could not parse max-width from rule: {container_rule!r}" | |
| px = int(px_match.group(1)) | |
| assert px >= 1800, ( | |
| f"gradio-container max-width={px}px is too narrow for 1920x1200 frames. " | |
| "Bump to >= 1800 or remove the constraint so the page fills the viewport." | |
| ) | |
| def test_hero_splits_into_main_and_side_rail() -> None: | |
| """The hero must split into a main column (manifesto) and a side rail | |
| (numbered marks) on wide viewports so the 1920x1200 frame has visual | |
| density on the right side, not a black hole. | |
| """ | |
| app = build_app(make_service()) | |
| html_blocks = [ | |
| b for b in app.blocks.values() | |
| if b.__class__.__name__ == "HTML" and "hero" in (getattr(b, "value", "") or "") | |
| ] | |
| assert html_blocks, "App must include at least one hero HTML block" | |
| html = html_blocks[0].value | |
| assert "hero-main" in html, "Hero must have a .hero-main column for the manifesto" | |
| assert "hero-side" in html, "Hero must have a .hero-side column with numbered marks" | |
| assert "mark-num" in html, "Hero side rail must include numbered marks (01/02/03/04)" | |
| assert "mark-text" in html, "Hero side rail must include mark descriptions" | |
| def test_workbench_has_three_columns_at_wide_viewport() -> None: | |
| """On >= 1100px, the workbench must render a 3-column grid: form / dispatch / rail. | |
| A 2-column layout leaves the right half of a 1920x1200 frame empty | |
| once the form finishes growing — the black-hole symptom. The third | |
| column (telemetry, keyboard hints, run state) is the fix. | |
| """ | |
| from arena.gradio_app import CSS | |
| root_block = next( | |
| (block for block in CSS.split("}") if ":root" in block and "--workbench-cols:" in block), | |
| None, | |
| ) | |
| assert root_block is not None, "Expected --workbench-cols variable in :root" | |
| import re | |
| match = re.search(r"--workbench-cols:\s*([^;]+);", root_block) | |
| assert match, f"Could not parse --workbench-cols from: {root_block!r}" | |
| cols = match.group(1).strip() | |
| track_count = len([t for t in re.split(r"\s+(?![^()]*\))", cols) if t.strip()]) | |
| assert track_count == 3, ( | |
| f"--workbench-cols must define 3 tracks, got {track_count}: {cols!r}" | |
| ) | |
| def _extract_status_from_html(html: str) -> str: | |
| import re | |
| match = re.search(r">(\w+)</span>", html) | |
| return match.group(1) if match else "" | |