File size: 6,576 Bytes
d7b3d84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
"""Test all recording and save functionality for Agent and BrowserSession."""

from pathlib import Path

import pytest

from browser_use import Agent, AgentHistoryList
from browser_use.browser import BrowserProfile, BrowserSession
from tests.ci.conftest import create_mock_llm


@pytest.fixture
def test_dir(tmp_path):
	"""Create a test directory that gets cleaned up after each test."""
	test_path = tmp_path / 'test_recordings'
	test_path.mkdir(exist_ok=True)
	yield test_path


@pytest.fixture
async def httpserver_url(httpserver):
	"""Simple test page."""
	# Use expect_ordered_request with multiple handlers to handle repeated requests
	for _ in range(10):  # Allow up to 10 requests to the same URL
		httpserver.expect_ordered_request('/').respond_with_data(
			"""
			<!DOCTYPE html>
			<html>
			<head>
				<title>Test Page</title>
			</head>
			<body>
				<h1>Test Recording Page</h1>
				<input type="text" id="search" placeholder="Search here" />
				<button type="button" id="submit">Submit</button>
			</body>
			</html>
			""",
			content_type='text/html',
		)
	return httpserver.url_for('/')


@pytest.fixture
def llm():
	"""Create mocked LLM instance for tests."""
	return create_mock_llm()


@pytest.fixture
def interactive_llm(httpserver_url):
	"""Create mocked LLM that navigates to page and interacts with elements."""
	actions = [
		# First action: Navigate to the page
		f"""
		{{
			"thinking": "null",
			"evaluation_previous_goal": "Starting the task",
			"memory": "Need to navigate to the test page",
			"next_goal": "Navigate to the URL",
			"action": [
				{{
					"navigate": {{
						"url": "{httpserver_url}",
						"new_tab": false
					}}
				}}
			]
		}}
		""",
		# Second action: Click in the search box
		"""
		{
			"thinking": "null",
		"evaluation_previous_goal": "Successfully navigated to the page",
		"memory": "Page loaded, can see search box and submit button",
		"next_goal": "Click on the search box to focus it",
		"action": [
			{
				"click": {
					"index": 0
				}
			}
		]
		}
		""",
		# Third action: Type text in the search box
		"""
		{
			"thinking": "null",
			"evaluation_previous_goal": "Clicked on search box",
			"memory": "Search box is focused and ready for input",
			"next_goal": "Type 'test' in the search box",
			"action": [
				{
					"input_text": {
						"index": 0,
						"text": "test"
					}
				}
			]
		}
		""",
		# Fourth action: Click submit button
		"""
		{
			"thinking": "null",
		"evaluation_previous_goal": "Typed 'test' in search box",
		"memory": "Text 'test' has been entered successfully",
		"next_goal": "Click the submit button to complete the task",
		"action": [
			{
				"click": {
					"index": 1
				}
			}
		]
		}
		""",
		# Fifth action: Done - task completed
		"""
		{
			"thinking": "null",
			"evaluation_previous_goal": "Clicked the submit button",
			"memory": "Successfully navigated to the page, typed 'test' in the search box, and clicked submit",
			"next_goal": "Task completed",
			"action": [
				{
					"done": {
						"text": "Task completed - typed 'test' in search box and clicked submit",
						"success": true
					}
				}
			]
		}
		""",
	]
	return create_mock_llm(actions)


class TestAgentRecordings:
	"""Test Agent save_conversation_path and generate_gif parameters."""

	@pytest.mark.parametrize('path_type', ['with_slash', 'without_slash', 'deep_directory'])
	async def test_save_conversation_path(self, test_dir, httpserver_url, llm, path_type):
		"""Test saving conversation with different path types."""
		if path_type == 'with_slash':
			conversation_path = test_dir / 'logs' / 'conversation'
		elif path_type == 'without_slash':
			conversation_path = test_dir / 'logs'
		else:  # deep_directory
			conversation_path = test_dir / 'logs' / 'deep' / 'directory' / 'conversation'

		browser_session = BrowserSession(browser_profile=BrowserProfile(headless=True, disable_security=True, user_data_dir=None))
		await browser_session.start()
		try:
			agent = Agent(
				task=f'go to {httpserver_url} and type "test" in the search box',
				llm=llm,
				browser_session=browser_session,
				save_conversation_path=str(conversation_path),
			)
			history: AgentHistoryList = await agent.run(max_steps=2)

			result = history.final_result()
			assert result is not None

			# Check that the conversation directory and files were created
			assert conversation_path.exists(), f'{path_type}: conversation directory was not created'
			# Files are now always created as conversation_<agent_id>_<step>.txt inside the directory
			conversation_files = list(conversation_path.glob('conversation_*.txt'))
			assert len(conversation_files) > 0, f'{path_type}: conversation file was not created in {conversation_path}'
		finally:
			await browser_session.kill()

	@pytest.mark.skip(reason='TODO: fix')
	@pytest.mark.parametrize('generate_gif', [False, True, 'custom_path'])
	async def test_generate_gif(self, test_dir, httpserver_url, llm, generate_gif):
		"""Test GIF generation with different settings."""
		# Clean up any existing GIFs first
		for gif in Path.cwd().glob('agent_*.gif'):
			gif.unlink()

		gif_param = generate_gif
		expected_gif_path = None

		if generate_gif == 'custom_path':
			expected_gif_path = test_dir / 'custom_agent.gif'
			gif_param = str(expected_gif_path)

		browser_session = BrowserSession(browser_profile=BrowserProfile(headless=True, disable_security=True, user_data_dir=None))
		await browser_session.start()
		try:
			agent = Agent(
				task=f'go to {httpserver_url}',
				llm=llm,
				browser_session=browser_session,
				generate_gif=gif_param,
			)
			history: AgentHistoryList = await agent.run(max_steps=2)

			result = history.final_result()
			assert result is not None

			# Check GIF creation
			if generate_gif is False:
				gif_files = list(Path.cwd().glob('*.gif'))
				assert len(gif_files) == 0, 'GIF file was created when generate_gif=False'
			elif generate_gif is True:
				# With mock LLM that doesn't navigate, all screenshots will be about:blank placeholders
				# So no GIF will be created (this is expected behavior)
				gif_files = list(Path.cwd().glob('agent_history.gif'))
				assert len(gif_files) == 0, 'GIF should not be created when all screenshots are placeholders'
			else:  # custom_path
				assert expected_gif_path is not None, 'expected_gif_path should be set for custom_path'
				# With mock LLM that doesn't navigate, no GIF will be created
				assert not expected_gif_path.exists(), 'GIF should not be created when all screenshots are placeholders'
		finally:
			await browser_session.kill()