APP-Backend / Browser_Agent /tests /test_agent_tools.py
Luca448's picture
Savestate: Fix Dockerfile and add Browser_Agent for HF build, bump version to 1.8.334
508316a
Raw History Blame Contribute Delete
5.82 kB
import pytest
from datetime import datetime, timedelta, timezone
from agent.tools import execute_tool
from dataclasses import dataclass, field
@dataclass
class MockTask:
steps: list = None
params: dict = field(default_factory=dict)
def __post_init__(self):
if self.steps is None:
self.steps = []
@pytest.mark.asyncio
async def test_tool_navigate_ssrf(mocker):
page = mocker.AsyncMock()
# Valid url
res = await execute_tool("navigate", {"url": "https://example.com"}, page)
assert res["status"] == "success"
page.goto.assert_called_with(
"https://example.com",
wait_until="domcontentloaded",
timeout=30000,
)
# SSRF blocked url
res = await execute_tool("navigate", {"url": "http://169.254.169.254"}, page)
assert "error" in res
assert "Sicherheitsverstoß" in res["error"]
@pytest.mark.asyncio
async def test_tool_request_user_permission(mocker):
page = mocker.AsyncMock()
page.url = "https://example.com/login"
page.context.cookies.return_value = []
task = MockTask()
res = await execute_tool(
"request_user_permission",
{"reason": "Login"},
page,
task=task,
)
assert res["status"] == "confirmation_required"
assert res["confirmation"]["origin"] == "https://example.com"
assert task.params["_confirmation_checkpoint"]["id"] == res["confirmation"]["id"]
@pytest.mark.asyncio
async def test_tool_request_temp_mail_bypass(mocker):
page = mocker.AsyncMock()
res = await execute_tool("request_temp_mail_bypass", {"email_address": "test@temp-mail.io"}, page)
assert res["status"] == "error"
assert res["error_code"] == "confirmation_required"
@pytest.mark.asyncio
async def test_tool_request_browser_handoff(mocker):
page = mocker.AsyncMock()
page.url = "https://example.com/verify"
page.context.cookies.return_value = [{"name": "auth", "value": "123"}]
res = await execute_tool("request_browser_handoff", {"reason": "captcha"}, page)
assert res["status"] == "handoff_required"
assert "[HANDOFF_REQUIRED|https://example.com/verify|" in res["handoff_signal"]
@pytest.mark.asyncio
async def test_tool_click_security(mocker):
page = mocker.AsyncMock()
page.url = "https://example.com/checkout"
task = MockTask()
# Mock JS eval to return True (simulating a form submit button)
page.evaluate.side_effect = [True]
res = await execute_tool("click", {"node_id": "42"}, page, task=task)
assert "error" in res
assert res["error_code"] == "confirmation_required"
confirmation_id = "confirm-1"
task.params["_approved_confirmation"] = {
"id": confirmation_id,
"origin": "https://example.com",
"expires_at": (datetime.now(timezone.utc) + timedelta(minutes=5)).isoformat(),
"consumed": False,
}
page.evaluate.side_effect = [True, {"x": 100, "y": 200}]
res2 = await execute_tool(
"click",
{"node_id": "42", "confirmation_id": confirmation_id},
page,
task=task,
)
assert res2["status"] == "success"
page.mouse.move.assert_awaited_once()
page.mouse.click.assert_awaited_once_with(100, 200)
assert "_approved_confirmation" not in task.params
@pytest.mark.asyncio
async def test_tool_click_stale_id_returns_without_default_timeout(mocker):
page = mocker.AsyncMock()
setattr(page, "mutation_allowed", True)
page.evaluate.return_value = None
res = await execute_tool("click", {"node_id": "ax-old"}, page)
assert "nicht mehr vorhanden" in res["error"]
page.click.assert_not_awaited()
@pytest.mark.asyncio
async def test_tool_visual_check_returns_only_structured_report(mocker):
page = mocker.AsyncMock()
page.screenshot.return_value = b"fake_image_data"
mocker.patch(
"agent.tools.vision_subagent.analyze",
new=mocker.AsyncMock(return_value={
"status": "pass",
"summary": "Layout ist stabil.",
"issues": [],
"confidence": 0.95,
"suggestedNextAction": "Keine Änderung.",
"model": "google/gemini-2.5-flash-lite",
"costUsd": 0.0003,
}),
)
res = await execute_tool("visual_check", {"goal": "Prüfe das Layout"}, page)
assert res["status"] == "success"
assert res["visual_check"]["status"] == "pass"
assert "image_base64" not in res
@pytest.mark.asyncio
async def test_tool_read_dom(mocker, monkeypatch):
page = mocker.AsyncMock()
# Mock extract_dom_text to avoid running actual JS
async def mock_extract(*args, **kwargs):
return "[BTN:Login] [LINK:Home]"
import browser.accessibility
monkeypatch.setattr(browser.accessibility, "extract_dom_text", mock_extract)
res = await execute_tool("read_dom", {}, page)
assert res["status"] == "success"
assert res["dom_text"] == "[BTN:Login] [LINK:Home]"
@pytest.mark.asyncio
async def test_tool_find_element(mocker):
page = mocker.AsyncMock()
# evaluate returns a JSON string of elements
page.evaluate.return_value = '[{"node_id": "ax-7", "tag": "BUTTON", "text": "Login", "x": 100, "y": 200}]'
res = await execute_tool("find_element", {"query": "login"}, page)
assert res["status"] == "success"
assert len(res["elements"]) == 1
assert res["elements"][0]["tag"] == "BUTTON"
assert res["elements"][0]["node_id"] == "ax-7"
assert res["elements"][0]["x"] == 100
@pytest.mark.asyncio
async def test_tool_read_text_content(mocker):
page = mocker.AsyncMock()
page.evaluate.return_value = "Hello World"
res = await execute_tool("read_text_content", {}, page)
assert res["status"] == "success"
assert res["text_content"] == "Hello World"