Skip to content

Commit 7f8aa2f

Browse files
committed
tests
1 parent a03db9d commit 7f8aa2f

File tree

1 file changed

+198
-0
lines changed

1 file changed

+198
-0
lines changed

tests/test_browser_events.py

+198
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
import pytest
2+
import asyncio
3+
import uuid
4+
from unittest.mock import MagicMock, AsyncMock, patch, call, PropertyMock
5+
6+
from lmnr.sdk.client.synchronous.sync_client import LaminarClient
7+
from lmnr.sdk.client.asynchronous.async_client import AsyncLaminarClient
8+
from lmnr.sdk.browser.playwright_otel import PlaywrightInstrumentor
9+
10+
11+
class TestBrowserEvents:
12+
@pytest.fixture
13+
def mock_sync_client(self):
14+
client = MagicMock(spec=LaminarClient)
15+
client._browser_events = MagicMock()
16+
client._browser_events.send = MagicMock()
17+
return client
18+
19+
@pytest.fixture
20+
def mock_async_client(self):
21+
client = AsyncMock(spec=AsyncLaminarClient)
22+
client._browser_events = AsyncMock()
23+
client._browser_events.send = AsyncMock()
24+
return client
25+
26+
@pytest.fixture
27+
def mock_sync_page(self):
28+
page = MagicMock(name="SyncPage")
29+
page.evaluate.return_value = True
30+
page.goto.return_value = None
31+
page.click.return_value = None
32+
page.is_closed.side_effect = [False, False, True] # Return False twice, then True
33+
return page
34+
35+
@pytest.fixture
36+
def mock_async_page(self):
37+
page = AsyncMock(name="AsyncPage")
38+
page.evaluate.return_value = True
39+
page.goto.return_value = None
40+
page.click.return_value = None
41+
page.is_closed.side_effect = [False, False, True] # Return False twice, then True
42+
return page
43+
44+
@patch('time.sleep') # Mock sleep to speed up tests
45+
def test_sync_browser_events(self, mock_sleep, mock_sync_client, mock_sync_page):
46+
"""Test that browser events are captured and sent to Laminar in synchronous mode"""
47+
# Set up the evaluate method to return events
48+
def evaluate_side_effect(js_code):
49+
if "lmnrGetAndClearEvents" in js_code:
50+
# Return mock events on the second call
51+
return [{"data": [1, 2, 3, 4]}]
52+
return True
53+
54+
mock_sync_page.evaluate.side_effect = evaluate_side_effect
55+
56+
# Directly test the send_events_sync function
57+
from lmnr.sdk.browser.pw_utils import send_events_sync
58+
59+
# Create test session_id and trace_id
60+
session_id = str(uuid.uuid4().hex)
61+
trace_id = format(12345, "032x") # Simple trace ID for testing
62+
63+
# Send events
64+
send_events_sync(mock_sync_page, session_id, trace_id, mock_sync_client)
65+
66+
# Verify events were sent
67+
assert mock_sync_client._browser_events.send.called, "No events were sent to Laminar"
68+
69+
# Verify call arguments
70+
args = mock_sync_client._browser_events.send.call_args[0]
71+
assert len(args) == 3, "send() should receive 3 arguments: session_id, trace_id, events"
72+
73+
# Verify data format
74+
sent_session_id, sent_trace_id, events = args
75+
assert sent_session_id == session_id, "session_id mismatch"
76+
assert sent_trace_id == trace_id, "trace_id mismatch"
77+
assert isinstance(events, list), "events should be a list"
78+
assert len(events) > 0, "events list should not be empty"
79+
80+
@patch('asyncio.sleep') # Mock sleep to speed up tests
81+
@pytest.mark.asyncio
82+
async def test_async_browser_events(self, mock_sleep, mock_async_client, mock_async_page):
83+
"""Test that browser events are captured and sent to Laminar in asynchronous mode"""
84+
# Set up the evaluate method to return events
85+
async def evaluate_side_effect(js_code):
86+
if "lmnrGetAndClearEvents" in js_code:
87+
# Return mock events
88+
return [{"data": [1, 2, 3, 4]}]
89+
return True
90+
91+
mock_async_page.evaluate.side_effect = evaluate_side_effect
92+
93+
# Directly test the send_events_async function
94+
from lmnr.sdk.browser.pw_utils import send_events_async
95+
96+
# Create test session_id and trace_id
97+
session_id = str(uuid.uuid4().hex)
98+
trace_id = format(67890, "032x") # Simple trace ID for testing
99+
100+
# Send events
101+
await send_events_async(mock_async_page, session_id, trace_id, mock_async_client)
102+
103+
# Verify events were sent
104+
assert mock_async_client._browser_events.send.called, "No events were sent to Laminar"
105+
106+
# Verify call arguments
107+
args = mock_async_client._browser_events.send.call_args.args
108+
assert len(args) == 3, "send() should receive 3 arguments: session_id, trace_id, events"
109+
110+
# Verify data format
111+
sent_session_id, sent_trace_id, events = args
112+
assert sent_session_id == session_id, "session_id mismatch"
113+
assert sent_trace_id == trace_id, "trace_id mismatch"
114+
assert isinstance(events, list), "events should be a list"
115+
assert len(events) > 0, "events list should not be empty"
116+
117+
@patch('time.sleep') # Mock sleep to speed up tests
118+
def test_multiple_pages(self, mock_sleep, mock_sync_client):
119+
"""Test that events are properly captured from multiple pages"""
120+
# Create two mock pages
121+
mock_page1 = MagicMock(name="Page1")
122+
mock_page1.evaluate.return_value = [{"data": [1, 2, 3, 4]}]
123+
mock_page1.goto.return_value = None
124+
mock_page1.is_closed.return_value = False
125+
126+
mock_page2 = MagicMock(name="Page2")
127+
mock_page2.evaluate.return_value = [{"data": [5, 6, 7, 8]}]
128+
mock_page2.goto.return_value = None
129+
mock_page2.is_closed.return_value = False
130+
131+
# Directly test the send_events_sync function
132+
from lmnr.sdk.browser.pw_utils import send_events_sync
133+
134+
# Create test session_id and trace_id
135+
session_id = str(uuid.uuid4().hex)
136+
trace_id = "0" * 32 # Simplified trace ID for testing
137+
138+
# Send events from both pages
139+
send_events_sync(mock_page1, session_id, trace_id, mock_sync_client)
140+
send_events_sync(mock_page2, session_id, trace_id, mock_sync_client)
141+
142+
# Verify events were sent twice
143+
assert mock_sync_client._browser_events.send.call_count == 2, "Events should be sent twice"
144+
145+
# Verify the session_id was consistent across calls
146+
session_ids = set()
147+
for call_args in mock_sync_client._browser_events.send.call_args_list:
148+
session_ids.add(call_args[0][0]) # First positional arg is session_id
149+
150+
assert len(session_ids) == 1, "There should be exactly one session ID used"
151+
152+
def test_instrumentor_structure(self, mock_sync_client):
153+
"""Test the structure of the instrumentor and its wrapped methods"""
154+
# Import the wrapped methods data structures
155+
from lmnr.sdk.browser.playwright_otel import WRAPPED_METHODS, WRAPPED_METHODS_ASYNC
156+
157+
# Verify that the wrapped methods have the expected structure
158+
expected_methods = [
159+
'new_page', 'launch', 'new_context', 'close',
160+
'connect', 'connect_over_cdp', 'launch_persistent_context'
161+
]
162+
163+
expected_objects = ['BrowserContext', 'Browser', 'BrowserType']
164+
165+
# Check sync methods
166+
sync_methods = set()
167+
for method in WRAPPED_METHODS:
168+
assert method['package'] == 'playwright.sync_api', f"Unexpected package for {method['method']}"
169+
assert method['object'] in expected_objects, f"Unexpected object for {method['method']}"
170+
assert method['method'] in expected_methods, f"Unexpected method: {method['method']}"
171+
assert callable(method['wrapper']), f"Wrapper for {method['method']} is not callable"
172+
sync_methods.add(method['method'])
173+
174+
# Check async methods
175+
async_methods = set()
176+
for method in WRAPPED_METHODS_ASYNC:
177+
assert method['package'] == 'playwright.async_api', f"Unexpected package for {method['method']}"
178+
assert method['object'] in expected_objects, f"Unexpected object for {method['method']}"
179+
assert method['method'] in expected_methods, f"Unexpected method: {method['method']}"
180+
assert callable(method['wrapper']), f"Wrapper for {method['method']} is not callable"
181+
async_methods.add(method['method'])
182+
183+
# Verify all expected methods are covered
184+
for method in expected_methods:
185+
assert method in sync_methods, f"Missing sync method: {method}"
186+
assert method in async_methods, f"Missing async method: {method}"
187+
188+
# Verify the instrumentor is initialized correctly
189+
instrumentor = PlaywrightInstrumentor(
190+
client=mock_sync_client,
191+
async_client=AsyncMock()
192+
)
193+
194+
# Check client references
195+
assert instrumentor.client == mock_sync_client, "Client reference mismatch"
196+
197+
# Verify instrumentation dependencies
198+
assert instrumentor.instrumentation_dependencies() == ("playwright >= 1.9.0",), "Unexpected instrumentation dependencies"

0 commit comments

Comments
 (0)