Skip to content

Commit 405d40f

Browse files
committed
[py] use convenience methods for scrolling
1 parent 884c3cc commit 405d40f

File tree

4 files changed

+79
-16
lines changed

4 files changed

+79
-16
lines changed

‎py/selenium/webdriver/common/action_chains.py

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"""
1919
The ActionChains implementation,
2020
"""
21-
from typing import Union
21+
from .actions.wheel_input import ScrollOrigin
2222

2323
from .utils import keys_to_typing
2424
from .actions.action_builder import ActionBuilder
@@ -323,25 +323,54 @@ def send_keys_to_element(self, element, *keys_to_send):
323323
self.send_keys(*keys_to_send)
324324
return self
325325

326-
def scroll(self, x: int, y: int, delta_x: int, delta_y: int, duration: int = 0, origin: Union[str, WebElement] = "viewport"):
326+
def scroll_to_element(self, element: WebElement):
327327
"""
328-
Scrolls by the provided amount from a designated origination point.
328+
If the element is outside the viewport, scrolls the bottom of the element to the bottom of the viewport.
329+
330+
:Args:
331+
- element: Which element to scroll into the viewport.
332+
"""
333+
334+
self.w3c_actions.wheel_action.scroll(origin=element)
335+
return self
336+
337+
def scroll_by_amount(self, delta_x: int, delta_y: int):
338+
"""
339+
Scrolls by provided amounts with the origin in the top left corner of the viewport.
340+
341+
:Args:
342+
- delta_x: Distance along X axis to scroll using the wheel. A negative value scrolls left.
343+
- delta_y: Distance along Y axis to scroll using the wheel. A negative value scrolls up.
344+
"""
345+
346+
self.w3c_actions.wheel_action.scroll(delta_x=delta_x, delta_y=delta_y)
347+
return self
348+
349+
def scroll_from_origin(self, scroll_origin: ScrollOrigin, delta_x: int, delta_y: int):
350+
"""
351+
Scrolls by provided amount based on a provided origin.
329352
The scroll origin is either the center of an element or the upper left of the viewport plus any offsets.
330353
If the origin is an element, and the element is not in the viewport, the bottom of the element will first
331354
be scrolled to the bottom of the viewport.
332355
333356
:Args:
334-
- x: The horizontal offset from the origin from which to start the scroll.
335-
- y: The vertical offset from the origin from which to start the scroll.
357+
- origin: Where scroll originates (viewport or element center) plus provided offsets.
336358
- delta_x: Distance along X axis to scroll using the wheel. A negative value scrolls left.
337359
- delta_y: Distance along Y axis to scroll using the wheel. A negative value scrolls up.
338-
- origin: Where scroll originates (viewport or element center). The default is upper left of viewport.
339360
340361
:Raises: If the origin with offset is outside the viewport.
341362
- MoveTargetOutOfBoundsException - If the origin with offset is outside the viewport.
342363
"""
343-
self.w3c_actions.wheel_action.scroll(x=x, y=y, delta_x=delta_x, delta_y=delta_y,
344-
duration=duration, origin=origin)
364+
365+
if not isinstance(scroll_origin, ScrollOrigin):
366+
raise TypeError('Expected object of type ScrollOrigin, got: '
367+
'{}'.format(type(scroll_origin)))
368+
369+
self.w3c_actions.wheel_action.scroll(origin=scroll_origin.origin,
370+
x=scroll_origin.x_offset,
371+
y=scroll_origin.y_offset,
372+
delta_x=delta_x,
373+
delta_y=delta_y)
345374
return self
346375

347376
# Context manager so ActionChains can be used in a 'with .. as' statements.

‎py/selenium/webdriver/common/actions/wheel_actions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,6 @@ def pause(self, duration=0):
2929
self.source.create_pause(duration)
3030
return self
3131

32-
def scroll(self, x, y, delta_x, delta_y, duration, origin):
32+
def scroll(self, x=0, y=0, delta_x=0, delta_y=0, duration=0, origin="viewport"):
3333
self.source.create_scroll(x, y, delta_x, delta_y, duration, origin)
3434
return self

‎py/selenium/webdriver/common/actions/wheel_input.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,34 @@
2121
from selenium.webdriver.remote.webelement import WebElement
2222

2323

24+
class ScrollOrigin:
25+
26+
def __init__(self, origin: Union[str, WebElement], x_offset: int, y_offset: int) -> None:
27+
self._origin = origin
28+
self._x_offset = x_offset
29+
self._y_offset = y_offset
30+
31+
@classmethod
32+
def from_element(cls, element: WebElement, x_offset: int = 0, y_offset: int = 0):
33+
return cls(element, x_offset, y_offset)
34+
35+
@classmethod
36+
def from_viewport(cls, x_offset: int = 0, y_offset: int = 0):
37+
return cls('viewport', x_offset, y_offset)
38+
39+
@property
40+
def origin(self) -> Union[str, WebElement]:
41+
return self._origin
42+
43+
@property
44+
def x_offset(self) -> int:
45+
return self._x_offset
46+
47+
@property
48+
def y_offset(self) -> int:
49+
return self._y_offset
50+
51+
2452
class WheelInput(InputDevice):
2553

