# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Unit tests for the AgentEngineSandboxComputer class.""" import time import unittest from unittest.mock import AsyncMock from unittest.mock import MagicMock from unittest.mock import patch from google.adk.integrations.vmaas.sandbox_computer import _STATE_KEY_ACCESS_TOKEN from google.adk.integrations.vmaas.sandbox_computer import _STATE_KEY_AGENT_ENGINE_NAME from google.adk.integrations.vmaas.sandbox_computer import _STATE_KEY_SANDBOX_NAME from google.adk.integrations.vmaas.sandbox_computer import _STATE_KEY_TOKEN_EXPIRY from google.adk.integrations.vmaas.sandbox_computer import AgentEngineSandboxComputer from google.adk.tools.computer_use.base_computer import ComputerEnvironment from google.adk.tools.computer_use.base_computer import ComputerState class TestAgentEngineSandboxComputer(unittest.IsolatedAsyncioTestCase): """Tests for AgentEngineSandboxComputer.""" def setUp(self): """Set up test fixtures.""" self.project_id = "test-project" self.location = "us-central1" self.service_account = "sa@test-project.iam.gserviceaccount.com" def test_init(self): """Test computer initialization.""" computer = AgentEngineSandboxComputer( project_id=self.project_id, location=self.location, service_account_email=self.service_account, ) self.assertEqual(computer._project_id, self.project_id) self.assertEqual(computer._location, self.location) self.assertEqual(computer._service_account_email, self.service_account) self.assertEqual(computer._screen_size, (1280, 720)) def test_init_with_byos(self): """Test initialization with bring-your-own-sandbox.""" agent_engine_name = ( "projects/test/locations/us-central1/reasoningEngines/123" ) sandbox_name = f"{agent_engine_name}/sandboxEnvironments/456" computer = AgentEngineSandboxComputer( project_id=self.project_id, sandbox_name=sandbox_name, ) # Agent engine name should be extracted from sandbox_name self.assertEqual(computer._agent_engine_name, agent_engine_name) self.assertEqual(computer._sandbox_name, sandbox_name) def test_init_with_template_derives_agent_engine(self): """Test agent engine is derived from sandbox_template_name.""" agent_engine_name = ( "projects/test/locations/us-central1/reasoningEngines/123" ) template_name = f"{agent_engine_name}/sandboxEnvironmentTemplates/789" computer = AgentEngineSandboxComputer( project_id=self.project_id, sandbox_template_name=template_name, ) # Agent engine name should be derived from the template name so the # sandbox is created under the template's reasoning engine (no BYOS). self.assertEqual(computer._agent_engine_name, agent_engine_name) self.assertEqual(computer._sandbox_template_name, template_name) def test_init_with_snapshot_derives_agent_engine(self): """Test agent engine is derived from sandbox_snapshot_name.""" agent_engine_name = ( "projects/test/locations/us-central1/reasoningEngines/123" ) snapshot_name = f"{agent_engine_name}/sandboxEnvironmentSnapshots/789" computer = AgentEngineSandboxComputer( project_id=self.project_id, sandbox_snapshot_name=snapshot_name, ) self.assertEqual(computer._agent_engine_name, agent_engine_name) self.assertEqual(computer._sandbox_snapshot_name, snapshot_name) def test_init_sandbox_name_takes_precedence_over_template(self): """Test sandbox_name wins when both sandbox_name and template are set.""" sandbox_engine = ( "projects/test/locations/us-central1/reasoningEngines/sandbox" ) sandbox_name = f"{sandbox_engine}/sandboxEnvironments/456" template_name = ( "projects/test/locations/us-central1/reasoningEngines/template" "/sandboxEnvironmentTemplates/789" ) computer = AgentEngineSandboxComputer( project_id=self.project_id, sandbox_name=sandbox_name, sandbox_template_name=template_name, ) self.assertEqual(computer._agent_engine_name, sandbox_engine) def test_init_without_sandbox_source_has_no_agent_engine(self): """Test agent engine is None when no sandbox source is provided.""" computer = AgentEngineSandboxComputer(project_id=self.project_id) self.assertIsNone(computer._agent_engine_name) async def test_ensure_agent_engine_with_template_name(self): """Test _ensure_agent_engine reuses the template's reasoning engine.""" agent_engine_name = ( "projects/test/locations/us-central1/reasoningEngines/123" ) template_name = f"{agent_engine_name}/sandboxEnvironmentTemplates/789" computer = AgentEngineSandboxComputer(sandbox_template_name=template_name) computer._session_state = {} result = await computer._ensure_agent_engine() self.assertEqual(result, agent_engine_name) # Should not have created or stored a new engine. self.assertNotIn(_STATE_KEY_AGENT_ENGINE_NAME, computer._session_state) def test_init_with_vertexai_client(self): """Test initialization with provided vertexai client.""" mock_client = MagicMock() computer = AgentEngineSandboxComputer(vertexai_client=mock_client) self.assertEqual(computer._client, mock_client) async def test_screen_size(self): """Test screen_size returns hardcoded size.""" computer = AgentEngineSandboxComputer() result = await computer.screen_size() self.assertEqual(result, (1280, 720)) async def test_environment(self): """Test environment returns ENVIRONMENT_BROWSER.""" computer = AgentEngineSandboxComputer() result = await computer.environment() self.assertEqual(result, ComputerEnvironment.ENVIRONMENT_BROWSER) async def test_ensure_agent_engine_with_sandbox_name(self): """Test _ensure_agent_engine extracts agent engine from sandbox_name.""" agent_engine_name = ( "projects/test/locations/us-central1/reasoningEngines/123" ) sandbox_name = f"{agent_engine_name}/sandboxEnvironments/456" computer = AgentEngineSandboxComputer(sandbox_name=sandbox_name) computer._session_state = {} result = await computer._ensure_agent_engine() self.assertEqual(result, agent_engine_name) # Should not have touched session state self.assertNotIn(_STATE_KEY_AGENT_ENGINE_NAME, computer._session_state) async def test_ensure_agent_engine_from_session_state(self): """Test _ensure_agent_engine uses session state value.""" agent_engine_name = ( "projects/test/locations/us-central1/reasoningEngines/123" ) computer = AgentEngineSandboxComputer() computer._session_state = {_STATE_KEY_AGENT_ENGINE_NAME: agent_engine_name} result = await computer._ensure_agent_engine() self.assertEqual(result, agent_engine_name) @patch("google.adk.integrations.vmaas.sandbox_computer.asyncio.to_thread") @patch.object(AgentEngineSandboxComputer, "_get_client") async def test_ensure_agent_engine_creates_new( self, mock_get_client, mock_to_thread ): """Test _ensure_agent_engine creates new agent engine.""" new_engine_name = "projects/test/locations/us-central1/reasoningEngines/new" mock_client = MagicMock() mock_get_client.return_value = mock_client mock_engine = MagicMock() mock_engine.api_resource.name = new_engine_name mock_to_thread.return_value = mock_engine computer = AgentEngineSandboxComputer(project_id=self.project_id) computer._session_state = {} result = await computer._ensure_agent_engine() self.assertEqual(result, new_engine_name) self.assertEqual( computer._session_state[_STATE_KEY_AGENT_ENGINE_NAME], new_engine_name ) @patch("google.adk.integrations.vmaas.sandbox_computer.asyncio.to_thread") @patch.object(AgentEngineSandboxComputer, "_get_client") async def test_get_sandbox_with_constructor_value( self, mock_get_client, mock_to_thread ): """Test _get_sandbox uses constructor value (BYOS mode).""" sandbox_name = "projects/test/sandboxEnvironments/123" mock_sandbox = MagicMock() mock_sandbox.name = sandbox_name mock_to_thread.return_value = mock_sandbox mock_client = MagicMock() mock_get_client.return_value = mock_client computer = AgentEngineSandboxComputer(sandbox_name=sandbox_name) computer._session_state = {} result_name, result_sandbox = await computer._get_sandbox() self.assertEqual(result_name, sandbox_name) self.assertEqual(result_sandbox, mock_sandbox) @patch("google.adk.integrations.vmaas.sandbox_computer.asyncio.to_thread") @patch.object(AgentEngineSandboxComputer, "_get_client") async def test_get_sandbox_from_session_state( self, mock_get_client, mock_to_thread ): """Test _get_sandbox uses session state value.""" sandbox_name = "projects/test/sandboxEnvironments/123" mock_sandbox = MagicMock() mock_to_thread.return_value = mock_sandbox mock_client = MagicMock() mock_get_client.return_value = mock_client computer = AgentEngineSandboxComputer() computer._session_state = {_STATE_KEY_SANDBOX_NAME: sandbox_name} result_name, result_sandbox = await computer._get_sandbox() self.assertEqual(result_name, sandbox_name) self.assertEqual(result_sandbox, mock_sandbox) async def test_get_access_token_cached(self): """Test _get_access_token uses cached token.""" sandbox_name = "projects/test/sandboxEnvironments/123" cached_token = "cached_token_123" # Set expiry far in the future token_expiry = time.time() + 3600 computer = AgentEngineSandboxComputer() computer._session_state = { _STATE_KEY_ACCESS_TOKEN: cached_token, _STATE_KEY_TOKEN_EXPIRY: token_expiry, } result = await computer._get_access_token(sandbox_name) self.assertEqual(result, cached_token) @patch("google.adk.integrations.vmaas.sandbox_computer.asyncio.to_thread") @patch.object(AgentEngineSandboxComputer, "_get_client") async def test_get_access_token_generates_new_when_expired( self, mock_get_client, mock_to_thread ): """Test _get_access_token generates new token when expired.""" sandbox_name = "projects/test/sandboxEnvironments/123" new_token = "new_token_456" # Set expiry in the past token_expiry = time.time() - 100 mock_to_thread.return_value = new_token mock_client = MagicMock() mock_get_client.return_value = mock_client computer = AgentEngineSandboxComputer( service_account_email=self.service_account ) computer._session_state = { _STATE_KEY_ACCESS_TOKEN: "old_token", _STATE_KEY_TOKEN_EXPIRY: token_expiry, } result = await computer._get_access_token(sandbox_name) self.assertEqual(result, new_token) self.assertEqual( computer._session_state[_STATE_KEY_ACCESS_TOKEN], new_token ) @patch.object(AgentEngineSandboxComputer, "_get_sandbox_client") async def test_click_at(self, mock_get_client): """Test click_at method.""" mock_client = AsyncMock() mock_client.click_at = AsyncMock() mock_client.get_screenshot = AsyncMock(return_value=b"png_data") mock_client.get_current_url = AsyncMock(return_value="https://example.com") mock_get_client.return_value = mock_client computer = AgentEngineSandboxComputer() computer._session_state = {} result = await computer.click_at(100, 200) mock_client.click_at.assert_called_once_with(100, 200) self.assertIsInstance(result, ComputerState) self.assertEqual(result.screenshot, b"png_data") self.assertEqual(result.url, "https://example.com") @patch.object(AgentEngineSandboxComputer, "_get_sandbox_client") async def test_hover_at(self, mock_get_client): """Test hover_at method.""" mock_client = AsyncMock() mock_client.hover_at = AsyncMock() mock_client.get_screenshot = AsyncMock(return_value=b"png_data") mock_client.get_current_url = AsyncMock(return_value="https://example.com") mock_get_client.return_value = mock_client computer = AgentEngineSandboxComputer() computer._session_state = {} result = await computer.hover_at(150, 250) mock_client.hover_at.assert_called_once_with(150, 250) self.assertIsInstance(result, ComputerState) @patch.object(AgentEngineSandboxComputer, "_get_sandbox_client") async def test_type_text_at(self, mock_get_client): """Test type_text_at method.""" mock_client = AsyncMock() mock_client.type_text_at = AsyncMock() mock_client.get_screenshot = AsyncMock(return_value=b"png_data") mock_client.get_current_url = AsyncMock(return_value="https://example.com") mock_get_client.return_value = mock_client computer = AgentEngineSandboxComputer() computer._session_state = {} result = await computer.type_text_at( 100, 200, "hello", press_enter=True, clear_before_typing=False, ) mock_client.type_text_at.assert_called_once_with( x=100, y=200, text="hello", press_enter=True, clear_before_typing=False, ) self.assertIsInstance(result, ComputerState) @patch.object(AgentEngineSandboxComputer, "_get_sandbox_client") async def test_scroll_document(self, mock_get_client): """Test scroll_document method.""" mock_client = AsyncMock() mock_client.scroll_at = AsyncMock() mock_client.get_screenshot = AsyncMock(return_value=b"png_data") mock_client.get_current_url = AsyncMock(return_value="https://example.com") mock_get_client.return_value = mock_client computer = AgentEngineSandboxComputer() computer._session_state = {} result = await computer.scroll_document("down") # Should scroll at center of screen mock_client.scroll_at.assert_called_once_with(640, 360, "down", 400) self.assertIsInstance(result, ComputerState) @patch.object(AgentEngineSandboxComputer, "_get_sandbox_client") async def test_scroll_at(self, mock_get_client): """Test scroll_at method.""" mock_client = AsyncMock() mock_client.scroll_at = AsyncMock() mock_client.get_screenshot = AsyncMock(return_value=b"png_data") mock_client.get_current_url = AsyncMock(return_value="https://example.com") mock_get_client.return_value = mock_client computer = AgentEngineSandboxComputer() computer._session_state = {} result = await computer.scroll_at(100, 200, "up", 500) mock_client.scroll_at.assert_called_once_with(100, 200, "up", 500) self.assertIsInstance(result, ComputerState) @patch.object(AgentEngineSandboxComputer, "_get_sandbox_client") async def test_navigate(self, mock_get_client): """Test navigate method.""" mock_client = AsyncMock() mock_client.navigate = AsyncMock() mock_client.get_screenshot = AsyncMock(return_value=b"png_data") mock_client.get_current_url = AsyncMock(return_value="https://newsite.com") mock_get_client.return_value = mock_client computer = AgentEngineSandboxComputer() computer._session_state = {} result = await computer.navigate("https://newsite.com") mock_client.navigate.assert_called_once_with("https://newsite.com") self.assertIsInstance(result, ComputerState) self.assertEqual(result.url, "https://newsite.com") @patch.object(AgentEngineSandboxComputer, "_get_sandbox_client") async def test_search(self, mock_get_client): """Test search method navigates to search engine.""" mock_client = AsyncMock() mock_client.navigate = AsyncMock() mock_client.get_screenshot = AsyncMock(return_value=b"png_data") mock_client.get_current_url = AsyncMock( return_value="https://www.google.com" ) mock_get_client.return_value = mock_client computer = AgentEngineSandboxComputer( search_engine_url="https://www.google.com" ) computer._session_state = {} result = await computer.search() mock_client.navigate.assert_called_once_with("https://www.google.com") self.assertIsInstance(result, ComputerState) @patch.object(AgentEngineSandboxComputer, "_get_sandbox_client") async def test_go_back(self, mock_get_client): """Test go_back method.""" mock_client = AsyncMock() mock_client.go_back = AsyncMock() mock_client.get_screenshot = AsyncMock(return_value=b"png_data") mock_client.get_current_url = AsyncMock(return_value="https://prev.com") mock_get_client.return_value = mock_client computer = AgentEngineSandboxComputer() computer._session_state = {} result = await computer.go_back() mock_client.go_back.assert_called_once() self.assertIsInstance(result, ComputerState) @patch.object(AgentEngineSandboxComputer, "_get_sandbox_client") async def test_go_forward(self, mock_get_client): """Test go_forward method.""" mock_client = AsyncMock() mock_client.go_forward = AsyncMock() mock_client.get_screenshot = AsyncMock(return_value=b"png_data") mock_client.get_current_url = AsyncMock(return_value="https://next.com") mock_get_client.return_value = mock_client computer = AgentEngineSandboxComputer() computer._session_state = {} result = await computer.go_forward() mock_client.go_forward.assert_called_once() self.assertIsInstance(result, ComputerState) @patch.object(AgentEngineSandboxComputer, "_get_sandbox_client") async def test_key_combination(self, mock_get_client): """Test key_combination method.""" mock_client = AsyncMock() mock_client.key_combination = AsyncMock() mock_client.get_screenshot = AsyncMock(return_value=b"png_data") mock_client.get_current_url = AsyncMock(return_value="https://example.com") mock_get_client.return_value = mock_client computer = AgentEngineSandboxComputer() computer._session_state = {} result = await computer.key_combination(["control", "c"]) mock_client.key_combination.assert_called_once_with(["control", "c"]) self.assertIsInstance(result, ComputerState) @patch.object(AgentEngineSandboxComputer, "_get_sandbox_client") async def test_drag_and_drop(self, mock_get_client): """Test drag_and_drop method.""" mock_client = AsyncMock() mock_client.drag_and_drop = AsyncMock() mock_client.get_screenshot = AsyncMock(return_value=b"png_data") mock_client.get_current_url = AsyncMock(return_value="https://example.com") mock_get_client.return_value = mock_client computer = AgentEngineSandboxComputer() computer._session_state = {} result = await computer.drag_and_drop(10, 20, 100, 200) mock_client.drag_and_drop.assert_called_once_with(10, 20, 100, 200) self.assertIsInstance(result, ComputerState) @patch.object(AgentEngineSandboxComputer, "_get_sandbox_client") async def test_wait(self, mock_get_client): """Test wait method.""" mock_client = AsyncMock() mock_client.get_screenshot = AsyncMock(return_value=b"png_data") mock_client.get_current_url = AsyncMock(return_value="https://example.com") mock_get_client.return_value = mock_client computer = AgentEngineSandboxComputer() computer._session_state = {} # Use a short wait time for testing start_time = time.time() result = await computer.wait(1) elapsed = time.time() - start_time self.assertGreaterEqual(elapsed, 0.9) # Allow some margin self.assertIsInstance(result, ComputerState) @patch.object(AgentEngineSandboxComputer, "_get_sandbox_client") async def test_current_state(self, mock_get_client): """Test current_state method.""" mock_client = AsyncMock() mock_client.get_screenshot = AsyncMock(return_value=b"png_data") mock_client.get_current_url = AsyncMock(return_value="https://example.com") mock_get_client.return_value = mock_client computer = AgentEngineSandboxComputer() computer._session_state = {} result = await computer.current_state() self.assertIsInstance(result, ComputerState) self.assertEqual(result.screenshot, b"png_data") self.assertEqual(result.url, "https://example.com") @patch.object(AgentEngineSandboxComputer, "_get_sandbox_client") async def test_open_web_browser(self, mock_get_client): """Test open_web_browser method returns current state.""" mock_client = AsyncMock() mock_client.get_screenshot = AsyncMock(return_value=b"png_data") mock_client.get_current_url = AsyncMock(return_value="about:blank") mock_get_client.return_value = mock_client computer = AgentEngineSandboxComputer() computer._session_state = {} result = await computer.open_web_browser() # open_web_browser is a no-op for sandbox, just returns current state self.assertIsInstance(result, ComputerState) async def test_initialize_is_noop(self): """Test initialize does nothing (lazy provisioning).""" computer = AgentEngineSandboxComputer() # Should not raise await computer.initialize() async def test_close_is_noop(self): """Test close does nothing (TTL-based cleanup).""" computer = AgentEngineSandboxComputer() # Should not raise await computer.close() if __name__ == "__main__": unittest.main()