Files
wehub-resource-sync 426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

369 lines
12 KiB
Python

"""Tests for the VisualWebBench package.
These tests use the offline labeled JSONL fixture and the ``--mock`` oracle to
verify that the right per-subtask metric runs end-to-end for every subtask.
"""
from __future__ import annotations
import asyncio
import json
from pathlib import Path
import pytest
from benchmarks.visualwebbench.agent import OracleVisualWebBenchAgent
from benchmarks.visualwebbench.dataset import VisualWebBenchDataset
from benchmarks.visualwebbench.evaluator import VisualWebBenchEvaluator
from benchmarks.visualwebbench.runner import VisualWebBenchRunner
from benchmarks.visualwebbench.types import (
VISUALWEBBENCH_TASK_TYPES,
VisualWebBenchConfig,
VisualWebBenchPrediction,
VisualWebBenchTask,
VisualWebBenchTaskType,
)
from eliza_adapter.visualwebbench import (
_build_app_harness_invocation,
_strip_html_entities,
)
# --------------------------------------------------------------------------- #
# Metric unit tests — one per subtask
# --------------------------------------------------------------------------- #
def _evaluate(task: VisualWebBenchTask, *, answer_text: str = "", choice_index=None):
return VisualWebBenchEvaluator().evaluate(
task,
VisualWebBenchPrediction(
task_id=task.id,
task_type=task.task_type,
answer_text=answer_text,
choice_index=choice_index,
),
)
def test_web_caption_uses_rouge():
task = VisualWebBenchTask(
id="cap-1",
task_type=VisualWebBenchTaskType.WEB_CAPTION,
website="ex.test",
prompt="caption it",
answer="A product listing page for hiking backpacks",
)
result = _evaluate(task, answer_text="A product listing page for hiking backpacks")
assert result.score_kind == "rouge"
assert set(result.metrics) == {"rouge_1", "rouge_2", "rouge_l"}
assert result.metrics["rouge_l"] > 99.0 # near-perfect match
def test_heading_ocr_uses_rouge():
task = VisualWebBenchTask(
id="head-1",
task_type=VisualWebBenchTaskType.HEADING_OCR,
website="ex.test",
prompt="read heading",
answer="Morning Market Brief",
)
result = _evaluate(task, answer_text="Morning Market Brief")
assert result.score_kind == "rouge"
assert result.metrics["rouge_l"] > 99.0
def test_element_ocr_uses_rouge():
task = VisualWebBenchTask(
id="el-1",
task_type=VisualWebBenchTaskType.ELEMENT_OCR,
website="ex.test",
prompt="ocr",
answer="Book now",
)
result = _evaluate(task, answer_text="The text is: Book now")
assert result.score_kind == "rouge"
# Partial overlap so rouge_l < 100 but > 0.
assert 0.0 < result.metrics["rouge_l"] < 100.0
def test_webqa_uses_f1_with_reference_list():
task = VisualWebBenchTask(
id="qa-1",
task_type=VisualWebBenchTaskType.WEBQA,
website="ex.test",
prompt="What is the APR?",
answer=["4.25%", "4.25 percent"],
question="What is the APR?",
)
result = _evaluate(task, answer_text="4.25 percent")
assert result.score_kind == "f1"
assert set(result.metrics) == {"f1"}
assert result.metrics["f1"] > 99.0
def test_element_ground_uses_choice_letter():
task = VisualWebBenchTask(
id="eg-1",
task_type=VisualWebBenchTaskType.ELEMENT_GROUND,
website="ex.test",
prompt="which element?",
options=[(0.1, 0.1, 0.2, 0.2), (0.5, 0.5, 0.7, 0.7), (0.7, 0.7, 0.9, 0.9)],
answer=2,
)
result = _evaluate(task, answer_text="C")
assert result.score_kind == "choice"
assert result.metrics == {"accuracy": 100.0}
assert result.success
def test_action_prediction_uses_choice():
task = VisualWebBenchTask(
id="ap-1",
task_type=VisualWebBenchTaskType.ACTION_PREDICTION,
website="ex.test",
prompt="action?",
options=["open", "submit", "clear"],
answer=1,
)
result = _evaluate(task, answer_text="B")
assert result.score_kind == "choice"
assert result.success
def test_action_ground_uses_choice():
task = VisualWebBenchTask(
id="ag-1",
task_type=VisualWebBenchTaskType.ACTION_GROUND,
website="ex.test",
prompt="which?",
options=[(0.1, 0.1, 0.2, 0.2), (0.5, 0.5, 0.7, 0.7), (0.7, 0.7, 0.9, 0.9)],
answer=0,
)
result = _evaluate(task, answer_text="A")
assert result.score_kind == "choice"
assert result.success
def test_choice_letter_parser_handles_sentences():
task = VisualWebBenchTask(
id="ag-2",
task_type=VisualWebBenchTaskType.ACTION_GROUND,
website="ex.test",
prompt="which?",
options=[(0, 0, 0.1, 0.1)] * 4,
answer=2,
)
result = _evaluate(task, answer_text="I think the answer is (C).")
assert result.success
def test_choice_letter_parser_rejects_wrong_letter():
task = VisualWebBenchTask(
id="eg-2",
task_type=VisualWebBenchTaskType.ELEMENT_GROUND,
website="ex.test",
prompt="which?",
options=[(0, 0, 0.1, 0.1)] * 4,
answer=2,
)
result = _evaluate(task, answer_text="A")
assert not result.success
assert result.metrics == {"accuracy": 0.0}
def test_prediction_error_forces_zero_score():
task = VisualWebBenchTask(
id="qa-err",
task_type=VisualWebBenchTaskType.WEBQA,
website="ex.test",
prompt="?",
answer=["x"],
)
result = VisualWebBenchEvaluator().evaluate(
task,
VisualWebBenchPrediction(
task_id=task.id,
task_type=task.task_type,
answer_text="x",
error="agent failed",
),
)
assert result.score == 0.0
assert not result.success
assert result.error == "agent failed"
# --------------------------------------------------------------------------- #
# Dataset loading
# --------------------------------------------------------------------------- #
def test_jsonl_fixture_loads_all_seven_subtasks():
ds = VisualWebBenchDataset()
asyncio.run(ds.load(use_huggingface=False, use_sample_tasks=True))
loaded_types = {t.task_type for t in ds.tasks}
assert loaded_types == set(VISUALWEBBENCH_TASK_TYPES)
def test_jsonl_fixture_edge_expansion_adds_ten_variants_per_task():
ds = VisualWebBenchDataset()
asyncio.run(
ds.load(
use_huggingface=False,
use_sample_tasks=True,
include_edge_scenarios=True,
)
)
assert ds.count_scenarios() == {
"base": 7,
"edge": 70,
"total": 77,
"edge_multiplier": 10,
}
assert ds.validate_scenarios() == []
assert len({task.id for task in ds.tasks}) == 77
def test_dataset_refuses_to_load_with_no_source():
ds = VisualWebBenchDataset()
with pytest.raises(RuntimeError):
asyncio.run(ds.load(use_huggingface=False, use_sample_tasks=False))
# --------------------------------------------------------------------------- #
# Runner — one sample per subtask, with the oracle, verifies right metric runs
# --------------------------------------------------------------------------- #
def test_runner_smoke_one_per_subtask(tmp_path: Path) -> None:
config = VisualWebBenchConfig(
output_dir=str(tmp_path),
mock=True,
use_huggingface=False,
use_sample_tasks=True,
)
report = asyncio.run(VisualWebBenchRunner(config).run_benchmark())
assert report.total_tasks == 7
seen = {tt for tt in report.by_task_type}
assert seen == {t.value for t in VISUALWEBBENCH_TASK_TYPES}
for tt, agg in report.by_task_type.items():
# Oracle always gives the right answer; check the right metric ran.
if tt in {"web_caption", "heading_ocr", "element_ocr"}:
assert "rouge_l" in agg
assert agg["rouge_l"] > 50.0
elif tt == "webqa":
assert "f1" in agg
assert agg["f1"] > 50.0
else:
assert "accuracy" in agg
assert agg["accuracy"] == 100.0
results_path = tmp_path / "visualwebbench-results.json"
summary_path = tmp_path / "summary.md"
trace_dir = tmp_path / "traces"
assert results_path.exists()
assert summary_path.exists()
assert trace_dir.exists()
assert len(list(trace_dir.glob("*.json"))) == 7
data = json.loads(results_path.read_text())
assert data["benchmark"] == "visualwebbench"
assert data["total_tasks"] == 7
def test_runner_expanded_sample_tasks_with_oracle(tmp_path: Path) -> None:
config = VisualWebBenchConfig(
output_dir=str(tmp_path),
mock=True,
use_huggingface=False,
use_sample_tasks=True,
include_edge_scenarios=True,
)
report = asyncio.run(VisualWebBenchRunner(config).run_benchmark())
assert report.total_tasks == 77
assert report.overall_accuracy > 0.95
trace_dir = tmp_path / "traces"
assert len(list(trace_dir.glob("*.json"))) == 77
def test_runner_refuses_without_mock_or_provider() -> None:
config = VisualWebBenchConfig(
mock=False,
provider="unknown-provider",
use_huggingface=False,
use_sample_tasks=True,
)
runner = VisualWebBenchRunner(config)
# Load tasks before exercising the agent picker so we don't hit HF.
asyncio.run(runner.dataset.load(use_huggingface=False, use_sample_tasks=True))
with pytest.raises(ValueError, match="VisualWebBench requires --mock"):
runner._create_agent()
def test_oracle_agent_only_returned_when_mock_is_set() -> None:
mock_cfg = VisualWebBenchConfig(mock=True, use_sample_tasks=True, use_huggingface=False)
runner = VisualWebBenchRunner(mock_cfg)
assert isinstance(runner._create_agent(), OracleVisualWebBenchAgent)
# --------------------------------------------------------------------------- #
# App-harness invocation (regression coverage from the old test file)
# --------------------------------------------------------------------------- #
def test_app_harness_invocation_uses_ui_prompt_by_default(tmp_path: Path) -> None:
task = VisualWebBenchTask(
id="webqa-fixture",
task_type=VisualWebBenchTaskType.WEBQA,
website="example.com/page",
prompt="What is the title?",
answer=["Example"],
question="What is the title?",
)
config = VisualWebBenchConfig(
output_dir=str(tmp_path),
provider="eliza-app-harness",
app_harness_script=tmp_path / "harness.mjs",
app_harness_runtime="bun",
timeout_ms=5000,
)
invocation = _build_app_harness_invocation(task, config, run_id="run-1")
assert invocation.run_id == "run-1"
assert "--prompt-via-ui" in invocation.command
assert "--no-launch" in invocation.command
assert "--target-url" in invocation.command
assert "visualwebbench" in invocation.prompt
def test_app_harness_invocation_can_use_api_prompt_fallback(tmp_path: Path) -> None:
task = VisualWebBenchTask(
id="caption",
task_type=VisualWebBenchTaskType.WEB_CAPTION,
website="https://example.org/",
prompt="Caption the page.",
answer="Example",
)
config = VisualWebBenchConfig(
output_dir=str(tmp_path),
provider="eliza-app-harness",
app_harness_script=tmp_path / "harness.mjs",
app_harness_prompt_via_ui=False,
)
invocation = _build_app_harness_invocation(task, config, run_id="run-2")
assert "--prompt-via-api" in invocation.command
assert "--prompt-via-ui" not in invocation.command
def test_visualwebbench_meta_description_context_decodes_html_entities() -> None:
assert (
_strip_html_entities(
"The world&#39;s largest &amp; most trusted animal facts&nbsp;site"
)
== "The world's largest & most trusted animal facts site"
)