2654
def __init__(self, name) -> None:

‎py/test/selenium/webdriver/common/interactions_tests.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"""Tests for advanced user interactions."""
1919
import pytest
2020
from selenium.common.exceptions import MoveTargetOutOfBoundsException
21+
from selenium.webdriver.common.actions.wheel_input import ScrollOrigin
2122

2223
from selenium.webdriver.common.by import By
2324
from selenium.webdriver.common.keys import Keys
@@ -269,7 +270,7 @@ def test_can_scroll_to_element(driver, pages):
269270

270271
assert not _in_viewport(driver, iframe)
271272

272-
ActionChains(driver).scroll(0, 0, 0, 0, origin=iframe).perform()
273+
ActionChains(driver).scroll_to_element(iframe).perform()
273274

274275
assert _in_viewport(driver, iframe)
275276

@@ -279,8 +280,9 @@ def test_can_scroll_to_element(driver, pages):
279280
def test_can_scroll_from_element_by_amount(driver, pages):
280281
pages.load("scrolling_tests/frame_with_nested_scrolling_frame_out_of_view.html")
281282
iframe = driver.find_element(By.TAG_NAME, "iframe")
283+
scroll_origin = ScrollOrigin.from_element(iframe)
282284

283-
ActionChains(driver).scroll(0, 0, 0, 200, origin=iframe).pause(0.2).perform()
285+
ActionChains(driver).scroll_from_origin(scroll_origin, 0, 200).pause(0.2).perform()
284286

285287
driver.switch_to.frame(iframe)
286288
checkbox = driver.find_element(By.NAME, "scroll_checkbox")
@@ -292,8 +294,9 @@ def test_can_scroll_from_element_by_amount(driver, pages):
292294
def test_can_scroll_from_element_with_offset_by_amount(driver, pages):
293295
pages.load("scrolling_tests/frame_with_nested_scrolling_frame_out_of_view.html")
294296
footer = driver.find_element(By.TAG_NAME, "footer")
297+
scroll_origin = ScrollOrigin.from_element(footer, 0, -50)
295298

296-
ActionChains(driver).scroll(0, -50, 0, 200, origin=footer).pause(0.2).perform()
299+
ActionChains(driver).scroll_from_origin(scroll_origin, 0, 200).pause(0.2).perform()
297300

298301
iframe = driver.find_element(By.TAG_NAME, "iframe")
299302
driver.switch_to.frame(iframe)
@@ -306,9 +309,10 @@ def test_can_scroll_from_element_with_offset_by_amount(driver, pages):
306309
def test_errors_when_element_offset_not_in_viewport(driver, pages):
307310
pages.load("scrolling_tests/frame_with_nested_scrolling_frame_out_of_view.html")
308311
footer = driver.find_element(By.TAG_NAME, "footer")
312+
scroll_origin = ScrollOrigin.from_element(footer, 0, 50)
309313

310314
with pytest.raises(MoveTargetOutOfBoundsException):
311-
ActionChains(driver).scroll(0, 50, 0, 200, origin=footer).perform()
315+
ActionChains(driver).scroll_from_origin(scroll_origin, 0, 200).pause(0.2).perform()
312316

313317

314318
@pytest.mark.xfail_firefox
@@ -318,7 +322,7 @@ def test_can_scroll_from_viewport_by_amount(driver, pages):
318322
footer = driver.find_element(By.TAG_NAME, "footer")
319323
delta_y = footer.rect['y']
320324

321-
ActionChains(driver).scroll(0, 0, 0, delta_y).pause(0.2).perform()
325+
ActionChains(driver).scroll_by_amount(0, delta_y).pause(0.2).perform()
322326

323327
assert _in_viewport(driver, footer)
324328

@@ -327,8 +331,9 @@ def test_can_scroll_from_viewport_by_amount(driver, pages):
327331
@pytest.mark.xfail_remote
328332
def test_can_scroll_from_viewport_with_offset_by_amount(driver, pages):
329333
pages.load("scrolling_tests/frame_with_nested_scrolling_frame.html")
334+
scroll_origin = ScrollOrigin.from_viewport(10, 10)
330335

331-
ActionChains(driver).scroll(10, 10, 0, 200).pause(0.2).perform()
336+
ActionChains(driver).scroll_from_origin(scroll_origin, 0, 200).pause(0.2).perform()
332337

333338
iframe = driver.find_element(By.TAG_NAME, "iframe")
334339
driver.switch_to.frame(iframe)
@@ -340,9 +345,10 @@ def test_can_scroll_from_viewport_with_offset_by_amount(driver, pages):
340345
@pytest.mark.xfail_remote
341346
def test_errors_when_origin_offset_not_in_viewport(driver, pages):
342347
pages.load("scrolling_tests/frame_with_nested_scrolling_frame.html")
348+
scroll_origin = ScrollOrigin.from_viewport(-10, -10)
343349

344350
with pytest.raises(MoveTargetOutOfBoundsException):
345-
ActionChains(driver).scroll(-10, -10, 0, 200).perform()
351+
ActionChains(driver).scroll_from_origin(scroll_origin, 0, 200).pause(0.2).perform()
346352

347353

348354
def _get_events(driver):

0 commit comments

Comments
 (0)