feat(mcp): Add three new tools to control browser sessions

Now you can open a browser, keep using it for other requests as you want, and close it when you want.
This commit is contained in:
Karim shoair
2026-03-28 00:16:14 +02:00
parent a356dd2f2b
commit c458ab65a2
2 changed files with 441 additions and 54 deletions
+120 -1
View File
@@ -1,7 +1,14 @@
import pytest
import pytest_httpbin
from scrapling.core.ai import ScraplingMCPServer, ResponseModel, _normalize_credentials
from scrapling.core.ai import (
ScraplingMCPServer,
ResponseModel,
SessionInfo,
SessionCreatedModel,
SessionClosedModel,
_normalize_credentials,
)
@pytest_httpbin.use_class_based_httpbin
@@ -59,6 +66,118 @@ class TestMCPServer:
assert all(isinstance(r, ResponseModel) for r in result)
@pytest_httpbin.use_class_based_httpbin
class TestSessionManagement:
"""Test persistent browser session management"""
@pytest.fixture(scope="class")
def test_url(self, httpbin):
return f"{httpbin.url}/html"
@pytest.fixture
def server(self):
return ScraplingMCPServer()
@pytest.mark.asyncio
async def test_open_and_close_session(self, server):
"""Test opening and closing a dynamic session"""
result = await server.open_session(session_type="dynamic", headless=True)
assert isinstance(result, SessionCreatedModel)
assert result.session_type == "dynamic"
assert result.is_alive is True
session_id = result.session_id
# Close the session
closed = await server.close_session(session_id)
assert isinstance(closed, SessionClosedModel)
assert closed.session_id == session_id
@pytest.mark.asyncio
async def test_list_sessions(self, server):
"""Test listing sessions"""
# Initially empty
sessions = await server.list_sessions()
assert sessions == []
# Open a session
result = await server.open_session(session_type="dynamic", headless=True)
session_id = result.session_id
# List should show it
sessions = await server.list_sessions()
assert len(sessions) == 1
assert isinstance(sessions[0], SessionInfo)
assert sessions[0].session_id == session_id
assert sessions[0].session_type == "dynamic"
assert sessions[0].is_alive is True
# Cleanup
await server.close_session(session_id)
@pytest.mark.asyncio
async def test_fetch_with_session(self, server, test_url):
"""Test fetching with a persistent dynamic session"""
result = await server.open_session(session_type="dynamic", headless=True)
session_id = result.session_id
# Fetch using the session
response = await server.fetch(url=test_url, session_id=session_id)
assert isinstance(response, ResponseModel)
assert response.status == 200
# Fetch again with the same session (reuse)
response2 = await server.fetch(url=test_url, session_id=session_id)
assert isinstance(response2, ResponseModel)
assert response2.status == 200
await server.close_session(session_id)
@pytest.mark.asyncio
async def test_bulk_fetch_with_session(self, server, test_url):
"""Test bulk fetching with a persistent dynamic session"""
result = await server.open_session(session_type="dynamic", headless=True, max_pages=5)
session_id = result.session_id
responses = await server.bulk_fetch(urls=[test_url, test_url], session_id=session_id)
assert len(responses) == 2
assert all(isinstance(r, ResponseModel) for r in responses)
await server.close_session(session_id)
@pytest.mark.asyncio
async def test_session_type_mismatch(self, server, test_url):
"""Test that using a dynamic session with stealthy_fetch raises an error"""
result = await server.open_session(session_type="dynamic", headless=True)
session_id = result.session_id
with pytest.raises(ValueError, match="'dynamic' session"):
await server.stealthy_fetch(url=test_url, session_id=session_id)
await server.close_session(session_id)
@pytest.mark.asyncio
async def test_close_nonexistent_session(self, server):
"""Test closing a session that doesn't exist"""
with pytest.raises(ValueError, match="not found"):
await server.close_session("nonexistent")
@pytest.mark.asyncio
async def test_fetch_with_nonexistent_session(self, server, test_url):
"""Test fetching with a session ID that doesn't exist"""
with pytest.raises(ValueError, match="not found"):
await server.fetch(url=test_url, session_id="nonexistent")
@pytest.mark.asyncio
async def test_fetch_with_closed_session(self, server, test_url):
"""Test fetching with a session that has been closed"""
result = await server.open_session(session_type="dynamic", headless=True)
session_id = result.session_id
await server.close_session(session_id)
with pytest.raises(ValueError, match="not found"):
await server.fetch(url=test_url, session_id=session_id)
class TestNormalizeCredentials:
"""Test the _normalize_credentials helper"""