Files
browser-use--browser-use/tests/ci/test_file_system_llm_integration.py
wehub-resource-sync 4cd2d4af2b
Test Browser Use CLI Install / uv pip install (macos-latest) (push) Waiting to run
Test Browser Use CLI Install / uv pip install (windows-latest) (push) Waiting to run
Test Browser Use CLI Install / uv pip install (ubuntu-latest) (push) Failing after 1s
Test Browser Use CLI Install / uvx browser-use from local wheel (push) Failing after 1s
Test Browser Use CLI Install / uvx browser-use[cli] from PyPI (push) Failing after 1s
package / pip-install-on-macos-latest-py-3.11 (push) Has been skipped
package / pip-install-on-macos-latest-py-3.13 (push) Has been skipped
package / pip-install-on-ubuntu-latest-py-3.11 (push) Has been skipped
package / pip-install-on-windows-latest-py-3.13 (push) Has been skipped
cloud_evals / trigger_cloud_eval_image_build (push) Failing after 1s
docker / build_publish_image (push) Failing after 1s
Test Browser Use CLI Install / browser-use skill sync (push) Failing after 1s
lint / code-style (push) Failing after 0s
lint / type-checker (push) Failing after 1s
package / pip-build (push) Failing after 1s
lint / syntax-errors (push) Failing after 3s
package / pip-install-on-ubuntu-latest-py-3.13 (push) Has been skipped
package / pip-install-on-windows-latest-py-3.11 (push) Has been skipped
test / ${{ matrix.test_filename }} (push) Has been skipped
test / evaluate-tasks (push) Has been skipped
test / setup-chromium (push) Failing after 2s
test / find_tests (push) Failing after 2s
chore: import upstream snapshot with attribution
2026-07-13 12:02:32 +08:00

387 lines
14 KiB
Python

