Skip to content

Commit

Permalink
Users can now modify ActionChains() duration.
Browse files Browse the repository at this point in the history
  • Loading branch information
rasjani committed Jan 19, 2023
1 parent 9e0e2ef commit a57a0d3
Show file tree
Hide file tree
Showing 10 changed files with 95 additions and 22 deletions.
6 changes: 5 additions & 1 deletion src/SeleniumLibrary/__init__.py
Expand Up @@ -48,7 +48,7 @@
)
from SeleniumLibrary.keywords.screenshot import EMBED
from SeleniumLibrary.locators import ElementFinder
from SeleniumLibrary.utils import LibraryListener, is_truthy, _convert_timeout
from SeleniumLibrary.utils import LibraryListener, is_truthy, _convert_timeout, _convert_delay


__version__ = "6.1.0.dev1"
Expand Down Expand Up @@ -431,6 +431,7 @@ def __init__(
self,
timeout=timedelta(seconds=5),
implicit_wait=timedelta(seconds=0),
action_chain_delay=timedelta(seconds=0.25),
run_on_failure="Capture Page Screenshot",
screenshot_root_directory: Optional[str] = None,
plugins: Optional[str] = None,
Expand All @@ -442,6 +443,8 @@ def __init__(
Default value for `timeouts` used with ``Wait ...`` keywords.
- ``implicit_wait``:
Default value for `implicit wait` used when locating elements.
- ``action_chain_delay``:
Default value for `ActionChains` delay to wait in between actions.
- ``run_on_failure``:
Default action for the `run-on-failure functionality`.
- ``screenshot_root_directory``:
Expand All @@ -456,6 +459,7 @@ def __init__(
"""
self.timeout = _convert_timeout(timeout)
self.implicit_wait = _convert_timeout(implicit_wait)
self.action_chain_delay = _convert_delay(action_chain_delay)
self.speed = 0.0
self.run_on_failure_keyword = RunOnFailureKeywords.resolve_keyword(
run_on_failure
Expand Down
2 changes: 1 addition & 1 deletion src/SeleniumLibrary/__init__.pyi
Expand Up @@ -6,7 +6,7 @@ from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.remote.webelement import WebElement

class SeleniumLibrary:
def __init__(self, timeout = timedelta(seconds=5.0), implicit_wait = timedelta(seconds=0.0), run_on_failure = 'Capture Page Screenshot', screenshot_root_directory: Optional[Union[str, None]] = None, plugins: Optional[Union[str, None]] = None, event_firing_webdriver: Optional[Union[str, None]] = None): ...
def __init__(self, timeout = timedelta(seconds=5.0), implicit_wait = timedelta(seconds=0.0), action_chain_delay(seconds=0.25)), run_on_failure = 'Capture Page Screenshot', screenshot_root_directory: Optional[Union[str, None]] = None, plugins: Optional[Union[str, None]] = None, event_firing_webdriver: Optional[Union[str, None]] = None): ...
def add_cookie(self, name: str, value: str, path: Optional[Union[str, None]] = None, domain: Optional[Union[str, None]] = None, secure: Optional[Union[bool, None]] = None, expiry: Optional[Union[str, None]] = None): ...
def add_location_strategy(self, strategy_name: str, strategy_keyword: str, persist: bool = False): ...
def alert_should_be_present(self, text: str = '', action: str = 'ACCEPT', timeout: Optional[Union[datetime.timedelta, None]] = None): ...
Expand Down
24 changes: 23 additions & 1 deletion src/SeleniumLibrary/keywords/browsermanagement.py
Expand Up @@ -25,7 +25,7 @@

from SeleniumLibrary.base import keyword, LibraryComponent
from SeleniumLibrary.locators import WindowManager
from SeleniumLibrary.utils import secs_to_timestr, _convert_timeout
from SeleniumLibrary.utils import timestr_to_secs, secs_to_timestr, _convert_timeout, _convert_delay

from .webdrivertools import WebDriverCreator

Expand Down Expand Up @@ -692,6 +692,28 @@ def set_selenium_implicit_wait(self, value: timedelta) -> str:
driver.implicitly_wait(self.ctx.implicit_wait)
return old_wait

@keyword
def set_action_chain_delay(self, value: timedelta) -> str:
"""Sets the duration of delay in ActionChains() used by SeleniumLibrary.
The value can be given as a number that is considered to be
seconds or as a human-readable string like ``1 second``.
Value is always stored as milliseconds internally.
The previous value is returned and can be used to restore
the original value later if needed.
"""
old_action_chain_delay = self.ctx.action_chain_delay
self.ctx.action_chain_delay = _convert_delay(value)
return timestr_to_secs(f"{old_action_chain_delay} milliseconds")

@keyword
def get_action_chain_delay(self):
"""Gets the currently stored value for chain_delay_value in timestr format.
"""
return timestr_to_secs(f"{self.ctx.action_chain_delay} milliseconds")

@keyword
def set_browser_implicit_wait(self, value: timedelta):
"""Sets the implicit wait value used by Selenium.
Expand Down
32 changes: 16 additions & 16 deletions src/SeleniumLibrary/keywords/element.py
Expand Up @@ -659,7 +659,7 @@ def click_element(

def _click_with_action_chain(self, locator: Union[WebElement, str]):
self.info(f"Clicking '{locator}' using an action chain.")
action = ActionChains(self.driver)
action = ActionChains(self.driver, duration=self.ctx.action_chain_delay)
element = self.find_element(locator)
# _unwrap_eventfiring_element can be removed when minimum required Selenium is 4.0 or greater.
element = _unwrap_eventfiring_element(element)
Expand All @@ -672,7 +672,7 @@ def _click_with_modifier(self, locator, tag, modifier):
f"Clicking {tag if tag[0] else 'element'} '{locator}' with {modifier}."
)
modifier = self.parse_modifier(modifier)
action = ActionChains(self.driver)
action = ActionChains(self.driver, duration=self.ctx.action_chain_delay)
for item in modifier:
action.key_down(item)
element = self.find_element(locator, tag=tag[0], required=False)
Expand Down Expand Up @@ -703,7 +703,7 @@ def click_element_at_coordinates(
element = self.find_element(locator)
# _unwrap_eventfiring_element can be removed when minimum required Selenium is 4.0 or greater.
element = _unwrap_eventfiring_element(element)
action = ActionChains(self.driver)
action = ActionChains(self.driver, duration=self.ctx.action_chain_delay)
action.move_to_element(element)
action.move_by_offset(xoffset, yoffset)
action.click()
Expand All @@ -720,7 +720,7 @@ def double_click_element(self, locator: Union[WebElement, str]):
element = self.find_element(locator)
# _unwrap_eventfiring_element can be removed when minimum required Selenium is 4.0 or greater.
element = _unwrap_eventfiring_element(element)
action = ActionChains(self.driver)
action = ActionChains(self.driver, duration=self.ctx.action_chain_delay)
action.double_click(element).perform()

@keyword
Expand All @@ -747,7 +747,7 @@ def scroll_element_into_view(self, locator: Union[WebElement, str]):
element = self.find_element(locator)
# _unwrap_eventfiring_element can be removed when minimum required Selenium is 4.0 or greater.
element = _unwrap_eventfiring_element(element)
ActionChains(self.driver).move_to_element(element).perform()
ActionChains(self.driver, duration=self.ctx.action_chain_delay).move_to_element(element).perform()

@keyword
def drag_and_drop(
Expand All @@ -768,7 +768,7 @@ def drag_and_drop(
target = self.find_element(target)
# _unwrap_eventfiring_element can be removed when minimum required Selenium is 4.0 or greater.
target = _unwrap_eventfiring_element(target)
action = ActionChains(self.driver)
action = ActionChains(self.driver, duration=self.ctx.action_chain_delay)
action.drag_and_drop(element, target).perform()

@keyword
Expand All @@ -789,7 +789,7 @@ def drag_and_drop_by_offset(
element = self.find_element(locator)
# _unwrap_eventfiring_element can be removed when minimum required Selenium is 4.0 or greater.
element = _unwrap_eventfiring_element(element)
action = ActionChains(self.driver)
action = ActionChains(self.driver, duration=self.ctx.action_chain_delay)
action.drag_and_drop_by_offset(element, xoffset, yoffset)
action.perform()

Expand All @@ -809,7 +809,7 @@ def mouse_down(self, locator: Union[WebElement, str]):
element = self.find_element(locator)
# _unwrap_eventfiring_element can be removed when minimum required Selenium is 4.0 or greater.
element = _unwrap_eventfiring_element(element)
action = ActionChains(self.driver)
action = ActionChains(self.driver, duration=self.ctx.action_chain_delay)
action.click_and_hold(element).perform()

@keyword
Expand All @@ -826,7 +826,7 @@ def mouse_out(self, locator: Union[WebElement, str]):
size = element.size
offsetx = (size["width"] / 2) + 1
offsety = (size["height"] / 2) + 1
action = ActionChains(self.driver)
action = ActionChains(self.driver, duration=self.ctx.action_chain_delay)
action.move_to_element(element)
action.move_by_offset(offsetx, offsety)
action.perform()
Expand All @@ -842,7 +842,7 @@ def mouse_over(self, locator: Union[WebElement, str]):
element = self.find_element(locator)
# _unwrap_eventfiring_element can be removed when minimum required Selenium is 4.0 or greater.
element = _unwrap_eventfiring_element(element)
action = ActionChains(self.driver)
action = ActionChains(self.driver, duration=self.ctx.action_chain_delay)
action.move_to_element(element).perform()

@keyword
Expand All @@ -856,15 +856,15 @@ def mouse_up(self, locator: Union[WebElement, str]):
element = self.find_element(locator)
# _unwrap_eventfiring_element can be removed when minimum required Selenium is 4.0 or greater.
element = _unwrap_eventfiring_element(element)
ActionChains(self.driver).release(element).perform()
ActionChains(self.driver, duration=self.ctx.action_chain_delay).release(element).perform()

@keyword
def open_context_menu(self, locator: Union[WebElement, str]):
"""Opens the context menu on the element identified by ``locator``."""
element = self.find_element(locator)
# _unwrap_eventfiring_element can be removed when minimum required Selenium is 4.0 or greater.
element = _unwrap_eventfiring_element(element)
action = ActionChains(self.driver)
action = ActionChains(self.driver, duration=self.ctx.action_chain_delay)
action.context_click(element).perform()

@keyword
Expand Down Expand Up @@ -954,12 +954,12 @@ def press_keys(self, locator: Union[WebElement, None, str] = None, *keys: str):
element = self.find_element(locator)
# _unwrap_eventfiring_element can be removed when minimum required Selenium is 4.0 or greater.
element = _unwrap_eventfiring_element(element)
ActionChains(self.driver).click(element).perform()
ActionChains(self.driver, duration=self.ctx.action_chain_delay).click(element).perform()
else:
self.info(f"Sending key(s) {keys} to page.")
element = None
for parsed_key in parsed_keys:
actions = ActionChains(self.driver)
actions = ActionChains(self.driver, duration=self.ctx.action_chain_delay)
for key in parsed_key:
if key.special:
self._press_keys_special_keys(actions, element, parsed_key, key)
Expand Down Expand Up @@ -1009,7 +1009,7 @@ def mouse_down_on_link(self, locator: Union[WebElement, str]):
element = self.find_element(locator, tag="link")
# _unwrap_eventfiring_element can be removed when minimum required Selenium is 4.0 or greater.
element = _unwrap_eventfiring_element(element)
action = ActionChains(self.driver)
action = ActionChains(self.driver, duration=self.ctx.action_chain_delay)
action.click_and_hold(element).perform()

@keyword
Expand Down Expand Up @@ -1059,7 +1059,7 @@ def mouse_down_on_image(self, locator: Union[WebElement, str]):
element = self.find_element(locator, tag="image")
# _unwrap_eventfiring_element can be removed when minimum required Selenium is 4.0 or greater.
element = _unwrap_eventfiring_element(element)
action = ActionChains(self.driver)
action = ActionChains(self.driver, duration=self.ctx.action_chain_delay)
action.click_and_hold(element).perform()

@keyword
Expand Down
1 change: 1 addition & 0 deletions src/SeleniumLibrary/utils/__init__.py
Expand Up @@ -24,6 +24,7 @@
is_truthy,
WINDOWS,
_convert_timeout,
_convert_delay,
) # noqa


Expand Down
7 changes: 7 additions & 0 deletions src/SeleniumLibrary/utils/types.py
Expand Up @@ -28,6 +28,13 @@
def is_noney(item):
return item is None or is_string(item) and item.upper() == "NONE"

def _convert_delay(delay):
if isinstance(delay, timedelta):
return delay.microseconds // 1000
else:
x = timestr_to_secs(delay)
return int( x * 1000)


def _convert_timeout(timeout):
if isinstance(timeout, timedelta):
Expand Down
Expand Up @@ -4,6 +4,8 @@ SeleniumLibrary can be imported with several optional arguments.
Default value for `timeouts` used with ``Wait ...`` keywords.
- ``implicit_wait``:
Default value for `implicit wait` used when locating elements.
- ``action_chain_delay``:
Default value for `ActionChains` delay to wait in between actions.
- ``run_on_failure``:
Default action for the `run-on-failure functionality`.
- ``screenshot_root_directory``:
Expand Down
2 changes: 1 addition & 1 deletion utest/test/api/test_plugins.py
Expand Up @@ -22,7 +22,7 @@ def setUpClass(cls):
def test_no_libraries(self):
for item in [None, "None", ""]:
sl = SeleniumLibrary(plugins=item)
self.assertEqual(len(sl.get_keyword_names()), 173)
self.assertEqual(len(sl.get_keyword_names()), 175)

def test_parse_library(self):
plugin = "path.to.MyLibrary"
Expand Down
19 changes: 19 additions & 0 deletions utest/test/keywords/test_browsermanagement.py
Expand Up @@ -23,6 +23,25 @@ def test_set_selenium_timeout_only_affects_open_browsers():
verifyNoMoreInteractions(second_browser)


def test_action_chain_delay_default():
sl = SeleniumLibrary()
assert sl.action_chain_delay == 250, f"Delay should have 250"


def test_set_action_chain_delay_default():
sl = SeleniumLibrary()
sl.set_action_chain_delay("3.0")
assert sl.action_chain_delay == 3000, f"Delay should have 3000"

sl.set_action_chain_delay("258 milliseconds")
assert sl.action_chain_delay == 258, f"Delay should have 258"


def test_get_action_chain_delay_default():
sl = SeleniumLibrary()
sl.set_action_chain_delay("300 milliseconds")
assert sl.get_action_chain_delay() == 0.3

def test_selenium_implicit_wait_default():
sl = SeleniumLibrary()
assert sl.implicit_wait == 0.0, "Wait should have 0.0"
Expand Down
22 changes: 20 additions & 2 deletions utest/test/keywords/test_keyword_arguments_element.py
@@ -1,13 +1,14 @@
import pytest
from mockito import mock, unstub, when

from mockito import mock, unstub, when, matchers
from SeleniumLibrary.keywords import ElementKeywords
import SeleniumLibrary.keywords.element as SUT


@pytest.fixture(scope="function")
def element():
ctx = mock()
ctx._browser = mock()
ctx.action_chain_delay = 251
return ElementKeywords(ctx)


Expand All @@ -27,3 +28,20 @@ def test_element_text_should_be(element):
with pytest.raises(AssertionError) as error:
element.element_text_should_be(locator, "not text", "foobar")
assert "foobar" in str(error.value)



def test_action_chain_delay_in_elements(element):
locator = "//div"
webelement = mock()
when(element).find_element(locator).thenReturn(webelement)

chain_mock = mock()
expected_delay_in_ms = 1000
element.ctx.action_chain_delay = expected_delay_in_ms
when(chain_mock).move_to_element(matchers.ANY).thenReturn(mock())
when(SUT).ActionChains(matchers.ANY, duration=expected_delay_in_ms).thenReturn(chain_mock)
element.scroll_element_into_view(locator)



0 comments on commit a57a0d3

Please sign in to comment.