Skip to content

Commit 0345b03

Browse files
committed
test: add tests for ScrollAPI functionality and smooth scrolling behavior
1 parent 11efb0c commit 0345b03

File tree

2 files changed

+358
-0
lines changed

2 files changed

+358
-0
lines changed

tests/test_interactions/__init__.py

Whitespace-only changes.
Lines changed: 358 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,358 @@
1+
import pytest
2+
import pytest_asyncio
3+
from unittest.mock import AsyncMock, MagicMock, patch, call
4+
5+
from pydoll.interactions.scroll import ScrollAPI
6+
from pydoll.constants import ScrollPosition, Scripts
7+
from pydoll.commands import RuntimeCommands
8+
9+
10+
@pytest_asyncio.fixture
11+
async def mock_tab():
12+
"""Mock Tab instance for ScrollAPI tests."""
13+
tab = MagicMock()
14+
tab._execute_command = AsyncMock()
15+
return tab
16+
17+
18+
@pytest_asyncio.fixture
19+
async def scroll_api(mock_tab):
20+
"""Create ScrollAPI instance with mocked tab."""
21+
return ScrollAPI(mock_tab)
22+
23+
24+
class TestScrollAPIInitialization:
25+
"""Test ScrollAPI initialization."""
26+
27+
def test_initialization(self, mock_tab):
28+
"""Test ScrollAPI is properly initialized with tab."""
29+
scroll_api = ScrollAPI(mock_tab)
30+
assert scroll_api._tab == mock_tab
31+
32+
33+
class TestScrollAPIBy:
34+
"""Test scroll.by() method."""
35+
36+
@pytest.mark.asyncio
37+
async def test_scroll_down_smooth(self, scroll_api, mock_tab):
38+
"""Test scrolling down with smooth animation."""
39+
await scroll_api.by(ScrollPosition.DOWN, 500, smooth=True)
40+
41+
# Verify execute_command was called
42+
assert mock_tab._execute_command.called
43+
call_args = mock_tab._execute_command.call_args
44+
45+
# Verify the command is RuntimeCommands.evaluate with await_promise=True
46+
command = call_args[0][0]
47+
assert command['method'] == 'Runtime.evaluate'
48+
assert command['params']['awaitPromise'] is True
49+
50+
# Verify the script contains expected values
51+
script = command['params']['expression']
52+
assert 'top: 5000' in script # 500 * 10
53+
assert "behavior: 'smooth'" in script
54+
55+
@pytest.mark.asyncio
56+
async def test_scroll_up_smooth(self, scroll_api, mock_tab):
57+
"""Test scrolling up with smooth animation."""
58+
await scroll_api.by(ScrollPosition.UP, 300, smooth=True)
59+
60+
call_args = mock_tab._execute_command.call_args
61+
command = call_args[0][0]
62+
script = command['params']['expression']
63+
64+
assert 'top: -3000' in script # -300 * 10
65+
assert "behavior: 'smooth'" in script
66+
67+
@pytest.mark.asyncio
68+
async def test_scroll_right_smooth(self, scroll_api, mock_tab):
69+
"""Test scrolling right with smooth animation."""
70+
await scroll_api.by(ScrollPosition.RIGHT, 200, smooth=True)
71+
72+
call_args = mock_tab._execute_command.call_args
73+
command = call_args[0][0]
74+
script = command['params']['expression']
75+
76+
assert 'left: 2000' in script # 200 * 10
77+
assert "behavior: 'smooth'" in script
78+
79+
@pytest.mark.asyncio
80+
async def test_scroll_left_smooth(self, scroll_api, mock_tab):
81+
"""Test scrolling left with smooth animation."""
82+
await scroll_api.by(ScrollPosition.LEFT, 150, smooth=True)
83+
84+
call_args = mock_tab._execute_command.call_args
85+
command = call_args[0][0]
86+
script = command['params']['expression']
87+
88+
assert 'left: -1500' in script # -150 * 10
89+
assert "behavior: 'smooth'" in script
90+
91+
@pytest.mark.asyncio
92+
async def test_scroll_down_instant(self, scroll_api, mock_tab):
93+
"""Test scrolling down without smooth animation."""
94+
await scroll_api.by(ScrollPosition.DOWN, 1000, smooth=False)
95+
96+
call_args = mock_tab._execute_command.call_args
97+
command = call_args[0][0]
98+
script = command['params']['expression']
99+
100+
assert 'top: 10000' in script # 1000 * 10
101+
assert "behavior: 'auto'" in script
102+
103+
@pytest.mark.asyncio
104+
async def test_scroll_with_float_distance(self, scroll_api, mock_tab):
105+
"""Test scrolling with float distance."""
106+
await scroll_api.by(ScrollPosition.DOWN, 250.5, smooth=True)
107+
108+
call_args = mock_tab._execute_command.call_args
109+
command = call_args[0][0]
110+
script = command['params']['expression']
111+
112+
assert 'top: 2505.0' in script # 250.5 * 10
113+
114+
115+
class TestScrollAPIToTop:
116+
"""Test scroll.to_top() method."""
117+
118+
@pytest.mark.asyncio
119+
async def test_scroll_to_top_smooth(self, scroll_api, mock_tab):
120+
"""Test scrolling to top with smooth animation."""
121+
await scroll_api.to_top(smooth=True)
122+
123+
assert mock_tab._execute_command.called
124+
call_args = mock_tab._execute_command.call_args
125+
command = call_args[0][0]
126+
127+
assert command['method'] == 'Runtime.evaluate'
128+
assert command['params']['awaitPromise'] is True
129+
130+
script = command['params']['expression']
131+
assert 'top: 0' in script
132+
assert "behavior: 'smooth'" in script
133+
134+
@pytest.mark.asyncio
135+
async def test_scroll_to_top_instant(self, scroll_api, mock_tab):
136+
"""Test scrolling to top without smooth animation."""
137+
await scroll_api.to_top(smooth=False)
138+
139+
call_args = mock_tab._execute_command.call_args
140+
command = call_args[0][0]
141+
script = command['params']['expression']
142+
143+
assert 'top: 0' in script
144+
assert "behavior: 'auto'" in script
145+
146+
147+
class TestScrollAPIToBottom:
148+
"""Test scroll.to_bottom() method."""
149+
150+
@pytest.mark.asyncio
151+
async def test_scroll_to_bottom_smooth(self, scroll_api, mock_tab):
152+
"""Test scrolling to bottom with smooth animation."""
153+
await scroll_api.to_bottom(smooth=True)
154+
155+
assert mock_tab._execute_command.called
156+
call_args = mock_tab._execute_command.call_args
157+
command = call_args[0][0]
158+
159+
assert command['method'] == 'Runtime.evaluate'
160+
assert command['params']['awaitPromise'] is True
161+
162+
script = command['params']['expression']
163+
assert 'top: document.body.scrollHeight' in script
164+
assert "behavior: 'smooth'" in script
165+
166+
@pytest.mark.asyncio
167+
async def test_scroll_to_bottom_instant(self, scroll_api, mock_tab):
168+
"""Test scrolling to bottom without smooth animation."""
169+
await scroll_api.to_bottom(smooth=False)
170+
171+
call_args = mock_tab._execute_command.call_args
172+
command = call_args[0][0]
173+
script = command['params']['expression']
174+
175+
assert 'top: document.body.scrollHeight' in script
176+
assert "behavior: 'auto'" in script
177+
178+
179+
class TestScrollAPIHelperMethods:
180+
"""Test ScrollAPI private helper methods."""
181+
182+
def test_get_axis_and_distance_down(self, scroll_api):
183+
"""Test _get_axis_and_distance for DOWN direction."""
184+
axis, distance = scroll_api._get_axis_and_distance(ScrollPosition.DOWN, 100)
185+
assert axis == 'top'
186+
assert distance == 1000 # 100 * 10
187+
188+
def test_get_axis_and_distance_up(self, scroll_api):
189+
"""Test _get_axis_and_distance for UP direction."""
190+
axis, distance = scroll_api._get_axis_and_distance(ScrollPosition.UP, 100)
191+
assert axis == 'top'
192+
assert distance == -1000 # -100 * 10
193+
194+
def test_get_axis_and_distance_right(self, scroll_api):
195+
"""Test _get_axis_and_distance for RIGHT direction."""
196+
axis, distance = scroll_api._get_axis_and_distance(ScrollPosition.RIGHT, 50)
197+
assert axis == 'left'
198+
assert distance == 500 # 50 * 10
199+
200+
def test_get_axis_and_distance_left(self, scroll_api):
201+
"""Test _get_axis_and_distance for LEFT direction."""
202+
axis, distance = scroll_api._get_axis_and_distance(ScrollPosition.LEFT, 50)
203+
assert axis == 'left'
204+
assert distance == -500 # -50 * 10
205+
206+
def test_get_behavior_smooth(self, scroll_api):
207+
"""Test _get_behavior with smooth=True."""
208+
behavior = scroll_api._get_behavior(True)
209+
assert behavior == 'smooth'
210+
211+
def test_get_behavior_instant(self, scroll_api):
212+
"""Test _get_behavior with smooth=False."""
213+
behavior = scroll_api._get_behavior(False)
214+
assert behavior == 'auto'
215+
216+
217+
class TestScrollAPIIntegrationWithTab:
218+
"""Test ScrollAPI integration with Tab."""
219+
220+
@pytest.mark.asyncio
221+
async def test_tab_has_scroll_property(self):
222+
"""Test that Tab has scroll property."""
223+
with patch('pydoll.connection.ConnectionHandler', autospec=True):
224+
from pydoll.browser.tab import Tab
225+
226+
mock_browser = MagicMock()
227+
tab = Tab(mock_browser, target_id='test-id')
228+
229+
# Access scroll property
230+
scroll = tab.scroll
231+
232+
# Verify it's a ScrollAPI instance
233+
assert isinstance(scroll, ScrollAPI)
234+
assert scroll._tab == tab
235+
236+
@pytest.mark.asyncio
237+
async def test_tab_scroll_property_is_lazy(self):
238+
"""Test that scroll property is created lazily."""
239+
with patch('pydoll.connection.ConnectionHandler', autospec=True):
240+
from pydoll.browser.tab import Tab
241+
242+
mock_browser = MagicMock()
243+
tab = Tab(mock_browser, target_id='test-id')
244+
245+
# Initially None
246+
assert tab._scroll is None
247+
248+
# Access creates instance
249+
scroll1 = tab.scroll
250+
assert tab._scroll is not None
251+
252+
# Second access returns same instance
253+
scroll2 = tab.scroll
254+
assert scroll1 is scroll2
255+
256+
@pytest.mark.asyncio
257+
async def test_scroll_execute_command_integration(self):
258+
"""Test that scroll methods properly call tab._execute_command."""
259+
with patch('pydoll.connection.ConnectionHandler', autospec=True):
260+
from pydoll.browser.tab import Tab
261+
262+
mock_browser = MagicMock()
263+
tab = Tab(mock_browser, target_id='test-id')
264+
tab._execute_command = AsyncMock()
265+
266+
# Call scroll method
267+
await tab.scroll.by(ScrollPosition.DOWN, 500, smooth=True)
268+
269+
# Verify _execute_command was called
270+
assert tab._execute_command.called
271+
272+
# Verify command structure
273+
call_args = tab._execute_command.call_args
274+
command = call_args[0][0]
275+
assert command['method'] == 'Runtime.evaluate'
276+
assert command['params']['awaitPromise'] is True
277+
278+
279+
class TestScrollAPIScriptGeneration:
280+
"""Test that correct JavaScript scripts are generated."""
281+
282+
@pytest.mark.asyncio
283+
async def test_scroll_by_script_structure(self, scroll_api, mock_tab):
284+
"""Test that scroll.by generates correct script structure."""
285+
await scroll_api.by(ScrollPosition.DOWN, 500, smooth=True)
286+
287+
call_args = mock_tab._execute_command.call_args
288+
command = call_args[0][0]
289+
script = command['params']['expression']
290+
291+
# Verify script has Promise structure
292+
assert 'new Promise' in script or 'Promise' in script
293+
assert 'scrollend' in script or 'scrollBy' in script
294+
assert 'resolve' in script
295+
296+
@pytest.mark.asyncio
297+
async def test_to_top_script_structure(self, scroll_api, mock_tab):
298+
"""Test that scroll.to_top generates correct script structure."""
299+
await scroll_api.to_top(smooth=True)
300+
301+
call_args = mock_tab._execute_command.call_args
302+
command = call_args[0][0]
303+
script = command['params']['expression']
304+
305+
# Verify script has Promise structure and scrollTo
306+
assert 'new Promise' in script or 'Promise' in script
307+
assert 'scrollTo' in script
308+
assert 'top: 0' in script
309+
310+
@pytest.mark.asyncio
311+
async def test_to_bottom_script_structure(self, scroll_api, mock_tab):
312+
"""Test that scroll.to_bottom generates correct script structure."""
313+
await scroll_api.to_bottom(smooth=True)
314+
315+
call_args = mock_tab._execute_command.call_args
316+
command = call_args[0][0]
317+
script = command['params']['expression']
318+
319+
# Verify script has Promise structure and scrollHeight
320+
assert 'new Promise' in script or 'Promise' in script
321+
assert 'scrollTo' in script
322+
assert 'scrollHeight' in script
323+
324+
325+
class TestScrollAPIAwaitPromise:
326+
"""Test that awaitPromise parameter is correctly set."""
327+
328+
@pytest.mark.asyncio
329+
async def test_scroll_by_uses_await_promise(self, scroll_api, mock_tab):
330+
"""Test that scroll.by uses awaitPromise parameter."""
331+
await scroll_api.by(ScrollPosition.DOWN, 100, smooth=True)
332+
333+
call_args = mock_tab._execute_command.call_args
334+
command = call_args[0][0]
335+
336+
# Verify awaitPromise is True
337+
assert command['params']['awaitPromise'] is True
338+
339+
@pytest.mark.asyncio
340+
async def test_to_top_uses_await_promise(self, scroll_api, mock_tab):
341+
"""Test that scroll.to_top uses awaitPromise parameter."""
342+
await scroll_api.to_top(smooth=True)
343+
344+
call_args = mock_tab._execute_command.call_args
345+
command = call_args[0][0]
346+
347+
assert command['params']['awaitPromise'] is True
348+
349+
@pytest.mark.asyncio
350+
async def test_to_bottom_uses_await_promise(self, scroll_api, mock_tab):
351+
"""Test that scroll.to_bottom uses awaitPromise parameter."""
352+
await scroll_api.to_bottom(smooth=True)
353+
354+
call_args = mock_tab._execute_command.call_args
355+
command = call_args[0][0]
356+
357+
assert command['params']['awaitPromise'] is True
358+

0 commit comments

Comments
 (0)