"""Integration tests for DOCX and image file support in LLM messages."""
import base64
import io
from pathlib import Path
import pytest
from PIL import Image
from browser_use.agent.message_manager.service import MessageManager
from browser_use.agent.prompts import AgentMessagePrompt
from browser_use.agent.views import ActionResult, AgentStepInfo
from browser_use.browser.views import BrowserStateSummary, TabInfo
from browser_use.dom.views import SerializedDOMState
from browser_use.filesystem.file_system import FileSystem
from browser_use.llm.messages import ContentPartImageParam, ContentPartTextParam, SystemMessage
class TestImageInLLMMessages:
"""Test that images flow correctly through to LLM messages."""
def create_test_image(self, width: int = 100, height: int = 100) -> bytes:
"""Create a test image and return bytes."""
img = Image.new('RGB', (width, height), color='red')
buffer = io.BytesIO()
img.save(buffer, format='PNG')
buffer.seek(0)
return buffer.read()
@pytest.mark.asyncio
async def test_image_stored_in_message_manager(self, tmp_path: Path):
"""Test that images are stored in MessageManager state."""
fs = FileSystem(tmp_path)
system_message = SystemMessage(content='Test system message')
mm = MessageManager(task='test', system_message=system_message, file_system=fs)
# Create ActionResult with images
images = [{'name': 'test.png', 'data': 'base64_test_data'}]
action_results = [
ActionResult(
extracted_content='Read image file test.png',
long_term_memory='Read image file test.png',
images=images,
include_extracted_content_only_once=True,
)
]
# Update message manager with results
step_info = AgentStepInfo(step_number=1, max_steps=10)
mm._update_agent_history_description(model_output=None, result=action_results, step_info=step_info)
# Verify images are stored
assert mm.state.read_state_images is not None
assert len(mm.state.read_state_images) == 1
assert mm.state.read_state_images[0]['name'] == 'test.png'
assert mm.state.read_state_images[0]['data'] == 'base64_test_data'
@pytest.mark.asyncio
async def test_images_cleared_after_step(self, tmp_path: Path):
"""Test that images are cleared after each step."""
fs = FileSystem(tmp_path)
system_message = SystemMessage(content='Test system message')
mm = MessageManager(task='test', system_message=system_message, file_system=fs)
# First step with images
images = [{'name': 'test.png', 'data': 'base64_data'}]
action_results = [ActionResult(images=images, include_extracted_content_only_once=True)]
step_info = AgentStepInfo(step_number=1, max_steps=10)
mm._update_agent_history_description(model_output=None, result=action_results, step_info=step_info)
assert len(mm.state.read_state_images) == 1
# Second step without images - should clear
action_results_2 = [ActionResult(extracted_content='No images')]
step_info_2 = AgentStepInfo(step_number=2, max_steps=10)
mm._update_agent_history_description(model_output=None, result=action_results_2, step_info=step_info_2)
assert len(mm.state.read_state_images) == 0
@pytest.mark.asyncio
async def test_multiple_images_accumulated(self, tmp_path: Path):
"""Test that multiple images in one step are accumulated."""
fs = FileSystem(tmp_path)
system_message = SystemMessage(content='Test system message')
mm = MessageManager(task='test', system_message=system_message, file_system=fs)
# Multiple action results with images
action_results = [
ActionResult(images=[{'name': 'img1.png', 'data': 'data1'}], include_extracted_content_only_once=True),
ActionResult(images=[{'name': 'img2.jpg', 'data': 'data2'}], include_extracted_content_only_once=True),
]
step_info = AgentStepInfo(step_number=1, max_steps=10)
mm._update_agent_history_description(model_output=None, result=action_results, step_info=step_info)
assert len(mm.state.read_state_images) == 2
assert mm.state.read_state_images[0]['name'] == 'img1.png'
assert mm.state.read_state_images[1]['name'] == 'img2.jpg'
def test_agent_message_prompt_includes_images(self, tmp_path: Path):
"""Test that AgentMessagePrompt includes images in message content."""
fs = FileSystem(tmp_path)
# Create browser state
browser_state = BrowserStateSummary(
url='https://example.com',
title='Test',
tabs=[TabInfo(target_id='test-0', url='https://example.com', title='Test')],
screenshot=None,
dom_state=SerializedDOMState(_root=None, selector_map={}),
)
# Create images
read_state_images = [{'name': 'test.png', 'data': 'base64_image_data_here'}]
# Create message prompt
prompt = AgentMessagePrompt(
browser_state_summary=browser_state,
file_system=fs,
read_state_images=read_state_images,
)
# Get user message with vision enabled
user_message = prompt.get_user_message(use_vision=True)
# Verify message has content parts (not just string)
assert isinstance(user_message.content, list)
# Find image content parts
image_parts = [part for part in user_message.content if isinstance(part, ContentPartImageParam)]
text_parts = [part for part in user_message.content if isinstance(part, ContentPartTextParam)]
# Should have at least one image
assert len(image_parts) >= 1
# Should have text label
image_labels = [part.text for part in text_parts if 'test.png' in part.text]
assert len(image_labels) >= 1
# Verify image data URL format
img_part = image_parts[0]
assert 'data:image/' in img_part.image_url.url
assert 'base64,base64_image_data_here' in img_part.image_url.url
def test_agent_message_prompt_png_vs_jpg_media_type(self, tmp_path: Path):
"""Test that AgentMessagePrompt correctly detects PNG vs JPG media types."""
fs = FileSystem(tmp_path)
browser_state = BrowserStateSummary(
url='https://example.com',
title='Test',
tabs=[TabInfo(target_id='test-0', url='https://example.com', title='Test')],
screenshot=None,
dom_state=SerializedDOMState(_root=None, selector_map={}),
)
# Test PNG
read_state_images_png = [{'name': 'test.png', 'data': 'data'}]
prompt_png = AgentMessagePrompt(
browser_state_summary=browser_state,
file_system=fs,
read_state_images=read_state_images_png,
)
message_png = prompt_png.get_user_message(use_vision=True)
image_parts_png = [part for part in message_png.content if isinstance(part, ContentPartImageParam)]
assert 'data:image/png;base64' in image_parts_png[0].image_url.url
# Test JPG
read_state_images_jpg = [{'name': 'photo.jpg', 'data': 'data'}]
prompt_jpg = AgentMessagePrompt(
browser_state_summary=browser_state,
file_system=fs,
read_state_images=read_state_images_jpg,
)
message_jpg = prompt_jpg.get_user_message(use_vision=True)
image_parts_jpg = [part for part in message_jpg.content if isinstance(part, ContentPartImageParam)]
assert 'data:image/jpeg;base64' in image_parts_jpg[0].image_url.url
def test_agent_message_prompt_no_images(self, tmp_path: Path):
"""Test that message works correctly when no images are present."""
fs = FileSystem(tmp_path)
browser_state = BrowserStateSummary(
url='https://example.com',
title='Test',
tabs=[TabInfo(target_id='test-0', url='https://example.com', title='Test')],
screenshot=None,
dom_state=SerializedDOMState(_root=None, selector_map={}),
)
# No images
prompt = AgentMessagePrompt(
browser_state_summary=browser_state,
file_system=fs,
read_state_images=[],
)
# Get user message without vision
user_message = prompt.get_user_message(use_vision=False)
# Should be plain text, not content parts
assert isinstance(user_message.content, str)
def test_agent_message_prompt_empty_base64_skipped(self, tmp_path: Path):
"""Test that images with empty base64 data are skipped."""
fs = FileSystem(tmp_path)
browser_state = BrowserStateSummary(
url='https://example.com',
title='Test',
tabs=[TabInfo(target_id='test-0', url='https://example.com', title='Test')],
screenshot=None,
dom_state=SerializedDOMState(_root=None, selector_map={}),
)
# Image with empty data field
read_state_images = [
{'name': 'empty.png', 'data': ''}, # Empty - should be skipped
{'name': 'valid.png', 'data': 'valid_data'}, # Valid
]
prompt = AgentMessagePrompt(
browser_state_summary=browser_state,
file_system=fs,
read_state_images=read_state_images,
)
user_message = prompt.get_user_message(use_vision=True)
image_parts = [part for part in user_message.content if isinstance(part, ContentPartImageParam)]
# Should only have 1 image (the valid one)
assert len(image_parts) == 1
assert 'valid_data' in image_parts[0].image_url.url
class TestDocxInLLMMessages:
"""Test that DOCX content flows correctly through to LLM messages."""
@pytest.mark.asyncio
async def test_docx_in_extracted_content(self, tmp_path: Path):
"""Test that DOCX text appears in extracted_content."""
fs = FileSystem(tmp_path)
# Create DOCX file
content = """# Title
Some important content here."""
await fs.write_file('test.docx', content)
# Read it
result = await fs.read_file('test.docx')
# Verify content is in the result
assert 'Title' in result
assert 'important content' in result
@pytest.mark.asyncio
async def test_docx_in_message_manager(self, tmp_path: Path):
"""Test that DOCX content appears in message manager state."""
fs = FileSystem(tmp_path)
system_message = SystemMessage(content='Test system message')
mm = MessageManager(task='test', system_message=system_message, file_system=fs)
# Simulate read_file action result
docx_content = """Read from file test.docx.
<content>
Title
Some content here.
</content>"""
action_results = [
ActionResult(
extracted_content=docx_content,
long_term_memory='Read file test.docx',
include_extracted_content_only_once=True,
)
]
step_info = AgentStepInfo(step_number=1, max_steps=10)
mm._update_agent_history_description(model_output=None, result=action_results, step_info=step_info)
# Verify it's in read_state_description
assert 'Title' in mm.state.read_state_description
assert 'Some content' in mm.state.read_state_description
class TestEndToEndIntegration:
"""End-to-end tests for file reading and LLM message creation."""
def create_test_image(self) -> bytes:
"""Create a test image."""
img = Image.new('RGB', (50, 50), color='blue')
buffer = io.BytesIO()
img.save(buffer, format='PNG')
buffer.seek(0)
return buffer.read()
@pytest.mark.asyncio
async def test_image_end_to_end(self, tmp_path: Path):
"""Test complete flow: external image → FileSystem → ActionResult → MessageManager → Prompt."""
# Step 1: Create external image
external_file = tmp_path / 'photo.png'
img_bytes = self.create_test_image()
external_file.write_bytes(img_bytes)
# Step 2: Read via FileSystem
fs = FileSystem(tmp_path / 'workspace')
structured_result = await fs.read_file_structured(str(external_file), external_file=True)
assert structured_result['images'] is not None
# Step 3: Create ActionResult (simulating tools/service.py)
action_result = ActionResult(
extracted_content=structured_result['message'],
long_term_memory='Read image file photo.png',
images=structured_result['images'],
include_extracted_content_only_once=True,
)
# Step 4: Process in MessageManager
system_message = SystemMessage(content='Test system message')
mm = MessageManager(task='test', system_message=system_message, file_system=fs)
step_info = AgentStepInfo(step_number=1, max_steps=10)
mm._update_agent_history_description(model_output=None, result=[action_result], step_info=step_info)
# Verify images stored
assert len(mm.state.read_state_images) == 1
assert mm.state.read_state_images[0]['name'] == 'photo.png'
# Step 5: Create message with AgentMessagePrompt
browser_state = BrowserStateSummary(
url='https://example.com',
title='Test',
tabs=[TabInfo(target_id='test-0', url='https://example.com', title='Test')],
screenshot=None,
dom_state=SerializedDOMState(_root=None, selector_map={}),
)
prompt = AgentMessagePrompt(
browser_state_summary=browser_state,
file_system=fs,
read_state_images=mm.state.read_state_images,
)
user_message = prompt.get_user_message(use_vision=True)
# Verify image is in message
assert isinstance(user_message.content, list)
image_parts = [part for part in user_message.content if isinstance(part, ContentPartImageParam)]
assert len(image_parts) >= 1
# Verify image data is correct
base64_str = base64.b64encode(img_bytes).decode('utf-8')
assert base64_str in image_parts[0].image_url.url
@pytest.mark.asyncio
async def test_docx_end_to_end(self, tmp_path: Path):
"""Test complete flow: DOCX file → FileSystem → ActionResult → MessageManager."""
# Step 1: Create DOCX
fs = FileSystem(tmp_path)
docx_content = """# Important Document
This is critical information."""
await fs.write_file('important.docx', docx_content)
# Step 2: Read it
read_result = await fs.read_file('important.docx')
# Step 3: Create ActionResult (simulating tools/service.py)
action_result = ActionResult(
extracted_content=read_result,
long_term_memory=read_result[:100] if len(read_result) > 100 else read_result,
include_extracted_content_only_once=True,
)
# Step 4: Process in MessageManager
system_message = SystemMessage(content='Test system message')
mm = MessageManager(task='test', system_message=system_message, file_system=fs)
step_info = AgentStepInfo(step_number=1, max_steps=10)
mm._update_agent_history_description(model_output=None, result=[action_result], step_info=step_info)
# Verify content is in read_state
assert 'Important Document' in mm.state.read_state_description
assert 'critical information' in mm.state.read_state_description
if __name__ == '__main__':
pytest.main([__file__, '-v'])