kolega-code 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kolega_code/__init__.py +151 -0
- kolega_code/agent/__init__.py +42 -0
- kolega_code/agent/baseagent.py +998 -0
- kolega_code/agent/browseragent.py +123 -0
- kolega_code/agent/coder.py +157 -0
- kolega_code/agent/common.py +41 -0
- kolega_code/agent/compression.py +81 -0
- kolega_code/agent/context.py +112 -0
- kolega_code/agent/conversation.py +408 -0
- kolega_code/agent/generalagent.py +146 -0
- kolega_code/agent/investigationagent.py +123 -0
- kolega_code/agent/planningagent.py +187 -0
- kolega_code/agent/prompt_provider.py +196 -0
- kolega_code/agent/prompt_templates/agents/browser.j2 +102 -0
- kolega_code/agent/prompt_templates/agents/coder_cli_mode.j2 +127 -0
- kolega_code/agent/prompt_templates/agents/general.j2 +68 -0
- kolega_code/agent/prompt_templates/agents/investigation.j2 +72 -0
- kolega_code/agent/prompt_templates/common/frontend_guidance.md +36 -0
- kolega_code/agent/prompt_templates/common/kolega_md_instructions.md +14 -0
- kolega_code/agent/prompt_templates/environment_variables/workspace_env_vars.md +11 -0
- kolega_code/agent/prompt_templates/template_guidance/expo-template.md +379 -0
- kolega_code/agent/prompt_templates/template_guidance/html-website-template.md +3 -0
- kolega_code/agent/prompt_templates/template_guidance/mern-stack-template.md +3 -0
- kolega_code/agent/prompt_templates/template_guidance/react-vite-shadcdn-template.md +182 -0
- kolega_code/agent/prompts.py +192 -0
- kolega_code/agent/tests/__init__.py +0 -0
- kolega_code/agent/tests/llm/__init__.py +0 -0
- kolega_code/agent/tests/llm/test_anthropic_token_counting.py +633 -0
- kolega_code/agent/tests/llm/test_billing_openai_cache.py +74 -0
- kolega_code/agent/tests/llm/test_client.py +773 -0
- kolega_code/agent/tests/llm/test_dashscope_mapping.py +32 -0
- kolega_code/agent/tests/llm/test_error_boundary.py +322 -0
- kolega_code/agent/tests/llm/test_exceptions.py +249 -0
- kolega_code/agent/tests/llm/test_instrumented_client.py +536 -0
- kolega_code/agent/tests/llm/test_instrumented_client_integration.py +547 -0
- kolega_code/agent/tests/llm/test_langfuse_normalization.py +39 -0
- kolega_code/agent/tests/llm/test_model_specs.py +17 -0
- kolega_code/agent/tests/llm/test_openai_cached_tokens.py +58 -0
- kolega_code/agent/tests/llm/test_openai_cached_tokens_stream.py +74 -0
- kolega_code/agent/tests/llm/test_openai_message_conversion.py +30 -0
- kolega_code/agent/tests/llm/test_openai_token_counting.py +687 -0
- kolega_code/agent/tests/llm/test_tool_execution_ids.py +193 -0
- kolega_code/agent/tests/services/__init__.py +1 -0
- kolega_code/agent/tests/services/test_browser.py +447 -0
- kolega_code/agent/tests/services/test_browser_parity.py +353 -0
- kolega_code/agent/tests/services/test_file_system.py +699 -0
- kolega_code/agent/tests/services/test_sandbox_terminal_input.py +98 -0
- kolega_code/agent/tests/services/test_terminal.py +154 -0
- kolega_code/agent/tests/services/test_terminal_command_tracking.py +385 -0
- kolega_code/agent/tests/services/test_terminal_state_serializer.py +262 -0
- kolega_code/agent/tests/test_agent_tools_inventory.py +267 -0
- kolega_code/agent/tests/test_base_agent.py +1942 -0
- kolega_code/agent/tests/test_coder_attachments.py +330 -0
- kolega_code/agent/tests/test_coder_prompt_extensions.py +61 -0
- kolega_code/agent/tests/test_commands.py +179 -0
- kolega_code/agent/tests/test_duplicate_tool_results.py +556 -0
- kolega_code/agent/tests/test_empty_message_handling.py +48 -0
- kolega_code/agent/tests/test_general_agent.py +242 -0
- kolega_code/agent/tests/test_html.py +320 -0
- kolega_code/agent/tests/test_parallel_tool_calls.py +291 -0
- kolega_code/agent/tests/test_planning_agent.py +227 -0
- kolega_code/agent/tests/test_prompt_provider.py +271 -0
- kolega_code/agent/tests/test_tool_registry.py +102 -0
- kolega_code/agent/tests/test_tools.py +549 -0
- kolega_code/agent/tests/tool_backend/__init__.py +0 -0
- kolega_code/agent/tests/tool_backend/test_agent_tool.py +356 -0
- kolega_code/agent/tests/tool_backend/test_base_tool.py +147 -0
- kolega_code/agent/tests/tool_backend/test_browser_tool.py +335 -0
- kolega_code/agent/tests/tool_backend/test_build_tool.py +93 -0
- kolega_code/agent/tests/tool_backend/test_create_file_tool.py +115 -0
- kolega_code/agent/tests/tool_backend/test_glob_tool.py +196 -0
- kolega_code/agent/tests/tool_backend/test_glob_tool_sandbox_parity.py +230 -0
- kolega_code/agent/tests/tool_backend/test_list_directory_tool.py +292 -0
- kolega_code/agent/tests/tool_backend/test_read_file_tool.py +173 -0
- kolega_code/agent/tests/tool_backend/test_replace_entire_file_tool.py +115 -0
- kolega_code/agent/tests/tool_backend/test_replace_lines_tool.py +141 -0
- kolega_code/agent/tests/tool_backend/test_search_and_replace_tool.py +174 -0
- kolega_code/agent/tests/tool_backend/test_search_codebase_tool.py +228 -0
- kolega_code/agent/tests/tool_backend/test_terminal_tool.py +482 -0
- kolega_code/agent/tests/tool_backend/test_think_hard_integration.py +189 -0
- kolega_code/agent/tests/tool_backend/test_think_hard_streaming.py +445 -0
- kolega_code/agent/tests/tool_backend/test_web_fetch_tool.py +194 -0
- kolega_code/agent/tool_backend/agent_tool.py +414 -0
- kolega_code/agent/tool_backend/apply_edit_tool.py +98 -0
- kolega_code/agent/tool_backend/apply_patch_tool.py +514 -0
- kolega_code/agent/tool_backend/base_tool.py +217 -0
- kolega_code/agent/tool_backend/browser_tool.py +271 -0
- kolega_code/agent/tool_backend/build_tool.py +93 -0
- kolega_code/agent/tool_backend/create_file_tool.py +52 -0
- kolega_code/agent/tool_backend/glob_tool.py +323 -0
- kolega_code/agent/tool_backend/list_directory_tool.py +300 -0
- kolega_code/agent/tool_backend/memory_tool.py +79 -0
- kolega_code/agent/tool_backend/read_file_tool.py +119 -0
- kolega_code/agent/tool_backend/replace_entire_file_tool.py +40 -0
- kolega_code/agent/tool_backend/replace_lines_tool.py +97 -0
- kolega_code/agent/tool_backend/search_and_replace_tool.py +146 -0
- kolega_code/agent/tool_backend/search_codebase_tool.py +377 -0
- kolega_code/agent/tool_backend/streaming_tool.py +47 -0
- kolega_code/agent/tool_backend/terminal_tool.py +643 -0
- kolega_code/agent/tool_backend/think_hard_tool.py +211 -0
- kolega_code/agent/tool_backend/web_fetch_tool.py +205 -0
- kolega_code/agent/tools.py +1704 -0
- kolega_code/agent/utils/commands.py +94 -0
- kolega_code/cli/__init__.py +1 -0
- kolega_code/cli/app.py +2756 -0
- kolega_code/cli/config.py +280 -0
- kolega_code/cli/connection.py +49 -0
- kolega_code/cli/file_index.py +147 -0
- kolega_code/cli/main.py +564 -0
- kolega_code/cli/mentions.py +155 -0
- kolega_code/cli/messages.py +89 -0
- kolega_code/cli/provider_registry.py +96 -0
- kolega_code/cli/session_store.py +207 -0
- kolega_code/cli/settings.py +87 -0
- kolega_code/cli/skills.py +409 -0
- kolega_code/cli/slash_commands.py +108 -0
- kolega_code/cli/tests/__init__.py +1 -0
- kolega_code/cli/tests/test_app.py +4251 -0
- kolega_code/cli/tests/test_cli_config.py +171 -0
- kolega_code/cli/tests/test_connection.py +26 -0
- kolega_code/cli/tests/test_file_index.py +103 -0
- kolega_code/cli/tests/test_main.py +455 -0
- kolega_code/cli/tests/test_mentions.py +108 -0
- kolega_code/cli/tests/test_session_store.py +67 -0
- kolega_code/cli/tests/test_settings.py +62 -0
- kolega_code/cli/tests/test_skills.py +157 -0
- kolega_code/cli/tests/test_slash_commands.py +88 -0
- kolega_code/cli/theme.py +180 -0
- kolega_code/config.py +154 -0
- kolega_code/events.py +202 -0
- kolega_code/llm/client.py +300 -0
- kolega_code/llm/exceptions.py +285 -0
- kolega_code/llm/instrumented_client.py +520 -0
- kolega_code/llm/models.py +1368 -0
- kolega_code/llm/providers/__init__.py +0 -0
- kolega_code/llm/providers/anthropic.py +387 -0
- kolega_code/llm/providers/base.py +71 -0
- kolega_code/llm/providers/google.py +157 -0
- kolega_code/llm/providers/models.py +37 -0
- kolega_code/llm/providers/openai.py +363 -0
- kolega_code/llm/ratelimit.py +40 -0
- kolega_code/llm/specs.py +67 -0
- kolega_code/llm/tool_execution_ids.py +18 -0
- kolega_code/models/__init__.py +9 -0
- kolega_code/models/sandbox_terminal_state.py +47 -0
- kolega_code/runtime.py +50 -0
- kolega_code/sandbox/README.md +200 -0
- kolega_code/sandbox/__init__.py +21 -0
- kolega_code/sandbox/async_filesystem.py +475 -0
- kolega_code/sandbox/base.py +297 -0
- kolega_code/sandbox/browser.py +25 -0
- kolega_code/sandbox/event_loop.py +43 -0
- kolega_code/sandbox/filesystem.py +341 -0
- kolega_code/sandbox/local.py +118 -0
- kolega_code/sandbox/serializer.py +175 -0
- kolega_code/sandbox/terminal.py +868 -0
- kolega_code/sandbox/utils.py +216 -0
- kolega_code/services/base.py +255 -0
- kolega_code/services/browser.py +444 -0
- kolega_code/services/file_system.py +749 -0
- kolega_code/services/html.py +221 -0
- kolega_code/services/terminal.py +903 -0
- kolega_code/tools/__init__.py +22 -0
- kolega_code/tools/core.py +33 -0
- kolega_code/tools/definitions.py +81 -0
- kolega_code/tools/registry.py +73 -0
- kolega_code-0.1.0.dist-info/METADATA +157 -0
- kolega_code-0.1.0.dist-info/RECORD +171 -0
- kolega_code-0.1.0.dist-info/WHEEL +4 -0
- kolega_code-0.1.0.dist-info/entry_points.txt +2 -0
- kolega_code-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
"""Test parity between local and BrowserStack browser managers."""
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
import os
|
|
5
|
+
import pytest
|
|
6
|
+
from unittest.mock import AsyncMock, MagicMock, patch, PropertyMock
|
|
7
|
+
from dotenv import load_dotenv
|
|
8
|
+
|
|
9
|
+
from kolega_code.services.browser import PlaywrightBrowserManager
|
|
10
|
+
from kolega_code.sandbox.browser import SandboxBrowserManager
|
|
11
|
+
|
|
12
|
+
# Load environment variables from .env file
|
|
13
|
+
load_dotenv()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TestBrowserManagerParity:
|
|
17
|
+
"""Test suite to ensure identical behavior between local and BrowserStack browser managers."""
|
|
18
|
+
|
|
19
|
+
@pytest.fixture
|
|
20
|
+
def mock_env_vars(self, monkeypatch):
|
|
21
|
+
"""Mock environment variables for BrowserStack and Browserless."""
|
|
22
|
+
monkeypatch.setenv("BROWSERSTACK_USERNAME", "test_user")
|
|
23
|
+
monkeypatch.setenv("BROWSERSTACK_ACCESS_KEY", "test_key")
|
|
24
|
+
monkeypatch.setenv("BROWSERLESS_API_KEY", "test_browserless_key")
|
|
25
|
+
|
|
26
|
+
@pytest.fixture
|
|
27
|
+
def local_browser_manager(self):
|
|
28
|
+
"""Create a local browser manager instance."""
|
|
29
|
+
return PlaywrightBrowserManager(browser_backend="local")
|
|
30
|
+
|
|
31
|
+
@pytest.fixture
|
|
32
|
+
def browserstack_browser_manager(self, mock_env_vars):
|
|
33
|
+
"""Create a BrowserStack browser manager instance."""
|
|
34
|
+
return PlaywrightBrowserManager(browser_backend="browserstack")
|
|
35
|
+
|
|
36
|
+
@pytest.fixture
|
|
37
|
+
def browserless_browser_manager(self, mock_env_vars):
|
|
38
|
+
"""Create a Browserless browser manager instance."""
|
|
39
|
+
return PlaywrightBrowserManager(browser_backend="browserless")
|
|
40
|
+
|
|
41
|
+
@pytest.fixture
|
|
42
|
+
def sandbox_browser_manager(self, mock_env_vars):
|
|
43
|
+
"""Create a sandbox browser manager instance (uses Browserless)."""
|
|
44
|
+
return SandboxBrowserManager()
|
|
45
|
+
|
|
46
|
+
def test_initialization(self, local_browser_manager, browserstack_browser_manager, sandbox_browser_manager):
|
|
47
|
+
"""Test that all managers initialize with the same properties."""
|
|
48
|
+
# Check common properties
|
|
49
|
+
assert local_browser_manager.viewport == browserstack_browser_manager.viewport
|
|
50
|
+
assert local_browser_manager.viewport == sandbox_browser_manager.viewport
|
|
51
|
+
|
|
52
|
+
assert local_browser_manager.user_agent == browserstack_browser_manager.user_agent
|
|
53
|
+
assert local_browser_manager.user_agent == sandbox_browser_manager.user_agent
|
|
54
|
+
|
|
55
|
+
assert local_browser_manager.headless == browserstack_browser_manager.headless
|
|
56
|
+
assert local_browser_manager.headless == sandbox_browser_manager.headless
|
|
57
|
+
|
|
58
|
+
assert local_browser_manager.interaction_timeout == browserstack_browser_manager.interaction_timeout
|
|
59
|
+
assert local_browser_manager.interaction_timeout == sandbox_browser_manager.interaction_timeout
|
|
60
|
+
|
|
61
|
+
assert (
|
|
62
|
+
local_browser_manager.max_console_logs_per_browser
|
|
63
|
+
== browserstack_browser_manager.max_console_logs_per_browser
|
|
64
|
+
)
|
|
65
|
+
assert (
|
|
66
|
+
local_browser_manager.max_console_logs_per_browser == sandbox_browser_manager.max_console_logs_per_browser
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Check backend-specific properties
|
|
70
|
+
assert local_browser_manager.browser_backend == "local"
|
|
71
|
+
assert browserstack_browser_manager.browser_backend == "browserstack"
|
|
72
|
+
assert sandbox_browser_manager.browser_backend == "browserless"
|
|
73
|
+
|
|
74
|
+
def test_browserstack_credentials_required(self):
|
|
75
|
+
"""Test that BrowserStack and Browserless managers require credentials."""
|
|
76
|
+
# Clear environment variables
|
|
77
|
+
os.environ.pop("BROWSERSTACK_USERNAME", None)
|
|
78
|
+
os.environ.pop("BROWSERSTACK_ACCESS_KEY", None)
|
|
79
|
+
os.environ.pop("BROWSERLESS_API_KEY", None)
|
|
80
|
+
|
|
81
|
+
# Should raise ValueError without BrowserStack credentials
|
|
82
|
+
with pytest.raises(ValueError, match="BrowserStack credentials not found"):
|
|
83
|
+
PlaywrightBrowserManager(browser_backend="browserstack")
|
|
84
|
+
|
|
85
|
+
# Should raise ValueError without Browserless credentials
|
|
86
|
+
with pytest.raises(ValueError, match="Browserless API key not found"):
|
|
87
|
+
PlaywrightBrowserManager(browser_backend="browserless")
|
|
88
|
+
|
|
89
|
+
# SandboxBrowserManager uses Browserless, so should fail without Browserless credentials
|
|
90
|
+
with pytest.raises(ValueError, match="Browserless API key not found"):
|
|
91
|
+
SandboxBrowserManager()
|
|
92
|
+
|
|
93
|
+
def test_backward_compatibility(self, mock_env_vars):
|
|
94
|
+
"""Test backward compatibility with use_browserstack parameter."""
|
|
95
|
+
# Using use_browserstack=True should set browser_backend to "browserstack"
|
|
96
|
+
manager = PlaywrightBrowserManager(use_browserstack=True)
|
|
97
|
+
assert manager.browser_backend == "browserstack"
|
|
98
|
+
|
|
99
|
+
# Using use_browserstack=False should keep default backend
|
|
100
|
+
manager = PlaywrightBrowserManager(use_browserstack=False)
|
|
101
|
+
assert manager.browser_backend == "local"
|
|
102
|
+
|
|
103
|
+
@pytest.mark.asyncio
|
|
104
|
+
async def test_launch_browser_interface(self, local_browser_manager, browserstack_browser_manager):
|
|
105
|
+
"""Test that launch_browser has the same interface for both managers."""
|
|
106
|
+
# Mock the playwright async_playwright
|
|
107
|
+
with patch("kolega_code.services.browser.async_playwright") as mock_playwright:
|
|
108
|
+
# Setup mock playwright - fix the async mock issue
|
|
109
|
+
mock_pw_instance = MagicMock()
|
|
110
|
+
mock_async_playwright = AsyncMock()
|
|
111
|
+
mock_async_playwright.start.return_value = mock_pw_instance
|
|
112
|
+
mock_playwright.return_value = mock_async_playwright
|
|
113
|
+
|
|
114
|
+
# Mock browser and page
|
|
115
|
+
mock_browser = AsyncMock()
|
|
116
|
+
mock_context = AsyncMock()
|
|
117
|
+
mock_page = AsyncMock()
|
|
118
|
+
|
|
119
|
+
mock_browser.new_context.return_value = mock_context
|
|
120
|
+
mock_context.new_page.return_value = mock_page
|
|
121
|
+
mock_page.url = "https://example.com"
|
|
122
|
+
mock_page.evaluate = AsyncMock()
|
|
123
|
+
mock_page.goto = AsyncMock()
|
|
124
|
+
mock_page.on = MagicMock()
|
|
125
|
+
|
|
126
|
+
# For local browser
|
|
127
|
+
mock_pw_instance.chromium = MagicMock()
|
|
128
|
+
mock_pw_instance.chromium.launch = AsyncMock(return_value=mock_browser)
|
|
129
|
+
|
|
130
|
+
# For BrowserStack browser - mock connect() instead of connect_over_cdp()
|
|
131
|
+
mock_pw_instance.chromium.connect = AsyncMock(return_value=mock_browser)
|
|
132
|
+
|
|
133
|
+
# Test local browser
|
|
134
|
+
local_result = await local_browser_manager.launch_browser("https://example.com")
|
|
135
|
+
assert local_result is not None
|
|
136
|
+
assert isinstance(local_result, str) # Should return browser ID
|
|
137
|
+
|
|
138
|
+
# Test BrowserStack browser
|
|
139
|
+
bs_result = await browserstack_browser_manager.launch_browser("https://example.com")
|
|
140
|
+
assert bs_result is not None
|
|
141
|
+
assert isinstance(bs_result, str) # Should return browser ID
|
|
142
|
+
|
|
143
|
+
# Verify different connection methods were used
|
|
144
|
+
mock_pw_instance.chromium.launch.assert_called_once()
|
|
145
|
+
mock_pw_instance.chromium.connect.assert_called_once()
|
|
146
|
+
|
|
147
|
+
@pytest.mark.asyncio
|
|
148
|
+
async def test_browser_info_structure(self, local_browser_manager, browserstack_browser_manager):
|
|
149
|
+
"""Test that browser info structure is identical for both managers."""
|
|
150
|
+
# Mock the playwright async_playwright
|
|
151
|
+
with patch("kolega_code.services.browser.async_playwright") as mock_playwright:
|
|
152
|
+
# Setup mock playwright - fix the async mock issue
|
|
153
|
+
mock_pw_instance = MagicMock()
|
|
154
|
+
mock_async_playwright = AsyncMock()
|
|
155
|
+
mock_async_playwright.start.return_value = mock_pw_instance
|
|
156
|
+
mock_playwright.return_value = mock_async_playwright
|
|
157
|
+
|
|
158
|
+
# Mock browser and page
|
|
159
|
+
mock_browser = AsyncMock()
|
|
160
|
+
mock_context = AsyncMock()
|
|
161
|
+
mock_page = AsyncMock()
|
|
162
|
+
|
|
163
|
+
mock_browser.new_context.return_value = mock_context
|
|
164
|
+
mock_context.new_page.return_value = mock_page
|
|
165
|
+
mock_page.url = "https://example.com"
|
|
166
|
+
mock_page.evaluate = AsyncMock()
|
|
167
|
+
mock_page.goto = AsyncMock()
|
|
168
|
+
mock_page.on = MagicMock()
|
|
169
|
+
|
|
170
|
+
# For both browser types
|
|
171
|
+
mock_pw_instance.chromium = MagicMock()
|
|
172
|
+
mock_pw_instance.chromium.launch = AsyncMock(return_value=mock_browser)
|
|
173
|
+
mock_pw_instance.chromium.connect = AsyncMock(return_value=mock_browser)
|
|
174
|
+
|
|
175
|
+
# Launch browsers
|
|
176
|
+
local_id = await local_browser_manager.launch_browser("https://example.com")
|
|
177
|
+
bs_id = await browserstack_browser_manager.launch_browser("https://example.com")
|
|
178
|
+
|
|
179
|
+
# Check browser info structure
|
|
180
|
+
local_info = local_browser_manager.browsers[local_id]
|
|
181
|
+
bs_info = browserstack_browser_manager.browsers[bs_id]
|
|
182
|
+
|
|
183
|
+
# Both should have the same keys
|
|
184
|
+
assert set(local_info.keys()) == set(bs_info.keys())
|
|
185
|
+
|
|
186
|
+
# Check specific fields
|
|
187
|
+
assert local_info["type"] == bs_info["type"] == "chromium"
|
|
188
|
+
assert local_info["url"] == bs_info["url"] == "https://example.com"
|
|
189
|
+
assert "playwright" in local_info and "playwright" in bs_info
|
|
190
|
+
assert "browser" in local_info and "browser" in bs_info
|
|
191
|
+
assert "context" in local_info and "context" in bs_info
|
|
192
|
+
assert "page" in local_info and "page" in bs_info
|
|
193
|
+
assert "console_logs" in local_info and "console_logs" in bs_info
|
|
194
|
+
assert "network_requests" in local_info and "network_requests" in bs_info
|
|
195
|
+
assert "launched_at" in local_info and "launched_at" in bs_info
|
|
196
|
+
|
|
197
|
+
# BrowserStack flag should be different
|
|
198
|
+
assert local_info["browserstack"] is False
|
|
199
|
+
assert bs_info["browserstack"] is True
|
|
200
|
+
|
|
201
|
+
# Backend field should be different
|
|
202
|
+
assert local_info["backend"] == "local"
|
|
203
|
+
assert bs_info["backend"] == "browserstack"
|
|
204
|
+
|
|
205
|
+
@pytest.mark.asyncio
|
|
206
|
+
async def test_console_log_handling(self, local_browser_manager, browserstack_browser_manager):
|
|
207
|
+
"""Test that console log handling is identical for both managers."""
|
|
208
|
+
# Create mock browser info with console logs
|
|
209
|
+
now = datetime.datetime.now()
|
|
210
|
+
console_logs = [
|
|
211
|
+
{
|
|
212
|
+
"type": "error",
|
|
213
|
+
"text": "Test error",
|
|
214
|
+
"timestamp": now.isoformat(),
|
|
215
|
+
"location": None,
|
|
216
|
+
}
|
|
217
|
+
]
|
|
218
|
+
|
|
219
|
+
browser_id = "test-browser-id"
|
|
220
|
+
mock_browser_info = {
|
|
221
|
+
"console_logs": console_logs,
|
|
222
|
+
"launched_at": now.isoformat(),
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
# Add to both managers
|
|
226
|
+
local_browser_manager.browsers[browser_id] = mock_browser_info.copy()
|
|
227
|
+
browserstack_browser_manager.browsers[browser_id] = mock_browser_info.copy()
|
|
228
|
+
|
|
229
|
+
# Test console log retrieval
|
|
230
|
+
local_logs = await local_browser_manager.get_browser_console_logs(browser_id)
|
|
231
|
+
bs_logs = await browserstack_browser_manager.get_browser_console_logs(browser_id)
|
|
232
|
+
|
|
233
|
+
# Results should be identical
|
|
234
|
+
assert local_logs == bs_logs
|
|
235
|
+
assert local_logs["total_logs_count"] == 1
|
|
236
|
+
assert local_logs["returned_count"] == 1
|
|
237
|
+
assert local_logs["console_logs"][0]["type"] == "error"
|
|
238
|
+
assert local_logs["console_logs"][0]["text"] == "Test error"
|
|
239
|
+
|
|
240
|
+
@pytest.mark.asyncio
|
|
241
|
+
async def test_error_handling(self, local_browser_manager, browserstack_browser_manager):
|
|
242
|
+
"""Test that error handling is consistent across both managers."""
|
|
243
|
+
# Test browser not found error
|
|
244
|
+
with pytest.raises(KeyError, match="Browser with ID nonexistent not found"):
|
|
245
|
+
await local_browser_manager.get_browser_console_logs("nonexistent")
|
|
246
|
+
|
|
247
|
+
with pytest.raises(KeyError, match="Browser with ID nonexistent not found"):
|
|
248
|
+
await browserstack_browser_manager.get_browser_console_logs("nonexistent")
|
|
249
|
+
|
|
250
|
+
# Test other methods with non-existent browser
|
|
251
|
+
with pytest.raises(KeyError):
|
|
252
|
+
await local_browser_manager.take_browser_screenshot("nonexistent")
|
|
253
|
+
|
|
254
|
+
with pytest.raises(KeyError):
|
|
255
|
+
await browserstack_browser_manager.take_browser_screenshot("nonexistent")
|
|
256
|
+
|
|
257
|
+
@pytest.mark.asyncio
|
|
258
|
+
async def test_sandbox_manager_inheritance(self, browserless_browser_manager, sandbox_browser_manager):
|
|
259
|
+
"""Test that SandboxBrowserManager behaves as a PlaywrightBrowserManager with Browserless backend."""
|
|
260
|
+
# Check that they both use Browserless backend
|
|
261
|
+
assert browserless_browser_manager.browser_backend == "browserless"
|
|
262
|
+
assert sandbox_browser_manager.browser_backend == "browserless"
|
|
263
|
+
|
|
264
|
+
# Both managers should have browserless credentials
|
|
265
|
+
assert hasattr(browserless_browser_manager, "browserless_api_key")
|
|
266
|
+
|
|
267
|
+
# Sandbox manager should have browserless credentials
|
|
268
|
+
assert hasattr(sandbox_browser_manager, "browserless_api_key")
|
|
269
|
+
|
|
270
|
+
# SandboxBrowserManager should have its additional sandbox attribute
|
|
271
|
+
assert hasattr(sandbox_browser_manager, "sandbox")
|
|
272
|
+
assert not hasattr(browserless_browser_manager, "sandbox")
|
|
273
|
+
|
|
274
|
+
def test_cdp_url_generation(
|
|
275
|
+
self, browserstack_browser_manager, browserless_browser_manager, sandbox_browser_manager
|
|
276
|
+
):
|
|
277
|
+
"""Test that managers generate correct CDP URLs for their respective backends."""
|
|
278
|
+
# BrowserStack manager should generate BrowserStack URL
|
|
279
|
+
bs_url = browserstack_browser_manager._get_browserstack_cdp_url()
|
|
280
|
+
assert bs_url.startswith("wss://cdp.browserstack.com/playwright?caps=")
|
|
281
|
+
assert "browserstack.username" in bs_url
|
|
282
|
+
assert "browserstack.accessKey" in bs_url
|
|
283
|
+
|
|
284
|
+
# Browserless manager should generate Browserless URL
|
|
285
|
+
browserless_url = browserless_browser_manager._get_browserless_cdp_url()
|
|
286
|
+
assert browserless_url.startswith("wss://production-sfo.browserless.io?token=")
|
|
287
|
+
assert "timeout=" in browserless_url
|
|
288
|
+
|
|
289
|
+
# Sandbox manager should generate Browserless URL
|
|
290
|
+
sandbox_browserless_url = sandbox_browser_manager._get_browserless_cdp_url()
|
|
291
|
+
assert sandbox_browserless_url.startswith("wss://production-sfo.browserless.io?token=")
|
|
292
|
+
assert "timeout=" in sandbox_browserless_url
|
|
293
|
+
|
|
294
|
+
@pytest.mark.asyncio
|
|
295
|
+
async def test_list_browsers_parity(self, local_browser_manager, browserstack_browser_manager):
|
|
296
|
+
"""Test that list_browsers returns the same structure for both managers."""
|
|
297
|
+
# Mock browser info
|
|
298
|
+
browser_info = {
|
|
299
|
+
"url": "https://example.com",
|
|
300
|
+
"launched_at": datetime.datetime.now().isoformat(),
|
|
301
|
+
"browserstack": False,
|
|
302
|
+
"backend": "local",
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
browser_id = "test-id"
|
|
306
|
+
local_browser_manager.browsers[browser_id] = {**browser_info, "browserstack": False, "backend": "local"}
|
|
307
|
+
browserstack_browser_manager.browsers[browser_id] = {
|
|
308
|
+
**browser_info,
|
|
309
|
+
"browserstack": True,
|
|
310
|
+
"backend": "browserstack",
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
local_list = await local_browser_manager.list_browsers()
|
|
314
|
+
bs_list = await browserstack_browser_manager.list_browsers()
|
|
315
|
+
|
|
316
|
+
# Check structure is the same
|
|
317
|
+
assert set(local_list[browser_id].keys()) == set(bs_list[browser_id].keys())
|
|
318
|
+
assert local_list[browser_id]["url"] == bs_list[browser_id]["url"]
|
|
319
|
+
assert local_list[browser_id]["launched_at"] == bs_list[browser_id]["launched_at"]
|
|
320
|
+
|
|
321
|
+
# Backend flags should be different
|
|
322
|
+
assert local_list[browser_id]["browserstack"] is False
|
|
323
|
+
assert bs_list[browser_id]["browserstack"] is True
|
|
324
|
+
assert local_list[browser_id]["backend"] == "local"
|
|
325
|
+
assert bs_list[browser_id]["backend"] == "browserstack"
|
|
326
|
+
|
|
327
|
+
@pytest.mark.asyncio
|
|
328
|
+
async def test_all_methods_available(
|
|
329
|
+
self, local_browser_manager, browserstack_browser_manager, sandbox_browser_manager
|
|
330
|
+
):
|
|
331
|
+
"""Test that all browser manager methods are available on all implementations."""
|
|
332
|
+
methods = [
|
|
333
|
+
"launch_browser",
|
|
334
|
+
"list_browsers",
|
|
335
|
+
"get_browser_console_logs",
|
|
336
|
+
"get_browser_interactive_elements",
|
|
337
|
+
"get_browser_content",
|
|
338
|
+
"take_browser_screenshot",
|
|
339
|
+
"interact_with_browser",
|
|
340
|
+
"set_select_value",
|
|
341
|
+
"close_browser",
|
|
342
|
+
"cleanup_all_browsers",
|
|
343
|
+
]
|
|
344
|
+
|
|
345
|
+
for method in methods:
|
|
346
|
+
assert hasattr(local_browser_manager, method)
|
|
347
|
+
assert hasattr(browserstack_browser_manager, method)
|
|
348
|
+
assert hasattr(sandbox_browser_manager, method)
|
|
349
|
+
|
|
350
|
+
# All methods should be callable
|
|
351
|
+
assert callable(getattr(local_browser_manager, method))
|
|
352
|
+
assert callable(getattr(browserstack_browser_manager, method))
|
|
353
|
+
assert callable(getattr(sandbox_browser_manager, method))
|