diff --git a/src/SeleniumLibrary/__init__.py b/src/SeleniumLibrary/__init__.py index 2a469a222..041503358 100644 --- a/src/SeleniumLibrary/__init__.py +++ b/src/SeleniumLibrary/__init__.py @@ -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" @@ -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, @@ -442,6 +443,9 @@ 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` deley to wait inbetween actions. + - ``run_on_failure``: Default action for the `run-on-failure functionality`. - ``screenshot_root_directory``: @@ -456,6 +460,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 diff --git a/src/SeleniumLibrary/__init__.pyi b/src/SeleniumLibrary/__init__.pyi index 170390580..cf36fe8c5 100644 --- a/src/SeleniumLibrary/__init__.pyi +++ b/src/SeleniumLibrary/__init__.pyi @@ -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): ... diff --git a/src/SeleniumLibrary/keywords/browsermanagement.py b/src/SeleniumLibrary/keywords/browsermanagement.py index f52ed23c4..095a17af9 100644 --- a/src/SeleniumLibrary/keywords/browsermanagement.py +++ b/src/SeleniumLibrary/keywords/browsermanagement.py @@ -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 @@ -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. diff --git a/src/SeleniumLibrary/keywords/element.py b/src/SeleniumLibrary/keywords/element.py index 51e3710cc..e799419ad 100644 --- a/src/SeleniumLibrary/keywords/element.py +++ b/src/SeleniumLibrary/keywords/element.py @@ -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) @@ -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) @@ -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() @@ -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 @@ -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( @@ -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 @@ -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() @@ -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 @@ -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() @@ -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 @@ -856,7 +856,7 @@ 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]): @@ -864,7 +864,7 @@ def open_context_menu(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.context_click(element).perform() @keyword @@ -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) @@ -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 @@ -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 diff --git a/src/SeleniumLibrary/utils/__init__.py b/src/SeleniumLibrary/utils/__init__.py index b03074682..ccc4df2c6 100644 --- a/src/SeleniumLibrary/utils/__init__.py +++ b/src/SeleniumLibrary/utils/__init__.py @@ -24,6 +24,7 @@ is_truthy, WINDOWS, _convert_timeout, + _convert_delay, ) # noqa diff --git a/src/SeleniumLibrary/utils/types.py b/src/SeleniumLibrary/utils/types.py index 4f000f44e..82a94ada5 100644 --- a/src/SeleniumLibrary/utils/types.py +++ b/src/SeleniumLibrary/utils/types.py @@ -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): diff --git a/utest/test/keywords/test_browsermanagement.py b/utest/test/keywords/test_browsermanagement.py index f3e67b576..33bb9c17f 100644 --- a/utest/test/keywords/test_browsermanagement.py +++ b/utest/test/keywords/test_browsermanagement.py @@ -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"