Skip to content

Commit c44efc8

Browse files
committed
Add basic unit tests.
1 parent 42849c3 commit c44efc8

File tree

3 files changed

+211
-1
lines changed

3 files changed

+211
-1
lines changed

apiserver/app.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,8 @@ async def delete_session(
188188

189189

190190
# Static HTML5 to test the API.
191-
app.mount("/", StaticFiles(directory="static", html=True), name="static")
191+
static_dir = os.path.join(os.path.dirname(__file__), "static")
192+
app.mount("/", StaticFiles(directory=static_dir, html=True), name="static")
192193

193194
if __name__ == "__main__":
194195
port = int(os.environ.get("PORT", "8000"))

test_agent.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import unittest
16+
from unittest.mock import MagicMock, patch
17+
from google.genai import types
18+
from agent import BrowserAgent, multiply_numbers
19+
from computers import EnvState
20+
21+
class TestBrowserAgent(unittest.TestCase):
22+
def setUp(self):
23+
self.mock_browser_computer = MagicMock()
24+
self.mock_browser_computer.screen_size.return_value = (1000, 1000)
25+
self.agent = BrowserAgent(
26+
browser_computer=self.mock_browser_computer,
27+
query="test query",
28+
model_name="test_model"
29+
)
30+
# Mock the genai client
31+
self.agent._client = MagicMock()
32+
33+
def test_multiply_numbers(self):
34+
self.assertEqual(multiply_numbers(2, 3), {"result": 5})
35+
36+
def test_handle_action_open_web_browser(self):
37+
action = types.FunctionCall(name="open_web_browser", args={})
38+
self.agent.handle_action(action)
39+
self.mock_browser_computer.open_web_browser.assert_called_once()
40+
41+
def test_handle_action_click_at(self):
42+
action = types.FunctionCall(name="click_at", args={"x": 100, "y": 200})
43+
self.agent.handle_action(action)
44+
self.mock_browser_computer.click_at.assert_called_once_with(x=100, y=200)
45+
46+
def test_handle_action_type_text_at(self):
47+
action = types.FunctionCall(name="type_text_at", args={"x": 100, "y": 200, "text": "hello"})
48+
self.agent.handle_action(action)
49+
self.mock_browser_computer.type_text_at.assert_called_once_with(
50+
x=100, y=200, text="hello", press_enter=False, clear_before_typing=True
51+
)
52+
53+
def test_handle_action_scroll_document(self):
54+
action = types.FunctionCall(name="scroll_document", args={"direction": "down"})
55+
self.agent.handle_action(action)
56+
self.mock_browser_computer.scroll_document.assert_called_once_with("down")
57+
58+
def test_handle_action_navigate(self):
59+
action = types.FunctionCall(name="navigate", args={"url": "https://example.com"})
60+
self.agent.handle_action(action)
61+
self.mock_browser_computer.navigate.assert_called_once_with("https://example.com")
62+
63+
def test_handle_action_unknown_function(self):
64+
action = types.FunctionCall(name="unknown_function", args={})
65+
with self.assertRaises(ValueError):
66+
self.agent.handle_action(action)
67+
68+
def test_normalize_x(self):
69+
self.assertEqual(self.agent.normalize_x(500), 500)
70+
71+
def test_normalize_y(self):
72+
self.assertEqual(self.agent.normalize_y(500), 500)
73+
74+
@patch('agent.BrowserAgent.get_model_response')
75+
def test_run_one_iteration_no_function_calls(self, mock_get_model_response):
76+
mock_response = MagicMock()
77+
mock_candidate = MagicMock()
78+
mock_candidate.content.parts = [types.Part(text="some reasoning")]
79+
mock_response.candidates = [mock_candidate]
80+
mock_get_model_response.return_value = mock_response
81+
82+
result = self.agent.run_one_iteration()
83+
84+
self.assertEqual(result, "COMPLETE")
85+
self.assertEqual(len(self.agent._contents), 2)
86+
self.assertEqual(self.agent._contents[1], mock_candidate.content)
87+
88+
@patch('agent.BrowserAgent.get_model_response')
89+
@patch('agent.BrowserAgent._execute_function_call')
90+
def test_run_one_iteration_with_function_call(self, mock_execute_function_call, mock_get_model_response):
91+
mock_response = MagicMock()
92+
mock_candidate = MagicMock()
93+
function_call = types.FunctionCall(name="navigate", args={"url": "https://example.com"})
94+
mock_candidate.content.parts = [types.Part(function_call=function_call)]
95+
mock_response.candidates = [mock_candidate]
96+
mock_get_model_response.return_value = mock_response
97+
98+
mock_env_state = EnvState(screenshot=b"screenshot", url="https://example.com")
99+
mock_execute_function_call.return_value = mock_env_state
100+
101+
result = self.agent.run_one_iteration()
102+
103+
self.assertEqual(result, "CONTINUE")
104+
mock_execute_function_call.assert_called_once_with(function_call)
105+
self.assertEqual(len(self.agent._contents), 3)
106+
107+
108+
if __name__ == "__main__":
109+
unittest.main()

test_main.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import unittest
16+
from unittest.mock import patch, MagicMock
17+
import main
18+
19+
class TestMain(unittest.TestCase):
20+
21+
@patch('main.argparse.ArgumentParser')
22+
@patch('main.CloudRunComputer')
23+
@patch('main.BrowserAgent')
24+
def test_main_cloud_run(self, mock_browser_agent, mock_cloud_run_computer, mock_arg_parser):
25+
mock_args = MagicMock()
26+
mock_args.env = 'cloud-run'
27+
mock_args.api_server = 'test_server'
28+
mock_args.api_server_key = 'test_key'
29+
mock_args.query = 'test_query'
30+
mock_args.model = 'test_model'
31+
mock_arg_parser.return_value.parse_args.return_value = mock_args
32+
33+
main.main()
34+
35+
mock_cloud_run_computer.assert_called_once_with(
36+
api_server='test_server',
37+
screen_size=main.CLOUD_RUN_SCREEN_SIZE,
38+
api_key='test_key'
39+
)
40+
mock_browser_agent.assert_called_once()
41+
mock_browser_agent.return_value.agent_loop.assert_called_once()
42+
43+
@patch('main.argparse.ArgumentParser')
44+
@patch('main.PlaywrightComputer')
45+
@patch('main.BrowserAgent')
46+
def test_main_playwright(self, mock_browser_agent, mock_playwright_computer, mock_arg_parser):
47+
mock_args = MagicMock()
48+
mock_args.env = 'playwright'
49+
mock_args.initial_url = 'test_url'
50+
mock_args.highlight_mouse = True
51+
mock_args.query = 'test_query'
52+
mock_args.model = 'test_model'
53+
mock_args.api_server = None
54+
mock_args.api_server_key = None
55+
mock_arg_parser.return_value.parse_args.return_value = mock_args
56+
57+
main.main()
58+
59+
mock_playwright_computer.assert_called_once_with(
60+
screen_size=main.PLAYWRIGHT_SCREEN_SIZE,
61+
initial_url='test_url',
62+
highlight_mouse=True
63+
)
64+
mock_browser_agent.assert_called_once()
65+
mock_browser_agent.return_value.agent_loop.assert_called_once()
66+
67+
@patch('main.argparse.ArgumentParser')
68+
@patch('main.BrowserbaseComputer')
69+
@patch('main.BrowserAgent')
70+
def test_main_browserbase(self, mock_browser_agent, mock_browserbase_computer, mock_arg_parser):
71+
mock_args = MagicMock()
72+
mock_args.env = 'browserbase'
73+
mock_args.query = 'test_query'
74+
mock_args.model = 'test_model'
75+
mock_args.api_server = None
76+
mock_args.api_server_key = None
77+
mock_args.initial_url = 'https://www.google.com'
78+
mock_args.highlight_mouse = False
79+
mock_arg_parser.return_value.parse_args.return_value = mock_args
80+
81+
main.main()
82+
83+
mock_browserbase_computer.assert_called_once_with(
84+
screen_size=main.PLAYWRIGHT_SCREEN_SIZE
85+
)
86+
mock_browser_agent.assert_called_once()
87+
mock_browser_agent.return_value.agent_loop.assert_called_once()
88+
89+
@patch('main.argparse.ArgumentParser')
90+
def test_main_cloud_run_no_api_server(self, mock_arg_parser):
91+
mock_args = MagicMock()
92+
mock_args.env = 'cloud-run'
93+
mock_args.api_server = None
94+
mock_arg_parser.return_value.parse_args.return_value = mock_args
95+
96+
with self.assertRaises(AssertionError):
97+
main.main()
98+
99+
if __name__ == '__main__':
100+
unittest.main()

0 commit comments

Comments
 (0)