Skip to content

Commit e1e9336

Browse files
committed
feat: add session tracking and cleanup for AgentCoreBrowser
Implement proper session lifecycle management for browser instances: Code changes: - Add _client_dict to track active browser sessions by session_id - Populate _client_dict in create_browser_session() for session tracking - Register atexit handler in __init__() to ensure cleanup on process exit - Ensures browser sessions are properly closed when process terminates Tests: - test_bedrock_browser_create_browser_session_hydrates_client_dict: Verifies _client_dict is populated when sessions are created - test_bedrock_browser_registers_atexit_handler: Confirms cleanup handler is registered at initialization This prevents resource leaks from orphaned browser sessions and ensures all active sessions are tracked and cleaned up properly.
1 parent 8064644 commit e1e9336

File tree

2 files changed

+53
-1
lines changed

2 files changed

+53
-1
lines changed

src/strands_tools/browser/agent_core_browser.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
AWS-hosted browser instances.
66
"""
77

8+
import atexit
89
import logging
910
from typing import Dict, Optional
1011

@@ -36,6 +37,7 @@ def __init__(self, region: Optional[str] = None, identifier: Optional[str] = Non
3637
self.identifier = identifier or "aws.browser.v1"
3738
self.session_timeout = session_timeout
3839
self._client_dict: Dict[str, AgentCoreBrowserClient] = {}
40+
atexit.register(super().close_platform)
3941

4042
def start_platform(self) -> None:
4143
"""Remote platform does not need additional initialization steps."""
@@ -49,6 +51,7 @@ async def create_browser_session(self) -> PlaywrightBrowser:
4951
# Create new browser client for this session
5052
session_client = AgentCoreBrowserClient(region=self.region)
5153
session_id = session_client.start(identifier=self.identifier, session_timeout_seconds=self.session_timeout)
54+
self._client_dict[session_id] = session_client
5255

5356
logger.info(f"started Bedrock AgentCore browser session: {session_id}")
5457

tests/browser/test_agent_core_browser.py

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Unit tests for the AgentCoreBrowser implementation.
33
"""
44

5-
from unittest.mock import Mock
5+
from unittest.mock import Mock, patch
66

77
import pytest
88

@@ -17,6 +17,21 @@ def test_bedrock_browser_initialization():
1717
assert browser._client_dict == {}
1818

1919

20+
def test_bedrock_browser_registers_atexit_handler():
21+
"""Test that close_platform is registered with atexit on initialization."""
22+
23+
with patch("strands_tools.browser.agent_core_browser.atexit.register") as mock_atexit:
24+
AgentCoreBrowser()
25+
26+
# Verify atexit.register was called once
27+
mock_atexit.assert_called_once()
28+
29+
# Verify it registered the parent class's close_platform method
30+
# (atexit.register is called with super().close_platform)
31+
registered_func = mock_atexit.call_args[0][0]
32+
assert callable(registered_func)
33+
34+
2035
def test_bedrock_browser_with_custom_params():
2136
"""Test AgentCoreBrowser initialization with custom parameters."""
2237
browser = AgentCoreBrowser(region="us-east-1", session_timeout=7200)
@@ -58,6 +73,40 @@ async def test_bedrock_browser_create_browser_session_no_playwright():
5873
await browser.create_browser_session()
5974

6075

76+
@pytest.mark.asyncio
77+
async def test_bedrock_browser_create_browser_session_hydrates_client_dict():
78+
"""Test that _client_dict is hydrated when create_browser_session is called."""
79+
from unittest.mock import AsyncMock, patch
80+
81+
browser = AgentCoreBrowser()
82+
83+
# Mock playwright
84+
mock_playwright = Mock()
85+
mock_chromium = Mock()
86+
mock_browser = Mock()
87+
mock_playwright.chromium = mock_chromium
88+
mock_chromium.connect_over_cdp = AsyncMock(return_value=mock_browser)
89+
browser._playwright = mock_playwright
90+
91+
# Mock BrowserClient
92+
mock_client = Mock()
93+
mock_session_id = "test-session-123"
94+
mock_client.start.return_value = mock_session_id
95+
mock_client.generate_ws_headers.return_value = ("ws://test-url", {"header": "value"})
96+
97+
with patch("strands_tools.browser.agent_core_browser.AgentCoreBrowserClient", return_value=mock_client):
98+
# Verify _client_dict is empty before
99+
assert browser._client_dict == {}
100+
101+
# Create browser session
102+
await browser.create_browser_session()
103+
104+
# Verify _client_dict is hydrated with the session
105+
assert mock_session_id in browser._client_dict
106+
assert browser._client_dict[mock_session_id] == mock_client
107+
assert len(browser._client_dict) == 1
108+
109+
61110
def test_bedrock_browser_start_platform():
62111
"""Test AgentCoreBrowser browser start platform (should be no-op)."""
63112
browser = AgentCoreBrowser()

0 commit comments

Comments
 (0)